I am trying to make a RecyclerView
of a list of Tasks with done and undone states and I want to create a beautiful transition between those states.
So I created an animated-selector
to make the transition between the states, but the transitions is not working when I use it inside the RecyclerView
, when I test it on a Button/ImageView outside the RecyclerView
it works as it should.
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/done"
android:drawable="@drawable/ic_done"
android:state_selected="true" />
<item
android:id="@+id/undone"
android:drawable="@drawable/ic_undone" />
<transition
android:drawable="@drawable/avd_done_to_undone"
android:fromId="@id/done"
android:toId="@id/undone" />
<transition
android:drawable="@drawable/avd_undone_to_undone"
android:fromId="@id/undone"
android:toId="@id/done" />
</animated-selector>
I toggle the attribute isDone
of the Task
in the adapter item onClickListener
and seted the animated-selector
as the android:src
of the AppCompatImageView
of my recyclerView adapter item:
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.toggleTaskState(task)}" >
...
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imageview_status_image"
android:layout_width="35dp"
android:layout_height="35dp"
android:src="@drawable/aslv_status"
.../>
...
fun toggleTaskState(task: Task) = viewModelScope.launch {
task.isDone = !task.isDone
taskRepository.updateTask(task)
}
On TaskAdapter
on init
I call notifyDataSetChanged()
when the liveData allTasks change, and on TaskViewHolder
binding I updated the AppCompatImageView
selected state.
private var allTasks = viewModel.allTasks
init {
viewModel.allTasks.observe(lifecycle, {
notifyDataSetChanged()
})
}
...
class TaskViewHolder(private var binding: ItemTaskBinding, private val viewModel: TaskViewModel)
: RecyclerView.ViewHolder(binding.root) {
fun bind(task: Task) {
binding.task = task
binding.viewModel = viewModel
binding.imageviewStatusImage.isSelected = task.isDone
binding.executePendingBindings()
}
}
I think the problem is the notifyDataSetChanged()
called when I change the state of the Task object, because I imagine the RecyclerView
updates the layout abruptly and doesn't show the transition state animation, but if I don't call the notifyDataSetChanged()
the RecyclerView
won't change the list items state.
Responding in case someone goes through the same situation as me.
In the notifyDataSetChanged()
method documentation:
RecyclerView will attempt to synthesize visible structural change events for adapters that report that they have {@link #hasStableIds() stable IDs} when this method is used. This can help for the purposes of animation and visual object persistence but individual item views will still need to be rebound and relaid out.
So I setted hasStableIds()
to my Adapter
and it worked!
val taskAdapter = TaskAdapter(this, taskViewModel)
taskAdapter.setHasStableIds(true)