• [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

  • 相关阅读:
    [CLYZ2017]day10
    标签
    FJOI2017一试滚粗
    [学习笔记]一些求gcd的方法的证明
    WC2017有感
    [学习笔记]splay
    [CLYZ2017]day9
    [CLYZ2017]day6
    转:Asp.net模板引擎技术(html)
    精华:ASP.NET开发网站程序安全解决方案(防注入等)
  • 原文地址:https://www.cnblogs.com/myzh/p/3664544.html
Copyright © 2020-2023  润新知