From 08e34241c7bf78db8e4a9febd506746787277793 Mon Sep 17 00:00:00 2001 From: Aleksandr Litovchenko Date: Thu, 5 Aug 2021 19:50:26 +0100 Subject: [PATCH] Add new line disappearing animation to LockPatternView. Instead of simply fading out the line now it disappears with moving gradient. According to the specs animation delay is 150ms and duration 500ms. Bug: 196067470 Bug: 193417780 Tests: Manually Change-Id: If4315683ba73895a0ca8287047d13da150727366 (cherry picked from commit 8392f9c4ac9505c79a4fe3c6f29ee4e24eaf4e3c) --- .../internal/widget/LockPatternView.java | 147 +++++++++++++++--- core/res/res/values/dimens.xml | 2 + core/res/res/values/symbols.xml | 1 + 3 files changed, 125 insertions(+), 25 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 3f756d7577066..e4385a697c85a 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -19,16 +19,20 @@ package com.android.internal.widget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.CanvasProperty; +import android.graphics.Color; +import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RecordingCanvas; import android.graphics.Rect; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Bundle; @@ -70,7 +74,9 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; - private static final float LINE_FADE_ALPHA_MULTIPLIER = 1.5f; + 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 final CellState[][] mCellStates; private final int mDotSize; @@ -139,6 +145,7 @@ public class LockPatternView extends View { private float mSquareWidth; @UnsupportedAppUsage private float mSquareHeight; + private final LinearGradient mFadeOutGradientShader; private final Path mCurrentPath = new Path(); private final Rect mInvalidate = new Rect(); @@ -232,7 +239,10 @@ public class LockPatternView extends View { float alpha = 1f; public float lineEndX = Float.MIN_VALUE; public float lineEndY = Float.MIN_VALUE; - public ValueAnimator lineAnimator; + @Nullable + ValueAnimator lineEndAnimator; + @Nullable + ValueAnimator lineDisappearingAnimator; } /** @@ -361,6 +371,14 @@ public class LockPatternView extends View { mExploreByTouchHelper = new PatternExploreByTouchHelper(this); setAccessibilityDelegate(mExploreByTouchHelper); mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + + int fadeAwayGradientWidth = getResources().getDimensionPixelSize( + R.dimen.lock_pattern_fade_away_gradient_width); + // Set up gradient shader with the middle in point (0, 0). + mFadeOutGradientShader = new LinearGradient(/* x0= */ -fadeAwayGradientWidth / 2f, + /* y0= */ 0,/* x1= */ fadeAwayGradientWidth / 2f, /* y1= */ 0, + Color.TRANSPARENT, pathColor, Shader.TileMode.CLAMP); + a.recycle(); } @@ -791,8 +809,13 @@ public class LockPatternView extends View { }); startLineEndAnimation(cellState, mInProgressX, mInProgressY, getCenterXForColumn(cell.column), getCenterYForRow(cell.row)); + startLineDisappearingAnimation(cellState); } + /** + * 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, final float startX, final float startY, final float targetX, final float targetY) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); @@ -808,13 +831,37 @@ public class LockPatternView extends View { valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - state.lineAnimator = null; + state.lineEndAnimator = null; } }); valueAnimator.setInterpolator(mFastOutSlowInInterpolator); - valueAnimator.setDuration(100); + valueAnimator.setDuration(LINE_END_ANIMATION_DURATION_MILLIS); valueAnimator.start(); - state.lineAnimator = valueAnimator; + state.lineEndAnimator = 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) { + 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.setStartDelay(LINE_FADE_OUT_DELAY_MILLIS); + valueAnimator.setDuration(LINE_FADE_OUT_DURATION_MILLIS); + valueAnimator.start(); + state.lineDisappearingAnimator = valueAnimator; } private void startRadiusAnimation(float start, float end, long duration, @@ -1051,11 +1098,16 @@ 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.lineAnimator != null) { - state.lineAnimator.cancel(); + if (state.lineEndAnimator != null) { + state.lineEndAnimator.cancel(); + state.lineEndAnimator = null; state.lineEndX = Float.MIN_VALUE; state.lineEndY = Float.MIN_VALUE; } + if (state.lineDisappearingAnimator != null) { + state.lineDisappearingAnimator.cancel(); + state.lineDisappearingAnimator = null; + } } } } @@ -1195,29 +1247,19 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); if (i != 0) { - // Set this line segment to fade away animated. - int lineFadeVal = (int) Math.min((elapsedRealtime - - mLineFadeStart[i]) * LINE_FADE_ALPHA_MULTIPLIER, 255f); - CellState state = mCellStates[cell.row][cell.column]; currentPath.rewind(); - currentPath.moveTo(lastX, lastY); + float endX; + float endY; if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) { - currentPath.lineTo(state.lineEndX, state.lineEndY); - if (mFadePattern) { - mPathPaint.setAlpha((int) 255 - lineFadeVal ); - } else { - mPathPaint.setAlpha(255); - } + endX = state.lineEndX; + endY = state.lineEndY; } else { - currentPath.lineTo(centerX, centerY); - if (mFadePattern) { - mPathPaint.setAlpha((int) 255 - lineFadeVal ); - } else { - mPathPaint.setAlpha(255); - } + endX = centerX; + endY = centerY; } - canvas.drawPath(currentPath, mPathPaint); + drawLineSegment(canvas, /* startX = */ lastX, /* startY = */ lastY, endX, endY, + mLineFadeStart[i], elapsedRealtime); } lastX = centerX; lastY = centerY; @@ -1260,6 +1302,61 @@ public class LockPatternView extends View { } } + private void drawLineSegment(Canvas canvas, float startX, float startY, float endX, float endY, + long lineFadeStart, long elapsedRealtime) { + float fadeAwayProgress; + if (mFadePattern) { + if (elapsedRealtime - lineFadeStart + >= LINE_FADE_OUT_DELAY_MILLIS + LINE_FADE_OUT_DURATION_MILLIS) { + // Time for this segment animation is out so we don't need to draw it. + return; + } + // Set this line segment to fade away animated. + fadeAwayProgress = Math.max( + ((float) (elapsedRealtime - lineFadeStart - LINE_FADE_OUT_DELAY_MILLIS)) + / LINE_FADE_OUT_DURATION_MILLIS, 0f); + drawFadingAwayLineSegment(canvas, startX, startY, endX, endY, fadeAwayProgress); + } else { + mPathPaint.setAlpha(255); + canvas.drawLine(startX, startY, endX, endY, mPathPaint); + } + } + + private void drawFadingAwayLineSegment(Canvas canvas, float startX, float startY, float endX, + float endY, float fadeAwayProgress) { + mPathPaint.setAlpha((int) (255 * (1 - fadeAwayProgress))); + + // To draw gradient segment we use mFadeOutGradientShader which has immutable coordinates + // thus we will need to translate and rotate the canvas. + mPathPaint.setShader(mFadeOutGradientShader); + canvas.save(); + + // First translate canvas to gradient middle point. + float gradientMidX = endX * fadeAwayProgress + startX * (1 - fadeAwayProgress); + float gradientMidY = endY * fadeAwayProgress + startY * (1 - fadeAwayProgress); + canvas.translate(gradientMidX, gradientMidY); + + // Then rotate it to the direction of the segment. + double segmentAngleRad = Math.atan((endY - startY) / (endX - startX)); + float segmentAngleDegrees = (float) Math.toDegrees(segmentAngleRad); + if (endX - startX < 0) { + // Arc tangent gives us angle degrees [-90; 90] thus to cover [90; 270] degrees we + // need this hack. + segmentAngleDegrees += 180f; + } + canvas.rotate(segmentAngleDegrees); + + // Pythagoras theorem. + float segmentLength = (float) Math.hypot(endX - startX, endY - startY); + + // Draw the segment in coordinates aligned with shader coordinates. + canvas.drawLine(/* startX= */ -segmentLength * fadeAwayProgress, /* startY= */ + 0,/* stopX= */ segmentLength * (1 - fadeAwayProgress), /* stopY= */ 0, mPathPaint); + + canvas.restore(); + mPathPaint.setShader(null); + } + private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) { float diffX = x - lastX; float diffY = y - lastY; diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e8bb6067932ec..610c5910b6c18 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -641,6 +641,8 @@ 22dp 14dp 30dp + + 8dp 40dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b2f7e8227e7d0..7da5045f05823 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1318,6 +1318,7 @@ +