zoukankan      html  css  js  c++  java
  • View Focus的处理过程及ViewGroup的mFocused字段分析

      通过上篇的介绍,我们知道在对KeyEvent的处理中有非常重要的一环,那就是KeyEvent在focus view的path上自上而下的分发,

    换句话说只有focus的view才有资格参与KeyEvent的处理,所以说focused view在KeyEvent的处理中很重要,我们需要弄清楚明白

    focus view是如何设置以及改变的。

      通过Android官方文档http://developer.android.com/reference/android/view/View.html中关于Focus Handling的介绍,

    我们知道framework会根据用户的输入处理常规的focus移动,包括当删除、隐藏或添加新的view时改变focus。一个view有资格获得

    focus的前提是isFocusable()方法返回true,你可以通过setFocusable(boolean)方法来设置它。另外当在touch mode下的时候,还

    需要isFocusableInTouchMode()也返回true,你也可以通过setFocusableInTouchMode(boolean)来设置它。focus的移动是基于这

    样的算法,它尝试在某个给定的方向上找最临近的view,设置它为新的focus。在极个别情况,如果默认的算法不符合你的需求,你也可以

    在xml布局文件中通过显式指定nextFocusDown/Left/Right/Up这些属性来表明focus移动的顺序。在运行时刻,你也可以通过调用

    View.requestFocus()方法来动态地让某个view获得focus。作为开始,我们先看看这几个具备获得焦点前提的方法,如下:

        /**
         * Returns whether this View is able to take focus.
         *
         * @return True if this view can take focus, or false otherwise.
         * @attr ref android.R.styleable#View_focusable
         */
        @ViewDebug.ExportedProperty(category = "focus")
        public final boolean isFocusable() {
            return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); // 各种位操作,不熟悉、习惯的同学可以翻本C语言的书看看,
        }                                                      // 这里顺便推荐下《C Primer Plus》,一本足矣,而且里面
                                                               // 有一章是专门介绍bit操作的应用的,非常赞!!!
        /**
         * When a view is focusable, it may not want to take focus when in touch mode.
         * For example, a button would like focus when the user is navigating via a D-pad
         * so that the user can click on it, but once the user starts touching the screen,
         * the button shouldn't take focus
         * @return Whether the view is focusable in touch mode.
         * @attr ref android.R.styleable#View_focusableInTouchMode
         */
        @ViewDebug.ExportedProperty
        public final boolean isFocusableInTouchMode() {
            return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
        }
    
        /**
         * Set whether this view can receive the focus.
         *
         * Setting this to false will also ensure that this view is not focusable
         * in touch mode.
         *
         * @param focusable If true, this view can receive the focus.
         *
         * @see #setFocusableInTouchMode(boolean)
         * @attr ref android.R.styleable#View_focusable
         */
        public void setFocusable(boolean focusable) {
            if (!focusable) { // 注意:是false的时候,会顺便保证在touch mode下也不能获得focus
                setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
            }
            setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); // 设置FOCUSABLEB位
        }
    
        /**
         * Set whether this view can receive focus while in touch mode.
         *
         * Setting this to true will also ensure that this view is focusable.
         *
         * @param focusableInTouchMode If true, this view can receive the focus while
         *   in touch mode.
         *
         * @see #setFocusable(boolean)
         * @attr ref android.R.styleable#View_focusableInTouchMode
         */
        public void setFocusableInTouchMode(boolean focusableInTouchMode) {
            // Focusable in touch mode should always be set before the focusable flag
            // otherwise, setting the focusable flag will trigger a focusableViewAvailable()
            // which, in touch mode, will not successfully request focus on this view
            // because the focusable in touch mode flag is not set
            setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
            if (focusableInTouchMode) { // 如果是true顺便打开FOCUSABLE位
                setFlags(FOCUSABLE, FOCUSABLE_MASK);
            }
        }

        接下来我们就看看本文的重点View.requestFocus()等相关方法,代码如下:

    /**
         * Call this to try to give focus to a specific view or to one of its
         * descendants.
         *
         * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
         * false), or if it is focusable and it is not focusable in touch mode
         * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
         *
         * See also {@link #focusSearch(int)}, which is what you call to say that you
         * have focus, and you want your parent to look for the next one.
         *
         * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
         * {@link #FOCUS_DOWN} and <code>null</code>.
         *
         * @return Whether this view or one of its descendants actually took focus.
         */
        public final boolean requestFocus() {
            return requestFocus(View.FOCUS_DOWN);
        }
    
        /**
         * Call this to try to give focus to a specific view or to one of its
         * descendants and give it a hint about what direction focus is heading.
         *
         * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
         * false), or if it is focusable and it is not focusable in touch mode
         * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
         *
         * See also {@link #focusSearch(int)}, which is what you call to say that you
         * have focus, and you want your parent to look for the next one.
         *
         * This is equivalent to calling {@link #requestFocus(int, Rect)} with
         * <code>null</code> set for the previously focused rectangle.
         *
         * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
         * @return Whether this view or one of its descendants actually took focus.
         */
        public final boolean requestFocus(int direction) {
            return requestFocus(direction, null);
        }
    
        /**
         * Call this to try to give focus to a specific view or to one of its descendants
         * and give it hints about the direction and a specific rectangle that the focus
         * is coming from.  The rectangle can help give larger views a finer grained hint
         * about where focus is coming from, and therefore, where to show selection, or
         * forward focus change internally.
         *
         * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
         * false), or if it is focusable and it is not focusable in touch mode
         * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
         *
         * A View will not take focus if it is not visible.
         *
         * A View will not take focus if one of its parents has
         * {@link android.view.ViewGroup#getDescendantFocusability()} equal to
         * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
         *
         * See also {@link #focusSearch(int)}, which is what you call to say that you
         * have focus, and you want your parent to look for the next one.
         *
         * You may wish to override this method if your custom {@link View} has an internal
         * {@link View} that it wishes to forward the request to.
         *
         * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
         * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
         *        to give a finer grained hint about where focus is coming from.  May be null
         *        if there is no hint.
         * @return Whether this view or one of its descendants actually took focus.
         */
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            return requestFocusNoSearch(direction, previouslyFocusedRect);
        }
    
        private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // 此方法就是最终被调用的版本
            // need to be focusable
            if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || // 不是FOCUSABLE,即没资格获取焦点
                    (mViewFlags & VISIBILITY_MASK) != VISIBLE) { // 或者不是VISIBLE的,都直接返回false,表示请求获取焦点失败
                return false; // 所以除了上文提到的2个获取focus的前提,其实这里的VISIBLE也应该算是第3个前提吧!
            }
    
            // need to be focusable in touch mode if in touch mode
            if (isInTouchMode() && // 同样在touch mode下,也要检测FOCUSABLE_IN_TOUCH_MODE标志
                (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
                   return false; // 不满足也直接返回false,表示失败
            }
    
            // need to not have any parents blocking us
            if (hasAncestorThatBlocksDescendantFocus()) { // parents阻止我们获得焦点的话,我们也只能以失败告终
                return false;
            }
            // 以上重重关卡都通过了,才会走到这里,真正设置focus
            handleFocusGainInternal(direction, previouslyFocusedRect);
            return true;
        }
    
        /**
         * Give this view focus. This will cause
         * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
         *
         * Note: this does not check whether this {@link View} should get focus, it just
         * gives it focus no matter what.  It should only be called internally by framework
         * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
         *
         * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
         *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
         *        focus moved when requestFocus() is called. It may not always
         *        apply, in which case use the default View.FOCUS_DOWN.
         * @param previouslyFocusedRect The rectangle of the view that had focus
         *        prior in this View's coordinate system.
         */
        void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
            if (DBG) {
                System.out.println(this + " requestFocus()");
            }
    
            if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { // 只有当前View不是focused view时才会发生一系列操作,否则do nothing
                mPrivateFlags |= PFLAG_FOCUSED; // 如果没focus的话,先设置此view的focused标志,isFocused,hasFocus等方法会检测此标志
    
                View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null; // 找到之前的focus
    
                if (mParent != null) {
                    mParent.requestChildFocus(this, this); // 如果有mParent,则将此新focus请求向上传递
                }
    
                if (mAttachInfo != null) { // callback接口,将focus change事件notify出去
                    mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
                }
    
                onFocusChanged(true, direction, previouslyFocusedRect);
                refreshDrawableState();
            }
        }

    接着我们看下关于PFLAG_FOCUSED标志位相关的几个方法,如下:

    /**
         * Returns true if this view has focus
         *
         * @return True if this view has focus, false otherwise.
         */
        @ViewDebug.ExportedProperty(category = "focus")
        public boolean isFocused() {
            return (mPrivateFlags & PFLAG_FOCUSED) != 0;
        }
    
        /**
         * Find the view in the hierarchy rooted at this view that currently has
         * focus.
         *
         * @return The view that currently has focus, or null if no focused view can
         *         be found.
         */
        public View findFocus() {
            return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
        }
    
        /**
         * Returns true if this view has focus iteself, or is the ancestor of the
         * view that has focus.
         *
         * @return True if this view has or contains focus, false otherwise.
         */
        @ViewDebug.ExportedProperty(category = "focus")
        public boolean hasFocus() { // 对View来说,hasFocus和isFocus是相同的,ViewGroup类重载了此方法
            return (mPrivateFlags & PFLAG_FOCUSED) != 0;
        }

      最后,我们看看ViewParent接口(以及其实现ViewGroup)的requestChildFocus()实现,代码如下:

        /**
         * Called when a child of this parent wants focus
         * 
         * @param child The child of this ViewParent that wants focus. This view
         *        will contain the focused view. It is not necessarily the view that
         *        actually has focus.
         * @param focused The view that is a descendant of child that actually has
         *        focus
         */
        public void requestChildFocus(View child, View focused); // parent中的某个child请求获得focus,child要么是focused,
                                                                 // 要么是focused的parent
        // 我们可以看到其实现类有ViewGroup、ScrollView等,这里我们看下ViewGroup类的,其他的有兴趣的同学可以自行研究
    
        @Override
        public void requestChildFocus(View child, View focused) {
            if (DBG) {
                System.out.println(this + " requestChildFocus()");
            }
            if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
                return; // 如果此ViewGroup被设置为阻止任何children获得focus,则直接返回
            }
    
            // Unfocus us, if necessary
            super.unFocus(); // 先unFocus 此ViewGroup
    
            // We had a previous notion of who had focus. Clear it.
            if (mFocused != child) { // 如果mFocused不同于传递进来的child,则更新mFocused
                if (mFocused != null) {
                    mFocused.unFocus(); // 让旧的放弃focus
                }
    
                mFocused = child; // 更新mFocused
            }
            if (mParent != null) { // 接着沿着focus path往上传递(递归调用)
                mParent.requestChildFocus(this, focused); // 注意这里的第2个参数,一直是传递进来的focused不变
            }
        }

    我们注意到只有ViewGroup才有mFocused字段,表示focus path上的一个节点。我们看看与之相关的代码:

        // The view contained within this ViewGroup that has or contains focus.
        private View mFocused; // 此ViewGroup中的child view,它要么是focused view本身要么包含focused view
    
        @Override
        void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { // 此方法重载了View中的
            if (mFocused != null) { // 添加了对mFocused的处理
                mFocused.unFocus(); // 让mFocused unFocus在这种情况下
                mFocused = null;
            }
            super.handleFocusGainInternal(direction, previouslyFocusedRect);
        }
    
        /**
         * {@inheritDoc}
         */
        public void clearChildFocus(View child) {
            if (DBG) {
                System.out.println(this + " clearChildFocus()");
            }
    
            mFocused = null; // 清空
            if (mParent != null) { // 将事件告诉parent
                mParent.clearChildFocus(this);
            }
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public void clearFocus() {
            if (DBG) {
                System.out.println(this + " clearFocus()");
            }
            if (mFocused == null) { // 如果没有mFocused,则ViewGroup自身clearFocus
                super.clearFocus();
            } else { // 否则,让mFocused clearFocus,并且重置为null
                View focused = mFocused;
                mFocused = null;
                focused.clearFocus();
            }
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        void unFocus() { // 大体同clearFocus,只是调的是unFocus方法
            if (DBG) {
                System.out.println(this + " unFocus()");
            }
            if (mFocused == null) {
                super.unFocus();
            } else {
                mFocused.unFocus();
                mFocused = null;
            }
        }
    
        /**
         * Returns the focused child of this view, if any. The child may have focus
         * or contain focus.
         *
         * @return the focused child or null.
         */
        public View getFocusedChild() { // 返回这个字段,供客户端代码使用
            return mFocused;
        }
    
        /**
         * Returns true if this view has or contains focus
         *
         * @return true if this view has or contains focus
         */
        @Override
        public boolean hasFocus() { // ViewGroup自己是focused或者其子、孙后代包含focused view
            return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
        }
    
        /*
         * (non-Javadoc)
         *
         * @see android.view.View#findFocus()
         */
        @Override
        public View findFocus() {
            if (DBG) {
                System.out.println("Find focus in " + this + ": flags="
                        + isFocused() + ", child=" + mFocused);
            }
    
            if (isFocused()) { // 自己是focused,直接返回this
                return this;
            }
    
            if (mFocused != null) { // 否则,mFocused不为空,则沿着这条线往下继续找
                return mFocused.findFocus();
            }
            return null;
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasFocusable() {
            if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
                return false;
            }
    
            if (isFocusable()) {
                return true;
            }
    
            final int descendantFocusability = getDescendantFocusability();
            if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
                final int count = mChildrenCount;
                final View[] children = mChildren;
    
                for (int i = 0; i < count; i++) {
                    final View child = children[i];
                    if (child.hasFocusable()) {
                        return true;
                    }
                }
            }
    
            return false;
        }

    接着我们看下View自己的unFocus、clearFocus实现,代码如下:

        /**
         * Called internally by the view system when a new view is getting focus.
         * This is what clears the old focus.
         * <p>
         * <b>NOTE:</b> The parent view's focused child must be updated manually
         * after calling this method. Otherwise, the view hierarchy may be left in
         * an inconstent state.
         */
        void unFocus() {
            if (DBG) {
                System.out.println(this + " unFocus()");
            }
    
            clearFocusInternal(false, false);
        }
    
        /**
         * Called when this view wants to give up focus. If focus is cleared
         * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
         * <p>
         * <strong>Note:</strong> When a View clears focus the framework is trying
         * to give focus to the first focusable View from the top. Hence, if this
         * View is the first from the top that can take focus, then all callbacks
         * related to clearing focus will be invoked after wich the framework will
         * give focus to this view.
         * </p>
         */
        public void clearFocus() {
            if (DBG) {
                System.out.println(this + " clearFocus()");
            }
    
            clearFocusInternal(true, true);
        }
    
        /**
         * Clears focus from the view, optionally propagating the change up through
         * the parent hierarchy and requesting that the root view place new focus.
         *
         * @param propagate whether to propagate the change up through the parent
         *            hierarchy
         * @param refocus when propagate is true, specifies whether to request the
         *            root view place new focus
         */
        void clearFocusInternal(boolean propagate, boolean refocus) {
            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { // 如果当前是focused,先清掉PFLAG_FOCUSED位
                mPrivateFlags &= ~PFLAG_FOCUSED;
    
                if (propagate && mParent != null) {
                    mParent.clearChildFocus(this); // 如果向上传播的话,调用parent.clearChildFocus方法
                }
    
                onFocusChanged(false, 0, null); // 调用callback方法
    
                refreshDrawableState(); // 刷新drawable状态
    
                if (propagate && (!refocus || !rootViewRequestFocus())) {
                    notifyGlobalFocusCleared(this);
                }
            }
        }

      通过上一篇的介绍,我们知道KeyEvent的派发就是在view层次结构的focus path上自上而下发生的,具体参见View.dispatchKeyEvent

    的方法doc。刚开始我一直不明白这里的focus path是怎么形成的,怎么按着这个链传递的。这里为了帮助大家理解,我举一个典型的例子,

    通过例子可以很清楚的看到传递过程。比方说我们的view层次结构是这样的,C是个Button,B是C的parent,LinearLayout,A是B的parent,

    FrameLayout。这里我们先假设C、B、A都是有资格且其parent都不阻止它获得焦点,当我们在代码里调用C.requestFocus()时发生的调用

    序列如下:

    1. --> B.requestChildFocus(C, C); 当此方法发生后产生的结果是:B.mFocused = C;接着产生2调用;

    2. --> A.requestChildFocus(B, C); 同样的,当此方法发生后,A.mFocused = B; 接着往上传递直到parent为空时停止。

    当C.requestFocus()调用结束时,如果没有各种失败的case发生,那么C就是当前view层次结构中的focus了,也就是C.isFocused()方法

    此时会返回true。看到了吗?通过这个递归调用,focus path的链就形成了,从最顶层的A能通过其mFocused字段找到B,从找到的B能通过

    其mFocused字段找到C,以此类推。为了加深这个印象,我们最后再看眼ViewGroup.dispatchKeyEvent()方法:

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            /// 2.2.1.1...
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onKeyEvent(event, 1);
            }
    
            if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                    == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { // ViewGroup是focused,则优先交给它自己处理
                /// 2.2.1.2. 
                if (super.dispatchKeyEvent(event)) {
                    return true;
                }
            } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                    == PFLAG_HAS_BOUNDS) { // 否则就沿着mFocused形成的focus path向下传递
                /// 2.2.1.3. 
                if (mFocused.dispatchKeyEvent(event)) {
                    return true;
                }
            }
    
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
            }
            /// 2.2.1.4. 
            return false;
        }

      现在再回过头来看这里的逻辑,是不是感觉特别简单呢?那是因为你已经完全弄清楚了mFocused的由来以及各种变化过程。至此view层次

    结构中关于focus的变化过程已经全部分析完毕了,enjoy。

  • 相关阅读:
    RHEL7.2安装及配置实验环境
    VMwareworkstation 12安装
    Iterator主要有三个方法:hasNext()、next()、remove()详解
    httpclient
    http接口测试——Jmeter接口测试实例讲解
    java获取Excel的导出
    java获取Excel的导入
    java的post请求
    java的get请求
    Python3 列表(List)基础
  • 原文地址:https://www.cnblogs.com/xiaoweiz/p/3812906.html
Copyright © 2011-2022 走看看