How I can handle items animation made with MotionLayout
inside RecyclerView
Because when I start scrolling like here everything becomes very messy.
I Know how RecyclerView
works, but I try a lot to reset or control the MotionLayout
Animate but It didn’t work all the time and is still messy
adapter code
class StoryViewHolder(private val mainView: View) : RecyclerView.ViewHolder(mainView){
private var root: MotionLayout = mainView.findViewById(R.id.motion_root)
private var deleteBtn: RelativeLayout = mainView.findViewById(R.id.delete_btn)
private var editBtn: RelativeLayout = mainView.findViewById(R.id.edit_btn)
private var storyImage: RoundedImageView = mainView.findViewById(R.id.story_image)
private var storyTitleTv: TextView = mainView.findViewById(R.id.story_title_tv)
private var storyAuthorTv: TextView = mainView.findViewById(R.id.story_author_tv)
private var tagsLL: LinearLayout = mainView.findViewById(R.id.tags_ll)
private var favouriteImg: ImageView = mainView.findViewById(R.id.favourite_img)
lateinit var storyListener: StoryViewListener
private lateinit var story: Story
interface StoryViewListener {
fun onDelete(story: Story, position: Int)
}
init {
deleteBtn.setOnClickListener {
storyListener.onDelete(story, adapterPosition)
}
editBtn.setOnClickListener {
}
storyImage.setOnClickListener {
}
}
fun set(story: Story) {
this.story = story
Glide
.with(mainView.context)
.load(story.image)
.centerCrop()
.into(storyImage)
storyTitleTv.text = story.title
storyAuthorTv.text = ""
addTags(story.tags)
}
private fun addTags(tags: String) {
tagsLL.removeAllViews()
tags.split(",").forEach {
val view =
LayoutInflater.from(mainView.context).inflate(R.layout.view_tag, null, false)
view.findViewById<TextView>(R.id.tag).text = it
tagsLL.addView(view)
}
}
fun setStart() {
root.transitionToStart()
}
fun setEnd() {
root.transitionToEnd()
}
}
ViewHolder code
class StoryViewHolder(private val mainView: View) : RecyclerView.ViewHolder(mainView){
private var root: MotionLayout = mainView.findViewById(R.id.motion_root)
private var deleteBtn: RelativeLayout = mainView.findViewById(R.id.delete_btn)
private var editBtn: RelativeLayout = mainView.findViewById(R.id.edit_btn)
private var storyImage: RoundedImageView = mainView.findViewById(R.id.story_image)
private var storyTitleTv: TextView = mainView.findViewById(R.id.story_title_tv)
private var storyAuthorTv: TextView = mainView.findViewById(R.id.story_author_tv)
private var tagsLL: LinearLayout = mainView.findViewById(R.id.tags_ll)
private var favouriteImg: ImageView = mainView.findViewById(R.id.favourite_img)
lateinit var storyListener: StoryViewListener
private lateinit var story: Story
interface StoryViewListener {
fun onDelete(story: Story, position: Int)
}
init {
deleteBtn.setOnClickListener {
storyListener.onDelete(story, adapterPosition)
}
editBtn.setOnClickListener {
}
storyImage.setOnClickListener {
}
}
fun set(story: Story) {
this.story = story
Glide
.with(mainView.context)
.load(story.image)
.centerCrop()
.into(storyImage)
storyTitleTv.text = story.title
storyAuthorTv.text = ""
addTags(story.tags)
}
private fun addTags(tags: String) {
tagsLL.removeAllViews()
tags.split(",").forEach {
val view =
LayoutInflater.from(mainView.context).inflate(R.layout.view_tag, null, false)
view.findViewById<TextView>(R.id.tag).text = it
tagsLL.addView(view)
}
}
fun setStart() {
root.transitionToStart()
}
fun setEnd() {
root.transitionToEnd()
}
}
MotionLayout Animation code
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="300">
<KeyFrameSet>
<KeyAttribute
android:alpha="0"
motion:framePosition="40"
motion:motionTarget="@+id/horizontalScrollView" />
<KeyAttribute
android:alpha="0"
motion:framePosition="100"
motion:motionTarget="@+id/horizontalScrollView" />
<KeyAttribute
android:alpha="0"
motion:framePosition="50"
motion:motionTarget="@+id/edit_btn" />
<KeyAttribute
android:alpha="1"
motion:framePosition="100"
motion:motionTarget="@+id/edit_btn" />
<KeyAttribute
android:alpha="0"
motion:framePosition="50"
motion:motionTarget="@+id/delete_btn" />
<KeyAttribute
android:alpha="1"
motion:framePosition="100"
motion:motionTarget="@+id/delete_btn" />
</KeyFrameSet>
<OnClick motion:targetId="@+id/story_image" />
</Transition>
<ConstraintSet android:id="@+id/start"></ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@+id/story_image"
motion:layout_constraintEnd_toEndOf="@+id/story_image"
motion:layout_constraintStart_toStartOf="@+id/story_image"
motion:layout_constraintTop_toTopOf="@+id/story_image"
motion:layout_constraintVertical_bias="0.04000002" />
<Constraint
android:id="@+id/favourite_img"
android:layout_width="1dp"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="18dp"
android:alpha="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="@+id/story_image" />
<Constraint
android:id="@+id/story_title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toTopOf="@+id/story_author_tv"
motion:layout_constraintEnd_toEndOf="@+id/story_image"
motion:layout_constraintStart_toStartOf="@+id/story_image"
motion:layout_constraintTop_toBottomOf="@+id/story_image" />
<Constraint
android:id="@+id/story_author_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toTopOf="@+id/story_image"
motion:layout_constraintEnd_toEndOf="@+id/story_image"
motion:layout_constraintStart_toStartOf="@+id/story_image"
motion:layout_constraintTop_toBottomOf="@+id/story_title_tv" />
<Constraint
android:id="@+id/story_image"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:riv_corner_radius="10dp" />
<Constraint
android:id="@+id/horizontalScrollView"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_marginBottom="10dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintHorizontal_bias="1.0"
motion:layout_constraintStart_toEndOf="@+id/story_image" />
<Constraint
android:id="@+id/delete_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/edit_btn"
motion:layout_constraintHorizontal_chainStyle="packed"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/story_image" />
<Constraint
android:id="@+id/edit_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintHorizontal_bias="0.5"
motion:layout_constraintStart_toEndOf="@+id/delete_btn"
motion:layout_constraintTop_toBottomOf="@id/story_image" />
</ConstraintSet>
</MotionScene>
2
Answers
I found the problem: I had to control the
MotionLayout
animation completely from theAdapter
insideonBindViewHolder(..)
not from theMotionScene
, So I deleted<OnClick motion:targetId="@+id/story_image" />
from the scene filexmlmotion_scene
,and I changed the adapter code.here's the short code:
You should use RecyclerView.ItemAnimator for
recyclerView
animations. See recycerView.setItemAnimator method. It’s wrong to animateViewHolder
views directly into adapter becauserecyclerView
reuse views. While you scrollrecyclerView
bind old views to new items, butrecyclerview
doesn’t know that old views are in process of animation, that’s why it becomes messy.Here is good video which explains how
recyclerView
andItemAnimator
works.Here is nice tutorial with custom
ItemAnimator
implementation.