EditText點選外部退出編輯

weixin_33860722發表於2017-04-17

思路:重寫EdieText父佈局的dispatchTouchEvent()方法,判斷EditText正在獲取焦點並且點選外部時呼叫EditText的clearFocus()方法,並關閉軟鍵盤。


public class EditTextLinearLayout  extends  LinearLayout {

    public EditTextLinearLayout(Context context,AttributeSet attrs) {

        super(context,attrs);

        setFocusableInTouchMode(true);

    }

    @Override

    public boolean dispatchTouchEvent(MotionEvent ev) {

        if(ev.getAction() == MotionEvent.ACTION_DOWN) {

            View v = ((Activity)getContext()).getCurrentFocus();

            if(isShouldHideInput(v,ev)) {

                InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

                if(imm !=null) {

                    imm.hideSoftInputFromWindow(v.getWindowToken(),0);

                }

            }

        }

        return super.dispatchTouchEvent(ev);

    }

    private boolean isShouldHideInput(View v,MotionEvent event) {

        if(v !=null&& (vinstanceofEditText)) {

            int[] leftTop = {0,0};

            //獲取輸入框當前的location位置

            v.getLocationInWindow(leftTop);

            intleft = leftTop[0];

            inttop = leftTop[1];

            intbottom = top + v.getHeight();

            intright = left + v.getWidth();

            if(event.getX() > left && event.getX() < right

            && event.getY() > top && event.getY() < bottom) {

                // 點選的是輸入框區域,保留點選EditText的事件

                return false;

            }else{

                v.clearFocus();

                return true;

            }

        }

        return false;

    }

Android應用中clearFocus方法呼叫無效的問題解決

clearFocus 無效?

EditText在focus與非focus的時候,顯示效果是不同的:focus的時候游標是閃的,而且我們通常也會給它設定selector,focus的時候給它加上邊框之類的.

通常當我們觸控EditText之外的View時,需要清除EditText的焦點.很自然的就會想到EditText.clearFocus(),然而常常並沒有用.(EditText.isFocus()依然是true,游標也依然在跳躍...)

clearFocus的實現

clearFocus的呼叫棧(重要的部分):

View.clearFocus() ->

View.clearFocusInternal() ->

{

  1. mParent.clearChildFocus(this);// 從該View一直向上遍歷父節點,知道DecorView,作用是將parent(ViewGroup)中儲存的mFocus設定為null,即清除焦點

  2. rootViewRequestFocus();// 呼叫DecorView的requestFocus()方法,作用是找到檢視中的一個View,並將其設定為焦點

}

根據上面列出的呼叫棧可以看出,清除focus其實包含2個部分的操作:

清除當前當前View的focus標誌,並且清除它的祖先節點中儲存的mFocus資訊

呼叫DecorView的requestFocus()方法,重新尋找一個View,並將其設定為focus

requestFocus()的實現

requestFocus(int)支援FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT 4個引數來表示focus的流向,然而事實上傳入的方向引數並沒有作用.(這個其實比較好理解,以FOCUS_RIGHT來說,是該選擇右子樹種的View,還是繪製在右邊的View呢?)

不管傳怎樣的引數,requestFocus()都是以先序遍歷的方式,找到第一個focusInTouchMode的View,並將其設定為焦點.

設定的方式是:

給當前View focus標誌(mPrivateFlags)

呼叫mParent.requestChildFocus()將自己賦值給其父View的mFocus,然後父View再呼叫mParent.requestChildFocus()一直到DecorView.

這樣從DecorView開始,只要根據mFocus就可以找到真正focus的View


    @Override

    public View findFocus() {

        if (DBG) {

            System.out.println("Find focus in " + this + ": flags="

            + isFocused() + ", child=" + mFocused);

        }

        if (isFocused()) {

            return this;

        }

        if (mFocused != null) {

            return mFocused.findFocus();

        }

        return null;

    }

注意:按照requestFocus這種尋找策略,那麼給定一個起始點,那麼尋找到的View將始終相同,也就是說,你多次呼叫DecorView.requestFocus(),獲得的焦點都是相同的,如果沒有改變檢視層級以及focusable的話.因此當你想讓某個特定的View獲得焦點的話,就應該直接呼叫它的requestFocus()方法.

tips:對於ViewGroup來說,可以通過descendantFocusability的設定來選擇優先讓parent,還是child獲得焦點.可選值:FOCUS_BEFORE_DESCENDANTS(預設), FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS.

clearFocus 真的無效嗎?

當然不是,之所以有時候發現EditText.clearFocus()無效,是因為:清除focus之後,還會按照先序遍歷的順序查詢一個focusInTouchMode的View,並將其設定為focus,而你的EditText恰好是這第一個符合條件的View.(因此不是沒清除成功,而是清除了之後,又給設定上了!!)

知道了原因之後,解決就很簡單了,找一個在EditText之前的View,將其設定為可獲得焦點的

View.setFocusableInTouchMode(true)

android:focusableInTouchMode="true"

如果不知道怎樣找到一個在EditText之前的View的話,那你可以直接選擇它的parent (xxxLayout),因為ViewGroup預設的策略是: FOCUS_BEFORE_DESCENDANTS

判斷是否focus

isFocused(), 它判斷自己是否擁有焦點

hasFocus(), 它判斷自己或著自己的child是否擁有焦點 常用

相關文章