在 Android 开发过程中,我们要考虑到无障碍用户的需求和体验。文中将为您介绍在滚动容器中的焦点问题,并提供解决方案和示例代码。
问题描述
在 Android 应用中,一些容器(例如:ScrollView, RecyclerView),往往会存在子元素超出容器大小的情况。这个时候,用户需要使用滑动手势才能查看全部内容。但是在某些情况下,当在容器中滑动焦点时,焦点可能会跳出容器,从而使得一些无障碍用户丧失使用效果。
例如,一个 RecyclerView 中包含了多个 TextView,假设当前焦点在第一个 TextView,用户向上滑动时,焦点会从该 RecyclerView 中跳出,从而使得用户失去了查看下一个 TextView 的能力。
解决方案
为了解决这个问题,我们需要重写容器的滑动方法,使其能够处理焦点留在容器内,并且当焦点到达容器边缘时能够自动滑动。我们可以通过重写容器的 dispatchKeyEventPreIme() 或者 dispatchKeyEvent(),在容器内部判断是否需要处理滑动事件。
以下是一个 ScrollView 中解决焦点跳出容器问题的示例代码:
// www.javascriptcn.com code example
public class MyScrollView extends ScrollView {
private boolean shouldHandleScroll;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (shouldHandleScroll && event.getAction() == KeyEvent.ACTION_DOWN) {
int keyCode = event.getKeyCode();
View focused = findFocus();
if (keyCode == KeyEvent.KEYCODE_DPAD_UP && focused != null && focused.canScrollVertically(-1)) {
scrollBy(0, -1 * getChildAt(0).getHeight() / 2);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && focused != null && focused.canScrollVertically(1)) {
scrollBy(0, getChildAt(0).getHeight() / 2);
}
return true;
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if (shouldHandleScroll && event.getAction() == KeyEvent.ACTION_DOWN) {
int keyCode = event.getKeyCode();
View focused = findFocus();
if (keyCode == KeyEvent.KEYCODE_BACK) {
// 焦点跳出容器,可以做一些自己想要的事情
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP && focused != null && focused.canScrollVertically(-1)) {
scrollBy(0, -1 * getChildAt(0).getHeight() / 2);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && focused != null && focused.canScrollVertically(1)) {
scrollBy(0, getChildAt(0).getHeight() / 2);
}
return true;
}
return super.dispatchKeyEventPreIme(event);
}
@Override
public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
shouldHandleScroll = true;
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
shouldHandleScroll = true;
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
shouldHandleScroll = gainFocus;
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
@Override
public void fling(int velocityY) {
shouldHandleScroll = true;
super.fling(velocityY);
}
@Override
public void smoothScrollBy(int dx, int dy) {
shouldHandleScroll = true;
super.smoothScrollBy(dx, dy);
}
@Override
public void smoothScrollTo(int x, int y) {
shouldHandleScroll = true;
super.smoothScrollTo(x, y);
}
}在这个 MyScrollView 中,我们做了以下处理:
- 重写了 dispatchKeyEventPreIme() 和 dispatchKeyEvent() 方法,当用户试图在容器内部滑动焦点时,如果达到了边界需要自动滚动,否则直接调用系统的处理方法。在该方法中我们会需要调用 ScrollView 的 scrollBy() 方法来实现滚动。
- 重写了 onFocusChanged() 方法,当 ScrollView 获得焦点时,将 shouldHandleScroll 设为 true,表示该 ScrollView 应该处理滑动事件。
- 重写了 fling() 方法和 smoothScrollBy() 方法,同样将 shouldHandleScroll 设为 true,表示该 ScrollView 应该处理滑动事件。需要注意的是,smoothScrollTo() 方法的使用需要注意跨年问题。
结语
通过以上的处理,我们可以在滑动容器中实现焦点的不出容器,并且滚动到边缘时自动滚动的操作。这样我们就能够为无障碍用户提供一个更加友好和亲切的体验。
Source: FunTeaLearn,Please indicate the source for reprints https://funteas.com/post/67d7ec4ea941bf7134e2911a