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

时间:2015-02-08 19:14:09

标签: android android-recyclerview

我需要修改与 RcyclerView 一起使用的垂直 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子级的最大宽度并更新childrenMaxWith。

2 个答案:

答案 0 :(得分:2)

LinearLayoutManager确实支持在一个方向上滚动,因此当需要布局新的子项时,它只是从左侧开始。 offsetChildren方法只会移动子项,不会保留任何信息。

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

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

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

这可能会奏效,但有可能打破LayoutManger未来的任何变化。虽然这种方法非常核心,但风险并不高。

答案 1 :(得分:0)

我最近在RecyclerView中显示表格数据时遇到了类似的问题。就我而言,所有被回收的视图都具有相同的宽度。我知道原来的问题不是这种情况,但是采用统一的宽度可以确保您的列都正确对齐,因此更容易阅读。它还使确定何时停止水平滚动变得更加容易。您只需使用getChildAt(0)?.measuredWidth。此外,API可能自2015年以来在LinearLayoutManager中已更改:我发现如果您要使用yigit建议的“快速而肮脏”的解决方案,则必须覆盖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)
    }
}