高仿SinaWeibo新浪微博釋出頁面話題效果
最近做了一個仿新浪微博話題效果的功能,網上搜索了幾個效果,都存在一定問題,最終借鑑別人的思路,完成這一套效果.
首先,我們拆分邏輯以及開發順序.
1,實現話題變色效果
2,實現插入話題效果
3,實現話題選中刪除效果
4,實現點選話題,游標在話題之後
下面我們就一步一步實現效果.
一,實現話題變色
實現邏輯主要是通過EditText
的addTextChangedListener()
來進行監聽文字變動,通過正則表示式來匹配出文字中的話題.
利用正則表示式獲取全部話題:
//正則表示式,一定要和伺服器以及 iOS 端統一
private static final String topicRegex = "#([^#]+?)#";
public static ArrayList<String> findTopic(String s) {
Pattern p = Pattern.compile(topicRegex);
Matcher m = p.matcher(s);
ArrayList<String> list = new ArrayList<>();
while (m.find()) {
list.add(m.group());
}
return list;
}
通過正則表示式匹配出的話題集合,遍歷出每個話題的 startIndex 位置(後來發現Matcher
方法是有獲取 index 的方法),並且通過EditText.getText()
方法獲取的Editable
直接對文字進行操作:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.i("MainActivity", "onTextChanged");
if (TextUtils.isEmpty(s)) return ;
//1,查詢話題
String content = s.toString();
mTopicList.clear();
mTopicList.addAll(findTopic(s.toString()));
//2,為查找出的變色
//首先要為editable,去除之前設定的colorSpan
Editable editable = mEditText.getText();
for (int i = 0; i < mColorSpans.size(); i++) {
editable.removeSpan(mColorSpans.get(i));
}
mColorSpans.clear();
//為editable,中的話題加入colorSpan
int findPos = 0;
int size = mTopicList.size();
for (int i = 0; i < size; i++) {//便利話題
String topic = mTopicList.get(i);
findPos = content.indexOf(topic, findPos);
if (findPos != -1) {
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.BLUE);
editable.setSpan(colorSpan, findPos, findPos = findPos + topic.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mColorSpans.add(colorSpan);
}
}
}
二,實現插入話題效果
新浪微博的插入話題效果,是開啟一個新的頁面,點選一個話題後,插入游標所在的位置.這裡我用一個按鈕模仿了這個動作.要注意的是,要記錄插入之前游標的位置,並且在插入話題後,把游標放置在話題之後:
if (v.getId() == R.id.button) {
//插入話題
int selectionStart = mEditText.getSelectionStart();
//下面這些操作也可以直接替換為操作 EditText 的Editable實現,下面的程式碼 review 後看起來由點蠢,直接呼叫 insert 方法比較巧妙
String content = mEditText.getText().toString();
String firstStr = content.substring(0, selectionStart);
String secondStr = content.substring(selectionStart, content.length());
String insertTopic = "#這是一個插入的話題#";
mEditText.setText(firstStr + insertTopic + secondStr);
mEditText.setSelection(selectionStart + insertTopic.length());
}
三,實現話題選中刪除效果
這裡也是要時時判斷游標所在的位置,當光標出現在話題之後,再次點選刪除,就截獲為選中話題效果,實現起來也是很簡單的.
但是要注意的是,我們不能利用 activity 裡面的onKeyDown()
和onKeyUp()
兩個回撥,通過 log 發現文字變動和按鍵點選的回撥順序為beforeTextChanged->onTextChanged->afterTextChanged->onKeyDown->onKeyUp
.
這也說明了如果通過 攔截onKeyDown()
和onKeyUp()
兩個回撥時,文字是已經刪除之後的文字,並能有效的達到我們要實現的目的,那麼有沒有是文字改變之前就能擷取到按鍵的方法呢?
其實我們可以通過監聽EditText
的setOnKeyListener()
方法來監聽按鍵(onKey->beforeTextChanged->onTextChanged->afterTextChanged->onKeyDown->onKeyUp
):
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i("MainActivity", "onKey");
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN && mCheckBox2.isChecked()) {
int selectionStart = mEditText.getSelectionStart();
int selectionEnd = mEditText.getSelectionEnd();
//如果游標起始和結束在同一位置,說明是選中效果,直接返回 false 交給系統執行刪除動作
if (selectionStart != selectionEnd) {
return false;
}
Editable editable = mEditText.getText();
String content = editable.toString();
int lastPos = 0;
int size = mTopicList.size();
//遍歷判斷游標的位置
for (int i = 0; i < size; i++) {
String topic = mTopicList.get(i);
lastPos = content.indexOf(topic, lastPos);
if (lastPos != -1) {
if (selectionStart != 0 && selectionStart >= lastPos && selectionStart <= (lastPos + topic.length())) {
//選中話題
mEditText.setSelection(lastPos, lastPos + topic.length());
return true;
}
}
lastPos += topic.length();
}
}
return false;
}
四,實現點選話題,游標在話題之後
邏輯處理就是監聽EditText
的點選事件,處理游標所在位置,如果在位置在話題內,立即放置在話題的後面,邏輯比較簡單,直接上程式碼:
if (v.getId() == R.id.editText && mCheckBox1.isChecked()) {
int selectionStart = mEditText.getSelectionStart();
int lastPos = 0;
int size = mTopicList.size();
for (int i = 0; i < size; i++) {
String topic = mTopicList.get(i);
lastPos = mEditText.getText().toString().indexOf(topic, lastPos);
if (lastPos != -1) {
if (selectionStart >= lastPos && selectionStart <= (lastPos + topic.length())) {
//在這position 區間就移動游標
mEditText.setSelection(lastPos + topic.length());
}
}
lastPos = lastPos + topic.length();
}
}
最終還是有點小遺憾沒有完成.當拷貝一段已經變色的話題,再貼上到文字後,無法刪除掉拷貝進來文字的變色效果,嘗試呼叫Editable.clearSpans()
去除全部 span 也無法實現,並且這個方法會導致直接卡死.後來直接使用EditText.setText()
控制文字,確實達到了效果,但是當輸入法是類似 mx4預設輸入法效果時(效果大概是,輸入的拼音直接顯示在 EditText 裡面,點選被選漢字,替換掉拼音),會把輸入的拼音放入到文本里,所以上面的程式碼,我一般是直接操作 Editable.
如果有更好的方法或者修復了上面的小遺憾,請留言指教.