最近項目需要做類似微信的@ 功能拷橘,在網(wǎng)上找了很多舞虱,發(fā)現(xiàn)基本都是使用Span 來實現(xiàn)的睦番,覺得寫的比較好的就是這篇
Android 如何優(yōu)雅地實現(xiàn)@人功能类茂?——仿微博、仿QQ托嚣、仿微信巩检、零入侵、高擴(kuò)展性
由于本人對kotlin 的理解不深示启,有些代碼看不太懂兢哭,自己根據(jù)這個思路用java 寫了一遍
首先,我們給 EditText 設(shè)置一個 Span 用來標(biāo)示這個一個整體夫嗓,代碼如下
/**
* 添加 @內(nèi)容
*
* @param text 包含 @ 符號的字符
*/
public void addSpan(String text) {
//將 @字符串插入到 光標(biāo)之后
getText().insert(getSelectionEnd(), text);
//創(chuàng)建一個數(shù)據(jù)類迟螺,由于后面尋找對應(yīng)的@ 內(nèi)容
DataSpan myTextSpan = new DataSpan();
//設(shè)置Span
getText().setSpan(myTextSpan, getSelectionEnd() - text.length(), getSelectionEnd(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//將光標(biāo)設(shè)置到最后
setSelection(getText().length());
}
設(shè)置好了 Span 字符實現(xiàn)了在 EditText 中插入了@ 字符串,下面需要對 @ 字符串進(jìn)行整體刪除舍咖,監(jiān)聽 EditText 的 setOnKeyListener 事件:
//該事件每次刪除一個字符都會回調(diào)一次
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) {
//找到光標(biāo)開始結(jié)束坐標(biāo)
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 對應(yīng) EditText 中的開始結(jié)束位置
int spanStart = text.getSpanStart(span);
int spanEnd = text.getSpanEnd(span);
//假如光標(biāo)的位置位于該 span 中的最后一位矩父,即位于@ 字符串后面
if (selectionEnd == spanEnd) {
//設(shè)置選中該 span 字符串
Selection.setSelection(text, spanStart, spanEnd);
return false;
}
}
}
return false;
}
注意 Selection.setSelection(text, spanStart, spanEnd); 這一行代碼,效果就相當(dāng)于長按后選擇字符串谎仲,setOnKeyListener 事件的調(diào)用時機是鍵盤按下刪除浙垫,但字符串還沒有做刪除操作刨仑,所以設(shè)置選中郑诺,在執(zhí)行刪除操作時候就能刪除選中的所有字符,實現(xiàn)@ 內(nèi)容整塊刪除效果
現(xiàn)在基本實現(xiàn)了 @ 功能和整塊刪除效果杉武,但是還有一點小問題辙诞,假如自己手動將光標(biāo)移動到 @字符串中間,然后在刪除字符轻抱,當(dāng)將@字符刪除飞涂,在將光標(biāo)移動到 @字符串之后,再點擊刪除祈搜,發(fā)現(xiàn)整個字符還是被刪除了较店。
要解決這個方法,需要監(jiān)聽 span 字符串是否包含 @ 字符
//設(shè)置工廠容燕,用來監(jiān)聽 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 字符串的監(jiān)聽
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 字符串改變時候監(jiān)聽
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;
}
}
}
}
}
}
到這里,仿微信的@ 功能已經(jīng)完成蘸秘,至于為什么會有這兩個類官卡,請看轉(zhuǎn)載的文章蝗茁,如果您有更好的監(jiān)聽方法,請告知我寻咒,謝謝
最后附上Demo