代码之家  ›  专栏  ›  技术社区  ›  Martin

使用LinearSnapHelper在RecyclerView中捕捉第一个和最后一个项目时出现问题

  •  2
  • Martin  · 技术社区  · 6 年前

    我有 recyclerview which can be scrolled in horizontal orientation.每个视图将捕捉到显示的中心。为了实现这一点,我使用了linearsnaphelper。这个工具工作得很好。

    问题出在RecyclerView中的 last和 first。当您滚动到开始位置时,这两个按钮都不在中间。如果在滚动发生之前检查recyclerview状态,可以看到第一个项目在屏幕中央的正确位置。我通过添加我的自定义项offsetdecoration实现了这一点。我已经根据显示和视图本身的宽度为最后一个和第一个视图添加了额外的填充,所以它将把它放在中间。

    问题是,如果滚动RecyclerView并将其向后滚动到开始或结束位置,则这些视图不会捕捉到中间位置。看起来LinearSnapHelper无法检测到它们已被捕捉。

    recyclerviewinit:

    TimelineSnapHelper=LinearSnapHelper()。
    TimelineViewManager=linearlayoutManager(上下文,linearlayoutManager.horizontal,false)
    TimelineViewAdapter=TimelineListAdapter(TimelineViewManager,TimelineList,this)
    timelineoffsetdecoration=itemoffsetdecoration(上下文!!,32, 45)
    
    TimelineRecyclerView=view.findViewByID<RecyclerView>(r.id.Timeline_Recycler_View)。应用{
    layoutManager=TimelineViewManager
    适配器=TimelineView适配器
    AdditemDecoration(TimelineOffsetDecoration)
    setHasFixedSize(真)
    }
    
    TimelineSnapHelper.AttachToRecycleView(TimelineRecycleView)
    
    TimelineRecyclerView.AddOnScrollListener(对象:RecyclerView.OnScrollListener()。{
    覆盖fun onscrollstatechanged(recyclerview:recyclerview?,新闻状态:int){
    if(newstate==abslistview.onscrolllistener.scroll_state_idle){
    val centerview=TimelineSnapHelper.FindSnapView(TimelineRecyclerView.LayoutManager)
    val anim=animationutils.loadanimation(context,r.anim.scale_timeline_start)
    中心视图!!.startanimation(动画)
    anim.filafter=真
    lastSnappedTime=中心视图
    }否则,如果(上次快照时间!{NULL){
    lastSnappedTime=空
    }
    
    如果(newstate==abslistview.onscrolllistener.scroll_state_touch_scroll&lastsnappedtime!){NULL){
    val anim=animationutils.loadAnimation(上下文,r.anim.scale_timeline_end)
    上次快照时间!!.startanimation(动画)
    anim.filafter=真
    }
    }
    })
    < /代码> 
    
    

    自定义项装饰:

    class itemoffsetdecoration(private val context:context,private val edgepadding:int,private var viewwidth:int):recyclerview.itemdecoration()。{
    
    
    覆盖fun getitemoffsets(outrect:rect,view:view,parent:recyclerview,state:recyclerview.state?){
    super.getitemoffsets(outrect、view、parent、state)
    
    val itemcount=状态!!
    
    
    val itemsposition=parent.getchildadapterposition(视图)
    //没有位置,别管它
    if(itemsposition==recyclerview.no_position){
    返回
    }
    
    val displaymetrics=context.resources.displaymetrics
    val displaywidth:int=displaymetrics.widthPixels
    val viewpixelwidth:int=(viewwidth*(displaymetrics.densitydpi/160f)).toint()。
    val startendpadding:int=(显示宽度-viewPixelWidth)/2
    
    /第一项
    如果(项位置==0){
    outrect.set(开始填充、边缘添加、边缘添加、边缘添加)
    }
    /最后一项
    否则,如果(itemCount>0&itemPosition==itemCount-1){
    outrect.set(边缘添加、边缘添加、开始添加、边缘添加)
    }
    //其他项目
    否则{
    外向设置(边缘添加、边缘添加、边缘添加、边缘添加)
    }
    }
    }
    < /代码> 
    
    

    在应用程序中预览该问题(I have to fix some animation bugs yet:d):

    =

    问题是最后的第一RecyclerView中的项。这两个是not snapped to the center当你滚动到开始。如果在滚动发生之前检查recyclerview状态,可以看到第一个项目在屏幕中央的正确位置。我通过添加我的自定义项offsetdecoration实现了这一点。我已经根据显示和视图本身的宽度为最后一个和第一个视图添加了额外的填充,所以它将把它放在中间。

    问题是,如果滚动RecyclerView并将其向后滚动到开始或结束位置,则这些视图不会捕捉到中间位置。LinearSnapHelper似乎无法检测到它们已被快照。

    回收视图初始化:

    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
                }
            }
        })
    

    自定义项装饰:

    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)
        }
     }
    }
    

    在应用程序中预览问题(I have to fix some animation bugs yet :D):

    1 回复  |  直到 6 年前
        1
  •  5
  •   Cheticamp    6 年前

    LinearSnapHelper 在相关视图的测量中包含项目装饰,因此第一个和最后一个视图从未真正居中,因为测量的视图大小延伸到 RecyclerView .

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

    <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 将不得不为您的应用程序和布局进行调整,您将不需要项目装饰转移视图是它的唯一目的。