Android Transition Framework: Getting Started

In this tutorial, you’ll learn how to animate your UI with Android Transition Framework. By Zahidur Rahman Faisal.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Tracking Animation States with Lifecycle Callbacks

Now that you’ve implemented the shared element transition, you may also want to set up notification when it’s started and finished. Android Transition Framework provides a flexible API to track different animation states.

There’s a floating Share button over the product image in DetailsFragment. The button overlaps with the image transition animation. What if you could make the button visible right after the transition animation ends?

To do that, add a listener to the shared transition animation, replacing the following code at line 65 inside DetailsFragment.kt:

if (!shareFab.isShown) {
  shareFab.show()
}

With:

activity?.window?.sharedElementEnterTransition?.addListener(
  object : Transition.TransitionListener {

  override fun onTransitionStart(transition: Transition) {}

  override fun onTransitionEnd(transition: Transition) {
    shareFab?.let {
      if (!shareFab.isShown) {
        shareFab.show()
      }
    }
  }

  override fun onTransitionCancel(transition: Transition) {}

  override fun onTransitionPause(transition: Transition) {}

  override fun onTransitionResume(transition: Transition) {}
})

The code above adds a TransitionListener interface to the active window’s sharedElementEnterTransition property, which holds the transition states.

TransitionListener allows you to track when the transition starts, ends, cancels, pauses or resumes. You make shareFab button visible on the onTransitionEnd() callback.

Build and run again.

Now the share button appears right after the shared transition animation.

Animating Transitions Between Fragments

Animating and sharing elements can get tricky during fragment-to-fragment transitions, but don’t worry. You can learn the trick!

Create a new Fragment called GalleryFragment, showing a complete image of the items displayed in DetailsFragment.

Creating the Image Viewer

Create fragment_gallery.xml inside the layout package. Add the code below inside the file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/details_scene_container"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".ui.details.GalleryFragment">

  <ImageView
    android:id="@+id/itemImageView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:scaleType="fitCenter"
    android:transitionName="@string/transition_image" />

  <TextView
    android:id="@+id/titleTextView"
    style="@style/TextAppearance.AppCompat.Headline"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/default_padding"
    android:layout_marginTop="@dimen/default_padding"
    android:gravity="center"
    android:text="@string/hint_title" />
</LinearLayout>

That’s a LinearLayout to display the image and title of the selected item.

Note that you added android:transitionName="@string/transition_image" to define a transition name for the shared element.

Now, create GalleryFragment.kt class inside the details package. Then, replace the class code with:

package com.raywenderlich.isell.ui.details

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

import com.raywenderlich.isell.R
import com.raywenderlich.isell.data.Item
import kotlinx.android.synthetic.main.fragment_gallery.*

class GalleryFragment : Fragment() {

  private var item: Item? = null

  // 1
  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                            savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_gallery, container, false)
  }

  // 2
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    item = activity?.intent?.getParcelableExtra(getString(R.string.bundle_extra_item))
    item?.let {
      populateDetails(item)
    }
  }

  // 3
  private fun populateDetails(item: Item?) {
    itemImageView.setImageResource(item?.imageId!!)
    titleTextView.text = item.title
  }
  
}

Here’s what the code above does:

  1. Generate the view for this Fragment from fragment_gallery.xml.
  2. Retrieve item from DetailsActivity. Calls populateDetails() to populate data when the view is ready.
  3. Assign item’s image and title to itemImageView and titleTextView.

Sharing Transition Elements Between Fragments

It’s time for a transition.

Open DetailsFragment again. Inside onViewCreated(), after transition animation callbacks, add a click listener to itemImageView. This will open the GalleryFragment when a user taps on it.

Add this code:

itemImageView.setOnClickListener {
  //1
  val changeImageAnimation = ChangeImageTransform()
  
  //2
  val galleryFragment = GalleryFragment()
  galleryFragment.sharedElementEnterTransition = changeImageAnimation
  galleryFragment.sharedElementReturnTransition = changeImageAnimation

  //3
  fragmentManager!!
      .beginTransaction()
      //4
      .addSharedElement(itemImageView, itemImageView.transitionName)
      .replace((view.parent as ViewGroup).id,
          galleryFragment,
          GalleryFragment::class.java.simpleName)
      .addToBackStack(null)
      .commit()
}

The code above performs four steps:

  1. Define a transition animation. changeImageAnimation is an instance of a library class from Android Transition Framework, which animates the shared image’s bounds from starting scene to ending scene.
  2. Create a GalleryFragment instance and assigns changeImageAnimation as enter and return shared transition animation.
  3. Navigate to GalleryFragment.
  4. Attach itemImageView as a shared element, followed by the transition name defined in the layout xml.
Note: Play around with other predefined transitions from that library such as Fade and Slide.

Build and run again.

Tap the image from detail screen to see the image animate, and bask in its glory:

Scene Transitions

Android Transition Framework provides an awesome feature called Scene Transition to switch views or layouts on the go.

You can create a Scene from a layout resource file or from a view or view group programmatically. Start by creating a scene from layouts.

Creating Scenes

Create scene_item_image.xml inside layout package and add the code below:

<?xml version="1.0" encoding="utf-8"?>
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/imageButton"
  android:layout_width="match_parent"
  android:layout_height="@android:dimen/thumbnail_height"
  android:scaleType="fitCenter" />

Then create another file named scene_upload.xml in the same package with the following layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/scene_upload"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="@dimen/default_padding">

  <ProgressBar
    android:id="@+id/progressBar"
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true" />

  <TextView
    android:id="@+id/uploadStatus"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@id/progressBar"
    android:layout_margin="@dimen/default_margin"
    android:gravity="center"
    android:text="@string/text_uploading"
    android:textAppearance="@style/TextAppearance.AppCompat.Headline"
    android:textColor="@color/colorAccent" />

</RelativeLayout>

Now, replace ImageButton inside activity_add_item.xml with FrameLayout :

<FrameLayout
  android:id="@+id/sceneContainer"
  android:layout_width="match_parent"
  android:layout_height="@android:dimen/thumbnail_height">
  <include layout="@layout/scene_item_image" />
</FrameLayout>

In the same em>activity_add_item.xml, find the TextView with id categoryTitle, and replace the property android:layout_below="@+id/imageButton" with android:layout_below="@+id/sceneContainer".

Similarly, find the Spinner with id categorySpinner and replace the property android:layout_below="@+id/imageButton" with android:layout_below="@+id/sceneContainer"

In the above steps, you created two separate layouts (scene_item_image.xml and scene_upload.xml) and are showing one of them (scene_item_image.xml) in activity_add_item.xml. In the next section, you’ll add a listener when clicking the Add Item button inside AddItemActivity, which will trigger a scene transition from scene_item_image.xml to scene_upload.xml.

Switching Scenes

Now, open AddItemActivity and add the lines below to the import section on top:

import android.support.transition.*
import kotlinx.android.synthetic.main.scene_item_image.*
import kotlinx.android.synthetic.main.scene_upload.*

The starting scene for your transition is automatically determined from the current layout. You need to provide a target scene for your TransitionManager to switch scenes.

To satisfy that, add the following code inside onClickAddItem() within if (hasValidInput()) { ... }:

fun onClickAddItem(view: View) {
  if (hasValidInput()) {
    // 1 - Apply Scene transition for uploading
    val uploadScene: Scene = Scene.getSceneForLayout(sceneContainer, R.layout.scene_upload, this)
    
    // 2
    TransitionManager.go(uploadScene, Fade())
    
     // ...
  }
}

The code above does two things:

  1. Execute Scene.getSceneForLayout() to generate the scene and assign it to uploadScene. In order to generate a Scene, you need three parameters:
    1. A reference to the scene root, which is sceneContainer.
    2. A layout resource id, which is R.layout.scene_upload.
    3. A reference to the current context.
  2. Call TransitionManager.go() providing uploadScene and a Fade animation to override the default transition.
  1. A reference to the scene root, which is sceneContainer.
  2. A layout resource id, which is R.layout.scene_upload.
  3. A reference to the current context.

Build and run to see the changes.

Navigate to add a new item. Click Add Item to see the Scene Transition in action:

Congrats, and nice work! You’ll learn to animate the uploading process in the next part of this tutorial.