Adding a RecyclerView

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.

I'm going to use Glide library 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.

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

Last updated