RecyclerView使用水平GridLayoutManager调整其高度以适应最大行

6

我希望使用GridLayoutManagerRecyclerView,实现以下效果:

目标

这里是水平方向和2行的GridLayoutManager。对我来说重要的是将RecyclerView设置为wrap_content

所以我尝试使用以下代码实现我的目标:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val adapter = Adapter()
    private val layoutManager = GridLayoutManager(this, 2, RecyclerView.HORIZONTAL, false)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView.adapter = adapter
        recyclerView.layoutManager = layoutManager

        adapter.submitList(listOf(Unit, Unit, Unit, Unit))
    }
}

Adapter.kt

class Adapter : ListAdapter<Any, ItemHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
        return ItemHolder(itemView)
    }

    override fun onBindViewHolder(holder: ItemHolder, position: Int) {
    }

    override fun getItemViewType(position: Int): Int {
        return when {
            position % 2 == 0 -> R.layout.item_small
            else -> R.layout.item_normal
        }
    }

    class DiffCallback : DiffUtil.ItemCallback<Any>() {
        override fun areItemsTheSame(oldItem: Any, newItem: Any) = false
        override fun areContentsTheSame(oldItem: Any, newItem: Any) = false
    }

    class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_margin="8dp"
    android:background="@color/colorAccent" />

item_small.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="50dp"
    android:layout_margin="8dp"
    android:background="@color/colorAccent" />

很不幸的是,结果是每一行都会拉伸到网格中最大的行:

current

问题是如何消除第一行和第二行之间的间距?请注意,我必须拥有可滚动的水平网格,其高度取决于其内容。并且项目必须具有固定的大小。


看看 FlexboxLayoutManager 是否适合你。 - Cheticamp
我尝试玩弄flexbox演示,但是我无法实现水平可滚动的视图。你能给任何线索吗? - Nominalista
请尝试这个:https://dev59.com/LI3da4cB1Zd3GeqP0384#34225902 - Rahul Khurana
我尝试了,它给出了与普通的GridLayoutManager相同的结果。请使用我提供的示例。 - Nominalista
1
@Nominalista,你搞定了吗? - Santanu Sur
显示剩余3条评论
3个回答

3
假设您知道每个行的高度比例,您可以尝试使用 SpanSizeLookup 来调整每一行的高度。在您的简单情况下,较小的项(50dp)将占用一个行(span size 1),而较大的项(100dp)将占用两行(span size 2),因此整个网络格将包含3行。
当然,对于更复杂的行配置,比例可能会变得更加复杂:例如,我想要高度为32dp / 48dp / 64dp的行,则高度比例为32/144、48/144和64/144,我们可以简化为2/9、3/9和4/9,总共得到9行,每个项目的 span size 为 2、3 和 4。 在极端情况下,这可能会导致大量行数(当分数不能简化时),但是假设您使用某种类型的网格(x8、x10等)并且物品大小合理,它仍然应该是可管理的。
无论如何,在您的情况下,代码如下:
val layoutManager = GridLayoutManager(this, 3, RecyclerView.HORIZONTAL, false)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
    override fun getSpanSize(position: Int) = when (position % 2) {
        0 -> 1 // 50dp item
        else -> 2 // 100dp item
    }
}

如果有更多的行,则when语句会变得更加复杂,但是如果您已经有了适配器,请使用getItemViewType来更轻松地区分when语句中的各个行。

如果项目类型数量很大或经常更改(例如,在不同屏幕上有不同的项目类型),当然也可以在代码中实现上述逻辑,假设您可以访问各个项目类型的高度。只需将高度相加以获得分母,然后找到所有高度和总和的最大公约数以找到“简化因子”。


看起来非常有前途,我会尽快检查你的解决方案。 - Nominalista

-1

我认为GridLayoutManager默认会将子视图限制为完全相同的大小。

我调整了代码,使用了LinearLayoutManager,并将ViewHolder的视图包装成一个RelativeLayout来容纳两种类型的视图。

以下是一些代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <View
        android:id="@+id/top"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_margin="8dp"
        android:layout_alignParentTop="true"
        android:background="@color/colorAccent" />


    <View
        android:id="@+id/bottom"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="8dp"
        android:layout_below="@+id/top"
        android:background="@color/colorAccent" />
</RelativeLayout>

RecyclerAdapter:

        View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                R.layout.item_normal,
                viewGroup,
                false
        );
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;

并设置LinearLayoutManager:

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false);
        recyclerView.setLayoutManager(layoutManager);

screenshot


谢谢您的建议,但我的示例比实际问题简单。我有许多不同配置的行。因此,您的解决方案在这里无法应用。 - Nominalista

-1
尝试使用StaggeredGridLayoutManager来实现不同的高度。将您的GridLayoutManager替换为以下StaggeredGridLayoutManager
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);

enter image description here


实际上它并没有生效,因为它调整的是水平RecyclerView中的项目宽度而不是高度。 - Nominalista
@Nominalista 抱歉,我犯了一个错误,需要将方向设置为“VERTICAL”。我更新了我的答案,请看一下。 - sanoJ
请注意,我在描述中提到它必须是水平的循环视图。在实际问题中有太多的项目无法适应两列。 - Nominalista
行的高度也会改变吗? - sanoJ
一行中的所有项高度相同。但它们在其他行之间有所不同。我认为在上面的示例中正好展示了它们的外观。 - Nominalista

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