1

Problem

I want to have a RecyclerView with width=match_parent and height=wrap_content. I need to display items horizontally so I use a proper LinearLayoutManager.

I face a problem with a RecyclerView behaviour. The items of my list have different content (some values are present, other are absent in different items), thus the height of my items can vary. And when I scroll the list and further items have bigger content than first items, the height of a RecyclerView doesn't adjust to display its children properly, but it cuts their content. It could work only if the first item is the biggest one.

Research

I saw some old question from 2016 when people had some similar issues. Most of the solutions recommend to update the recyclerView version to 'com.android.support:recyclerview-v7:23.2.1'. But nowadays we use Androidx. The current stable version for recyclerView is 1.0.0 I tried 1.1.0-rc1 but got the same result.

Also, I tried to setAutoMeasureEnabled(true) for my LinearLayoutManager, but it doesn't work as well.

My Workaround

So finally I tried to update the ViewHolder view not directly by assigning values to TextViews, but through view.post(Runnable {}). And it works, I don't know exactly why.

Not working example GIF

Working example GIF - with a help of view.post(Runnable {})

The expected behaviour is shown on a second GIF: as soon as we scroll to the item with a bigger content, the view size gets updated and recyclerView height gets updated as well.

My Question

Do you think the above workaround is okay? Maybe you have some more intelligent ideas / approaches? Thank you for help and attention.

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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="match_parent"
        android:layout_height="wrap_content"
        android:background="#C5C510" />

</FrameLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="200dp"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/itemTextView1"
        android:layout_width="match_parent"
        android:textSize="40dp"
        tools:text="dsgsdfgsdf"
        android:gravity="center"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/itemTextView2"
        android:layout_width="match_parent"
        android:textSize="40dp"
        tools:text="dsgsdfgsdf"
        android:gravity="center"
        android:visibility="gone"
        android:layout_height="wrap_content"/>


    <TextView
        android:id="@+id/itemTextView3"
        android:layout_width="match_parent"
        android:textSize="40dp"
        tools:text="dsgsdfgsdf"
        android:gravity="center"
        android:visibility="gone"
        android:layout_height="wrap_content"/>

</LinearLayout>

MainActivity

class MainActivity : AppCompatActivity() {

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

        recyclerView.layoutManager =
            LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        recyclerView.adapter = Adapter(100) // 100 is the number of items to be displayed, adapter generates them
    }
}

Adapter snippet (Default, Doesn't work as expected):

    override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
        holder.itemView.itemTextView1.text = "String  \n" + pos.toString()
        holder.itemView.itemTextView2.text = "String  \n" + pos.toString()
        holder.itemView.itemTextView3.text = "String  \n" + pos.toString()

        holder.itemView.itemTextView2.visibility = if (pos > 10) View.VISIBLE else View.GONE
        holder.itemView.itemTextView3.visibility = if (pos > 30) View.VISIBLE else View.GONE
    }

Adapter snippet (Makes the RecyclerView work as expected):

    override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
        holder.itemView.post {
            holder.itemView.itemTextView1.text = "String  \n" + pos.toString()
            holder.itemView.itemTextView2.text = "String  \n" + pos.toString()
            holder.itemView.itemTextView3.text = "String  \n" + pos.toString()

            holder.itemView.itemTextView2.visibility = if (pos > 10) View.VISIBLE else View.GONE
            holder.itemView.itemTextView3.visibility = if (pos > 30) View.VISIBLE else View.GONE

        }
    }
  • Generally you should avoid changing size/layout of your `ViewHolder`s inside `onBindViewHolder`. I suggest creating 3 different item types and moving your visibility calls into `onCreateViewHolder`. – Pawel Oct 29 '19 at 20:14
  • @Pawel Interesting idea, I will try it. One concern I have about this approach is that the described view is just an example, in my real task I have more complex view with different labels which can be VISIBLE or GONE so they impact the view height. So this approach assumes I will need a big set of different view types (for every possible combination of my fields). Anyway, I didn't hear before about avoiding changing size/layout inside onBindViewHolder. Could you, please, provide some links to get more info? Thank you :) – Oleksandr Karpovich Oct 29 '19 at 20:43

0 Answers0