How to use Android shared elements with fragments

Roman Shtykalo
Nov 4 · 3 min read

Transitions in Material Design apps provide visual continuity. As the user navigates the app, views in the app change state. Motion and transformation reinforce the idea that interfaces are tangible, connecting common elements from one view to the next.

This post aims to provide guidelines and implementation for a specific continuous transition between Android Fragments. I will demonstrate how to implement a transition from an image in a RecyclerView into an image in Fragment and back :)

The final product. Thanks Unspalsh for providing this beautiful photos.

Transition Names

The framework needs a way to associate Views on the first screen to their counterparts on the second screen. Lollipop also introduced a new “transition name” property on Views that is the key to making this association.

There are two ways to add a transition name to your Views:

  • In code, you can use View.transitionName.
pictureView.transitionName = "transitionName"
  • In your layout XML, you can use the android:transitionName attribute. But forget about it, if you need to use transitions between fragments from mutable views, such as RecyclerView.

So now you should generate unique transition view for each view. You can do it in this way in your onBind in your ViewHolder:

override fun onBindViewHolder(myPagedViewHolder: MyPagedViewHolder, position: Int){                   
itemView.picture.transitionName = "transision name" + position
}

Set up the FragmentTransaction

Setting up your FragmentTransactions should look very familiar, but there are some essential changes you should add. Adding setReorderingAllowed(true) allow optimizing operations within and across transactions. Also, you need to send transition name to target fragment somehow, you can use bundle for this :

private fun itemClick(view: View, position: Int) {       
val bundle = Bundle()
bundle.putString("transition_name", "transition name" + i)

val imageFragment = ImageFragment()
imageFragment.arguments = bundle
activity?.supportFragmentManager?.beginTransaction()
?.setReorderingAllowed(true)
?.replace(R.id.container, imageFragment)
?.addToBackStack(null)
?.addSharedElement(view, view.transitionName)
?.commit()
}

In target fragment you should specify the transition name of view:

if (arguments != null) { 
bigImage.transitionName = arguments.getString("transition
name").toString()
}

Specifying the transition animations in fragments

Finally, we need to specify how we want to animate the transition between Fragments.

For the shared element:

Note that you need to call these methods on the second Fragment. If you set these on the originating Fragment, nothing will happen.

You can animate the transition for all non-shared Views as well. For these Views, use setEnterTransition(), setExitTransition(), setReturnTransition(), and setReenterTransition() on the appropriate Fragment.

Postpone transaction

To make your transition correct, you also need to postpone transaction in your fragment to give them enough time to draw transaction. Add postponeEnterTransition() to fragments’ onViewCreated method:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
}

If transition is postponed, you need to resume it, right? Right. Add startPostponedEnterTransition() to Glides’ listener like this:

Glide.with(this).load(uri)
.apply(
RequestOptions().dontTransform()
)
.centerCrop()
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
startPostponedEnterTransition()
return false
}

override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
startPostponedEnterTransition()
return false
}
}).into(bigImage)

You also can add startPostponedEnterTransition() to start transition just right after view is drawn in fragments’ onViewCreated method like this:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewInit()
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
}

Completed example

Our final code for performing this transition is fairly simple, you can check it out here.

There you have it! An easy to implement transition between two Fragments with RecyclerView, thanks for reading!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade