使用LinearSnapHelper在recyclerview中捕捉第一个和最后一个项目的问题

11

我有一个recyclerView可以水平滚动。每个视图都会自动对齐到屏幕中心。为了实现这个功能,我使用了LinearSnapHelper。这个工具已经很好地解决了问题。

唯一的问题出现在recyclerview的第一个和最后一个项上。当您向开始位置滚动时,它们没有对齐到中心。如果在滚动之前检查recyclerview状态,您可以看到第一个项位于屏幕中央的正确位置。我通过添加我的自定义ItemOffsetDecoration实现了这一点。基于显示器的宽度和视图本身,我为最后和第一个视图添加了额外的填充,以便将其定位到中间位置。

问题是,如果您滚动recyclerview并将其向前滚动到开始或结束位置,则这些视图不会自动对齐到中间位置。看起来LinearSnapHelper无法检测到它们已经被对齐到中心。

RecyclerViewInit:

timelineSnapHelper = LinearSnapHelper()
    timelineViewManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
    timelineViewAdapter = TimelineListAdapter(timelineViewManager, timelineList, this)
    timelineOffsetDecoration = ItemOffsetDecoration(context!!, 32, 45)

    timelineRecyclerView = view.findViewById<RecyclerView>(R.id.timeline_recycler_view).apply {
        layoutManager = timelineViewManager
        adapter = timelineViewAdapter
        addItemDecoration(timelineOffsetDecoration)
        setHasFixedSize(true)
    }

    timelineSnapHelper.attachToRecyclerView(timelineRecyclerView)

    timelineRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
            if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
                val centerView = timelineSnapHelper.findSnapView(timelineRecyclerView.layoutManager)
                val anim = AnimationUtils.loadAnimation(context, R.anim.scale_timeline_start)
                centerView!!.startAnimation(anim)
                anim.fillAfter = true
                lastSnappedTime = centerView
            } else if (lastSnappedTime != null){
                lastSnappedTime = null
            }

            if(newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL && lastSnappedTime != null){
                val anim = AnimationUtils.loadAnimation(context, R.anim.scale_timeline_end)
                lastSnappedTime!!.startAnimation(anim)
                anim.fillAfter = true
            }
        }
    })

自定义ItemDecoration:

class ItemOffsetDecoration(private val context: Context, private val edgePadding: Int, private var viewWidth: Int): RecyclerView.ItemDecoration() {


override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
    super.getItemOffsets(outRect, view, parent, state)

    val itemCount = state!!.itemCount


    val itemPosition = parent.getChildAdapterPosition(view)
    // no position, leave it alone
    if (itemPosition == RecyclerView.NO_POSITION) {
        return
    }

    val displayMetrics = context.resources.displayMetrics
    val displayWidth: Int = displayMetrics.widthPixels
    val viewPixelWidth: Int = (viewWidth * (displayMetrics.densityDpi / 160f)).toInt()
    val startEndPadding: Int = (displayWidth - viewPixelWidth) / 2

    // first item
    if (itemPosition == 0) {
        outRect.set(startEndPadding, edgePadding, edgePadding, edgePadding)
    }
    // last item
    else if (itemCount > 0 && itemPosition == itemCount - 1) {
        outRect.set(edgePadding, edgePadding, startEndPadding, edgePadding)
    }
    // every other item
    else {
        outRect.set(edgePadding, edgePadding, edgePadding, edgePadding)
    }
 }
}

应用程序问题的简短预览(我还需要修复一些动画错误:D):

输入图像描述


当你说“LinearSnapHelper无法将它们检测为已对齐”时,你是指文本没有精确地放置在中心吗?如果是这样,那么它们只是偏离几个像素,因为它们看起来对我来说是居中的,或者我误解了吗?我想你所说的不是第一个和最后一个项目缺乏动画,而是实际的位置。 - Cheticamp
正确,它们已经偏移了。因为第一个和最后一个项目在视图中添加了额外的填充。而SnapHelper正在将整个视图计算为填充+视图宽度作为视图的宽度。但我真的不知道该如何以不同的方式完成它。如果我更改视图填充,它将导致SnapHelper无法对齐。但是,在ItemOffsetDecoration中,每个单独的项目都设置了32dp的填充上/左/右/下,并且其他项目正常工作。第一个项目的额外padding_start和最后一个项目的padding_end是一个问题。有时它超过200dp(在ItemOffsetDecoration中计算)。 - martin1337
这正是我所想的。请查看我的答案,了解另一种处理方法。 - Cheticamp
可能是RecyclerView SnapHelper无法显示第一个/最后一个项的重复问题。 - aminography
1个回答

13

LinearSnapHelper 包括与关联视图的项装饰在测量中,因此您的第一个和最后一个视图从未真正居中,因为测量的视图大小延伸到 RecyclerView 的左侧或右侧。

您没有发布您的 XML,因此您可能已经执行了以下操作。将起始和结束视图正确居中的典型方法是向 RecyclerView 添加填充并指定 android:clipToPadding="false"。这是从 FAB 等移动结束视图的相同技术。

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:paddingStart="35dp"
    android:paddingEnd="35dp"/>

这里的35dp需要根据您的应用程序和布局进行调整,您不需要使用该项装饰来移动视图,因为它只有移动视图的作用。


我的clipToPadding出了问题。由于recyclerview父布局的原因,它没有正常工作。但现在它已经可以正常工作了。 - martin1337
1
@martin1337,你能告诉我问题出在哪里吗? - egorikem
3
@martin1337,如果还有其他内容,为什么要将此答案标记为正确答案?我的recyclerView的padding/clipToPadding设置如上所示。 - Zach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接