如標題所說, 今天要實現 @ 的效果, 明確要實現的效果邊界:
- 輸入 @ 字符, 彈出一界面選擇好友(監聽回調)
- 選擇好友後更新 @ 的內容
- 刪除 @ 內容將一次刪除 @+好友名 所有字符
廢話不多說, 先上效果圖:
效果圖中我打開好友列表以延時500ms隨機取名字代替:
@Override
public void triggerAt() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
atTextWatcher.insertTextForAt(et, names[random.nextInt(names.length)]);
}
}, 500);
}
下面說下具體的實現方法:
首先使用 TextWatcher 是毋庸置疑的, 通過分析微信 @ 功能, 發現 @張三 分爲三部分:
- 第一部分爲@功能的開始符號 @ 字符
- 第二部分爲好友名字
- 第三部分爲@功能的結束符號 ’ ’
- 注意: 通過分析微信中結束符號, 發現它並不是空格, 而是 unicode 編碼爲 8197 的字符, 該字符跟空格符長得一樣但其實是兩個東西
分析完思路就有了:
-
- 通過TextWatcher監聽EditText
-
- 監聽到內容增加一字符並且該字符爲 @ 符號執行監聽, 彈出選擇好友界面, 並記錄 @ 符號位置
-
- 當監聽到刪除一字符並且該字符爲@功能結束符, 編碼爲 8197 的字符, 根據結束符位置往前找 @ 符號, 並移除之間的內容
實現:
- 我定義了 AtTextWatcher 實現 TextWatcher, 來處理 @ 效果, 在
onTextChanged
中判斷新增字符是否爲 @ , 如果是執行回調AtListener.triggerAt()
方法(Activity 中實現此方法打開好友列表選擇好友), 並記錄 @ 字符的位置. - 外部選擇好友完成後, 調用方法
AtTextWatcher.insertTextForAt("選擇的好友名字")
, 將好友名字和結束符號插入到記錄的**@**符號後面 - 監聽刪除 @字符串 的邏輯必須寫在監聽方法
beforeTextChanged
中, 因爲執行該方法時刪除的字符並未從EditText中移除, 可以拿到字符並判斷是否是 @功能結束符, 並記錄結束符位置 - 然後在方法
afterTextChanged
中執行刪除邏輯(之所以不在beforeTextChanged
中刪除是因爲結束符未刪除完全就刪除其他字符可能會引發一些不必要的問題)
完整代碼如下, AtTextWatcher 的使用方法:
final EditText et = mEt;
final AtTextWatcher atTextWatcher;
AtTextWatcher.AtListener listener = new AtTextWatcher.AtListener() {
@Override
public void triggerAt() {
// 此處跳轉好友列表
// 選擇完好友後執行下面的方法
atTextWatcher.insertTextForAt(et, "張三")
}
};
atTextWatcher = new AtTextWatcher(listener);
et.addTextChangedListener(atTextWatcher);
下面是 AtTextWatcher 的完整代碼:
/**
* Created by Eshel on 2019/4/8.
*/
public class AtTextWatcher implements TextWatcher {
char atEndFlag = (char) 8197;
AtListener mListener;
private int atIndex = -1;
private int endFlagIndex = -1;
public AtTextWatcher(AtListener listener) {
this.mListener = listener;
}
public void insertTextForAt(EditText et, CharSequence text){
if(atIndex == -1)
return;
StringBuilder sb = new StringBuilder(text);
sb.append(atEndFlag);
text = sb.toString();
Editable text1 = et.getText();
text1.insert(atIndex+1, text);
// et.invalidate();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if(count == 1){//刪除一個字符
char c = s.charAt(start);
if(c == atEndFlag){
endFlagIndex = start;
}
}
}
/**
* @param s 新文本內容,即文本改變之後的內容
* @param start 被修改文本的起始偏移量
* @param before 被替換舊文本長度
* @param count 替換的新文本長度
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(count == 1){//新增(輸入)一個字符
char c = s.charAt(start);
if(c == '@'){
atIndex = start;
if(mListener != null){
mListener.triggerAt();
}
}
}
}
@Override
public void afterTextChanged(Editable s) {
Log.i(TAG, "afterTextChanged() called with: s = [" + s + "]");
if(endFlagIndex != -1){
int index = endFlagIndex;
while ((index -= 1) != -1){
char c = s.charAt(index);
if(c == '@'){
break;
}
}
int endFlagIndex = this.endFlagIndex;
this.endFlagIndex = -1;
if(index != -1)
s.delete(index, endFlagIndex);
}
}
/**
* 輸入 @ 監聽
*/
public interface AtListener{
void triggerAt();
}
}