Android Open Source - android-bubble-text Chips Edit Text






From Project

Back to project page android-bubble-text.

License

The source code is released under:

MIT License

If you think the Android project android-bubble-text listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.oliveira.bubble;
//from   w ww.j av a 2  s  .  co m
import android.content.Context;
import android.graphics.*;
import android.text.*;
import android.text.style.ReplacementSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;

import java.util.ArrayList;

@SuppressWarnings("unused")
public class ChipsEditText extends MultilineEditText {

    private ArrayList<AutoCompletePopover.Entity> availableItems =
            new ArrayList<AutoCompletePopover.Entity>();

    private ArrayList<AutoCompletePopover.Entity> filteredItems =
            new ArrayList<AutoCompletePopover.Entity>();

    private AutoCompletePopover popover;
    private AutoCompleteManager manager;
    private BubbleStyle currentStyle;
    private String triggerChar = "#";

    private boolean autoShow;
    private int maxBubbleCount = -1;

    public CharSequence savedHint;
    protected EditAction lastEditAction;

    public ChipsEditText(Context context) {
        super(context);
        init();
    }

    public ChipsEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ChipsEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    void init() {

        manager = new AutoCompleteManager();
        manager.setResolver(new AutoCompleteManager.Resolver() {

            @Override
            public ArrayList<AutoCompletePopover.Entity> getSuggestions(String query) throws Exception {
                if (resolver == null)
                    return null;
                return resolver.getSuggestions(query);
            }

            @Override
            public ArrayList<AutoCompletePopover.Entity> getDefaultSuggestions() {
                return resolver.getDefaultSuggestions();
            }

            @Override
            public void update(String query, ArrayList<AutoCompletePopover.Entity> results) {
                setAvailableItems(results);
            }
        });

        addTextChangedListener(hashWatcher);
        addTextChangedListener(autocompleteWatcher);
        setOnEditorActionListener(editorActionListener);

        setCursorVisible(false);
        float width = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 1.5f,
                getContext().getResources().getDisplayMetrics());

        this.cursorDrawable = new CursorDrawable(this, getTextSize()*1.5f, width, getContext());
        this.savedHint = getHint();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        post(cursorRunnable);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(cursorRunnable);
    }

    Runnable cursorRunnable = new Runnable() {
        @Override
        public void run() {
            cursorBlink = !cursorBlink;
            postInvalidate();
            postDelayed(cursorRunnable, 500);
        }
    };

    public BubbleStyle getCurrentStyle() {
        return currentStyle;
    }

    public void setCurrentStyle(BubbleStyle currentStyle) {
        this.currentStyle = currentStyle;
    }

    boolean cursorBlink;
    CursorDrawable cursorDrawable;

    @Override
    public boolean onPreDraw() {

        CharSequence hint = getHint();
        boolean empty = TextUtils.isEmpty(getText());

        if (manualModeOn && empty) {
            if (!TextUtils.isEmpty(hint)) {
                setHint("");
            }
        } else if (!manualModeOn && empty && !TextUtils.isEmpty(savedHint)) {
            setHint(savedHint);
        }

        return super.onPreDraw();
    }

    @SuppressWarnings("NullableProblems")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isFocused()) {
            cursorDrawable.draw(canvas, cursorBlink);
        }
    }

    public void resetAutocompleList() {
        lastEditAction = null;
        manager.search("");
    }

    public String getTriggerChar() {
        return triggerChar;
    }

    public void setTriggerChar(String triggerChar) {
        this.triggerChar = triggerChar;
    }

    public void setAutocomplePopover(AutoCompletePopover popover) {
        this.popover = popover;
    }

    public void addBubble(String text, int start, Object data) {

        if (start > getText().length()) {
            start = getText().length();
        }

        getText().insert(start, text);
        makeChip(start, start+text.length(), true, data);
        onBubbleCountChanged();
    }

    boolean finalizing;

    public void makeChip(int start, int end, boolean finalize, Object data) {

        if (finalizing) {
            return;
        }

        int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        String finalText = null;
        if (finalize) {

            finalizing = true;
            try {
                getText().insert(start, " ");
                getText().insert(end + 1, " ");
                end += 2;
                finalText = getText().subSequence(start + 1, end - 1).toString();
            } catch (java.lang.IndexOutOfBoundsException e) {
                finalizing = false;
                return;
            }
        }

        int textSize = (int)(getTextSize() - TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 1,
                getContext().getResources().getDisplayMetrics()));

        BubbleStyle bubbleStyle = DefaultBubbles.get(DefaultBubbles.GREEN, getContext(), textSize);
        if (currentStyle != null) {
            bubbleStyle = currentStyle;
        }

        Utils.bubblify(getText(),
                finalText,
                start,
                end,
                maxWidth,
                bubbleStyle,
                this,
                data);

        finalizing = false;
    }

    boolean manualModeOn;
    int manualStart;

    public void setMaxBubbleCount(int maxBubbleCount) {
        this.maxBubbleCount = maxBubbleCount;
    }

    public boolean canAddMoreBubbles() {
        return maxBubbleCount == -1 || getBubbleCount() < maxBubbleCount;
    }

    public int getBubbleCount() {
        try {
            return getText().getSpans(0, getText().length(), BubbleSpan.class).length;
        } catch (Exception e) {
            return 0;
        }
    }

    public BubbleSpanImpl[] getBubbleList() {
        return getText().getSpans(0, getText().length(), BubbleSpanImpl.class);
    }

    public void startManualMode() {

        resetAutocompleList();
        if (!canAddMoreBubbles()) {
            return;
        }

        int i = getSelectionStart() - 1;
        if (i >= 0 && (!Character.isWhitespace(getText().charAt(i)) || hasBubbleAt(i))) {
            getText().insert(i+1, " ");
        }

        lastEditAction = null;
        manualModeOn = true;
        manualStart = getSelectionStart();
    }

    public boolean hasBubbleAt(int position) {
        return getText().getSpans(position, position+1, BubbleSpanImpl.class).length > 0;
    }

    public void endManualMode() {

        boolean madeChip = false;
        if (manualStart < getSelectionEnd() && manualModeOn) {
            makeChip(manualStart, getSelectionEnd(), true, null);
            madeChip = true;
            onBubbleCountChanged();
        }

        manualModeOn = false;
        popover.hide();

        if (madeChip && getSelectionEnd() == getText().length()) {
            getText().append(" ");
            setSelection(getText().length());
        }
    }

    public void cancelManualMode() {
        if (manualStart < getSelectionEnd() && manualModeOn) {
            getText().delete(manualStart, getSelectionEnd());
        }
        manualModeOn = false;
        popover.hide();
    }

    TextWatcher autocompleteWatcher = new TextWatcher() {
        ReplacementSpan manipulatedSpan;

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            manipulatedSpan = null;
            if (after < count && !manualModeOn) {
                ReplacementSpan[] spans = ((Spannable)s).getSpans(start, start+count, ReplacementSpan.class);
                if (spans.length == 1) {
                    manipulatedSpan = spans[0];
                } else {
                    manipulatedSpan = null;
                }
            }
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

            if (!shouldShow()) {
                return;
            }

            String textForAutocomplete;
            try {

                if (manualModeOn && manualStart < start) {
                    count += start - manualStart;
                    start = manualStart;
                }

                textForAutocomplete = s.toString().substring(start, start+count);
                if (resolver != null)
                    manager.search(textForAutocomplete);
                if (!TextUtils.isEmpty(textForAutocomplete)) {
                    showAutocomplete(new EditAction(textForAutocomplete, start, before, count));
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void afterTextChanged(Editable s) {

            if (manualModeOn) {

                int end = getSelectionStart();
                if (end < manualStart) {
                    manualModeOn = false;
                } else {
                    makeChip(manualStart, end, false, null);
                }

            } else if (manipulatedSpan != null) {

                int start = s.getSpanStart(manipulatedSpan);
                int end = s.getSpanEnd(manipulatedSpan);
                if (start > -1 && end > -1) {
                    s.delete(start, end);
                }

                onBubbleCountChanged();
                manipulatedSpan = null;
                manualModeOn = false;
            }

            if (manualModeOn) {
                popover.reposition();
            }
            else {
                popover.hide();
            }
        }
    };

    protected void setAvailableItems(ArrayList<AutoCompletePopover.Entity> items) {
        popover.scrollToTop();
        availableItems = items;
        filter();
    }

    private void removeByText(String text) {

        AutoCompletePopover.Entity toRemove = null;
        for (AutoCompletePopover.Entity item : availableItems) {
            if (text.equals(item.label)) {
                toRemove = item;
                break;
            }
        }

        if (toRemove != null) {
            availableItems.remove(toRemove);
        }
    }

    private void filter() {

        ArrayList<AutoCompletePopover.Entity> availableItems =
                new ArrayList<AutoCompletePopover.Entity>();

        if (this.availableItems != null) {
            for (AutoCompletePopover.Entity item : this.availableItems) {
                item.label = item.label.trim();
                availableItems.add(item);
            }
        }

        if (availableItems.size() > 0) {
            BubbleSpan[] spans = getText().getSpans(0, getText().length(), BubbleSpan.class);
            for (BubbleSpan span : spans) {

                int start = getText().getSpanStart(span);
                int end = getText().getSpanEnd(span);

                if (start == -1 || end == -1 || end <= start || (manualStart == start && manualModeOn)) {
                    continue;
                }

                String text = getText().subSequence(start, end).toString().trim();
                removeByText(text);
            }
        }

        filteredItems.clear();
        if (lastEditAction != null) {
            String text = lastEditAction.text.toLowerCase();
            if (!TextUtils.isEmpty(text))
                for (AutoCompletePopover.Entity item : availableItems) {
                    if ((text.length() > 1 && item.label.toLowerCase().startsWith(text))
                            || (manualModeOn && item.label.toLowerCase().contains(text) && text.length() > 3)) {
                        filteredItems.add(item);
                    }
                }
        }

        if (filteredItems.size() > 0) {
            popover.setItems(filteredItems);
            if (shouldShow()) {
                popover.show();
            }
        } else {
            if (!manualModeOn) {
                popover.hide();
            }
            popover.setItems(availableItems);
        }
    }

    private boolean shouldShow() {
        return autoShow || manualModeOn;
    }

    public void showAutocomplete(EditAction editAction) {
        lastEditAction = editAction;
        filter();
    }

    TextView.OnEditorActionListener editorActionListener = new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {

            if (keyEvent == null) {
                if (actionId == EditorInfo.IME_ACTION_UNSPECIFIED)
                    actionId = EditorInfo.IME_ACTION_DONE;
                if (actionId == EditorInfo.IME_ACTION_DONE && manualModeOn) {
                    cancelManualMode();
                    onActionDone();
                    return true;
                } else if (actionId == EditorInfo.IME_ACTION_DONE) {
                    hideKeyboard();
                    onActionDone();
                    return true;
                }
            } else if (actionId == EditorInfo.IME_ACTION_UNSPECIFIED) {
                return true;
            }

            return false;
        }
    };

    public void hideKeyboard() {
        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getWindowToken(), 0);
        popover.hide();
    }

    public void showKeyboard() {
        InputMethodManager inputMgr = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMgr.showSoftInput(this, InputMethodManager.SHOW_FORCED);
    }

    public Point getInnerCursorPosition() {
        int pos = getSelectionStart();
        Layout layout = getLayout();
        if (layout == null) {
            return new Point(0, 0);
        }
        int line = layout.getLineForOffset(pos);
        int baseline = layout.getLineBaseline(line);
        int ascent = layout.getLineAscent(line);
        float x = layout.getPrimaryHorizontal(pos);
        float y = baseline + ascent;
        return new Point((int)x, (int)y);
    }

    public Point getCursorPosition() {
        Point p = getInnerCursorPosition();
        p.offset(getPaddingLeft(), getPaddingTop());
        return p;
    }

    public static class EditAction {
        String text;
        int start;
        int before;
        int count;
        int end() {
            return start + count;
        }

        public EditAction(String text, int start, int before, int count) {
            this.text = text;
            this.start = start;
            this.before = before;
            this.count = count;
        }
    }

    public void setAutocompleteResolver(AutocompleteResolver resolver) {
        this.resolver = resolver;
    }

    private AutocompleteResolver resolver;

    public interface AutocompleteResolver {
        public ArrayList<AutoCompletePopover.Entity> getSuggestions(String query) throws Exception;
        public ArrayList<AutoCompletePopover.Entity> getDefaultSuggestions();
    }

    boolean muteHashWatcher;
    protected void muteHashWatcher(boolean value) {
        muteHashWatcher = value;
    }

    private TextWatcher hashWatcher = new TextWatcher() {
        String before;
        String after;

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            if (muteHashWatcher)
                return;
            before = s.toString();
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (muteHashWatcher)
                return;
            after = s.toString();
        }

        @Override
        public void afterTextChanged(Editable s) {
            if (muteHashWatcher)
                return;
            if (after.length() > before.length() && after.lastIndexOf(triggerChar) > before.lastIndexOf(triggerChar)) {
                int lastIndex = after.lastIndexOf(triggerChar);
                if (manualModeOn || canAddMoreBubbles())
                    s.delete(lastIndex, lastIndex + 1);
                if (!manualModeOn) {
                    startManualMode();
                    popover.show();
                } else if (manualModeOn && manualStart < lastIndex) {
                    endManualMode();
                    if (canAddMoreBubbles()) {
                        startManualMode();
                    }
                }
            }
        }
    };

    private int previousWidth = 0;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (previousWidth != widthSize) {

            previousWidth = widthSize;
            int maxBubbleWidth = widthSize - getPaddingLeft() - getPaddingTop();

            Editable e = getText();
            BubbleSpan[] spans = e.getSpans(0, getText().length(), BubbleSpan.class);

            for (BubbleSpan span : spans) {
                span.resetWidth(maxBubbleWidth);
                int start = getText().getSpanStart(span);
                int end = getText().getSpanEnd(span);
                e.removeSpan(span);
                e.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private ArrayList<Listener> listeners = new ArrayList<Listener>();

    protected void onBubbleCountChanged() {
        for (Listener listener : listeners) {
            listener.onBubbleCountChanged();
        }
    }

    protected void onActionDone() {
        for (Listener listener : listeners) {
            listener.onActionDone();
        }
    }

    protected void onBubbleSelected(int position) {
        for (Listener listener : listeners) {
            listener.onBubbleSelected(position);
        }
    }

    protected void onXPressed() {
        for (Listener listener : listeners) {
            listener.onXPressed();
        }
    }

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public interface Listener {
        public void onBubbleCountChanged();
        public void onActionDone();
        public void onBubbleSelected(int position);
        public void onXPressed();
    }

    public CursorDrawable getCursorDrawable() {
        return this.cursorDrawable;
    }
}




Java Source Code List

com.asolutions.widget.RowLayout.java
com.oliveira.bubble.AutoCompleteManager.java
com.oliveira.bubble.AutoCompletePopover.java
com.oliveira.bubble.AwesomeBubble.java
com.oliveira.bubble.BubbleSpanImpl.java
com.oliveira.bubble.BubbleSpan.java
com.oliveira.bubble.BubbleStyle.java
com.oliveira.bubble.ChipsEditText.java
com.oliveira.bubble.ChipsTextView.java
com.oliveira.bubble.CursorDrawable.java
com.oliveira.bubble.DefaultBubbles.java
com.oliveira.bubble.ILayoutCallback.java
com.oliveira.bubble.Linkify.java
com.oliveira.bubble.MultilineEditText.java
com.oliveira.bubble.Regex.java
com.oliveira.bubble.TappableSpan.java
com.oliveira.bubble.Utils.java