在寻找此问题的可能解决方案时,我发现了这篇文章https://programmersought.com/article/8487993014/。它与这个问题没有直接关系,但文章显示来自ScrollView 的一些源代码的一个特定部分引起了我的注意:
public void requestChildFocus(View child, View focused) {
if (focused != null && focused.getRevealOnFocusHint()) {
If (!mIsLayoutDirty) {//This boolean value marks whether the layout has been completed. If it is completed, it is false.
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
}
//super method [handling FOCUS_BLOCK_DESCENDANTS case] is called after scrolling on top.
//So setting the android:descendantFocusability="blocksDescendants" attribute to the ScrollView is invalid.
super.requestChildFocus(child, focused);
}
在这里您可以看到ScrollView 如果focused != null 将尝试滚动到焦点子项。因此,要禁用此行为,请创建 ScrollView 的子类并像这样覆盖此方法:
package com.test
// imports here...
public class MyScrollView extends ScrollView {
// constructors here...
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, null);
}
}
通过忽略focused参数并将null传递给超级实现,滚动将永远不会发生,但父级仍会请求子级接收焦点,因此仍然可以选择文本。
剩下要做的就是用上面定义的自定义实现替换布局文件中的ScrollView父级。
编辑
该解决方案在 API 30 上经过测试并且运行良好,但是当我在 API 25 上对其进行测试时,会抛出 NPE 并且应用程序崩溃。 Android Docs 建议使用NestedScrollView 视图代替垂直滚动,但应用程序仍然崩溃。我查看了requestChildFocus(View, View)的源代码,略有不同:
@Override
public void requestChildFocus(View child, View focused) {
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
}
private void scrollToChild(View child) {
child.getDrawingRect(mTempRect);
/* Offset from child's local coordinates to ScrollView coordinates */
offsetDescendantRectToMyCoords(child, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
}
scrollToChild(View) 方法是私有的,所以我们不能覆盖默认实现(因为为什么会有人想要这样做,对吧?),但 scrollBy(int, int) 是公开的。因此,不要在 MyScrollView 类中覆盖 requestChildFocus(View, View),而是覆盖 scrollBy(int, int) 并让它什么都不做。这已在 API 30 和 25 上进行了测试,并且按预期工作而不会崩溃。后来我尝试恢复到扩展ScrollView,它仍然有效。所以你只需要重写方法,超类型无关紧要。