Merge "Combine UDFPS enroll segments into a single circle" into sc-v2-dev am: e1d531787c
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16200850 Change-Id: I0e1f370dad1d7a6806c986267a664b7b57c77e39
This commit is contained in:
35
packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
Normal file
35
packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="54dp"
|
||||
android:viewportWidth="54"
|
||||
android:viewportHeight="54">
|
||||
<path
|
||||
android:pathData="M26.9999,3.9619C39.7029,3.9619 50.0369,14.2969 50.0369,26.9999C50.0369,39.7029 39.7029,50.0379 26.9999,50.0379C14.2969,50.0379 3.9629,39.7029 3.9629,26.9999C3.9629,14.2969 14.2969,3.9619 26.9999,3.9619Z"
|
||||
android:fillColor="?android:colorBackground"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
|
||||
android:fillColor="@color/udfps_enroll_progress"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
|
||||
android:fillColor="@color/udfps_enroll_progress"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -16,17 +16,22 @@
|
||||
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* UDFPS enrollment progress bar.
|
||||
@@ -34,108 +39,193 @@ import java.util.List;
|
||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||
private static final String TAG = "UdfpsProgressBar";
|
||||
|
||||
private static final float SEGMENT_GAP_ANGLE = 12f;
|
||||
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
||||
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
|
||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||
private static final float STROKE_WIDTH_DP = 12f;
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
private final float mStrokeWidthPx;
|
||||
@ColorInt private final int mProgressColor;
|
||||
@ColorInt private final int mHelpColor;
|
||||
@NonNull private final Drawable mCheckmarkDrawable;
|
||||
@NonNull private final Interpolator mCheckmarkInterpolator;
|
||||
@NonNull private final Paint mBackgroundPaint;
|
||||
@NonNull private final Paint mFillPaint;
|
||||
|
||||
@Nullable private UdfpsEnrollHelper mEnrollHelper;
|
||||
@NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
|
||||
private boolean mAfterFirstTouch;
|
||||
|
||||
private int mRemainingSteps = 0;
|
||||
private int mTotalSteps = 0;
|
||||
private int mProgressSteps = 0;
|
||||
private boolean mIsShowingHelp = false;
|
||||
private float mProgress = 0f;
|
||||
@Nullable private ValueAnimator mProgressAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||
|
||||
private boolean mShowingHelp = false;
|
||||
@Nullable private ValueAnimator mFillColorAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||
|
||||
private boolean mComplete = false;
|
||||
private float mCheckmarkScale = 0f;
|
||||
@Nullable private ValueAnimator mCheckmarkAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
||||
|
||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
|
||||
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
|
||||
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
|
||||
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
||||
mCheckmarkDrawable.mutate();
|
||||
mCheckmarkInterpolator = new OvershootInterpolator();
|
||||
|
||||
void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
|
||||
mEnrollHelper = enrollHelper;
|
||||
if (enrollHelper != null) {
|
||||
final int stageCount = enrollHelper.getStageCount();
|
||||
mSegments = new ArrayList<>(stageCount);
|
||||
float startAngle = SEGMENT_GAP_ANGLE / 2f;
|
||||
final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
|
||||
final Runnable invalidateRunnable = this::invalidateSelf;
|
||||
for (int index = 0; index < stageCount; index++) {
|
||||
mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
|
||||
sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
|
||||
startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
|
||||
}
|
||||
invalidateSelf();
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
// Set background paint color and alpha.
|
||||
final int[] attrs = new int[] {android.R.attr.colorControlNormal};
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
|
||||
try {
|
||||
@ColorInt final int tintColor = typedArray.getColor(0, mBackgroundPaint.getColor());
|
||||
mBackgroundPaint.setColor(tintColor);
|
||||
} finally {
|
||||
typedArray.recycle();
|
||||
}
|
||||
TypedValue alpha = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
|
||||
mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
|
||||
|
||||
// Progress fill should *not* use the extracted system color.
|
||||
mFillPaint = new Paint();
|
||||
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mFillPaint.setColor(mProgressColor);
|
||||
mFillPaint.setAntiAlias(true);
|
||||
mFillPaint.setStyle(Paint.Style.STROKE);
|
||||
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mProgressUpdateListener = animation -> {
|
||||
mProgress = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mFillColorUpdateListener = animation -> {
|
||||
mFillPaint.setColor((int) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mCheckmarkUpdateListener = animation -> {
|
||||
mCheckmarkScale = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||
mTotalSteps = totalSteps;
|
||||
|
||||
// Show some progress for the initial touch.
|
||||
updateState(Math.max(1, totalSteps - remaining), false /* isShowingHelp */);
|
||||
mAfterFirstTouch = true;
|
||||
updateState(remaining, totalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||
updateState(Math.max(0, totalSteps - remaining), true /* isShowingHelp */);
|
||||
updateState(remaining, totalSteps, true /* showingHelp */);
|
||||
}
|
||||
|
||||
void onLastStepAcquired() {
|
||||
updateState(mTotalSteps, false /* isShowingHelp */);
|
||||
updateState(0, mTotalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
private void updateState(int progressSteps, boolean isShowingHelp) {
|
||||
updateProgress(progressSteps);
|
||||
updateFillColor(isShowingHelp);
|
||||
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||
updateProgress(remainingSteps, totalSteps);
|
||||
updateFillColor(showingHelp);
|
||||
}
|
||||
|
||||
private void updateProgress(int progressSteps) {
|
||||
if (mProgressSteps == progressSteps) {
|
||||
private void updateProgress(int remainingSteps, int totalSteps) {
|
||||
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
||||
return;
|
||||
}
|
||||
mProgressSteps = progressSteps;
|
||||
mRemainingSteps = remainingSteps;
|
||||
mTotalSteps = totalSteps;
|
||||
|
||||
if (mEnrollHelper == null) {
|
||||
Log.e(TAG, "updateState: UDFPS enroll helper was null");
|
||||
return;
|
||||
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
||||
|
||||
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
||||
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
||||
|
||||
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
||||
|
||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||
mProgressAnimator.cancel();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
int prevThreshold = 0;
|
||||
while (index < mSegments.size()) {
|
||||
final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
|
||||
final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
|
||||
if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
|
||||
segment.updateProgress(1f);
|
||||
break;
|
||||
} else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
|
||||
final int relativeSteps = progressSteps - prevThreshold;
|
||||
final int relativeThreshold = thresholdSteps - prevThreshold;
|
||||
final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
|
||||
segment.updateProgress(segmentProgress);
|
||||
break;
|
||||
}
|
||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
||||
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||
mProgressAnimator.start();
|
||||
|
||||
index++;
|
||||
prevThreshold = thresholdSteps;
|
||||
}
|
||||
|
||||
if (progressSteps >= mTotalSteps) {
|
||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||
segment.startCompletionAnimation();
|
||||
}
|
||||
} else {
|
||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||
segment.cancelCompletionAnimation();
|
||||
}
|
||||
if (remainingSteps == 0) {
|
||||
startCompletionAnimation();
|
||||
} else if (remainingSteps > 0) {
|
||||
rollBackCompletionAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFillColor(boolean isShowingHelp) {
|
||||
if (mIsShowingHelp == isShowingHelp) {
|
||||
private void updateFillColor(boolean showingHelp) {
|
||||
if (mShowingHelp == showingHelp) {
|
||||
return;
|
||||
}
|
||||
mIsShowingHelp = isShowingHelp;
|
||||
mShowingHelp = showingHelp;
|
||||
|
||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||
segment.updateFillColor(isShowingHelp);
|
||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||
mFillColorAnimator.cancel();
|
||||
}
|
||||
|
||||
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
||||
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||
mFillColorAnimator.start();
|
||||
}
|
||||
|
||||
private void startCompletionAnimation() {
|
||||
if (mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = true;
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
||||
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
||||
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
||||
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
private void rollBackCompletionAnimation() {
|
||||
if (!mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = false;
|
||||
|
||||
// Adjust duration based on how much of the completion animation has played.
|
||||
final float animatedFraction = mCheckmarkAnimator != null
|
||||
? mCheckmarkAnimator.getAnimatedFraction()
|
||||
: 0f;
|
||||
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
||||
mCheckmarkAnimator.setDuration(durationMs);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,12 +235,55 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||
// Progress starts from the top, instead of the right
|
||||
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
||||
|
||||
// Draw each of the enroll segments.
|
||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||
segment.draw(canvas);
|
||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||
|
||||
if (mProgress < 1f) {
|
||||
// Draw the background color of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (mProgress > 0f) {
|
||||
// Draw the filled portion of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f * mProgress /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mFillPaint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
if (mCheckmarkScale > 0f) {
|
||||
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
||||
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerX = getBounds().centerX() + centerXOffset;
|
||||
final float centerY = getBounds().centerY() + centerYOffset;
|
||||
|
||||
final float boundsXOffset =
|
||||
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
||||
final float boundsYOffset =
|
||||
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
||||
|
||||
final int left = Math.round(centerX - boundsXOffset);
|
||||
final int top = Math.round(centerY - boundsYOffset);
|
||||
final int right = Math.round(centerX + boundsXOffset);
|
||||
final int bottom = Math.round(centerY + boundsYOffset);
|
||||
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
||||
mCheckmarkDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.android.systemui.biometrics;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* A single segment of the UDFPS enrollment progress bar.
|
||||
*/
|
||||
public class UdfpsEnrollProgressBarSegment {
|
||||
private static final String TAG = "UdfpsProgressBarSegment";
|
||||
|
||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
|
||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||
private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
|
||||
private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
|
||||
|
||||
private static final float STROKE_WIDTH_DP = 12f;
|
||||
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@NonNull private final Rect mBounds;
|
||||
@NonNull private final Runnable mInvalidateRunnable;
|
||||
private final float mStartAngle;
|
||||
private final float mSweepAngle;
|
||||
private final float mMaxOverSweepAngle;
|
||||
private final float mStrokeWidthPx;
|
||||
@ColorInt private final int mProgressColor;
|
||||
@ColorInt private final int mHelpColor;
|
||||
|
||||
@NonNull private final Paint mBackgroundPaint;
|
||||
@NonNull private final Paint mProgressPaint;
|
||||
|
||||
private float mProgress = 0f;
|
||||
private float mAnimatedProgress = 0f;
|
||||
@Nullable private ValueAnimator mProgressAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||
|
||||
private boolean mIsShowingHelp = false;
|
||||
@Nullable private ValueAnimator mFillColorAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||
|
||||
private float mOverSweepAngle = 0f;
|
||||
@Nullable private ValueAnimator mOverSweepAnimator;
|
||||
@Nullable private ValueAnimator mOverSweepReverseAnimator;
|
||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
|
||||
@NonNull private final Runnable mOverSweepAnimationRunnable;
|
||||
|
||||
public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
|
||||
float startAngle, float sweepAngle, float maxOverSweepAngle,
|
||||
@NonNull Runnable invalidateRunnable) {
|
||||
|
||||
mBounds = bounds;
|
||||
mInvalidateRunnable = invalidateRunnable;
|
||||
mStartAngle = startAngle;
|
||||
mSweepAngle = sweepAngle;
|
||||
mMaxOverSweepAngle = maxOverSweepAngle;
|
||||
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
|
||||
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
|
||||
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
|
||||
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
// Background paint color + alpha
|
||||
final int[] attrs = new int[] {android.R.attr.colorControlNormal};
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs);
|
||||
@ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
|
||||
mBackgroundPaint.setColor(tintColor);
|
||||
ta.recycle();
|
||||
TypedValue alpha = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
|
||||
mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
|
||||
|
||||
// Progress should not be color extracted
|
||||
mProgressPaint = new Paint();
|
||||
mProgressPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mProgressPaint.setColor(mProgressColor);
|
||||
mProgressPaint.setAntiAlias(true);
|
||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mProgressUpdateListener = animation -> {
|
||||
mAnimatedProgress = (float) animation.getAnimatedValue();
|
||||
mInvalidateRunnable.run();
|
||||
};
|
||||
|
||||
mFillColorUpdateListener = animation -> {
|
||||
mProgressPaint.setColor((int) animation.getAnimatedValue());
|
||||
mInvalidateRunnable.run();
|
||||
};
|
||||
|
||||
mOverSweepUpdateListener = animation -> {
|
||||
mOverSweepAngle = (float) animation.getAnimatedValue();
|
||||
mInvalidateRunnable.run();
|
||||
};
|
||||
mOverSweepAnimationRunnable = () -> {
|
||||
if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
|
||||
mOverSweepAnimator.cancel();
|
||||
}
|
||||
mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
|
||||
mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
|
||||
mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
|
||||
mOverSweepAnimator.start();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws this segment to the given canvas.
|
||||
*/
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||
|
||||
if (mAnimatedProgress < 1f) {
|
||||
// Draw the unfilled background color of the segment.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
mBounds.right - halfPaddingPx,
|
||||
mBounds.bottom - halfPaddingPx,
|
||||
mStartAngle,
|
||||
mSweepAngle,
|
||||
false /* useCenter */,
|
||||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (mAnimatedProgress > 0f) {
|
||||
// Draw the filled progress portion of the segment.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
mBounds.right - halfPaddingPx,
|
||||
mBounds.bottom - halfPaddingPx,
|
||||
mStartAngle,
|
||||
mSweepAngle * mAnimatedProgress + mOverSweepAngle,
|
||||
false /* useCenter */,
|
||||
mProgressPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
|
||||
* animated, returns the value it is animating to.
|
||||
*/
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fill progress of this segment, animating if necessary.
|
||||
*
|
||||
* @param progress The new fill progress, in the range [0, 1].
|
||||
*/
|
||||
public void updateProgress(float progress) {
|
||||
updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
|
||||
}
|
||||
|
||||
private void updateProgress(float progress, long animationDurationMs) {
|
||||
if (mProgress == progress) {
|
||||
return;
|
||||
}
|
||||
mProgress = progress;
|
||||
|
||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||
mProgressAnimator.cancel();
|
||||
}
|
||||
|
||||
mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
|
||||
mProgressAnimator.setDuration(animationDurationMs);
|
||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||
mProgressAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fill color of this segment, animating if necessary.
|
||||
*
|
||||
* @param isShowingHelp Whether fill color should indicate that a help message is being shown.
|
||||
*/
|
||||
public void updateFillColor(boolean isShowingHelp) {
|
||||
if (mIsShowingHelp == isShowingHelp) {
|
||||
return;
|
||||
}
|
||||
mIsShowingHelp = isShowingHelp;
|
||||
|
||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||
mFillColorAnimator.cancel();
|
||||
}
|
||||
|
||||
@ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
|
||||
mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
|
||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||
mFillColorAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues and runs the completion animation for this segment.
|
||||
*/
|
||||
public void startCompletionAnimation() {
|
||||
final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
|
||||
if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
|
||||
Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
|
||||
+ ", mOverSweepAngle = " + mOverSweepAngle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset sweep angle back to zero if the animation is being rolled back.
|
||||
if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
|
||||
mOverSweepReverseAnimator.cancel();
|
||||
mOverSweepAngle = 0f;
|
||||
}
|
||||
|
||||
// Clear help color and start filling the segment if it isn't already.
|
||||
if (mAnimatedProgress < 1f) {
|
||||
updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
|
||||
updateFillColor(false /* isShowingHelp */);
|
||||
}
|
||||
|
||||
// Queue the animation to run after fill completes.
|
||||
mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels (and reverses, if necessary) a queued or running completion animation.
|
||||
*/
|
||||
public void cancelCompletionAnimation() {
|
||||
// Cancel the animation if it's queued or running.
|
||||
mHandler.removeCallbacks(mOverSweepAnimationRunnable);
|
||||
if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
|
||||
mOverSweepAnimator.cancel();
|
||||
}
|
||||
|
||||
// Roll back the animation if it has at least partially run.
|
||||
if (mOverSweepAngle > 0f) {
|
||||
if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
|
||||
mOverSweepReverseAnimator.cancel();
|
||||
}
|
||||
|
||||
final float completion = mOverSweepAngle / mMaxOverSweepAngle;
|
||||
final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
|
||||
mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
|
||||
mOverSweepReverseAnimator.setDuration(proratedDuration);
|
||||
mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
|
||||
mOverSweepReverseAnimator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,6 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
|
||||
}
|
||||
|
||||
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
||||
mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
|
||||
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user