5.1 descendantFocusability 机制深度解析
属性定义与作用
descendantFocusability 是ViewGroup的关键属性,决定其如何处理子视图的焦点:
// ViewGroup.java
public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
| 值 | 常量 | 行为 |
|---|---|---|
| 0 | FOCUS_BEFORE_DESCENDANTS | ViewGroup优先获取焦点 |
| 1 | FOCUS_AFTER_DESCENDANTS | 子视图优先获取焦点 |
| 2 | FOCUS_BLOCK_DESCENDANTS | 阻止子视图获取焦点 |
源码实现
// ViewGroup.java
public void addFocusables(ArrayList<View> views, int direction) {
final int focusableCount = views.size();
// 1. FOCUS_BEFORE_DESCENDANTS: 先添加自身
if (mDescendantFocusability != FOCUS_AFTER_DESCENDANTS) {
super.addFocusables(views, direction);
}
// 2. 添加可聚焦的子视图
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
child.addFocusables(views, direction);
}
}
// 3. FOCUS_AFTER_DESCENDANTS: 最后添加自身
if (mDescendantFocusability == FOCUS_AFTER_DESCENDANTS &&
focusableCount == views.size()) {
super.addFocusables(views, direction);
}
}
5.2 焦点请求处理流程
请求焦点时的决策
// ViewGroup.java
@Override
public boolean requestChildFocus(View child, View focused) {
// 1. 更新当前焦点子视图
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(); // 清除原焦点
}
mFocused = child; // 设置新焦点
}
// 2. 处理滚动容器的自动滚动
if (mParent != null) {
mParent.requestChildFocus(this, focused);
// 如果是ScrollView,自动滚动到子视图
if (isScrollContainer()) {
scrollToDescendant(focused);
}
}
return true;
}
ScrollView的滚动实现
// ScrollView.java
@Override
public void requestChildFocus(View child, View focused) {
if (!mIsLayoutDirty) {
// 立即滚动到焦点视图
scrollToChild(focused);
} else {
// 布局未完成时延迟滚动
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
}
private void scrollToChild(View child) {
child.getDrawingRect(mTempRect);
// 坐标转换
offsetDescendantRectToMyCoords(child, mTempRect);
// 计算滚动距离
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
}
5.3 RecyclerView焦点优化
焦点查找特殊处理
// RecyclerView.java
@Override
public View focusSearch(View focused, int direction) {
// 1. 委托给LayoutManager处理
View result = mLayout.onInterceptFocusSearch(focused, direction);
if (result != null) {
return result;
}
// 2. 处理自定义焦点逻辑
final boolean canRunFocusFailure = mLayout != null && mAdapter != null;
// 3. 处理方向键导航
if (canRunFocusFailure &&
(direction == View.FOCUS_UP || direction == View.FOCUS_DOWN)) {
return handleGridFocusSearch(focused, direction);
}
return super.focusSearch(focused, direction);
}
网格布局焦点处理
// GridLayoutManager.java
@Override
public View onInterceptFocusSearch(View focused, int direction) {
final int currentPos = getPosition(focused);
switch (direction) {
case View.FOCUS_UP:
return findViewByPosition(currentPos - mSpanCount);
case View.FOCUS_DOWN:
return findViewByPosition(currentPos + mSpanCount);
case View.FOCUS_LEFT:
return findViewByPosition(currentPos - 1);
case View.FOCUS_RIGHT:
return findViewByPosition(currentPos + 1);
}
return null;
}
5.4 焦点边界处理
边界循环逻辑
// 实现环形焦点导航
@Override
public View focusSearch(View focused, int direction) {
int currentIndex = indexOfChild(focused);
switch (direction) {
case FOCUS_RIGHT:
if (currentIndex < getChildCount() - 1) {
return getChildAt(currentIndex + 1);
} else {
return getChildAt(0); // 循环到第一个
}
case FOCUS_LEFT:
if (currentIndex > 0) {
return getChildAt(currentIndex - 1);
} else {
return getChildAt(getChildCount() - 1); // 循环到最后一个
}
}
return super.focusSearch(focused, direction);
}
边界检测算法
// FocusFinder.java
private boolean isCandidate(Rect focusedRect, Rect candidateRect, int direction) {
switch (direction) {
case FOCUS_RIGHT:
// 候选视图必须在焦点视图右侧
return focusedRect.right <= candidateRect.right;
case FOCUS_LEFT:
// 候选视图必须在焦点视图左侧
return focusedRect.left >= candidateRect.left;
// ... 其他方向类似
}
return false;
}
5.5 焦点性能优化
优化策略
-
空间分区索引:
// 使用空间索引加速查找 SparseArray<View> focusableViews = new SparseArray<>(); void indexFocusables() { ArrayList<View> focusables = new ArrayList<>(); addFocusables(focusables, View.FOCUS_FORWARD); for (View v : focusables) { Rect rect = new Rect(); v.getFocusedRect(rect); focusableViews.put(hashPosition(rect), v); } } -
延迟计算:
// 仅在布局变化时重新计算 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { indexFocusables(); } } -
方向预过滤:
// 只考虑当前方向的候选视图 void addFocusables(ArrayList<View> views, int direction) { // 根据方向过滤视图 for (View child : children) { if (isInDirection(child, direction)) { views.add(child); } } }
5.6 常见问题解决方案
问题1:焦点被父容器拦截
// 解决方案:设置正确的descendantFocusability
viewGroup.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
问题2:滚动容器不自动滚动
// 确保容器可滚动且子视图正确请求焦点
scrollView.setScrollContainer(true);
childView.requestFocus();
问题3:RecyclerView焦点跳跃
// 自定义LayoutManager处理焦点
recyclerView.setLayoutManager(new LinearLayoutManager(context) {
@Override
public View onInterceptFocusSearch(View focused, int direction) {
// 自定义焦点逻辑
}
});
5.7 调试工具与技术
焦点边界可视化
// 在onDraw中绘制焦点边界
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isFocused()) {
Rect rect = new Rect();
getFocusedRect(rect);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
canvas.drawRect(rect, paint);
}
}
ADB调试命令
# 查看ViewGroup的焦点策略
adb shell dumpsys activity top | grep -A 10 "Focusability"
# 检查descendantFocusability值
adb shell dumpsys view --property descendantFocusability com.example.app
本章小结
-
descendantFocusability机制:
- 控制ViewGroup与子视图的焦点获取优先级
- 三种策略:BEFORE_DESCENDANTS, AFTER_DESCENDANTS, BLOCK_DESCENDANTS
-
滚动容器焦点处理:
- ScrollView自动滚动到焦点子视图
- RecyclerView委托LayoutManager处理焦点查找
-
焦点边界处理:
- 边界循环导航实现
- 方向性候选视图筛选
-
性能优化策略:
- 空间分区索引
- 延迟计算
- 方向预过滤
-
常见问题解决:
- 焦点被拦截:调整descendantFocusability
- 滚动不生效:确保容器可滚动
- RecyclerView焦点跳跃:自定义LayoutManager
-
调试技术:
- 焦点边界可视化
- ADB命令检查焦点策略
关键源码路径:
frameworks/base/core/java/android/view/ViewGroup.java
frameworks/base/core/java/android/widget/ScrollView.java
frameworks/base/core/java/android/support/v7/widget/RecyclerView.java
在下一章中,我们将探讨自定义焦点控制的高级技巧,包括如何重写焦点行为、构建动态焦点链以及解决复杂场景下的焦点冲突。