天天看點

EditText實作@功能(一)

如标題所說, 今天要實作 @ 的效果, 明确要實作的效果邊界:

  • 輸入 @ 字元, 彈出一界面選擇好友(監聽回調)
  • 選擇好友後更新 @ 的内容
  • 删除 @ 内容将一次删除 @+好友名 所有字元

廢話不多說, 先上效果圖:

EditText實作@功能(一)

效果圖中我打開好友清單以延時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 的字元, 該字元跟空格符長得一樣但其實是兩個東西

分析完思路就有了:

    1. 通過TextWatcher監聽EditText
    1. 監聽到内容增加一字元并且該字元為 @ 符号執行監聽, 彈出選擇好友界面, 并記錄 @ 符号位置
    1. 當監聽到删除一字元并且該字元為@功能結束符, 編碼為 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();
    }
}