• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.statusbar;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ArgbEvaluator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.Paint;
28 import android.graphics.PorterDuff;
29 import android.graphics.drawable.Drawable;
30 import android.util.AttributeSet;
31 import android.view.View;
32 import android.view.ViewAnimationUtils;
33 import android.view.animation.AnimationUtils;
34 import android.view.animation.Interpolator;
35 import android.widget.ImageView;
36 import com.android.systemui.R;
37 
38 /**
39  * An ImageView which does not have overlapping renderings commands and therefore does not need a
40  * layer when alpha is changed.
41  */
42 public class KeyguardAffordanceView extends ImageView {
43 
44     private static final long CIRCLE_APPEAR_DURATION = 80;
45     private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
46     private static final long NORMAL_ANIMATION_DURATION = 200;
47     public static final float MAX_ICON_SCALE_AMOUNT = 1.5f;
48     public static final float MIN_ICON_SCALE_AMOUNT = 0.8f;
49 
50     private final int mMinBackgroundRadius;
51     private final Paint mCirclePaint;
52     private final Interpolator mAppearInterpolator;
53     private final Interpolator mDisappearInterpolator;
54     private final int mInverseColor;
55     private final int mNormalColor;
56     private final ArgbEvaluator mColorInterpolator;
57     private final FlingAnimationUtils mFlingAnimationUtils;
58     private final Drawable mArrowDrawable;
59     private final int mHintChevronPadding;
60     private float mCircleRadius;
61     private int mCenterX;
62     private int mCenterY;
63     private ValueAnimator mCircleAnimator;
64     private ValueAnimator mAlphaAnimator;
65     private ValueAnimator mScaleAnimator;
66     private ValueAnimator mArrowAnimator;
67     private float mCircleStartValue;
68     private boolean mCircleWillBeHidden;
69     private int[] mTempPoint = new int[2];
70     private float mImageScale;
71     private int mCircleColor;
72     private boolean mIsLeft;
73     private float mArrowAlpha = 0.0f;
74     private View mPreviewView;
75     private float mCircleStartRadius;
76     private float mMaxCircleSize;
77     private Animator mPreviewClipper;
78     private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
79         @Override
80         public void onAnimationEnd(Animator animation) {
81             mPreviewClipper = null;
82         }
83     };
84     private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
85         @Override
86         public void onAnimationEnd(Animator animation) {
87             mCircleAnimator = null;
88         }
89     };
90     private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() {
91         @Override
92         public void onAnimationEnd(Animator animation) {
93             mScaleAnimator = null;
94         }
95     };
96     private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() {
97         @Override
98         public void onAnimationEnd(Animator animation) {
99             mAlphaAnimator = null;
100         }
101     };
102     private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() {
103         @Override
104         public void onAnimationEnd(Animator animation) {
105             mArrowAnimator = null;
106         }
107     };
108 
KeyguardAffordanceView(Context context)109     public KeyguardAffordanceView(Context context) {
110         this(context, null);
111     }
112 
KeyguardAffordanceView(Context context, AttributeSet attrs)113     public KeyguardAffordanceView(Context context, AttributeSet attrs) {
114         this(context, attrs, 0);
115     }
116 
KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr)117     public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
118         this(context, attrs, defStyleAttr, 0);
119     }
120 
KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)121     public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
122             int defStyleRes) {
123         super(context, attrs, defStyleAttr, defStyleRes);
124         mCirclePaint = new Paint();
125         mCirclePaint.setAntiAlias(true);
126         mCircleColor = 0xffffffff;
127         mCirclePaint.setColor(mCircleColor);
128 
129         mNormalColor = 0xffffffff;
130         mInverseColor = 0xff000000;
131         mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
132                 R.dimen.keyguard_affordance_min_background_radius);
133         mHintChevronPadding = mContext.getResources().getDimensionPixelSize(
134                 R.dimen.hint_chevron_circle_padding);
135         mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
136                 android.R.interpolator.linear_out_slow_in);
137         mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
138                 android.R.interpolator.fast_out_linear_in);
139         mColorInterpolator = new ArgbEvaluator();
140         mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
141         mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left);
142         mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(),
143                 mArrowDrawable.getIntrinsicHeight());
144     }
145 
146     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)147     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
148         super.onLayout(changed, left, top, right, bottom);
149         mCenterX = getWidth() / 2;
150         mCenterY = getHeight() / 2;
151         mMaxCircleSize = getMaxCircleSize();
152     }
153 
154     @Override
onDraw(Canvas canvas)155     protected void onDraw(Canvas canvas) {
156         drawBackgroundCircle(canvas);
157         drawArrow(canvas);
158         canvas.save();
159         canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
160         super.onDraw(canvas);
161         canvas.restore();
162     }
163 
setPreviewView(View v)164     public void setPreviewView(View v) {
165         mPreviewView = v;
166         if (mPreviewView != null) {
167             mPreviewView.setVisibility(INVISIBLE);
168         }
169     }
170 
drawArrow(Canvas canvas)171     private void drawArrow(Canvas canvas) {
172         if (mArrowAlpha > 0) {
173             canvas.save();
174             canvas.translate(mCenterX, mCenterY);
175             if (mIsLeft) {
176                 canvas.scale(-1.0f, 1.0f);
177             }
178             canvas.translate(- mCircleRadius - mHintChevronPadding
179                     - mArrowDrawable.getIntrinsicWidth() / 2,
180                     - mArrowDrawable.getIntrinsicHeight() / 2);
181             mArrowDrawable.setAlpha((int) (mArrowAlpha * 255));
182             mArrowDrawable.draw(canvas);
183             canvas.restore();
184         }
185     }
186 
updateIconColor()187     private void updateIconColor() {
188         Drawable drawable = getDrawable().mutate();
189         float alpha = mCircleRadius / mMinBackgroundRadius;
190         alpha = Math.min(1.0f, alpha);
191         int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
192         drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
193     }
194 
drawBackgroundCircle(Canvas canvas)195     private void drawBackgroundCircle(Canvas canvas) {
196         if (mCircleRadius > 0) {
197             updateCircleColor();
198             canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
199         }
200     }
201 
updateCircleColor()202     private void updateCircleColor() {
203         float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
204                 (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
205         if (mPreviewView != null) {
206             float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius)
207                     / (mMaxCircleSize - mCircleStartRadius);
208             fraction *= finishingFraction;
209         }
210         int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
211                 Color.red(mCircleColor),
212                 Color.green(mCircleColor), Color.blue(mCircleColor));
213         mCirclePaint.setColor(color);
214     }
215 
finishAnimation(float velocity, final Runnable mAnimationEndRunnable)216     public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
217         cancelAnimator(mCircleAnimator);
218         cancelAnimator(mPreviewClipper);
219         mCircleStartRadius = mCircleRadius;
220         float maxCircleSize = getMaxCircleSize();
221         ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize);
222         mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
223                 velocity, maxCircleSize);
224         animatorToRadius.addListener(new AnimatorListenerAdapter() {
225             @Override
226             public void onAnimationEnd(Animator animation) {
227                 mAnimationEndRunnable.run();
228             }
229         });
230         animatorToRadius.start();
231         setImageAlpha(0, true);
232         if (mPreviewView != null) {
233             mPreviewView.setVisibility(View.VISIBLE);
234             mPreviewClipper = ViewAnimationUtils.createCircularReveal(
235                     mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
236                     maxCircleSize);
237             mFlingAnimationUtils.applyDismissing(mPreviewClipper, mCircleRadius, maxCircleSize,
238                     velocity, maxCircleSize);
239             mPreviewClipper.addListener(mClipEndListener);
240             mPreviewClipper.start();
241         }
242     }
243 
getMaxCircleSize()244     private float getMaxCircleSize() {
245         getLocationInWindow(mTempPoint);
246         float rootWidth = getRootView().getWidth();
247         float width = mTempPoint[0] + mCenterX;
248         width = Math.max(rootWidth - width, width);
249         float height = mTempPoint[1] + mCenterY;
250         return (float) Math.hypot(width, height);
251     }
252 
setCircleRadius(float circleRadius)253     public void setCircleRadius(float circleRadius) {
254         setCircleRadius(circleRadius, false, false);
255     }
256 
setCircleRadius(float circleRadius, boolean slowAnimation)257     public void setCircleRadius(float circleRadius, boolean slowAnimation) {
258         setCircleRadius(circleRadius, slowAnimation, false);
259     }
260 
setCircleRadiusWithoutAnimation(float circleRadius)261     public void setCircleRadiusWithoutAnimation(float circleRadius) {
262         cancelAnimator(mCircleAnimator);
263         setCircleRadius(circleRadius, false ,true);
264     }
265 
setCircleRadius(float circleRadius, boolean slowAnimation, boolean noAnimation)266     private void setCircleRadius(float circleRadius, boolean slowAnimation, boolean noAnimation) {
267 
268         // Check if we need a new animation
269         boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden)
270                 || (mCircleAnimator == null && mCircleRadius == 0.0f);
271         boolean nowHidden = circleRadius == 0.0f;
272         boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation;
273         if (!radiusNeedsAnimation) {
274             if (mCircleAnimator == null) {
275                 mCircleRadius = circleRadius;
276                 updateIconColor();
277                 invalidate();
278                 if (nowHidden) {
279                     if (mPreviewView != null) {
280                         mPreviewView.setVisibility(View.INVISIBLE);
281                     }
282                 }
283             } else if (!mCircleWillBeHidden) {
284 
285                 // We just update the end value
286                 float diff = circleRadius - mMinBackgroundRadius;
287                 PropertyValuesHolder[] values = mCircleAnimator.getValues();
288                 values[0].setFloatValues(mCircleStartValue + diff, circleRadius);
289                 mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
290             }
291         } else {
292             cancelAnimator(mCircleAnimator);
293             cancelAnimator(mPreviewClipper);
294             ValueAnimator animator = getAnimatorToRadius(circleRadius);
295             Interpolator interpolator = circleRadius == 0.0f
296                     ? mDisappearInterpolator
297                     : mAppearInterpolator;
298             animator.setInterpolator(interpolator);
299             long duration = 250;
300             if (!slowAnimation) {
301                 float durationFactor = Math.abs(mCircleRadius - circleRadius)
302                         / (float) mMinBackgroundRadius;
303                 duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor);
304                 duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
305             }
306             animator.setDuration(duration);
307             animator.start();
308             if (mPreviewView != null && mPreviewView.getVisibility() == View.VISIBLE) {
309                 mPreviewView.setVisibility(View.VISIBLE);
310                 mPreviewClipper = ViewAnimationUtils.createCircularReveal(
311                         mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
312                         circleRadius);
313                 mPreviewClipper.setInterpolator(interpolator);
314                 mPreviewClipper.setDuration(duration);
315                 mPreviewClipper.addListener(mClipEndListener);
316                 mPreviewClipper.addListener(new AnimatorListenerAdapter() {
317                     @Override
318                     public void onAnimationEnd(Animator animation) {
319                         mPreviewView.setVisibility(View.INVISIBLE);
320                     }
321                 });
322                 mPreviewClipper.start();
323             }
324         }
325     }
326 
getAnimatorToRadius(float circleRadius)327     private ValueAnimator getAnimatorToRadius(float circleRadius) {
328         ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius);
329         mCircleAnimator = animator;
330         mCircleStartValue = mCircleRadius;
331         mCircleWillBeHidden = circleRadius == 0.0f;
332         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
333             @Override
334             public void onAnimationUpdate(ValueAnimator animation) {
335                 mCircleRadius = (float) animation.getAnimatedValue();
336                 updateIconColor();
337                 invalidate();
338             }
339         });
340         animator.addListener(mCircleEndListener);
341         return animator;
342     }
343 
cancelAnimator(Animator animator)344     private void cancelAnimator(Animator animator) {
345         if (animator != null) {
346             animator.cancel();
347         }
348     }
349 
setImageScale(float imageScale, boolean animate)350     public void setImageScale(float imageScale, boolean animate) {
351         setImageScale(imageScale, animate, -1, null);
352     }
353 
354     /**
355      * Sets the scale of the containing image
356      *
357      * @param imageScale The new Scale.
358      * @param animate Should an animation be performed
359      * @param duration If animate, whats the duration? When -1 we take the default duration
360      * @param interpolator If animate, whats the interpolator? When null we take the default
361      *                     interpolator.
362      */
setImageScale(float imageScale, boolean animate, long duration, Interpolator interpolator)363     public void setImageScale(float imageScale, boolean animate, long duration,
364             Interpolator interpolator) {
365         cancelAnimator(mScaleAnimator);
366         if (!animate) {
367             mImageScale = imageScale;
368             invalidate();
369         } else {
370             ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale);
371             mScaleAnimator = animator;
372             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
373                 @Override
374                 public void onAnimationUpdate(ValueAnimator animation) {
375                     mImageScale = (float) animation.getAnimatedValue();
376                     invalidate();
377                 }
378             });
379             animator.addListener(mScaleEndListener);
380             if (interpolator == null) {
381                 interpolator = imageScale == 0.0f
382                         ? mDisappearInterpolator
383                         : mAppearInterpolator;
384             }
385             animator.setInterpolator(interpolator);
386             if (duration == -1) {
387                 float durationFactor = Math.abs(mImageScale - imageScale)
388                         / (1.0f - MIN_ICON_SCALE_AMOUNT);
389                 durationFactor = Math.min(1.0f, durationFactor);
390                 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
391             }
392             animator.setDuration(duration);
393             animator.start();
394         }
395     }
396 
setImageAlpha(float alpha, boolean animate)397     public void setImageAlpha(float alpha, boolean animate) {
398         setImageAlpha(alpha, animate, -1, null, null);
399     }
400 
401     /**
402      * Sets the alpha of the containing image
403      *
404      * @param alpha The new alpha.
405      * @param animate Should an animation be performed
406      * @param duration If animate, whats the duration? When -1 we take the default duration
407      * @param interpolator If animate, whats the interpolator? When null we take the default
408      *                     interpolator.
409      */
setImageAlpha(float alpha, boolean animate, long duration, Interpolator interpolator, Runnable runnable)410     public void setImageAlpha(float alpha, boolean animate, long duration,
411             Interpolator interpolator, Runnable runnable) {
412         cancelAnimator(mAlphaAnimator);
413         int endAlpha = (int) (alpha * 255);
414         final Drawable background = getBackground();
415         if (!animate) {
416             if (background != null) background.mutate().setAlpha(endAlpha);
417             setImageAlpha(endAlpha);
418         } else {
419             int currentAlpha = getImageAlpha();
420             ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha);
421             mAlphaAnimator = animator;
422             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
423                 @Override
424                 public void onAnimationUpdate(ValueAnimator animation) {
425                     int alpha = (int) animation.getAnimatedValue();
426                     if (background != null) background.mutate().setAlpha(alpha);
427                     setImageAlpha(alpha);
428                 }
429             });
430             animator.addListener(mAlphaEndListener);
431             if (interpolator == null) {
432                 interpolator = alpha == 0.0f
433                         ? mDisappearInterpolator
434                         : mAppearInterpolator;
435             }
436             animator.setInterpolator(interpolator);
437             if (duration == -1) {
438                 float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f;
439                 durationFactor = Math.min(1.0f, durationFactor);
440                 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
441             }
442             animator.setDuration(duration);
443             if (runnable != null) {
444                 animator.addListener(getEndListener(runnable));
445             }
446             animator.start();
447         }
448     }
449 
getEndListener(final Runnable runnable)450     private Animator.AnimatorListener getEndListener(final Runnable runnable) {
451         return new AnimatorListenerAdapter() {
452             boolean mCancelled;
453             @Override
454             public void onAnimationCancel(Animator animation) {
455                 mCancelled = true;
456             }
457 
458             @Override
459             public void onAnimationEnd(Animator animation) {
460                 if (!mCancelled) {
461                     runnable.run();
462                 }
463             }
464         };
465     }
466 
getCircleRadius()467     public float getCircleRadius() {
468         return mCircleRadius;
469     }
470 
showArrow(boolean show)471     public void showArrow(boolean show) {
472         cancelAnimator(mArrowAnimator);
473         float targetAlpha = show ? 1.0f : 0.0f;
474         if (mArrowAlpha == targetAlpha) {
475             return;
476         }
477         ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha);
478         mArrowAnimator = animator;
479         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
480             @Override
481             public void onAnimationUpdate(ValueAnimator animation) {
482                 mArrowAlpha = (float) animation.getAnimatedValue();
483                 invalidate();
484             }
485         });
486         animator.addListener(mArrowEndListener);
487         Interpolator interpolator = show
488                     ? mAppearInterpolator
489                     : mDisappearInterpolator;
490         animator.setInterpolator(interpolator);
491         float durationFactor = Math.abs(mArrowAlpha - targetAlpha);
492         long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
493         animator.setDuration(duration);
494         animator.start();
495     }
496 
setIsLeft(boolean left)497     public void setIsLeft(boolean left) {
498         mIsLeft = left;
499     }
500 
501     @Override
performClick()502     public boolean performClick() {
503         if (isClickable()) {
504             return super.performClick();
505         } else {
506             return false;
507         }
508     }
509 }
510