Adding the shared element transition
Enable window content transition
Note that the shared element transitions require Android 5.0 (API level 21) and above. At lower API versions they will be ignored.
<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
.
<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.
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/iv_avatar"
...
android:transitionName="@string/transition_avatar_main_to_detail"
/>
If we try to run it now we will see that the app will crash. The reason for that is because we're using the View in a RecyclerView. Thus we need to bind the transitionName from the RecyclerView. We will solve this in a minute.
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 ViewHolder
class 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