美文网首页
Android焦点分发策略

Android焦点分发策略

作者: aosima | 来源:发表于2019-08-21 10:42 被阅读0次

    [TOC]

    焦点分发基础知识

    获取焦点的前提

    • View#isFocusable返回true, 如果在触摸模式, 则View#isFocusableInTouchMode也要返回true
    • 控件必须可见
    • 控件相关的父控件, 包括祖父控件等, ViewGroup#getDescendantFocusability()不能为ViewGroup#FOCUS_BLOCK_DESCENDANTS

    焦点相关api

    • requestFocus

    主动请求焦点

    • clearFocus

    主动清除焦点

    • focusSearch

    递归搜索需要焦点的View

    • ViewGroup.setDescendantFocusability

    三个参数含义:
    FOCUS_BEFORE_DESCENDANTS
    ViewGroup本身相对焦点进行处理,如果沒有处理则分发给child View进行处理

    FOCUS_AFTER_DESCENDANTS
    先分发给Child View进行处理,如果所有的Child View都沒有处理,則自己再处理

    FOCUS_BLOCK_DESCENDANTS
    ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理

    • onRequestFocusInDescendants

    可以传入方向来改变遍历的顺序, 默认是从0递增,遍历子控件,调用子控件的View#requestFocus来尝试把焦点给可见的子控件, 某个子控件成功获取到焦点后, 停止遍历

    注: 重写该方法可以改变ViewGroup分发焦点给子控件的行为, 例如遍历顺

    • requestChildFocus

    当子控件主动放弃焦点的时候,回调这个方法通知父控件.

    • clearChildFocus

    当子控件获取了焦点后, 回调这个方法通知父控件

    • focusableViewAvailable

    通知父控件, 子控件的状态发生改变, 从不能获取焦点, 变成可能可以获取焦点.

    • hasFocus

    当前视图是否是焦点视图或子视图里面有焦点视图

    • isFocused()

    当前视图是否是焦点视图

    • findFocus

    查找焦点控件

    • getFocusedChild

    获取子控件的焦点view

    参考博客

    焦点分发源码分析

    在Android中任何事件都会经由ViewRootImpl$ViewPostImeInputStage的onProcess处理.在onProcess方法中会判断不同事件类型做不同的处理.这里我们只分析触发焦点转移的KeyEvent.事件分发机制参考

    当我们触发按键,最终会由底层把事件传入onProcess,在onProcess中如果是KeyEvent类型就进入processKeyEvent方法中进行处理.

    processKeyEvent方法源码如下:

    private int processKeyEvent(QueuedInputEvent q) {
                final KeyEvent event = (KeyEvent)q.mEvent;
    
                if (event.getAction() != KeyEvent.ACTION_UP) {
                    // If delivering a new key event, make sure the window is
                    // now allowed to start updating.
                    handleDispatchDoneAnimating();
                }
                //1.向View树分发KeyEvent事件
                // Deliver the key to the view hierarchy.
                if (mView.dispatchKeyEvent(event)) {
                    return FINISH_HANDLED;
                }
    
                //........................此处省略部分代码
                
                //2.处理焦点的转移
                // Handle automatic focus changes.
               //........................此处省略部分代码
                    if (direction != 0) {
                    //step1 找到当前View树中的焦点View
                        View focused = mView.findFocus();
                        if (focused != null) {
                        //step2 当前焦点View根据方向搜索下一个需要焦点的View并主动请求焦点
                            View v = focused.focusSearch(direction);
                            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);
                                }
                                if (v.requestFocus(direction, mTempRect)) {
                                    playSoundEffect(SoundEffectConstants
                                            .getContantForFocusDirection(direction));
                                    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
                            //step3 ViewRootImpl根据方向搜索下一个需要焦点的View并主动请求焦点
                            View v = focusSearch(null, direction);
                            if (v != null && v.requestFocus(direction)) {
                                return FINISH_HANDLED;
                            }
                        }
                    }
                }
    
    

    这个方法中主要做了两件事,1.向View树分发KeyEvent事件 2.处理焦点的转移,下面主要分析处理焦点的转移这条逻辑.

    1.向View树分发KeyEvent事件

    // Deliver the key to the view hierarchy.
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    

    这就是我们熟悉View层的事件分发,同理还有

    • dispatchHoverEvent

    • dispatchTouchEvent

    2.处理焦点的转移

    step1 : 当我们的按键有一个确定的方向,首先找到当前View树中的焦点view.

     if (direction != 0) {
        View focused = mView.findFocus();
    }
    

    step2 : 如果找到了当前焦点View,则调用当前焦点View的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

      View v = focused.focusSearch(direction);
      
      if (v.requestFocus(direction, mTempRect)) {
        playSoundEffect(SoundEffectConstants
                .getContantForFocusDirection(direction));
        return FINISH_HANDLED;
        }
    

    最终View#focusSearch直接调用的是ViewPaent#focusSearch
    ViewParent是一个接口,最后在ViewGroup中实现,后文分析ViewGroup#focusSearch,View的focusSearch方法如下:

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

    step3 : 如果未找到当前焦点View,则调用ViewRootImpl的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

    View v = focusSearch(null, direction);
    if (v != null && v.requestFocus(direction)) {
        return FINISH_HANDLED;
    }
    

    本质上ViewRootImpl#focusSearch方法重写的是ViewPaent#focusSearch,此方法内部直接调用焦点帮助类,返回一个需要焦点的View.方法如下:

        @Override
        public View focusSearch(View focused, int direction) {
            checkThread();
            if (!(mView instanceof ViewGroup)) {
                return null;
            }
            return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
        }
    

    至此,焦点分发的逻辑就进入了View层,下面分析View层的逻辑.

    ViewGroup#focusSearch

    因为View的focusSearch直接调用的是ViewParent的focusSearch,所以我们只分析ViewGroup的focusSearch

    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;
        }
    
    

    ViewGroup的focusSearch方法非常简单,如果是root View就调用焦点帮助类返回一个需要焦点的View,反之递归调用ViewGroup的focusSearch,直到找到一个需要焦点的View.

    requestFocus

    了解完focusSearch我们继续分析requestFocus,requestFocus在View和ViewGrope有各自不同的实现.

    ViewGroup#requestFocus

    @Override
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            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);
                    return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
                }
                case FOCUS_AFTER_DESCENDANTS: {
                    final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                    return took ? took : super.requestFocus(direction, previouslyFocusedRect);
                }
            }
        }
    

    ViewGroup的requestFocus会根据三种分发策略决定是自己先请求还是child先请求,最终都会调用View的requestFocus

    View#requestFocus

    View的requestFocus会调用到requestFocusNoSearch
    在requestFocusNoSearch中做一些位运算后最终调用handleFocusGainInternal

    void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    
            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();
            }
        }
    

    handleFocusGainInternal中通知父容器自己请求了焦点,回调onFocusChanged,并刷新View

    相关文章

      网友评论

          本文标题:Android焦点分发策略

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