Android View系统源码分析(九)—— ViewRoot.ensureTouchMode()

VIEWROOT-ENSURETOUCHMODE

点击下载vsdx

ViewRoot.ensureTouchMode(boolean inTouchMode)

  boolean ensureTouchMode(boolean inTouchMode) {
        if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
                + "touch mode is " + mAttachInfo.mInTouchMode);
 
        //判断参数传入的inTouchMode和当前的Touch模式是否相同,如果相同就直接返回false,什么都不做。当前的值存放在View.mAttachInfo.mInTouchMode中。
 
        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
 
        // tell the window manager
        try {
 
            //以下都是需要改变,首先报告 WindowManagerService 当前的Touch模式发生变化,因为 WindowManagerService 在进行客户窗口布局时,需要根据客户窗口的Touch模式进行不同的处理。
 
            sWindowSession.setInTouchMode(inTouchMode);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
 
        // handle the change
 
        //调用ensureTouchModeLocally()函数进行报告
 
        return ensureTouchModeLocally(inTouchMode);
    }
 
 
ViewRoot.ensureTouchModeLocally(boolean inTouchMode)
 private boolean ensureTouchModeLocally(boolean inTouchMode) {
        if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
                + "touch mode is " + mAttachInfo.mInTouchMode);
 
        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
 
        //首先给mInTouchMode重新赋值,因为当前状态发生改变。
 
        mAttachInfo.mInTouchMode = inTouchMode;
 
        //每个View中都包含一个mAttachInfo对象,该对象来源于ViewRoot中的mAttachInfo,该对象内部又有一个mTreeObserver变量,它是一个ViewTreeObserver对象,调用该对象的dispatchOnTouchModeChanged()。
 
        mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
 
        //如果函数是要进入Touch状态,则调用enterTouchMode(),否则调用leaveTouchMode()。
 
        return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
    }
 
 
 
 
对于应用程序而言,可以调用View类的 getTreeObserver()函数获取该View所在窗口的ViewTreeObserver对象,同一个窗口中不同View对象的该方法返回的是同一个对象,即一个窗口中只有一个该对象。ViewTreeObserver内部有一个列表容器,我们可以调用该类的addOnGloabalFocusChangedListener()向该列表中添加一个接口元素。上面的dispatchOnTouchModeChanged()实际上是循环调用该列表容器中多个接口对象的onGloabalFocusChanged()方法,而这个方法是程序员可以自定义实现的,以便处理Focus改变的情况。
ViewTreeObserver类除了有Focus改变的接口,还提供了Layout、pre-draw 、 scroll等不同的接口。
View.AttachiInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
    final void dispatchOnTouchModeChanged(boolean inTouchMode) {
        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
        // perform the dispatching. The iterator is a safe guard against listeners that
        // could mutate the list by calling the various add/remove methods. This prevents
        // the array from being modified while we iterate it.
        final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
                mOnTouchModeChangeListeners;
        //遍历容器中的Listner,分别调用。
        if (listeners != null) {
            for (OnTouchModeChangeListener listener : listeners) {
                listener.onTouchModeChanged(inTouchMode);
            }
        }
    }
 
ViewRoot.enterTouchMode()
private boolean enterTouchMode() {
        if (mView != null) {
 
            //判断是否拥有焦点(一整枝都拥有焦点),该方法是View类的,ViewGroup中重载了该函数。View.getFocusChild(),表示找到当前View(Group)中包含焦点的子视图,该函数返回当前View直接子视图。
 
            if (mView.hasFocus()) {
                // note: not relying on mFocusedView here because this could
                // be when the window is first being added, and mFocused isn't
                // set yet.
 
                //找到真正拥有焦点的View或者ViewGroup,这里的mView就是根视图。
 
                final View focused = mView.findFocus();
 
                //如果存在焦点,并且该视图没有在Touch模式下获取焦点的能力,直接返回false,这种情况下比较少。应用程序可以调用setFocusableInTouchMode()改变该属性,在默认情况下,视图在Touch模式下是不能拥有焦点的。
 
                if (focused != null && !focused.isFocusableInTouchMode()) {
 
                    //【1】
                    final ViewGroup ancestorToTakeFocus =
                            findAncestorToTakeFocusInTouchMode(focused);
 
                    //如果可以,则直接调用父类视图的.requestFocus(),并将结果直接返回。就是自己没能力问问老爸有没有这个Touch获取焦点的能力。
 
                    if (ancestorToTakeFocus != null) {
                        // there is an ancestor that wants focus after its descendants that
                        // is focusable in touch mode.. give it focus
                        return ancestorToTakeFocus.requestFocus();
                    } else {
 
                        //一般程序都会走到这步,说明可以正常进入Touch并需要清除其焦点(自己没有,但是有可能父视图有)。
                        // nothing appropriate to have focus in touch mode, clear it out
 
                        //该函数将从根上清除所有子视图中的焦点。
                        mView.unFocus();
 
                        //调用ViewTreeObserver的dispatchOnFocusChanged(),以便应用程序对此执行相关的操作,注意~这里是Focus的回调,上面是Touch的回调。
 
                        mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
 
                        //置空mFocusedView
                        mFocusedView = null;
 
                        //代表是做了改变了。
                        return true;
                    }
                }
            }
        }
        //表示没变,自己没有这个能力。
        return false;
    }
 
 
【1】大多数情况下,Touch模式下不能拥有焦点,因此需要清除焦点。不过,该步骤中首先判断该视图的父视图是否可以在Touch模式下拥有焦点,如果可以的话,则调用父视图的requestFocus(),并返回其结果,如果不可以才开始真正清除焦点。
ViewRoot.leaveTouchMode()
 private boolean leaveTouchMode() {
        if (mView != null) {
 
            //如果该View体系中是有焦点的
 
            if (mView.hasFocus()) {
                // i learned the hard way to not trust mFocusedView :)
 
                //找到真正拥有焦点的View。
 
                mFocusedView = mView.findFocus();
 
                if (!(mFocusedView instanceof ViewGroup)) {
 
                    //如果是View对象,就不用重新给该视图赋予焦点了,直接返回false。
 
                    // some view has focus, let it keep it
                    return false;
                } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
                        ViewGroup.FOCUS_AFTER_DESCENDANTS) {
 
                    //如果该视图是一个ViewGroup对象,并且该ViewGroup对象阻止其子视图获得焦点,那么也直接返回false。(ListView就是可以获得焦点的ViewGroup,并且ListView不会直接把焦点传递给其包含的item,而是使用特别的逻辑让item获得焦点。)
 
                    // some view group has focus, and doesn't prefer its children
                    // over itself for focus, so let them keep it.
                    return false;
                }
            }
 
 
            //如果没有找到拥有焦点的视图,则调用ViewRoot.focusSearch()使该视图获得焦点。【2】
            // find the best view to give focus to in this brave new non-touch-mode
            // world
            final View focused = focusSearch(null, View.FOCUS_DOWN);
            if (focused != null) {
                return focused.requestFocus(View.FOCUS_DOWN);
            }
        }
        return false;
    }
 
 
【2】参数null代表当前拥有焦点的视图,即当前没有视图拥有焦点。DOWN代表往下继续找,这就是为什么当用户从按键模式切换到Touch模式,然后再进入按键模式时,上一个有用焦点的视图不能再次获得焦点,而是第一个视图获得焦点的原因。对于ListView而言,上一个获得焦点的视图,当重新切换到按键模式后依然能够获得焦点,因为ListView使用了特别的逻辑为所包含的item赋予焦点。
View.unFocus()
   void unFocus() {
        if (DBG) {
            System.out.println(this + " unFocus()");
        }
 
        if ((mPrivateFlags & FOCUSED) != 0) {
            mPrivateFlags &= ~FOCUSED;
 
            onFocusChanged(false, 0, null);
            refreshDrawableState();
        }
    }
 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值