完美解決RecyclerView + EditText的輸入問題
完美解決RecyclerView + EditText的輸入問題
一、實現效果
- 問題描述
當EditText與RecyclerView結合使用,實現富文字編輯器,因為EditText的高度使用了WRAP_CONTENT的方式。導致使用者必須點選EditText才能觸發輸入法。問題不大,體驗不佳。理想的方式應該點選整個RecyclerView都能觸發輸入法。
- 實現效果
EditText的控制元件大小以圖中紅線位置為邊界。頂部有24dp的Margin。並且實現使用者滑動到輸入法區域關閉輸入法的功能。
二、下載地址
三、使用方式
- 建立
EditTouchHelper
this.editTouchHelper = new EditTouchHelper();
- 附加到
RecyclerView
editTouchHelper.attach(recyclerView);
Done. 2句程式碼,已經實現演示圖的效果。
四、程式碼解析
之前實現過一個版本,通過繼承RecyclerView
,過載dispatchTouchEvent
方法的方式實現。使用繼承方式過於暴力,非常不優雅。
當前版本的實現通過OnItemTouchListener
,動態新增到RecyclerView
的方式實現,比前個版本優雅許多。
1. 實現RecyclerView.OnItemTouchListener
介面
實現該介面來處理Touch事件。
public class EditTouchHelper implements RecyclerView.OnItemTouchListener {
}
2. 過載onInterceptTouchEvent
方法
在該方法中,判斷Touch事件是否傳入到RecyclerView的子控制元件中,如果沒有,則尋找EditText子控制元件,並將事件傳給EditText。
int action = e.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
this.target = findTarget(rv, e);
if (target != null) {
rect = getRect(rv, target);
}
break;
}
}
3. 判斷Touch事件下是否有子控制元件
這裡將X座標調整到RecyclerView的中間,為確保子控制元件設定了左右Margin時,findChildViewUnder()
方法返回null
的問題。(更好的解決方案或許是重新寫個方法。)如果findChildViewUnder()
返回null
則使用最後一個子控制元件或者第一個子控制元件。
float x = rv.getWidth() / 2;
float y = e.getY();
View view = rv.findChildViewUnder(x, y);
if (view == null) {
while (rv.getChildCount() != 0) {
// try last one
{
view = rv.getChildAt(rv.getChildCount() - 1);
rect = getRect(rv, view);
if (y > rect.bottom) {
break;
}
}
// try first one
{
view = rv.getChildAt(0);
rect = getRect(rv, view);
if (y < rect.top) {
break;
}
}
// remain null
{
view = null;
break;
}
}
}
4. 查詢EditText
這裡查詢的是TextView,當TextViewisTextSelectable
時,認為可互動的,應該使之可以彈出選擇選單。
TextView findTextView(View child) {
if (child.getVisibility() != View.VISIBLE) {
return null;
}
if (!child.isEnabled()) {
return null;
}
if (child instanceof EditText) {
return (EditText)child;
}
if (child instanceof TextView) {
TextView view = (TextView)child;
if (view.onCheckIsTextEditor()) {
return view;
}
if (view.isTextSelectable()) {
return view;
}
return null;
}
if (child instanceof ViewGroup) {
ViewGroup layout = (ViewGroup)child;
for (int i = 0, size = layout.getChildCount(); i < size; i++) {
TextView view = findTextView(layout.getChildAt(i));
if (view != null) {
return view;
}
}
}
return null;
}
5. 調整座標,傳遞給EditText
建立相對於EditText的座標。
MotionEvent obtain(MotionEvent e) {
MotionEvent event = MotionEvent.obtain(e);
float x = e.getX();
if (x < rect.left) {
x = 0;
} else if (x > rect.right) {
x = target.getWidth();
} else {
x = (x - rect.left);
}
float y = e.getY();
if (y < rect.top) {
y = 0;
} else if (y > rect.bottom) {
y = target.getHeight();
} else {
y = (y - rect.top);
}
event.setLocation(x, y);
return event;
}
if (target != null) {
MotionEvent event = this.obtain(e);
target.dispatchTouchEvent(event);
event.recycle();
}
五、遺留問題
這裡假定了RecyclerView中可互動的控制元件只有EditText,並且所有子控制元件沒有上下Margin。
前者導致出現其他可互動的控制元件如Button時,可能導致無法響應問題。
後者導致在2個子控制元件Margin區域,無法觸發輸入法問題。
目前神馬筆記的編輯器只有一個EditText,所以完美解決了問題。