• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep.util;
17 
18 import android.animation.Animator;
19 import android.annotation.ColorInt;
20 import android.annotation.Nullable;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.view.animation.Interpolator;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Px;
28 
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.anim.AnimatedFloat;
31 import com.android.launcher3.anim.AnimatorListeners;
32 import com.android.launcher3.anim.Interpolators;
33 
34 /**
35  * Utility class for drawing a rounded-rect border around a view.
36  * <p>
37  * To use this class:
38  * 1. Create an instance in the target view.
39  * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
40  *      {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
41  * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
42  *      {@link BorderAnimator#setBorderVisible(boolean)} where appropriate.
43  */
44 public final class BorderAnimator {
45 
46     public static final int DEFAULT_BORDER_COLOR = 0xffffffff;
47 
48     private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
49     private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
50     private static final Interpolator DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
51 
52     @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
53             this::updateOutline);
54     @NonNull private final Rect mBorderBounds = new Rect();
55     @NonNull private final BorderBoundsBuilder mBorderBoundsBuilder;
56     @Px private final int mBorderWidthPx;
57     @Px private final int mBorderRadiusPx;
58     @NonNull private final Runnable mInvalidateViewCallback;
59     private final long mAppearanceDurationMs;
60     private final long mDisappearanceDurationMs;
61     @NonNull private final Interpolator mInterpolator;
62     @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
63 
64     private int mAlignmentAdjustment;
65 
66     @Nullable private Animator mRunningBorderAnimation;
67 
BorderAnimator( @onNull BorderBoundsBuilder borderBoundsBuilder, int borderWidthPx, int borderRadiusPx, @ColorInt int borderColor, @NonNull Runnable invalidateViewCallback)68     public BorderAnimator(
69             @NonNull BorderBoundsBuilder borderBoundsBuilder,
70             int borderWidthPx,
71             int borderRadiusPx,
72             @ColorInt int borderColor,
73             @NonNull Runnable invalidateViewCallback) {
74         this(borderBoundsBuilder,
75                 borderWidthPx,
76                 borderRadiusPx,
77                 borderColor,
78                 invalidateViewCallback,
79                 DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
80                 DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
81                 DEFAULT_INTERPOLATOR);
82     }
83 
BorderAnimator( @onNull BorderBoundsBuilder borderBoundsBuilder, int borderWidthPx, int borderRadiusPx, @ColorInt int borderColor, @NonNull Runnable invalidateViewCallback, long appearanceDurationMs, long disappearanceDurationMs, @NonNull Interpolator interpolator)84     public BorderAnimator(
85             @NonNull BorderBoundsBuilder borderBoundsBuilder,
86             int borderWidthPx,
87             int borderRadiusPx,
88             @ColorInt int borderColor,
89             @NonNull Runnable invalidateViewCallback,
90             long appearanceDurationMs,
91             long disappearanceDurationMs,
92             @NonNull Interpolator interpolator) {
93         mBorderBoundsBuilder = borderBoundsBuilder;
94         mBorderWidthPx = borderWidthPx;
95         mBorderRadiusPx = borderRadiusPx;
96         mInvalidateViewCallback = invalidateViewCallback;
97         mAppearanceDurationMs = appearanceDurationMs;
98         mDisappearanceDurationMs = disappearanceDurationMs;
99         mInterpolator = interpolator;
100 
101         mBorderPaint.setColor(borderColor);
102         mBorderPaint.setStyle(Paint.Style.STROKE);
103         mBorderPaint.setAlpha(0);
104     }
105 
updateOutline()106     private void updateOutline() {
107         float interpolatedProgress = mInterpolator.getInterpolation(
108                 mBorderAnimationProgress.value);
109         mAlignmentAdjustment = (int) Utilities.mapBoundToRange(
110                 mBorderAnimationProgress.value,
111                 /* lowerBound= */ 0f,
112                 /* upperBound= */ 1f,
113                 /* toMin= */ 0f,
114                 /* toMax= */ (float) (mBorderWidthPx / 2f),
115                 mInterpolator);
116 
117         mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
118         mBorderPaint.setStrokeWidth(Math.round(mBorderWidthPx * interpolatedProgress));
119         mInvalidateViewCallback.run();
120     }
121 
122     /**
123      * Draws the border on the given canvas.
124      * <p>
125      * Call this method in the target view's {@link android.view.View#draw(Canvas)} method after
126      * calling super.
127      */
drawBorder(Canvas canvas)128     public void drawBorder(Canvas canvas) {
129         canvas.drawRoundRect(
130                 /* left= */ mBorderBounds.left + mAlignmentAdjustment,
131                 /* top= */ mBorderBounds.top + mAlignmentAdjustment,
132                 /* right= */ mBorderBounds.right - mAlignmentAdjustment,
133                 /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
134                 /* rx= */ mBorderRadiusPx - mAlignmentAdjustment,
135                 /* ry= */ mBorderRadiusPx - mAlignmentAdjustment,
136                 /* paint= */ mBorderPaint);
137     }
138 
139     /**
140      * Builds the border appearance/disappearance animation.
141      */
142     @NonNull
buildAnimator(boolean isAppearing)143     public Animator buildAnimator(boolean isAppearing) {
144         mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
145         mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
146         mRunningBorderAnimation.setDuration(
147                 isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
148 
149         mRunningBorderAnimation.addListener(
150                 AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null));
151 
152         return mRunningBorderAnimation;
153     }
154 
155     /**
156      * Immediately shows/hides the border without an animation.
157      *
158      * To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
159      */
setBorderVisible(boolean visible)160     public void setBorderVisible(boolean visible) {
161         if (mRunningBorderAnimation != null) {
162             mRunningBorderAnimation.end();
163         }
164         mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
165     }
166 
167     /**
168      * Callback to update the border bounds when building this animation.
169      */
170     public interface BorderBoundsBuilder {
171 
172         /**
173          * Sets the given rect to the most up-to-date bounds.
174          */
updateBorderBounds(Rect rect)175         void updateBorderBounds(Rect rect);
176     }
177 }
178