過年這段時間正比如較有空,並且有一個客服相關的需求,借這個機會把一年前寫的支持輸入表情和@mention的EditText又重構了一遍,具體見SpEditTool,重構過程當中對EditText選擇模式又有了一些新的認識,在這裏記錄下git
在實現響應軟鍵盤光標移動事件以前已經實現了讓光標不進入@mention字符串的邏輯(離start位置近就重置回start位置,離end位置近就重置回end位置),可是在光標只移動一格的狀況下會回退到以前的光標位置,光標永遠沒法跨過一個@mention字符串。因此對於軟鍵盤的光標移動時通過@mention須要特殊處理github
這種狀況比較好處理,無非是判斷光標是否進入了@mention內部,左移的時候就把selectionStart和selectionEnd都設置到@mention的start位置,右移的時候設置到end位置bash
這種狀況是使用軟鍵盤選中一段文字時出現微信
在處理這個場景時,我最開始犯了一個錯誤app
int selectionStart = Selection.getSelectionStart(text);
int selectionEnd = Selection.getSelectionEnd(text);
複製代碼
我認爲selectionStart表明簽名的光標位置,selectionEnd表明後面的光標位置,selectionStart必定小於等於selectionEnd。 由於光標左右移動並無參數表示是移動哪一個光標,因此最初實現的時候想固然的忽略了這個點,以爲左右移動只有兩種狀況:ide
光標 | 移動方向 | 結果 |
---|---|---|
前面的光標 | 左移 | 選中前面的@mention |
後面的光標 | 右移 | 選中後面的@mention |
然而實際的狀況是四種:ui
光標 | 移動方向 | 結果 |
---|---|---|
前面的光標 | 左移 | 選中左邊的@mention |
前面的光標 | 右移 | 取消選中左邊的@mention |
後面的光標 | 右移 | 選中右邊的@mention |
後面的光標 | 左移 | 取消選中右邊的@mention |
固然這樣寫出來的邏輯是有問題的,在編碼的過程當中發現其實selectionStart和selectionEnd的意思和本身最開始想的並不同this
因此知道了selectionStart/selectionEnd和左右移動方向就能夠覆蓋以上的四種狀況了,可是場景分類跟以前會有些區別編碼
selectionEnd光標移動方向 | selectionEnd>selectionStart | 結果 |
---|---|---|
左移 | true | 選中左邊的@mention |
左移 | false | 取消選中右邊的@mention |
右移 | true | 選中右邊的@mention |
右移 | false | 取消選中左邊的@mention |
對於Selection.setSelection(Spannable text, int start, int stop)
,start!=stop的狀況下,start表示選擇過程當中不變的光標,stop表示變化的光標spa
最終實現代碼
//處理光標左移事件
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT
&& keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
int selectionStart = Selection.getSelectionStart(text);
int selectionEnd = Selection.getSelectionEnd(text);
IntegratedSpan[] integratedSpans = text.getSpans(selectionEnd, selectionEnd, IntegratedSpan.class);
if (integratedSpans != null && integratedSpans.length > 0) {
for (IntegratedSpan span : integratedSpans) {
int spanStart = text.getSpanStart(span);
int spanEnd = text.getSpanEnd(span);
//selectionEnd表示移動的光標
if (spanEnd == selectionEnd) {
Selection.setSelection(text, selectionStart, spanStart);
return true;
}
}
}
}
//處理光標右移事件
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT
&& keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
int selectionStart = Selection.getSelectionStart(text);
int selectionEnd = Selection.getSelectionEnd(text);
IntegratedSpan[] integratedSpans = text.getSpans(selectionEnd, selectionEnd, IntegratedSpan.class);
if (integratedSpans != null && integratedSpans.length > 0) {
for (IntegratedSpan span : integratedSpans) {
int spanStart = text.getSpanStart(span);
int spanEnd = text.getSpanEnd(span);
if (spanStart == selectionEnd) {
Selection.setSelection(text, selectionStart, spanEnd);
return true;
}
}
}
}
複製代碼
兩個地方的setSelection可能有些反直覺,不過仔細想想確實是取消選中和選中用的是一樣的參數
有個朋友在使用這個庫的時候提了個Issues #7 ,就扔了一張圖
不得不說這張圖仍是挺有誤導性的,我最初一直覺得後面輸入的部分的樣式是來自於第一個@mention,並且後面一長串都帶了樣式,讓我認爲是持續輸入了多個字符都帶了樣式,這個現象挺讓我費解的,由於個人demo中全部setSpan(Object what, int start, int end, int flags)
的flags全都是SPAN_EXCLUSIVE_EXCLUSIVE
,按道理不會出現後面輸入的字符也帶樣式的狀況,本身嘗試復現也沒有成功
今天一個偶然的操做讓我能夠弄出圖上的效果,說下本身的操做路徑
以上操做能夠復現出Issues #7 中的問題,可是緣由卻不是第一個@mention的樣式影響到了後面的字符串,而是有兩個@mention,第二個@mention在選中狀態下被replace,樣式沒有消失
由於庫中自定義了一個SpannableStringBuilder,因此解決方案也比較簡單
@Override
public SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart,
int tbend) {
...
//先刪除再插入,解決選擇模式下span樣式不正常消失的問題
if (start != end && tbstart != tbend) {
super.replace(start, end, "", 0, 0);
super.insert(start, tb, tbstart, tbend);
} else {
super.replace(start, end, tb, tbstart, tbend);
}
...
return this;
}
複製代碼
固然有可能Issues #7的問題並非我這樣操做出現的,後續有碰到一樣問題的童鞋歡迎反饋
發現本身的東西有問題,固然得去試一試微信有沒有問題,畢竟行業標杆嘛。 使人失望的是微信的@mention並無上面的問題,不過微信的單個表情在選中時打字會沒有效果
反過頭看本身的表情輸入,通過上面的特別處理以後,選中單個表情輸入文字文字卻是照常輸進去了,可是表情居然沒刪掉
調試了一下發現選中表情時調用replace(int start, int end, CharSequence tb, int tbstart, int tbend)
,end只比start大1,可是demo中ImageSpan對應的字符串長度應該都是4,問題就出在這裏了,對一個表情,選中狀況下得replace4次才能被刪掉
緣由看了下代碼沒分析出來,不過解決方案卻是簡單,以前@mention已經實現了讓光標不能進入內部的邏輯,將對應的Span用IntegratedSpan標記下就好了
public class IsoheightImageSpan extends ImageSpan implements IntegratedSpan {
...
}
複製代碼
重構過程當中參考了iYaoy的思路,在此特別感謝