水平居中 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
    }
}