代码之家  ›  专栏  ›  技术社区  ›  android developer

如何使RecyclerView捕捉到中心,并能够滚动到所有项目,而中心是“选中”?

  •  1
  • android developer  · 技术社区  · 6 年前

    背景

    我正在尝试实现类似于相机应用程序的模式:

    我可能不需要使用viewpager,因为它似乎在水平列表的上方使用,但最好将其作为一个选项。

    问题

    虽然从技术上讲,我成功地让RecyclerView Center拥有了它的项目,但它并不能让您真正拥有能够位于中心的所有项目(例如,第一个/最后一个项目)。当您试图滚动到第一个或最后一个项目时,它不会允许您,因为您已经到达RecyclerView的边缘:

    不仅如此,而且在开始的时候,它并不是真正的中心,如果我的RecyclerView只有很少的项目,这就成了一个问题,因为我希望它们是中心的,但是有了android:layout\width=“match\u parent”(因为所有的项目都应该是可触摸的)会产生这样的结果:

    安卓系统:layout_width=“wrap_content”给我:

    在这两种情况下,我都无法滚动。当它是“包装内容”时,这是一个问题,因为我将无法在侧面滚动。

    我试过的

    可以对项目进行捕捉,以便在RecyclerView的中心始终有一个项目,如下所示:

    val snaphelper=linearsnaphelper()
    SnapHelper.AttachToRecycleView(分类回收视图)
    

    我们还可以通过使用滚动侦听器和使用SnapHelper.findsnapView(layoutManagaer)来获取中心中的项目(如图所示here)。

    但正如我所写,我不能真的用这种方式选择第一个/最后一个项目,因为我无法滚动到它,所以它将位于中间。

    我试图查看相关类的文档,但找不到这样的内容。

    这是当前代码(示例可用here):。

    mainactivity.kt

    class mainactivity:appcompatactivity()。{
    
    覆盖fun oncreate(savedinstancestate:bundle?){
    super.oncreate(保存的状态)
    setContentView(r.layout.activity_-main)
    recyclerview.adapter=对象:recyclerview.adapter<recyclerview.viewholder>()。{
    覆盖fun onCreateViewHolder(父级:ViewGroup,视图类型:int):RecyclerView.ViewHolder{
    val holder=对象:recyclerview.viewholder(
    布局充气器。来自(this@mainactivity)。充气(
    r.layout.list_项,
    起源,
    假
    )
    {}
    holder.itemview.setonclickListener{
    }
    返回架
    }
    
    override fun getitemcount():int=20
    
    覆盖bindViewholder上的fun(holder:recyclerview.viewholder,position:int){
    holder.itemview.textview.text=“位置:$position”
    }
    }
    val snaphelper=linearsnaphelper()。
    SnapHelper.AttachToRecycleView(回收视图)
    }
    }
    < /代码> 
    
    

    活动\main.xml

    <androidx.constraintlayout.widget.constraintlayout
    xmlns:android=“http://schemas.android.com/apk/res/android”
    xmlns:tools=“http://schemas.android.com/tools”
    xmlns:app=“http://schemas.android.com/apk/res-auto”
    android:layout_width=“匹配父级”
    android:layout_height=“匹配父级”
    工具:context=“mainActivity”>
    
    <androidx.recyclerview.widget.recyclerview android:background=“66000000”
    android:id=“@+id/recyclerview”
    android:layout_width=“匹配父级”
    android:layout_height=“@dimen/list_item_size”
    android:orientation=“水平”
    app:layoutManager=“Androidx.RecyclerView.Widget.LinearLayoutManager”
    app:layout_constraintBottom_tobottomof=“父级”
    app:layout_constrainted_toendoff=“父级”
    app:layout_constraintStart_toStartof=“父级”
    app:layout_constraintop_totopof=“父级”
    工具:listem=“@layout/list_item”/>
    </androidx.constraintlayout.widget.constraintlayout>
    < /代码> 
    
    

    列出项目.xml

    文本视图 android:id=“@+id/textview”xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto”xmlns:tools=“http://schemas.android.com/tools” android:layout_width=“wrap_content”android:layout_height=“@dimen/list_item_size” android:background=“?attr/selectableItembackground“安卓:breakstrategy=”balanced“安卓:clickable=”true“ android:focusable=“true”android:gravity=“center”android:maxlines=“1”android:padding=“8dp” android:shadowcolor=“222”android:shadowdx=“1”android:shadowdy=“1”android:textcolor=“fff” app:autosizetexttype=“uniform”工具:targetapi=“m”工具:text=“@tools:sample/lorem”/>gt; < /代码>

    问题

    我怎样才能让用户在里面自由滚动,这样边缘就由第一个/最后一个项目是否在中间来决定了?我如何始终将这些项目放在中心,包括我刚开始查看RecyclerView时,以及当它们很少时?

    我可能不需要一个viewpager,就像它在水平列表上面使用的那样,但是最好将它作为一个选项。

    问题

    虽然从技术上讲,我成功地让RecyclerView Center拥有了它的项目,但它并不能让您真正拥有能够位于中心的所有项目(例如,第一个/最后一个项目)。当您试图滚动到第一个或最后一个项目时,它不会允许您,因为您已经到达RecyclerView的边缘:

    enter image description here

    不仅如此,而且在开始的时候,它并不是真正的中心,如果我的RecyclerView只有很少的项目,这就成了一个问题,因为我希望它们是中心的,但是有了android:layout\width=“match\u parent”(因为所有的项目都应该是可触摸的)会产生这样的结果:

    enter image description here

    使用android:layout_width=“wrap_content”时,请告诉我:

    enter image description here

    在这两种情况下,我都无法滚动。当它是“包装内容”时,这是一个问题,因为我无法在侧面滚动。

    我试过什么

    可以对项目进行捕捉,以便在RecyclerView的中心始终有一个项目,如下所示:

    val snapHelper = LinearSnapHelper()
    snapHelper.attachToRecyclerView(categoriesRecyclerView)
    

    我们还可以得到中间的项目(如图所示here,通过使用滚动侦听器snapHelper.findSnapView(layoutManagaer).

    但是正如我写的,我不能真的用这种方式选择第一个/最后一个项目,因为我不能滚动到它,所以它将在中间。

    我试着看相关课程的文档,但找不到这样的东西。

    这是当前代码(示例可用here):

    主要活动

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                    val holder = object : RecyclerView.ViewHolder(
                        LayoutInflater.from(this@MainActivity).inflate(
                            R.layout.list_item,
                            parent,
                            false
                        )
                    ) {}
                    holder.itemView.setOnClickListener {
                    }
                    return holder
                }
    
                override fun getItemCount(): Int = 20
    
                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                    holder.itemView.textView.text = "pos:$position"
                }
            }
            val snapHelper = LinearSnapHelper()
            snapHelper.attachToRecyclerView(recyclerView)
        }
    }
    

    活动\main.xml

    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView android:background="#66000000"
                                                   android:id="@+id/recyclerView"
                                                   android:layout_width="match_parent"
                                                   android:layout_height="@dimen/list_item_size"
                                                   android:orientation="horizontal"
                                                   app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                                                   app:layout_constraintBottom_toBottomOf="parent"
                                                   app:layout_constraintEnd_toEndOf="parent"
                                                   app:layout_constraintStart_toStartOf="parent"
                                                   app:layout_constraintTop_toTopOf="parent"
                                                   tools:listitem="@layout/list_item"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    列表项.xml

    <TextView
        android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="wrap_content" android:layout_height="@dimen/list_item_size"
        android:background="?attr/selectableItemBackground" android:breakStrategy="balanced" android:clickable="true"
        android:focusable="true" android:gravity="center" android:maxLines="1" android:padding="8dp"
        android:shadowColor="#222" android:shadowDx="1" android:shadowDy="1" android:textColor="#fff"
        app:autoSizeTextType="uniform" tools:targetApi="m" tools:text="@tools:sample/lorem"/>
    

    问题

    我怎样才能让用户在里面自由滚动,这样边缘就由第一个/最后一个项目是否在中间来决定了?我如何始终将这些项目放在中心,包括我刚开始查看RecyclerView时,以及当它们很少时?

    1 回复  |  直到 6 年前
        1
  •  4
  •   android developer    6 年前

    我试了一下

    5项: https://drive.google.com/open?id=1RPyiY9UndXcrbfBDWLB-UklxjPKMiR8- 2项: https://drive.google.com/open?id=1HkG8NShxQ3illFupK-urSPwsUhag74WS

    首先,应用项目装饰使第一个和最后一个项目居中:

    class CenterDecoration(@Px private val spacing: Int) : RecyclerView.ItemDecoration() {
    
        private var firstViewWidth = -1
        private var lastViewWidth = -1
    
        override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
            super.getItemOffsets(outRect, view, parent, state)
            val adapterPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition
            val lm = parent.layoutManager as LinearLayoutManager
            if (adapterPosition == 0) {
                // Invalidate decorations when this view width has changed
                if (view.width != firstViewWidth) {
                    view.doOnPreDraw { parent.invalidateItemDecorations() }
                }
                firstViewWidth = view.width
                outRect.left = parent.width / 2 - view.width / 2
                // If we have more items, use the spacing provided
                if (lm.itemCount > 1) {
                    outRect.right = spacing / 2
                } else {
                    // Otherwise, make sure this to fill the whole width with the decoration
                    outRect.right = outRect.left
                }
            } else if (adapterPosition == lm.itemCount - 1) {
                // Invalidate decorations when this view width has changed
                if (view.width != lastViewWidth) {
                    view.doOnPreDraw { parent.invalidateItemDecorations() }
                }
                lastViewWidth = view.width
                outRect.right = parent.width / 2 - view.width / 2
                outRect.left = spacing / 2
            } else {
                outRect.left = spacing / 2
                outRect.right = spacing / 2
            }
        }
    
    }
    

    现在,LinearSnapHelper确定视图的中心并包括其装饰。您可以创建一个自定义的,将装饰从计算中排除,仅使视图居中:

    /**
     * A LinearSnapHelper that ignores item decorations to determine a view's center
     */
    class CenterSnapHelper : LinearSnapHelper() {
    
        private var verticalHelper: OrientationHelper? = null
        private var horizontalHelper: OrientationHelper? = null
        private var scrolled = false
        private var recyclerView: RecyclerView? = null
        private val scrollListener = object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == RecyclerView.SCROLL_STATE_IDLE && scrolled) {
                    if (recyclerView.layoutManager != null) {
                        val view = findSnapView(recyclerView.layoutManager)
                        if (view != null) {
                            val out = calculateDistanceToFinalSnap(recyclerView.layoutManager!!, view)
                            if (out != null) {
                                recyclerView.smoothScrollBy(out[0], out[1])
                            }
                        }
                    }
                    scrolled = false
                } else {
                    scrolled = true
                }
            }
        }
    
        fun scrollTo(position: Int, smooth: Boolean) {
            if (recyclerView?.layoutManager != null) {
                val viewHolder = recyclerView!!.findViewHolderForAdapterPosition(position)
                if (viewHolder != null) {
                    val distances = calculateDistanceToFinalSnap(recyclerView!!.layoutManager!!, viewHolder.itemView)
                    if (smooth) {
                        recyclerView!!.smoothScrollBy(distances!![0], distances[1])
                    } else {
                        recyclerView!!.scrollBy(distances!![0], distances[1])
                    }
                } else {
                    if (smooth) {
                        recyclerView!!.smoothScrollToPosition(position)
                    } else {
                        recyclerView!!.scrollToPosition(position)
                    }
                }
            }
        }
    
        override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
            if (layoutManager == null) {
                return null
            }
            if (layoutManager.canScrollVertically()) {
                return findCenterView(layoutManager, getVerticalHelper(layoutManager))
            } else if (layoutManager.canScrollHorizontally()) {
                return findCenterView(layoutManager, getHorizontalHelper(layoutManager))
            }
            return null
        }
    
        override fun attachToRecyclerView(recyclerView: RecyclerView?) {
            this.recyclerView = recyclerView
            recyclerView?.addOnScrollListener(scrollListener)
        }
    
        override fun calculateDistanceToFinalSnap(
            layoutManager: RecyclerView.LayoutManager,
            targetView: View
        ): IntArray? {
            val out = IntArray(2)
            if (layoutManager.canScrollHorizontally()) {
                out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager))
            } else {
                out[0] = 0
            }
            if (layoutManager.canScrollVertically()) {
                out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager))
            } else {
                out[1] = 0
            }
            return out
        }
    
        private fun findCenterView(
            layoutManager: RecyclerView.LayoutManager,
            helper: OrientationHelper
        ): View? {
            val childCount = layoutManager.childCount
            if (childCount == 0) {
                return null
            }
            var closestChild: View? = null
            val center: Int = if (layoutManager.clipToPadding) {
                helper.startAfterPadding + helper.totalSpace / 2
            } else {
                helper.end / 2
            }
            var absClosest = Integer.MAX_VALUE
    
            for (i in 0 until childCount) {
                val child = layoutManager.getChildAt(i)
                val childCenter = if (helper == horizontalHelper) {
                    (child!!.x + child.width / 2).toInt()
                } else {
                    (child!!.y + child.height / 2).toInt()
                }
                val absDistance = Math.abs(childCenter - center)
    
                if (absDistance < absClosest) {
                    absClosest = absDistance
                    closestChild = child
                }
            }
            return closestChild
        }
    
        private fun distanceToCenter(
            layoutManager: RecyclerView.LayoutManager,
            targetView: View,
            helper: OrientationHelper
        ): Int {
            val childCenter = if (helper == horizontalHelper) {
                (targetView.x + targetView.width / 2).toInt()
            } else {
                (targetView.y + targetView.height / 2).toInt()
            }
            val containerCenter = if (layoutManager.clipToPadding) {
                helper.startAfterPadding + helper.totalSpace / 2
            } else {
                helper.end / 2
            }
            return childCenter - containerCenter
        }
    
        private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
            if (verticalHelper == null || verticalHelper!!.layoutManager !== layoutManager) {
                verticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
            }
            return verticalHelper!!
        }
    
        private fun getHorizontalHelper(
            layoutManager: RecyclerView.LayoutManager
        ): OrientationHelper {
            if (horizontalHelper == null || horizontalHelper!!.layoutManager !== layoutManager) {
                horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
            }
            return horizontalHelper!!
        }
    }
    

    用途:

    class MainActivity : AppCompatActivity() {
    
        private val snapHelper = CenterSnapHelper()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            recyclerView.addItemDecoration(CenterDecoration(0))
            snapHelper.attachToRecyclerView(recyclerView)
            recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                    val holder = object : RecyclerView.ViewHolder(
                        LayoutInflater.from(this@MainActivity).inflate(
                            R.layout.list_item,
                            parent,
                            false
                        )
                    ) {}
                    holder.itemView.setOnClickListener {
                        if (holder.adapterPosition != RecyclerView.NO_POSITION) {
                            snapHelper.scrollTo(holder.adapterPosition, true)
                        }
                    }
                    return holder
                }
    
                override fun getItemCount(): Int = 20
    
                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                    holder.itemView.textView.text = "pos:$position"
                }
            }
    
        }
    }
    

    在这里发布XML以防有人要签出它:

    主要活动

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    
        <View
            android:layout_width="4dp"
            android:layout_height="0dp"
            android:background="@color/colorAccent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    ListIsIt.xml

    <TextView
            xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools" android:id="@+id/textView"
            android:layout_width="wrap_content" android:layout_height="@dimen/list_item_size"
            android:background="?attr/selectableItemBackground" android:clickable="true"
            android:focusable="true" android:gravity="center" android:maxLines="1" android:padding="8dp"
            android:shadowColor="#222" android:shadowDx="1" android:shadowDy="1" android:textColor="#fff"
            tools:targetApi="m" tools:text="@tools:sample/lorem"/>
    

    编辑:以下是如何使用此的示例:

    http://s000.tinyupload.com/?file_id=01184747175525079378