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