仿微信@功能

作者: 猫KK | 来源:发表于2018-10-26 11:33 被阅读0次

最近项目需要做类似微信的@ 功能,在网上找了很多,发现基本都是使用Span 来实现的,觉得写的比较好的就是这篇
Android 如何优雅地实现@人功能?——仿微博、仿QQ、仿微信、零入侵、高扩展性
由于本人对kotlin 的理解不深,有些代码看不太懂,自己根据这个思路用java 写了一遍

首先,我们给 EditText 设置一个 Span 用来标示这个一个整体,代码如下

/**
     * 添加 @内容
     *
     * @param text 包含 @ 符号的字符
     */
    public void addSpan(String text) {
        //将 @字符串插入到 光标之后
        getText().insert(getSelectionEnd(), text);
        //创建一个数据类,由于后面寻找对应的@ 内容
        DataSpan myTextSpan = new DataSpan(); 
        //设置Span
        getText().setSpan(myTextSpan, getSelectionEnd() - text.length(), getSelectionEnd(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
       //将光标设置到最后
        setSelection(getText().length());
    }

设置好了 Span 字符实现了在 EditText 中插入了@ 字符串,下面需要对 @ 字符串进行整体删除,监听 EditText 的 setOnKeyListener 事件:

        //该事件每次删除一个字符都会回调一次
        mEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //  判断是否为 按下删除 事件
                if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
                    //做整体删除处理
                    return CopyWeChatEditText.KeyDownHelper(mEditText.getText());
                }
                return false;
            }
        });

    /**
     * 找到最后 Span 块
     *
     * @param text
     * @return
     */
    public static boolean KeyDownHelper(Editable text) {
        //找到光标开始结束坐标
        int selectionEnd = Selection.getSelectionEnd(text);
        int selectionStart = Selection.getSelectionStart(text);
        //获取 EditText 中所有的 Span 通过 DataSpan 绑定是的类型
        DataSpan[] spans = text.getSpans(selectionStart, selectionEnd, DataSpan.class);
        for (DataSpan span : spans) {
            if (span != null) {
                //找到第一个非空的 span 和该 span 对应 EditText 中的开始结束位置
                int spanStart = text.getSpanStart(span);
                int spanEnd = text.getSpanEnd(span);
                //假如光标的位置位于该 span 中的最后一位,即位于@ 字符串后面
                if (selectionEnd == spanEnd) {
                    //设置选中该 span 字符串
                    Selection.setSelection(text, spanStart, spanEnd);
                    return false;
                }
            }
        }
        return false;
    }

注意 Selection.setSelection(text, spanStart, spanEnd); 这一行代码,效果就相当于长按后选择字符串,setOnKeyListener 事件的调用时机是键盘按下删除,但字符串还没有做删除操作,所以设置选中,在执行删除操作时候就能删除选中的所有字符,实现@ 内容整块删除效果

现在基本实现了 @ 功能和整块删除效果,但是还有一点小问题,假如自己手动将光标移动到 @字符串中间,然后在删除字符,当将@字符删除,在将光标移动到 @字符串之后,再点击删除,发现整个字符还是被删除了。

要解决这个方法,需要监听 span 字符串是否包含 @ 字符

//设置工厂,用来监听 span 字符串变化
     setEditableFactory(new NoCopySpanEditableFactory(new DirtySpanWatcher()));


    class NoCopySpanEditableFactory extends Editable.Factory {

        private NoCopySpan spans;

        public NoCopySpanEditableFactory(NoCopySpan spans) {
            this.spans = spans;
        }

        @Override
        public Editable newEditable(CharSequence source) {
            //添加 span 字符串的监听
            SpannableStringBuilder stringBuilder = new SpannableStringBuilder(source);
            stringBuilder.setSpan(spans, 0, source.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
            return stringBuilder;
        }
    }


    class DirtySpanWatcher implements SpanWatcher {

        @Override
        public void onSpanAdded(Spannable text, Object what, int start, int end) {

        }

        @Override
        public void onSpanRemoved(Spannable text, Object what, int start, int end) {

        }

        @Override
        public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
            //span 字符串改变时候监听
            int spanEnd = text.getSpanEnd(what);
            int spanStart = text.getSpanStart(what);
            if (spanStart >= 0 && spanEnd >= 0 && what instanceof DataSpan) {
                CharSequence charSequence = text.subSequence(spanStart, spanEnd);
                //删除后的字符是否包含 @ 字符
                if (!charSequence.toString().contains("@")) {
                    DataSpan[] spans = text.getSpans(spanStart, spanEnd, DataSpan.class);
                    for (DataSpan span : spans) {
                        if (span != null) {
                            //不包含 @ 字符,将该 span 属性去除
                            text.removeSpan(span);
                            break;
                        }
                    }
                }
            }
        }
    }

到这里,仿微信的@ 功能已经完成,至于为什么会有这两个类,请看转载的文章,如果您有更好的监听方法,请告知我,谢谢
最后附上Demo

相关文章

网友评论

    本文标题:仿微信@功能

    本文链接:https://www.haomeiwen.com/subject/kalktqtx.html