【问题标题】:Stop ScrollView from scrolling when TextView with selectable text becomes focused当具有可选文本的 TextView 成为焦点时,停止 ScrollView 滚动
【发布时间】:2020-11-28 22:57:59
【问题描述】:

我有一个 ScrollView,其中嵌套了多个 TextView 子级。我希望这些子项中的文本是可选择的 (android:textIsSelectable="true"),因此用户可以使用复制、共享和选择所有操作。但是当其中一个孩子获得焦点(通过触摸或长按)时,它会导致父 ScrollView 滚动到焦点孩子。我想这是一个特性,但它给我带来了三个问题。

  1. 当长按选择文本时,它会导致文本滚动,因此焦点子项上方的子项将滚动到可见范围之外。
  2. 所选文本的位置会发生变化,不会出现在用户长按的屏幕上,不直观。
  3. 我的应用程序响应双击手势。双击的第一次触摸(或点击)将导致接收到触摸事件的孩子获得焦点,从而导致滚动。显然,滚动会导致手势失败;子被触摸,父滚动,子被再次触摸,但未检测到双击手势。

使子项无法聚焦会阻止滚动,但随后无法选择文本。那么我怎样才能让TextView 视图嵌套在带有可选文本的ScrollView 中,但在其中一个视图获得焦点时阻止滚动?

【问题讨论】:

    标签: android textview focus android-scrollview


    【解决方案1】:

    在寻找此问题的可能解决方案时,我发现了这篇文章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,它仍然有效。所以你只需要重写方法,超类型无关紧要。

    【讨论】:

      猜你喜欢
      • 2021-09-08
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-02
      • 1970-01-01
      相关资源
      最近更新 更多