Adding the shared element transition

Enable window content transition

styles.xml
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    
        <item name="android:windowContentTransitions">true</item>
         ...
        
    </style>

</resources>

Create and assign a common transition name

The views to be shared need to have an extra attribute added to them called transitionName. This attribute takes a string which must be common in both of them. We are going to declare that String in the strings.xml.

strings.xml
<string name="transition_avatar_main_to_detail" tools:ignore="ExtraTranslation">transition_avatar_main_to_detail</string>

And then we're going to add it to our avatar view.

li_main.xml AND activity_detail.xxml
<de.hdodenhof.circleimageview.CircleImageView

    android:id="@+id/iv_avatar"
    ...
    android:transitionName="@string/transition_avatar_main_to_detail"
  />

Start Activity

Last but not least we need to start the target activity by specifying a bundle of those shared elements and views from the source.

This means that to be able to do this we need to return also the View to the MainActivity.kt instead of just the position. And we've still left a bug from the previous step. So let's go and change our ContactsAdapter.kt.

First of all we are going to change our listener and add a View parameter

class ContactsAdapter(val context: Context, val items:List<Contact>, val listener: (Contact, View) -> Unit) : RecyclerView.Adapter .... >

Then we will add a bind method in our ViewHolderclass and we're also going to bind the transitionName.

class ViewHolder (itemView: View?) : RecyclerView.ViewHolder(itemView) {
    // Holds the TextView that will add each animal to
    ...

    fun bind (item: Contact, listener: (Contact, View) -> Unit) = with(itemView) {
        avatar?.transitionName = context.getString(R.string.transition_avatar_main_to_detail)
        avatar?.let { Glide.with(context).load(item.avatarId).into(it) }
        tvName?.text = item.name
        tvPhone?.text = item.phone
        setOnClickListener { listener(item, avatar as View) }
    }
}

And we're going to call the bind() method at the onBindViewHolder()

class ContactsAdapter(val context: Context, val items:List<Contact>, val listener: (Contact, View) -> Unit) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>() {

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        ...

        fun bind (item: Contact, listener: (Contact, View) -> Unit) = with(itemView) {
            ...
        }
    }
    ...
    override fun onBindViewHolder(holder: ContactsAdapter.ViewHolder, position: Int) {
        holder.bind(items[position], listener)
    }
}

Finally we're going to adjust the way we're starting our DetailActivity to our changes and specify the bundle for the shared views.

rv_contacts.apply {
    ...
    adapter = ContactsAdapter(context, contacts) { item, sharedView ->
        val optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@MainActivity, sharedView, getString(R.string.transition_avatar_main_to_detail))
        startActivity(DetailActivity.newIntent(this@MainActivity, item), optionsCompat.toBundle())
    }
}

Last updated