美文网首页
android requestFocus()和cleanFocu

android requestFocus()和cleanFocu

作者: Mr_王大锤 | 来源:发表于2017-10-25 01:17 被阅读0次

    在最近写的项目中遇到由focus引起的问题,例如:

    1. 在两个嵌套的RecyclerView中,外层滑动停止后,由于内层的RecyclerView获取了焦点,导致外层又自动滑动了一些距离;

    2. 需要监听EditText的焦点变化来控制界面的显示于隐藏,在调用EditText的cleanFocus()方法时,回调接口会调用两次,并且EditText的焦点依然还在。

    对遇到的这两个问题的解决方法都一样,就是在外层,或根布局中添加focusInTouchModel:true,focus:true两个属性,优先获取到焦点就可以了。

    布局文件中和focus有关的属性

    //是否可获取焦点
    focusable:true|false
    //触摸模式下是否可获取焦点
    focusableInTouchMode:true|false
    descendantFocusability:blocksDescendants|beforeDescendants|afterDescendants
    

    这里解释一下descendantFocusability的三个属性值,分别代表的意思:

    1. blocksDescendants:ViewGroup拦截,不让子 view获取焦点。
    2. beforeDescendants:ViewGroup优先尝试(尝试的意思是,根据View或ViewGroup当前状态来判断是否能得到焦点,如是否可见,是否可获取焦点等等,在View的requestFocus方法的注释中提到,下同)获取焦点,若ViewGroup没拿到焦点,再遍历子 view(包括所有直接子 view和间接子 view),让子 view尝试获取焦点。
      3.afterDescendants:先遍历子 view,让子 view尝试获取焦点,若所有子 view(包括所有直接子 view和间接子 view)都没拿到焦点,才让ViewGroup尝试获取焦点。

    下面是ViewGroup的与descendantFocusability有关的源码

        @Override
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            if (DBG) {
                System.out.println(this + " ViewGroup.requestFocus direction="
                        + direction);
            }
            //获取当前viewgroup的descendantFocusability属性值
            int descendantFocusability = getDescendantFocusability();
    
            switch (descendantFocusability) {
                case FOCUS_BLOCK_DESCENDANTS:
                    //直接调用超类也就是View的方法获取焦点,忽略子 view
                    return super.requestFocus(direction, previouslyFocusedRect);
                case FOCUS_BEFORE_DESCENDANTS: {
                    //当前ViewGroup先尝试获取焦点,返回值表示是否拿到来焦点,若没有,则让子 view尝试获取  
                    final boolean took = super.requestFocus(direction, previouslyFocusedRect);
                    return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
                }
                case FOCUS_AFTER_DESCENDANTS: {
                   //与上面刚好相反
                    final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                    return took ? took : super.requestFocus(direction, previouslyFocusedRect);
                }
                default:
                    throw new IllegalStateException("descendant focusability must be "
                            + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                            + "but is " + descendantFocusability);
            }
        }
    
    requestFocus() 方法的执行流程

    上面看的是ViewGroup的requestFocus()方法,而ViewGroup的这个方法只是对ViewGroup和子 view之间获取焦点的顺序做的一个处理,要拿到焦点,最终还是要通过调用View的requestFocus()方法来拿到焦点。所以接下来主要看的是View的方法。

    public final boolean requestFocus() {
            return requestFocus(View.FOCUS_DOWN);
        }
    public final boolean requestFocus(int direction) {
            return requestFocus(direction, null);
        }
    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 ||
                    (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
                return false;
            }
    
            // need to be focusable in touch mode if in touch mode,判断在触摸模式下是否可获取焦点
            if (isInTouchMode() &&
                (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
                   return false;
            }
    
            // need to not have any parents blocking us,判断是否有父view拦截
            if (hasAncestorThatBlocksDescendantFocus()) {
                return false;
            }
    
            handleFocusGainInternal(direction, previouslyFocusedRect);
            return true;
        }
    

    可以看到,在外部调用requestFocus()这个无参方法后,经过层层传递后,最终是调用requestFocusNoSearch(int direction, Rect previouslyFocusedRect)方法,这几个方法的返回值表示是否拿到了焦点,在里面可以看到有3个if判断语句,这就是我们上面提到的“尝试”的意思了,即需要根据当前状态判断,通过判断,最后直接返回了true,表示拿到了焦点,而在返回之前,还执行了一个很重要的方法handleFocusGainInternal(direction, previouslyFocusedRect),来看看里面都干了些什么:

    void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
            if (DBG) {
                System.out.println(this + " requestFocus()");
            }
            //  若当前已拿到焦点,则方法结束,所以一个已获焦点的view,在执行requestFocus()后,它的onFocusChanged方法是不会回调的
            if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
                //标记已拿到焦点
                mPrivateFlags |= PFLAG_FOCUSED;
            //上个拿到焦点的view
                View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
                
                if (mParent != null) {
                  //递归调用,重置ViewGroup的mFocused的值,重置oldFocus的mPrivateFlags标记位
                    mParent.requestChildFocus(this, this);
                }
    
                if (mAttachInfo != null) {
                    mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
                }
    
                onFocusChanged(true, direction, previouslyFocusedRect);
                refreshDrawableState();
            }
        }
    

    ViewGroup的requestChildFocus(this, this)方法

     @Override
        public void requestChildFocus(View child, View focused) {
            if (DBG) {
                System.out.println(this + " requestChildFocus()");
            }
            if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
                return;
            }
    
            // Unfocus us, if necessary
            super.unFocus(focused);
    
            // We had a previous notion of who had focus. Clear it.
            if (mFocused != child) {
                if (mFocused != null) {
                    mFocused.unFocus(focused);
                }
              //重置mFocused变量值
                mFocused = child;
            }
            if (mParent != null) {
                mParent.requestChildFocus(this, focused);
            }
        }
    

    然后通过View的unFocus()重置标记位

    void unFocus(View focused) {
            if (DBG) {
                System.out.println(this + " unFocus()");
            }
          //注意看两个参数都是false
            clearFocusInternal(focused, false, false);
        }
    /**
         * 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(View focused, boolean propagate, boolean refocus) {
            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
                mPrivateFlags &= ~PFLAG_FOCUSED;
    
                if (propagate && mParent != null) {
                    mParent.clearChildFocus(this);
                }
    
                onFocusChanged(false, 0, null);
                refreshDrawableState();
    
                if (propagate && (!refocus || !rootViewRequestFocus())) {
                    notifyGlobalFocusCleared(this);
                }
            }
        }
    
    requestFocus()主要流程是判断view当前状态是否能拿到焦点,若能,则清除原来获取焦点的view的标记位,然后返回结果。这里说明一下:ViewGroup的mFocused属性,它表示当前ViewGroup的一个直接子 view获取,而真正拿到焦点的view则是mFocused或mFocused的子 view。
    
    再来看看CleanFocus()方法
    public void clearFocus() {
            if (DBG) {
                System.out.println(this + " clearFocus()");
            }
            //注意参数
            clearFocusInternal(null, 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(View focused, boolean propagate, boolean refocus) {
            //若当前没有拿到焦点,则直接返回
            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
                mPrivateFlags &= ~PFLAG_FOCUSED;
    
                if (propagate && mParent != null) {
                //将ViewGroup的mFocused置空
                    mParent.clearChildFocus(this);
                }
    
                onFocusChanged(false, 0, null);
                refreshDrawableState();
                //rootViewRequestFocus()让根视图重新设置focus,就是之前说的requestFocus方法,按一定顺序重新给焦点的过程
                if (propagate && (!refocus || !rootViewRequestFocus())) {
                    notifyGlobalFocusCleared(this);
                }
            }
        }
    

    看了上面的代码可以解释上面遇到的第二个问题了,当EditText是第一个能获得焦点的view时,执行cleanFocus()方法,在重置焦点标记位时会调用一次onFocusChanged回调方法,在之后的requestFocus流程中,又得到了焦点,所以经历了两次回调,所以在父 view或根视图加上focusable和focusableInTouchMode为true时,就解决了这个问题。

    关于focus的内容说到这里就完了,相信大家看完这个,对focus应该会有了解了,有不对的地方,欢迎大家指出,互相学习。虽然遇到focus的问题比较少,而且解决起来也很简单,但还是研究了一下源码,主要就是学习看源码的方法,就是debug,跟着一步一步的走,搞清代码执行的流程。

    相关文章

      网友评论

          本文标题:android requestFocus()和cleanFocu

          本文链接:https://www.haomeiwen.com/subject/rpkyuxtx.html