joy keeps flowin'

View的焦点

xx
目次

本篇内容探讨View的焦点。怎么算有焦点,同一时间有几个View有焦点,焦点的分发又是怎么回事。

焦点 #

View有个方法叫hasFocus,返回值是boolean类型的,用来判断View是否有焦点。hasFocus只做了简单的一件事,判断flag中是否包含PFLAG_FOCUSED。

1
 return (mPrivateFlags & PFLAG_FOCUSED) != 0;

因此只要有PFLAG_FOCUSED这个flag就是有焦点了。

焦点分发 #

以PFLAG_FOCUSED作为突破点,很容易找到开端。最最开始是从ViewRootImpl开始的。事件的分发相信你是熟悉的,上层事件分发的起点也是焦点分发的起点,原因是在事件的ACTION_DOWN事件中,把焦点交给符合条件的View。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private boolean performFocusNavigation(KeyEvent event) {
   ...
        View focused = mView.findFocus();
        if (focused != null) {
            View v = focused.focusSearch(direction);
            if (v != null && v != focused) {
                // Do the math to 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)) {
                    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;
            }
        }
  ...
}

ViewRootImpl中唯一的一个View是DecorView,开始时找不到焦点,会把焦点给DecorView。DecorView会在requestFocus中分发下去,最终焦点会给第一个满足条件的View。

在这里ViewGroup需要策略和顺序。 策略分别有:

分发的方向是根据ViewRootImpl分发的事件的KeyEvent。

这就是第一次点击时焦点分发的过程。

第二次点击时,拿到上次得到焦点的View。也会按照这次的分发的方向找到一个应该获取焦点的View。

焦点的条件 #

View需要满足以下几个条件时才有可能获取到焦点。

  1. 可见
  2. focusable
  3. enable
  4. size > 0

转换 #

两个View不同时用到了两个方法:

方法名转换方向常用场景
​offsetDescendantRectToMyCoords子视图坐标系到当前视图坐标系计算子视图在当前视图中的具体位置
​offsetRectIntoDescendantCoords当前视图坐标系到子视图坐标系计算当前视图的某区域相对于子视图的位置

结合起来也就是把两个View的坐标转换成一致的,把这个矩形给下去,以便在View的onFocusChanged可以用到,比如ListView需要显示有焦点的View,需要移动。

标签:
Categories: