[Android學習筆記]理解焦點處理原理的相關記錄


焦點處理相關記錄#

以下所涉及的焦點部分,只是按鍵移動部分,不明確包含Touch Focus部分

需解決問題##

控件的下一個焦點是哪?

分析思路##

當用戶通過按鍵(遙控器等)觸發焦點切換時,事件指令會通過底層進行一系列處理。
在ViewRootImpl.java中有一個方法,deliverKeyEventPostIme(...),因為涉及到底層代碼,所以沒有詳細的跟蹤分析此方法的調用邏輯,根據網上的資料,按鍵相關的處理會經過此方法。

    private void deliverKeyEventPostIme(QueuedInputEvent q) {
        ...
        // Handle automatic focus changes.
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int direction = 0;
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_LEFT;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_RIGHT;
                    }
                    break;
                ...
            }
            if (direction != 0) {
                View focused = mView.findFocus();
                if (focused != null) {
                    View v = focused.focusSearch(direction);
                    if (v != null && v != focused) {
                       .....
                        if (v.requestFocus(direction, mTempRect)) {
                            ...finishInputEvent(q, true);
                            return;
                        }
                    }
                    ...
                }
            }

由此方法可以看出,最主要的兩個核心過程:

    View v = focused.focusSearch(direction);
    v.requestFocus(direction, mTempRect)

接下來詳細的分析下,看看過程中進行了什么操作

具體分析###

在具體分析前,首先我們先明確下相關變量的定義

View mView : 主體View[DecorView]

        //一般把主View“DecorView”添加到WindowManagerImpl中(通過addView)
        //WindowManagerImpl.java
            private void addView(View view...) {
                ViewRootImpl root;
                root = new ViewRootImpl(view.getContext());
                ...
                root.setView(view, wparams, panelParentView);
                ...
            }        
        //ViewRootImpl.java
        public void setView(View view....) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;
                    ...
                }
            ...
            }
        }

所以mView是一個DecorView類型的變量.

View focused :

        View focused = mView.findFocus();        
        //PhoneWindow.java
        private final class DecorView extends FrameLayout implements RootVie.... {
            ...
        }        
        //FrameLayout.java
        public class FrameLayout extends ViewGroup {
            ...
        }        
        //ViewGroup.java
        //mFocused記錄的是當前被焦點選中的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;
    }

所以最終得到的focused為當前頁面中得到焦點的view.

在明確的相關變量后,我們開始View v = focused.focusSearch(direction)的具體分析.

   //View.java
   public View focusSearch(int direction) {
   //如果存在父控件,則執行父控件的focusSearch方法
      if (mParent != null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }
    //ViewGroup.java
    public View focusSearch(View focused, int direction) {
        //判斷是否為頂層布局,若是則執行對應方法,若不是則繼續向上尋找,說明會從內到外的一層層進行判斷,直到最外層的布局為止
        if (isRootNamespace()) {
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

說明在這個過程中,其實是從里層開始一直遍歷到最外層布局,然后在最外層布局將處理交給了FocusFinder中的方法.

    FocusFinder.getInstance().findNextFocus(this, focused, direction);

那我們來看看此方法具體做了什么操作

    //FocusFinder.java
    public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }
    //FocusFinder.java
    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }

發現在findNextFocus的執行過程的開始,先執行了findNextUserSpecifiedFocus(...)方法,由代碼可以看出,此方法先去判斷特定Id值是否存在,若存在則查詢出Id對應的view.其實這些Id就是xml里通過android:nextFocusUp="..."等或者代碼特別指定的焦點順序.所以在此過程先判斷,若存在,說明下個焦點已經找到,直接返回.

    //FocusFinder.java
    private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
        // check for user specified next focus
        View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
        if (userSetNextFocus != null && userSetNextFocus.isFocusable()
                && (!userSetNextFocus.isInTouchMode()
                        || userSetNextFocus.isFocusableInTouchMode())) {
            return userSetNextFocus;
        }
        return null;
    }    
    //View.java
    View findUserSetNextFocus(View root, int direction) {
        switch (direction) {
            case FOCUS_LEFT:
                if (mNextFocusLeftId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusLeftId);
            case FOCUS_RIGHT:
                if (mNextFocusRightId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusRightId);
            case FOCUS_UP:
                if (mNextFocusUpId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusUpId);
            case FOCUS_DOWN:
                if (mNextFocusDownId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusDownId);
            case FOCUS_FORWARD:
                if (mNextFocusForwardId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusForwardId);
            case FOCUS_BACKWARD: {
                if (mID == View.NO_ID) return null;
                final int id = mID;
                return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                    @Override
                    public boolean apply(View t) {
                        return t.mNextFocusForwardId == id;
                    }
                });
            }
        }
        return null;
    }

如果上面過程沒有查詢到,則會執行到findNextFocus(...)方法.在這個方法中,先通過offsetDescendantRectToMyCoords(...)方法獲得焦點控件的位置矩陣.然后通過比較得到下一個焦點的控件。具體的比較規則可以查看findNextFocusInRelativeDirection(...)方法與findNextFocusInAbsoluteDirection(...)方法.

    //FocusFinder.java
    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        if (focused != null) {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
            }
            // fill in interesting rect from focused
            focused.getFocusedRect(focusedRect);
            root.offsetDescendantRectToMyCoords(focused, focusedRect);
        } else {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
                // make up a rect at top left or bottom right of root
                switch (direction) {
                    case View.FOCUS_RIGHT:
                    case View.FOCUS_DOWN:
                        setFocusTopLeft(root, focusedRect);
                        break;
                    case View.FOCUS_FORWARD:
                        if (root.isLayoutRtl()) {
                            setFocusBottomRight(root, focusedRect);
                        } else {
                            setFocusTopLeft(root, focusedRect);
                        }
                        break;
                    case View.FOCUS_LEFT:
                    case View.FOCUS_UP:
                        setFocusBottomRight(root, focusedRect);
                        break;
                    case View.FOCUS_BACKWARD:
                        if (root.isLayoutRtl()) {
                            setFocusTopLeft(root, focusedRect);
                        } else {
                            setFocusBottomRight(root, focusedRect);
                        break;
                    }
                }
            }
        }
        switch (direction) {
            case View.FOCUS_FORWARD:
            case View.FOCUS_BACKWARD:
                return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                        direction);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return findNextFocusInAbsoluteDirection(focusables, root, focused,
                        focusedRect, direction);
            default:
                throw new IllegalArgumentException("Unknown direction: " + direction);
        }
    }

結論###

查找焦點的過程,主要是從View的focusSearch(...)方法開始,從當前焦點開始逐層往外,最終在最外層布局執行FocusFinder中的核心方法來獲得下個焦點所在的視圖view.

如果需要指定跳轉,可以在逐層focusSearch(...)的時候,返回特定的view


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM