Animations in Android
  • Introduction
  • About us
  • Basic animations
  • ConstraintLayout: Core concepts
  • I like to move it, move it!
  • Step 1
  • Step 2
  • Step 2: Solution
  • Step 3
  • Step 3: Solution
  • Step 4
  • Step 4: Solution
  • Step 5
  • Step 5: Solution
  • Step 6
  • Step 7
  • Step 7: Solution
  • Step 8
  • Step 8: Solution
  • MotionLayout
  • Party time!
  • Shared element transition
  • Adding a RecyclerView
  • Activity transitions
  • Adding a second Activity
  • Adding the shared element transition
  • You made it!
  • VectorDrawable
  • VectorDrawable: Solution
  • Using the VectorDrawable
  • RecyclerView item animation & self-contained MotionScene
  • Creating a self-contained MotionScene
  • Creating a self-contained MotionScene : Solution
  • Adding animations in the RecyclerView items: Step 1
  • Adding animations in the RecyclerView items: Step 2
  • RecyclerView animations step 2: Solution
  • Physic based animations
  • Implement a spring animation
  • Spring animation: Solution
  • The finish line!
Powered by GitBook
On this page

Adding a RecyclerView

PreviousShared element transitionNextActivity transitions

Last updated 6 years ago

Before we move into the shared element transitions there are a few changes we need to change to our app as we want to reproduce the most common use of the shared element transitions, a RecyclerView with a details screen.

By the end of this step this will be our final app:

First of all we need to change the CardViewin both of our XML layout files to a RecyclerView.

activity_main_start.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout ....>

    <!-- Circle ImageView --> 
    <!-- Main ImageView --> 
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_contacts"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="16dp"
        android:elevation="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/guideline_horizontal30"
        tools:listitem="@layout/li_main" />
    
    <!-- ImageView Paw 1--> 
    <!-- ImageView Paw 2--> 
    <!-- ImageView Paw 3--> 
    <!-- ImageView Paw 4--> 
    <!-- Second Background View--> 
    <!-- ImageView Leaf 1 -->
    <!-- ImageView Leaf 2 -->
    <!-- Guidelines --> 

</android.support.constraint.motion.MotionLayout>
activity_main_end.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout ....>

    <!-- Circle ImageView --> 
    <!-- ImageView Leaf 1 -->
    <!-- ImageView Leaf 2 -->
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_contacts"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        tools:listitem="@layout/li_main"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/guideline_horizontal20"
        app:layout_constraintVertical_bias="0.0"
        android:elevation="2dp"/>
        
    <!-- Main ImageView --> 
    <!-- ImageView Paw 1--> 
    <!-- ImageView Paw 2--> 
    <!-- ImageView Paw 3--> 
    <!-- ImageView Paw 4--> 
    <!-- Second Background View--> 
    <!-- Guidelines --> 

</android.support.constraint.motion.MotionLayout>

Then we need to create the XML layout for our list items.

li_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout android:layout_height="wrap_content"
    android:layout_width="match_parent"
    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">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv_avatar"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:elevation="3dp"
        app:layout_constraintBottom_toBottomOf="@+id/cardView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/cardView"
        tools:src="@tools:sample/avatars" />

    <android.support.v7.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:background="@color/cardview_light_background"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/tv_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="29dp"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="8dp"
                android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="@tools:sample/full_names" />

            <TextView
                android:id="@+id/tv_phone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:layout_marginEnd="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="@+id/tv_name"
                app:layout_constraintTop_toBottomOf="@+id/tv_name"
                tools:text="@tools:sample/us_phones" />

        </android.support.constraint.ConstraintLayout>
    </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>

We then create a data class for our mock data:

Contact.kt
data class Contact (val avatarId: Int?, val name: String, val phone:String)

On our MainActivity.kt we add the following methods

MainActivity.kt
private val contacts = arrayListOf<Contact>()
private val NUMBER_OF_ITEMS = 8

....

// Create dummy data to populate to our RecyclerView
private fun getData(){
    var count =0
    while (count < NUMBER_OF_ITEMS) {
        contacts.add(Contact(getDrawableForPosition(count+1), "XYZ", "645654654"))
        count ++
    }
}

/**
   We want to have an automatic way to get the images for the avatars.
   This will return the resource id for a specific name 
   The position refers to the item position on the list
*/
private fun getDrawableForPosition(position:Int): Int? {
    val name = "avatar_$position"
    return resources.getIdentifier(name, "drawable",packageName)
}

We also need to create a RecyclerView Adapter.

To add Glide in your project add the following to your Gradle: implementation 'com.github.bumptech.glide:glide:4.8.0'

ContactsAdapter.kt
class ContactsAdapter(val context: Context, val items:List<Contact>) : 
                        RecyclerView.Adapter<ContactsAdapter.ViewHolder>() {

    class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
        // Holds the TextView that will add each animal to
        val avatar = view.iv_avatar!!
        val tvName = view.tv_name!!
        val tvPhone = view.tv_phone!!
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactsAdapter.ViewHolder {
        return ViewHolder(LayoutInflater.from(context).inflate(R.layout.li_main, parent, false))
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: ContactsAdapter.ViewHolder, position: Int) {
        Glide.with(context).load(items[position].avatarId).into(holder.avatar)
        holder.tvName.text = items[position].name
        holder.tvPhone.text = items[position].phone
    }
}

Finally we populate our RecyclerView with our dummy data

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main_start)

    getData()

    rv_contacts.apply {
        setHasFixedSize(true)
        layoutManager = LinearLayoutManager(context)
        adapter = ContactsAdapter(context, contacts)
    }
}

And of course we run our app to test that everything is working

I'm going to use for setting the images to our ImageViews. Even though for this project we could use only thesetImageResource property, I find it a good practice to have a handy way of handling loading and errors (and unless you have the exact ID there's always a slight chance to get a NPE). And while you should also implement those, we don't have time to cover everything in this workshop.

Glide library