I have a RecyclerView with a GridLayoutManager. Every item has a button and a invisible content. When the user presses the button, the content switches its visibility.
When the content is invisible and the user press the button for the first time, the item expands, and so do the other items on the same row
The problem arises after the user presses again the button: the row doesn't shrink
The same happens for every row
The code I'm using is quite simple... A very basic adapter
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="#00ff00"
android:orientation="vertical"
android:padding="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Press me"
android:visibility="visible" />
<View
android:id="@+id/hidden_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#0000ff"
android:visibility="gone" />
</LinearLayout>
class TestAdapter : RecyclerView.Adapter<ViewHolder>() {
val items = arrayOf(false, false, false, false)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemTestBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemBinding.hiddenView.isVisible = items[position]
holder.itemBinding.button.setOnClickListener {
items[position] = !items[position]
notifyItemChanged(position)
}
return
}
}
open class ViewHolder internal constructor(val itemBinding: ItemTestBinding) :
RecyclerView.ViewHolder(itemBinding.root)
In a very basic layout
<?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"
android:background="#ff0000"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="4"
tools:listitem="@layout/item_test" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#ffff00"
app:layout_constraintTop_toBottomOf="@id/recycler"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recycler.adapter = TestAdapter()
}
}
I'm expecting to have the row shrink to the lower item
Please, note that when using a LinearLayout or a GridLayoutManager with a spanCount = 1, the behaviour is correct:
The problem seems to be related to this one:
Items height in GridLayoutManager after dynamic action but that solution is not working here
This is a minimal project that replicates the problem:
https://mega.nz/file/R7IgmSwD#mwy28aMvMTagfkzSNjArIHANI5cUskG8vN7ZrVp-u94
After the hint of @RyanM and @Biscuit, I found a solution (or a workaround, I'm not sure about how to consider that).
When I update the item I do not just need to notify the recycler about the fact that I updated that single item, but also that all the items on the same row are updated: when an item is expanded, all the other items on the row change their height. So, calling
notifyItemChanged(position)
will make the layout manager recalculate that item height, but all the other items on the row will keep the "expanded" height, so the row won't shrink.
Calling
notifyDataSetChanged()
will force the GridLayoutManager to recalculate all the view sizes and will make the row shrink as expected.
This is the working code for onBindViewHolder
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemBinding.hiddenView.isVisible = items[position]
holder.itemBinding.button.setOnClickListener {
items[position] = !items[position]
// Just a tiny optimization, that's needed only when shrinking
if (!items[position])
notifyDataSetChanged()
}
return
}