我正在创建一个食品菜单布局,该菜单有类别和项目。 在顶部是一个类别名称的列表,如饮料、寿司等,这是一个水平滚动的回收器视图,在底部是类别项目,例如在饮料下有可口可乐芬达等,这是一个垂直滚动的回收器视图。我试图将这两个回收器视图同步起来,当你垂直滚动时,它就会水平滚动,反之亦然。

我创建了这个类来实现这个功能。

import android.graphics.Typeface
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
class TwoRecyclerViews(
    private val recyclerViewHorizontal: RecyclerView,
    private val recyclerViewVertical: RecyclerView,
    private var indices: List<Int>,
    private var isSmoothScroll: Boolean = false,
private var attached = false
private var horizontalRecyclerState = RecyclerView.SCROLL_STATE_IDLE
private var verticalRecyclerState = RecyclerView.SCROLL_STATE_IDLE
private val smoothScrollerVertical: RecyclerView.SmoothScroller =
    object : LinearSmoothScroller(recyclerViewVertical.context) {
        override fun getVerticalSnapPreference(): Int {
            return SNAP_TO_START
fun attach() {
    recyclerViewHorizontal.adapter
        ?: throw RuntimeException("Cannot attach with no Adapter provided to RecyclerView")
    recyclerViewVertical.adapter
        ?: throw RuntimeException("Cannot attach with no Adapter provided to RecyclerView")
    updateFirstPosition()
    notifyIndicesChanged()
    attached = true
private fun detach() {
    recyclerViewVertical.clearOnScrollListeners()
    recyclerViewHorizontal.clearOnScrollListeners()
fun reAttach() {
    detach()
    attach()
private fun updateFirstPosition() {
    Handler(Looper.getMainLooper()).postDelayed({
        val view = recyclerViewHorizontal.findViewHolderForLayoutPosition(0)?.itemView
        val textView = view?.findViewById<TextView>(R.id.horizontalCategoryName)
        val imageView = view?.findViewById<ImageView>(R.id.categorySelectionIndicator)
        imageView?.visibility = View.VISIBLE
        textView?.setTypeface(null, Typeface.BOLD)
        textView?.setTextColor(recyclerViewVertical.context.getColor(R.color.primary_1))
    }, 100)
fun isAttached() = attached
private fun notifyIndicesChanged() {
    recyclerViewHorizontal.addOnScrollListener(onHorizontalScrollListener)
    recyclerViewVertical.addOnScrollListener(onVerticalScrollListener)
private val onHorizontalScrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        horizontalRecyclerState = newState
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val linearLayoutManager: LinearLayoutManager =
            recyclerView.layoutManager as LinearLayoutManager?
                ?: throw RuntimeException("No LinearLayoutManager attached to the RecyclerView.")
        var itemPosition =
            linearLayoutManager.findFirstCompletelyVisibleItemPosition()
        if (itemPosition == -1) {
            itemPosition =
                linearLayoutManager.findFirstVisibleItemPosition()
        if (horizontalRecyclerState == RecyclerView.SCROLL_STATE_DRAGGING ||
            horizontalRecyclerState == RecyclerView.SCROLL_STATE_SETTLING
            for (position in indices.indices) {
                val view = recyclerView.findViewHolderForLayoutPosition(indices[position])?.itemView
                val textView = view?.findViewById<TextView>(R.id.horizontalCategoryName)
                val imageView = view?.findViewById<ImageView>(R.id.categorySelectionIndicator)
                if (itemPosition == indices[position]) {
                    if (isSmoothScroll) {
                        smoothScrollerVertical.targetPosition = indices[position]
                        recyclerViewVertical.layoutManager?.startSmoothScroll(smoothScrollerVertical)
                    } else {
                        (recyclerViewVertical.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(
                            indices[position], 16.dpToPx()
                    imageView?.visibility = View.VISIBLE
                    textView?.setTypeface(null, Typeface.BOLD)
                    textView?.setTextColor(recyclerView.context.getColor(R.color.primary_1))
                } else {
                    imageView?.visibility = View.GONE
                    textView?.setTypeface(null, Typeface.NORMAL)
                    textView?.setTextColor(recyclerView.context.getColor(R.color.secondary_5))
private val onVerticalScrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        verticalRecyclerState = newState
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val linearLayoutManager: LinearLayoutManager =
            recyclerView.layoutManager as LinearLayoutManager?
                ?: throw RuntimeException("No LinearLayoutManager attached to the RecyclerView.")
        var itemPosition =
            linearLayoutManager.findFirstCompletelyVisibleItemPosition()
        if (itemPosition == -1) {
            itemPosition =
                linearLayoutManager.findFirstVisibleItemPosition()
        if (verticalRecyclerState == RecyclerView.SCROLL_STATE_DRAGGING ||
            verticalRecyclerState == RecyclerView.SCROLL_STATE_SETTLING
            for (position in indices.indices) {
                val view = recyclerViewHorizontal.findViewHolderForAdapterPosition(indices[position])?.itemView
                val textView = view?.findViewById<TextView>(R.id.horizontalCategoryName)
                val imageView = view?.findViewById<ImageView>(R.id.categorySelectionIndicator)
                if (itemPosition == indices[position]) {
                    (recyclerViewHorizontal.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(
                        indices[position], 16.dpToPx()
                    imageView?.visibility = View.VISIBLE
                    textView?.setTypeface(null, Typeface.BOLD)
                    textView?.setTextColor(recyclerViewVertical.context.getColor(R.color.primary_1))
                } else {
                    imageView?.visibility = View.GONE
                    textView?.setTypeface(null, Typeface.NORMAL)
                    textView?.setTextColor(recyclerViewVertical.context.getColor(R.color.secondary_5))

该类在垂直滚动中工作正常,但在水平滚动中存在不稳定性。

5 个评论
你可能会更好地要求点击第一个回收器来选择一个项目,然后在另一个循环器中显示该项目。 这样的话,你会有更少的角落案例。
@GabeSechan 水平回收器有一个类似于标签布局的行为,当屏幕加载时,第一个项目必须被选中,这就是为什么我有updateFirstPosition()函数。
对于默认的选择来说,这很好。 我想说的是,试图在滚动时而不是在选择时做到这一点,会给你带来很多痛苦、时间和错误。 在选择的时候做,就会变得很简单--只要在onClickListener中改变第二个适配器的数据。 这实际上正是标签的工作方式--你点击一个标签,它就会加载其数据。 在滚动时这样做,就会打开大量的角落案例,使它很难测试。
@GabeSechan 我已经用标签布局和recyclerview实现了这种方式,而且效果很好,但滚动时的选择却没有。
Zain
Which RV list does the indices refer to?
android
kotlin
android-recyclerview
Ayodele Kayode
Ayodele Kayode
发布于 2022-11-25
1 个回答
KZoNE
KZoNE
发布于 2022-12-03
0 人赞同

实现你的UI/UX要求的最好方法是使用TabLayout带有垂直回收器的视图。

回收器视图中的列表项和标签布局中的标签都可以被设置为动态的项目/标签数量

当你向上和向下滚动并到达相应的类别时,使用以下代码更新标签布局。你可以从以下方面识别每个类别

TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); // Once for the Activity of Fragment
TabLayout.Tab tab = tabLayout.getTabAt(someIndex); // Some index should be obtain from the dataset 
tab.select();