Android 利用 SpannableString 实现微信
2018-08-30 本文已影响27人
d74f37143a31
实现的效果如下:
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
,添加请备注