Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView with horizontal GridLayoutManager adjusts its height to largest row

I want to use RecyclerView with GridLayoutManager to achieve something like that:

Target

Here is GridLayoutManager with horizontal orientation and 2 rows. What's important for me here is that RecyclerView is set as wrap_content.

So I tried to achieve my goal with such code:

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" />

Unfortunately as the result, each row stretches to the largest row in grid:

current

The question is how can I remove the spacing between first row and second row? Keep in mind that I have to have scrollable horizontal grid that its height depends on its content. And items have to have fixed size.

like image 479
Nominalista Avatar asked Nov 06 '22 15:11

Nominalista


1 Answers

Assuming you know the ratios between heights of individual rows, you can try to use SpanSizeLookup to adjust height of each row. In your simple case, the smaller item (50dp) will take one row (span size 1) while the larger item (100dp) will take two rows (span size 2) so the whole grid overall will contain 3 rows.

Of course, for a more complex row configuration, the ratios might get a little more complicated: Say I wanted rows of heights 32dp/48dp/64dp, then the height ratios are 32/144, 48/144 and 64/144, which we can simplify to 2/9, 3/9, 4/9, getting 9 rows in total, with span sizes 2, 3, and 4 for individual items. In extreme cases, this can result in large number of rows (when the fractions cannot be simplified), but assuming you are using some type of grid (x8, x10, etc.) and the items are reasonably sized, it should still be manageable.

Anyway, in your case, the code would be this:

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

Given more rows, the when statement is going to get more complex, but if you already have the adapter at hand, you can use getItemViewType to differentiate individual rows in the when statement more easily.

If the number of item types is large or changes often (for example different item types on different screens), you can of course also implement the logic above in code, assuming you have access to the heights of the individual item types. Simply sum the heights to obtain the denominator and then find greatest common divisor of all heights and the sum to find the "simplification factor".

like image 189
daemontus Avatar answered Nov 15 '22 06:11

daemontus