Android焦点处理流程

102 篇文章 4 订阅
16 篇文章 0 订阅

1.ViewRootImpl

1.连接WindowManager和DecorView的纽带
2.完成view的measure,layout,draw
3.向DecorView分发按键、触摸事件等。

关于按键事件和焦点寻找:
先判断是否有按键事件处理
1.若返回true,则打断该方向上的焦点寻找。
2.若返回fasle,则根据指定的方向寻找最近且可获取焦点的view
2.1判断view的类型,是否为ViewGroup。

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);
            }
        }
    }
    @Override
    protected void onDeliverToNext(QueuedInputEvent q) {
        //此处不分析,省略N行...........
    }

    private int processKeyEvent(QueuedInputEvent q) {
        final KeyEvent event = (KeyEvent)q.mEvent;
        //首先由dispatchKeyEvent进行分发,如果返回true的,则不再继续。
//1.此处的mView是DecorView,即是个ViewGroup对象,则调用的是其dispatchKeyEvent方法
//2.若未被处理,则以下流程,比如,按上下键,则需要寻找下一个焦点等等
        if (mView.dispatchKeyEvent(event)) {
            return FINISH_HANDLED;
        }
        if (shouldDropInputEvent(q)) {
            return FINISH_NOT_HANDLED;
        }
        // If the Control modifier is held, try to interpret the key as a shortcut.
        if (event.getAction() == KeyEvent.ACTION_DOWN
                && event.isCtrlPressed()
                && event.getRepeatCount() == 0
                && !KeyEvent.isModifierKey(event.getKeyCode())) {
        //首先会将事件进行分发,如果没有被消费,则开始寻找下一个可获取焦点的view。
        //如果被消费,则不再继续。
            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) {
            //在根视图层中寻找当前有焦点的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,计算出一个坐标矩形。并将其赋值给可聚焦view
                        if (mView instanceof ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focused, mTempRect);
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTempRect);
                        }
                     ========= 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内部的focusSearch(),调用了View.requestFocus()

作用:1.寻找指定方向上的view
2.判断是否有mParent,即其父view。

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

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()
关于其分发策略的标记:

  1. FOCUS_BLOCK_DESCENDANTS: 拦截焦点, 直接自己尝试获取焦点
  2. FOCUS_BEFORE_DESCENDANTS: 首先自己尝试获取焦点, 如果自己不能获取焦点, 则尝试让子控件获取焦点
  3. 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;
    }
    
    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

处理其他的回调事件,判断是否被拦截处理。

public final boolean dispatch(Callback receiver, DispatcherState state,
        Object target) {
    switch (mAction) {
        case ACTION_DOWN: {
            mFlags &= ~FLAG_START_TRACKING;
            if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
                    + ": " + this);
            boolean res = receiver.onKeyDown(mKeyCode, this);
            if (state != null) {
                if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                    if (DEBUG) Log.v(TAG, "  Start tracking!");
                    state.startTracking(this, target);
                } else if (isLongPress() && state.isTracking(this)) {
                    try {
                        if (receiver.onKeyLongPress(mKeyCode, this)) {
                            if (DEBUG) Log.v(TAG, "  Clear from long press!");
                            state.performedLongPress(this);
                            res = true;
                        }
                    } catch (AbstractMethodError e) {
                    }
                }
            }
            return res;
        }
        case ACTION_UP:
            if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
                    + ": " + this);
            if (state != null) {
                state.handleUpEvent(this);
            }
            return receiver.onKeyUp(mKeyCode, this);
        case ACTION_MULTIPLE:
            final int count = mRepeatCount;
            final int code = mKeyCode;
            if (receiver.onKeyMultiple(code, count, this)) {
                return true;
            }
            if (code != KeyEvent.KEYCODE_UNKNOWN) {
                mAction = ACTION_DOWN;
                mRepeatCount = 0;
                boolean handled = receiver.onKeyDown(code, this);
                if (handled) {
                    mAction = ACTION_UP;
                    receiver.onKeyUp(code, this);
                }
                mAction = ACTION_MULTIPLE;
                mRepeatCount = count;
                return handled;
            }
            return false;
    }
    return false;
}

4.FocusFinder

实现根据给定的按键方向,通过已获取焦点的View,查找下一个获取焦点的view这样算法的类。
焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过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来确定添加
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接收按键事件,并对其进行分发处理。
● DecorView会调用dispatchKey逐层进行焦点的分发,若某个view的dispatchKeyEvent方法返回true,
则按键不再传递,焦点都不再继续处理。(可对其设置OnKeyListener监听,返true即可)
● 如果焦点没有被拦截的话,那么焦点就会交给系统来处理,还是会继续分发,直到找到那个获取焦点的View。
● focusSearch内部其实是通过FocusFinder来查找焦点的。FocusFinder会优先通过View在XML布局设置的下一个焦点的ID来查找焦点。
● 若没有找到,又会根据按键的方向,执行focusSearch方法来寻找下一个将要获取焦点的View
● 最终如果找到将要获取焦点的View,就让其requestFocus。如果请求无效,将其放在onWindowFocusChanged()这个方法中去请求。这是在Activity寻找到焦点的时候。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值