×
广告

Android 利用 SpannableString 实现微信 @朋友 整块删除功能

96
龙衣袭
2018.08.30 23:39 字数 229

实现的效果如下:

span.gif

就是利用 Span 实现文本的整块添加和删除
主要参考:
CloudEditText
ddssingsong/AtFriend 来实现。

实现原理

  • 自定义 EditText 实现
    通过继承自 EditText 实现文本框的输入,然后在通过设置 Span 来拼接文本改变字体颜色,点击等效果
  • 整块添加

通过 StringBuilder 拼接字符串

 /**
   * 添加一个块,在文字的后面添加
     *
     * @param maskText 内容签名的标签
     * @param showText 显示到界面的内容
     */
    public void addAtSpan(String maskText, String showText) {
        StringBuilder builder = new StringBuilder();
        builder.delete(0,builder.length());
        if (!TextUtils.isEmpty(maskText)) {
            //已经添加了#
            builder.append(maskText).append(showText).append(" ");
        } else {
            builder.append(showText).append(" ");
        }
        // 插在光标之后
//        getText().insert(getSelectionStart(), builder.toString());
        // 插在第一位
        getText().insert(0, builder.toString());
        SpannableString sps = new SpannableString(getText());
        // 拼接在中间
//        int start = getSelectionEnd() - builder.toString().length() - (TextUtils.isEmpty(maskText) ? 1 : 0);
        // 在光标末尾
//        int end = getSelectionEnd();
        int start = 0;
        int end = builder.toString().length();
        // 添加 Sapn 的设置
        makeSpan(sps, new UnSpanText(start, end, builder.toString()));
        setText(sps);
        setSelection(end);
        builder.setLength(0);
    }

makeSpan 方法

 /**
     * 生成一个需要整体删除的Span
     * @param sps
     * @param unSpanText
     */
    private void makeSpan(Spannable sps, UnSpanText unSpanText) {
        PublishContentTextSpan what = new PublishContentTextSpan(unSpanText.returnText);
        PublishContentTextColorSpan circleWhat = new PublishContentTextColorSpan(ContextCompat.getColor(getContext(), R.color.colorAccent));
        PublishContentTextClickSpan circleClickWhat = new PublishContentTextClickSpan(getContext());
        int start = unSpanText.start;
        int end = unSpanText.end;
        sps.setSpan(what, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        sps.setSpan(circleWhat, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        sps.setSpan(circleClickWhat, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

UnSpanText 内部类,用于获取文本内容以及设置文本的开始位置和结束位置

    /**
     * 文字块
     */
    private class UnSpanText {
        int start;
        int end;
        String returnText;

        UnSpanText(int start, int end, String returnText) {
            this.start = start;
            this.end = end;
            this.returnText = returnText;
        }
    }
  • 整块删除

通过重写 onTextChange 方法判断要删除文本的开始和结束是否是 span 的开始和结束

@Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        //向前删除一个字符,@后的内容必须大于一个字符,可以在后面加一个空格
        if (lengthBefore == 1 && lengthAfter == 0) {
            PublishContentTextSpan[] spans = getText().getSpans(0, getText().length(), PublishContentTextSpan.class);
            for (PublishContentTextSpan publishContentTextSpan : spans) {
                if (getText().getSpanEnd(publishContentTextSpan) == start && !text.toString().endsWith(publishContentTextSpan.getShowText())) {
                    getText().delete(getText().getSpanStart(publishContentTextSpan), getText().getSpanEnd(publishContentTextSpan));
                    break;
                }
            }
        }
    }

完整代码

  • ContentEditText.java
public class ContentEditText extends AppCompatEditText {
    public ContentEditText(Context context) {
        super(context);
    }

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

    public ContentEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 添加一个块,在文字的后面添加
     *
     * @param maskText 内容签名的标签
     * @param showText 显示到界面的内容
     */
    public void addAtSpan(String maskText, String showText) {
        StringBuilder builder = new StringBuilder();
        builder.delete(0,builder.length());
        if (!TextUtils.isEmpty(maskText)) {
            //已经添加了#
            builder.append(maskText).append(showText).append(" ");
        } else {
            builder.append(showText).append(" ");
        }
        // 插在光标之后
//        getText().insert(getSelectionStart(), builder.toString());
        // 插在第一位
        getText().insert(0, builder.toString());
        SpannableString sps = new SpannableString(getText());
        // 拼接在中间
//        int start = getSelectionEnd() - builder.toString().length() - (TextUtils.isEmpty(maskText) ? 1 : 0);
        // 在光标末尾
//        int end = getSelectionEnd();
        int start = 0;
        int end = builder.toString().length();
        makeSpan(sps, new UnSpanText(start, end, builder.toString()));
        setText(sps);
        setSelection(end);
        builder.setLength(0);
    }
    /**
     * 从草稿箱中读取使用
     *
     * @param maskText #
     * @param showText 圈子名字
     * @param afterText 圈子之后的内容
     */
    public void addAtSpan(String maskText, String showText, String afterText) {
        StringBuilder builder = new StringBuilder();
        builder.delete(0,builder.length());
        if (!TextUtils.isEmpty(maskText)) {
            //已经添加了@
            builder.append(maskText).append(showText).append(" ");
        } else {
            builder.append(showText).append(" ");
        }

        builder.append(afterText);
        SpannableString sps = new SpannableString(builder);

        int start = 0;
        int end = builder.toString().length();
        makeSpan(sps, new UnSpanText(start, showText.length()+1, showText));

        setText(sps);
        setSelection(end);
        builder.setLength(0);
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        //向前删除一个字符,@后的内容必须大于一个字符,可以在后面加一个空格
        if (lengthBefore == 1 && lengthAfter == 0) {
            PublishContentTextSpan[] spans = getText().getSpans(0, getText().length(), PublishContentTextSpan.class);
            for (PublishContentTextSpan publishContentTextSpan : spans) {
                if (getText().getSpanEnd(publishContentTextSpan) == start && !text.toString().endsWith(publishContentTextSpan.getShowText())) {
                    getText().delete(getText().getSpanStart(publishContentTextSpan), getText().getSpanEnd(publishContentTextSpan));
                    break;
                }
            }
        }
    }
    /**
     * 生成一个需要整体删除的Span
     * @param sps
     * @param unSpanText
     */
    private void makeSpan(Spannable sps, UnSpanText unSpanText) {
        PublishContentTextSpan what = new PublishContentTextSpan(unSpanText.returnText);
        PublishContentTextColorSpan circleWhat = new PublishContentTextColorSpan(ContextCompat.getColor(getContext(), R.color.colorAccent));
        PublishContentTextClickSpan circleClickWhat = new PublishContentTextClickSpan(getContext());
        int start = unSpanText.start;
        int end = unSpanText.end;
        sps.setSpan(what, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        sps.setSpan(circleWhat, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        sps.setSpan(circleClickWhat, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    /**
     * 文字块
     */
    private class UnSpanText {
        int start;
        int end;
        String returnText;

        UnSpanText(int start, int end, String returnText) {
            this.start = start;
            this.end = end;
            this.returnText = returnText;
        }
    }
}
  • PublishContentTextSpan.java 设置文本内容的 Span
public class PublishContentTextSpan extends MetricAffectingSpan {
    private String showText;
    private long userId;
    public PublishContentTextSpan(String showText) {
        this.showText = showText;
    }


    public String getShowText() {
        return showText;
    }

    @Override
    public void updateMeasureState(TextPaint p) {

    }

    @Override
    public void updateDrawState(TextPaint tp) {

    }
}
  • PublishContentTextClickSpan.java 设置点击事件的 Span
public class PublishContentTextClickSpan extends ClickableSpan {
    private Context mContext ;
    public PublishContentTextClickSpan(Context context) {
        this.mContext = context;
    }

    @Override
    public void onClick(View widget) {
        Toast.makeText(mContext, "点了可点击链接", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        ds.setUnderlineText(false);
    }
}
  • PublishContentTextColorSpan.java 设置颜色的 Span
public class PublishContentTextColorSpan extends ForegroundColorSpan {
    public PublishContentTextColorSpan(@ColorInt int color) {
        super(color);
    }
}
  • xml 中使用
 <com.wyq.animationtest.ContentEditText
        android:id="@+id/content_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:background="@null"
        android:minLines="5"/>
  • java 代码中调用
mEtContent.addAtSpan("#","我是大佬?");
mEtContent.setSelection(mEtContent.getText().length());

欢迎添加微信互相学习

本人微信weixin1105894953,添加请备注

开发
Web note ad 1