diff --git a/catalog/build.gradle b/catalog/build.gradle index 0b8f91a8ff8..ad74eb8524f 100644 --- a/catalog/build.gradle +++ b/catalog/build.gradle @@ -1,6 +1,13 @@ apply plugin: 'com.android.application' dependencies { + + constraints { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { + because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") + } + } + api 'com.google.dagger:dagger:2.45' annotationProcessor 'com.google.dagger:dagger-compiler:2.45' @@ -12,6 +19,7 @@ dependencies { api 'androidx.constraintlayout:constraintlayout:2.1.0' api 'androidx.gridlayout:gridlayout:1.0.0' api "androidx.multidex:multidex:2.0.1" + api "androidx.activity:activity:1.8.0-alpha02" api "androidx.recyclerview:recyclerview:1.2.1" api 'androidx.window:window:1.0.0-beta04' api "androidx.window:window-java:1.0.0-beta04" diff --git a/catalog/java/io/material/catalog/bottomsheet/BottomSheetMainDemoFragment.java b/catalog/java/io/material/catalog/bottomsheet/BottomSheetMainDemoFragment.java index 894b3dbd59f..badbac5a9d3 100644 --- a/catalog/java/io/material/catalog/bottomsheet/BottomSheetMainDemoFragment.java +++ b/catalog/java/io/material/catalog/bottomsheet/BottomSheetMainDemoFragment.java @@ -19,6 +19,7 @@ import io.material.catalog.R; import android.app.Activity; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.LayoutInflater; @@ -28,9 +29,12 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import android.window.BackEvent; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -42,8 +46,37 @@ /** A fragment that displays the main BottomSheet demo for the Catalog app. */ public class BottomSheetMainDemoFragment extends DemoFragment { + + private final OnBackPressedCallback persistentBottomSheetBackCallback = + new OnBackPressedCallback(/* enabled= */ false) { + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void handleOnBackStarted(@NonNull BackEvent backEvent) { + persistentBottomSheetBehavior.startBackProgress(backEvent); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void handleOnBackProgressed(@NonNull BackEvent backEvent) { + persistentBottomSheetBehavior.updateBackProgress(backEvent); + } + + @Override + public void handleOnBackPressed() { + persistentBottomSheetBehavior.handleBackInvoked(); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void handleOnBackCancelled() { + persistentBottomSheetBehavior.cancelBackProgress(); + } + }; + private WindowPreferencesManager windowPreferencesManager; private BottomSheetDialog bottomSheetDialog; + private BottomSheetBehavior persistentBottomSheetBehavior; private WindowInsetsCompat windowInsets; private int peekHeightPx; @@ -119,8 +152,10 @@ public View onCreateDemoView( .addBottomSheetCallback(createBottomSheetCallback(dialogText)); TextView bottomSheetText = view.findViewById(R.id.cat_persistent_bottomsheet_state); View bottomSheetPersistent = view.findViewById(R.id.bottom_drawer); - BottomSheetBehavior.from(bottomSheetPersistent) - .addBottomSheetCallback(createBottomSheetCallback(bottomSheetText)); + persistentBottomSheetBehavior = BottomSheetBehavior.from(bottomSheetPersistent); + persistentBottomSheetBehavior.addBottomSheetCallback( + createBottomSheetCallback(bottomSheetText)); + setupBackHandling(persistentBottomSheetBehavior); Button button1 = view.findViewById(R.id.cat_bottomsheet_button); button1.setOnClickListener( @@ -247,4 +282,34 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) {} }; return bottomSheetCallback; } + + private void setupBackHandling(BottomSheetBehavior behavior) { + requireActivity() + .getOnBackPressedDispatcher() + .addCallback(this, persistentBottomSheetBackCallback); + behavior.addBottomSheetCallback( + new BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + switch (newState) { + case BottomSheetBehavior.STATE_EXPANDED: + case BottomSheetBehavior.STATE_HALF_EXPANDED: + persistentBottomSheetBackCallback.setEnabled(true); + break; + case BottomSheetBehavior.STATE_COLLAPSED: + case BottomSheetBehavior.STATE_HIDDEN: + persistentBottomSheetBackCallback.setEnabled(false); + break; + case BottomSheetBehavior.STATE_DRAGGING: + case BottomSheetBehavior.STATE_SETTLING: + default: + // Do nothing, only change callback enabled for "stable" states. + break; + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) {} + }); + } } diff --git a/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java b/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java index b3eb8775e4e..af4c580e93b 100644 --- a/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java +++ b/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java @@ -22,6 +22,8 @@ import static java.lang.Math.max; import static java.lang.Math.min; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; @@ -37,6 +39,7 @@ import android.util.SparseIntArray; import android.util.TypedValue; import android.view.MotionEvent; +import android.view.RoundedCorner; import android.view.VelocityTracker; import android.view.View; import android.view.View.MeasureSpec; @@ -44,12 +47,15 @@ import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewParent; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; +import android.window.BackEvent; import androidx.annotation.FloatRange; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; +import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; @@ -57,6 +63,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams; import androidx.core.graphics.Insets; import androidx.core.math.MathUtils; +import androidx.core.os.BuildCompat; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; @@ -66,6 +73,8 @@ import androidx.customview.widget.ViewDragHelper; import com.google.android.material.internal.ViewUtils; import com.google.android.material.internal.ViewUtils.RelativePadding; +import com.google.android.material.motion.MaterialBackHandler; +import com.google.android.material.motion.MaterialBottomContainerBackHelper; import com.google.android.material.resources.MaterialResources; import com.google.android.material.shape.MaterialShapeDrawable; import com.google.android.material.shape.ShapeAppearanceModel; @@ -84,7 +93,8 @@ * window-like. For BottomSheetDialog use {@link BottomSheetDialog#setTitle(int)}, and for * BottomSheetDialogFragment use {@link ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}. */ -public class BottomSheetBehavior extends CoordinatorLayout.Behavior { +public class BottomSheetBehavior extends CoordinatorLayout.Behavior + implements MaterialBackHandler { /** Callback for monitoring events about bottom sheets. */ public abstract static class BottomSheetCallback { @@ -318,6 +328,7 @@ void onLayout(@NonNull View bottomSheet) {} @NonNull private final ArrayList callbacks = new ArrayList<>(); @Nullable private VelocityTracker velocityTracker; + @Nullable MaterialBottomContainerBackHelper bottomContainerBackHelper; int activePointerId; @@ -456,6 +467,7 @@ public void onAttachedToLayoutParams(@NonNull LayoutParams layoutParams) { // first time we layout with this behavior by checking (viewRef == null). viewRef = null; viewDragHelper = null; + bottomContainerBackHelper = null; } @Override @@ -464,6 +476,7 @@ public void onDetachedFromLayoutParams() { // Release references so we don't run unnecessary codepaths while not attached to a view. viewRef = null; viewDragHelper = null; + bottomContainerBackHelper = null; } @Override @@ -533,6 +546,7 @@ public boolean onLayoutChild( setWindowInsetsListener(child); ViewCompat.setWindowInsetsAnimationCallback(child, new InsetsAnimationCallback(child)); viewRef = new WeakReference<>(child); + bottomContainerBackHelper = new MaterialBottomContainerBackHelper(child); // Only set MaterialShapeDrawable as background if shapeTheming is enabled, otherwise will // default to android:background declared in styles or layout. if (materialShapeDrawable != null) { @@ -1409,7 +1423,7 @@ private void updateDrawableForTargetState(@State int state, boolean animate) { if (interpolatorAnimator.isRunning()) { interpolatorAnimator.reverse(); } else { - float to = removeCorners ? 0f : 1f; + float to = removeCorners ? calculateInterpolationWithCornersRemoved() : 1f; float from = 1f - to; interpolatorAnimator.setFloatValues(from, to); interpolatorAnimator.start(); @@ -1418,8 +1432,48 @@ private void updateDrawableForTargetState(@State int state, boolean animate) { if (interpolatorAnimator != null && interpolatorAnimator.isRunning()) { interpolatorAnimator.cancel(); } - materialShapeDrawable.setInterpolation(expandedCornersRemoved ? 0f : 1f); + materialShapeDrawable.setInterpolation( + expandedCornersRemoved ? calculateInterpolationWithCornersRemoved() : 1f); + } + } + + private float calculateInterpolationWithCornersRemoved() { + if (materialShapeDrawable != null + && viewRef != null + && viewRef.get() != null + && VERSION.SDK_INT >= VERSION_CODES.S) { + V view = viewRef.get(); + int[] location = new int[2]; + view.getLocationOnScreen(location); + // Only use device corner radius if sheet is touching top of screen. + if (location[1] == 0) { + final WindowInsets insets = view.getRootWindowInsets(); + if (insets != null) { + float topLeftInterpolation = + calculateCornerInterpolation( + materialShapeDrawable.getTopLeftCornerResolvedSize(), + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)); + float topRightInterpolation = + calculateCornerInterpolation( + materialShapeDrawable.getTopRightCornerResolvedSize(), + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)); + return Math.max(topLeftInterpolation, topRightInterpolation); + } + } + } + return 0; + } + + @RequiresApi(VERSION_CODES.S) + private float calculateCornerInterpolation( + float materialShapeDrawableCornerSize, @Nullable RoundedCorner deviceRoundedCorner) { + if (deviceRoundedCorner != null) { + float deviceCornerRadius = deviceRoundedCorner.getRadius(); + if (deviceCornerRadius > 0 && materialShapeDrawableCornerSize > 0) { + return deviceCornerRadius / materialShapeDrawableCornerSize; + } } + return 0; } private boolean isExpandedAndShouldRemoveCorners() { @@ -1506,6 +1560,67 @@ boolean shouldHide(@NonNull View child, float yvel) { return Math.abs(newTop - collapsedOffset) / (float) peek > HIDE_THRESHOLD; } + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void startBackProgress(@NonNull BackEvent backEvent) { + if (bottomContainerBackHelper == null) { + return; + } + bottomContainerBackHelper.startBackProgress(backEvent); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void updateBackProgress(@NonNull BackEvent backEvent) { + if (bottomContainerBackHelper == null) { + return; + } + bottomContainerBackHelper.updateBackProgress(backEvent); + } + + @Override + public void handleBackInvoked() { + if (bottomContainerBackHelper == null) { + return; + } + BackEvent backEvent = bottomContainerBackHelper.onHandleBackInvoked(); + if (backEvent == null || !BuildCompat.isAtLeastU()) { + // If using traditional button system nav or if pre-U, just hide or collapse the bottom sheet. + setState(hideable ? STATE_HIDDEN : STATE_COLLAPSED); + return; + } + if (hideable) { + bottomContainerBackHelper.finishBackProgressNotPersistent( + backEvent, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Hide immediately following the built-in predictive back slide down animation. + setStateInternal(STATE_HIDDEN); + } + }); + } else { + bottomContainerBackHelper.finishBackProgressPersistent( + backEvent, /* animatorListener= */ null); + setState(STATE_COLLAPSED); + } + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @Override + public void cancelBackProgress() { + if (bottomContainerBackHelper == null) { + return; + } + bottomContainerBackHelper.cancelBackProgress(); + } + + @VisibleForTesting + @Nullable + MaterialBottomContainerBackHelper getBackHelper() { + return bottomContainerBackHelper; + } + @Nullable @VisibleForTesting View findScrollingChild(View view) { @@ -1556,7 +1671,7 @@ MaterialShapeDrawable getMaterialShapeDrawable() { } private void createShapeValueAnimator() { - interpolatorAnimator = ValueAnimator.ofFloat(0f, 1f); + interpolatorAnimator = ValueAnimator.ofFloat(calculateInterpolationWithCornersRemoved(), 1f); interpolatorAnimator.setDuration(CORNER_ANIMATION_DURATION); interpolatorAnimator.addUpdateListener( new AnimatorUpdateListener() { diff --git a/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java b/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java index 24eaae716c9..17a9f113d78 100644 --- a/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java +++ b/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java @@ -50,6 +50,7 @@ import androidx.core.view.WindowInsetsControllerCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import com.google.android.material.internal.EdgeToEdgeUtils; +import com.google.android.material.motion.MaterialBackOrchestrator; import com.google.android.material.shape.MaterialShapeDrawable; /** @@ -80,6 +81,7 @@ public class BottomSheetDialog extends AppCompatDialog { private boolean canceledOnTouchOutsideSet; private EdgeToEdgeCallback edgeToEdgeCallback; private boolean edgeToEdgeEnabled; + @Nullable private MaterialBackOrchestrator backOrchestrator; public BottomSheetDialog(@NonNull Context context) { this(context, 0); @@ -161,6 +163,9 @@ public void setCancelable(boolean cancelable) { if (behavior != null) { behavior.setHideable(cancelable); } + if (getWindow() != null) { + updateListeningForBackCallbacks(); + } } } @@ -193,6 +198,8 @@ public void onAttachedToWindow() { edgeToEdgeCallback.setWindow(window); } } + + updateListeningForBackCallbacks(); } @Override @@ -200,6 +207,10 @@ public void onDetachedFromWindow() { if (edgeToEdgeCallback != null) { edgeToEdgeCallback.setWindow(null); } + + if (backOrchestrator != null) { + backOrchestrator.stopListeningForBackCallbacks(); + } } /** @@ -283,6 +294,7 @@ private FrameLayout ensureContainerAndBehavior() { behavior = BottomSheetBehavior.from(bottomSheet); behavior.addBottomSheetCallback(bottomSheetCallback); behavior.setHideable(cancelable); + backOrchestrator = new MaterialBackOrchestrator(behavior, bottomSheet); } return container; } @@ -370,6 +382,17 @@ public boolean onTouch(View view, MotionEvent event) { return container; } + private void updateListeningForBackCallbacks() { + if (backOrchestrator == null) { + return; + } + if (cancelable) { + backOrchestrator.startListeningForBackCallbacks(); + } else { + backOrchestrator.stopListeningForBackCallbacks(); + } + } + boolean shouldWindowCloseOnTouchOutside() { if (!canceledOnTouchOutsideSet) { TypedArray a = diff --git a/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java b/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java index 4caabe6eafe..005419abc32 100644 --- a/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java +++ b/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java @@ -18,6 +18,7 @@ import com.google.android.material.R; import android.animation.TimeInterpolator; +import android.content.Context; import android.os.Build.VERSION_CODES; import android.view.View; import android.window.BackEvent; @@ -33,20 +34,37 @@ */ abstract class MaterialBackAnimationHelper { + private static final int HIDE_DURATION_MAX_DEFAULT = 300; + private static final int HIDE_DURATION_MIN_DEFAULT = 150; + private static final int CANCEL_DURATION_DEFAULT = 100; + @NonNull private final TimeInterpolator progressInterpolator; @NonNull protected final View view; + protected final int hideDurationMax; + protected final int hideDurationMin; + protected final int cancelDuration; @Nullable private BackEvent backEvent; public MaterialBackAnimationHelper(@NonNull View view) { this.view = view; + Context context = view.getContext(); progressInterpolator = MotionUtils.resolveThemeInterpolator( - view.getContext(), + context, R.attr.motionEasingStandardDecelerateInterpolator, PathInterpolatorCompat.create(0, 0, 0, 1)); + hideDurationMax = + MotionUtils.resolveThemeDuration( + context, R.attr.motionDurationMedium2, HIDE_DURATION_MAX_DEFAULT); + hideDurationMin = + MotionUtils.resolveThemeDuration( + context, R.attr.motionDurationShort3, HIDE_DURATION_MIN_DEFAULT); + cancelDuration = + MotionUtils.resolveThemeDuration( + context, R.attr.motionDurationShort2, CANCEL_DURATION_DEFAULT); } protected float interpolateProgress(float progress) { diff --git a/lib/java/com/google/android/material/motion/MaterialBottomContainerBackHelper.java b/lib/java/com/google/android/material/motion/MaterialBottomContainerBackHelper.java new file mode 100644 index 00000000000..5fe6582286c --- /dev/null +++ b/lib/java/com/google/android/material/motion/MaterialBottomContainerBackHelper.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.material.motion; + +import com.google.android.material.R; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.res.Resources; +import android.os.Build.VERSION_CODES; +import android.view.View; +import android.view.ViewGroup; +import android.window.BackEvent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import com.google.android.material.animation.AnimationUtils; + +/** + * Utility class for container views on the bottom edge of the screen (e.g., bottom sheet) that + * support back progress animations. + * + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public class MaterialBottomContainerBackHelper extends MaterialBackAnimationHelper { + + private final float maxScaleXDistance; + private final float maxScaleYDistance; + + public MaterialBottomContainerBackHelper(@NonNull View view) { + super(view); + + Resources resources = view.getResources(); + maxScaleXDistance = + resources.getDimension(R.dimen.m3_back_progress_bottom_container_max_scale_x_distance); + maxScaleYDistance = + resources.getDimension(R.dimen.m3_back_progress_bottom_container_max_scale_y_distance); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startBackProgress(@NonNull BackEvent backEvent) { + super.onStartBackProgress(backEvent); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void updateBackProgress(@NonNull BackEvent backEvent) { + super.onUpdateBackProgress(backEvent); + + updateBackProgress(backEvent.getProgress()); + } + + @VisibleForTesting + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void updateBackProgress(float progress) { + progress = interpolateProgress(progress); + + float width = view.getWidth(); + float height = view.getHeight(); + float maxScaleXDelta = maxScaleXDistance / width; + float maxScaleYDelta = maxScaleYDistance / height; + float scaleXDelta = AnimationUtils.lerp(0, maxScaleXDelta, progress); + float scaleYDelta = AnimationUtils.lerp(0, maxScaleYDelta, progress); + float scaleX = 1 - scaleXDelta; + float scaleY = 1 - scaleYDelta; + view.setScaleX(scaleX); + view.setPivotY(height); + view.setScaleY(scaleY); + + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View childView = viewGroup.getChildAt(i); + // Preserve the original aspect ratio and container alignment of the child content. + childView.setPivotY(-childView.getTop()); + childView.setScaleY(scaleX / scaleY); + } + } + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void finishBackProgressPersistent( + @NonNull BackEvent backEvent, @Nullable AnimatorListener animatorListener) { + Animator animator = createResetScaleAnimator(); + animator.setDuration( + AnimationUtils.lerp(hideDurationMax, hideDurationMin, backEvent.getProgress())); + if (animatorListener != null) { + animator.addListener(animatorListener); + } + animator.start(); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void finishBackProgressNotPersistent( + @NonNull BackEvent backEvent, @Nullable AnimatorListener animatorListener) { + float scaledHeight = view.getHeight() * view.getScaleY(); + ObjectAnimator finishAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, scaledHeight); + finishAnimator.setInterpolator(new FastOutSlowInInterpolator()); + finishAnimator.setDuration( + AnimationUtils.lerp(hideDurationMax, hideDurationMin, backEvent.getProgress())); + finishAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setTranslationY(0); + updateBackProgress(/* progress= */ 0); + } + }); + if (animatorListener != null) { + finishAnimator.addListener(animatorListener); + } + finishAnimator.start(); + } + + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + public void cancelBackProgress() { + super.onCancelBackProgress(); + + Animator animator = createResetScaleAnimator(); + animator.setDuration(cancelDuration); + animator.start(); + } + + private Animator createResetScaleAnimator() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(view, View.SCALE_X, 1), + ObjectAnimator.ofFloat(view, View.SCALE_Y, 1)); + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View childView = viewGroup.getChildAt(i); + animatorSet.playTogether(ObjectAnimator.ofFloat(childView, View.SCALE_Y, 1)); + } + } + animatorSet.setInterpolator(new FastOutSlowInInterpolator()); + return animatorSet; + } +} diff --git a/lib/java/com/google/android/material/motion/MaterialSideContainerBackHelper.java b/lib/java/com/google/android/material/motion/MaterialSideContainerBackHelper.java index 1ce20f1354e..483a5e4e313 100644 --- a/lib/java/com/google/android/material/motion/MaterialSideContainerBackHelper.java +++ b/lib/java/com/google/android/material/motion/MaterialSideContainerBackHelper.java @@ -51,10 +51,6 @@ @RestrictTo(LIBRARY_GROUP) public class MaterialSideContainerBackHelper extends MaterialBackAnimationHelper { - private static final int HIDE_DURATION_MAX = 300; - private static final int HIDE_DURATION_MIN = 150; - private static final int CANCEL_DURATION = 75; - private final float maxScaleXDistanceShrink; private final float maxScaleXDistanceGrow; private final float maxScaleYDistance; @@ -135,7 +131,7 @@ public void finishBackProgress( } finishAnimator.setInterpolator(new FastOutSlowInInterpolator()); finishAnimator.setDuration( - AnimationUtils.lerp(HIDE_DURATION_MAX, HIDE_DURATION_MIN, backEvent.getProgress())); + AnimationUtils.lerp(hideDurationMax, hideDurationMin, backEvent.getProgress())); finishAnimator.addListener( new AnimatorListenerAdapter() { @Override @@ -166,7 +162,7 @@ public void cancelBackProgress() { ObjectAnimator.ofFloat(childView, View.SCALE_Y, 1)); } - cancelAnimatorSet.setDuration(CANCEL_DURATION); + cancelAnimatorSet.setDuration(cancelDuration); cancelAnimatorSet.start(); } diff --git a/lib/java/com/google/android/material/motion/res/values/dimens.xml b/lib/java/com/google/android/material/motion/res/values/dimens.xml index ad033e7e5d8..b3a666cf8aa 100644 --- a/lib/java/com/google/android/material/motion/res/values/dimens.xml +++ b/lib/java/com/google/android/material/motion/res/values/dimens.xml @@ -20,4 +20,6 @@ 12dp 48dp + 48dp + 24dp