Update Catalog to use Container Transform transition for navigation

PiperOrigin-RevId: 307096919
This commit is contained in:
dniz 2020-04-17 15:46:35 -04:00 committed by Daniel Nizri
parent 57a8ebdcdd
commit d5faac589b
5 changed files with 154 additions and 27 deletions

View File

@ -18,8 +18,11 @@ package io.material.catalog.feature;
import io.material.catalog.R;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
@ -28,6 +31,9 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.transition.MaterialContainerTransform;
import com.google.android.material.transition.MaterialContainerTransformSharedElementCallback;
import dagger.android.AndroidInjection;
import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector;
@ -40,6 +46,11 @@ public abstract class DemoActivity extends AppCompatActivity implements HasAndro
public static final String EXTRA_DEMO_TITLE = "demo_title";
static final String EXTRA_TRANSITION_NAME = "EXTRA_TRANSITION_NAME";
private static final long DURATION_ENTER = 300;
private static final long DURATION_RETURN = 275;
private Toolbar toolbar;
private ViewGroup demoContainer;
@ -47,6 +58,15 @@ public abstract class DemoActivity extends AppCompatActivity implements HasAndro
@Override
protected void onCreate(@Nullable Bundle bundle) {
String transitionName = getIntent().getStringExtra(EXTRA_TRANSITION_NAME);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && transitionName != null) {
findViewById(android.R.id.content).setTransitionName(transitionName);
setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
getWindow().setSharedElementEnterTransition(buildContainerTransform(DURATION_ENTER));
getWindow().setSharedElementReturnTransition(buildContainerTransform(DURATION_RETURN));
}
safeInject();
super.onCreate(bundle);
WindowPreferencesManager windowPreferencesManager = new WindowPreferencesManager(this);
@ -96,6 +116,17 @@ public abstract class DemoActivity extends AppCompatActivity implements HasAndro
}
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
private MaterialContainerTransform buildContainerTransform(long duration) {
MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDuration(duration);
transform.addTarget(android.R.id.content);
transform.setContainerColor(
MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface));
transform.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH);
return transform;
}
private void initDemoActionBar() {
if (shouldShowDefaultDemoActionBar()) {
setSupportActionBar(toolbar);

View File

@ -26,6 +26,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.core.view.ViewCompat;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@ -86,6 +87,12 @@ public abstract class DemoFragment extends Fragment
View view =
layoutInflater.inflate(R.layout.cat_demo_fragment, viewGroup, false /* attachToRoot */);
Bundle arguments = getArguments();
if (arguments != null) {
String transitionName = arguments.getString(FeatureDemoUtils.ARG_TRANSITION_NAME);
ViewCompat.setTransitionName(view, transitionName);
}
toolbar = view.findViewById(R.id.toolbar);
// show a memory widget on Kitkat
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
@ -180,9 +187,12 @@ public abstract class DemoFragment extends Fragment
private final FragmentActivity activity = getActivity();
private final Runnable listener = () -> activity.runOnUiThread(() -> {
memoryWidget.refreshMemStats(Runtime.getRuntime());
});
private final Runnable listener =
() ->
activity.runOnUiThread(
() -> {
memoryWidget.refreshMemStats(Runtime.getRuntime());
});
private boolean memoryWidgetShown;

View File

@ -18,10 +18,13 @@ package io.material.catalog.feature;
import io.material.catalog.R;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import androidx.annotation.ArrayRes;
import androidx.annotation.ColorInt;
@ -29,8 +32,10 @@ import androidx.annotation.DimenRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.MenuItemCompat;
import androidx.core.view.ViewCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.LayoutInflater;
@ -42,6 +47,7 @@ import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
import com.google.android.material.resources.MaterialResources;
import com.google.android.material.transition.MaterialContainerTransformSharedElementCallback;
import dagger.android.support.DaggerFragment;
import java.util.Collections;
import java.util.List;
@ -68,6 +74,12 @@ public abstract class DemoLandingFragment extends DaggerFragment {
layoutInflater.inflate(
R.layout.cat_demo_landing_fragment, viewGroup, false /* attachToRoot */);
Bundle arguments = getArguments();
if (arguments != null) {
String transitionName = arguments.getString(FeatureDemoUtils.ARG_TRANSITION_NAME);
ViewCompat.setTransitionName(view, transitionName);
}
Toolbar toolbar = view.findViewById(R.id.toolbar);
AppCompatActivity activity = (AppCompatActivity) getActivity();
@ -145,7 +157,9 @@ public abstract class DemoLandingFragment extends DaggerFragment {
TextView titleTextView = demoView.findViewById(R.id.cat_demo_landing_row_title);
TextView subtitleTextView = demoView.findViewById(R.id.cat_demo_landing_row_subtitle);
rootView.setOnClickListener(v -> startDemo(demo));
String transitionName = getString(demo.getTitleResId());
ViewCompat.setTransitionName(rootView, transitionName);
rootView.setOnClickListener(v -> startDemo(v, demo, transitionName));
titleTextView.setText(demo.getTitleResId());
subtitleTextView.setText(getDemoClassName(demo));
@ -169,26 +183,41 @@ public abstract class DemoLandingFragment extends DaggerFragment {
}
}
private void startDemo(Demo demo) {
private void startDemo(View sharedElement, Demo demo, String transitionName) {
if (demo.createFragment() != null) {
startDemoFragment(demo.createFragment());
startDemoFragment(sharedElement, demo.createFragment(), transitionName);
} else if (demo.createActivityIntent() != null) {
startDemoActivity(demo.createActivityIntent());
startDemoActivity(sharedElement, demo.createActivityIntent(), transitionName);
} else {
throw new IllegalStateException("Demo must implement createFragment or createActivityIntent");
}
}
private void startDemoFragment(Fragment fragment) {
private void startDemoFragment(View sharedElement, Fragment fragment, String transitionName) {
Bundle args = new Bundle();
args.putString(DemoFragment.ARG_DEMO_TITLE, getString(getTitleResId()));
args.putString(FeatureDemoUtils.ARG_TRANSITION_NAME, transitionName);
fragment.setArguments(args);
FeatureDemoUtils.startFragment(getActivity(), fragment, FRAGMENT_DEMO_CONTENT);
FeatureDemoUtils.startFragment(
getActivity(), fragment, FRAGMENT_DEMO_CONTENT, sharedElement, transitionName);
}
private void startDemoActivity(Intent intent) {
private void startDemoActivity(View sharedElement, Intent intent, String transitionName) {
intent.putExtra(DemoActivity.EXTRA_DEMO_TITLE, getString(getTitleResId()));
startActivity(intent);
intent.putExtra(DemoActivity.EXTRA_TRANSITION_NAME, transitionName);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
// Set up shared element transition and disable overlay so views don't show above system bars
FragmentActivity activity = getActivity();
activity.setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
activity.getWindow().setSharedElementsUseOverlay(false);
ActivityOptions options =
ActivityOptions.makeSceneTransitionAnimation(activity, sharedElement, transitionName);
startActivity(intent, options.toBundle());
} else {
startActivity(intent);
}
}
private void setMarginStart(View view, @DimenRes int marginResId) {
@ -231,9 +260,9 @@ public abstract class DemoLandingFragment extends DaggerFragment {
/**
* Whether or not the feature shown by this fragment should be flagged as restricted.
*
* Examples of restricted feature could be features which depends on an API level that is
* greater than MDCs min sdk version. If overriding this method, you should also override
* {@link #getRestrictedMessageId()} and provide information about why the feature is restricted.
* <p>Examples of restricted feature could be features which depends on an API level that is
* greater than MDCs min sdk version. If overriding this method, you should also override {@link
* #getRestrictedMessageId()} and provide information about why the feature is restricted.
*/
public boolean isRestricted() {
return false;
@ -242,10 +271,10 @@ public abstract class DemoLandingFragment extends DaggerFragment {
/**
* The message to display if a feature {@link #isRestricted()}.
*
* This message should provide insight into why the feature is restricted for the device it
* is running on. This message will be displayed in the description area of the demo fragment
* instead of the the provided {@link #getDescriptionResId()}. Additionally, all demos, both the
* main demo and any additional demos will not be shown.
* <p>This message should provide insight into why the feature is restricted for the device it is
* running on. This message will be displayed in the description area of the demo fragment instead
* of the the provided {@link #getDescriptionResId()}. Additionally, all demos, both the main demo
* and any additional demos will not be shown.
*/
@StringRes
public int getRestrictedMessageId() {

View File

@ -20,25 +20,76 @@ import io.material.catalog.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import android.view.View;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.transition.Hold;
import com.google.android.material.transition.MaterialContainerTransform;
/** Utils for feature demos. */
public abstract class FeatureDemoUtils {
static final String ARG_TRANSITION_NAME = "ARG_TRANSITION_NAME";
private static final int MAIN_ACTIVITY_FRAGMENT_CONTAINER_ID = R.id.container;
private static final String DEFAULT_CATALOG_DEMO = "default_catalog_demo";
public static void startFragment(FragmentActivity activity, Fragment fragment, String tag) {
activity
.getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(
R.anim.abc_grow_fade_in_from_bottom,
R.anim.abc_fade_out,
R.anim.abc_fade_in,
R.anim.abc_shrink_fade_out_from_bottom)
startFragmentInternal(activity, fragment, tag, null, null);
}
public static void startFragment(
FragmentActivity activity,
Fragment fragment,
String tag,
View sharedElement,
String sharedElementName) {
startFragmentInternal(activity, fragment, tag, sharedElement, sharedElementName);
}
public static void startFragmentInternal(
FragmentActivity activity,
Fragment fragment,
String tag,
@Nullable View sharedElement,
@Nullable String sharedElementName) {
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
&& sharedElement != null
&& sharedElementName != null) {
Fragment currentFragment = getCurrentFragment(activity);
currentFragment.setExitTransition(new Hold());
MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setContainerColor(MaterialColors.getColor(sharedElement, R.attr.colorSurface));
transform.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH);
fragment.setSharedElementEnterTransition(transform);
transaction.addSharedElement(sharedElement, sharedElementName);
if (fragment.getArguments() == null) {
Bundle args = new Bundle();
args.putString(ARG_TRANSITION_NAME, sharedElementName);
fragment.setArguments(args);
} else {
fragment.getArguments().putString(ARG_TRANSITION_NAME, sharedElementName);
}
} else {
transaction.setCustomAnimations(
R.anim.abc_grow_fade_in_from_bottom,
R.anim.abc_fade_out,
R.anim.abc_fade_in,
R.anim.abc_shrink_fade_out_from_bottom);
}
transaction
.replace(MAIN_ACTIVITY_FRAGMENT_CONTAINER_ID, fragment, tag)
.addToBackStack(null /* name */)
.commit();

View File

@ -19,6 +19,7 @@ package io.material.catalog.tableofcontents;
import io.material.catalog.R;
import androidx.fragment.app.FragmentActivity;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
@ -40,6 +41,7 @@ class TocViewHolder extends ViewHolder {
private FragmentActivity activity;
private FeatureDemo featureDemo;
private String transitionName;
TocViewHolder(FragmentActivity activity, ViewGroup viewGroup) {
super(
@ -54,7 +56,9 @@ class TocViewHolder extends ViewHolder {
void bind(FragmentActivity activity, FeatureDemo featureDemo) {
this.activity = activity;
this.featureDemo = featureDemo;
this.transitionName = activity.getString(featureDemo.getTitleResId());
ViewCompat.setTransitionName(itemView, transitionName);
titleView.setText(featureDemo.getTitleResId());
imageView.setImageResource(featureDemo.getDrawableResId());
itemView.setOnClickListener(clickListener);
@ -63,5 +67,7 @@ class TocViewHolder extends ViewHolder {
}
private final OnClickListener clickListener =
v -> FeatureDemoUtils.startFragment(activity, featureDemo.createFragment(), FRAGMENT_CONTENT);
v ->
FeatureDemoUtils.startFragment(
activity, featureDemo.createFragment(), FRAGMENT_CONTENT, v, transitionName);
}