整体流程
绘制
1.ViewRootImpl#performTraversals
ini
private void performTraversals() {
...
boolean skipDraw = false;
if (mFirst) {
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
}
}
}
mFirst = false;
...
}
第一次收到VSYNC信号后,判断DecorView内子孙View是否有获焦,没有则调用DecorView#requestFocus
2.ViewGroup#requestFocus
java
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);
}
}
}
FOCUS_AFTER_DESCENDANTS:先让子View处理,未消费交给ViewGroup
FOCUS_BLOCK_DESCENDANTS:ViewGroup本身处理,不让子View获焦
FOCUS_BEFORE_DESCENDANTS:先让ViewGroup处理,未消费交给子View
3.ViewGroup#onRequestFocusInDescendants
arduino
protected boolean onRequestFocusInDescendants(int direction,
Rect previouslyFocusedRect) {
...
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;
}
4.View#requestFocus
kotlin
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
(mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
判断当前View是否获焦、可见,以及祖先ViewGroup是否Block
5.View#handleFocusGainInternal
scss
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();
}
}
告诉父View已获焦 回调ViewTreeObserver#OnGlobalFocusChangeListener 回调View#OnFocusChangedListener
6.ViewGroup#requestChildFocus
scss
public void requestChildFocus(View child, View focused) {
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
super.unFocus(focused);
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
尝试清理焦点,更新mFocused mFocused:获焦View、包含获焦View的直接子View
7.ViewGroup#unFocus
typescript
@Override
void unFocus(View focused) {
if (mFocused == null) {
super.unFocus(focused);
} else {
mFocused.unFocus(focused);
mFocused = null;
}
}
这里其实就是找到和焦点View相同的公共父节点View,调用unFocus,将焦点所有的祖先View的mFocused置为空
8.View#unFocus
scss
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
if (propagate && mParent != null) {
mParent.clearChildFocus(this);
}
onFocusChanged(false, 0, null);
refreshDrawableState();
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
按键
1.ViewPostImeInputStage#processKeyEvent
java
private int processKeyEvent(QueuedInputEvent q) {
...
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
...
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
...
if (v.requestFocus(direction, mTempRect)) {
return FINISH_HANDLED;
}
}
} else {
View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}
}
}
ACTION_DOWN返回true,系统不会再进行焦点处理 找到当前焦点,从当前获焦位置进行寻焦,找到焦点后进行获焦 如果没有焦点,从DecorView中进行焦点查找
2.ViewGroup#dispatchKeyEvent
csharp
public boolean dispatchKeyEvent(KeyEvent event) {
...
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
...
return false;
}
这里可以看出KeyEvent只在焦点View的祖先由上而下进行分发
3.ViewGroup#findFocus
kotlin
public View findFocus() {
...
if (isFocused()) {
return this;
}
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
通过每一层持有的mFocused找到焦点
4.View#focusSearch
kotlin
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
5.ViewGroup#focusSearch
kotlin
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;
}
递归到DecorView后调用FocusFinder进行焦点查找。当然ViewPager、RecyclerView的focusSearch已经override,并不一定能走到DecorView
寻焦策略
1. FocusFinder#findNextFocus
ini
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
if (focused != null) {
next = findNextUserSpecifiedFocus(root, focused, direction);
}
if (next != null) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
root.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
next = findNextFocus(root, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
寻找nextFocusLeft、nextFocusRight等focused在代码中强制指定要获焦的View 调用root#addFocusables,将ViewGroup下的参与本次选焦过程
2.ViewGroup#addFocusables
ini
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
if (shouldBlockFocusForTouchscreen()) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
child.addFocusables(views, direction, focusableMode);
}
}
}
递归调用所有子View的addFocusables方法,可能将所有View都添加到ArrayList中,ViewPager、RecyclerView等的addFocusables也已override
3.FocusFinder#findNextFocus
csharp
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);
}
...
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);
}
}
- 统一到root所在的坐标系内, 修正focusedRect,left、top 、right、bottom 相对于root
- 对下上左右与前进后退进行区分
4.FocusFinder#findNextFocusInAbsoluteDirection
ini
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
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;
int numFocusables = focusables.size();
for (int i = 0; i < numFocusables; i++) {
View focusable = focusables.get(i);
if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system
focusable.getFocusedRect(mOtherRect);
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
return closest;
}
修正后代的Rect,判断后代的Rect是否比mBestCandidateRect好,mBestCandidateRect初始值为假定的(FOCUS_LEFT : 大小相同,在其右侧+1px)
5.FocusFinder#isBetterCandidate
kotlin
boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
// to be a better candidate, need to at least be a candidate in the first
// place :)
if (!isCandidate(source, rect1, direction)) {
return false;
}
// we know that rect1 is a candidate.. if rect2 is not a candidate,
// rect1 is better
if (!isCandidate(source, rect2, direction)) {
return true;
}
// if rect1 is better by beam, it wins
if (beamBeats(direction, source, rect1, rect2)) {
return true;
}
// if rect2 is better, then rect1 cant' be :)
if (beamBeats(direction, source, rect2, rect1)) {
return false;
}
// otherwise, do fudge-tastic comparison of the major and minor axis
return (getWeightedDistanceFor(
majorAxisDistance(direction, source, rect1),
minorAxisDistance(direction, source, rect1))
< getWeightedDistanceFor(
majorAxisDistance(direction, source, rect2),
minorAxisDistance(direction, source, rect2)));
}
- rect1不在获焦View左侧,直接丢弃
- rect1在、rect2不在获焦View左侧,rect1是最佳候选
- rect1、rect2都在获焦View左侧,通过向左投影进行判断
- rect1在投影上、rect2不在投影上,rect1是最佳候选
- rect2在投影上、rect1不在投影上,rect1竞选失败
- 最后根据主、次轴加权距离进行判断,距离短获胜
6.FocusFinder#isCandidate
arduino
boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
switch (direction) {
case View.FOCUS_LEFT:
return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
&& srcRect.left > destRect.left;
case View.FOCUS_RIGHT:
return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
&& srcRect.right < destRect.right;
case View.FOCUS_UP:
return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
&& srcRect.top > destRect.top;
case View.FOCUS_DOWN:
return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
&& srcRect.bottom < destRect.bottom;
}
}
FOCUS_LEFT :与source相比,src的left必须要小, right要小。可以简单理解为在左侧即可
7.FocusFinder#beamBeats
arduino
boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
if (rect2InSrcBeam || !rect1InSrcBeam) {
return false;
}
if (!isToDirectionOf(direction, source, rect2)) {
return true;
}
if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
return true;
}
return (majorAxisDistance(direction, source, rect1)
< majorAxisDistanceToFarEdge(direction, source, rect2));
}
- 判断rect1是不是更好,从焦点处向获焦方向进行投影,如果rect2在投影内或rect1不在投影内,说明rect1不符合竞选结果。
- rect2不在source的寻焦方向上,rect1竞选成功。
9.FocusFinder#getWeightedDistanceFor
arduino
int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
return 13 * majorAxisDistance * majorAxisDistance
+ minorAxisDistance * minorAxisDistance;
}
13*主轴距离的平方+次轴距离的平方
arduino
static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
switch (direction) {
case View.FOCUS_LEFT:
return source.left - dest.right;
case View.FOCUS_RIGHT:
return dest.left - source.right;
case View.FOCUS_UP:
return source.top - dest.bottom;
case View.FOCUS_DOWN:
return dest.top - source.bottom;
}
}
主轴距离: 获焦View的left到目标的right
arduino
static int minorAxisDistance(int direction, Rect source, Rect dest) {
switch (direction) {
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
// the distance between the center verticals
return Math.abs(
((source.top + source.height() / 2) -
((dest.top + dest.height() / 2))));
case View.FOCUS_UP:
case View.FOCUS_DOWN:
// the distance between the center horizontals
return Math.abs(
((source.left + source.width() / 2) -
((dest.left + dest.width() / 2))));
}
}
次轴距离: 获焦View与目标View的竖直中心距离
Case分析
焦点记忆
方案1:ViewGroup整体参与竞选
java
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (hasFocus()) {
super.addFocusables(views, direction, focusableMode);
} else {
views.add(this);
}
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (mLastFocused != null && mLastFocused.requestFocus()) {
return true;
}
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
mLastFocused = focused;
}
方案2: 只让上次获焦View参与竞选
java
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (hasFocus()) {
super.addFocusables(views, direction, focusableMode);
} else {
views.add(mLastFocused);
}
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
mLastFocused = focused;
}
参考文档
cloud.tencent.com/developer/a... cloud.tencent.com/developer/a... tech.bytedance.net/articles/11...