修改垂直 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
并通过您的水平滚动偏移偏移 left
和 right
值,然后调用 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)
}
}
我需要修改与 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
并通过您的水平滚动偏移偏移left
和right
值,然后调用 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)
}
}