loeschg
loeschg

Reputation: 30581

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

When an EditText or other focusable View exists inside of a HorizontalScrollView it gets focused when flinging.

Digging into source, you can see why:

    /**
     * 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();
        }
    }

Some of the suggested workarounds for this involve using the following attributes:

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

This works well for some simple cases, but it can lead to odd behavior if your HorizontalScrollView is nested in another ScrollView (e.g the outer scroll view will jump to the container now being focused).

Also from experience trying this solution, it can require adding it to every parent container containing a focusable view (e.g. EditText). If you have any sort of complex focus logic going on, this all can get out of hand.

Are there any other workaround solutions?

Upvotes: 2

Views: 359

Answers (1)

loeschg
loeschg

Reputation: 30581

One other solution to this is to subclass HorizontalScrollView and make the following modifications:


/**
 * 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)
    }
  }

}

Upvotes: 1

Related Questions