水平居中 RecyclerView 的第一项
Horizontally center first item of RecyclerView
我想使用 RecyclerView
来模拟 MultiViewPager
的行为,特别是我希望将所选项目放在屏幕中央,包括第一个和最后一个元素.
正如您在此图片中看到的,第一项居中,这将是我的预期结果。
我所做的是设置一个 RecyclerView
和一个水平 LinearLayoutManager
和一个 LinearSnapHelper
。这个解决方案的问题是第一个和最后一个项目永远不会水平居中作为选择。我应该切换我的代码以便它使用 MultiViewPager
还是可以利用 RecyclerView
获得类似的结果?
The problem with this solution is that the first and the last item
will never be horizontally centered as selection.
这可能是因为您的 RecycleView 负责在其布局范围内准确显示数据集中的项目数。
在您提供的示例图像中,您可以通过在数据集的第一个和最后一个位置添加 "placeholder" 项来实现该效果。这样,您可以让一个不可见的项目占据第一个插槽,从而抵消您想要居中的项目。
此占位符项目不应响应触摸事件,也不应干扰其他项目上的点击事件处理(特别是位置处理)。
您将不得不修改您的适配器 getItemCount
,也许 getItemType
。
您可以在 getItemOffsets()
中使用 RecyclerView.ItemDecoration
来实现这一点,以适当地偏移第一项和最后一项。
Retrieve any offsets for the given item. Each field of outRect
specifies the number of pixels that the item view should be inset by, similar to padding or margin. The default implementation sets the bounds of outRect to 0 and returns.
If you need to access Adapter for additional data, you can call getChildAdapterPosition(View)
to get the adapter position of the View.
您可能需要使用物品的尺寸和 RecyclerView
。但是这些信息无论如何都可以使用。
只需在 RecyclerView
上添加填充并添加 clipToPadding=false
,它只会影响末端的项目。
对@I.S 答案的改进,100% 的时间都有效,并且非常容易实现,没有任何动画故障。首先,我们必须使用 PagerSnapHelper()
来像滚动一样查看寻呼机。要使项目居中,您需要在 recyclerView 上添加一个大的填充,然后剪辑到填充。然后使用自定义 LinearSmoothScroller
使您的项目顺利居中。要使项目在加载时居中,只需使用平滑滚动到位置 0。下面是代码
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_selection"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="150dp"
android:paddingRight="150dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_selection_alert"
app:layout_constraintBottom_toBottomOf="@+id/guideline_1"
android:clipToPadding="false"
android:background="@drawable/bg_stat"/>
在代码中(在 C# 中)
RecyclerView.LayoutManager lm = new LinearLayoutManager(Context, LinearLayoutManager.Horizontal, false);
recycler_selection = view.FindViewById<RecyclerView>(Resource.Id.recycler_selection);
recycler_selection.SetLayoutManager(new LinearLayoutManager(Context, LinearLayoutManager.Horizontal, false));
// <Set Adapter to the Recycler>
RecyclerView.SmoothScroller smoothScroller = new CenterScroller(recycler_selection.Context);
SnapHelper helper = new PagerSnapHelper();
helper.AttachToRecyclerView(recycler_selection);
smoothScroller.TargetPosition = 0;
lm.StartSmoothScroll(smoothScroller);
public class CenterScroller : LinearSmoothScroller
{
float MILLISECONDS_PER_INCH = 350f;
public CenterScroller(Context context) : base(context)
{
}
public override int CalculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference)
{
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
protected override float CalculateSpeedPerPixel(DisplayMetrics displayMetrics)
{
return MILLISECONDS_PER_INCH / displayMetrics.Xdpi;
}
}
我最终在我的项目中实现了这个实现。您可以在构造函数中传递不同的尺寸来设置项目之间的间距。正如我在 class' KDoc 中所写,它会将 (total parent space - child width) / 2
添加到第一个项目的左侧和最后一个项目的右侧,以使第一个项目和最后一个项目居中。
import android.graphics.Rect
import android.view.View
import androidx.annotation.DimenRes
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
/**
* Adds (total parent space - child width) / 2 to the left of first and to the right of last item (in order to center first and last items),
* and [spacing] between items.
*/
internal class OffsetItemDecoration constructor(
@DimenRes private val spacing: Int,
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
val itemPosition: Int = parent.getChildAdapterPosition(view)
if (itemPosition == RecyclerView.NO_POSITION) return
val spacingPixelSize: Int = parent.context.resources.getDimensionPixelSize(spacing)
when (itemPosition) {
0 ->
outRect.set(getOffsetPixelSize(parent, view), 0, spacingPixelSize / 2, 0)
parent.adapter!!.itemCount - 1 ->
outRect.set(spacingPixelSize / 2, 0, getOffsetPixelSize(parent, view), 0)
else ->
outRect.set(spacingPixelSize / 2, 0, spacingPixelSize / 2, 0)
}
}
private fun getOffsetPixelSize(parent: RecyclerView, view: View): Int {
val orientationHelper = OrientationHelper.createHorizontalHelper(parent.layoutManager)
return (orientationHelper.totalSpace - view.layoutParams.width) / 2
}
}
我想使用 RecyclerView
来模拟 MultiViewPager
的行为,特别是我希望将所选项目放在屏幕中央,包括第一个和最后一个元素.
正如您在此图片中看到的,第一项居中,这将是我的预期结果。
我所做的是设置一个 RecyclerView
和一个水平 LinearLayoutManager
和一个 LinearSnapHelper
。这个解决方案的问题是第一个和最后一个项目永远不会水平居中作为选择。我应该切换我的代码以便它使用 MultiViewPager
还是可以利用 RecyclerView
获得类似的结果?
The problem with this solution is that the first and the last item will never be horizontally centered as selection.
这可能是因为您的 RecycleView 负责在其布局范围内准确显示数据集中的项目数。
在您提供的示例图像中,您可以通过在数据集的第一个和最后一个位置添加 "placeholder" 项来实现该效果。这样,您可以让一个不可见的项目占据第一个插槽,从而抵消您想要居中的项目。
此占位符项目不应响应触摸事件,也不应干扰其他项目上的点击事件处理(特别是位置处理)。
您将不得不修改您的适配器 getItemCount
,也许 getItemType
。
您可以在 getItemOffsets()
中使用 RecyclerView.ItemDecoration
来实现这一点,以适当地偏移第一项和最后一项。
Retrieve any offsets for the given item. Each field of
outRect
specifies the number of pixels that the item view should be inset by, similar to padding or margin. The default implementation sets the bounds of outRect to 0 and returns.If you need to access Adapter for additional data, you can call
getChildAdapterPosition(View)
to get the adapter position of the View.
您可能需要使用物品的尺寸和 RecyclerView
。但是这些信息无论如何都可以使用。
只需在 RecyclerView
上添加填充并添加 clipToPadding=false
,它只会影响末端的项目。
对@I.S 答案的改进,100% 的时间都有效,并且非常容易实现,没有任何动画故障。首先,我们必须使用 PagerSnapHelper()
来像滚动一样查看寻呼机。要使项目居中,您需要在 recyclerView 上添加一个大的填充,然后剪辑到填充。然后使用自定义 LinearSmoothScroller
使您的项目顺利居中。要使项目在加载时居中,只需使用平滑滚动到位置 0。下面是代码
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_selection"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="150dp"
android:paddingRight="150dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_selection_alert"
app:layout_constraintBottom_toBottomOf="@+id/guideline_1"
android:clipToPadding="false"
android:background="@drawable/bg_stat"/>
在代码中(在 C# 中)
RecyclerView.LayoutManager lm = new LinearLayoutManager(Context, LinearLayoutManager.Horizontal, false);
recycler_selection = view.FindViewById<RecyclerView>(Resource.Id.recycler_selection);
recycler_selection.SetLayoutManager(new LinearLayoutManager(Context, LinearLayoutManager.Horizontal, false));
// <Set Adapter to the Recycler>
RecyclerView.SmoothScroller smoothScroller = new CenterScroller(recycler_selection.Context);
SnapHelper helper = new PagerSnapHelper();
helper.AttachToRecyclerView(recycler_selection);
smoothScroller.TargetPosition = 0;
lm.StartSmoothScroll(smoothScroller);
public class CenterScroller : LinearSmoothScroller
{
float MILLISECONDS_PER_INCH = 350f;
public CenterScroller(Context context) : base(context)
{
}
public override int CalculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference)
{
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
protected override float CalculateSpeedPerPixel(DisplayMetrics displayMetrics)
{
return MILLISECONDS_PER_INCH / displayMetrics.Xdpi;
}
}
我最终在我的项目中实现了这个实现。您可以在构造函数中传递不同的尺寸来设置项目之间的间距。正如我在 class' KDoc 中所写,它会将 (total parent space - child width) / 2
添加到第一个项目的左侧和最后一个项目的右侧,以使第一个项目和最后一个项目居中。
import android.graphics.Rect
import android.view.View
import androidx.annotation.DimenRes
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
/**
* Adds (total parent space - child width) / 2 to the left of first and to the right of last item (in order to center first and last items),
* and [spacing] between items.
*/
internal class OffsetItemDecoration constructor(
@DimenRes private val spacing: Int,
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
val itemPosition: Int = parent.getChildAdapterPosition(view)
if (itemPosition == RecyclerView.NO_POSITION) return
val spacingPixelSize: Int = parent.context.resources.getDimensionPixelSize(spacing)
when (itemPosition) {
0 ->
outRect.set(getOffsetPixelSize(parent, view), 0, spacingPixelSize / 2, 0)
parent.adapter!!.itemCount - 1 ->
outRect.set(spacingPixelSize / 2, 0, getOffsetPixelSize(parent, view), 0)
else ->
outRect.set(spacingPixelSize / 2, 0, spacingPixelSize / 2, 0)
}
}
private fun getOffsetPixelSize(parent: RecyclerView, view: View): Int {
val orientationHelper = OrientationHelper.createHorizontalHelper(parent.layoutManager)
return (orientationHelper.totalSpace - view.layoutParams.width) / 2
}
}