2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar;
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.CanvasProperty;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.graphics.PorterDuff;
30 import android.graphics.drawable.Drawable;
31 import android.util.AttributeSet;
32 import android.view.DisplayListCanvas;
33 import android.view.RenderNodeAnimator;
34 import android.view.View;
35 import android.view.ViewAnimationUtils;
36 import android.view.animation.Interpolator;
37 import android.widget.ImageView;
39 import com.android.systemui.Interpolators;
40 import com.android.systemui.R;
41 import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
44 * An ImageView which does not have overlapping renderings commands and therefore does not need a
45 * layer when alpha is changed.
47 public class KeyguardAffordanceView extends ImageView {
49 private static final long CIRCLE_APPEAR_DURATION = 80;
50 private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
51 private static final long NORMAL_ANIMATION_DURATION = 200;
52 public static final float MAX_ICON_SCALE_AMOUNT = 1.5f;
53 public static final float MIN_ICON_SCALE_AMOUNT = 0.8f;
55 private final int mMinBackgroundRadius;
56 private final Paint mCirclePaint;
57 private final int mInverseColor;
58 private final int mNormalColor;
59 private final ArgbEvaluator mColorInterpolator;
60 private final FlingAnimationUtils mFlingAnimationUtils;
61 private float mCircleRadius;
64 private ValueAnimator mCircleAnimator;
65 private ValueAnimator mAlphaAnimator;
66 private ValueAnimator mScaleAnimator;
67 private float mCircleStartValue;
68 private boolean mCircleWillBeHidden;
69 private int[] mTempPoint = new int[2];
70 private float mImageScale = 1f;
71 private int mCircleColor;
72 private boolean mIsLeft;
73 private View mPreviewView;
74 private float mCircleStartRadius;
75 private float mMaxCircleSize;
76 private Animator mPreviewClipper;
77 private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT;
78 private boolean mSupportHardware;
79 private boolean mFinishing;
80 private boolean mLaunchingAffordance;
82 private CanvasProperty<Float> mHwCircleRadius;
83 private CanvasProperty<Float> mHwCenterX;
84 private CanvasProperty<Float> mHwCenterY;
85 private CanvasProperty<Paint> mHwCirclePaint;
87 private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
89 public void onAnimationEnd(Animator animation) {
90 mPreviewClipper = null;
93 private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
95 public void onAnimationEnd(Animator animation) {
96 mCircleAnimator = null;
99 private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() {
101 public void onAnimationEnd(Animator animation) {
102 mScaleAnimator = null;
105 private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() {
107 public void onAnimationEnd(Animator animation) {
108 mAlphaAnimator = null;
112 public KeyguardAffordanceView(Context context) {
116 public KeyguardAffordanceView(Context context, AttributeSet attrs) {
117 this(context, attrs, 0);
120 public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
121 this(context, attrs, defStyleAttr, 0);
124 public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
126 super(context, attrs, defStyleAttr, defStyleRes);
127 mCirclePaint = new Paint();
128 mCirclePaint.setAntiAlias(true);
129 mCircleColor = 0xffffffff;
130 mCirclePaint.setColor(mCircleColor);
132 mNormalColor = 0xffffffff;
133 mInverseColor = 0xff000000;
134 mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
135 R.dimen.keyguard_affordance_min_background_radius);
136 mColorInterpolator = new ArgbEvaluator();
137 mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
141 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
142 super.onLayout(changed, left, top, right, bottom);
143 mCenterX = getWidth() / 2;
144 mCenterY = getHeight() / 2;
145 mMaxCircleSize = getMaxCircleSize();
149 protected void onDraw(Canvas canvas) {
150 mSupportHardware = false;//canvas.isHardwareAccelerated();
151 drawBackgroundCircle(canvas);
153 canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
154 super.onDraw(canvas);
158 public void setPreviewView(View v) {
159 View oldPreviewView = mPreviewView;
161 if (mPreviewView != null) {
162 mPreviewView.setVisibility(mLaunchingAffordance
163 ? oldPreviewView.getVisibility() : INVISIBLE);
167 private void updateIconColor() {
168 Drawable drawable = getDrawable().mutate();
169 float alpha = mCircleRadius / mMinBackgroundRadius;
170 alpha = Math.min(1.0f, alpha);
171 int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
172 drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
175 private void drawBackgroundCircle(Canvas canvas) {
176 if (mCircleRadius > 0 || mFinishing) {
177 if (mFinishing && mSupportHardware) {
178 DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
179 displayListCanvas.drawCircle(mHwCenterX, mHwCenterY, mHwCircleRadius,
183 canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
188 private void updateCircleColor() {
189 float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
190 (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
191 if (mPreviewView != null && mPreviewView.getVisibility() == VISIBLE) {
192 float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius)
193 / (mMaxCircleSize - mCircleStartRadius);
194 fraction *= finishingFraction;
196 int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
197 Color.red(mCircleColor),
198 Color.green(mCircleColor), Color.blue(mCircleColor));
199 mCirclePaint.setColor(color);
202 public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
203 cancelAnimator(mCircleAnimator);
204 cancelAnimator(mPreviewClipper);
206 mCircleStartRadius = mCircleRadius;
207 final float maxCircleSize = getMaxCircleSize();
208 Animator animatorToRadius;
209 if (mSupportHardware) {
211 animatorToRadius = getRtAnimatorToRadius(maxCircleSize);
212 startRtAlphaFadeIn();
214 animatorToRadius = getAnimatorToRadius(maxCircleSize);
216 mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
217 velocity, maxCircleSize);
218 animatorToRadius.addListener(new AnimatorListenerAdapter() {
220 public void onAnimationEnd(Animator animation) {
221 mAnimationEndRunnable.run();
223 mCircleRadius = maxCircleSize;
227 animatorToRadius.start();
228 setImageAlpha(0, true);
229 if (mPreviewView != null) {
230 mPreviewView.setVisibility(View.VISIBLE);
231 mPreviewClipper = ViewAnimationUtils.createCircularReveal(
232 mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
234 mFlingAnimationUtils.applyDismissing(mPreviewClipper, mCircleRadius, maxCircleSize,
235 velocity, maxCircleSize);
236 mPreviewClipper.addListener(mClipEndListener);
237 mPreviewClipper.start();
238 if (mSupportHardware) {
239 startRtCircleFadeOut(animatorToRadius.getDuration());
245 * Fades in the Circle on the RenderThread. It's used when finishing the circle when it had
246 * alpha 0 in the beginning.
248 private void startRtAlphaFadeIn() {
249 if (mCircleRadius == 0 && mPreviewView == null) {
250 Paint modifiedPaint = new Paint(mCirclePaint);
251 modifiedPaint.setColor(mCircleColor);
252 modifiedPaint.setAlpha(0);
253 mHwCirclePaint = CanvasProperty.createPaint(modifiedPaint);
254 RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint,
255 RenderNodeAnimator.PAINT_ALPHA, 255);
256 animator.setTarget(this);
257 animator.setInterpolator(Interpolators.ALPHA_IN);
258 animator.setDuration(250);
263 public void instantFinishAnimation() {
264 cancelAnimator(mPreviewClipper);
265 if (mPreviewView != null) {
266 mPreviewView.setClipBounds(null);
267 mPreviewView.setVisibility(View.VISIBLE);
269 mCircleRadius = getMaxCircleSize();
270 setImageAlpha(0, false);
274 private void startRtCircleFadeOut(long duration) {
275 RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint,
276 RenderNodeAnimator.PAINT_ALPHA, 0);
277 animator.setDuration(duration);
278 animator.setInterpolator(Interpolators.ALPHA_OUT);
279 animator.setTarget(this);
283 private Animator getRtAnimatorToRadius(float circleRadius) {
284 RenderNodeAnimator animator = new RenderNodeAnimator(mHwCircleRadius, circleRadius);
285 animator.setTarget(this);
289 private void initHwProperties() {
290 mHwCenterX = CanvasProperty.createFloat(mCenterX);
291 mHwCenterY = CanvasProperty.createFloat(mCenterY);
292 mHwCirclePaint = CanvasProperty.createPaint(mCirclePaint);
293 mHwCircleRadius = CanvasProperty.createFloat(mCircleRadius);
296 private float getMaxCircleSize() {
297 getLocationInWindow(mTempPoint);
298 float rootWidth = getRootView().getWidth();
299 float width = mTempPoint[0] + mCenterX;
300 width = Math.max(rootWidth - width, width);
301 float height = mTempPoint[1] + mCenterY;
302 return (float) Math.hypot(width, height);
305 public void setCircleRadius(float circleRadius) {
306 setCircleRadius(circleRadius, false, false);
309 public void setCircleRadius(float circleRadius, boolean slowAnimation) {
310 setCircleRadius(circleRadius, slowAnimation, false);
313 public void setCircleRadiusWithoutAnimation(float circleRadius) {
314 cancelAnimator(mCircleAnimator);
315 setCircleRadius(circleRadius, false ,true);
318 private void setCircleRadius(float circleRadius, boolean slowAnimation, boolean noAnimation) {
320 // Check if we need a new animation
321 boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden)
322 || (mCircleAnimator == null && mCircleRadius == 0.0f);
323 boolean nowHidden = circleRadius == 0.0f;
324 boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation;
325 if (!radiusNeedsAnimation) {
326 if (mCircleAnimator == null) {
327 mCircleRadius = circleRadius;
331 if (mPreviewView != null) {
332 mPreviewView.setVisibility(View.INVISIBLE);
335 } else if (!mCircleWillBeHidden) {
337 // We just update the end value
338 float diff = circleRadius - mMinBackgroundRadius;
339 PropertyValuesHolder[] values = mCircleAnimator.getValues();
340 values[0].setFloatValues(mCircleStartValue + diff, circleRadius);
341 mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
344 cancelAnimator(mCircleAnimator);
345 cancelAnimator(mPreviewClipper);
346 ValueAnimator animator = getAnimatorToRadius(circleRadius);
347 Interpolator interpolator = circleRadius == 0.0f
348 ? Interpolators.FAST_OUT_LINEAR_IN
349 : Interpolators.LINEAR_OUT_SLOW_IN;
350 animator.setInterpolator(interpolator);
352 if (!slowAnimation) {
353 float durationFactor = Math.abs(mCircleRadius - circleRadius)
354 / (float) mMinBackgroundRadius;
355 duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor);
356 duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
358 animator.setDuration(duration);
360 if (mPreviewView != null && mPreviewView.getVisibility() == View.VISIBLE) {
361 mPreviewView.setVisibility(View.VISIBLE);
362 mPreviewClipper = ViewAnimationUtils.createCircularReveal(
363 mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
365 mPreviewClipper.setInterpolator(interpolator);
366 mPreviewClipper.setDuration(duration);
367 mPreviewClipper.addListener(mClipEndListener);
368 mPreviewClipper.addListener(new AnimatorListenerAdapter() {
370 public void onAnimationEnd(Animator animation) {
371 mPreviewView.setVisibility(View.INVISIBLE);
374 mPreviewClipper.start();
379 private ValueAnimator getAnimatorToRadius(float circleRadius) {
380 ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius);
381 mCircleAnimator = animator;
382 mCircleStartValue = mCircleRadius;
383 mCircleWillBeHidden = circleRadius == 0.0f;
384 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
386 public void onAnimationUpdate(ValueAnimator animation) {
387 mCircleRadius = (float) animation.getAnimatedValue();
392 animator.addListener(mCircleEndListener);
396 private void cancelAnimator(Animator animator) {
397 if (animator != null) {
402 public void setImageScale(float imageScale, boolean animate) {
403 setImageScale(imageScale, animate, -1, null);
407 * Sets the scale of the containing image
409 * @param imageScale The new Scale.
410 * @param animate Should an animation be performed
411 * @param duration If animate, whats the duration? When -1 we take the default duration
412 * @param interpolator If animate, whats the interpolator? When null we take the default
415 public void setImageScale(float imageScale, boolean animate, long duration,
416 Interpolator interpolator) {
417 cancelAnimator(mScaleAnimator);
419 mImageScale = imageScale;
422 ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale);
423 mScaleAnimator = animator;
424 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
426 public void onAnimationUpdate(ValueAnimator animation) {
427 mImageScale = (float) animation.getAnimatedValue();
431 animator.addListener(mScaleEndListener);
432 if (interpolator == null) {
433 interpolator = imageScale == 0.0f
434 ? Interpolators.FAST_OUT_LINEAR_IN
435 : Interpolators.LINEAR_OUT_SLOW_IN;
437 animator.setInterpolator(interpolator);
438 if (duration == -1) {
439 float durationFactor = Math.abs(mImageScale - imageScale)
440 / (1.0f - MIN_ICON_SCALE_AMOUNT);
441 durationFactor = Math.min(1.0f, durationFactor);
442 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
444 animator.setDuration(duration);
449 public void setRestingAlpha(float alpha) {
450 mRestingAlpha = alpha;
452 // TODO: Handle the case an animation is playing.
453 setImageAlpha(alpha, false);
456 public float getRestingAlpha() {
457 return mRestingAlpha;
460 public void setImageAlpha(float alpha, boolean animate) {
461 setImageAlpha(alpha, animate, -1, null, null);
465 * Sets the alpha of the containing image
467 * @param alpha The new alpha.
468 * @param animate Should an animation be performed
469 * @param duration If animate, whats the duration? When -1 we take the default duration
470 * @param interpolator If animate, whats the interpolator? When null we take the default
473 public void setImageAlpha(float alpha, boolean animate, long duration,
474 Interpolator interpolator, Runnable runnable) {
475 cancelAnimator(mAlphaAnimator);
476 alpha = mLaunchingAffordance ? 0 : alpha;
477 int endAlpha = (int) (alpha * 255);
478 final Drawable background = getBackground();
480 if (background != null) background.mutate().setAlpha(endAlpha);
481 setImageAlpha(endAlpha);
483 int currentAlpha = getImageAlpha();
484 ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha);
485 mAlphaAnimator = animator;
486 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
488 public void onAnimationUpdate(ValueAnimator animation) {
489 int alpha = (int) animation.getAnimatedValue();
490 if (background != null) background.mutate().setAlpha(alpha);
491 setImageAlpha(alpha);
494 animator.addListener(mAlphaEndListener);
495 if (interpolator == null) {
496 interpolator = alpha == 0.0f
497 ? Interpolators.FAST_OUT_LINEAR_IN
498 : Interpolators.LINEAR_OUT_SLOW_IN;
500 animator.setInterpolator(interpolator);
501 if (duration == -1) {
502 float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f;
503 durationFactor = Math.min(1.0f, durationFactor);
504 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
506 animator.setDuration(duration);
507 if (runnable != null) {
508 animator.addListener(getEndListener(runnable));
514 private Animator.AnimatorListener getEndListener(final Runnable runnable) {
515 return new AnimatorListenerAdapter() {
518 public void onAnimationCancel(Animator animation) {
523 public void onAnimationEnd(Animator animation) {
531 public float getCircleRadius() {
532 return mCircleRadius;
536 public boolean performClick() {
538 return super.performClick();
544 public void setLaunchingAffordance(boolean launchingAffordance) {
545 mLaunchingAffordance = launchingAffordance;