![](/img/trans.png)
[英]Horizontal recyclerview with snaphelper, how to center first and last element?
[英]How can I properly center the first and last items in a horizontal RecyclerView
StackOverflow 包含很多这样的问题,但到目前为止绝对没有解决方案 100% 有效。
我尝试了这些解决方案:
RecyclerView ItemDecoration - 如何为每个 viewHolder 绘制不同宽度的分隔线?
第一个项目中心在 RecyclerView 中的 SnapHelper 中对齐
LinearSnapHelper 不会捕捉 RecyclerView 的边缘项目
如何让 RecyclerView 对齐到中心并且能够滚动到所有项目,而中心被“选中”?
如何在水平 RecyclerView 中捕捉到 LinearSnapHelper 的特定位置?
并且每次第一项未能正确居中时。
我正在使用的示例代码
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(
@NonNull Rect outRect,
@NonNull View view,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state
) {
super.getItemOffsets(outRect, view, parent, state);
final int count = state.getItemCount();
final int position = parent.getChildAdapterPosition(view);
if (position == 0 || position == count - 1) {
int offset = (int) (parent.getWidth() * 0.5f - view.getWidth() * 0.5f);
if (position == 0) {
setupOutRect(outRect, offset, true);
} else if (position == count - 1) {
setupOutRect(outRect, offset, false);
}
}
}
private void setupOutRect(Rect rect, int offset, boolean start) {
if (start) {
rect.left = offset;
} else {
rect.right = offset;
}
}
});
经过调查,我发现这是因为在getItemOffsets
view.getWidth
为 0,尚未测量。
我试图强制测量它,但每次它给出的尺寸都不正确,与它占据的实际尺寸完全不同,它更小。
我也尝试使用addOnGlobalLayoutListener
技巧,但是当它被调用并具有正确的宽度时, outRect
已经被消耗了,所以它丢失了。
我不想设置任何固定大小,因为RecyclerView
的项目可以有不同的大小,因此不能提前设置其填充。
我也不想添加“幽灵”项目来填充空间,而且这些项目也不适用于滚动体验。
我怎样才能让它正常工作?
理想情况下, ItemDecorator
方法看起来是最好的,但对于第一个项目,它ItemDecorator
了。
您也可以更改RecyclerView
本身的填充以获得此效果(只要禁用了clipToPadding
)。 我们可以在LayoutManager
拦截第一个布局阶段,因此即使在第一次布局项目时,它也可以使用更新的填充:
添加这个布局管理器:
open class CenterLinearLayoutManager : LinearLayoutManager {
constructor(context: Context) : super(context)
constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private lateinit var recyclerView: RecyclerView
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
// always measure first item, its size determines starting offset
// this must be done before super.onLayoutChildren
if (childCount == 0 && state.itemCount > 0) {
val firstChild = recycler.getViewForPosition(0)
measureChildWithMargins(firstChild, 0, 0)
recycler.recycleView(firstChild)
}
super.onLayoutChildren(recycler, state)
}
override fun measureChildWithMargins(child: View, widthUsed: Int, heightUsed: Int) {
val lp = (child.layoutParams as RecyclerView.LayoutParams).absoluteAdapterPosition
super.measureChildWithMargins(child, widthUsed, heightUsed)
if (lp != 0 && lp != itemCount - 1) return
// after determining first and/or last items size use it to alter host padding
when (orientation) {
HORIZONTAL -> {
val hPadding = ((width - child.measuredWidth) / 2).coerceAtLeast(0)
if (!reverseLayout) {
if (lp == 0) recyclerView.updatePaddingRelative(start = hPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(end = hPadding)
} else {
if (lp == 0) recyclerView.updatePaddingRelative(end = hPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(start = hPadding)
}
}
VERTICAL -> {
val vPadding = ((height - child.measuredHeight) / 2).coerceAtLeast(0)
if (!reverseLayout) {
if (lp == 0) recyclerView.updatePaddingRelative(top = vPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(bottom = vPadding)
} else {
if (lp == 0) recyclerView.updatePaddingRelative(bottom = vPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(top = vPadding)
}
}
}
}
// capture host recyclerview
override fun onAttachedToWindow(view: RecyclerView) {
recyclerView = view
super.onAttachedToWindow(view)
}
}
然后将它用于您的 RecyclerView:
recyclerView.layoutManager = CenterLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView.clipToPadding = false // disabling clip to padding is critical
我一直在使用Pawel 的回答中的CenterLinearLayoutManager
,到目前为止它几乎完美地工作。 我说几乎,不是因为它不能立即工作,而是因为我最终在使用提到的布局管理器的同一个RecyclerView
中使用了LinearSnapHelper
。
因为 snap helper 依赖于知道RecyclerView
paddings 来计算它的正确中心,所以只设置第一个项目的 padding 会破坏这个过程,导致第一个项目(以及直到最后一个实际显示的后续项目)从中心偏移.
我的解决方案是确保在显示第一个项目时从一开始就设置两个填充。
所以这:
if (!reverseLayout) {
if (lp == 0) recyclerView.updatePaddingRelative(start = hPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(end = hPadding)
} else {
if (lp == 0) recyclerView.updatePaddingRelative(end = hPadding)
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(start = hPadding)
}
变成这个
if (!reverseLayout) {
if (lp == 0) recyclerView.updatePaddingRelative(start = hPadding, end = hPadding) // here we set the same padding for both sides
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(end = hPadding)
} else {
if (lp == 0) recyclerView.updatePaddingRelative(end = hPadding, start = hPadding) // here we set the same padding for both sides
if (lp == itemCount - 1) recyclerView.updatePaddingRelative(start = hPadding)
}
我假设同样的逻辑也必须应用于垂直块。
我相信现在可以进一步优化,所以上面的最终块看起来像这样:
if (lp == 0) recyclerView.updatePaddingRelative(start = hPadding, end = hPadding) // here we set the same padding for both sides
if (lp == itemCount - 1) {
if (!reverseLayout) recyclerView.updatePaddingRelative(end = hPadding)
if (reverseLayout) recyclerView.updatePaddingRelative(start = hPadding)
}
注意:我最终发现,如果我使用它们之间具有显着宽度差异的项目,那么在加载最后一个项目时也会发生我在此处提到的相同问题,这是有道理的,因为那时我们在与另一侧不同的一侧设置了填充一,再次抛开原生中心计算。
这个解决方案是在第一项加载和最后一项加载时为双方设置相同的填充,就像这样
recyclerView.updatePaddingRelative(start = hPadding, end = hPadding)
字面意思就是这样,与上一个示例中的 if 不同。
当然,这仍然无法解决以显示第一个和最后一个项目的方式显示的项目太少的问题,但是当我设法找到该特定情况的解决方案时,我将在此处更新。
据我所知,您希望您的第一个和最后一个项目位于您的recyclerview
的中心。 如果是这样,我会推荐一个更简单的解决方法。
public class OverlaysAdapter extends RecyclerView.Adapter<OverlaysAdapter2.CategoryViewHolder> {
private int fullWidth;//gets the recyclerview full width in constructor. In my case it is full display width.
@Override
public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
FrameLayout fr = (FrameLayout) inflater.inflate(R.layout.item_sticker, parent, false);
if (viewType==TYPE_LEFT_ITEM) {
int marginLeft = (fullWidth-leftItemWidth)/2;
((RecyclerView.LayoutParams)fr.getLayoutParams()).setMargins(marginLeft, 0,0,0);
} else if (viewType==TYPE_RIGHT_ITEM) {
int marginRight = (fullWidth-rightItemWidth)/2;
((RecyclerView.LayoutParams)fr.getLayoutParams()).setMargins(0, 0,marginRight,0);
} else if (viewType==TYPE_MIDDLE_ITEM) {
((RecyclerView.LayoutParams)fr.getLayoutParams()).setMargins(0, 0,0,0);
}
return new CategoryViewHolder(fr);
}
private final int TYPE_LEFT_ITEM = 1;
private final int TYPE_MIDDLE_ITEM = 2;
private final int TYPE_RIGHT_ITEM = 3;
@Override
public int getItemViewType(int position) {
if (position==0)
return TYPE_RIGHT_ITEM;
else if (position==items.size()-1)
return TYPE_LEFT_ITEM;
else
return TYPE_MIDDLE_ITEM;
}
}
这个想法很简单。 为第一项、中间项和最后一项定义三种视图类型。 注意项目视图类型并计算所需的边距并设置边距。
请注意,在我的情况下,左边距用于最后一项,右边距用于第一项,因为布局始终为RTL
。 您可能希望使用相反的顺序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.