最近项目需要做类似微信的@ 功能,在网上找了很多,发现基本都是使用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
网友评论