Explode Animation - Activity Transition With Shared Elements

This article demonstrates how to navigate from one activity to another activity in Android with the use of shared elements during the transition of two activities.

Introduction

You guys are aware of Android activity transitions but there are some animated ways also to do a transition. Explode Animation consists of three parts - entering the scene, exiting the scene, and a shared transaction between the activities.

Android supports some kind of animations as follows,

  • Explode - move the View in or out from the center of the scene.
  • Slide - move the View in or out from the edge of the scene.
  • Fade - addition and removal of View from the scene.

 

What is Explode Animation?

 
A transition followed by a tapping on the View to let that View fly in the middle of the screen and vice versa with activity transition is called Explode Animation.
  • An enter transition is the first phase of animation in which the View enters the scene. This can be understood from an example of when we touch a view it will fly towards the center.
  • An exit transition determines how a touched view exits the scene and goes to its previous place like the reverse of entering the scene is exiting the scene.
  • Shared elements transaction is between of these two scenes of entering and exit. This shows how a view is shared between transitions of the activities.

However, the default animation between the activity transitions is cross-fading animation. With the use of custom animation, we can change the transition behavior of the application.

According to Google, Android supports some of the following shared elements transition,
  • ChangeBounds
  • ChangeClipBounds
  • ChangeTransform
  • ChangeImageTransform
Now, let's start coding the application. Firstly, take a look at the build.gradle. Here, we have added the Picasso library so that the images can load better. You can add the image loading library of your choice.

build.gradle
  1. buildscript {  
  2.     repositories {  
  3.         jcenter()  
  4.         google()  
  5.     }  
  6.   
  7.     dependencies {  
  8.         classpath 'com.android.tools.build:gradle:3.0.1'  
  9.     }  
  10. }  
  11.   
  12. apply plugin: 'com.android.application'  
  13.   
  14. repositories {  
  15.     jcenter()  
  16.     google()  
  17. }  
  18.   
  19. dependencies {  
  20.     compile "com.android.support:support-v4:27.0.2"  
  21.     compile "com.android.support:support-v13:27.0.2"  
  22.     compile "com.android.support:cardview-v7:27.0.2"  
  23.     compile "com.android.support:appcompat-v7:27.0.2"  
  24.     compile 'com.squareup.picasso:picasso:2.4.0'  
  25.     implementation 'com.android.support:recyclerview-v7:27.0.0'  
  26.     implementation 'com.google.code.gson:gson:2.8.4'  
  27.   
  28. }  
  29.   
  30. // The sample build uses multiple directories to  
  31. // keep boilerplate and common code separate from  
  32. // the main sample code.  
  33. List<String> dirs = [  
  34.     'main',     // main sample code; look here for the interesting stuff.  
  35.     'common',   // components that are reused by multiple samples  
  36.     'template'] // boilerplate code that is generated by the sample template process  
  37.   
  38. android {  
  39.     compileSdkVersion 27  
  40.   
  41.     buildToolsVersion "27.0.2"  
  42.   
  43.     defaultConfig {  
  44.         minSdkVersion 21  
  45.         targetSdkVersion 27  
  46.     }  
  47.   
  48.     compileOptions {  
  49.         sourceCompatibility JavaVersion.VERSION_1_7  
  50.         targetCompatibility JavaVersion.VERSION_1_7  
  51.     }  
  52.   
  53.     sourceSets {  
  54.         main {  
  55.             dirs.each { dir ->  
  56.                 java.srcDirs "src/${dir}/java"  
  57.                 res.srcDirs "src/${dir}/res"  
  58.             }  
  59.         }  
  60.         androidTest.setRoot('tests')  
  61.         androidTest.java.srcDirs = ['tests/src']  
  62.   
  63.     }  
  64.   
  65. }  
For showing the images, we are creating a class for the size and measure of the grid items. See below the code for SquareFrameLayout.java
  1. public class SquareFrameLayout extends FrameLayout {  
  2.   
  3.     public SquareFrameLayout(Context context) {  
  4.         super(context);  
  5.     }  
  6.   
  7.     public SquareFrameLayout(Context context, AttributeSet attrs) {  
  8.         super(context, attrs);  
  9.     }  
  10.   
  11.     public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  12.         super(context, attrs, defStyleAttr);  
  13.     }  
  14.   
  15.     public SquareFrameLayout(Context context, AttributeSet attrs,  
  16.             int defStyleAttr, int defStyleRes) {  
  17.         super(context, attrs, defStyleAttr, defStyleRes);  
  18.     }  
  19.   
  20.     @Override  
  21.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  22.         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  23.         final int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  24.   
  25.         if (widthSize == 0 && heightSize == 0) {  
  26.             // If there are no constraints on size, let FrameLayout measure  
  27.             super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  28.   
  29.             // Now use the smallest of the measured dimensions for both dimensions  
  30.             final int minSize = Math.min(getMeasuredWidth(), getMeasuredHeight());  
  31.             setMeasuredDimension(minSize, minSize);  
  32.             return;  
  33.         }  
  34.   
  35.         final int size;  
  36.         if (widthSize == 0 || heightSize == 0) {  
  37.             // If one of the dimensions has no restriction on size, set both dimensions to be the  
  38.             // on that does  
  39.             size = Math.max(widthSize, heightSize);  
  40.         } else {  
  41.             // Both dimensions have restrictions on size, set both dimensions to be the  
  42.             // smallest of the two  
  43.             size = Math.min(widthSize, heightSize);  
  44.         }  
  45.   
  46.         final int newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);  
  47.         super.onMeasure(newMeasureSpec, newMeasureSpec);  
  48.     }  
  49. }  
grid_item.xml
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.               android:layout_width="match_parent"  
  3.               android:layout_height="wrap_content"  
  4.               android:orientation="vertical">  
  5.   
  6.     <com.example.android.activityscenetransitionbasic.SquareFrameLayout  
  7.           android:layout_width="match_parent"  
  8.           android:layout_height="wrap_content">  
  9.   
  10.         <ImageView  
  11.               android:id="@+id/imageview_item"  
  12.               android:layout_width="match_parent"  
  13.               android:layout_height="match_parent"  
  14.               android:scaleType="centerCrop" />  
  15.   
  16.     </com.example.android.activityscenetransitionbasic.SquareFrameLayout>  
  17.   
  18.     <TextView  
  19.           android:id="@+id/textview_name"  
  20.           android:layout_width="match_parent"  
  21.           android:layout_height="wrap_content"  
  22.           android:background="?android:attr/colorPrimary"  
  23.           android:theme="@android:style/Theme.Material"  
  24.           android:textAppearance="@android:style/TextAppearance.Material.Subhead"  
  25.           android:maxLines="1"  
  26.           android:padding="16dp" />  
  27.   
  28. </LinearLayout>  
Let's see Item.java, a pojo class.
  1. public class Item {  
  2.   
  3.     private static final String LARGE_BASE_URL = "http://storage.googleapis.com/androiddevelopers/sample_data/activity_transition/large/";  
  4.     private static final String THUMB_BASE_URL = "http://storage.googleapis.com/androiddevelopers/sample_data/activity_transition/thumbs/";  
  5.   
  6.     public static Item[] ITEMS = new Item[] {  
  7.             new Item("Flying in the Light""Romain Guy""flying_in_the_light.jpg"),  
  8.             new Item("Caterpillar""Romain Guy""caterpillar.jpg"),  
  9.             new Item("Look Me in the Eye""Romain Guy""look_me_in_the_eye.jpg"),  
  10.             new Item("Flamingo""Romain Guy""flamingo.jpg"),  
  11.             new Item("Rainbow""Romain Guy""rainbow.jpg"),  
  12.             new Item("Over there""Romain Guy""over_there.jpg"),  
  13.             new Item("Jelly Fish 2""Romain Guy""jelly_fish_2.jpg"),  
  14.             new Item("Lone Pine Sunset""Romain Guy""lone_pine_sunset.jpg"),  
  15.     };  
  16.   
  17.     public static Item getItem(int id) {  
  18.         for (Item item : ITEMS) {  
  19.             if (item.getId() == id) {  
  20.                 return item;  
  21.             }  
  22.         }  
  23.         return null;  
  24.     }  
  25.   
  26.     private final String mName;  
  27.     private final String mAuthor;  
  28.     private final String mFileName;  
  29.   
  30.     Item (String name, String author, String fileName) {  
  31.         mName = name;  
  32.         mAuthor = author;  
  33.         mFileName = fileName;  
  34.     }  
  35.   
  36.     public int getId() {  
  37.         return mName.hashCode() + mFileName.hashCode();  
  38.     }  
  39.   
  40.     public String getAuthor() {  
  41.         return mAuthor;  
  42.     }  
  43.   
  44.     public String getName() {  
  45.         return mName;  
  46.     }  
  47.   
  48.     public String getPhotoUrl() {  
  49.         return LARGE_BASE_URL + mFileName;  
  50.     }  
  51.   
  52.     public String getThumbnailUrl() {  
  53.         return THUMB_BASE_URL + mFileName;  
  54.     }  
  55.   
  56. }  
Let's code the MainActivity.java and its details - DetailActivity.java.
 
First, let us look into MainActivity.java.
  1. public class MainActivity extends Activity implements UserAdapter.OnItemClickListener {  
  2.   
  3.     RecyclerView mRecyclerView;  
  4.     private UserAdapter mAdapter;  
  5.     private ArrayList<Item> list;  
  6.   
  7.     @Override  
  8.     public void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         list = new ArrayList<>();  
  12.   
  13.         mRecyclerView = findViewById(R.id.recycler_view);  
  14.         populateList();  
  15.         mAdapter = new UserAdapter(list,this,this);  
  16.         GridLayoutManager gridLayoutManager = new GridLayoutManager(this3);  
  17.         mRecyclerView.setLayoutManager(gridLayoutManager);  
  18.         mRecyclerView.setItemAnimator(new DefaultItemAnimator());  
  19.         mRecyclerView.setAdapter(mAdapter);  
  20.   
  21.     }   
  22.     private void populateList() {  
  23.   
  24.         Item item = new Item("Flying in the Light""Romain Guy""flying_in_the_light.jpg");  
  25.         list.add(item);  
  26.   
  27.         item = new Item("Caterpillar""Romain Guy""caterpillar.jpg");  
  28.         list.add(item);  
  29.   
  30.         item = new Item("Look Me in the Eye""Romain Guy""look_me_in_the_eye.jpg");  
  31.         list.add(item);  
  32.   
  33.         item = new Item("Flamingo""Romain Guy""flamingo.jpg");  
  34.         list.add(item);  
  35.   
  36.         item = new Item("Rainbow""Romain Guy""rainbow.jpg");  
  37.         list.add(item);  
  38.   
  39.         item = new Item("Over there""Romain Guy""over_there.jpg");  
  40.         list.add(item);  
  41.   
  42.         item = new Item("Jelly Fish 2""Romain Guy""jelly_fish_2.jpg");  
  43.         list.add(item);  
  44.   
  45.         item = new Item("Lone Pine Sunset""Romain Guy""lone_pine_sunset.jpg");  
  46.         list.add(item);  
  47.   
  48.     }  
  49.   
  50.     @Override  
  51.     public void onItemClick(Item item, UserAdapter.ViewHolder viewHolder) {  
  52.         Intent intent = new Intent(this, DetailActivity.class);  
  53.         intent.putExtra(DetailActivity.EXTRA_OBJ, new Gson().toJson(item));  
  54.   
  55.         ActivityOptionsCompat activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(  
  56.                 thisnew Pair<View, String>(viewHolder.imgView.findViewById(R.id.imageview_item),  
  57.                         DetailActivity.VIEW_NAME_HEADER_IMAGE),  
  58.                 new Pair<View, String>(viewHolder.nameTextView.findViewById(R.id.textview_name),  
  59.                         DetailActivity.VIEW_NAME_HEADER_TITLE));  
  60.   
  61.         ActivityCompat.startActivity(this, intent, activityOptions.toBundle());  
  62.     }  
activity_main.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/recycler_view"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" />  
Now, after making activity transaction,  we need another activity. So, let us see DetailActivity.java.
  1. public class DetailActivity extends Activity {  
  2.   
  3.     public static final String EXTRA_OBJ = "name";  
  4.   
  5.   
  6.     public static final String VIEW_NAME_HEADER_IMAGE = "detail:header:image";  
  7.     public static final String VIEW_NAME_HEADER_TITLE = "detail:header:title";  
  8.   
  9.     private ImageView mHeaderImageView;  
  10.     private TextView mHeaderTitle;  
  11.   
  12.     private Item mItem;  
  13.   
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.details);  
  18.   
  19.         if (getIntent() != null) {  
  20.   
  21.             mItem = new Gson().fromJson(getIntent().getStringExtra(EXTRA_OBJ), Item.class);  
  22.         }  
  23.   
  24.   
  25.         mHeaderImageView = (ImageView) findViewById(R.id.imageview_header);  
  26.         mHeaderTitle = (TextView) findViewById(R.id.textview_title);  
  27.         ViewCompat.setTransitionName(mHeaderImageView, VIEW_NAME_HEADER_IMAGE);  
  28.         ViewCompat.setTransitionName(mHeaderTitle, VIEW_NAME_HEADER_TITLE);  
  29.   
  30.         loadItem();  
  31.     }  
  32.   
  33.     private void loadItem() {  
  34.   
  35.         mHeaderTitle.setText(getString(R.string.image_header, mItem.getName(), mItem.getAuthor()));  
  36.   
  37.         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && addTransitionListener()) {  
  38.             // If we're running on Lollipop and we have added a listener to the shared element  
  39.             // transition, load the thumbnail. The listener will load the full-size image when  
  40.             // the transition is complete.  
  41.             loadThumbnail();  
  42.         } else {  
  43.             // If all other cases we should just load the full-size image now  
  44.             loadFullSizeImage();  
  45.         }  
  46.     }  
  47.   
  48.     /** 
  49.      * Load the item's thumbnail image into our {@link ImageView}. 
  50.      */  
  51.     private void loadThumbnail() {  
  52.         Picasso.with(mHeaderImageView.getContext())  
  53.                 .load(mItem.getThumbnailUrl())  
  54.                 .noFade()  
  55.                 .into(mHeaderImageView);  
  56.     }  
  57.   
  58.     /** 
  59.      * Load the item's full-size image into our {@link ImageView}. 
  60.      */  
  61.     private void loadFullSizeImage() {  
  62.         Picasso.with(mHeaderImageView.getContext())  
  63.                 .load(mItem.getPhotoUrl())  
  64.                 .noFade()  
  65.                 .noPlaceholder()  
  66.                 .into(mHeaderImageView);  
  67.     }  
  68.   
  69.     /** 
  70.      * Try and add a {@link Transition.TransitionListener} to the entering shared element 
  71.      * {@link Transition}. We do this so that we can load the full-size image after the transition 
  72.      * has completed. 
  73.      * 
  74.      * @return true if we were successful in adding a listener to the enter transition 
  75.      */  
  76.     private boolean addTransitionListener() {  
  77.         final Transition transition = getWindow().getSharedElementEnterTransition();  
  78.   
  79.         if (transition != null) {  
  80.             // There is an entering shared element transition so add a listener to it  
  81.             transition.addListener(new Transition.TransitionListener() {  
  82.                 @Override  
  83.                 public void onTransitionEnd(Transition transition) {  
  84.                     // As the transition has ended, we can now load the full-size image  
  85.                     loadFullSizeImage();  
  86.   
  87.                     // Make sure we remove ourselves as a listener  
  88.                     transition.removeListener(this);  
  89.                 }  
  90.   
  91.                 @Override  
  92.                 public void onTransitionStart(Transition transition) {  
  93.                     // No-op  
  94.                 }  
  95.   
  96.                 @Override  
  97.                 public void onTransitionCancel(Transition transition) {  
  98.                     // Make sure we remove ourselves as a listener  
  99.                     transition.removeListener(this);  
  100.                 }  
  101.   
  102.                 @Override  
  103.                 public void onTransitionPause(Transition transition) {  
  104.                     // No-op  
  105.                 }  
  106.   
  107.                 @Override  
  108.                 public void onTransitionResume(Transition transition) {  
  109.                     // No-op  
  110.                 }  
  111.             });  
  112.             return true;  
  113.         }  
  114.   
  115.         // If we reach here then we have not added a listener  
  116.         return false;  
  117.     }  
  118. }  
Now, we could also see adapter UserAdapter.java.
  1. public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {  
  2.   
  3.     private ArrayList<Item> mlist;  
  4.     Context ctx;  
  5.     OnItemClickListener listener;  
  6.   
  7.     public UserAdapter(ArrayList<Item> list, Context context, OnItemClickListener listen) {  
  8.   
  9.         mlist = list;  
  10.         ctx = context;  
  11.         listener = listen;  
  12.   
  13.     }  
  14.   
  15.     @NonNull  
  16.     @Override  
  17.     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {  
  18.         View itemView = LayoutInflater.from(parent.getContext())  
  19.                 .inflate(R.layout.grid_item, parent, false);  
  20.   
  21.         return new ViewHolder(itemView);  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onBindViewHolder(@NonNull final ViewHolder viewHolder, final int i) {  
  26.   
  27.   
  28.         Picasso.with(ctx).load(mlist.get(i).getThumbnailUrl()).into(viewHolder.imgView);  
  29.         viewHolder.nameTextView.setText(mlist.get(i).getName());  
  30.         viewHolder.frontView.setOnClickListener(new View.OnClickListener() {  
  31.             @Override  
  32.             public void onClick(View v) {  
  33.   
  34.                 listener.onItemClick(mlist.get(i), viewHolder);  
  35.   
  36.             }  
  37.         });  
  38.   
  39.   
  40.     }  
  41.   
  42.     @Override  
  43.     public int getItemCount() {  
  44.         return mlist.size();  
  45.     }  
  46.   
  47.     public class ViewHolder extends RecyclerView.ViewHolder {  
  48.   
  49.         TextView nameTextView;  
  50.         ImageView imgView;  
  51.         LinearLayout frontView;  
  52.   
  53.         public ViewHolder(View itemView) {  
  54.             super(itemView);  
  55.   
  56.             nameTextView = itemView.findViewById(R.id.textview_name);  
  57.             imgView = itemView.findViewById(R.id.imageview_item);  
  58.             frontView = itemView.findViewById(R.id.front_view);  
  59.   
  60.   
  61.         }  
  62.   
  63.   
  64.     }  
  65.   
  66.     public interface OnItemClickListener {  
  67.   
  68.         void onItemClick(Item item, ViewHolder viewHolder);  
  69.   
  70.     }  
  71.   
  72. }  

Output


First, you will see the grid and when you click on any element or image, you will see the animated activity transition.

Explode Animation - Activity Transition With Shared Elements

Now, when you tap on any item, it will give you the result and transit to other activity.

Explode Animation - Activity Transition With Shared Elements
 

Conclusion


In this article, we have seen how a shared element performs and behaves in the transaction from one activity to other activity. This kind of animation in which a View is shared among two activities is called "Explode", as already discussed in the article. So, this is a simple animation we can achieve by just sharing Views across two activities.