zoukankan      html  css  js  c++  java
  • Android TV 焦点控制逻辑

    1. 首先简单的焦点控制在对应的布局控件里设置如下属性:

    android:nextFocusUp="@id/下一个控件的id"
    android:nextFocusDown=""
    android:nextFocusLeft=""
    android:nextFocusRight=""

    分别对应该控件按下↑、↓、←、→键对应的下一个控件。

    2.焦点控制逻辑:

    翻看各大博客,对与AndroidTV焦点控制的理解都大同小异,接下来是我对与焦点控制的理解:

    2.1Event事件机制:

    在哪些对象中进行的:

    Activity -> Window -> ViewGroup -> View

    包含拦截、分发、响应:

    拦截发生在: onInterceptTouchEvent()方法中,当用户触发event事件后,由上层传入,当此方法返回true时,则被拦截不会继续往子view传递,由当前view的 onTouchEvent()来响该事件

                                                                                                                                                                   返回false时,不会被拦截,事件将继续传递  ,由子view调用当前view的 dispatchTouchEvent()  去分发, 最后由具体的控件去消费此事件。

    分发:

       dispatchEvent(MotionEvent event)  负责事件的调度,很多人称之为分发和传递也一个意思,主要负责将事件交由哪个控件去处理,如果自己不想处理,则可以继续往下传递,想处理则触发本身view的ontuchEvent(),
       此方法也返回boolean类型,返回ture代表传递,返回false代表不传递,和我们的事件拦截恰恰相反,对于初学者来说很容易搞糊涂,本事件Activty,ViewGroup,View都拥有处理权,主要将事件负责转发,无论交由别人处理还是自己,其实都在充当调度角色,是事件的核心。
     
    响应(消费):
     
      安卓中事件具体处理由 onTouchEvent()  来执行,此阶段主要负责事件的消费响应,通过处理完事件后,然后逐步向上级汇报,如果消费了上级则不会再进行做响应消费处理,只会继续返回给根布局。
     
      此方法返回布尔类型,如果消费了此事件,则会调用上级的此方法,默认返回false做处理,如果返回true,则代表不消费此时间 ,让上级调用本方法去做处理,逐步网上汇报,直到Activity得到消息为止。
     
    过程:
     

     如图A:代表activity,B:代表ViewGroup(如:布局),C:代表View(如:button)

    点击屏幕上的C时整个事件将会由A—B --C —B—A这样的顺序进行分发。

    具体情况如下:

     当点击C (Button)时,首先有A进行分发,然后传递到B,如果B不拦截,则继续分发,传递到C ,此时C无法继续传递 ,则执行事件,消费后继续向上反馈,
    上级则不会进行消费处理,如果不消费,
    则由上级B(Layout)进行处理,如果不处理,则继续交由A(Activity)处理,此时此事件结束。
     

    2.2按键事件:

    KeyEvent:位于android.view下,KeyEvent主要有以下事件类型:

       KeyEvent.KEYCODE_DPAD_UP; 上
       KeyEvent.KEYCODE_DPAD_DOWN; 下
       KeyEvent.KEYCODE_DPAD_LEFT;左
       KeyEvent.KEYCODE_DPAD_RIGHT;右
       KeyEvent.KEYCODE_DPAD_CENTER;确定键
       KeyEvent.KEYCODE_DPAD_RIGHT; 右
       KeyEvent.KEYCODE_XXX:数字键 (xx表示你按了数字几)
       KeyEvent.KEYCODE_BACK; 返回键
       KeyEvent.KEYCODE_HOME;房子键

       KeyEvent.KEYCODE_A: A-Z,26个字母

       KeyEvent.KEYCODE_MENU菜单键。

    首先看事件分发图:

    如上图:

    首先,KeyEvent会流转到ViewRootImpl中开始进行处理,具体方法是内部类ViewPostImeInputStage中的processKeyEvent。

    代码如下:

    private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            ...
            // Deliver the key to the view hierarchy.
            // 1. 先去执行mView的dispatchKeyEvent
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            ...
            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                ...
                if (direction != 0) {
                    View focused = mView.findFocus();
                    if (focused != null) {
                        // 2. 之后会通过focusSearch去找下一个焦点视图
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            ...
                            if (v.requestFocus(direction, mTempRect)) {
                                ...
                                return FINISH_HANDLED;
                            }
                        }
    
                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        // 3. 如果当前本来就没有焦点视图,也会通过focusSearch找一个视图
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }
            return FORWARD;
        }

    看上面的代码可以了解:

          先执行mView的dispatchKeyEvent()方法,再通过focusSearch()去找下一个焦点视图,如果当前没由焦点视图也会执行focusSearch()找一个视图。

    2.2.1 dispatchKeyEvent()执行流程

        DecorView →Activity→ViewGroup→view。

    DecorView 的 dispatchKeyEvent ():

    public boolean dispatchKeyEvent(KeyEvent event) {
        ... ...
        if (!mWindow.isDestroyed()) {
            // Activity实现了Window.Callback接口,具体可以参考 Activity.java 源码.
            final Window.Callback cb = mWindow.getCallback();
            // mFeatureId < 0,表示为 application 的 DecorView.
            // cb.dispatchKeyEven 调用的是 Activity 的 dispatchKeyEven.
            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                    : super.dispatchKeyEvent(event);
            // 是否消耗掉事件.
            if (handled) {
                return true;
            }
        }
        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }

    这里将会调用Activty的dispatchKeyEvent();

    Activity 的 dispatchKeyEvent ():

    // 补充知识点:
    // 这就是为何在 Activity 直接 return true,事件被消耗,就不执行焦点搜索等等操作了.
    // 所以这里也是可以做 焦点控制的,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 进行.
    // 因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断
    // if (event.getAction() == KeyEvent.ACTION_DOWN)
    
    public boolean dispatchKeyEvent(KeyEvent event) {
            ... ...
            Window win = getWindow();
            // 调用 PhoneWindow 的 superDispatchKeyEvent
            // 里面又调用 mDecor.superDispatchKeyEvent(event)
            // mDecor 为 DecorView.
            if (win.superDispatchKeyEvent(event)) {
                return true;
            }
            View decor = mDecor;
            if (decor == null) decor = win.getDecorView();
            // onKeyDown,onKeyUp,onKeyLongPress 等等回调的处理.
            // 只有 onKeyDown return true 可以进行焦点控制,
            // 因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断
            // if (event.getAction() == KeyEvent.ACTION_DOWN)
            return event.dispatch(this, decor != null
                    ? decor.getKeyDispatcherState() : null, this);
        }

    ViewGroup的dispatchKeyEvent():

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        ...
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            // 1.1 以View的身份处理KeyEvent
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            // 1.2 以ViewGroup的身份把KeyEvent交给mFocused处理
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
        ...
        return false;
    }

    通过flag的判断,有两个处理路径,也可以看到在处理keyEvent时,ViewGroup扮演两个角色:

           View的角色,也就是此时keyEvent需要在自己与其他View之间流转。:调用自身的dispathKeyEvent()。

           ViewGroup的角色,此时keyEvent需要在自己的子View之间流转 。:调用当前焦点子View的dispatchKeyEvent()。

    再来看看view的dispatchKeyEvent():

    public boolean dispatchKeyEvent(KeyEvent event) {
        ...
        ListenerInfo li = mListenerInfo;
        // 1.3 如果设置了mOnKeyListener,则优先走onKey方法
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }
        // 1.4 把View自己当作参数传入,调用KeyEvent的dispatch方法
        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }
        ...
        return false;
    }

    View这里,会优先处理OnKeyListener的onKey回调。然后才可能会走KeyEvent的dispatch,最终走到View的OnKeyDown或者OnKeyUp。

    大体的流转顺序总结如下图:

     其中任何一步都可以通过return true的方式来消费掉这个KeyEvent,结束这个分发过程。

     按键事件分发结束,接下来让我们看看如和查找焦点。

    3.焦点查找方法。

    如果dispatchKeyEvent没有消耗掉KeyEvent,会由系统来处理焦点移动。

    通过view的focusSearch方法找到下一个获取焦点的View,然后调用requestFocus设置焦点。

    3.1:focusSearch()

    // View.java
    public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }

    由上面的代码可以看出,View不会直接去查找,而是会交给其parent的focusSearch方法去查找,也就是ViewGroup的focusSearch()方法去查找。

     ViewGroup的focusSearch()方法:
    // ViewGroup.java
    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {
            // root namespace means we should consider ourselves the top of the
            // tree for focus searching; otherwise we could be focus searching
            // into other tabs.  see LocalActivityManager and TabHost for more info
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

    这里会判断是否为根布局,也就是顶层布局,如果是则最后交给FocusFinder去查找

     如果不是则会接调用上层parent的focusSearch()。

    isRootNamespace的()

    /**
     * {@hide}
     *
     * @param isRoot true if the view belongs to the root namespace, false
     *        otherwise
     */
    public void setIsRootNamespace(boolean isRoot) {
        if (isRoot) {
            mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE;
        } else {
            mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE;
        }
    }

    3.2:findNextFocus():

    位于顶层的ViewGroup把自己和当前焦点(View)以及方向传入。

    findNextFocus()代码:

    // FocusFinder.java
    
    public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }
    
    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            // 2.1 优先从xml或者代码中指定focusid的View中找
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                // 2.2 其次,根据算法去找,原理就是找在方向上最近的View
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }

    这里root是上面isRootNamespace()为true的ViewGroup,focused是当前焦点视图

    优先找开发者指定的下一个focus的视图 ,就是在xml或者代码中指定NextFocusDirection Id的视图。

    其次,根据算法去找,原理就是找在方向上最近的视图。

    4.按键焦点查找流程

    4.1界面第一次进入的时候,是如何获取到焦点的

    先看下DecoreView的流程图:

     上图ViewRootImpl类中 performTraversals方法:

    .. ...
    if (mFirst) {
        if (mView != null) {
            if (!mView.hasFocus()) {
                // 调用 View 的 requestFocus(int direction)
                mView.requestFocus(View.FOCUS_FORWARD);
            }
            ... ...
        }
    ... ...

    整体的过程:

    ViewRootlmpl.performTraversals→DecoreView.requestFocus→ActionBarOverlayLayout.requestFocus→FrameLayout(android:id/content).requestFocus→FrameLayout(activity_test.xml).requestFocus→Button1(activity_test.xml).requestFocus

    代码步骤:

    View.java
    public final boolean requestFocus(int direction) {
        // 因为 DecoreView 继承 ViewGroup
        // ViewGroup 重写了此函数,
        // 会调用 ViewGroup 的 requestFocus(int direction, Rect previouslyFocusedRect)
        return requestFocus(direction, null);
    }
    
    ViewGroup.java
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        // 关注内容:
        // 处理 DescendantFocusabilit
        // 1)FOCUS_AFTER_DESCENDANTS 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
        // 2)FOCUS_BEFORE_DESCENDANTS ViewGroup先对焦点进行处理,如果没有处理则分发给child View进行处理
        // 3)FOCUS_BLOCK_DESCENDANTS ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理
        // setDescendantFocusability 可以设置.
        int descendantFocusability = getDescendantFocusability();
        switch (descendantFocusability) {
            case FOCUS_BLOCK_DESCENDANTS:
                return super.requestFocus(direction, previouslyFocusedRect);
            case FOCUS_BEFORE_DESCENDANTS: { 
                // 其它的 ActionBarOverlayLayout,Content等继承ViewGroup
                // 默认进入 FOCUS_BEFORE_DESCENDANTS,因为 ViewGroup 初始化的时候设置了
                // setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
                
                // mViewFlags 判断 FOCUSABLE_MASK,FOCUSABLE_IN_TOUCH_MODE.
                // Button 以上的父布局,不满足以上条件判断,全部都是 直接 return false.
                final boolean took = super.requestFocus(
                direction, previouslyFocusedRect);
                // took=false, 调用 onRequestFocusInDescendants 遍历子控件进行请求
                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            case FOCUS_AFTER_DESCENDANTS: { 
                // DecoreView 进入这里,因为 PhoneWindow 给 DecoreView 初始化 设置
                // setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                // setIsRootNamespace(true);
                // 像 RecyclerView, Leanback 也会进入这里.
                // 遍历子控件进行请求
                final boolean took = onRequestFocusInDescendants(
                direction, previouslyFocusedRect);
                // took=true,子控件有焦点,不调用 super.request...,反之.
                return took ? took : super.requestFocus(
                direction, previouslyFocusedRect);
            }
            ... ...
        }
    }
    
    View.java
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            return requestFocusNoSearch(direction, previouslyFocusedRect);
    }
    
    ViewGroup.java
    // 补充知识点: onRequestFocusInDescendants 是可以做焦点记忆控制的.
    protected boolean onRequestFocusInDescendants(int direction, 
    Rect previouslyFocusedRect) {
        .. ...
        for (int i = index; i != end; i += increment) {
            View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // 
                if (child.requestFocus(direction, previouslyFocusedRect)) {
                    return true;
                }
            }
        }
        return false;
    }

    Button1获取焦点:

     关键代码是 View.java 的函数 handleFocusGainInternal : mPrivateFlags |= PFLAG_FOCUSED 和 mParent.requestChildFocus(this, this)

    View.java
    private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
        // need to be focusable
        // Button 默认 android:focusable="true"
        // button1 以上的父布局都没有设置此类属性,进入这里,直接就 return false.
        if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
                (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
            return false;
        }
    
        // need to be focusable in touch mode if in touch mode
        // 当 button1 没有设置 android:focusableInTouchMode="true" 的时候,
        // 直接 return false,那么界面上是没有任何控件获取到焦点的.
        // 鼠标|触摸支持的属性.
        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 ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            // 关键代码,设置 有焦点的标志位. 
            // 这个时候 button1 已经标志上焦点
            mPrivateFlags |= PFLAG_FOCUSED;
            // 获取父布局的老焦点.
            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
            // 调用此函数,告诉上一层父布局,让它做一些事情.
            if (mParent != null) {
                mParent.requestChildFocus(this, this);
            }
            // 此函数是全局焦点监听的回调.
            // 调用方式: View.getViewTreeObserver().addOnGlobalFocusChangeListener
            if (mAttachInfo != null) {
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }
            // 回调处理.
            onFocusChanged(true, direction, previouslyFocusedRect);
            // 刷新按键的 selector drawable state状态
            refreshDrawableState();
        }
    }
    
    ViewGroup.java
    public void requestChildFocus(View child, View focused) {
        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);
            }
            // 保存上一级的焦点view.
            mFocused = child;
        }
        // 一层层调用回去父布局,相当于 
        // FrameLayout(activity_test.xml) 的 mFocused 是 Button1.
        // FrameLayout(android:id/content) 的 mFocused 是 FrameLayout(activity_test.xml)
        // ActionBarOverlayLayout 的 mFocused 是 FrameLayout(android:id/content)
        // 最后 DecoreView 的 mFocused 是 ActionBarOverlayLayout
        // 在最后的后面,ViewRootImpl 会调用 
        // requestChildFocus,又会再次调用 
        // performTraversals刷新界面.(再执行 layout, draw)
        // 形成了一个关联, dispatchKeyEvent 的 mFocused 也在使用.
        if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
     }
    
    // ViewRootImpl.java
    @Override
    public void requestChildFocus(View child, View focused) {
        checkThread();
        scheduleTraversals();
    }

    初步获取焦点已经了解,接下来看看焦点是如何从 view2 →view2的。

    4.2按键焦点的搜索过程

     focusView(2) 按下右键后:由上面的3.焦点查找方法可以得出下图:

    在没有消耗 dispatchKeyEvent的情况下: 

    FocusSearch 一层层上去,调用 FocusFinder.getInstance().findNextFocus… … 后,在 …addFocusables 下,将所有带焦点属性的 view 全部加到数组里面去,然后通用方向,位置等查找相近的view. 最后找到的是  focusView(3).

    private int processKeyEvent(QueuedInputEvent q) {
        ... ...
        // 以上代码不消耗事件.
        // 判断 action 为 ACTION_DOWN 才处理焦点搜索以及请求.
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
        // 根据按键判断,设置 direction 属性.
        if (direction != 0) {
            // 一层层查找(根据mFocused),最后获取到 button1.
            View focused = mView.findFocus();
            if (focused != null) {
                // button1_view 调用 focusSearch(), 右键,direction=66
                View v = focused.focusSearch(direction);
                // 最终返回 v = button2
                if (v != null && v != focused) {
                    // do the math the get the interesting rect
                    // of previous focused into the coord system of
                    // newly focused view
                    focused.getFocusedRect(mTempRect);
                    if (mView instanceof ViewGroup) {
                        ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                focused, mTempRect);
                        ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                v, mTempRect);
                    }
                    // button2 View 调用 requestFocus
                    // 这里的过程 和 第一次获取焦点button1请求是一样的.
                    if (v.requestFocus(direction, mTempRect)) {
                        // 播放音效
                        playSoundEffect(SoundEffectConstants
                                .getContantForFocusDirection(direction));
                        return FINISH_HANDLED;
                    }
                }
                // 进行最后的垂死挣扎,
                // 这里其实可以处理一些焦点问题或者滚动翻页问题.
                // 滚动翻页的demo可以参考 原生 Launcher 的 Workspace.java
                // Give the focused view a last chance to handle the dpad key.
                if (mView.dispatchUnhandledMove(focused, direction)) {
                    return FINISH_HANDLED;
                }
            } else {
                // 这里处理第一次无焦点 view 的情况.
                // 基本上和有焦点view 的情况差不多.
                View v = focusSearch(null, direction);
                if (v != null && v.requestFocus(direction)) {
                    return FINISH_HANDLED;
                }
            }
        }
        }
        ... ...
    }

    button1下一个焦点搜索流程图:

    View v = focused.focusSearch(direction); # focused=>button1 direction=>66
    Button1_View→focusSearch(int direction)→FrameLayout(activity_test.xml)_ViewGroup→focusSearch(View focused, int direction)→。。。→FrameLayout(activity_test.xml)_ViewGroup→

    focusSearch(View focused, int direction)→DecoreView_ViewGroup→FocusFinder.getInstance().findNextFocus(this, focused, direction)→FocusFinder.findNextFocus()→ViewGroup.addFocusables()->。。。



    代码流程:

    View.java
    public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            // button1 的父布局ViewGroup调用 focusSearch
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }
    
    ViewGroup.java
    // 像 RecyclerView 会重写 focusSearch 进行焦点搜索.
    // 也是调用的 FocusFinder.getInstance().findNextFocus
    // leanback 的 GridLayoutmanger 也重写了 onAddFocusables.
    public View focusSearch(View focused, int direction) {
        // 只有 DecoreView 设置了 setIsRootNamespace
        // 最终由 DecoreView 进入这里.
        if (isRootNamespace()) {
            // 传入参数(this: DecoreView focused: button1 direction: 66)
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }
    
    FocusFinder.java
    findNextFocus(ViewGroup root, View focused, int direction)->findNextFocus(root, focused, null, direction)->
    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            // 关于XML布局中的 android:nextFocusRight 等等的查找.
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            // 要进行 findNextFocus,关键在于 addFocusables,一层层调用下去.
            // DecorView_View.addFocusables
            // DecorView_ViewGroup.addFocusables
            // ActionBarOverlayLayout_ViewGroup.addFocusables
            // FrameLayout(android:id/content)_ViewGroup.addFocusables
            // FrameLayout(activity_test.xml)_ViewGroup.addFocusables
            // 到最后 button1, button2 添加到 views 数组中,也就是 focusables .
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                 // 关键函数 findNextFocus,想深入了解是如何查找到下一个焦点的,
                // 可以去看看源码,这里不进行过多篇幅的讲解.
                // focusables 数组有 button1, button2
                // 内部调用 findNextFocusInAbsoluteDirection,这里进行了一些判断,查找某个方向比较近的view.
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }
    
    ViewGroup.java
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        final int focusableCount = views.size();
        final int descendantFocusability = getDescendantFocusability();
        ... ...
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                // 循环 child view 调用 addFocusables,一层层调用下去,将满足条件的添加进 views 数组.
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    child.addFocusables(views, direction, focusableMode);
                }
            }
        }
        if ... ...
            // 调用 view 的 addFocusables,父布局是不满足条件的,直接返回了.
            super.addFocusables(views, direction, focusableMode);
        }
    }
    
    View.java
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        if (views == null) {
            return;
        }
        if (!isFocusable()) {
            return;
        }
        if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
                && isInTouchMode() && !isFocusableInTouchMode()) {
            return;
        }
        // button1 以上条件满足,加入views数组.
        // button2 以上条件也满足,加入views数组.
        // 同理,焦点记忆的原理就很简单了,后续会讲解.
        views.add(this);
    }
  • 相关阅读:
    Jzoj4822 完美标号
    Jzoj4822 完美标号
    Jzoj4792 整除
    Jzoj4792 整除
    Educational Codeforces Round 79 A. New Year Garland
    Good Bye 2019 C. Make Good
    ?Good Bye 2019 B. Interesting Subarray
    Good Bye 2019 A. Card Game
    力扣算法题—088扰乱字符串【二叉树】
    力扣算法题—086分隔链表
  • 原文地址:https://www.cnblogs.com/123-sxs/p/13446830.html
Copyright © 2011-2022 走看看