material-components_materia.../lib/java/com/google/android/material/motion/MaterialBottomContainerBackHelper.java
dsn5ft d6fad952c6 [Predictive Back][Bottom Sheet] Update Bottom Sheet to support predictive back
- Enable predictive back by default for Bottom Sheet dialogs
- Update Catalog demo to enable predictive back for persistent/standard bottom sheet

PiperOrigin-RevId: 518896205
2023-03-23 18:15:27 +00:00

161 lines
5.7 KiB
Java

/*
* 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;
}
}