Skip to content

Commit d5faac5

Browse files
committed
Update Catalog to use Container Transform transition for navigation
PiperOrigin-RevId: 307096919
1 parent 57a8ebd commit d5faac5

File tree

5 files changed

+154
-27
lines changed

5 files changed

+154
-27
lines changed

catalog/java/io/material/catalog/feature/DemoActivity.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
import io.material.catalog.R;
2020

21+
import android.os.Build.VERSION;
22+
import android.os.Build.VERSION_CODES;
2123
import android.os.Bundle;
2224
import androidx.annotation.Nullable;
25+
import androidx.annotation.RequiresApi;
2326
import androidx.annotation.StringRes;
2427
import androidx.appcompat.app.ActionBar;
2528
import androidx.appcompat.app.AppCompatActivity;
@@ -28,6 +31,9 @@
2831
import android.view.MenuItem;
2932
import android.view.View;
3033
import android.view.ViewGroup;
34+
import com.google.android.material.color.MaterialColors;
35+
import com.google.android.material.transition.MaterialContainerTransform;
36+
import com.google.android.material.transition.MaterialContainerTransformSharedElementCallback;
3137
import dagger.android.AndroidInjection;
3238
import dagger.android.AndroidInjector;
3339
import dagger.android.DispatchingAndroidInjector;
@@ -40,13 +46,27 @@ public abstract class DemoActivity extends AppCompatActivity implements HasAndro
4046

4147
public static final String EXTRA_DEMO_TITLE = "demo_title";
4248

49+
static final String EXTRA_TRANSITION_NAME = "EXTRA_TRANSITION_NAME";
50+
51+
private static final long DURATION_ENTER = 300;
52+
private static final long DURATION_RETURN = 275;
53+
4354
private Toolbar toolbar;
4455
private ViewGroup demoContainer;
4556

4657
@Inject DispatchingAndroidInjector<Object> androidInjector;
4758

4859
@Override
4960
protected void onCreate(@Nullable Bundle bundle) {
61+
String transitionName = getIntent().getStringExtra(EXTRA_TRANSITION_NAME);
62+
63+
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && transitionName != null) {
64+
findViewById(android.R.id.content).setTransitionName(transitionName);
65+
setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
66+
getWindow().setSharedElementEnterTransition(buildContainerTransform(DURATION_ENTER));
67+
getWindow().setSharedElementReturnTransition(buildContainerTransform(DURATION_RETURN));
68+
}
69+
5070
safeInject();
5171
super.onCreate(bundle);
5272
WindowPreferencesManager windowPreferencesManager = new WindowPreferencesManager(this);
@@ -96,6 +116,17 @@ private void safeInject() {
96116
}
97117
}
98118

119+
@RequiresApi(VERSION_CODES.LOLLIPOP)
120+
private MaterialContainerTransform buildContainerTransform(long duration) {
121+
MaterialContainerTransform transform = new MaterialContainerTransform();
122+
transform.setDuration(duration);
123+
transform.addTarget(android.R.id.content);
124+
transform.setContainerColor(
125+
MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface));
126+
transform.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH);
127+
return transform;
128+
}
129+
99130
private void initDemoActionBar() {
100131
if (shouldShowDefaultDemoActionBar()) {
101132
setSupportActionBar(toolbar);

catalog/java/io/material/catalog/feature/DemoFragment.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import androidx.annotation.StringRes;
2727
import androidx.fragment.app.Fragment;
2828
import androidx.fragment.app.FragmentActivity;
29+
import androidx.core.view.ViewCompat;
2930
import androidx.appcompat.app.ActionBar;
3031
import androidx.appcompat.app.AppCompatActivity;
3132
import androidx.appcompat.widget.Toolbar;
@@ -86,6 +87,12 @@ public View onCreateView(
8687
View view =
8788
layoutInflater.inflate(R.layout.cat_demo_fragment, viewGroup, false /* attachToRoot */);
8889

90+
Bundle arguments = getArguments();
91+
if (arguments != null) {
92+
String transitionName = arguments.getString(FeatureDemoUtils.ARG_TRANSITION_NAME);
93+
ViewCompat.setTransitionName(view, transitionName);
94+
}
95+
8996
toolbar = view.findViewById(R.id.toolbar);
9097
// show a memory widget on Kitkat
9198
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
@@ -180,9 +187,12 @@ private final class GestureListener extends GestureDetector.SimpleOnGestureListe
180187

181188
private final FragmentActivity activity = getActivity();
182189

183-
private final Runnable listener = () -> activity.runOnUiThread(() -> {
184-
memoryWidget.refreshMemStats(Runtime.getRuntime());
185-
});
190+
private final Runnable listener =
191+
() ->
192+
activity.runOnUiThread(
193+
() -> {
194+
memoryWidget.refreshMemStats(Runtime.getRuntime());
195+
});
186196

187197
private boolean memoryWidgetShown;
188198

catalog/java/io/material/catalog/feature/DemoLandingFragment.java

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@
1818

1919
import io.material.catalog.R;
2020

21+
import android.app.ActivityOptions;
2122
import android.content.Context;
2223
import android.content.Intent;
2324
import android.content.res.ColorStateList;
2425
import android.content.res.TypedArray;
26+
import android.os.Build.VERSION;
27+
import android.os.Build.VERSION_CODES;
2528
import android.os.Bundle;
2629
import androidx.annotation.ArrayRes;
2730
import androidx.annotation.ColorInt;
2831
import androidx.annotation.DimenRes;
2932
import androidx.annotation.Nullable;
3033
import androidx.annotation.StringRes;
3134
import androidx.fragment.app.Fragment;
35+
import androidx.fragment.app.FragmentActivity;
3236
import androidx.core.view.MarginLayoutParamsCompat;
3337
import androidx.core.view.MenuItemCompat;
38+
import androidx.core.view.ViewCompat;
3439
import androidx.appcompat.app.AppCompatActivity;
3540
import androidx.appcompat.widget.Toolbar;
3641
import android.view.LayoutInflater;
@@ -42,6 +47,7 @@
4247
import android.view.ViewGroup.MarginLayoutParams;
4348
import android.widget.TextView;
4449
import com.google.android.material.resources.MaterialResources;
50+
import com.google.android.material.transition.MaterialContainerTransformSharedElementCallback;
4551
import dagger.android.support.DaggerFragment;
4652
import java.util.Collections;
4753
import java.util.List;
@@ -68,6 +74,12 @@ public View onCreateView(
6874
layoutInflater.inflate(
6975
R.layout.cat_demo_landing_fragment, viewGroup, false /* attachToRoot */);
7076

77+
Bundle arguments = getArguments();
78+
if (arguments != null) {
79+
String transitionName = arguments.getString(FeatureDemoUtils.ARG_TRANSITION_NAME);
80+
ViewCompat.setTransitionName(view, transitionName);
81+
}
82+
7183
Toolbar toolbar = view.findViewById(R.id.toolbar);
7284

7385
AppCompatActivity activity = (AppCompatActivity) getActivity();
@@ -145,7 +157,9 @@ private void addDemoView(
145157
TextView titleTextView = demoView.findViewById(R.id.cat_demo_landing_row_title);
146158
TextView subtitleTextView = demoView.findViewById(R.id.cat_demo_landing_row_subtitle);
147159

148-
rootView.setOnClickListener(v -> startDemo(demo));
160+
String transitionName = getString(demo.getTitleResId());
161+
ViewCompat.setTransitionName(rootView, transitionName);
162+
rootView.setOnClickListener(v -> startDemo(v, demo, transitionName));
149163

150164
titleTextView.setText(demo.getTitleResId());
151165
subtitleTextView.setText(getDemoClassName(demo));
@@ -169,26 +183,41 @@ private String getDemoClassName(Demo demo) {
169183
}
170184
}
171185

172-
private void startDemo(Demo demo) {
186+
private void startDemo(View sharedElement, Demo demo, String transitionName) {
173187
if (demo.createFragment() != null) {
174-
startDemoFragment(demo.createFragment());
188+
startDemoFragment(sharedElement, demo.createFragment(), transitionName);
175189
} else if (demo.createActivityIntent() != null) {
176-
startDemoActivity(demo.createActivityIntent());
190+
startDemoActivity(sharedElement, demo.createActivityIntent(), transitionName);
177191
} else {
178192
throw new IllegalStateException("Demo must implement createFragment or createActivityIntent");
179193
}
180194
}
181195

182-
private void startDemoFragment(Fragment fragment) {
196+
private void startDemoFragment(View sharedElement, Fragment fragment, String transitionName) {
183197
Bundle args = new Bundle();
184198
args.putString(DemoFragment.ARG_DEMO_TITLE, getString(getTitleResId()));
199+
args.putString(FeatureDemoUtils.ARG_TRANSITION_NAME, transitionName);
185200
fragment.setArguments(args);
186-
FeatureDemoUtils.startFragment(getActivity(), fragment, FRAGMENT_DEMO_CONTENT);
201+
FeatureDemoUtils.startFragment(
202+
getActivity(), fragment, FRAGMENT_DEMO_CONTENT, sharedElement, transitionName);
187203
}
188204

189-
private void startDemoActivity(Intent intent) {
205+
private void startDemoActivity(View sharedElement, Intent intent, String transitionName) {
190206
intent.putExtra(DemoActivity.EXTRA_DEMO_TITLE, getString(getTitleResId()));
191-
startActivity(intent);
207+
intent.putExtra(DemoActivity.EXTRA_TRANSITION_NAME, transitionName);
208+
209+
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
210+
// Set up shared element transition and disable overlay so views don't show above system bars
211+
FragmentActivity activity = getActivity();
212+
activity.setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
213+
activity.getWindow().setSharedElementsUseOverlay(false);
214+
215+
ActivityOptions options =
216+
ActivityOptions.makeSceneTransitionAnimation(activity, sharedElement, transitionName);
217+
startActivity(intent, options.toBundle());
218+
} else {
219+
startActivity(intent);
220+
}
192221
}
193222

194223
private void setMarginStart(View view, @DimenRes int marginResId) {
@@ -231,9 +260,9 @@ private void setMenuItemChecked(MenuItem menuItem, boolean isChecked) {
231260
/**
232261
* Whether or not the feature shown by this fragment should be flagged as restricted.
233262
*
234-
* Examples of restricted feature could be features which depends on an API level that is
235-
* greater than MDCs min sdk version. If overriding this method, you should also override
236-
* {@link #getRestrictedMessageId()} and provide information about why the feature is restricted.
263+
* <p>Examples of restricted feature could be features which depends on an API level that is
264+
* greater than MDCs min sdk version. If overriding this method, you should also override {@link
265+
* #getRestrictedMessageId()} and provide information about why the feature is restricted.
237266
*/
238267
public boolean isRestricted() {
239268
return false;
@@ -242,10 +271,10 @@ public boolean isRestricted() {
242271
/**
243272
* The message to display if a feature {@link #isRestricted()}.
244273
*
245-
* This message should provide insight into why the feature is restricted for the device it
246-
* is running on. This message will be displayed in the description area of the demo fragment
247-
* instead of the the provided {@link #getDescriptionResId()}. Additionally, all demos, both the
248-
* main demo and any additional demos will not be shown.
274+
* <p>This message should provide insight into why the feature is restricted for the device it is
275+
* running on. This message will be displayed in the description area of the demo fragment instead
276+
* of the the provided {@link #getDescriptionResId()}. Additionally, all demos, both the main demo
277+
* and any additional demos will not be shown.
249278
*/
250279
@StringRes
251280
public int getRestrictedMessageId() {

catalog/java/io/material/catalog/feature/FeatureDemoUtils.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,76 @@
2020

2121
import android.content.Context;
2222
import android.content.SharedPreferences;
23+
import android.os.Build.VERSION;
24+
import android.os.Build.VERSION_CODES;
25+
import android.os.Bundle;
2326
import android.preference.PreferenceManager;
27+
import androidx.annotation.Nullable;
2428
import androidx.fragment.app.Fragment;
2529
import androidx.fragment.app.FragmentActivity;
30+
import androidx.fragment.app.FragmentTransaction;
31+
import android.view.View;
32+
import com.google.android.material.color.MaterialColors;
33+
import com.google.android.material.transition.Hold;
34+
import com.google.android.material.transition.MaterialContainerTransform;
2635

2736
/** Utils for feature demos. */
2837
public abstract class FeatureDemoUtils {
2938

39+
static final String ARG_TRANSITION_NAME = "ARG_TRANSITION_NAME";
40+
3041
private static final int MAIN_ACTIVITY_FRAGMENT_CONTAINER_ID = R.id.container;
3142
private static final String DEFAULT_CATALOG_DEMO = "default_catalog_demo";
3243

3344
public static void startFragment(FragmentActivity activity, Fragment fragment, String tag) {
34-
activity
35-
.getSupportFragmentManager()
36-
.beginTransaction()
37-
.setCustomAnimations(
38-
R.anim.abc_grow_fade_in_from_bottom,
39-
R.anim.abc_fade_out,
40-
R.anim.abc_fade_in,
41-
R.anim.abc_shrink_fade_out_from_bottom)
45+
startFragmentInternal(activity, fragment, tag, null, null);
46+
}
47+
48+
public static void startFragment(
49+
FragmentActivity activity,
50+
Fragment fragment,
51+
String tag,
52+
View sharedElement,
53+
String sharedElementName) {
54+
startFragmentInternal(activity, fragment, tag, sharedElement, sharedElementName);
55+
}
56+
57+
public static void startFragmentInternal(
58+
FragmentActivity activity,
59+
Fragment fragment,
60+
String tag,
61+
@Nullable View sharedElement,
62+
@Nullable String sharedElementName) {
63+
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
64+
65+
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
66+
&& sharedElement != null
67+
&& sharedElementName != null) {
68+
Fragment currentFragment = getCurrentFragment(activity);
69+
currentFragment.setExitTransition(new Hold());
70+
71+
MaterialContainerTransform transform = new MaterialContainerTransform();
72+
transform.setContainerColor(MaterialColors.getColor(sharedElement, R.attr.colorSurface));
73+
transform.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH);
74+
fragment.setSharedElementEnterTransition(transform);
75+
transaction.addSharedElement(sharedElement, sharedElementName);
76+
77+
if (fragment.getArguments() == null) {
78+
Bundle args = new Bundle();
79+
args.putString(ARG_TRANSITION_NAME, sharedElementName);
80+
fragment.setArguments(args);
81+
} else {
82+
fragment.getArguments().putString(ARG_TRANSITION_NAME, sharedElementName);
83+
}
84+
} else {
85+
transaction.setCustomAnimations(
86+
R.anim.abc_grow_fade_in_from_bottom,
87+
R.anim.abc_fade_out,
88+
R.anim.abc_fade_in,
89+
R.anim.abc_shrink_fade_out_from_bottom);
90+
}
91+
92+
transaction
4293
.replace(MAIN_ACTIVITY_FRAGMENT_CONTAINER_ID, fragment, tag)
4394
.addToBackStack(null /* name */)
4495
.commit();

catalog/java/io/material/catalog/tableofcontents/TocViewHolder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.material.catalog.R;
2020

2121
import androidx.fragment.app.FragmentActivity;
22+
import androidx.core.view.ViewCompat;
2223
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
2324
import android.view.LayoutInflater;
2425
import android.view.View;
@@ -40,6 +41,7 @@ class TocViewHolder extends ViewHolder {
4041

4142
private FragmentActivity activity;
4243
private FeatureDemo featureDemo;
44+
private String transitionName;
4345

4446
TocViewHolder(FragmentActivity activity, ViewGroup viewGroup) {
4547
super(
@@ -54,7 +56,9 @@ class TocViewHolder extends ViewHolder {
5456
void bind(FragmentActivity activity, FeatureDemo featureDemo) {
5557
this.activity = activity;
5658
this.featureDemo = featureDemo;
59+
this.transitionName = activity.getString(featureDemo.getTitleResId());
5760

61+
ViewCompat.setTransitionName(itemView, transitionName);
5862
titleView.setText(featureDemo.getTitleResId());
5963
imageView.setImageResource(featureDemo.getDrawableResId());
6064
itemView.setOnClickListener(clickListener);
@@ -63,5 +67,7 @@ void bind(FragmentActivity activity, FeatureDemo featureDemo) {
6367
}
6468

6569
private final OnClickListener clickListener =
66-
v -> FeatureDemoUtils.startFragment(activity, featureDemo.createFragment(), FRAGMENT_CONTENT);
70+
v ->
71+
FeatureDemoUtils.startFragment(
72+
activity, featureDemo.createFragment(), FRAGMENT_CONTENT, v, transitionName);
6773
}

0 commit comments

Comments
 (0)