One of the most interesting aspects of the Material Design specifications is the visual continuity between activities. With just a few lines of code, the new Lollipop APIs allow you to meaningfully transition between two activities. This breaks the classic activity boundaries of the previous Android versions and allows the user to understand how elements go from one point to another.
In this tutorial, we are going to learn how to achieve this result with Google’s Material Design guidelines.
1. Create a new project in Android Studio by navigating to File ⇒ New ⇒ New Project and fill required details. By default my activity is MainActivity.java.
2. Open build.gradle and include this libraries show below:
1 |
compile 'com.android.support:recyclerview-v7:24.1.1' |
3. Create activity_main.xml and add the RecylerView to it..
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f5f5f5"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> |
4. Create POJO class naming User.java for RecyelerView rows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package com.activitytransitions_demo; /** * Created by sonu on 06/08/16. */ public class User { static final User[] USER = new User[]{ }; // The fields associated to the User private final String mName, mPhone, mEmail, mCity, mColor; private User(String name, String color, String phone, String email, String city) { mName = name; mColor = color; mPhone = phone; mEmail = email; mCity = city; } // This method allows to get the item associated to a particular id, // uniquely generated by the method getId defined below static User getItem(int id) { for (User item : USER) { if (item.getId() == id) { return item; } } return null; } // since mName and mPhone combined are surely unique, // we don't need to add another id field public int getId() { return mName.hashCode() + mPhone.hashCode(); } static enum Field { NAME, COLOR, PHONE, EMAIL, CITY } String get(Field f) { switch (f) { case COLOR: return mColor; case PHONE: return mPhone; case EMAIL: return mEmail; case CITY: return mCity; case NAME: default: return mName; } } } |
5. For circle shape view create circle_bg.xml under drawable directory.
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="@color/colorAccent" /> <size android:width="32dp" android:height="32dp" /> </shape> |
6. Now create custom_user_row_layout.xml for RecyclerView row items and set above created circle_bg.xml to View.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="82dp" android:background="?android:selectableItemBackground" android:clickable="true" android:focusable="true" android:orientation="vertical" android:padding="@dimen/activity_horizontal_margin"> <!-- For each sharing view, set attribute transitionName FROM --> <View android:id="@+id/user_circle" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/circle_bg" android:transitionName="@string/transition_name_circle" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/user_circle" android:orientation="vertical"> <TextView android:id="@+id/user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Harsh" android:textColor="#000" android:textSize="18sp" android:transitionName="@string/transition_name_name" /> <TextView android:id="@+id/user_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+91 9876436718" android:textColor="#9f9f9f" android:textSize="15sp" android:transitionName="@string/transition_name_phone" /> </LinearLayout> </RelativeLayout> |
For android:transitionName attribute check Step 10.
7. For RecyclerView click listener create RecyclerClickListener.java class and add the following code to it. This class will be use for implementing click listener on RecyclerView items.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package com.activitytransitions_demo; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; /** * Created by sonu on 06/08/16. */ public class RecyclerClickListener implements RecyclerView.OnItemTouchListener { private OnItemClickListener mListener; private GestureDetector mGestureDetector; interface OnItemClickListener { public void onItemClick(View view, int position); } RecyclerClickListener(Context context, OnItemClickListener listener) { mListener = listener; mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return true; } }); } @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { View childView = view.findChildViewUnder(e.getX(), e.getY()); if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { mListener.onItemClick(childView, view.getChildPosition(childView)); return true; } return false; } @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } } |
8. Now create RecyclerView_Adapter.java for displaying views and setting data over views.
Important Lines:
1 2 3 |
// Set the color of the shape GradientDrawable bgShape = (GradientDrawable) viewHolder.mCircle.getBackground(); bgShape.setColor(Color.parseColor(user.get(User.Field.COLOR))); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package com.activitytransitions_demo; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** * Created by sonu on 06/08/16. */ public class RecyclerView_Adapter extends RecyclerView.Adapter<RecyclerView_Adapter.RecyclerViewHolder> { static class RecyclerViewHolder extends RecyclerView.ViewHolder { TextView mName, mPhone; View mCircle; RecyclerViewHolder(View itemView) { super(itemView); mName = (TextView) itemView.findViewById(R.id.user_name); mPhone = (TextView) itemView.findViewById(R.id.user_phone); mCircle = itemView.findViewById(R.id.user_circle); } } @Override public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.custom_user_row_layout, viewGroup, false); return new RecyclerViewHolder(v); } @Override public void onBindViewHolder(RecyclerViewHolder viewHolder, int i) { // get the single element from the main array final User user = User.USER[i]; // Set the values viewHolder.mName.setText(user.get(User.Field.NAME)); viewHolder.mPhone.setText(user.get(User.Field.PHONE)); // Set the color of the shape GradientDrawable bgShape = (GradientDrawable) viewHolder.mCircle.getBackground(); bgShape.setColor(Color.parseColor(user.get(User.Field.COLOR))); } @Override public int getItemCount() { return User.USER.length; } } |
9. Now create new directory naming values-v21 under res directory and under that directory create styles.xml. Here you have to enable content transitions and set the entrance and the exit of the views that are shared between the two activities.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat"> <!-- Set the enter and exit transition between two activities --> <item name="android:windowContentTransitions">true</item> <item name="android:windowEnterTransition">@android:transition/slide_bottom</item> <item name="android:windowExitTransition">@android:transition/slide_bottom</item> <item name="android:windowAllowEnterTransitionOverlap">true</item> <item name="android:windowAllowReturnTransitionOverlap">true</item> <item name="android:windowSharedElementEnterTransition">@android:transition/move</item> <item name="android:windowSharedElementExitTransition">@android:transition/move</item> </style> </resources> |
Please note that your project must be targeted to (and thus be compiled with) at least Android API 21 or 21+.
The animations will be ignored on systems that don’t have Lollipop installed. Unfortunately, because of performance reasons, the AppCompat library does not provide complete backward compatibility for these animations.
10. Now you have to point out the relationship between the two common elements of the views. In this example, the shared views are the field containing the name of the User, the one of the phone number, user name, and the colored circle. For each of them, you have to specify a common transition name. For this reason, start adding in the strings.xml resource file the following items:
1 2 3 4 |
<!-- Specify the common transition name --> <string name="transition_name_name">transition:NAME</string> <string name="transition_name_circle">transition:CIRCLE</string> <string name="transition_name_phone">transition:PHONE</string> |
Then, for each of the three pairs, in the layout files add the android:transitionName
attribute with the corresponding value. For the User Name, the code looks like this:
1 2 3 4 5 6 7 8 9 |
<!— the item we are transitioning FROM —> <TextView android:id="@+id/user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Harsh" android:textColor="#000" android:textSize="18sp" android:transitionName="@string/transition_name_name" /> |
1 2 3 4 5 6 7 8 9 10 11 12 |
<!— the item we are transitioning TO —> <TextView android:id="@+id/details_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/details_circle" android:text="Harsh" android:textColor="#000" android:textSize="25sp" android:transitionName="@string/transition_name_name" /> |
Repeat the same process for the other two views, that we had done in Step 6.
11. For showing details of user form one activity to another create new layout naming details_activity_layout.xml and add the transition attributes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
<?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:layout_width="match_parent" android:layout_height="match_parent" android:background="#f5f5f5" android:orientation="vertical"> <!-- For each sharing view, set attribute transitionName TO --> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:scaleType="centerCrop" android:src="@drawable/placeholder" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="82dp" android:padding="@dimen/activity_vertical_margin"> <View android:id="@+id/details_circle" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/circle_bg" android:transitionName="@string/transition_name_circle" /> <TextView android:id="@+id/details_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/details_circle" android:text="Harsh" android:textColor="#000" android:textSize="25sp" android:transitionName="@string/transition_name_name" /> </RelativeLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:orientation="vertical" android:padding="@dimen/activity_horizontal_margin"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/details_phone_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Phone:" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/details_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/details_phone_label" android:text="+91 85647328134" android:textColor="#9f9f9f" android:textSize="20sp" android:transitionName="@string/transition_name_phone" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/details_email_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Email:" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/details_email" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/details_email_label" android:textColor="#9f9f9f" android:textSize="20sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/details_city_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="City:" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/details_city" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_toRightOf="@+id/details_city_label" android:text="Bangalore" android:textColor="#9f9f9f" android:textSize="20sp" /> </RelativeLayout> </LinearLayout> </LinearLayout> |
12. Now open your MainActivity.java and the following code to it. In this following code the main code that we need to focus is:
You will need to attach a specific ActivityOptions
bundle to the intent. The method you need is makeSceneTransitionAnimation
, which takes as parameters the context of the application and as many shared elements as we need. In the onItemClick
method of the RecyclerView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation( // the context of the activity MainActivity.this, // For each shared element, add to this method a new Pair item, // which contains the reference of the view we are transitioning *from*, // and the value of the transitionName attribute new Pair<View, String>(view.findViewById(R.id.user_circle), getString(R.string.transition_name_circle)), new Pair<View, String>(view.findViewById(R.id.user_name), getString(R.string.transition_name_name)), new Pair<View, String>(view.findViewById(R.id.user_phone), getString(R.string.transition_name_phone)) ); ActivityCompat.startActivity(MainActivity.this, intent, options.toBundle()); |
For each shared element to be animated, you will have to add to the makeSceneTransitionAnimation
method a new Pair
item. Each Pair
has two values, the first is a reference to the view you are transitioning from, the second is the value of the transitionName
attribute.
Be careful when importing the Pair
class. You will need to include the android.support.v4.util
package,not the android.util
package. Also, remember to use ActivityCompat.startActivity
method instead of the startActivity
method, because otherwise you will not be able to run your application on environments with API below 16.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package com.activitytransitions_demo; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.util.Pair; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView rv = (RecyclerView) findViewById(R.id.recycler_view); // layout reference LinearLayoutManager llm = new LinearLayoutManager(this); rv.setLayoutManager(llm); rv.setHasFixedSize(true); // to improve performance rv.setAdapter(new RecyclerView_Adapter()); // the adapter is assigner to the Recycler View rv.addOnItemTouchListener( // and the click is handled new RecyclerClickListener(this, new RecyclerClickListener.OnItemClickListener() { @Override public void onItemClick(View view, int position) { Intent intent = new Intent(MainActivity.this, DetailsActivity.class); intent.putExtra(DetailsActivity.ID, User.USER[position].getId()); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation( // the context of the activity MainActivity.this, // For each shared element, add to this method a new Pair item, // which contains the reference of the view we are transitioning *from*, // and the value of the transitionName attribute new Pair<View, String>(view.findViewById(R.id.user_circle), getString(R.string.transition_name_circle)), new Pair<View, String>(view.findViewById(R.id.user_name), getString(R.string.transition_name_name)), new Pair<View, String>(view.findViewById(R.id.user_phone), getString(R.string.transition_name_phone)) ); ActivityCompat.startActivity(MainActivity.this, intent, options.toBundle()); } })); } } |
13. Finally create DetailsActivity.java and get the receive data from MainActivity and set over views.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package com.activitytransitions_demo; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import android.view.View; import android.widget.TextView; /** * Created by sonu on 06/08/16. */ public class DetailsActivity extends AppCompatActivity { public final static String ID = "ID"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.details_activity_layout); User mUser = User.getItem(getIntent().getIntExtra(ID, 0));// Get User from Intent getSupportActionBar().setDisplayHomeAsUpEnabled(true);//Setting back navigation button //Find all views View mCircle = findViewById(R.id.details_circle); TextView mName = (TextView) findViewById(R.id.details_name); TextView mPhone = (TextView) findViewById(R.id.details_phone); TextView mEmail = (TextView) findViewById(R.id.details_email); TextView mCity = (TextView) findViewById(R.id.details_city); //set user data on text view mName.setText(mUser.get(User.Field.NAME)); mPhone.setText(mUser.get(User.Field.PHONE)); mEmail.setText(mUser.get(User.Field.EMAIL)); mCity.setText(mUser.get(User.Field.CITY)); //Set color to Circle View GradientDrawable bgShape = (GradientDrawable) mCircle.getBackground(); bgShape.setColor(Color.parseColor(mUser.get(User.Field.COLOR))); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: super.onBackPressed();//Call back press method on back Navigation button click break; } return super.onOptionsItemSelected(item); } } |
On back navigation click call super.onBackPressed() else the back transition will not appear.
14. That’s all you have to done.
Thanks. 🙂
Subscribe to us and get the latest news.
2 Comments
nigel
Tuesday, March 21st, 2017dependencies {
compile fileTree(dir: ‘libs’, include: [‘*.jar’])
androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.2’, {
exclude group: ‘com.android.support’, module: ‘support-annotations’
})
compile ‘com.android.support:appcompat-v7:25.1.1’ //–<—add this here? (compile 'com.android.support:recyclerview- v7:24.1.1')——do I remove the other one ?
testCompile 'junit:junit:4.12'
}
Dr. Droid
Tuesday, March 21st, 2017Hi Nigel,
You need to use both the dependencies:
compile ‘com.android.support:appcompat-v7:25.1.1’
compile ‘com.android.support:recyclerview- v7:24.1.1’
Thanks