如何防止 Horizo​​ntalScrollView 的 scroll/fling 上的焦点发生变化?

How can I prevent focus changes on scroll/fling for a HorizontalScrollView?

当 EditText 或其他可聚焦视图存在于 HorizontalScrollView 内部时,它会在滑动时获得聚焦。

深入研究源码,你会明白为什么:

    /**
     * Fling the scroll view
     *
     * @param velocityX The initial velocity in the X direction. Positive
     *                  numbers mean that the finger/cursor is moving down the screen,
     *                  which means we want to scroll towards the left.
     */
    public void fling(int velocityX) {
        if (getChildCount() > 0) {
            int width = getWidth() - mPaddingRight - mPaddingLeft;
            int right = getChildAt(0).getWidth();

            mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
                    Math.max(0, right - width), 0, 0, width/2, 0);

            final boolean movingRight = velocityX > 0;

            View currentFocused = findFocus();
            View newFocused = findFocusableViewInMyBounds(movingRight,
                    mScroller.getFinalX(), currentFocused);

            if (newFocused == null) {
                newFocused = this;
            }

            if (newFocused != currentFocused) {
                newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
            }

            postInvalidateOnAnimation();
        }
    }

为此建议的一些解决方法涉及使用以下属性:

   android:descendantFocusability="beforeDescendants"
   android:focusable="true"
   android:focusableInTouchMode="true"

这适用于一些简单的情况,但如果你的 HorizontalScrollView 嵌套在另一个 ScrollView 中,它可能会导致奇怪的行为(例如,外部滚动视图将跳转到现在被聚焦的容器).

同样根据尝试此解决方案的经验,可能需要将其添加到 每个 包含可聚焦视图(例如 EditText)的父容器中。如果你有任何复杂的焦点逻辑在进行,这一切都会失控。

还有其他解决方法吗?

另一种解决方案是继承 HorizontalScrollView 并进行以下修改:


/**
 * Class which modifies default focus logic so that children aren't focused
 * when scrolling/flinging.
 */
class NoFocusHorizontalScrollView(context: Context, attrs: AttributeSet) : HorizontalScrollView(context, attrs) {

  private var flingCallInProgress: Boolean = false

  override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean {
    return true
  }

  override fun fling(velocityX: Int) {
    // Mark that the fling is in progress before calling the fling logic. 
    // This should be fairly safe given we're on the UI thread.

    flingCallInProgress = true
    super.fling(velocityX)
    flingCallInProgress = false
  }

  override fun getFocusables(direction: Int): ArrayList<View> {
    // During a fling HSV will try to focus on a child. This helps prevents that behavior.

    return if (flingCallInProgress) {
      ArrayList(0)
    } else {
      super.getFocusables(direction)
    }
  }

}