RecipientEditTextView代码分析

Android的通话,短信和联系人,每个模块都会有一个重量级的自定义控件,代码量是千行起,文件一般也有多个。通话是GlowPadView,短信就是RecipientEditTextView了,联系人的卖个关子以后会介绍。三个中最复杂的就是RecipientEditTextView,都单独编译成了一个静态库了,除了之前介绍的相关类外,RecipientEditTextView.java自身就有3000+的代码行数,mtk修改后的MtkRecipientEditTextView代码行数直逼7000行。控件使用起来并不难,不过有问题了要修改或者优化就很麻烦了。

本文从控件的各种操作角度入手来分析代码


RecipientTextWatcher

输入文字是最正常的操作,处理该操作的就是RecipientTextWatcher,该成员在构造方法中初始化。

        mTextWatcher = new RecipientTextWatcher();
        addTextChangedListener(mTextWatcher);

在某些方法中有处理字符串操作会先取消TextWatcher的监听,处理完毕后再恢复监听,mAddTextWatcher就是处理这个的。

    private final Runnable mAddTextWatcher = new Runnable() {
        @Override
        public void run() {
            if (mTextWatcher == null) {
                mTextWatcher = new RecipientTextWatcher();
                addTextChangedListener(mTextWatcher);
            }
        }
    };

onTextChanged

       @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (before - count == 1) {
                //按了删除键,如果之前是一个chip的话,删除整个chip
                int selStart = getSelectionStart();
                DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
                        DrawableRecipientChip.class);
                if (repl.length > 0) {
                    // There is a chip there! Just remove it.
                    DrawableRecipientChip toDelete = repl[0];
                    Editable editable = getText();
                    // Add the separator token.
                    int deleteStart = editable.getSpanStart(toDelete);
                    int deleteEnd = editable.getSpanEnd(toDelete) + 1;
                    if (deleteEnd > editable.length()) {
                        deleteEnd = editable.length();
                    }
                    editable.removeSpan(toDelete);
                    editable.delete(deleteStart, deleteEnd);
                }
            } else if (count > before) {
                //新添字符,当前所选chip不是联系人,且新增字符串的最后一位是分隔符,则新提交一个chip
                if (mSelectedChip != null
                    && isGeneratedContact(mSelectedChip)) {
                    if (lastCharacterIsCommitCharacter(s)) {
                        commitByCharacter();
                        return;
                    }
                }
            }
        }

afterTextChanged

        @Override
        public void afterTextChanged(Editable s) {
            // If the text has been set to null or empty, make sure we remove
            // all the spans we applied.
            if (TextUtils.isEmpty(s)) { //为空
                // Remove all the chips spans.
                Spannable spannable = getSpannable();
                DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
                        DrawableRecipientChip.class);
                for (DrawableRecipientChip chip : chips) {
                    spannable.removeSpan(chip); //删除所有的span
                }
                if (mMoreChip != null) {
                    spannable.removeSpan(mMoreChip); //删除mMoreChip
                }
                clearSelectedChip(); //清除mSelectedChip
                return;
            }
            // Get whether there are any recipients pending addition to the
            // view. If there are, don't do anything in the text watcher.
            if (chipsPending()) { //当前有chip正在添加,返回
                return;
            }
            // If the user is editing a chip, don't clear it.
            if (mSelectedChip != null) { //当前有所选chip
                if (!isGeneratedContact(mSelectedChip)) { //所选chip没有对应联系人转到编辑状态
                    setCursorVisible(true); //设置光标可见
                    setSelection(getText().length()); //设置光标在控件末尾
                    clearSelectedChip();//清除mSelectedChip
                } else {
                    return;
                }
            }
            int length = s.length();
            // Make sure there is content there to parse and that it is
            // not just the commit character.
            if (length > 1) {//字符串长度大于1
                if (lastCharacterIsCommitCharacter(s)) {//s末尾为分隔符
                    commitByCharacter();  //提交chip
                    return;
                }
                char last;
                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
                int len = length() - 1;
                if (end != len) {
                    last = s.charAt(end);
                } else {
                    last = s.charAt(len);
                }
                if (last == COMMIT_CHAR_SPACE) { //末尾字符为空格
                    if (!isPhoneQuery()) { //输入为邮件地址
                        // Check if this is a valid email address. If it is,
                        // commit it.
                        String text = getText().toString();
                        int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
                        String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
                                tokenStart));
                        if (isValidEmailAddress(sub)) { //判断是否为合法邮件地址
                            commitByCharacter(); //提交chip
                        }
                    }
                }
            }
        }

焦点的获取与失去

    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
        super.onFocusChanged(hasFocus, direction, previous);
        if (!hasFocus) {
            shrink();
        } else {
            expand();
        }
    }

失去焦点后如果chip较多会收缩控件显示,只显示前几个chip并显示mMoreChip;获取焦点的时候会展开显示所有chip并且去除mMoreChip。

shrink

    private void shrink() {
        ...
        long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1;
        if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT
                && (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) {
            clearSelectedChip(); //所选chip有对应联系人,清除mSelectedChip
        } else {
            if (getWidth() <= 0) { //width为0,证明控件显示流程还没有走完
                mHandler.removeCallbacks(mDelayedShrink);
                if (getVisibility() == GONE) {
                    mRequiresShrinkWhenNotGone = true;
                } else {

                    mHandler.post(mDelayedShrink); //延后运行mDelayedShrink
                }
                return;
            }
            if (mPendingChipsCount > 0) {
                postHandlePendingChips(); //延后处理PendingChips
            } else {
                Editable editable = getText();
                int end = getSelectionEnd(); //字符串末尾
                int start = mTokenizer.findTokenStart(editable, end); //从末尾开始往前找token的开始位置
                DrawableRecipientChip[] chips =
                        getSpannable().getSpans(start, end, DrawableRecipientChip.class); //寻找chips
                if ((chips == null || chips.length == 0)) { //当前没有chip
                    Editable text = getText();
                    int whatEnd = mTokenizer.findTokenEnd(text, start);

                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
                        whatEnd = movePastTerminators(whatEnd);  //已格式化字符的特殊处理
                    }
         
                    int selEnd = getSelectionEnd();
                    if (whatEnd != selEnd) {
                        handleEdit(start, whatEnd); //selection end和token end不等,设置selection end为token end然后提交chip
                    } else {
                        commitChip(start, end, editable); //提交chip
                    }
                }
            }
            mHandler.post(mAddTextWatcher);
        }
        createMoreChip(); //添加mMoreChip,收缩的主要实现都在这个方法中,之前的只是些预处理
    }

expand

    private void expand() {
        ...
        removeMoreChip(); //移除mMoreChip
        setCursorVisible(true); //光标可见
        Editable text = getText();
        setSelection(text != null && text.length() > 0 ? text.length() : 0); //光标位于控件末尾
        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
            new RecipientReplacementTask().execute(); //这种情况是处理收缩态可能未处理的chips,收缩态是只处理前两个chip的
            mTemporaryRecipients = null;
        }
    }

moreChip相关

     void createMoreChip() {
        ...
        ReplacementDrawableSpan[] tempMore = getSpannable().getSpans(0, getText().length(),
                MoreImageSpan.class);
        if (tempMore.length > 0) {
            getSpannable().removeSpan(tempMore[0]); //如果已存在morechip,移除
        }
        DrawableRecipientChip[] recipients = getSortedRecipients();

        if (recipients == null || recipients.length <= CHIP_LIMIT) { //如果chip数目小于2,直接返回
            mMoreChip = null;
            return;
        }
        Spannable spannable = getSpannable();
        int numRecipients = recipients.length; //chip 总数
        int overage = numRecipients - CHIP_LIMIT; //不显示的chip总数
        MoreImageSpan moreSpan = createMoreSpan(overage);
        mRemovedSpans = new ArrayList<DrawableRecipientChip>();
        int totalReplaceStart = 0;
        int totalReplaceEnd = 0;
        Editable text = getText();
        for (int i = numRecipients - overage; i < recipients.length; i++) {
            mRemovedSpans.add(recipients[i]); //添加要移除的chip
            if (i == numRecipients - overage) {
                totalReplaceStart = spannable.getSpanStart(recipients[i]);
            }
            if (i == recipients.length - 1) {
                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
            }
            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
                int spanStart = spannable.getSpanStart(recipients[i]);
                int spanEnd = spannable.getSpanEnd(recipients[i]);
                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); //设置为对应的原始字符串
            }
            spannable.removeSpan(recipients[i]); // 清除对应span
        }
        if (totalReplaceEnd < text.length()) {
            totalReplaceEnd = text.length();
        }
        int end = Math.max(totalReplaceStart, totalReplaceEnd);
        int start = Math.min(totalReplaceStart, totalReplaceEnd);
        SpannableString chipText = new SpannableString(text.subSequence(start, end));
        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //把要移除显示的字符串用moreChip替代
        text.replace(start, end, chipText);
        mMoreChip = moreSpan;
        ...
    }
removeMoreChip就是createMoreChip的相反过程,清除moreChip并依据mRemovedSpans恢复原有的chips。

RecipientReplacementTask

继承自AsyncTask<Void, Void, Void>
       private void processReplacements(final List<DrawableRecipientChip> recipients,
                final List<DrawableRecipientChip> replacements) 
recipients和replacements是一一对应的关系,它们不仅size相等,而且对应的通信地址也是相等的。该方法主体就是一个循环,如果replacements对应的chip是更好的匹配,就替换recipients中的对应chip。
        protected void onPreExecute() {         
            final List<DrawableRecipientChip> originalRecipients =
                    new ArrayList<DrawableRecipientChip>();
            final DrawableRecipientChip[] existingChips = getSortedRecipients();
            Collections.addAll(originalRecipients, existingChips);
            if (mRemovedSpans != null) {
                originalRecipients.addAll(mRemovedSpans);
            }

            final List<DrawableRecipientChip> replacements =
                    new ArrayList<DrawableRecipientChip>(originalRecipients.size());

            for (final DrawableRecipientChip chip : originalRecipients) {
                if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId())
                        && getSpannable().getSpanStart(chip) != -1) {
                    replacements.add(createFreeChip(chip.getEntry()));
                } else {
                    replacements.add(null);
                }
            }

            processReplacements(originalRecipients, replacements);
        }
本段代码是把某些InvisibleRecipientChip替换成VisibleRecipientChip,替换不会导致超出上限。该方法运行在UI线程,目的是先显示chips,然后再后台刷新。
    protected Void doInBackground(Void... params) {
            ...
            final BaseRecipientAdapter adapter = getAdapter();
            adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() {
                        @Override
                        public void matchesFound(Map<String, RecipientEntry> entries) {
                            ...
                            processReplacements(recipients, replacements);
                        }

                        @Override
                        public void matchesNotFound(final Set<String> unfoundAddresses) {
                            ...
                            processReplacements(recipients, replacements);
                        }
                    });
            return null;
        }
使用BaseRecipientAdapter的getMatchingRecipients查询数据库,并替换相应的chip。
RecipientReplacementTask的功能就是chip的异步更新。

触摸事件

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        ...
        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
            float x = event.getX();
            float y = event.getY();
            int offset = putOffsetInRange(x, y);
            DrawableRecipientChip currentChip = findChip(offset); //找到点击点对应的chip
            if (currentChip != null) {
                if (mSelectedChip != null && mSelectedChip != currentChip) { //mSelectedChip 不为空的的情况下点击了另一个chip
                    clearSelectedChip();  //清除mSelectedChip 
                    selectChip(currentChip); //切换到点击的chip
                } else if (mSelectedChip == null) {   
                    commitDefault();  //依据选择范围生成可能的chip
                    selectChip(currentChip);  //切换到点击的chip
                } else {
                    onClick(mSelectedChip); //click事件,实际就是清除mSelectedChip 
                }
                chipWasSelected = true;
                handled = true;
            } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
                chipWasSelected = true;
            }
        }
        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
            clearSelectedChip();
        }
        return handled;
    }

clearSelectedChip

    public void clearSelectedChip() {
        if (mSelectedChip != null) {
            unselectChip(mSelectedChip);
            mSelectedChip = null;
        }
        ...
    }
    private void unselectChip(DrawableRecipientChip chip) {
        int start = getChipStart(chip);
        int end = getChipEnd(chip);
        Editable editable = getText();
        mSelectedChip = null;
        if (start == -1 || end == -1) { //chip不存在,或者是用户正在编辑
            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
            setSelection(editable.length());
            commitDefault(); //从字符串最后开始向前找第一个chip提交
        } else {
            getSpannable().removeSpan(chip); //删除chip
            QwertyKeyListener.markAsReplaced(editable, start, end, "");
            editable.removeSpan(chip);
            try {
                if (!mNoChips) {
                    editable.setSpan(constructChipSpan(chip.getEntry()),
                            start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //重新提交chip
                }
            } catch (NullPointerException e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }
        ...
    }
unselectChip中的重新提交貌似没啥意义,因为原生代码中选中和不选中UI上没啥区别,mtk在这点上做了修改。

selectChip

     private void selectChip(DrawableRecipientChip currentChip) {
        if (shouldShowEditableText(currentChip)) { //当前chip无对应联系人,直接进入编辑状态
            CharSequence text = currentChip.getValue();
            Editable editable = getText();
            Spannable spannable = getSpannable();
            int spanStart = spannable.getSpanStart(currentChip);
            int spanEnd = spannable.getSpanEnd(currentChip);
            spannable.removeSpan(currentChip); //删除chip
            // Don't need leading space if it's the only chip
            if (spanEnd - spanStart == editable.length() - 1) {
                spanEnd++;
            }
            editable.delete(spanStart, spanEnd); //删除对应内容
            setCursorVisible(true); //显示光标
            setSelection(editable.length()); //selection移动到末尾
            editable.append(text); //添加原chip相关字符串
            mSelectedChip = constructChipSpan(
                    RecipientEntry.constructFakeEntry((String) text, isValid(text.toString()))); //赋值新的chip到mSelectedChip
        } else {
            final boolean showAddress =
                    currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT ||
                    getAdapter().forceShowAddress();
            if (showAddress && mNoChips) {
                return;
            }
            mSelectedChip = currentChip;
            setSelection(getText().getSpanEnd(mSelectedChip));
            setCursorVisible(false);

            if (showAddress) {
                showAddress(currentChip, mAddressPopup); //弹出dialog,电子邮件地址可能会走这里,删除当前地址
            } else {
                showAlternates(currentChip, mAlternatesPopup); //弹出dialong,以便替换当前号码
            }
        }
    }

手势操作

    private GestureDetector mGestureDetector;
构造方法中
        mGestureDetector = new GestureDetector(context, this);
实现手势接口的就是RecipientEditTextView类本身,实现的主要是长按事件
    public void onLongPress(MotionEvent event) {
        ...
        if (currentChip != null) {
            if (mDragEnabled) {
                // Start drag-and-drop for the selected chip.
                startDrag(currentChip); //拖拽操作
            } else {
                // Copy the selected chip email address.
                showCopyDialog(currentChip.getEntry().getDestination()); //显示拷贝地址的对话框
            }
        }
    }

drag

    private void startDrag(DrawableRecipientChip currentChip) {
        String address = currentChip.getEntry().getDestination();
        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
        startDrag(data, new RecipientChipShadow(currentChip), null, 0); //启动拖拽模式
        removeChip(currentChip); //删除拖拽的chip
    }
    @Override
    public boolean onDragEvent(@NonNull DragEvent event) {
        switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:
                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
            case DragEvent.ACTION_DRAG_ENTERED:
                requestFocus();
                return true;
            case DragEvent.ACTION_DROP:
                handlePasteClip(event.getClipData()); //粘贴拖拽的chip
                return true;
        }
        return false;
    }

paste

粘贴操作除了拖拽外,控件菜单选择粘贴选项也可以触发
 void handlePasteClip(ClipData clip) {
        ...
        final ClipDescription clipDescription = clip.getDescription();
        for (int i = 0; i < clip.getItemCount(); i++) {
            ...
            final CharSequence pastedItem = clip.getItemAt(i).getText();
            if (!TextUtils.isEmpty(pastedItem)) {
                final Editable editable = getText();
                final int start = getSelectionStart();
                final int end = getSelectionEnd();
                if (start < 0 || end < 1) {
                    // No selection.
                    editable.append(pastedItem);
                } else if (start == end) {
                    // Insert at position.
                    editable.insert(start, pastedItem);
                } else {
                    editable.append(pastedItem, start, end);
                }
                handlePasteAndReplace();
            }
        }
        ...
    }
把字符串添加到editable,然后调用handlePasteAndReplace
    private void handlePasteAndReplace() {
        ArrayList<DrawableRecipientChip> created = handlePaste(); //首先获得chips
        if (created != null && created.size() > 0) {
            IndividualReplacementTask replace = new IndividualReplacementTask(); //然后启动异步更新
            replace.execute(created);
        }
    }
handlePaste依据selection范围,生成chips结构,不再具体分析其代码。
IndividualReplacementTask类似于RecipientReplacementTask,同样使用getMatchingRecipients方法更新数据,但是在回调中最终走的是replaceChip方法

replaceChip

replaceChip不只在IndividualReplacementTask中,在selectChip小节中替换当前地址也是用的该方法。
    void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
        ...
        getSpannable().removeSpan(chip); //删除chip
        Editable editable = getText();
        CharSequence chipText = createChip(entry); //新建chip
        if (chipText != null) {
            if (start == -1 || end == -1) {
                Log.e(TAG, "The chip to replace does not exist but should.");
                editable.insert(0, chipText); //原chip不存在就直接插入
            } else {
                if (!TextUtils.isEmpty(chipText)) {
                    ...
                    editable.replace(start, toReplace, chipText); //替换chip
                }
            }
        }
        ...
    }

按键事件

onKeyUp

    public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_TAB:
                if (event.hasNoModifiers()) {
                    if (mSelectedChip != null) {
                        clearSelectedChip();
                    } else {
                        commitDefault();
                    }
                }
                break;
        }
        return super.onKeyUp(keyCode, event);
    }

onKeyDown

    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { //删除键的处理
            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
                mAlternatesPopup.dismiss();
            }
            removeChip(mSelectedChip); //删除当前chip
        }

        switch (keyCode) {
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_DPAD_CENTER:
                if (event.hasNoModifiers()) {
                    if (commitDefault()) { //回车键的处理,提交默认chip
                        return true;
                    }
                    if (mSelectedChip != null) {
                        clearSelectedChip(); //清除选择项
                        return true;
                    } else if (focusNext()) {
                        return true;
                    }
                }
                break;
        }

        return super.onKeyDown(keyCode, event);
    }

removeChip

删除chip的方法之前拖拽小节也用过
    /* package */void removeChip(DrawableRecipientChip chip) {
        Spannable spannable = getSpannable();
        ...
        spannable.removeSpan(chip);
        if (spanStart >= 0 && toDelete > 0) {
            text.delete(spanStart, toDelete);
        }
        ...
    }
太简单没啥好讲的了。

输入法确认按键

    @Override
    public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
        if (action == EditorInfo.IME_ACTION_DONE) {
            if (commitDefault()) { //提交默认chip
                return true;
            }
        ...
        }
    }

系统接口和回调

onSizeChanged

    @Override
    public void onSizeChanged(int width, int height, int oldw, int oldh) {
        super.onSizeChanged(width, height, oldw, oldh);
        if (width != 0 && height != 0) {
            if (mPendingChipsCount > 0) {
                postHandlePendingChips(); //延时处理PendingChips
            } else {
                checkChipWidths(); //处理chip宽度的变化
            }
        }
        ...
    }
    private void checkChipWidths() {
        // Check the widths of the associated chips.
        DrawableRecipientChip[] chips = getSortedRecipients();
        if (chips != null) {
            Rect bounds;
            for (DrawableRecipientChip chip : chips) {
                bounds = chip.getBounds();
                if (getWidth() > 0 && bounds.right - bounds.left >
                        getWidth() - getPaddingLeft() - getPaddingRight()) {
                    // Need to redraw that chip.
                    replaceChip(chip, chip.getEntry()); //数据无变化,其实就是重新绘制chip
                }
            }
        }
    }
checkChipWidths的功能就是重新绘制chip

append

append是TextView定义的方法,这个是提供给控件使用者的方法,用于往控件中添加字符串。RecipientEditTextView要想保持自定义的UI当然要重写这个方法
    @Override
    public void append(CharSequence text, int start, int end) {
        if (mTextWatcher != null) {
            removeTextChangedListener(mTextWatcher); //去除TextWatcher,代码中多处用到这段代码
        }
        super.append(text, start, end);
        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
            String displayString = text.toString(); 
            ...

            if (!TextUtils.isEmpty(displayString)
                    && TextUtils.getTrimmedLength(displayString) > 0) {
                mPendingChipsCount++;
                mPendingChips.add(displayString); //PendingChips添加任务
            }
        }

        if (mPendingChipsCount > 0) {
            postHandlePendingChips(); //处理PendingChips
        }
        mHandler.post(mAddTextWatcher); //添加TextWatcher
    }
和onSizeChanged一样使用了postHandlePendingChips
private void postHandlePendingChips() {
        mHandler.removeCallbacks(mHandlePendingChips);
        mHandler.post(mHandlePendingChips);
    }
postHandlePendingChips只是简单的post了个Runnable

    private Runnable mHandlePendingChips = new Runnable() {
        @Override
        public void run() {
            handlePendingChips();
        }
    };
最终调用handlePendingChips
    void handlePendingChips() {
        ...
        synchronized (mPendingChips) {
            Editable editable = getText();
            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
                for (int i = 0; i < mPendingChips.size(); i++) { //循环处理mPendingChips
                    String current = mPendingChips.get(i);
                    int tokenStart = editable.toString().indexOf(current);
                  
                    int tokenEnd = tokenStart + current.length() - 1;
                    if (tokenStart >= 0) {
                        ...
                        createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
                                || !mShouldShrink); //生成对应的chip,chip会添加到mTemporaryRecipients中
                    }
                    mPendingChipsCount--;
                }
                sanitizeEnd();
            } else {
                mNoChips = true;
            }

            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { //有焦点,则处理全部的chips
                    new RecipientReplacementTask().execute();
                    mTemporaryRecipients = null;
                } else { //无焦点则显示moreChip
                    // Create the "more" chip
                    mIndividualReplacements = new IndividualReplacementTask();
                    mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>( //只处理前CHIP_LIMIT个Chip
                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
                    if (mTemporaryRecipients.size() > CHIP_LIMIT) { //不显示的chip都添加到mTemporaryRecipients中
                        mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(
                                mTemporaryRecipients.subList(CHIP_LIMIT,
                                        mTemporaryRecipients.size()));
                    } else {
                        mTemporaryRecipients = null;
                    }
                    createMoreChip();
                }
            } else { //数量已超限制则只添加moreChip,mTemporaryRecipients也无需赋值
                mTemporaryRecipients = null;
                createMoreChip();
            }
            mPendingChipsCount = 0;
            mPendingChips.clear();
        }
    }
处理mTemporaryRecipients流程中的两个Task在之前的代码中都已介绍过。无焦点时的mTemporaryRecipients保留未处理的chips,这些chips在之前介绍的expand方法中再做处理。

提交Chip

commitByCharacter

    private void commitByCharacter() {
        // We can't possibly commit by character if we can't tokenize.
        if (mTokenizer == null) {
            return;
        }
        Editable editable = getText();
        int end = getSelectionEnd();
        int start = mTokenizer.findTokenStart(editable, end);
        if (shouldCreateChip(start, end)) { //是否需要创建chip
            commitChip(start, end, editable); //提交chip
        }
        setSelection(getText().length());
    }
    private boolean shouldCreateChip(int start, int end) {
        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 
        //四个条件
        //1.chip数没有超出最大限制
        //2.控件当前有焦点
        //3.输入的字符数超过阀值
        //4.start和end之间当前没有指定span
    }

commitDefault

    private boolean commitDefault() {
        ...
        if (shouldCreateChip(start, end)) {
            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
            whatEnd = movePastTerminators(whatEnd);
            if (whatEnd != getSelectionEnd()) {
                handleEdit(start, whatEnd); //会把selection end设置为token end,然后创建chip
                return true;
            }
            return commitChip(start, end , editable);
        }
        return false;
    }
和commitByCharacter类似,唯一不同的是添加了针对token end和selection end不一样时的处理。

commitChip

    private boolean commitChip(int start, int end, Editable editable) {
        ListAdapter adapter = getAdapter();
        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
                && end == getSelectionEnd() && !isPhoneQuery()) {//控件匹配列表有值,且地址是电子邮件
            if (!isValidEmailAddress(editable.toString().substring(start, end).trim())) { //当前非合法的电子邮件地址
                final int selectedPosition = getListSelection();
                if (selectedPosition == -1) {
                    // Nothing is selected; use the first item
                    submitItemAtPosition(0); //拿匹配列表的第一个值替换
                } else { 
                    submitItemAtPosition(selectedPosition); //拿匹配列表选中值替换
                }
            }
            dismissDropDown();
            return true;
        } else {
            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
            ...
            String text = editable.toString().substring(start, tokenEnd).trim();
            clearComposingText();
            if (text.length() > 0 && !text.equals(" ")) {
                RecipientEntry entry = createTokenizedEntry(text);
                if (entry != null) {
                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
                    CharSequence chipText = createChip(entry); //创建chip
                    if (chipText != null && start > -1 && end > -1) {
                        editable.replace(start, end, chipText); //替换字符串
                    }
                }
                if (end == getSelectionEnd()) {
                    dismissDropDown();
                }
                sanitizeBetween(); //消除chip之间的空格
                return true;
            }
        }
        return false;
    }

createChip

commitChip, handleEdit, replaceChip和submitItemAtPosition四个方法的基本流程都是类似的,都是先用createChip生成chip,然后insert或者replace字符串。有四个方法基本流程类似可以看出RecipientEditTextView的代码冗余程度挺高的,有失google的水准。
    private CharSequence createChip(RecipientEntry entry) {
         ...
                DrawableRecipientChip chip = constructChipSpan(entry);
                chipText.setSpan(chip, 0, textLength,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                chip.setOriginalText(chipText.toString());
         ...
    }
    private DrawableRecipientChip constructChipSpan(RecipientEntry contact) {
        ...
        Bitmap tmpBitmap = createChipBitmap(contact, paint); //生成chip所要显示的图片
        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
        VisibleRecipientChip recipientChip =
                new VisibleRecipientChip(result, contact);//创建chip
        ...
    }
最重要的其实就是创建图片
    private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint) {
        paint.setColor(getDefaultChipTextColor(contact));
        ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint,
                getChipBackground(contact), getDefaultChipBackgroundColor(contact)); //绘制背景和名称,确认头像位置和大小

        if (bitmapContainer.loadIcon) {
            loadAvatarIcon(contact, bitmapContainer); //异步读取头像数据
        }
        return bitmapContainer.bitmap;
    }

    private ChipBitmapContainer createChipBitmap(RecipientEntry contact, TextPaint paint,
            Drawable overrideBackgroundDrawable, int backgroundColor) {

        ...
            canvas.drawRoundRect(new RectF(0, 0, width, height), radius, radius,
                    mWorkPaint); //绘制背景
        

        ...
        canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
                textX, getTextYOffset(height), paint); //绘制名称

        int iconX = shouldPositionAvatarOnRight() ? width - backgroundPadding.right - iconWidth :
                backgroundPadding.left; //设置头像位置和大小
        result.left = iconX;
        result.top = backgroundPadding.top;
        result.right = iconX + iconWidth;
        result.bottom = height - backgroundPadding.bottom;

        return result;
    }
loadAvatarIcon就是使用PhotoManager异步读取头像数据,不再分析

总结

RecipientEditTextView从原理上讲并不复杂,不过代码确实有问题;
1.首先大量代码集中在一个类中,很多工具方法可以新建文件来放置,例如isPhoneNumber这个方法和控件本身没有任何关系,它只是用来判断字符串是否为号码。类似的还有createChipBitmap,loadAvatarIcon。
2.chip的操作,其实和控件关联也很小,就是字符串的操作,如createMoreSpan,完全可以搞个新文件,例如ChipManager等。
        Editable editable = getText();
通过上面一行语句就能和控件建立关系,无需其他关联。
3.一个类实现大量接口,到处是this,我怎么知道一个接口的方法到底是什么。把代码范围控制在最小比较好分析或查看代码,一个局部的代码被搞成this后就成了成员方法范围。
4.同一段代码多处冗余,commitChip, handleEdit, replaceChip和submitItemAtPosition四个方法的基本流程都是类似的,RecipientReplacementTask和IndividualReplacementTask其实也基本类似,createMoreChip和也createMoreChipPlainText类似。有removeChip方法,但是在onTextChanged中删除chip的时候却不用,还是把差不多的代码又重复了一遍。
5.chip操作入口太多了,就保留TextWatcher一个入口也能完成同样功能,可以看出大量代码就是为了效率。



  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值