In introduction, one day I was debugging an interface. When THE XML layout contained Scroll View and recyclerView was nested inside, the interface automatically rolled to recyclerView as soon as it was inside. Puzzled, I searched a lot of materials on the Internet, most of which only mentioned solutions. However, there is no good explanation for why this happens. In line with the responsible attitude towards technology, it took some time to straighten out the situation

1. In the XML layout that contains the ScrollView, as soon as we load it, the ScrollView automatically scrolls to the child view that gets the focus, so we need to look at what’s going on in our activity onCreate.

A: When we call setContentView(int layRes) in our activity’s onCreate method, We’re going to call the Inflate (XmlPullParser Parser, @Nullable ViewGroup root, Boolean attachToRoot) method of the LayoutInflater, and we’re going to find the XML rootView, Then rInflateChildren(Parser, temp, attrs, true) loads the child View of the XML rootView. If so, the addView method will be called.

public void addView(View child, int index, LayoutParams params) {
    ......
    requestLayout();
    invalidate(true);
    addViewInner(child, index, params, false);
}
Copy the code

AddView addViewInner(View Child, int index, LayoutParams params, Boolean preventRequestLayout);

Android. View. ViewGroup {... private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ......if(child.hasFocus()) { requestChildFocus(child, child.findFocus()); }... }}}Copy the code

Here we need to understand the principle of view rendering, is the hierarchy of the view tree rendering, is the top of the tree, that is, the child view, and then the parent view mechanism. With that in mind, let’s look at the requestChildFocus method of the ViewGroup,

    @Override
    public void requestChildFocus(View child, View focused) {
        if (DBG) {
            System.out.println(this + " requestChildFocus()");
        }
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return;
        }

        // Unfocus us, if necessary
        super.unFocus(focused);

        // We had a previous notion of who had focus. Clear it.
        if(mFocused ! = child) {if(mFocused ! = null) { mFocused.unFocus(focused); } mFocused = child; }if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }

Copy the code

In the above will see mParent. RequestChildFocus (this, focused); “, which is typical in Android and one of the 24 design patterns (chain of responsibility mode), will always be called, that’s it, we will definitely call the ScrollView requestChidlFocus method, and then the Android ScrollView control, Rewrite the requestChildFocus method:

@Override
public void requestChildFocus(View child, View focused) {
    if(! mIsLayoutDirty) { scrollToChild(focused); }else {
        mChildToScrollTo = focused;
    }
    super.requestChildFocus(child, focused);
}
Copy the code

Because the requestLayout() method is called before addViewInner:

@Override
public void requestLayout() {
    mIsLayoutDirty = true;
    super.requestLayout();
}
Copy the code

(mChildToScrollTo = focused) (mChildToScrollTo = focused)

2. Next, we continue to analyze the mParent. RequestChildFocus method (this, focused)?

android.view.ViewGroup{
@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    }

    // Unfocus us, if necessary
    super.unFocus(focused);

    // We had a previous notion of who had focus. Clear it.
    if(mFocused ! = child) {if(mFocused ! = null) { mFocused.unFocus(focused); } mFocused = child; }if(mParent ! = null) { mParent.requestChildFocus(this, focused); }}}Copy the code

First, we evaluate the descendantFocusability property of the ViewGroup, which is returned directly if the FOCUS_BLOCK_DESCENDANTS value is descendantFocusability. And android: descendantFocusability = “blocksDescendants” attribute can solve the cause of the automatic sliding), now let’s look at the if (mParent! = null) mParent. RequestChildFocus (this, focused)} was set up, there will always be the call, until the call to ViewRootImpl requestChildFocus method

@Override
public void requestChildFocus(View child, View focused) {
    if (DEBUG_INPUT_RESIZE) {
        Log.v(mTag, "Request child focus: focus now " + focused);
    }
    checkThread();
    scheduleTraversals();
}
Copy the code

ScheduleTraversals () starts a runnable that performs the performTraversals method to redraw the view tree.

3. Why does the ScrollView slide to the child view that gets the focus?

A: We can see that when a Scrollview contains a view with a focus, the view tree will be redrawn, so the onLayout method of the View will be called. Let’s look at the onLayout method of the Scrollview

android.view.ScrollView{ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); .if(mChildToScrollTo ! = null && isViewDescendantOf(mChildToScrollTo, this)) { scrollToChild(mChildToScrollTo); } mChildToScrollTo = null; . }}Copy the code

As we can see from the first step, we are assigning mChildToScrollTo in requestChildFocus, so we will go to the if judgment and call scrollToChild(mChildToScrollTo) :

private void scrollToChild(View child) {
    child.getDrawingRect(mTempRect);
    offsetDescendantRectToMyCoords(child, mTempRect);

    int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);

    if (scrollDelta != 0) {
        scrollBy(0, scrollDelta);
    }
}
Copy the code

Obviously, the current method is to move the ScrollView into the view specified to get the focus. Here we can see why the ScrollView automatically slides to the child view that gets the focus.

4. Why add the android: in the ScrollView child viewGroup descendantFocusability = “blocksDescendants” attribute can stop the ScrollView automatic sliding?

A: As described in the first step, the view works by: It’s a hierarchical drawing of the view tree, it’s a mechanism for drawing the top of the tree, which is the child view, and then the parent view, So we in the ScrollView immediate child view set android: descendantFocusability blocksDescendants “property =”, ScrollView’s requestChildFocus(View Child, View Focused) method will not continue to execute.

    @Override
    public void requestChildFocus(View child, View focused) {
        ......
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return; }...if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }

Copy the code

5. Believe that have a lot of people here have a question: if you explanation is according to the blogger, whether the ScrollView topped with android: descendantFocusability = “blocksDescendants” attribute can stop automatically slide?

A: According to the previous analysis, it seems to work, but if we look at the source code of ScrollView, we can see that

private void initScrollView() {
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mOverscrollDistance = configuration.getScaledOverscrollDistance();
        mOverflingDistance = configuration.getScaledOverflingDistance();
    }
Copy the code

When you are happy set android: descendantFocusability = “blocksDescendants” attribute thought to solve the problem. But the descendantFocusability property is set to FOCUS_AFTER_DESCENDANTS in the ScrollView code, so adding descendantFocusability to the XML doesn’t help.

The ScrollView will slide to the position of the child view that gets the focus. See also increase android: descendantFocusability = “blocksDescendants” attribute can prevent ScrollView will automatically scroll to get focus child view of reason, but why in gets the child view set of a layer of the view outside of the focus, How about adding the focusableInTouchMode= True attribute to resolve such a slide?

HasFocus () : view.hasfocus () : view.hasfocus () : view.hasfocus () : view.hasfocus () : view.hasfocus () HasFocus () can only be true if the current view is displayed when it is first displayed; 2. If a view of the same level has more than one focus view, then only the first view gets the focus. Adding the focusableInTouchMode= True attribute to the View tag in the layout means that the view’s HasFocus is true when loading, but when fetching the hasFocus method of its child views, They are false. (This means that the ScrollView will slide, but to the position of the view with the focusableInTouchMode= True attribute added. If the view position is filled with the ScrollView, it will not slide. That’s why adding a focusableInTouchMode= True attribute to the external layout prevents The ScrollView from automatically scrolling to the child view that gets the focus.) So putting a layer of focusableInTouchMode= True on the external layout isn’t strictly a statement. Because even though we have a layer of view, if the view is not a scrollView, it will probably slide automatically. So when we set the focusableInTouchMode= True attribute, it is best to add it to the direct child of the ScrollView.

conclusion

According to the above analysis, in fact, we can get a variety of solutions to solve the ScrollView will automatically scroll to the focus of the child view, such as the customized rewrite of ScrollView requestChildFocus method, directly return, can interrupt the ScrollView automatic sliding, RequestChildFocus (ScrollView) {requestChildFocus (ScrollView) {requestChildFocus (ScrollView) { ** Similarly, we can also understand that if RecyclerView nested RecyclerView, leading to automatic sliding, then requestChildFocus should also be rewritten in RecyclerView to prepare for automatic sliding. I also hope you can read the source code to verify yourself.

There are three ways: The first way.

<ScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusableInTouchMode="true"
        android:orientation="vertical">
    </LinearLayout>
</ScrollView>
Copy the code

The second.

<ScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="blocksDescendants"
        android:orientation="vertical">
    </LinearLayout>
</ScrollView>
Copy the code

The third kind.

public class StopAutoScrollView extends ScrollView {
    public StopAutoScrollView(Context context) {
        super(context);
    }

    public StopAutoScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public StopAutoScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void requestChildFocus(View child, View focused) {
    }
}
Copy the code

If you have a better solution, we can discuss, if the article is wrong, welcome to clap brick. If you find this article enlightening, I hope you can help to like it or follow it. Thank you