Android Trick - AutoCompleteTextView & MultiAutoCompleteTextView & Spannable & EditText

Story came from I tried implement an EditText View which supports "Mention feature" like Facebook and Twitter.

After user type in "@" symbol there is a dropdown list come out and let user select some item to replace that word started with "@".

The first I try to use EditText and popup window to customize this component. Which I need to listen EditText text change and using popup window to show the dropdown list with suggestions. But when I tried to handle the focus between EditText and PopupWindow I gave up this way . Could not find better solution for that means when EditText get focus all the time my dropdown list can not be clicked . Because popup window needs focusable to make views inside can be clicked. But if I give popup window focus user can not type in anymore.

Then I realized how stupid I worked. There supposed to be a solution from Android . Coz Contacts app support autocompletion. Then I found AutoCompleteTextView . Followed document I implemented this view which can be used for suggestion of mention users. But after clicked list item whole text of EditText will be replaced. Then I went through the codes of AutoCompleteTextView.

     private void performCompletion(View selectedView, int position, long id) {
        if (isPopupShowing()) {
            ...

            mBlockCompletion = true;
            replaceText(convertSelectionToString(selectedItem));
            mBlockCompletion = false;

            if (mItemClickListener != null) {

                ...
            }
        }

        if (mDropDownDismissedOnCompletion 

               && !mPopup.isDropDownAlwaysVisible()) {
            dismissDropDown();
        }
    }

     /**
     * <p>Performs the text completion by replacing the current text by the
     * selected item. Subclasses should override this method to avoid replacing
     * the whole content of the edit box.</p>
     *
     * @param text the selected suggestion in the drop down list
     */

     protected void replaceText(CharSequence text) {
        clearComposingText();

        setText(text);
        // make sure we keep the caret at the end of the text view
        Editable spannable = getText();
        Selection.setSelection(spannable, spannable.length());
    }
From comments we can get that we need to override this replaceText method to avoid replace the whole text . 

When I was trying to do that I found another interesting Class MultiAutoCompleteTextView.
It extends from AutoCompleteTextView but support multiple token auto complete feature.Then go through its codes.

So inside of MultiAutoCompleteTextView the key is Tokenizer which we need to implement our own which can meet demands . There is one simple one inside of this java file CommaTokenizer. I just changed a little in findTokenStart

public int findTokenStart(CharSequence text, int cursor) {
      int i = cursor;
      while (i > 0
            &&text.charAt(i - 1) != ','
            && text.charAt(i - 1) != ' ') {
            i--;
            if(text.charAt(i) == '@'){
                  break;
            }
      }
      return i;
}
This means I only need the last "@XX" as filter keyword.
And also remove adding "," in function terminateToken   Which means I don't want to add comma at the end when replacing the text.

Then multi auto complete was ok . 

There was another issue is the Text shown is not same as the text we need to post to server. We have a specified format for mention part. So how to storage mention information inside of different tokens of that text.

Then I chose to use spannable. There are many official span from SDK. What I did is extends one span from official one and storage the mention information inside of that object. Besides span can help us to style that part of specific text, like highlight , underline, changing font, etc. 

public class MentionSpan extends ForegroundColorSpan{
    public MentionSpan(int color) {
        super(color);
    }
    public YZMentioned mention;
    public MentionSpan(YZMentioned mention) {
        super(Color.parseColor("#33b5e5"));
        this.mention = mention;
    }
}


And then I implemented a separate function inside of that Component to get the formatted string:

public Editable getFormatedText() {
    Editable s =  super.getText();
    MentionSpan[] ms = ((Spanned) s).getSpans(0, s.length(),
    MentionSpan.class);
    if (ms != null && ms.length > 0) {
        for (MentionSpan m : ms) {
            int start = s.getSpanStart(m);
            int end = s.getSpanEnd(m);
            s.replace(start, end, m.mention.getFormatedString());
        }
    }
    return s;
}


Then this feature was realized. 
There was one trick did not noticed before. Spannable can also get all the spans informations using 

((Spanned) s).getSpans(0, s.length(), MentionSpan.class);

for each span we can get the start and end position also. We can even remove span from Editable. Then we can do whatever we want to do to customize it .


Comments

Post a Comment

Popular posts from this blog

A wired issue of MediaPlayer on Android

Problem when using Jackson json parser in Android.

Gradle issue - peer not authenticated