From 5d37bc82f4ed570ac13f38715c9c8a3f5b075c41 Mon Sep 17 00:00:00 2001 From: "ouerghemi.houssein" Date: Mon, 21 Dec 2015 09:35:41 +0100 Subject: [PATCH 1/2] add autocomplete functionality --- .../taggroup/demo/TagEditorActivity.java | 39 +++ .../android/taggroup/demo/db/TagsManager.java | 19 ++ demo/src/main/res/layout/autocomplete_row.xml | 12 + .../me/gujun/android/taggroup/TagGroup.java | 223 +++++++++++++--- library/src/main/res/values/countries.xml | 246 ++++++++++++++++++ 5 files changed, 501 insertions(+), 38 deletions(-) create mode 100644 demo/src/main/res/layout/autocomplete_row.xml create mode 100644 library/src/main/res/values/countries.xml diff --git a/demo/src/main/java/me/gujun/android/taggroup/demo/TagEditorActivity.java b/demo/src/main/java/me/gujun/android/taggroup/demo/TagEditorActivity.java index 27d2ea1..3f198e4 100644 --- a/demo/src/main/java/me/gujun/android/taggroup/demo/TagEditorActivity.java +++ b/demo/src/main/java/me/gujun/android/taggroup/demo/TagEditorActivity.java @@ -1,9 +1,12 @@ package me.gujun.android.taggroup.demo; +import android.content.Context; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; +import android.widget.ArrayAdapter; +import android.widget.Filter; import me.gujun.android.taggroup.TagGroup; import me.gujun.android.taggroup.demo.db.TagsManager; @@ -22,6 +25,7 @@ protected void onCreate(Bundle savedInstanceState) { String[] tags = mTagsManager.getTags(); mTagGroup = (TagGroup) findViewById(R.id.tag_group); + mTagGroup.setAutoCompleteAdapter(new TagAutoCompleteAdapter(this)); mTagGroup.setTags(tags); } @@ -49,4 +53,39 @@ public void onBackPressed() { mTagsManager.updateTags(mTagGroup.getTags()); super.onBackPressed(); } + + private class TagAutoCompleteAdapter extends ArrayAdapter { + public TagAutoCompleteAdapter(Context context) { + super(context, R.layout.autocomplete_row); + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected Filter.FilterResults performFiltering(CharSequence constraint) { + Filter.FilterResults result = new Filter.FilterResults(); + result.count = 0; + if(constraint!=null) { + String[] data = mTagsManager.getAutoCompleteData(TagEditorActivity.this, constraint.toString()); + result.values = data; + result.count = data.length; + } + return result; + } + + @Override + protected void publishResults(CharSequence constraint, Filter.FilterResults results) { + clear(); + if (results.count > 0) { + for (String s : (String[]) results.values) { + add(s); + } + } + notifyDataSetChanged(); + } + }; + } + + } } \ No newline at end of file diff --git a/demo/src/main/java/me/gujun/android/taggroup/demo/db/TagsManager.java b/demo/src/main/java/me/gujun/android/taggroup/demo/db/TagsManager.java index 841fc28..3d832c6 100644 --- a/demo/src/main/java/me/gujun/android/taggroup/demo/db/TagsManager.java +++ b/demo/src/main/java/me/gujun/android/taggroup/demo/db/TagsManager.java @@ -4,9 +4,15 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Random; + +import me.gujun.android.taggroup.demo.R; /** * Manage the tags from SQLite database. @@ -65,4 +71,17 @@ public void clearTags() { db.delete(TagsTable.TABLE_NAME, null, null); db.close(); } + public String[] getAutoCompleteData(Context context,String _query) { + //Return countries + String[] data = context.getResources().getStringArray(R.array.countries_array); + String query = _query.toLowerCase(); + ArrayList filtredData = new ArrayList<>(); + + for (String d : data) { + if(d.toLowerCase().startsWith(query)) + filtredData.add(d); + } + + return filtredData.toArray(new String[filtredData.size()]); + } } \ No newline at end of file diff --git a/demo/src/main/res/layout/autocomplete_row.xml b/demo/src/main/res/layout/autocomplete_row.xml new file mode 100644 index 0000000..292b082 --- /dev/null +++ b/demo/src/main/res/layout/autocomplete_row.xml @@ -0,0 +1,12 @@ + + diff --git a/library/src/main/java/me/gujun/android/taggroup/TagGroup.java b/library/src/main/java/me/gujun/android/taggroup/TagGroup.java index 5f2ae99..e125ec1 100644 --- a/library/src/main/java/me/gujun/android/taggroup/TagGroup.java +++ b/library/src/main/java/me/gujun/android/taggroup/TagGroup.java @@ -13,6 +13,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.Editable; +import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.ArrowKeyMovementMethod; @@ -26,6 +27,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; +import android.widget.ArrayAdapter; +import android.widget.MultiAutoCompleteTextView; import android.widget.TextView; import java.util.ArrayList; @@ -68,71 +71,123 @@ public class TagGroup extends ViewGroup { private final float default_horizontal_padding; private final float default_vertical_padding; - /** Indicates whether this TagGroup is set up to APPEND mode or DISPLAY mode. Default is false. */ + /** + * Indicates whether this TagGroup is set up to APPEND mode or DISPLAY mode. Default is false. + */ private boolean isAppendMode; - /** The text to be displayed when the text of the INPUT tag is empty. */ + /** + * The text to be displayed when the text of the INPUT tag is empty. + */ private CharSequence inputHint; - /** The tag outline border color. */ + /** + * The tag outline border color. + */ private int borderColor; - /** The tag text color. */ + /** + * The tag text color. + */ private int textColor; - /** The tag background color. */ + /** + * The tag background color. + */ private int backgroundColor; - /** The dash outline border color. */ + /** + * The dash outline border color. + */ private int dashBorderColor; - /** The input tag hint text color. */ + /** + * The input tag hint text color. + */ private int inputHintColor; - /** The input tag type text color. */ + /** + * The input tag type text color. + */ private int inputTextColor; - /** The checked tag outline border color. */ + /** + * The checked tag outline border color. + */ private int checkedBorderColor; - /** The check text color */ + /** + * The check text color + */ private int checkedTextColor; - /** The checked marker color. */ + /** + * The checked marker color. + */ private int checkedMarkerColor; - /** The checked tag background color. */ + /** + * The checked tag background color. + */ private int checkedBackgroundColor; - /** The tag background color, when the tag is being pressed. */ + /** + * The tag background color, when the tag is being pressed. + */ private int pressedBackgroundColor; - /** The tag outline border stroke width, default is 0.5dp. */ + /** + * The tag outline border stroke width, default is 0.5dp. + */ private float borderStrokeWidth; - /** The tag text size, default is 13sp. */ + /** + * The tag text size, default is 13sp. + */ private float textSize; - /** The horizontal tag spacing, default is 8.0dp. */ + /** + * The horizontal tag spacing, default is 8.0dp. + */ private int horizontalSpacing; - /** The vertical tag spacing, default is 4.0dp. */ + /** + * The vertical tag spacing, default is 4.0dp. + */ private int verticalSpacing; - /** The horizontal tag padding, default is 12.0dp. */ + /** + * The horizontal tag padding, default is 12.0dp. + */ private int horizontalPadding; - /** The vertical tag padding, default is 3.0dp. */ + /** + * The vertical tag padding, default is 3.0dp. + */ private int verticalPadding; - /** Listener used to dispatch tag change event. */ + /** + * Listener used to dispatch tag change event. + */ private OnTagChangeListener mOnTagChangeListener; - /** Listener used to dispatch tag click event. */ + /** + * Listener used to dispatch tag click event. + */ private OnTagClickListener mOnTagClickListener; - /** Listener used to handle tag click event. */ + /** + * Listener used to handle tag click event. + */ private InternalTagClickListener mInternalTagClickListener = new InternalTagClickListener(); + /** + * if without space, add tag when space char is typed + */ + private boolean tagsWithoutSpace = true; + /** + * The auto complete list adapter + */ + private ArrayAdapter autoCompleteAdapter = null; public TagGroup(Context context) { this(context, null); @@ -191,6 +246,32 @@ public void onClick(View v) { } } + public boolean isTagsWithoutSpace() { + return tagsWithoutSpace; + } + + /** + * set tags without space + * if true, the tag is add when space is typed + * @param tagsWithoutSpace + */ + public void setTagsWithoutSpace(boolean tagsWithoutSpace) { + this.tagsWithoutSpace = tagsWithoutSpace; + } + + public ArrayAdapter getAutoCompleteAdapter() { + return autoCompleteAdapter; + } + + /** + * set the auto complete adapter + * this adapter is necessary when you want to autocomplete the tag's input + * @param autoCompleteAdapter + */ + public void setAutoCompleteAdapter(ArrayAdapter autoCompleteAdapter) { + this.autoCompleteAdapter = autoCompleteAdapter; + } + /** * Call this to submit the INPUT tag. */ @@ -477,6 +558,7 @@ protected void appendInputTag(String tag) { final TagView newInputTag = new TagView(getContext(), TagView.STATE_INPUT, tag); newInputTag.setOnClickListener(mInternalTagClickListener); + addView(newInputTag); } @@ -648,23 +730,33 @@ public void onClick(View v) { /** * The tag view which has two states can be either NORMAL or INPUT. */ - class TagView extends TextView { + class TagView extends MultiAutoCompleteTextView { public static final int STATE_NORMAL = 1; public static final int STATE_INPUT = 2; - /** The offset to the text. */ + /** + * The offset to the text. + */ private static final int CHECKED_MARKER_OFFSET = 3; - /** The stroke width of the checked marker */ + /** + * The stroke width of the checked marker + */ private static final int CHECKED_MARKER_STROKE_WIDTH = 4; - /** The current state. */ + /** + * The current state. + */ private int mState; - /** Indicates the tag if checked. */ + /** + * Indicates the tag if checked. + */ private boolean isChecked = false; - /** Indicates the tag if pressed. */ + /** + * Indicates the tag if pressed. + */ private boolean isPressed = false; private Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -673,28 +765,44 @@ class TagView extends TextView { private Paint mCheckedMarkerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - /** The rect for the tag's left corner drawing. */ + /** + * The rect for the tag's left corner drawing. + */ private RectF mLeftCornerRectF = new RectF(); - /** The rect for the tag's right corner drawing. */ + /** + * The rect for the tag's right corner drawing. + */ private RectF mRightCornerRectF = new RectF(); - /** The rect for the tag's horizontal blank fill area. */ + /** + * The rect for the tag's horizontal blank fill area. + */ private RectF mHorizontalBlankFillRectF = new RectF(); - /** The rect for the tag's vertical blank fill area. */ + /** + * The rect for the tag's vertical blank fill area. + */ private RectF mVerticalBlankFillRectF = new RectF(); - /** The rect for the checked mark draw bound. */ + /** + * The rect for the checked mark draw bound. + */ private RectF mCheckedMarkerBound = new RectF(); - /** Used to detect the touch event. */ + /** + * Used to detect the touch event. + */ private Rect mOutRect = new Rect(); - /** The path for draw the tag's outline border. */ + /** + * The path for draw the tag's outline border. + */ private Path mBorderPath = new Path(); - /** The path effect provide draw the dash border. */ + /** + * The path effect provide draw the dash border. + */ private PathEffect mPathEffect = new DashPathEffect(new float[]{10, 5}, 0); { @@ -707,7 +815,7 @@ class TagView extends TextView { } - public TagView(Context context, final int state, CharSequence text) { + public TagView(final Context context, final int state, CharSequence text) { super(context); setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); setLayoutParams(new TagGroup.LayoutParams( @@ -804,12 +912,51 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override - public void afterTextChanged(Editable s) { + public synchronized void afterTextChanged(Editable s) { + if (tagsWithoutSpace) { + String trimmed = s.toString().trim(); + if (trimmed.length() >= 1 && s.toString().endsWith(" ")) { + submitTag(); + } else { + String[] spliteds = trimmed.split(" "); + if (spliteds.length >= 2) { + for (int i = 0; i < spliteds.length; i++) { + String t = spliteds[i]; + if (TextUtils.isEmpty(t.trim())) + continue; + if (i == 0) {//this first is current tag + if (mOnTagChangeListener != null) { + mOnTagChangeListener.onAppend(TagGroup.this, t); + } + //setText(t); + s.clear(); + s.append(t); + endInput(); + } else if (i == spliteds.length - 1) {//the new current tag is the str after the last space + appendInputTag(t); + } else { + appendTag(t); + if (mOnTagChangeListener != null) { + mOnTagChangeListener.onAppend(TagGroup.this, t); + } + } + } + + } + } + + } } }); } invalidatePaint(); + + setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + setTokenizer(new SpaceTokenizer()); + setBackgroundDrawable(null); + if(autoCompleteAdapter!=null) + this.setAdapter(autoCompleteAdapter); } /** diff --git a/library/src/main/res/values/countries.xml b/library/src/main/res/values/countries.xml new file mode 100644 index 0000000..eb512be --- /dev/null +++ b/library/src/main/res/values/countries.xml @@ -0,0 +1,246 @@ + + + + Afghanistan + Albania + Algeria + American Samoa + Andorra + Angola + Anguilla + Antarctica + Antigua and Barbuda + Argentina + Armenia + Aruba + Australia + Austria + Azerbaijan + Bahrain + Bangladesh + Barbados + Belarus + Belgium + Belize + Benin + Bermuda + Bhutan + Bolivia + Bosnia and Herzegovina + Botswana + Bouvet Island + Brazil + British Indian Ocean Territory + British Virgin Islands + Brunei + Bulgaria + Burkina Faso + Burundi + Cambodia + Cameroon + Canada + Cape Verde + Cayman Islands + Central African Republic + Chad + Chile + China + Christmas Island + Cocos (Keeling) Islands + Colombia + Comoros + Congo + Cook Islands + Costa Rica + Cote d\'Ivoire + Croatia + Cuba + Cyprus + Czech Republic + Democratic Republic of the Congo + Denmark + Djibouti + Dominica + Dominican Republic + East Timor + Ecuador + Egypt + El Salvador + Equatorial Guinea + Eritrea + Estonia + Ethiopia + Faeroe Islands + Falkland Islands + Fiji + Finland + Former Yugoslav Republic of Macedonia + France + French Guiana + French Polynesia + French Southern Territories + Gabon + Georgia + Germany + Ghana + Gibraltar + Greece + Greenland + Grenada + Guadeloupe + Guam + Guatemala + Guinea + Guinea-Bissau + Guyana + Haiti + Heard Island and McDonald Islands + Honduras + Hong Kong + Hungary + Iceland + India + Indonesia + Iran + Iraq + Ireland + Israel + Italy + Jamaica + Japan + Jordan + Kazakhstan + Kenya + Kiribati + Kuwait + Kyrgyzstan + Laos + Latvia + Lebanon + Lesotho + Liberia + Libya + Liechtenstein + Lithuania + Luxembourg + Macau + Madagascar + Malawi + Malaysia + Maldives + Mali + Malta + Marshall Islands + Martinique + Mauritania + Mauritius + Mayotte + Mexico + Micronesia + Moldova + Monaco + Mongolia + Montenegro + Montserrat + Morocco + Mozambique + Myanmar + Namibia + Nauru + Nepal + Netherlands + Netherlands Antilles + New Caledonia + New Zealand + Nicaragua + Niger + Nigeria + Niue + Norfolk Island + North Korea + Northern Marianas + Norway + Oman + Pakistan + Palau + Panama + Papua New Guinea + Paraguay + Peru + Philippines + Pitcairn Islands + Poland + Portugal + Puerto Rico + Qatar + Reunion + Romania + Russia + Rwanda + Sqo Tome and Principe + Saint Helena + Saint Kitts and Nevis + Saint Lucia + Saint Pierre and Miquelon + Saint Vincent and the Grenadines + Samoa + San Marino + Saudi Arabia + Senegal + Serbia + Seychelles + Sierra Leone + Singapore + Slovakia + Slovenia + Solomon Islands + Somalia + South Africa + South Georgia and the South Sandwich Islands + South Korea + South Sudan + Spain + Sri Lanka + Sudan + Suriname + Svalbard and Jan Mayen + Swaziland + Sweden + Switzerland + Syria + Taiwan + Tajikistan + Tanzania + Thailand + The Bahamas + The Gambia + Togo + Tokelau + Tonga + Trinidad and Tobago + Tunisia + Turkey + Turkmenistan + Turks and Caicos Islands + Tuvalu + Virgin Islands + Uganda + Ukraine + United Arab Emirates + United Kingdom + United States + United States Minor Outlying Islands + Uruguay + Uzbekistan + Vanuatu + Vatican City + Venezuela + Vietnam + Wallis and Futuna + Western Sahara + Yemen + Yugoslavia + Zambia + Zimbabwe + + \ No newline at end of file From d78203f8b7c15a58cda08023542ae5dbc2f5ff7f Mon Sep 17 00:00:00 2001 From: "ouerghemi.houssein" Date: Mon, 21 Dec 2015 10:01:08 +0100 Subject: [PATCH 2/2] add autocomplete functionality --- .../android/taggroup/SpaceTokenizer.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 library/src/main/java/me/gujun/android/taggroup/SpaceTokenizer.java diff --git a/library/src/main/java/me/gujun/android/taggroup/SpaceTokenizer.java b/library/src/main/java/me/gujun/android/taggroup/SpaceTokenizer.java new file mode 100644 index 0000000..76156a2 --- /dev/null +++ b/library/src/main/java/me/gujun/android/taggroup/SpaceTokenizer.java @@ -0,0 +1,61 @@ +package me.gujun.android.taggroup; + +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextUtils; +import android.widget.MultiAutoCompleteTextView; + +/** + * http://stackoverflow.com/questions/3482981/how-to-replace-the-comma-with-a-space-when-i-use-the-multiautocompletetextview + */ +public class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer { + + public int findTokenStart(CharSequence text, int cursor) { + int i = cursor; + + while (i > 0 && text.charAt(i - 1) != ' ') { + i--; + } + while (i < cursor && text.charAt(i) == ' ') { + i++; + } + + return i; + } + + public int findTokenEnd(CharSequence text, int cursor) { + int i = cursor; + int len = text.length(); + + while (i < len) { + if (text.charAt(i) == ' ') { + return i; + } else { + i++; + } + } + + return len; + } + + public CharSequence terminateToken(CharSequence text) { + int i = text.length(); + + while (i > 0 && text.charAt(i - 1) == ' ') { + i--; + } + + if (i > 0 && text.charAt(i - 1) == ' ') { + return text; + } else { + if (text instanceof Spanned) { + SpannableString sp = new SpannableString(text + " "); + TextUtils.copySpansFrom((Spanned) text, 0, text.length(), + Object.class, sp, 0); + return sp; + } else { + return text + " "; + } + } + } +} \ No newline at end of file