From ea5b316f1b05fc650547206427aaa295a5a91f96 Mon Sep 17 00:00:00 2001 From: Jamie Garside Date: Thu, 26 Aug 2021 17:17:19 +0100 Subject: [PATCH] Update motion for one-handed bouncer. This is a little awkward to implement properly because of the animation parameters; see the comment in KeyguardSecurityContainer for more information. Video link: https://drive.google.com/file/d/1EJIZo-yn_Pez3nKgipodQ_QwqyDjXPT8/view?usp=sharing&resourcekey=0-TQWNbg-y6f-OcN_FkrC52w Test: Manually tested. Bug: 195012405 Change-Id: I550847d7e47608649422709d96b31b90f0e6467d --- .../SystemUI/res-keyguard/values/dimens.xml | 7 ++ .../keyguard/KeyguardSecurityContainer.java | 100 +++++++++++++++--- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index a2ae5023166f3..e854b027e074b 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -104,4 +104,11 @@ allow it to use the whole screen space, 0.6 will allow it to use just under half of the screen. --> 0.0 + + + 120dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 64d214d1eee7e..d80a408ec1d05 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -21,8 +21,7 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static java.lang.Integer.max; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; @@ -40,6 +39,8 @@ import android.view.ViewPropertyAnimator; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowManager; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.widget.FrameLayout; import androidx.annotation.VisibleForTesting; @@ -55,7 +56,6 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.ArrayList; import java.util.List; @@ -85,6 +85,13 @@ public class KeyguardSecurityContainer extends FrameLayout { private static final long IME_DISAPPEAR_DURATION_MS = 125; + // The duration of the animation to switch bouncer sides. + private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500; + + // How much of the switch sides animation should be dedicated to fading the bouncer out. The + // remainder will fade it back in again. + private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f; + @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; private AlertDialog mAlertDialog; @@ -322,18 +329,87 @@ public class KeyguardSecurityContainer extends FrameLayout { ? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth()); if (animate) { - mRunningOneHandedAnimator = - mSecurityViewFlipper.animate().translationX(targetTranslation); - mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRunningOneHandedAnimator = null; + // This animation is a bit fun to implement. The bouncer needs to move, and fade in/out + // at the same time. The issue is, the bouncer should only move a short amount (120dp or + // so), but obviously needs to go from one side of the screen to the other. This needs a + // pretty custom animation. + // + // This works as follows. It uses a ValueAnimation to simply drive the animation + // progress. This animator is responsible for both the translation of the bouncer, and + // the current fade. It will fade the bouncer out while also moving it along the 120dp + // path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer + // to its destination, then fade it back in again. The effect is that the bouncer will + // move from 0 -> X while fading out, then (destination - X) -> destination while fading + // back in again. + // TODO(b/195012405): Make this animation properly abortable. + Interpolator positionInterpolator = AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_extra_slow_in); + Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; + Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; + + ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 1.0f); + anim.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS); + anim.setInterpolator(Interpolators.LINEAR); + + int initialTranslation = (int) mSecurityViewFlipper.getTranslationX(); + int totalTranslation = (int) getResources().getDimension( + R.dimen.one_handed_bouncer_move_animation_translation); + + final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering() + && mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; + if (shouldRestoreLayerType) { + mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); + } + + anim.addUpdateListener(animation -> { + float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION; + boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; + + int currentTranslation = (int) (positionInterpolator.getInterpolation( + animation.getAnimatedFraction()) * totalTranslation); + int translationRemaining = totalTranslation - currentTranslation; + + // Flip the sign if we're going from right to left. + if (mIsSecurityViewLeftAligned) { + currentTranslation = -currentTranslation; + translationRemaining = -translationRemaining; + } + + if (isFadingOut) { + // The bouncer fades out over the first X%. + float fadeOutFraction = MathUtils.constrainedMap( + /* rangeMin= */0.0f, + /* rangeMax= */1.0f, + /* valueMin= */0.0f, + /* valueMax= */switchPoint, + animation.getAnimatedFraction()); + float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); + mSecurityViewFlipper.setAlpha(1f - opacity); + + // Animate away from the source. + mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation); + } else { + // And in again over the remaining (100-X)%. + float fadeInFraction = MathUtils.constrainedMap( + /* rangeMin= */0.0f, + /* rangeMax= */1.0f, + /* valueMin= */switchPoint, + /* valueMax= */1.0f, + animation.getAnimatedFraction()); + + float opacity = fadeInInterpolator.getInterpolation(fadeInFraction); + mSecurityViewFlipper.setAlpha(opacity); + + // Fading back in, animate towards the destination. + mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining); + } + + if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { + mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); } }); - mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - mRunningOneHandedAnimator.start(); + anim.start(); } else { mSecurityViewFlipper.setTranslationX(targetTranslation); }