![](/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.