修改垂直 LinearLayoutManager 使其也可以水平滚动(包括视频)

Modifying vertical LinearLayoutManager so it can also be scrolled horizontally (video included)

我需要修改与 RecyclerView 一起使用的垂直 LinearLayoutManager,以便它可以水平滚动。

当 RecyclerView 垂直滚动时,它会添加新项目,因为我正在扩展 LinearLayoutManager


我认为这会解决问题。

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;
    }

}

现在,当我水平滚动 RecyclerView 时,它可以正常工作,但内容可以滚动到屏幕外。 此外,当内容水平滚动然后垂直滚动时,RecycerView 将添加与左屏幕边框对齐的新视图,而不是与初始视图对齐。
演示行为的视频: http://youtu.be/FbuZ_jph7pw
我做错了什么?
更新 (正如@yigit 所建议的)
现在我正在使用 LinearLayoutManager 的修改版本(我不再扩展它,因为我需要在私有方法 fill(... .))。
这些是我所做的更改:

    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);

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

滚动 CustomLinearLayout(更新视频)http://youtu.be/DHeXkvpgruo

现在的问题是获取RecyclerView children的最大宽度并更新childrenMaxWith。

LinearLayoutManager 确实支持单向滚动,因此当它需要布局新的 child 时,它只需从左侧开始。 offsetChildren方法只是移动children,不保留任何信息。

您最好克隆 LinearLayoutManager 并覆盖 layoutChunk 方法。但是每次发布新的 RV 版本时,您都需要这样做。

另一种可能的(快速且肮脏的)解决方案是:

  • 跟踪您的水平滚动位置
  • 在您的 LayoutManager 中覆盖 layoutDecorated 并通过您的水平滚动偏移偏移 leftright 值,然后调用 super.

这可能会起作用,但有可能破坏 LayoutManger 中的任何未来更改。不过风险不高,因为这个方法很核心。

我最近在 RecyclerView 中显示表格数据时遇到了类似的问题。就我而言,所有被回收的视图都具有相同的宽度。我知道原始问题并非如此,但使用统一宽度可确保您的列全部正确对齐,因此这样更容易阅读。它还可以更容易地确定何时停止水平滚动;你只需使用 getChildAt(0)?.measuredWidth。此外,API 可能自 2015 年以来在 LinearLayoutManager 中发生了变化:我发现如果你想使用 yigit 建议的 "quick and dirty" 解决方案,你必须覆盖 layoutDecoratedWithMargins 而不是 layoutDecorated (覆盖两者可能没有什么坏处)。我是这样实现的:

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