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.

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.

Creating Custom Transitions

You can create you own transition animation, simulating an upload process in AddItemActivity.

To create a transition, you need to extend Transition class from Android Transition Framework and manipulate your animation.

To do that, you’ll customize the default progress animation of ProgressBar.

Create a new class named ProgressBarTransition inside the util package and add following code:

package com.raywenderlich.isell.util

import android.widget.ProgressBar
import android.view.ViewGroup
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.animation.DecelerateInterpolator
import android.support.transition.Transition
import android.support.transition.TransitionValues

class ProgressBarTransition : Transition() {

  val PROGRESSBAR_PROPERTY = "progress"
  val TRANSITION_PROPERTY = "ProgressBarTransition:progress"

  // 1
  override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?,
                              endValues: TransitionValues?): Animator? {
    if (startValues != null && endValues != null && endValues.view is ProgressBar) {
      val progressBar = endValues.view as ProgressBar
      val startValue = startValues.values[TRANSITION_PROPERTY] as Int
      val endValue = endValues.values[TRANSITION_PROPERTY] as Int
      if (startValue != endValue) {
        // 2
        val objectAnimator = ObjectAnimator
                .ofInt(progressBar, PROGRESSBAR_PROPERTY, startValue, endValue)
        objectAnimator.interpolator = DecelerateInterpolator()

        return objectAnimator
      }
    }

    return null
  }
  
  // 3
  private fun captureValues(transitionValues: TransitionValues) {
    if (transitionValues.view is ProgressBar) {
      // Save current progress in the transitionValues Map
      val progressBar = transitionValues.view as ProgressBar
      transitionValues.values[TRANSITION_PROPERTY] = progressBar.progress
    }
  }

  // 4
  override fun captureStartValues(transitionValues: TransitionValues) {
    captureValues(transitionValues)
  }

  // 5
  override fun captureEndValues(transitionValues: TransitionValues) {
    captureValues(transitionValues)
  }

}

In the first part of the code above, the important arguments are startValues and endValues as TransitionValues. A TransitionValues instance holds information about the View and a Map with properties and current values from the view.

The arguments startValues and endValues contain the value for a property name modified in this transition. If there are startValues and endValues and the view type is ProgressBar, then the code extracts the values and puts them into startValue and endValue variables, respectively.

Here’s what the remaining code does:

  1. Creates an ObjectAnimator to animate changes of the progress property from ProgressBar. Set a DecelerateInterpolator to reflect the hustle of uploading data during the progress animation.
  2. Takes the progress value of the progress bar and save it to TransitionValues instance internal Map along with TRANSITION_PROPERTY.
  3. Stores the starting state using captureStartValues().
  4. Stores the end state of the transition using captureEndValues().

Applying Custom Transitions

Now, you’ll apply your custom transition while simulating the upload process. Open AddItemActivity and import ProgressBarTransition:

import com.raywenderlich.isell.util.ProgressBarTransition

Then, replace everything inside onClickAddItem():

fun onClickAddItem(view: View) {
  if (hasValidInput()) {
    // Scene Transition
    val uploadScene: Scene = Scene
            .getSceneForLayout(sceneContainer, R.layout.scene_upload, this)
    TransitionManager.go(uploadScene, Fade())

    // 1
    val uploadTime: Long = 3000
    val statusInterval: Long = 600

    object : CountDownTimer(uploadTime, statusInterval) {
      val maxProgress = 100
      var uploadProgress = 0

      // 2
      override fun onTick(millisUntilFinished: Long) {
        if (uploadProgress < maxProgress) {
          uploadProgress += 20
          TransitionManager
              .beginDelayedTransition(sceneContainer, ProgressBarTransition())
          progressBar.progress = uploadProgress
        }
      }

      // 3
      override fun onFinish() {
        uploadStatus.text = getString(R.string.text_uploaded)
        addItemData()
        showAddItemConfirmation()
      }
    }.start()
  }
}

Here’s what’s happening in the code above:

  1. Declare uploadTime for three seconds and statusInterval for 600 milliseconds. CountDownTimer will fire its onTick() function five times before going to onFinish().
  2. onTick() checks whether uploadProgress reached 100%. If not, it increases uploadProgress by 20% and calls TransitionManager.beginDelayedTransition() to apply your custom ProgressBarTransition on sceneContainer.
  3. onFinish() fires after three seconds. Change uploadStatus text to Upload complete! and then add a confirmation message by calling addItemData() and showAddItemConfirmation().

Build and run now, and try adding another item:

Congratulations! You've mastered the art of transitions and turned a simple app into an awesome one!