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);
    }
  • 相关阅读:
    iOS-SetNeedsLayout,layoutNeeded和layoutSubviews方法之间的关系
    iOS-基础知识-assign,copy,retain,strong,weak的区别
    iOS中单例的两种方法
    iOS-项目国际化
    iOS-项目分析,框架搭建
    iOS-项目远程推送1
    iOS-developer.Apple开发账号操作步骤
    iOS系统办公软件ppt等如何转换windows下可以打开的格式
    iOS-Senior18-第三方初级
    iOS-Senior23-科大讯飞语音识别2
  • 原文地址:https://www.cnblogs.com/123-sxs/p/13446830.html
Copyright © 2011-2022 走看看