Loading packages/SystemUI/res/drawable/ic_chevron_left.xml 0 → 100644 +28 −0 Original line number Diff line number Diff line <!-- ~ Copyright (C) 2014 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:width="24dp" android:height="24dp"/> <viewport android:viewportWidth="36.0" android:viewportHeight="36.0"/> <path android:fill="#ffffffff" android:pathData="M23.1,11.1l-2.1000004,-2.1000004 -9.0,9.0 9.0,9.0 2.1000004,-2.1000004 -6.8999996,-6.8999996z"/> </vector> packages/SystemUI/res/layout/keyguard_bottom_area.xml +3 −3 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" > <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/camera_button" android:layout_height="64dp" android:layout_width="64dp" Loading @@ -32,7 +32,7 @@ android:scaleType="center" android:contentDescription="@string/accessibility_camera_button" /> <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/phone_button" android:layout_height="64dp" android:layout_width="64dp" Loading @@ -52,7 +52,7 @@ android:textColor="#ffffff" android:textAppearance="?android:attr/textAppearanceSmall"/> <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/lock_icon" android:layout_width="64dp" android:layout_height="64dp" Loading packages/SystemUI/res/values/dimens.xml +9 −3 Original line number Diff line number Diff line Loading @@ -295,6 +295,15 @@ <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> <dimen name="keyguard_min_swipe_amount">75dp</dimen> <!-- The minimum background radius when swiping to a side for the camera / phone affordances. --> <dimen name="keyguard_affordance_min_background_radius">30dp</dimen> <!-- The grow amount for the camera and phone circles when hinting --> <dimen name="hint_grow_amount_sideways">60dp</dimen> <!-- The chevron padding to the circle when hinting --> <dimen name="hint_chevron_circle_padding">16dp</dimen> <!-- Volume panel dialog y offset --> <dimen name="volume_panel_top">0dp</dimen> Loading @@ -317,9 +326,6 @@ <!-- Move distance for the unlock hint animation on the lockscreen --> <dimen name="hint_move_distance">75dp</dimen> <!-- Move distance for the other hint animations on the lockscreen (phone, camera)--> <dimen name="hint_move_distance_sideways">60dp</dimen> <!-- The width of the region on the left/right edge of the screen for performing the camera/ phone hints. --> <dimen name="edge_tap_area_width">48dp</dimen> Loading packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +0 −6 Original line number Diff line number Diff line Loading @@ -244,9 +244,6 @@ public abstract class BaseStatusBar extends SystemUI implements // the user switches to home. We know it is safe to do at this // point, so make sure new activity switches are now allowed. ActivityManagerNative.getDefault().resumeAppSwitches(); // Also, notifications can be launched from the lock screen, // so dismiss the lock screen when the activity starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } Loading Loading @@ -1153,9 +1150,6 @@ public abstract class BaseStatusBar extends SystemUI implements // the user switches to home. We know it is safe to do at this // point, so make sure new activity switches are now allowed. ActivityManagerNative.getDefault().resumeAppSwitches(); // Also, notifications can be launched from the lock screen, // so dismiss the lock screen when the activity starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java 0 → 100644 +429 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ArgbEvaluator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.ImageView; import com.android.systemui.R; /** * An ImageView which does not have overlapping renderings commands and therefore does not need a * layer when alpha is changed. */ public class KeyguardAffordanceView extends ImageView { private static final long CIRCLE_APPEAR_DURATION = 80; private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200; private static final long NORMAL_ANIMATION_DURATION = 200; public static final float MAX_ICON_SCALE_AMOUNT = 1.5f; public static final float MIN_ICON_SCALE_AMOUNT = 0.8f; private final int mMinBackgroundRadius; private final Paint mCirclePaint; private final Interpolator mAppearInterpolator; private final Interpolator mDisappearInterpolator; private final int mInverseColor; private final int mNormalColor; private final ArgbEvaluator mColorInterpolator; private final FlingAnimationUtils mFlingAnimationUtils; private final Drawable mArrowDrawable; private final int mHintChevronPadding; private float mCircleRadius; private int mCenterX; private int mCenterY; private ValueAnimator mCircleAnimator; private ValueAnimator mAlphaAnimator; private ValueAnimator mScaleAnimator; private ValueAnimator mArrowAnimator; private float mCircleStartValue; private boolean mCircleWillBeHidden; private int[] mTempPoint = new int[2]; private float mImageScale; private int mCircleColor; private boolean mIsLeft; private float mArrowAlpha = 0.0f; private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCircleAnimator = null; } }; private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mScaleAnimator = null; } }; private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAlphaAnimator = null; } }; private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mArrowAnimator = null; } }; public KeyguardAffordanceView(Context context) { this(context, null); } public KeyguardAffordanceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mCirclePaint = new Paint(); mCirclePaint.setAntiAlias(true); mCircleColor = 0xffffffff; mCirclePaint.setColor(mCircleColor); mNormalColor = 0xffffffff; mInverseColor = 0xff000000; mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_affordance_min_background_radius); mHintChevronPadding = mContext.getResources().getDimensionPixelSize( R.dimen.hint_chevron_circle_padding); mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in); mColorInterpolator = new ArgbEvaluator(); mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f); mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left); mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(), mArrowDrawable.getIntrinsicHeight()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mCenterX = getWidth() / 2; mCenterY = getHeight() / 2; } @Override protected void onDraw(Canvas canvas) { drawBackgroundCircle(canvas); drawArrow(canvas); canvas.save(); updateIconColor(); canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2); super.onDraw(canvas); canvas.restore(); } private void drawArrow(Canvas canvas) { if (mArrowAlpha > 0) { canvas.save(); canvas.translate(mCenterX, mCenterY); if (mIsLeft) { canvas.scale(-1.0f, 1.0f); } canvas.translate(- mCircleRadius - mHintChevronPadding - mArrowDrawable.getIntrinsicWidth() / 2, - mArrowDrawable.getIntrinsicHeight() / 2); mArrowDrawable.setAlpha((int) (mArrowAlpha * 255)); mArrowDrawable.draw(canvas); canvas.restore(); } } private void updateIconColor() { Drawable drawable = getDrawable().mutate(); float alpha = mCircleRadius / mMinBackgroundRadius; alpha = Math.min(1.0f, alpha); int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor); drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); } private void drawBackgroundCircle(Canvas canvas) { if (mCircleRadius > 0) { updateCircleColor(); canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint); } } private void updateCircleColor() { float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f, (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius))); int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction), Color.red(mCircleColor), Color.green(mCircleColor), Color.blue(mCircleColor)); mCirclePaint.setColor(color); } public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) { cancelAnimator(mCircleAnimator); float maxCircleSize = getMaxCircleSize(); ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize); mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize, velocity, maxCircleSize); animatorToRadius.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAnimationEndRunnable.run(); } }); animatorToRadius.start(); setImageAlpha(0, true); } private float getMaxCircleSize() { getLocationInWindow(mTempPoint); float rootWidth = getRootView().getWidth(); float width = mTempPoint[0] + mCenterX; width = Math.max(rootWidth - width, width); float height = mTempPoint[1] + mCenterY; return (float) Math.hypot(width, height); } public void setCircleRadius(float circleRadius) { setCircleRadius(circleRadius, false); } public void setCircleRadiusWithoutAnimation(float circleRadius) { cancelAnimator(mCircleAnimator); setCircleRadius(circleRadius, true); } private void setCircleRadius(float circleRadius, boolean noAnimation) { // Check if we need a new animation boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden) || (mCircleAnimator == null && mCircleRadius == 0.0f); boolean nowHidden = circleRadius == 0.0f; boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation; if (!radiusNeedsAnimation) { if (mCircleAnimator == null) { mCircleRadius = circleRadius; invalidate(); } else if (!mCircleWillBeHidden) { // We just update the end value float diff = circleRadius - mMinBackgroundRadius; PropertyValuesHolder[] values = mCircleAnimator.getValues(); values[0].setFloatValues(mCircleStartValue + diff, circleRadius); mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime()); } } else { cancelAnimator(mCircleAnimator); ValueAnimator animator = getAnimatorToRadius(circleRadius); Interpolator interpolator = circleRadius == 0.0f ? mDisappearInterpolator : mAppearInterpolator; animator.setInterpolator(interpolator); float durationFactor = Math.abs(mCircleRadius - circleRadius) / (float) mMinBackgroundRadius; long duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor); duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION); animator.setDuration(duration); animator.start(); } } private ValueAnimator getAnimatorToRadius(float circleRadius) { ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius); mCircleAnimator = animator; mCircleStartValue = mCircleRadius; mCircleWillBeHidden = circleRadius == 0.0f; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCircleRadius = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mCircleEndListener); return animator; } private void cancelAnimator(Animator animator) { if (animator != null) { animator.cancel(); } } public void setImageScale(float imageScale, boolean animate) { setImageScale(imageScale, animate, -1, null); } /** * Sets the scale of the containing image * * @param imageScale The new Scale. * @param animate Should an animation be performed * @param duration If animate, whats the duration? When -1 we take the default duration * @param interpolator If animate, whats the interpolator? When null we take the default * interpolator. */ public void setImageScale(float imageScale, boolean animate, long duration, Interpolator interpolator) { cancelAnimator(mScaleAnimator); if (!animate) { mImageScale = imageScale; invalidate(); } else { ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale); mScaleAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mImageScale = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mScaleEndListener); if (interpolator == null) { interpolator = imageScale == 0.0f ? mDisappearInterpolator : mAppearInterpolator; } animator.setInterpolator(interpolator); if (duration == -1) { float durationFactor = Math.abs(mImageScale - imageScale) / (1.0f - MIN_ICON_SCALE_AMOUNT); durationFactor = Math.min(1.0f, durationFactor); duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); } animator.setDuration(duration); animator.start(); } } public void setImageAlpha(float alpha, boolean animate) { setImageAlpha(alpha, animate, -1, null, null); } /** * Sets the alpha of the containing image * * @param alpha The new alpha. * @param animate Should an animation be performed * @param duration If animate, whats the duration? When -1 we take the default duration * @param interpolator If animate, whats the interpolator? When null we take the default * interpolator. */ public void setImageAlpha(float alpha, boolean animate, long duration, Interpolator interpolator, Runnable runnable) { cancelAnimator(mAlphaAnimator); int endAlpha = (int) (alpha * 255); if (!animate) { setImageAlpha(endAlpha); } else { int currentAlpha = getImageAlpha(); ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha); mAlphaAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setImageAlpha((int) animation.getAnimatedValue()); } }); animator.addListener(mAlphaEndListener); if (interpolator == null) { interpolator = alpha == 0.0f ? mDisappearInterpolator : mAppearInterpolator; } animator.setInterpolator(interpolator); if (duration == -1) { float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f; durationFactor = Math.min(1.0f, durationFactor); duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); } animator.setDuration(duration); if (runnable != null) { animator.addListener(getEndListener(runnable)); } animator.start(); } } private Animator.AnimatorListener getEndListener(final Runnable runnable) { return new AnimatorListenerAdapter() { boolean mCancelled; @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mCancelled) { runnable.run(); } } }; } public float getCircleRadius() { return mCircleRadius; } public void showArrow(boolean show) { cancelAnimator(mArrowAnimator); float targetAlpha = show ? 1.0f : 0.0f; if (mArrowAlpha == targetAlpha) { return; } ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha); mArrowAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mArrowAlpha = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mArrowEndListener); Interpolator interpolator = show ? mAppearInterpolator : mDisappearInterpolator; animator.setInterpolator(interpolator); float durationFactor = Math.abs(mArrowAlpha - targetAlpha); long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); animator.setDuration(duration); animator.start(); } public void setIsLeft(boolean left) { mIsLeft = left; } } Loading
packages/SystemUI/res/drawable/ic_chevron_left.xml 0 → 100644 +28 −0 Original line number Diff line number Diff line <!-- ~ Copyright (C) 2014 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:width="24dp" android:height="24dp"/> <viewport android:viewportWidth="36.0" android:viewportHeight="36.0"/> <path android:fill="#ffffffff" android:pathData="M23.1,11.1l-2.1000004,-2.1000004 -9.0,9.0 9.0,9.0 2.1000004,-2.1000004 -6.8999996,-6.8999996z"/> </vector>
packages/SystemUI/res/layout/keyguard_bottom_area.xml +3 −3 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" > <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/camera_button" android:layout_height="64dp" android:layout_width="64dp" Loading @@ -32,7 +32,7 @@ android:scaleType="center" android:contentDescription="@string/accessibility_camera_button" /> <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/phone_button" android:layout_height="64dp" android:layout_width="64dp" Loading @@ -52,7 +52,7 @@ android:textColor="#ffffff" android:textAppearance="?android:attr/textAppearanceSmall"/> <com.android.systemui.statusbar.AlphaImageView <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/lock_icon" android:layout_width="64dp" android:layout_height="64dp" Loading
packages/SystemUI/res/values/dimens.xml +9 −3 Original line number Diff line number Diff line Loading @@ -295,6 +295,15 @@ <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> <dimen name="keyguard_min_swipe_amount">75dp</dimen> <!-- The minimum background radius when swiping to a side for the camera / phone affordances. --> <dimen name="keyguard_affordance_min_background_radius">30dp</dimen> <!-- The grow amount for the camera and phone circles when hinting --> <dimen name="hint_grow_amount_sideways">60dp</dimen> <!-- The chevron padding to the circle when hinting --> <dimen name="hint_chevron_circle_padding">16dp</dimen> <!-- Volume panel dialog y offset --> <dimen name="volume_panel_top">0dp</dimen> Loading @@ -317,9 +326,6 @@ <!-- Move distance for the unlock hint animation on the lockscreen --> <dimen name="hint_move_distance">75dp</dimen> <!-- Move distance for the other hint animations on the lockscreen (phone, camera)--> <dimen name="hint_move_distance_sideways">60dp</dimen> <!-- The width of the region on the left/right edge of the screen for performing the camera/ phone hints. --> <dimen name="edge_tap_area_width">48dp</dimen> Loading
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +0 −6 Original line number Diff line number Diff line Loading @@ -244,9 +244,6 @@ public abstract class BaseStatusBar extends SystemUI implements // the user switches to home. We know it is safe to do at this // point, so make sure new activity switches are now allowed. ActivityManagerNative.getDefault().resumeAppSwitches(); // Also, notifications can be launched from the lock screen, // so dismiss the lock screen when the activity starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } Loading Loading @@ -1153,9 +1150,6 @@ public abstract class BaseStatusBar extends SystemUI implements // the user switches to home. We know it is safe to do at this // point, so make sure new activity switches are now allowed. ActivityManagerNative.getDefault().resumeAppSwitches(); // Also, notifications can be launched from the lock screen, // so dismiss the lock screen when the activity starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java 0 → 100644 +429 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ArgbEvaluator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.ImageView; import com.android.systemui.R; /** * An ImageView which does not have overlapping renderings commands and therefore does not need a * layer when alpha is changed. */ public class KeyguardAffordanceView extends ImageView { private static final long CIRCLE_APPEAR_DURATION = 80; private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200; private static final long NORMAL_ANIMATION_DURATION = 200; public static final float MAX_ICON_SCALE_AMOUNT = 1.5f; public static final float MIN_ICON_SCALE_AMOUNT = 0.8f; private final int mMinBackgroundRadius; private final Paint mCirclePaint; private final Interpolator mAppearInterpolator; private final Interpolator mDisappearInterpolator; private final int mInverseColor; private final int mNormalColor; private final ArgbEvaluator mColorInterpolator; private final FlingAnimationUtils mFlingAnimationUtils; private final Drawable mArrowDrawable; private final int mHintChevronPadding; private float mCircleRadius; private int mCenterX; private int mCenterY; private ValueAnimator mCircleAnimator; private ValueAnimator mAlphaAnimator; private ValueAnimator mScaleAnimator; private ValueAnimator mArrowAnimator; private float mCircleStartValue; private boolean mCircleWillBeHidden; private int[] mTempPoint = new int[2]; private float mImageScale; private int mCircleColor; private boolean mIsLeft; private float mArrowAlpha = 0.0f; private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCircleAnimator = null; } }; private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mScaleAnimator = null; } }; private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAlphaAnimator = null; } }; private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mArrowAnimator = null; } }; public KeyguardAffordanceView(Context context) { this(context, null); } public KeyguardAffordanceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mCirclePaint = new Paint(); mCirclePaint.setAntiAlias(true); mCircleColor = 0xffffffff; mCirclePaint.setColor(mCircleColor); mNormalColor = 0xffffffff; mInverseColor = 0xff000000; mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_affordance_min_background_radius); mHintChevronPadding = mContext.getResources().getDimensionPixelSize( R.dimen.hint_chevron_circle_padding); mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in); mColorInterpolator = new ArgbEvaluator(); mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f); mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left); mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(), mArrowDrawable.getIntrinsicHeight()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mCenterX = getWidth() / 2; mCenterY = getHeight() / 2; } @Override protected void onDraw(Canvas canvas) { drawBackgroundCircle(canvas); drawArrow(canvas); canvas.save(); updateIconColor(); canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2); super.onDraw(canvas); canvas.restore(); } private void drawArrow(Canvas canvas) { if (mArrowAlpha > 0) { canvas.save(); canvas.translate(mCenterX, mCenterY); if (mIsLeft) { canvas.scale(-1.0f, 1.0f); } canvas.translate(- mCircleRadius - mHintChevronPadding - mArrowDrawable.getIntrinsicWidth() / 2, - mArrowDrawable.getIntrinsicHeight() / 2); mArrowDrawable.setAlpha((int) (mArrowAlpha * 255)); mArrowDrawable.draw(canvas); canvas.restore(); } } private void updateIconColor() { Drawable drawable = getDrawable().mutate(); float alpha = mCircleRadius / mMinBackgroundRadius; alpha = Math.min(1.0f, alpha); int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor); drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); } private void drawBackgroundCircle(Canvas canvas) { if (mCircleRadius > 0) { updateCircleColor(); canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint); } } private void updateCircleColor() { float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f, (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius))); int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction), Color.red(mCircleColor), Color.green(mCircleColor), Color.blue(mCircleColor)); mCirclePaint.setColor(color); } public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) { cancelAnimator(mCircleAnimator); float maxCircleSize = getMaxCircleSize(); ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize); mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize, velocity, maxCircleSize); animatorToRadius.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAnimationEndRunnable.run(); } }); animatorToRadius.start(); setImageAlpha(0, true); } private float getMaxCircleSize() { getLocationInWindow(mTempPoint); float rootWidth = getRootView().getWidth(); float width = mTempPoint[0] + mCenterX; width = Math.max(rootWidth - width, width); float height = mTempPoint[1] + mCenterY; return (float) Math.hypot(width, height); } public void setCircleRadius(float circleRadius) { setCircleRadius(circleRadius, false); } public void setCircleRadiusWithoutAnimation(float circleRadius) { cancelAnimator(mCircleAnimator); setCircleRadius(circleRadius, true); } private void setCircleRadius(float circleRadius, boolean noAnimation) { // Check if we need a new animation boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden) || (mCircleAnimator == null && mCircleRadius == 0.0f); boolean nowHidden = circleRadius == 0.0f; boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation; if (!radiusNeedsAnimation) { if (mCircleAnimator == null) { mCircleRadius = circleRadius; invalidate(); } else if (!mCircleWillBeHidden) { // We just update the end value float diff = circleRadius - mMinBackgroundRadius; PropertyValuesHolder[] values = mCircleAnimator.getValues(); values[0].setFloatValues(mCircleStartValue + diff, circleRadius); mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime()); } } else { cancelAnimator(mCircleAnimator); ValueAnimator animator = getAnimatorToRadius(circleRadius); Interpolator interpolator = circleRadius == 0.0f ? mDisappearInterpolator : mAppearInterpolator; animator.setInterpolator(interpolator); float durationFactor = Math.abs(mCircleRadius - circleRadius) / (float) mMinBackgroundRadius; long duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor); duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION); animator.setDuration(duration); animator.start(); } } private ValueAnimator getAnimatorToRadius(float circleRadius) { ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius); mCircleAnimator = animator; mCircleStartValue = mCircleRadius; mCircleWillBeHidden = circleRadius == 0.0f; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCircleRadius = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mCircleEndListener); return animator; } private void cancelAnimator(Animator animator) { if (animator != null) { animator.cancel(); } } public void setImageScale(float imageScale, boolean animate) { setImageScale(imageScale, animate, -1, null); } /** * Sets the scale of the containing image * * @param imageScale The new Scale. * @param animate Should an animation be performed * @param duration If animate, whats the duration? When -1 we take the default duration * @param interpolator If animate, whats the interpolator? When null we take the default * interpolator. */ public void setImageScale(float imageScale, boolean animate, long duration, Interpolator interpolator) { cancelAnimator(mScaleAnimator); if (!animate) { mImageScale = imageScale; invalidate(); } else { ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale); mScaleAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mImageScale = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mScaleEndListener); if (interpolator == null) { interpolator = imageScale == 0.0f ? mDisappearInterpolator : mAppearInterpolator; } animator.setInterpolator(interpolator); if (duration == -1) { float durationFactor = Math.abs(mImageScale - imageScale) / (1.0f - MIN_ICON_SCALE_AMOUNT); durationFactor = Math.min(1.0f, durationFactor); duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); } animator.setDuration(duration); animator.start(); } } public void setImageAlpha(float alpha, boolean animate) { setImageAlpha(alpha, animate, -1, null, null); } /** * Sets the alpha of the containing image * * @param alpha The new alpha. * @param animate Should an animation be performed * @param duration If animate, whats the duration? When -1 we take the default duration * @param interpolator If animate, whats the interpolator? When null we take the default * interpolator. */ public void setImageAlpha(float alpha, boolean animate, long duration, Interpolator interpolator, Runnable runnable) { cancelAnimator(mAlphaAnimator); int endAlpha = (int) (alpha * 255); if (!animate) { setImageAlpha(endAlpha); } else { int currentAlpha = getImageAlpha(); ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha); mAlphaAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setImageAlpha((int) animation.getAnimatedValue()); } }); animator.addListener(mAlphaEndListener); if (interpolator == null) { interpolator = alpha == 0.0f ? mDisappearInterpolator : mAppearInterpolator; } animator.setInterpolator(interpolator); if (duration == -1) { float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f; durationFactor = Math.min(1.0f, durationFactor); duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); } animator.setDuration(duration); if (runnable != null) { animator.addListener(getEndListener(runnable)); } animator.start(); } } private Animator.AnimatorListener getEndListener(final Runnable runnable) { return new AnimatorListenerAdapter() { boolean mCancelled; @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mCancelled) { runnable.run(); } } }; } public float getCircleRadius() { return mCircleRadius; } public void showArrow(boolean show) { cancelAnimator(mArrowAnimator); float targetAlpha = show ? 1.0f : 0.0f; if (mArrowAlpha == targetAlpha) { return; } ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha); mArrowAnimator = animator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mArrowAlpha = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(mArrowEndListener); Interpolator interpolator = show ? mAppearInterpolator : mDisappearInterpolator; animator.setInterpolator(interpolator); float durationFactor = Math.abs(mArrowAlpha - targetAlpha); long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor); animator.setDuration(duration); animator.start(); } public void setIsLeft(boolean left) { mIsLeft = left; } }