Home » Android » android – scrollBy doesn't work properly in nested recyclerview

android – scrollBy doesn't work properly in nested recyclerview

Posted by: admin May 14, 2020 Leave a comment

Questions:

I have a vertically scrolling RecyclerView with horizontally scrolling inner RecyclerViews just like this.

expl

With this implementation, users can scroll each horizontal recyclerview synchronously. However, when a user scroll vertically to the parent recyclerView, a new horizontal recyclerview which has just attached on window doesn’t display on same scroll x position. This is normal. Because it has just created.

So, I had tried to scroll to the scrolled position before it was displayed. Just like this:

Note: this is in adapter of the parent recyclerview whose orientation is vertical.

 @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        CellColumnViewHolder viewHolder = (CellColumnViewHolder) holder;
        if (m_nXPosition != 0) {
             // this doesn't work properly 
             viewHolder.m_jRecyclerView.scrollBy(m_nXPosition, 0);
        }
    }

enter image description here

As you can see, scrollBy doesn’t effect for row 10, row 11, row 12 and row 13 After that, I debugged the code to be able find out find out what’s happening. When I set scroll position using scrollBy, childCount() return zero for row 10, row 11, row 12 and row 13 So they don’t scroll. But why ? and Why others work ?

  • How can I fix this ?
  • Is onViewAttachedToWindow right place to scroll new attached recyclervViews ?

Note: I have also test scrollToPosition(), it doesn’t get any problem like this. But I can’t use it at my case. Because users can scroll to the any x position which may not the exact position. So I need to set scroll position using x value instead of the position.

Edit: You can check The source code

How to&Answers:

I found a solution that is use scrollToPositionWithOffset method instead using scrollBy. Even if both of two scroll another position, they have really different work process in back side.

For example: if you try to use scrollBy to scroll any pixel position and your recyclerView had not been set any adapter which means there is no any data to display and so it has no any items yet, then scrollBy doesn’t work. RecyclerView uses its layoutManager’s scrollBy method. So in my case, I am using LinearLayoutManager to the horizontal recyclerViews.

Lets see what it’s doing :

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

As you can see scrollBy ignores the scroll intentions if there is no any child at that time.

  if (getChildCount() == 0 || dy == 0) {
     return 0;
  }

On the other hand scrollToPosition can work perfectly even if there is no any set data yet.

According to the Pro RecyclerView slide, the below sample works perfectly. However you can not do that with scrollBy.

void onCreate(SavedInstanceState state) {
    ....
    mRecyclerView.scrollToPosition(selectedPosition);
    mRecyclerView.setAdapter(myAdapter);
}

As a result, I have changed little thing to use scrollToPositionWithOffset().

Before this implementation I was calculating the exact scroll x position as a pixel.

After that, when the scroll came idle state, calculating the first complete visible position to the first parameter of the scrollToPositionWithOffset().

For second parameter which is the offset, I am getting the value using view.getLeft() function which helps to get left position of this view relative to its parent.

enter image description here

And it works perfectly!!