1. 程式人生 > >完美解決RecyclerView + EditText的輸入問題

完美解決RecyclerView + EditText的輸入問題

完美解決RecyclerView + EditText的輸入問題

一、實現效果

  • 問題描述

當EditText與RecyclerView結合使用,實現富文字編輯器,因為EditText的高度使用了WRAP_CONTENT的方式。導致使用者必須點選EditText才能觸發輸入法。問題不大,體驗不佳。理想的方式應該點選整個RecyclerView都能觸發輸入法。

  • 實現效果

EditText的控制元件大小以圖中紅線位置為邊界。頂部有24dp的Margin。並且實現使用者滑動到輸入法區域關閉輸入法的功能。

EditTouchHelper

二、下載地址

演示APK

EditTouchHelper

演示專案工程

三、使用方式

  1. 建立EditTouchHelper
    例項
this.editTouchHelper = new EditTouchHelper();
  1. 附加到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,所以完美解決了問題。