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,

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

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.