1

I need to modify the vertical LinearLayoutManager which I use with a RecyclerView so it can be scrolled horizontally.

When the RecyclerView is scrolled vertically it adds new items because I’m extending LinearLayoutManager.


I thought this will solve the problem.

public class CustomLayoutManager extends LinearLayoutManager {


    public TableLayoutManager(Context context) {
        super(context);
    }

    @Override
    public boolean canScrollHorizontally() {
        return true;
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int delta = -dx;

        offsetChildrenHorizontal(delta);

        return -delta;
    }

}

Now when I scroll the RecyclerView horizonatlly it works but the content can be scrolled offscreen. Also when the content is scrolled horizontally and then I scroll vertically, the RecycerView will add the new views aligned with left screen border and not with initial views.
Video that demonstrates the behaviour: http://youtu.be/FbuZ_jph7pw
What I'm doing wrong ?
UPDATE (as suggested by @yigit)
Now I'm using the modified version of LinearLayoutManager (I'm not extending it anymore because I need to add offset to RecyclerView children in private method fill(....)).
These are the changes I made:

    private int childrenLeftOffset = 0;
    private int childrenMaxWith = 800;  // I must update this value with children max width
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {

        int delta = -dx;
        int newOffset = childrenLeftOffset + delta;
        if (newOffset < 0) {
            if(newOffset < -childrenMaxWith) {
                //right edge reached
                offsetChildrenHorizontal(-(childrenMaxWith - Math.abs(childrenLeftOffset)));
                childrenLeftOffset  = -childrenMaxWith;
                delta = 0;  // RecyclerView will draw right edge effect.
            } else {
                //scroll horizontally
                childrenLeftOffset  = childrenLeftOffset + delta;
                offsetChildrenHorizontal(delta);
            }
        } else {
            //left edge reached
            offsetChildrenHorizontal(Math.abs(childrenLeftOffset));
            childrenLeftOffset  = 0;
            delta = 0; // RecyclerView will draw left edge effect.
        }

        return -delta;
    }

    private int fill(RecyclerView.Recycler recycler, RenderState renderState, RecyclerView.State state, boolean stopOnFocusable) {
            ........
            ........

            left = left + myLeftOffset;
            layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin);

            .......
            .......
    }

Scrolling CustomLinearLayout (updated video) http://youtu.be/DHeXkvpgruo

The problem now is to get the maximum width of RecyclerView children and update childrenMaxWith.

Cœur
  • 37,241
  • 25
  • 195
  • 267
vovahost
  • 34,185
  • 17
  • 113
  • 116
  • Hey @VovaHost i'm in a similar kind of problem can you help me out –  May 18 '18 at 06:21
  • Hey @VovaHost can you help me with this https://stackoverflow.com/questions/49952965/recyclerview-horizontal-scrolling-to-left –  May 18 '18 at 11:55

2 Answers2

2

LinearLayoutManager does supports scrolling in one direction so when it needs to layout a new child, it simply starts from left. offsetChildren method just moves children, does not keep any information.

You are better off cloning LinearLayoutManager and overriding layoutChunk method. But you'll need to do it every time a new RV version is released.

Another possible (quick & dirty) solution is:

  • Keep track of your horizontal scroll position
  • override layoutDecorated in your LayoutManager and offset left and right values by your horizontal scroll offset, then call super.

This will probably work but has the potential of breaking in any future change in LayoutManger. It is not high risk though because that method is pretty core.

yigit
  • 37,683
  • 13
  • 72
  • 58
  • I can't find layoutChunk method. I have some troubles implementing the second solution you suggested. I updated the code. – vovahost Feb 09 '15 at 13:08
  • You need a custom solution for `childrenMaxWith` as RV cannot possibly know it. Probably your better option is to calculate and cache it every time you need that value. And you can do it only for visible items. – yigit Feb 09 '15 at 21:45
  • I'm trying now to get the RecyclerView width after it has been measured. I will post here the answer if it works. – vovahost Feb 09 '15 at 21:49
  • Still curious what was `layoutChunk` about. – vovahost Feb 09 '15 at 21:50
  • @vovahost if you're curious about `layoutChunk`, check out the sources of `GridLayoutManager` – Takhion Oct 09 '15 at 16:36
1

I recently had a similar problem with displaying tabular data in a RecyclerView. In my case, all of the views being recycled had the same width. I know that's not the case with the original question, but going with a uniform width ensures that your columns are all aligned properly, so it's easier to read that way. It also makes it much easier to determine when to stop scrolling horizontally; you just use getChildAt(0)?.measuredWidth. Also, the API might have changed since 2015 within LinearLayoutManager: I found that you have to override layoutDecoratedWithMargins instead of layoutDecorated if you want to use the "quick and dirty" solution suggested by yigit (it probably doesn't hurt to override both). This is how I implemented it:

class MyRowLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.VERTICAL, false) {

    private var horizontalOffset = 0

    override fun canScrollHorizontally(): Boolean = true

    override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
        if (childCount == 0) return 0

        val rowWidth = getChildAt(0)?.measuredWidth ?: return 0

        // if the row is narrower than the RecyclerView's width, don't scroll
        if (rowWidth <= width) return 0

        val remainingPxRight = rowWidth - width - horizontalOffset
        // cut scrolling short if we're about to reach end of the row
        val scrolled = when {
            dx > remainingPxRight -> remainingPxRight
            dx < -horizontalOffset -> -horizontalOffset
            else -> dx
        }

        horizontalOffset += scrolled
        offsetChildrenHorizontal(-scrolled)
        return scrolled
    }

    override fun layoutDecorated(child: View, left: Int, top: Int, right: Int, bottom: Int) {
        super.layoutDecorated(child, left - horizontalOffset, top, right - horizontalOffset, bottom)
    }

    override fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {
        super.layoutDecoratedWithMargins(child, left - horizontalOffset, top, right - horizontalOffset, bottom)
    }
}
Alex H
  • 767
  • 8
  • 7