From c3cabe0af9038794402333bd48ec6ebf2ae17c4c Mon Sep 17 00:00:00 2001 From: Aleksandr Litovchenko Date: Mon, 9 Aug 2021 19:07:38 +0100 Subject: [PATCH 1/2] LockPatternView dot activation animation. Animate dot color when it's activated, duration is synchronized with line disappearing animation. Do not animate dot size if it's not needed. Bug: 196067470 Bug: 193417780 Tests: Manually Change-Id: I13fa478f434b2c56a5818a4b639f3e38f7c05c4d (cherry picked from commit 450633259bc6283f25d98d5e056d39e74bcb8cc7) --- .../internal/widget/LockPatternView.java | 69 +++++++++++++++---- core/res/res/values/attrs.xml | 2 + 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index e4385a697c85a..eb0a7258c7331 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -18,6 +18,7 @@ package com.android.internal.widget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -56,6 +57,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.internal.R; +import com.android.internal.graphics.ColorUtils; import java.util.ArrayList; import java.util.List; @@ -77,6 +79,7 @@ public class LockPatternView extends View { private static final int LINE_END_ANIMATION_DURATION_MILLIS = 50; private static final int LINE_FADE_OUT_DURATION_MILLIS = 500; private static final int LINE_FADE_OUT_DELAY_MILLIS = 150; + private static final int DOT_ACTIVATION_DURATION_MILLIS = 50; private final CellState[][] mCellStates; private final int mDotSize; @@ -156,6 +159,7 @@ public class LockPatternView extends View { private int mErrorColor; private int mSuccessColor; private int mDotColor; + private int mDotActivatedColor; private final Interpolator mFastOutSlowInInterpolator; private final Interpolator mLinearOutSlowInInterpolator; @@ -237,6 +241,7 @@ public class LockPatternView extends View { float radius; float translationY; float alpha = 1f; + float activationAnimationProgress; public float lineEndX = Float.MIN_VALUE; public float lineEndY = Float.MIN_VALUE; @Nullable @@ -330,6 +335,7 @@ public class LockPatternView extends View { mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, 0); mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, 0); mDotColor = a.getColor(R.styleable.LockPatternView_dotColor, mRegularColor); + mDotActivatedColor = a.getColor(R.styleable.LockPatternView_dotActivatedColor, mDotColor); int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, mRegularColor); mPathPaint.setColor(pathColor); @@ -798,20 +804,52 @@ public class LockPatternView extends View { private void startCellActivatedAnimation(Cell cell) { final CellState cellState = mCellStates[cell.row][cell.column]; - startRadiusAnimation(mDotSize/2, mDotSizeActivated/2, 96, mLinearOutSlowInInterpolator, - cellState, new Runnable() { - @Override - public void run() { - startRadiusAnimation(mDotSizeActivated/2, mDotSize/2, 192, - mFastOutSlowInInterpolator, - cellState, null); - } - }); + if (mDotSize != mDotSizeActivated) { + startRadiusAnimation(mDotSize / 2, mDotSizeActivated / 2, 96, + mLinearOutSlowInInterpolator, + cellState, new Runnable() { + @Override + public void run() { + startRadiusAnimation(mDotSizeActivated / 2, mDotSize / 2, 192, + mFastOutSlowInInterpolator, + cellState, null); + } + }); + } + startDotActivationColorAnimation(cellState); startLineEndAnimation(cellState, mInProgressX, mInProgressY, getCenterXForColumn(cell.column), getCenterYForRow(cell.row)); startLineDisappearingAnimation(cellState); } + private void startDotActivationColorAnimation(CellState cellState) { + ValueAnimator.AnimatorUpdateListener updateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + cellState.activationAnimationProgress = + (float) valueAnimator.getAnimatedValue(); + invalidate(); + } + }; + ValueAnimator activateAnimator = ValueAnimator.ofFloat(0f, 1f); + ValueAnimator deactivateAnimator = ValueAnimator.ofFloat(1f, 0f); + activateAnimator.addUpdateListener(updateListener); + deactivateAnimator.addUpdateListener(updateListener); + activateAnimator.setInterpolator(mFastOutSlowInInterpolator); + deactivateAnimator.setInterpolator(mLinearOutSlowInInterpolator); + + // Align dot animation duration with line fade out animation. + activateAnimator.setDuration(DOT_ACTIVATION_DURATION_MILLIS); + deactivateAnimator.setDuration(DOT_ACTIVATION_DURATION_MILLIS); + AnimatorSet set = new AnimatorSet(); + set.play(deactivateAnimator) + .after(LINE_FADE_OUT_DELAY_MILLIS + LINE_FADE_OUT_DURATION_MILLIS + - DOT_ACTIVATION_DURATION_MILLIS * 2) + .after(activateAnimator); + set.start(); + } + /** * On the last frame before cell activates the end point of in progress line is not aligned * with dot center so we execute a short animation moving the end point to exact dot center. @@ -1295,7 +1333,8 @@ public class LockPatternView extends View { cellState.hwRadius, cellState.hwPaint); } else { drawCircle(canvas, (int) centerX, (int) centerY + translationY, - cellState.radius, drawLookup[i][j], cellState.alpha); + cellState.radius, drawLookup[i][j], cellState.alpha, + cellState.activationAnimationProgress); } } } @@ -1395,8 +1434,14 @@ public class LockPatternView extends View { * @param partOfPattern Whether this circle is part of the pattern. */ private void drawCircle(Canvas canvas, float centerX, float centerY, float radius, - boolean partOfPattern, float alpha) { - mPaint.setColor(getDotColor()); + boolean partOfPattern, float alpha, float activationAnimationProgress) { + if (mFadePattern && !mInStealthMode) { + int resultColor = ColorUtils.blendARGB(mDotColor, mDotActivatedColor, + /* ratio= */ activationAnimationProgress); + mPaint.setColor(resultColor); + } else { + mPaint.setColor(getDotColor()); + } mPaint.setAlpha((int) (alpha * 255)); canvas.drawCircle(centerX, centerY, radius, mPaint); } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index a5f505176d5d1..a56ed147876a2 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8658,6 +8658,8 @@ + + From f5ca99130af03fc51a7ea7e50a75dea7435d1c5e Mon Sep 17 00:00:00 2001 From: Aleksandr Litovchenko Date: Tue, 17 Aug 2021 20:26:35 +0100 Subject: [PATCH 2/2] LockPatternView: Combine all cell activation animations. Combine dot radius, dot color, line disappearing, line end animations together. It helps to reduce inconsistent states when one animation is cancelled and another is still running. Bug: 196067470 Change-Id: Ieac0bb3f329ac94924e3c5dd5a4a4e119404a56c (cherry picked from commit f5436912207f512493270ee5a1824b89f792aea4) --- .../internal/widget/LockPatternView.java | 158 ++++++++---------- 1 file changed, 71 insertions(+), 87 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index eb0a7258c7331..3994fbdca2df1 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -80,6 +80,8 @@ public class LockPatternView extends View { private static final int LINE_FADE_OUT_DURATION_MILLIS = 500; private static final int LINE_FADE_OUT_DELAY_MILLIS = 150; private static final int DOT_ACTIVATION_DURATION_MILLIS = 50; + private static final int DOT_RADIUS_INCREASE_DURATION_MILLIS = 96; + private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192; private final CellState[][] mCellStates; private final int mDotSize; @@ -245,9 +247,7 @@ public class LockPatternView extends View { public float lineEndX = Float.MIN_VALUE; public float lineEndY = Float.MIN_VALUE; @Nullable - ValueAnimator lineEndAnimator; - @Nullable - ValueAnimator lineDisappearingAnimator; + Animator activationAnimator; } /** @@ -804,33 +804,39 @@ public class LockPatternView extends View { private void startCellActivatedAnimation(Cell cell) { final CellState cellState = mCellStates[cell.row][cell.column]; - if (mDotSize != mDotSizeActivated) { - startRadiusAnimation(mDotSize / 2, mDotSizeActivated / 2, 96, - mLinearOutSlowInInterpolator, - cellState, new Runnable() { - @Override - public void run() { - startRadiusAnimation(mDotSizeActivated / 2, mDotSize / 2, 192, - mFastOutSlowInInterpolator, - cellState, null); - } - }); + + if (cellState.activationAnimator != null) { + cellState.activationAnimator.cancel(); } - startDotActivationColorAnimation(cellState); - startLineEndAnimation(cellState, mInProgressX, mInProgressY, - getCenterXForColumn(cell.column), getCenterYForRow(cell.row)); - startLineDisappearingAnimation(cellState); + AnimatorSet animatorSet = new AnimatorSet(); + AnimatorSet.Builder animatorSetBuilder = animatorSet + .play(createLineDisappearingAnimation()) + .with(createLineEndAnimation(cellState, mInProgressX, mInProgressY, + getCenterXForColumn(cell.column), getCenterYForRow(cell.row))); + if (mDotSize != mDotSizeActivated) { + animatorSetBuilder.with(createDotRadiusAnimation(cellState)); + } + if (mDotColor != mDotActivatedColor) { + animatorSetBuilder.with(createDotActivationColorAnimation(cellState)); + } + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + cellState.activationAnimator = null; + invalidate(); + } + }); + cellState.activationAnimator = animatorSet; + animatorSet.start(); } - private void startDotActivationColorAnimation(CellState cellState) { + private Animator createDotActivationColorAnimation(CellState cellState) { ValueAnimator.AnimatorUpdateListener updateListener = - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - cellState.activationAnimationProgress = - (float) valueAnimator.getAnimatedValue(); - invalidate(); - } + valueAnimator -> { + cellState.activationAnimationProgress = + (float) valueAnimator.getAnimatedValue(); + invalidate(); }; ValueAnimator activateAnimator = ValueAnimator.ofFloat(0f, 1f); ValueAnimator deactivateAnimator = ValueAnimator.ofFloat(1f, 0f); @@ -847,82 +853,62 @@ public class LockPatternView extends View { .after(LINE_FADE_OUT_DELAY_MILLIS + LINE_FADE_OUT_DURATION_MILLIS - DOT_ACTIVATION_DURATION_MILLIS * 2) .after(activateAnimator); - set.start(); + return set; } /** * On the last frame before cell activates the end point of in progress line is not aligned * with dot center so we execute a short animation moving the end point to exact dot center. */ - private void startLineEndAnimation(final CellState state, + private Animator createLineEndAnimation(final CellState state, final float startX, final float startY, final float targetX, final float targetY) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - state.lineEndX = (1 - t) * startX + t * targetX; - state.lineEndY = (1 - t) * startY + t * targetY; - invalidate(); - } - }); - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - state.lineEndAnimator = null; - } + valueAnimator.addUpdateListener(animation -> { + float t = (float) animation.getAnimatedValue(); + state.lineEndX = (1 - t) * startX + t * targetX; + state.lineEndY = (1 - t) * startY + t * targetY; + invalidate(); }); valueAnimator.setInterpolator(mFastOutSlowInInterpolator); valueAnimator.setDuration(LINE_END_ANIMATION_DURATION_MILLIS); - valueAnimator.start(); - state.lineEndAnimator = valueAnimator; + return valueAnimator; } /** * Starts animator to fade out a line segment. It does only invalidate because all the * transitions are applied in {@code onDraw} method. */ - private void startLineDisappearingAnimation(final CellState state) { + private Animator createLineDisappearingAnimation() { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - invalidate(); - } - }); - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - state.lineDisappearingAnimator = null; - } - }); + valueAnimator.addUpdateListener(animation -> invalidate()); valueAnimator.setStartDelay(LINE_FADE_OUT_DELAY_MILLIS); valueAnimator.setDuration(LINE_FADE_OUT_DURATION_MILLIS); - valueAnimator.start(); - state.lineDisappearingAnimator = valueAnimator; + return valueAnimator; } - private void startRadiusAnimation(float start, float end, long duration, - Interpolator interpolator, final CellState state, final Runnable endRunnable) { - ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - state.radius = (float) animation.getAnimatedValue(); - invalidate(); - } - }); - if (endRunnable != null) { - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - endRunnable.run(); - } - }); - } - valueAnimator.setInterpolator(interpolator); - valueAnimator.setDuration(duration); - valueAnimator.start(); + private Animator createDotRadiusAnimation(CellState state) { + float defaultRadius = mDotSize / 2f; + float activatedRadius = mDotSizeActivated / 2f; + + ValueAnimator.AnimatorUpdateListener animatorUpdateListener = + animation -> { + state.radius = (float) animation.getAnimatedValue(); + invalidate(); + }; + + ValueAnimator activationAnimator = ValueAnimator.ofFloat(defaultRadius, activatedRadius); + activationAnimator.addUpdateListener(animatorUpdateListener); + activationAnimator.setInterpolator(mLinearOutSlowInInterpolator); + activationAnimator.setDuration(DOT_RADIUS_INCREASE_DURATION_MILLIS); + + ValueAnimator deactivationAnimator = ValueAnimator.ofFloat(activatedRadius, defaultRadius); + deactivationAnimator.addUpdateListener(animatorUpdateListener); + deactivationAnimator.setInterpolator(mFastOutSlowInInterpolator); + deactivationAnimator.setDuration(DOT_RADIUS_DECREASE_DURATION_MILLIS); + + AnimatorSet set = new AnimatorSet(); + set.playSequentially(activationAnimator, deactivationAnimator); + return set; } // helper method to find which cell a point maps to @@ -1136,16 +1122,14 @@ public class LockPatternView extends View { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { CellState state = mCellStates[i][j]; - if (state.lineEndAnimator != null) { - state.lineEndAnimator.cancel(); - state.lineEndAnimator = null; + if (state.activationAnimator != null) { + state.activationAnimator.cancel(); + state.activationAnimator = null; + state.radius = mDotSize / 2f; + state.activationAnimationProgress = 0f; state.lineEndX = Float.MIN_VALUE; state.lineEndY = Float.MIN_VALUE; } - if (state.lineDisappearingAnimator != null) { - state.lineDisappearingAnimator.cancel(); - state.lineDisappearingAnimator = null; - } } } }