Android焦点流程梳理

news2024/11/21 2:29:14

作者:Cy13er

前言

最近在看一些焦点处理的问题,认真处理起来发现不跟着源码自己走一遍焦点相关的流程,对于问题的分析上会比较困难。所以本文主要对焦点流程进行一次梳理,在处理类似问题时也可以作为手册阅读。

起源

一切都要从ViewRootImpl收到输入事件开始

// ViewRootImpl.ViewPostImeInputStage.java
@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        return processKeyEvent(q); // KeyEvent会在此处处理
    } 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;

    。。。

    // 1\. KeyEvent事件分发
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }

    。。。

    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (groupNavigationDirection != 0) {
            if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                return FINISH_HANDLED;
            }
        } else {
            // 2\. ACTION_DOWN时,寻找下一个获取焦点的View
            if (performFocusNavigation(event)) {
                return FINISH_HANDLED;
            }
        }
    }
    return FORWARD;
}

焦点流程主要分成两部分问题:

  • KeyEvent事件分发,寻找当前焦点需要消费事件的子View,并消费事件,见注释1处
  • 若事件没有消费,而且当前是KeyEvent.ACTION_DOWN时,需要寻找下一个焦点,见注释2处

寻找下一个焦点

// ViewRootImpl.ViewPostImeInputStage.java
private boolean performFocusNavigation(KeyEvent event) {
    int direction = 0;
    switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_LEFT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_RIGHT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_UP:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_UP;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_DOWN;
            }
            break;
        case KeyEvent.KEYCODE_TAB:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_FORWARD;
            } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                direction = View.FOCUS_BACKWARD;
            }
            break;
    }
    if (direction != 0) {
        // 1\. 获取目前拥有焦点的View
        View focused = mView.findFocus();
        if (focused != null) {
            // 2\. 查找到下一个获取焦点的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);
                }
                // 3\. 让View获取焦点
                if (v.requestFocus(direction, mTempRect)) {
                    boolean isFastScrolling = event.getRepeatCount() > 0;
                    playSoundEffect(
                            SoundEffectConstants.getConstantForFocusDirection(direction,
                                    isFastScrolling));
                    return true;
                }
            }

            // Give the focused view a last chance to handle the dpad key.
            if (mView.dispatchUnhandledMove(focused, direction)) {
                return true;
            }
        } else {
            if (mView.restoreDefaultFocus()) {
                return true;
            }
        }
    }
    return false;
}

大概就是3步:

  1. 通过findFocus获取到当前拥有焦点的View
  2. 通过focusSearch获取到下一个获取焦点的View
  3. 让下一个获取焦点的View获取焦点

大概流程

这里大概流程如下

按键事件分发

dispatchKeyEvent

KeyEvent事件分发与触摸事件有类似,都是从顶层的DecorView开始寻找消费事件的子View。不同的是,它没有过多复杂的机制,譬如事件拦截。

ViewGroup#dispatchKeyEvent

// ViewGroup.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    。。。

    // 1\. 如果父View自己拥有焦点,则判断自己是否消费事件
    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    // 2\. 如果该父View包含拥有焦点的子View,将事件分发到对应子View方向    
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
        }
    }

    。。。
    return false;
}

ViewGroup#dispatchKeyEvent分两种情况:

  • 注释1,如果父View自己拥有焦点,则判断自己是否消费事件,实际是调用了父类View中定义的dispatchKeyEvent判断。
  • 注释2,如果父View当前包含了拥有焦点的子View,则将事件往拥有焦点的子View方向传递

也就是说,KeyEvent事件分发只会往拥有焦点的子View方向传递。也就有以下两种情况:

ps:红色部分是事件传递的方向。

View#dispatchKeyEvent

// View.java
public boolean dispatchKeyEvent(KeyEvent event) {
    。。。

    // Give any attached key listener a first crack at the event.
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    // 1
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }

    // 2
    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }

    。。。
    return false;
}

View#dispatchKeyEvent就与dispatchTouchEvent类似了,第一步

  • 判断OnKeyListener是否为null
  • 判断OnKeyListener.onKey是否返回true
  • View是否是Enable状态

三者成立则认为View消费事件,否则通过View#onKeyDown或者View#onKeyUp判断。事件的调用通过KeyEvent#dispatch,也就是注释2。

ps:与Touch事件类似,DecorView会先将事件传递给Activity,然后经过PhoneWindow回传给DecorView进行事件分发

获取当前拥有焦点的View

findFocus

findFocus的作用就是获取到当前拥有焦点的View,View的实现就是通过判断mPrivateFlags来确定自己是否拥有焦点。如果自己拥有焦点则返回自己。

// View.java
public View findFocus() {
    return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}

ViewGroup的实现会在View的基础上扩展:

  • 如果自己拥有焦点则返回自己。
  • 如果自己包含了拥有焦点的子View,则往拥有焦点的子View方向寻找。
// ViewGroup.java
@Override
public View findFocus() {
    if (DBG) {
        System.out.println("Find focus in " + this + ": flags="
                + isFocused() + ", child=" + mFocused);
    }

    if (isFocused()) {
        return this;
    }

    if (mFocused != null) {
        return mFocused.findFocus();
    }
    return null;
}

也就是下图中,红色箭头表示findFocus的调用方向,红色区域表示当前拥有焦点的View

下一个获取焦点的View

focusSearch

focusSearch寻找下一个获取焦点的View,它是以当前拥有焦点的View作为起点,往上传递。View的实现中,会调用父View的focusSearch,将自己也就是拥有焦点的View传递上去,同时携带当前需要移动的方向direction

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

ViewGroup中,会分为两种情况:

  • 没有到达根视图,那么与View相同直接传递上去。
  • 如果到达根视图,则会通过FocusFinder这一单例来寻找下一个获取焦点的View。ps:这里的根视图可以理解成是View树的最上层。
// ViewGroup.java
@Override
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;
}

ps:FocusFinder.getInstance().findNextFocus(this, focused, direction)中的this的意思是以当前View作为起点寻找下一个获取焦点的View。

方法的调用方向如下图红色部分:

焦点搜索控制技巧

可能会有这样一个需求:使用自定义RecyclerView做一个Tabbar效果时,不想在移动到最左或者最右时,焦点移出RecyclerView,那么可以从focusSearch入手,拦截它的搜索过程

override fun focusSearch(focused: View?, direction: Int): View? {
    if (focused == null || layoutManager == null || adapter == null || adapter?.itemCount == 0)
        return super.focusSearch(focused, direction)

    val view = super.focusSearch(focused, direction)

    if (view != null) {
        //  findContainingItemView获取到该view所在的父View,如果不在RecyclerView内则返回null
        val nextFocusItemView = findContainingItemView(view)
        //  为null证明焦点已经移出了RecyclerView
        if (nextFocusItemView == null) {    
            //  左右限制移出RecyclerView
            if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
                return focused
            }
        }
    }

    return view
}

这里如果是左右移动到边界,则直接返回当前拥有焦点的View,使焦点还在当前View上,不会被移动到别处。

FocusFinder

// FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
    if (focused != null) {
        // 1\. 获取开发者指定的下一个获取焦点的View
        next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
    }
    if (next != null) {
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        // 2\. 通过遍历子View,可以获取焦点的备选View放入focusables集合中
        effectiveRoot.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            // 3\. 通过可以获取焦点的备选view中获取到下一个获取焦点的View
            next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}

FocusFinder.getInstance().findNextFocus中主要做了以下3件事:

  • 尝试获取用户在xml中指定的下一个获取焦点的View
  • 通过遍历子View,将可以**获取到下一个焦点的子View加入到备选集合focusables**中
  • 通过上述的备选集合focusables筛选出下一个获取焦点的View

用户在XML指定下一个获取焦点的View

用户可以在XML中指定不同方向下,基于该View的下一个获取焦点的View。

android:nextFocusLeft="@id/xxx"
android:nextFocusRight="@id/xxx"
android:nextFocusDown="@id/xxx"
android:nextFocusUp="@id/xxx"

获取焦点获取备选集合

如果用户未指定下一个获取焦点的View,则会通过备选集合的方式筛选出下一个获取焦点的View。首先需要获取到这个备选集合。

  • 先来看看View的实现:
    // View.java
    public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
        addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
    }

    public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
            @FocusableMode int focusableMode) {
        if (views == null) {
            return;
        }
        if (!canTakeFocus()) {
            return;
        }
        if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
                && !isFocusableInTouchMode()) {
            return;
        }
        views.add(this);
    }

这里比较简单,如果符合获取焦点的资格,就会将自己添加到集合当中。ps:views即为获取焦点的备选集合。

  • 再来看看ViewGroup的实现:
// ViewGroup.java
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    final int focusableCount = views.size();

    final int descendantFocusability = getDescendantFocusability();
    final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
    final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);

    // 1\. 若descendantFocusability为FOCUS_BLOCK_DESCENDANTS,则会拦截子View获取焦点
    if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
        if (focusSelf) {
            super.addFocusables(views, direction, focusableMode);
        }
        return;
    }

    if (blockFocusForTouchscreen) {
        focusableMode |= FOCUSABLES_TOUCH_MODE;
    }

    // 2\. 若descendantFocusability为FOCUS_BEFORE_DESCENDANTS,则会优先将自己加入到集合
    if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
        super.addFocusables(views, direction, focusableMode);
    }

    int count = 0;
    final View[] children = new View[mChildrenCount];
    for (int i = 0; i < mChildrenCount; ++i) {
        View child = mChildren[i];
        // 3\. 只允许添加当前是Visible的子View
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            children[count++] = child;
        }
    }
    FocusFinder.sort(children, 0, count, this, isLayoutRtl());
    for (int i = 0; i < count; ++i) {
        children[i].addFocusables(views, direction, focusableMode);
    }

    // 4\. 若descendantFocusability为FOCUS_AFTER_DESCENDANTS,
    // 则如果没有子View符合条件时将自己添加到集合
    if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
            && focusableCount == views.size()) {
        super.addFocusables(views, direction, focusableMode);
    }
}

首先需要先科普一下ViewGroup获取焦点的3种策略

  • FOCUS_BEFORE_DESCENDANTS: 优先于子View获取焦点
  • FOCUS_AFTER_DESCENDANTS: 当子View都不获取焦点时,才获取焦点
  • FOCUS_BLOCK_DESCENDANTS: 禁止子View获取焦点

同样可以通过XML设置,譬如:

android:descendantFocusability="beforeDescendants"

而这里就是根据不同的策略对集合的添加进行控制:

  • descendantFocusability = FOCUS_BLOCK_DESCENDANTS,则会拦截子View获取焦点,见注释1处
  • descendantFocusability = FOCUS_BEFORE_DESCENDANTS,则会优先将自己加入到集合,见注释2处
  • 遍历子View,并调用它的addFocusables,如果是ViewGroup的话就会起到往下递归目的,见注释3处
  • descendantFocusability = FOCUS_AFTER_DESCENDANTS的话,则如果没有子View符合条件时将自己添加到集合,见注释4处

焦点记忆技巧

如果需要做焦点记忆的需求,就可以考虑在addFocusables这里下手了,譬如在RecyclerView中记录当前选中的位置,在addFocusables只将该位置对应的View添加到集合,这样就能将焦点重新给到这个View。

override fun addFocusables(views: ArrayList<View>?, direction: Int, focusableMode: Int) {
    // 根据选中位置,重新获取到View
    val view: View? = layoutManager?.findViewByPosition(currentSelectedPosition)
    if (hasFocus() || currentSelectedPosition < 0 || view == null) {
        super.addFocusables(views, direction, focusableMode)
    } else if (view.isFocusable && view.visibility == View.VISIBLE) {
        // 只将这个View添加到集合,并终止方法往下递归
        views?.add(view)
    } else {
        super.addFocusables(views, direction, focusableMode)
    }
}

寻找下一个焦点

// FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
        int direction, ArrayList<View> focusables) {
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        }

        // 1\. 获取到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:
            // 2\. 根据focused所在区域获取到下一个获取焦点的View
            return findNextFocusInAbsoluteDirection(focusables, root, focused,
                    focusedRect, direction);
        default:
            throw new IllegalArgumentException("Unknown direction: " + direction);
    }
}

焦点寻找是就近原则,默认会根据对比两块Rect区域的位置,来确定焦点位置。所以findNextFocus做了两件事:

  • 求出当前拥有焦点的View即focused的Rect区域,并将它转换成与根视图root相同的坐标系。ps:这样是为了后续比较时能在同一坐标系下比较。
  • 根据focused所在区域获取到下一个获取焦点的View
// FocusFinder.java
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
        Rect focusedRect, int direction) {
    // 1\. 最佳候选View所在的区域,初始值为focused的区域
    mBestCandidateRect.set(focusedRect);
    switch(direction) {
        case View.FOCUS_LEFT:
            mBestCandidateRect.offset(focusedRect.width() + 1, 0);
            break;
        case View.FOCUS_RIGHT:
            mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
            break;
        case View.FOCUS_UP:
            mBestCandidateRect.offset(0, focusedRect.height() + 1);
            break;
        case View.FOCUS_DOWN:
            mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
    }

    View closest = null;

    // 2\. 遍历候选集合,筛选出最佳候选区域
    int numFocusables = focusables.size();
    for (int i = 0; i < numFocusables; i++) {
        View focusable = focusables.get(i);

        // only interested in other non-root views
        if (focusable == focused || focusable == root) continue;

        // 3\. 获取候选的View所在区域,并将它转换为与root相同坐标系
        focusable.getFocusedRect(mOtherRect);
        root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
        // 4\. 对比两个区域,如果可行,就更新最佳候选View所在的区域
        if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
            mBestCandidateRect.set(mOtherRect);
            closest = focusable;
        }
    }
    return closest;
}

mBestCandidateRect最佳候选View的区域,这里大概分几步:

  • mBestCandidateRect初始化为当前拥有焦点的区域focusedRect
  • 通过遍历候选集合,筛选出最佳候选区域,与其对应的View对象
  • 在对比前需要将候选的View所在区域转换成与root相同坐标系
  • 对比区域可行后,会更新mBestCandidateRect,以及对应的View对象,整个过程类似求最大值的过程。

至此,下一个获取焦点的View就得出来了。

获取焦点

requestFocus

View#requestFocus

View获取焦点的流程

// View.java
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
}

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    if (!canTakeFocus()) {
        return false;
    }

    // 在TouchMode下需要声明focusableInTouchMode为true才能获取焦点
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    }

    // 如果存在descendantFocusability为FOCUS_BLOCK_DESCENDANTS的父View则不能获取焦点
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    }

    if (!isLayoutValid()) {
        mPrivateFlags |= PFLAG_WANTS_FOCUS;
    } else {
        clearParentsWantFocus();
    }

    // 获取焦点
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " requestFocus()");
    }

    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED;  // 1\. 标记为获取了焦点

        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

        if (mParent != null) {
            // 2\. 通知自己的父View自己获取了焦点
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        }

        if (mAttachInfo != null) {
            // 3\. 回调viewTreeObserver的OnGlobalFocusChange
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }

        // 4\. 如果有注册OnFocusChangeListener,会有回调
        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}

View#requestFocus最后会调用到handleFocusGainInternal方法

  • 更新mPrivateFlags,标记为自己已经获取了焦点
  • 调用ViewGroup#requestChildFocus,通知自己的父View自己获取了焦点
  • 回调相关监听
    • viewTreeObserver的OnGlobalFocusChange
    • OnFocusChangeListener.onFocusChange

ViewGroup#requestChildFocus

// ViewGroup.java
@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    // 如果descendantFocusability为FOCUS_BLOCK_DESCENDANTS则直接返回
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    }

    // Unfocus us, if necessary
    super.unFocus(focused);

    // 1\. 清空旧的焦点
    if (mFocused != child) {
        if (mFocused != null) {
            mFocused.unFocus(focused);
        }

        mFocused = child;
    }
    // 2\. 继续往父View方向传递
    if (mParent != null) {
        mParent.requestChildFocus(this, focused);
    }
}

ViewGroup#requestChildFocus主要做了两件事:

  • 清空旧的焦点状态
  • 继续调用父View的requestChildFocus,目的是继续更新父View的记录状态

ViewGroup#requestFocus

ViewGroup会对requestFocus进行重写

// ViewGroup.java
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " ViewGroup.requestFocus direction="
                + direction);
    }
    int descendantFocusability = getDescendantFocusability();

    boolean result;
    switch (descendantFocusability) {
        // 1\. 不允许子View获取焦点
        case FOCUS_BLOCK_DESCENDANTS:
            result = super.requestFocus(direction, previouslyFocusedRect);
            break;
        // 2\. 自己优先于子View获取焦点    
        case FOCUS_BEFORE_DESCENDANTS: {
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            result = took ? took : onRequestFocusInDescendants(direction,
                    previouslyFocusedRect);
            break;
        }
        // 3\. 子View优先于自己获取焦点
        case FOCUS_AFTER_DESCENDANTS: {
            final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
            result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
            break;
        }
        default:
            throw new IllegalStateException("descendant focusability must be "
        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
        + "but is " + descendantFocusability);
    }
    if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
        mPrivateFlags |= PFLAG_WANTS_FOCUS;
    }
    return result;
}

// 4\. 寻找可以获取焦点的子View
protected boolean onRequestFocusInDescendants(int direction,
        Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = mChildrenCount;
    if ((direction & FOCUS_FORWARD) != 0) {
        index = 0;
        increment = 1;
        end = count;
    } else {
        index = count - 1;
        increment = -1;
        end = -1;
    }
    final View[] children = mChildren;
    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;
}

onRequestFocusInDescendants的作用是寻找一个子View获取焦点,见注释4处

这里主要是扩展了获取焦点策略的能力,也是根据定义

  • FOCUS_BLOCK_DESCENDANTS不允许子View获取焦点,所以这里直接令自己去获取焦点,见注释1处
  • FOCUS_BEFORE_DESCENDANTS:自己优先于子View获取焦点,所以这里会先自己调用super.requestFocus,再通过结果调用onRequestFocusInDescendants,见注释2处
  • FOCUS_AFTER_DESCENDANTS子View优先于自己获取焦点,所以会先通过onRequestFocusInDescendants返回的结果,再视情况令自己获得焦点,见注释3处

开发中,可以重写onRequestFocusInDescendants以此来控制您想希望获取焦点的子View获取焦点

默认获取焦点

回到刚开始的流程,如果目前还没有找到拥有焦点的View,会怎样呢?

// ViewRootImpl.ViewPostImeInputStage.java#performFocusNavigation
View focused = mView.findFocus();
if (focused != null) {
    。。。
} else {
    if (mView.restoreDefaultFocus()) {
        return true;
    }
}

// View.java
public boolean restoreDefaultFocus() {
    return requestFocus(View.FOCUS_DOWN);
}

如果找不到当前拥有焦点的View,则会调用View#restoreDefaultFocus,实际上就是requestFocus,一般其实是ViewGroup#requestFocus,因为mView是DecorView。那这样就有机会通过onRequestFocusInDescendants令子View获取到焦点。

最后

本文主要对焦点流程进行了梳理,包括焦点的产生与搜索过程,事件分发的过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/491584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache Kafka 进阶(一)

官网 Apache Kafka是一个开源的分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。 核心能力 高吞吐量 在网络有限的吞吐量下&#xff0c;使用延迟低至2ms的机器集群交付消息。可扩展性 将生产集群扩展到1000个代理&#xff0c…

SQLite安装配置

1.什么是 SQLite&#xff1f; SQLite是一个软件库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite是一个增长最快的数据库引擎&#xff0c;这是在普及方面的增长&#xff0c;与它的尺寸大小无关。SQLite源代码不受版权限制。 SQLite是…

Linux 五种网络IO模式(阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO)

Linux网络编程中&#xff0c;有五种网络IO模式&#xff0c;分别是阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO&#xff1b; 虽然说不能全都认识得很透彻&#xff0c;但至少得都知道一点&#xff01; 开始之前&#xff0c;先了解以下同步IO和异步IO&#xff1b; 1. 同步…

探索AIGC创新实践,亚马逊云科技与全球咨询合作伙伴携手同行

AIGC(AI Generated Content&#xff0c;人工智能生成内容)&#xff0c;已经成为全球出圈的科技热点。各行各业都在重新审视和思考AIGC的创新价值、未来趋势和成功实践&#xff0c;力争在这波热潮下寻找更多创新的可能性&#xff0c;重塑行业格局。 在AIGC领域&#xff0c;亚马…

无代码资讯|ChatGPT新功能曝光;Mendix与亚马逊云科技底层融合;无代码开发平台Appy Pie推出内置AI

栏目导读&#xff1a;无代码资讯栏目从全球视角出发&#xff0c;带您了解无代码相关最新资讯。 Top3大事件 1、ChatGPT 新功能曝光&#xff0c;GPT-4 迎来 AGI 历史性时刻&#xff01; 北美时间4月20日&#xff0c;Open AI联合创始人Greg Brockman受邀出席 “2023TED” 大会&…

手写【深拷贝】

JS中深拷贝的实现 JSON.parse(JSON.stringify())递归实现深拷贝 使用JSON.parse(JSON.stringify()) 实现 无法拷贝 函数、正则、时间格式、原型上的属性和方法等 递归实现深拷贝 es5实现深拷贝 源对象 const obj {name: 张桑,age: 18,hobby: [{name: 篮球,year: 5,loveSta…

极简爬虫通用模板

网络爬虫的一般步骤如下&#xff1a; 1、确定爬取目标&#xff1a;确定需要爬取的数据类型和来源网站。 2、制定爬取策略&#xff1a;确定爬取哪些网页、如何爬取和频率等。 3、构建爬虫程序&#xff1a;使用编程语言&#xff08;如Python&#xff09;实现爬虫程序&#xff…

【python】列表、字典、元组与集合的特点以及对比

一、列表&#xff08;List&#xff09; 1. 列表的特点 数据按顺序存储列表有正序、倒序两种索引列表可存储任意类型的数据&#xff0c;并且允许重复。 2. 列表的遍历&#xff1a; lst[1,2,3] for i in range(len(lst)):print(lst[i],end" ")3. 列表的缺点&#x…

虹科方案 | HK-TrueNAS:音频协作的理想存储

一、虹科HK-TRUENAS 非常适合 AVID PRO TOOLS™ 专业音频编辑和大多数媒体和娱乐 (M&E) 工作流程从录制开始&#xff0c;经过后期制作&#xff0c;最后进入播放。这一过程可能需要几个月的时间来拍摄一部大型的电影&#xff0c;也可能需要几个小时甚至几分钟的时间来播放最…

Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、工程交易等业务的企业

招投标管理系统-适合于招标代理、政府采购、企业采购、工程交易等业务的企业 招投标管理系统是一个用于内部业务项目管理的应用平台。以项目为主线&#xff0c;从项目立项&#xff0c;资格预审&#xff0c;标书编制审核&#xff0c;招标公告&#xff0c;项目开标&#xff0c;项…

使用篇丨链路追踪(Tracing)很简单:链路拓扑

作者&#xff1a;涯海 最近一年&#xff0c;小玉所在的业务部门发起了轰轰烈烈的微服务化运动&#xff0c;大量业务中台应用被拆分成更细粒度的微服务应用。为了迎接即将到来的双十一大促重保活动&#xff0c;小玉的主管让她在一周内梳理出订单中心的全局关键上下游依赖&#…

反射~~~

文章目录 反射反射获取Class类对象反射获取构造器对象反射获取成员变量对象反射获取方法对象反射的作用绕过编译阶段为集合添加数据通用框架的底层原理 反射 反射获取Class类对象 getClass()方法为Object类中的成员方法 反射获取构造器对象 parametTypes为参数的类对象 获得类的…

智安网络|网络安全威胁越来越多,教你如何全方面应对

随着互联网的普及和发展&#xff0c;各大网站已经成为人们获取信息和交流的主要平台。然而&#xff0c;随着网络攻击和恶意软件的威胁不断增加&#xff0c;网站经常成为攻击者的目标。因此&#xff0c;在建立和维护网站系统时&#xff0c;必须采取强大的安全措施。 一、网站系…

阅读有感重庆rcgl

1.json转为对应的泛型集合 List<String> resourceList JSON.parseArray(JSON.toJSONString(obj), String.class); 2.集合转换为数组 String[] roles (String[])resourceList.toArray(new String[0]); 3.json转换为对应的javabean SLoginRule loginRule (SLoginRul…

【Web项目实战】从零开始学习Web自动化测试:用Python和Selenium实现网站登录功能

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 1.环境搭建 2.编写测试用例 3.运行测试用例 3.1 命令行方式 3.2 集成到CI/CD流程中 4.结论 Web自动化测…

Windows安装配置Tomcat服务器教程 ——外网远程访问

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 转载自cpolar文章&#xff1a;外网访问本地Tomcat服务器【cpolar内网穿透】…

ReadTimeoutError: HTTPSConnectionPool(host=‘files.pythonhosted.org‘, port=443)

问题&#xff1a; 今天在遇到了安装pytorch中的torchvision包的时候一直超时失败报错如下 ReadTimeoutError: HTTPSConnectionPool(hostfiles.pythonhosted.org, port443): Read timed out. 之前的安装的方式是&#xff1a; pip install --no-deps torchvision 解决办法&…

uni——模拟购物车(全选、全不选、步进器、结算等)

案例演示 案例代码 <template><pageBox ref="pagebox"><view class=

Java程序设计入门教程--案例:自由落体

程序模拟物体从10000米高空掉落后的反弹行为。 球体每落地一次&#xff0c;就会反弹至原高度的一半。按用户输入的弹跳次数&#xff0c;计算球体每次弹跳的高度。 实现过程&#xff1a; 1. 新建项目&#xff1b; 2. 接收 用户输入的弹跳次数&#xff1a; &#xff08;1&#…

通过Robotstudio修改机器人程序的具体方法和步骤

通过Robotstudio修改机器人程序的具体方法和步骤 基本步骤可参考以下内容: 用网线连接机器人和电脑,机器人一侧要插入LAN2口;机器人和电脑的IP地址要在同一个网段内;请求写入权限;修改程序—编译—应用;加载修改后的程序到机器人;保存Robotstudio程序到电脑端;只能修改…