• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.systemui.navigationbar.gestural;
18 
19 import android.animation.ArgbEvaluator;
20 import android.animation.ObjectAnimator;
21 import android.annotation.ColorInt;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.drawable.Drawable;
27 import android.util.AttributeSet;
28 import android.util.FloatProperty;
29 import android.view.ContextThemeWrapper;
30 import android.view.View;
31 import android.view.animation.Interpolator;
32 
33 import com.android.app.animation.Interpolators;
34 import com.android.settingslib.Utils;
35 import com.android.systemui.navigationbar.buttons.ButtonInterface;
36 import com.android.systemui.res.R;
37 
38 public class NavigationHandle extends View implements ButtonInterface {
39 
40     protected final Paint mPaint = new Paint();
41     private @ColorInt final int mLightColor;
42     private @ColorInt final int mDarkColor;
43     protected final float mRadius;
44     protected final float mBottom;
45     private final float mAdditionalWidthForAnimation;
46     private final float mAdditionalHeightForAnimation;
47     private final float mShrinkWidthForAnimation;
48     private boolean mRequiresInvalidate;
49     private boolean mShrink;
50 
51     private ObjectAnimator mPulseAnimator = null;
52     private float mPulseAnimationProgress;
53 
54     private static final FloatProperty<NavigationHandle> PULSE_ANIMATION_PROGRESS =
55             new FloatProperty<>("pulseAnimationProgress") {
56                 @Override
57                 public Float get(NavigationHandle controller) {
58                     return controller.getPulseAnimationProgress();
59                 }
60 
61                 @Override
62                 public void setValue(NavigationHandle controller, float progress) {
63                     controller.setPulseAnimationProgress(progress);
64                 }
65             };
66 
NavigationHandle(Context context)67     public NavigationHandle(Context context) {
68         this(context, null);
69     }
70 
NavigationHandle(Context context, AttributeSet attr)71     public NavigationHandle(Context context, AttributeSet attr) {
72         super(context, attr);
73         final Resources res = context.getResources();
74         mRadius = res.getDimension(R.dimen.navigation_handle_radius);
75         mBottom = res.getDimension(R.dimen.navigation_handle_bottom);
76         mAdditionalWidthForAnimation =
77                 res.getDimension(R.dimen.navigation_home_handle_additional_width_for_animation);
78         mAdditionalHeightForAnimation =
79                 res.getDimension(R.dimen.navigation_home_handle_additional_height_for_animation);
80         mShrinkWidthForAnimation =
81                 res.getDimension(R.dimen.navigation_home_handle_shrink_width_for_animation);
82 
83         final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
84         final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
85         Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme);
86         Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme);
87         mLightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.homeHandleColor);
88         mDarkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.homeHandleColor);
89         mPaint.setAntiAlias(true);
90         setFocusable(false);
91     }
92 
93     @Override
setAlpha(float alpha)94     public void setAlpha(float alpha) {
95         super.setAlpha(alpha);
96         if (alpha > 0f && mRequiresInvalidate) {
97             mRequiresInvalidate = false;
98             invalidate();
99         }
100     }
101 
102     @Override
onDraw(Canvas canvas)103     protected void onDraw(Canvas canvas) {
104         super.onDraw(canvas);
105 
106         // Draw that bar
107         int navHeight = getHeight();
108         float additionalHeight;
109         float additionalWidth;
110         if (mShrink) {
111             additionalHeight = 0;
112             additionalWidth = -mShrinkWidthForAnimation * mPulseAnimationProgress;
113         } else {
114             additionalHeight = mAdditionalHeightForAnimation * mPulseAnimationProgress;
115             additionalWidth = mAdditionalWidthForAnimation * mPulseAnimationProgress;
116         }
117 
118         float height = mRadius * 2 + additionalHeight;
119         float width = getWidth() + additionalWidth;
120         float x = -additionalWidth;
121         float y = navHeight - mBottom - height + (additionalHeight / 2);
122         float adjustedRadius = height / 2;
123         canvas.drawRoundRect(x, y, width, y + height, adjustedRadius, adjustedRadius, mPaint);
124     }
125 
126     @Override
setImageDrawable(Drawable drawable)127     public void setImageDrawable(Drawable drawable) {}
128 
129     @Override
abortCurrentGesture()130     public void abortCurrentGesture() {}
131 
132     @Override
setVertical(boolean vertical)133     public void setVertical(boolean vertical) {}
134 
135     @Override
setDarkIntensity(float intensity)136     public void setDarkIntensity(float intensity) {
137         int color = (int) ArgbEvaluator.getInstance().evaluate(intensity, mLightColor, mDarkColor);
138         if (mPaint.getColor() != color) {
139             mPaint.setColor(color);
140             if (getVisibility() == VISIBLE && getAlpha() > 0) {
141                 invalidate();
142             } else {
143                 // If we are currently invisible, then invalidate when we are next made visible
144                 mRequiresInvalidate = true;
145             }
146         }
147     }
148 
149     @Override
setDelayTouchFeedback(boolean shouldDelay)150     public void setDelayTouchFeedback(boolean shouldDelay) {}
151 
152     @Override
animateLongPress(boolean isTouchDown, boolean shrink, long durationMs)153     public void animateLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
154         if (mPulseAnimator != null) {
155             mPulseAnimator.cancel();
156         }
157 
158         mShrink = shrink;
159         Interpolator interpolator;
160         if (shrink) {
161             interpolator = Interpolators.LEGACY_DECELERATE;
162         } else {
163             if (isTouchDown) {
164                 // For now we animate the navbar expanding and contracting so that the navbar is
165                 // the original size by the end of {@code duration}. This is because a screenshot
166                 // is taken at that point and we don't want to capture the larger navbar.
167                 // TODO(b/306400785): Determine a way to exclude navbar from the screenshot.
168 
169                 // Fraction of the touch down animation to expand; remaining is used to contract
170                 // again.
171                 float expandFraction = 0.9f;
172                 interpolator = t -> t <= expandFraction
173                         ? Interpolators.clampToProgress(Interpolators.LEGACY, t, 0, expandFraction)
174                         : 1 - Interpolators.clampToProgress(
175                                 Interpolators.LINEAR, t, expandFraction, 1);
176             } else {
177                 interpolator = Interpolators.LEGACY_DECELERATE;
178             }
179         }
180 
181         mPulseAnimator =
182                 ObjectAnimator.ofFloat(this, PULSE_ANIMATION_PROGRESS, isTouchDown ? 1 : 0);
183         mPulseAnimator.setDuration(durationMs).setInterpolator(interpolator);
184         mPulseAnimator.start();
185     }
186 
setPulseAnimationProgress(float pulseAnimationProgress)187     private void setPulseAnimationProgress(float pulseAnimationProgress) {
188         mPulseAnimationProgress = pulseAnimationProgress;
189         invalidate();
190     }
191 
getPulseAnimationProgress()192     private float getPulseAnimationProgress() {
193         return mPulseAnimationProgress;
194     }
195 }
196