android focus查找派发全解析

focus只有两个点:

1.刚启动activity时,焦点默认在哪里,为什么在那里?

2.上下按键,焦点如何查找的,它怎么知道下一个获取焦点的是谁?

其中2只是针对键盘即key事件,touch事件则直接是皇统继承,点谁是谁。

一、启动activity时,焦点默认行为:

首先是wms进行setview,将decorView添加进来,此时viewRootImpl中保存了decorView,viewroot中的mView保存这个decorView。

1.第一步performTraversals

看ViewRootImp中的函数 private void performTraversals()
 private void performTraversals() {
//跳过n多代码,只关注焦点处理的部分
        if (mFirst) {
            // handle first focus request
            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
                    + mView.hasFocus());
            if (mView != null) {
                if (!mView.hasFocus()) {
                    mView.requestFocus(View.FOCUS_FORWARD);
                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
                            + mView.findFocus());
                } else {
                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
                            + mView.findFocus());
                }
            }
        }
一般来讲decorView不会是hasFocus的(也有是的,这部分是逻辑还没找到,哪里给decorView设置的焦点),需要走到 mView.requestFocus(View.FOCUS_FORWARD)

2.requesFocus

A. 如何找到获取焦点的view:

在viewroot中可以看到哦啊,调用了requestfocus。mview是啥,是decorview。走到viewgroup里。这里开始递归,规规矩矩的挨个查找。先看图在看code就明白了。
直接描述查找的逻辑:
做个简单的比喻,在女儿国,媒婆来提亲,直接找的家里的长老,现在长老说:好,我看看哪个孩子出嫁。
DecorView(mview)就是长老,child就是他的女儿,第一代孙、第二代孙...。
长老家有规矩,按家族辈份来,先看老大家,没有再看老二家...;老大家也是这样,先看他大女儿家是否有待出嫁的,没有在看二女儿家...,依次类推。
当然,家族庞大,已经有了n待悬孙。长老不会自己去问,而是告诉大女儿,大女儿家没有找到待嫁的孩子,告诉长老没有,长老才会去问二女儿...,这个询问的过程也是依次类推。
最后,找到n代孙女:meimei.han待家,通过meimei.han告诉她妈,她妈在告诉她奶奶...,一辈一辈的把消息通知到长老。
这个传统封建式的婚嫁,就是view焦点查找的过程。
下面根据图片,再结合代码来具体讲一下。



1)mView是decorView,他有1个child(linearlayout:尾号cb8),第一步就是从decorView查到这个大女儿linearlayout;长老告诉大女儿cb8,去看看你孩子们,是否有待出嫁的。
2)长子cb8家有3孩子:老大798,老二9f8,老三078.
     2.1) cb8把他的大女儿798叫过来,问他:家里是否有带出嫁的闺女。
              798说:我光棍,没有。
              cb8说:滚。
     2.2)然后把老二9f8叫过来,问他:家里是否有带出嫁的闺女。
             2.1.1)98f有一女360,于是回家问女儿:是否愿出嫁。
                     360说:未成年,不出嫁。
            98f速度返回他爹cb8处:我家没有可以出嫁的。
    2.3)然后把老三078叫过来,问他:家里是否有带出嫁的闺女。
          2.3.1)老三078家有一女6d4,...
这个过程类似了就,不再多说。虽然不是很恰当,但是这个比喻能比较浅显的解释清楚具体的查找过程。再结合代码,就基本可以搞明白了。
代码解析:
知道view图谱是decorview--viewgroup--...view。
这里也是一样,在viewgroup。这里只关注: FOCUS_BEFORE_DESCENDANTS,因为block的没啥看的,更简单。       
    @Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (true) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        int descendantFocusability = getDescendantFocusability();

        switch (descendantFocusability) {
            case FOCUS_BLOCK_DESCENDANTS:
                return super.requestFocus(direction, previouslyFocusedRect);
            case FOCUS_BEFORE_DESCENDANTS: {
                final boolean took = super.requestFocus(direction, previouslyFocusedRect);//调用view中的函数,如果不是根view,继续走viewgroup,否则走view中xxx函数,下面会讲到。
                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);
        }
    }
这个函数就是上面说的一代一代问的过程了:
    @SuppressWarnings({"ConstantConditions"})
    protected boolean onRequestFocusInDescendants(int direction,
            Rect previouslyFocusedRect) {
        int index;
        int increment;
        int end;
        int count = mChildrenCount;
        if ((direction & FOCUS_FORWARD) != 0) {
            index = 0;
            increment = 1;
            end = count;
        } else {
            index = count - 1;
            increment = -1;
            end = -1;
        }
        final View[] children = mChildren;
        for (int i = index; i != end; i += increment) {
            View child = children[i];//同一辈份的,老大优先
            Log.d("lstlog","onRequestFocusInDescendants i: "+i+", child: "+child);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                if (child.requestFocus(direction, previouslyFocusedRect)) {//看看孩子是否可以获取焦点,并设置
                    return true;
                }
            }
        }
        return false;
    }
经过这个遍历,最终会找到并设置焦点给特定的那个view:可能是未出嫁的女孩也可能是她妈。。。孩子是亲生的(在xml里面写出来的)就不会发生这种情况,不是亲生的(xml里面么有的,通过code add进去的)就可能这样。
上面图中的最终获取焦点的是就listview 6a8,而不是里面那些子布局了。

B.穿嫁衣
在找到具体的女孩或者女孩她妈以后,就要准备出嫁了,要认识出嫁的女孩的妈,奶奶...一直到长老。他们这里就不需要认识她的啊姨们了。
这里就是一个反向查找了,并要记录下来。(HierarchyState中需要记录)
在A的过程中,之所以没记录,需要在B的过成中再查找一遍,是因为第一次查找,不确定这个家族就能有,是哪个分支,也可能没有,所以没办法记录,只能反向查找并设置一下HierarchyState的状态,这样可以在Hierarchy工具中看到。

code:
view中的函数,上面也说了,到拿到焦点后,就会调用view中的函数了。
    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
        if (hasAncestorThatBlocksDescendantFocus()) {
            return false;
        }

        handleFocusGainInternal(direction, previouslyFocusedRect);
        return true;
    }

    void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " requestFocus()");
        }

        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            mPrivateFlags |= PFLAG_FOCUSED;

            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

            if (mParent != null) {
                mParent.requestChildFocus(this, this);
            }

            if (mAttachInfo != null) {
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }

            onFocusChanged(true, direction, previouslyFocusedRect);
            refreshDrawableState();
        }
    }

viewgroup中
    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();
        Log.d("lstlog","this: "+this+", mFocused: "+mFocused+", focused: "+focused);

        // We had a previous notion of who had focus. Clear it.
        if (mFocused != child) {
            if (mFocused != null) {
                mFocused.unFocus();
            }

            mFocused = child;
        }
        if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }
关注函数 requestChildFocus第一个参数是父视图,第二个是focused视图,该函数内部进行递归调用,从获取焦点的view逐层往外一直到辈份最大的。这里看都是this,为啥说第一个参数是父呢?看viewgroup中的函数就明白了。

4.requestChildFocus(ViewGroup)

 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();

        // We had a previous notion of who had focus. Clear it.
        if (mFocused != child) {
            if (mFocused != null) {
                mFocused.unFocus();
            }

            mFocused = child;
        }
        if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }
先清除自己的焦点( super.unFocus()), 如果原来内部有焦点, 先清除其焦点( mFocused.unFocus()), 保存获取焦点的view( mFocused = child),然后调用上一层的requestChildFocus. 最后的调用可知,这个方法会一直调用到View的树的root节点。
使用 Hierarchy Viewer, 可以看到最小的view如果有焦点,则其父布局的hasfocus也是true。就上上面这段逻辑了。
其中一句log:(从view中传递的是this,this两个参数,但是调用的时候是parent,所以到viewgroup中的this就是parent了,focused就是view的this)


至此,启动一个新的activity的焦点派发过程分析到此,基本框架和思想就是这样了。
下一篇文章分析一下:方向按键,如何查找焦点的。
touch就不要关注这些了,点击比较简单。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值