美文网首页Android自定义View源码分析TV
Android焦点处理流程(源码分析)

Android焦点处理流程(源码分析)

作者: 烬日沉香 | 来源:发表于2018-06-11 10:13 被阅读438次

    主要涉及到的类:ViewRootImp,ViewGroup,View,FocusFinder

    当事件发生时,最主要是从ViewRootImpl的processKeyEvent开始处理分发。

    1.ViewRootImpl

        1.连接WindowManager和DecorView的纽带
        2.完成view的measure,layout,draw
        3.向DecorView分发按键、触摸事件等。
          1.先判断是否有按键事件处理
         1.若返回true,则打断该方向上的焦点寻找。
        2.若返回fasle,则根据指定的方向寻找最近且可获取焦点的view
           1.如果mView.findFocus()找到了focused
            1.1判断mView的类型,是否为ViewGroup。
             1.2判断该focused是否是mView内的view
    offsetDescendantRectToMyCoords()该方法判断
           2.如果没有找到则调用自身的focusSearch() ===此处后文会展开==
    补充:ViewRootImpl中的mView指的是Activity中的DecorView后文中会频繁的对mView进行判断或调用mView的方法。

    final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }
        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q); //若是按键事件,则走该方法处理按键和焦点
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);  //若是触摸事件,则走该方法处理
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            //首先由dispatchKeyEvent进行分发,如果返回true的,则不再继续。
           //未被处理KeyEvent处理,则进入寻找下一个焦点的流程。
            if (mView.dispatchKeyEvent(event)) {
             //该方法内部首先判断拥有focus的view,是否重写了onKeyDown、onKeyUp方法,事件会交给它优先处理。
    //当它返回true时,那么事件不再继续传递。也就是说我们可以通过重写返true来拦截。
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.isCtrlPressed()
                    && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())) {
      
                if (mView.dispatchKeyShortcutEvent(event)) {
                    return FINISH_HANDLED;
                }
                if (shouldDropInputEvent(q)) {
                    return FINISH_NOT_HANDLED;
                }
            }
         //若未被子view拦截,开始处理按键,根据direction进行处理
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                switch (event.getKeyCode()) {
                    //根据code设置direction的值...............
                }
                if (direction != 0) {
             //DecorView会一层一层往下调用findFocus方法找到当前获取焦点的View
                    View focused = mView.findFocus();
                    if (focused != null) {
                     ========= 1. 展开分析focused.focusSearch()   ===========
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) ;
                            focused.getFocusedRect(mTempRect);
                  //若是ViewGroup类型,计算被聚焦的view,是否在mView内部
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTemRect);
                            }
                         ========= 2.此处下面展开分析 =========
                            if (v.requestFocus(direction, mTempRect)) {
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                    =========  3.如果focused为null,以下展开分析  ===========
                     View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }
            return FORWARD;
    }
    

    1.1展开分析ViewRootImpl,调用了View.focusSearch()

    作用:1.寻找指定方向上的view
    2.判断是否有mParent,即其父view,交给父view处理,如其父view是RecyclerView,则先让RecylerView的focusSearch()执行,若内部调用了super.focusSearch(),则还会交给ViewGroup处理。
    如下图:


    image.png

    ViewGroup内部会不断地向上调父View的focusSearch()(如下图),具体代码可以看后文关于ViewGroup的分析。


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

    关于mParent的由来,如果view存在mParent,则其父view是ViewGroup。
    子view被add在ViewGrop中,调用addView()---->addViewInne()时,会为子view赋值parent为this。

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        addInArray(child, index);
       //此处省略N行.......
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }
      //此处省略N行.......
    }
    

    1.2展开分析ViewRootImpl内部调用View的requestFocus()

    requestFocus() ----> requestFocusNoSearch() ----->handleFocusGainInternal()
    1.调用mParent.requestChildFocus()通知父控件,即将获取焦点。
    2.通知其他部件,焦点即将发生变化。
    3.通知回调。
    4.强制布局更新绘制。

    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) {
                 //此时调用view的parent的requestChildFocus的回调,
                //可重写RecyclerView的requestChildFocus做一些处理。
                    mParent.requestChildFocus(this, this);
                    updateFocusedInCluster(oldFocus, direction);
                }
                if (mAttachInfo != null) {
                   //调用globalFocus回调                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
                }
                onFocusChanged(true, direction, previouslyFocusedRect);
                refreshDrawableState();
            }
        }
    

    1.3展开分析当mView没有找到focused时,ViewRootImp调用自身requestFocus

    最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的.
    1.检查线程
    2.判断mView是否为ViewGroup
    3.使用FocusFinder寻找焦点 ----->后文会分析如何寻找焦点

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

    2.ViewGroup内的dispatchKeyEvent方法

    规则:
    1.如果这个viewGroup持有焦点, 那么就会直接调用super.dispatchKeyEvent()
    2.如果是它的子控件持有焦点, 那么就会调用子控件的view.dispatchKeyEvetn()
    关于其分发策略的标记:
    FOCUS_BLOCK_DESCENDANTS: 拦截焦点, 直接自己尝试获取焦点
    FOCUS_BEFORE_DESCENDANTS: 首先自己尝试获取焦点, 如果自己不能获取焦点, 则尝试让子控件获取焦点
    FOCUS_AFTER_DESCENDANTS: 首先尝试把焦点给子控件, 如果所有子控件都不要, 则自己尝试获取焦点

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
      //mInputEventConsistencyVerifier是调试用的,暂时不理会。
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }
    
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
     //如果viewgroup持有焦点,先调用其自身的dispacthKeyevent()
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            //如果子view持有焦点,先将事件传给子view。
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }
    

    3.View内的dispatchKeyEvent方法

    1.先处理当前view的onKey监听
    2.再处理其他监听的回调

    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }
        ListenerInfo li = mListenerInfo;
        //判断view是否注册了onKeyListener监听,先判断其返回值,若为true,则事件处理到此为止。
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }
       //dispatch()内部处理其他的回调事件,判断是否被拦截处理。
        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }
    

    4.FocusFinder

    实现根据给定的按键方向,通过已获取焦点的View,查找下一个获取焦点的view规则算法的类。焦点没有被拦截的情况下,FocusFinder的查找规则来查找。

    关键方法findNextFocus,通过给定的矩形坐标,寻找根视图的子view中可以获取focus的view
    规则:
    1.优先寻找用户在direction上已经指定获取focus的view。
    如果有,则直接返回该view。如果不存在,则进入2.
    2.把根root中所有可以获取focus的view添加到focusables列表中。
    根root一般是viewGroup,则调用其addFocusablse,其会遍历所有child,调用child的addFocusable。
    【 这里有一个误区,认为会取direction方向上的view,实际上未以direction来确定添加,而是将所有的v可focus的view都add到列表中】
    3.根据现有focused,所有可focus的focusables,寻找下一个合适的view

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
     //若当前focus的不为空
        if (focused != null) {
       //优先一层一层寻找该用户已经指定的可获取焦点的view
      //执行当前focus的view的findUserSetNextFocus方法
      //如果该方法返回的View不为空,且isFocusable = true && isInTouchMode() = true的话。
      //FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
      //若能找到,则直接返回。
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
      //赋值后,先清空该对象的历史值
            focusables.clear();
    //添加任何可聚焦的view,这些view是root的子view(可能)
    //包括这个视图,如果它本身可以聚焦到视图。如果我们处于触摸模式,添加在触摸模式中也是可聚焦的视图。
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
    //根据root,当前focus的view,其坐标矩形,按键方向,所有可获取焦点的view,寻找下一个符合条件的view
             next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }
    

    4.1优先寻找用户在direction上已经指定获取focus的view

    private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
        View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
        if (userSetNextFocus != null && userSetNextFocus.isFocusable()
                && (!userSetNextFocus.isInTouchMode()
                        || userSetNextFocus.isFocusableInTouchMode())) {
            return userSetNextFocus;
        }
        return null;
    }
    
    

    调用view中的findUserSetNextFocus()

    View findUserSetNextFocus(View root, @FocusDirection int direction) {
        switch (direction) {
            case FOCUS_LEFT:
                if (mNextFocusLeftId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusLeftId);
            case FOCUS_RIGHT:
                if (mNextFocusRightId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusRightId);
            case FOCUS_UP:
                if (mNextFocusUpId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusUpId);
            case FOCUS_DOWN:
                if (mNextFocusDownId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusDownId);
            case FOCUS_FORWARD:
                if (mNextFocusForwardId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusForwardId);
            case FOCUS_BACKWARD: {
                if (mID == View.NO_ID) return null;
                final int id = mID;
                return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                    @Override
                    public boolean apply(View t) {
                        return t.mNextFocusForwardId == id;
                    }
                });
            }
        }
        return null;
    }
    

    4.2寻找所有可focus的child中合适的view规则

    具体没有获取指定view后,寻找该方向上,可获取view的findNextFocus方法

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        if (focused != null) {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
            }
            // fill in interesting rect from focused
            focused.getFocusedRect(focusedRect);
            root.offsetDescendantRectToMyCoords(focused, focusedRect);
        } else {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
                // make up a rect at top left or bottom right of root
                switch (direction) {
                    case View.FOCUS_RIGHT:
                    case View.FOCUS_DOWN:
                        setFocusTopLeft(root, focusedRect);
                        break;
                    case View.FOCUS_FORWARD:
                        if (root.isLayoutRtl()) {
                            setFocusBottomRight(root, focusedRect);
                        } else {
                            setFocusTopLeft(root, focusedRect);
                        }
                        break;
    
                    case View.FOCUS_LEFT:
                    case View.FOCUS_UP:
                        setFocusBottomRight(root, focusedRect);
                        break;
                    case View.FOCUS_BACKWARD:
                        if (root.isLayoutRtl()) {
                            setFocusTopLeft(root, focusedRect);
                        } else {
                            setFocusBottomRight(root, focusedRect);
                        break;
                    }
                }
            }
        }
    switch (direction) {
            case View.FOCUS_FORWARD:
            case View.FOCUS_BACKWARD:
                return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                        direction);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return findNextFocusInAbsoluteDirection(focusables, root, focused,
                        focusedRect, direction);
            default:
                throw new IllegalArgumentException("Unknown direction: " + direction);
        }
    }
    
    

    5.小结

    ViewRootImpl接收按键事件,并对其进行分发处理。
    1.DecorView会调用dispatchKey逐层进行焦点的分发,若某个view的dispatchKeyEvent方法返回true。则按键不再传递,焦点都不再继续处理。(可对其设置OnKeyListener监听,返true即可达到不再传递目的)
    2.如果焦点没有被拦截的话,则进入查找流程。首先判断当前mView是否有可获取focus的View。
         2.1若有:根据方向查找该View内是否有符合条件的view。若找到,先判断其mView是否为ViewGroup,然后判断该view是否在mView内部。然后请求获取焦点requestFocus()
         2.2若无:直接调用ViewRootImp内的focusSearch()方法,该方调用FocusFinder的findNextFocus来查找合适的控件。
    4.FocusFinder优先寻找开发者指定该方向上下一个可获取的view(比如在XML文件中指定了下一个可获取焦点的View的ID。如果没有,则使用FocusFinder类内的方法findNextFocus()来查找。

    相关文章

      网友评论

        本文标题:Android焦点处理流程(源码分析)

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