Loading core/java/com/android/internal/widget/LockPatternView.java +34 −8 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; private final CellState[][] mCellStates; private boolean mDrawingProfilingStarted = false; private Paint mPaint = new Paint(); Loading Loading @@ -187,6 +188,12 @@ public class LockPatternView extends View { } } public static class CellState { public float scale = 1.0f; public float translateY = 0.0f; public float alpha = 1.0f; } /** * How to display the current pattern. */ Loading Loading @@ -296,6 +303,18 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } mPaint.setFilterBitmap(true); mCellStates = new CellState[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { mCellStates[i][j] = new CellState(); } } } public CellState[][] getCellStates() { return mCellStates; } private Bitmap getBitmapFor(int resId) { Loading Loading @@ -873,18 +892,22 @@ public class LockPatternView extends View { //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); for (int j = 0; j < 3; j++) { float leftX = paddingLeft + j * squareWidth; drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); float scale = mCellStates[i][j].scale; mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255)); float translationY = mCellStates[i][j].translateY; drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]); } } // Reset the alpha to draw normally mPaint.setAlpha(255); // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless we are in stealth mode) final boolean drawPath = !mInStealthMode; // draw the arrows associated with the path (unless we are in stealth mode) boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms if (drawPath) { for (int i = 0; i < count - 1; i++) { Cell cell = pattern.get(i); Loading @@ -898,7 +921,8 @@ public class LockPatternView extends View { } float leftX = paddingLeft + cell.column * squareWidth; float topY = paddingTop + cell.row * squareHeight; float topY = paddingTop + cell.row * squareHeight + mCellStates[cell.row][cell.column].translateY; drawArrow(canvas, leftX, topY, cell, next); } Loading @@ -919,6 +943,9 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); // Respect translation in animation centerY += mCellStates[cell.row][cell.column].translateY; if (i == 0) { currentPath.moveTo(centerX, centerY); } else { Loading @@ -933,8 +960,6 @@ public class LockPatternView extends View { } canvas.drawPath(currentPath, mPathPaint); } mPaint.setFilterBitmap(oldFlag); // restore default flag } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { Loading Loading @@ -979,7 +1004,8 @@ public class LockPatternView extends View { * @param topY * @param partOfPattern Whether this circle is part of the pattern. */ private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { private void drawCircle(Canvas canvas, float leftX, float topY, float scale, boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; Loading Loading @@ -1019,7 +1045,7 @@ public class LockPatternView extends View { mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY); mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2); mCircleMatrix.preScale(sx, sy); mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); Loading packages/Keyguard/res/values/dimens.xml +1 −1 Original line number Diff line number Diff line Loading @@ -162,5 +162,5 @@ <dimen name="big_font_size">120dp</dimen> <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">24dp</dimen> <dimen name="appear_y_translation_start">32dp</dimen> </resources> packages/Keyguard/src/com/android/keyguard/AppearAnimationCreator.java 0 → 100644 +29 −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.keyguard; import android.animation.Animator; import android.view.animation.Interpolator; /** * An interface which can create animations when starting an appear animation with * {@link com.android.keyguard.AppearAnimationUtils} */ public interface AppearAnimationCreator<T> { void createAnimation(T animatedObject, long delay, long duration, float startTranslationY, Interpolator interpolator, Runnable finishListener); } packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java +83 −43 Original line number Diff line number Diff line Loading @@ -16,84 +16,124 @@ package com.android.keyguard; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.content.Context; import android.view.View; import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; /** * A class to make nice appear transitions for views in a tabular layout. */ public class AppearAnimationUtils { public class AppearAnimationUtils implements AppearAnimationCreator<View> { public static final long APPEAR_DURATION = 220; private final Interpolator mLinearOutSlowIn; private final float mStartTranslation; private final AppearAnimationProperties mProperties = new AppearAnimationProperties(); private final float mDelayScale; public AppearAnimationUtils(Context ctx) { this(ctx, 1.0f, 1.0f); } public AppearAnimationUtils(Context ctx, float delayScaleFactor, float translationScaleFactor) { mLinearOutSlowIn = AnimationUtils.loadInterpolator( ctx, android.R.interpolator.linear_out_slow_in); mStartTranslation = ctx.getResources().getDimensionPixelOffset(R.dimen.appear_y_translation_start); mStartTranslation = ctx.getResources().getDimensionPixelOffset( R.dimen.appear_y_translation_start) * translationScaleFactor; mDelayScale = delayScaleFactor; } public void startAppearAnimation(View[][] views, final Runnable finishListener) { public void startAppearAnimation(View[][] objects, final Runnable finishListener) { startAppearAnimation(objects, finishListener, this); } public <T> void startAppearAnimation(T[][] objects, final Runnable finishListener, AppearAnimationCreator<T> creator) { AppearAnimationProperties properties = getDelays(objects); startAnimations(properties, objects, finishListener, creator); } private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects, final Runnable finishListener, AppearAnimationCreator creator) {; if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) { finishListener.run(); return; } for (int row = 0; row < properties.delays.length; row++) { long[] columns = properties.delays[row]; for (int col = 0; col < columns.length; col++) { long delay = columns[col]; Runnable endRunnable = null; if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) { endRunnable = finishListener; } creator.createAnimation(objects[row][col], delay, APPEAR_DURATION, mStartTranslation, mLinearOutSlowIn, endRunnable); } } } private <T> AppearAnimationProperties getDelays(T[][] items) { long maxDelay = 0; ViewPropertyAnimator maxDelayAnimator = null; for (int row = 0; row < views.length; row++) { View[] columns = views[row]; mProperties.maxDelayColIndex = -1; mProperties.maxDelayRowIndex = -1; mProperties.delays = new long[items.length][]; for (int row = 0; row < items.length; row++) { T[] columns = items[row]; mProperties.delays[row] = new long[columns.length]; for (int col = 0; col < columns.length; col++) { long delay = calculateDelay(row, col); ViewPropertyAnimator animator = startAppearAnimation(columns[col], delay); if (animator != null && delay > maxDelay) { mProperties.delays[row][col] = delay; if (items[row][col] != null && delay > maxDelay) { maxDelay = delay; maxDelayAnimator = animator; mProperties.maxDelayColIndex = col; mProperties.maxDelayRowIndex = row; } } } if (maxDelayAnimator != null) { maxDelayAnimator.setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishListener.run(); return mProperties; } }); } else { finishListener.run(); private long calculateDelay(int row, int col) { return (long) ((row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20) * mDelayScale); } public Interpolator getInterpolator() { return mLinearOutSlowIn; } public float getStartTranslation() { return mStartTranslation; } private ViewPropertyAnimator startAppearAnimation(View view, long delay) { if (view == null) return null; @Override public void createAnimation(View view, long delay, long duration, float startTranslationY, Interpolator interpolator, Runnable endRunnable) { if (view != null) { view.setAlpha(0f); view.setTranslationY(mStartTranslation); view.setTranslationY(startTranslationY); view.animate() .alpha(1f) .translationY(0) .setInterpolator(mLinearOutSlowIn) .setDuration(APPEAR_DURATION) .setStartDelay(delay) .setListener(null); .setInterpolator(interpolator) .setDuration(duration) .setStartDelay(delay); if (view.hasOverlappingRendering()) { view.animate().withLayer(); } return view.animate(); if (endRunnable != null) { view.animate().withEndAction(endRunnable); } private long calculateDelay(int row, int col) { return (long) (row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20); } public TimeInterpolator getInterpolator() { return mLinearOutSlowIn; } public float getStartTranslation() { return mStartTranslation; public class AppearAnimationProperties { public long[][] delays; public int maxDelayRowIndex; public int maxDelayColIndex; } } packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +80 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; Loading @@ -28,10 +31,13 @@ import android.os.Bundle; import android.os.CountDownTimer; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.Button; import android.widget.LinearLayout; Loading @@ -41,7 +47,8 @@ import com.android.internal.widget.LockPatternView; import java.io.IOException; import java.util.List; public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, AppearAnimationCreator<LockPatternView.CellState> { private static final String TAG = "SecurityPatternView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; Loading @@ -59,6 +66,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final AppearAnimationUtils mAppearAnimationUtils; private CountDownTimer mCountdownTimer = null; private LockPatternUtils mLockPatternUtils; Loading Loading @@ -87,6 +95,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private SecurityMessageDisplay mSecurityMessageDisplay; private View mEcaView; private Drawable mBouncerFrame; private ViewGroup mKeyguardBouncerFrame; private KeyguardMessageArea mHelpMessage; enum FooterMode { Normal, Loading @@ -101,6 +111,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mAppearAnimationUtils = new AppearAnimationUtils(context, 1.5f /* delayScale */, 2.0f /* transitionScale */); } public void setKeyguardCallback(KeyguardSecurityCallback callback) { Loading Loading @@ -148,6 +160,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit if (bouncerFrameView != null) { mBouncerFrame = bouncerFrameView.getBackground(); } mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame); mHelpMessage = (KeyguardMessageArea) findViewById(R.id.keyguard_message_area); } private void updateFooter(FooterMode mode) { Loading Loading @@ -403,8 +418,69 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void startAppearAnimation() { // TODO: Fancy animation. setAlpha(0); animate().alpha(1).withLayer().setDuration(200); enableClipping(false); mAppearAnimationUtils.startAppearAnimation( mLockPatternView.getCellStates(), new Runnable() { @Override public void run() { enableClipping(true); } }, this); if (!TextUtils.isEmpty(mHelpMessage.getText())) { mAppearAnimationUtils.createAnimation(mHelpMessage, 0, AppearAnimationUtils.APPEAR_DURATION, mAppearAnimationUtils.getStartTranslation(), mAppearAnimationUtils.getInterpolator(), null /* finishRunnable */); } } private void enableClipping(boolean enable) { setClipChildren(enable); mKeyguardBouncerFrame.setClipToPadding(enable); mKeyguardBouncerFrame.setClipChildren(enable); } @Override public void createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float startTranslationY, Interpolator interpolator, final Runnable finishListener) { animatedCell.scale = 0.0f; animatedCell.translateY = startTranslationY; ValueAnimator animator = ValueAnimator.ofFloat(startTranslationY, 0.0f); animator.setInterpolator(interpolator); animator.setDuration(duration); animator.setStartDelay(delay); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedFraction = animation.getAnimatedFraction(); animatedCell.scale = animatedFraction; animatedCell.translateY = (float) animation.getAnimatedValue(); mLockPatternView.invalidate(); } }); if (finishListener != null) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishListener.run(); } }); // Also animate the Emergency call mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, startTranslationY, interpolator, null); // And the forgot pattern button if (mForgotPatternButton.getVisibility() == View.VISIBLE) { mAppearAnimationUtils.createAnimation(mForgotPatternButton, delay, duration, startTranslationY, interpolator, null); } } animator.start(); mLockPatternView.invalidate(); } } Loading
core/java/com/android/internal/widget/LockPatternView.java +34 −8 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; private final CellState[][] mCellStates; private boolean mDrawingProfilingStarted = false; private Paint mPaint = new Paint(); Loading Loading @@ -187,6 +188,12 @@ public class LockPatternView extends View { } } public static class CellState { public float scale = 1.0f; public float translateY = 0.0f; public float alpha = 1.0f; } /** * How to display the current pattern. */ Loading Loading @@ -296,6 +303,18 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } mPaint.setFilterBitmap(true); mCellStates = new CellState[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { mCellStates[i][j] = new CellState(); } } } public CellState[][] getCellStates() { return mCellStates; } private Bitmap getBitmapFor(int resId) { Loading Loading @@ -873,18 +892,22 @@ public class LockPatternView extends View { //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); for (int j = 0; j < 3; j++) { float leftX = paddingLeft + j * squareWidth; drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); float scale = mCellStates[i][j].scale; mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255)); float translationY = mCellStates[i][j].translateY; drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]); } } // Reset the alpha to draw normally mPaint.setAlpha(255); // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless we are in stealth mode) final boolean drawPath = !mInStealthMode; // draw the arrows associated with the path (unless we are in stealth mode) boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms if (drawPath) { for (int i = 0; i < count - 1; i++) { Cell cell = pattern.get(i); Loading @@ -898,7 +921,8 @@ public class LockPatternView extends View { } float leftX = paddingLeft + cell.column * squareWidth; float topY = paddingTop + cell.row * squareHeight; float topY = paddingTop + cell.row * squareHeight + mCellStates[cell.row][cell.column].translateY; drawArrow(canvas, leftX, topY, cell, next); } Loading @@ -919,6 +943,9 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); // Respect translation in animation centerY += mCellStates[cell.row][cell.column].translateY; if (i == 0) { currentPath.moveTo(centerX, centerY); } else { Loading @@ -933,8 +960,6 @@ public class LockPatternView extends View { } canvas.drawPath(currentPath, mPathPaint); } mPaint.setFilterBitmap(oldFlag); // restore default flag } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { Loading Loading @@ -979,7 +1004,8 @@ public class LockPatternView extends View { * @param topY * @param partOfPattern Whether this circle is part of the pattern. */ private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { private void drawCircle(Canvas canvas, float leftX, float topY, float scale, boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; Loading Loading @@ -1019,7 +1045,7 @@ public class LockPatternView extends View { mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY); mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2); mCircleMatrix.preScale(sx, sy); mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); Loading
packages/Keyguard/res/values/dimens.xml +1 −1 Original line number Diff line number Diff line Loading @@ -162,5 +162,5 @@ <dimen name="big_font_size">120dp</dimen> <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">24dp</dimen> <dimen name="appear_y_translation_start">32dp</dimen> </resources>
packages/Keyguard/src/com/android/keyguard/AppearAnimationCreator.java 0 → 100644 +29 −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.keyguard; import android.animation.Animator; import android.view.animation.Interpolator; /** * An interface which can create animations when starting an appear animation with * {@link com.android.keyguard.AppearAnimationUtils} */ public interface AppearAnimationCreator<T> { void createAnimation(T animatedObject, long delay, long duration, float startTranslationY, Interpolator interpolator, Runnable finishListener); }
packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java +83 −43 Original line number Diff line number Diff line Loading @@ -16,84 +16,124 @@ package com.android.keyguard; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.content.Context; import android.view.View; import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; /** * A class to make nice appear transitions for views in a tabular layout. */ public class AppearAnimationUtils { public class AppearAnimationUtils implements AppearAnimationCreator<View> { public static final long APPEAR_DURATION = 220; private final Interpolator mLinearOutSlowIn; private final float mStartTranslation; private final AppearAnimationProperties mProperties = new AppearAnimationProperties(); private final float mDelayScale; public AppearAnimationUtils(Context ctx) { this(ctx, 1.0f, 1.0f); } public AppearAnimationUtils(Context ctx, float delayScaleFactor, float translationScaleFactor) { mLinearOutSlowIn = AnimationUtils.loadInterpolator( ctx, android.R.interpolator.linear_out_slow_in); mStartTranslation = ctx.getResources().getDimensionPixelOffset(R.dimen.appear_y_translation_start); mStartTranslation = ctx.getResources().getDimensionPixelOffset( R.dimen.appear_y_translation_start) * translationScaleFactor; mDelayScale = delayScaleFactor; } public void startAppearAnimation(View[][] views, final Runnable finishListener) { public void startAppearAnimation(View[][] objects, final Runnable finishListener) { startAppearAnimation(objects, finishListener, this); } public <T> void startAppearAnimation(T[][] objects, final Runnable finishListener, AppearAnimationCreator<T> creator) { AppearAnimationProperties properties = getDelays(objects); startAnimations(properties, objects, finishListener, creator); } private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects, final Runnable finishListener, AppearAnimationCreator creator) {; if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) { finishListener.run(); return; } for (int row = 0; row < properties.delays.length; row++) { long[] columns = properties.delays[row]; for (int col = 0; col < columns.length; col++) { long delay = columns[col]; Runnable endRunnable = null; if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) { endRunnable = finishListener; } creator.createAnimation(objects[row][col], delay, APPEAR_DURATION, mStartTranslation, mLinearOutSlowIn, endRunnable); } } } private <T> AppearAnimationProperties getDelays(T[][] items) { long maxDelay = 0; ViewPropertyAnimator maxDelayAnimator = null; for (int row = 0; row < views.length; row++) { View[] columns = views[row]; mProperties.maxDelayColIndex = -1; mProperties.maxDelayRowIndex = -1; mProperties.delays = new long[items.length][]; for (int row = 0; row < items.length; row++) { T[] columns = items[row]; mProperties.delays[row] = new long[columns.length]; for (int col = 0; col < columns.length; col++) { long delay = calculateDelay(row, col); ViewPropertyAnimator animator = startAppearAnimation(columns[col], delay); if (animator != null && delay > maxDelay) { mProperties.delays[row][col] = delay; if (items[row][col] != null && delay > maxDelay) { maxDelay = delay; maxDelayAnimator = animator; mProperties.maxDelayColIndex = col; mProperties.maxDelayRowIndex = row; } } } if (maxDelayAnimator != null) { maxDelayAnimator.setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishListener.run(); return mProperties; } }); } else { finishListener.run(); private long calculateDelay(int row, int col) { return (long) ((row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20) * mDelayScale); } public Interpolator getInterpolator() { return mLinearOutSlowIn; } public float getStartTranslation() { return mStartTranslation; } private ViewPropertyAnimator startAppearAnimation(View view, long delay) { if (view == null) return null; @Override public void createAnimation(View view, long delay, long duration, float startTranslationY, Interpolator interpolator, Runnable endRunnable) { if (view != null) { view.setAlpha(0f); view.setTranslationY(mStartTranslation); view.setTranslationY(startTranslationY); view.animate() .alpha(1f) .translationY(0) .setInterpolator(mLinearOutSlowIn) .setDuration(APPEAR_DURATION) .setStartDelay(delay) .setListener(null); .setInterpolator(interpolator) .setDuration(duration) .setStartDelay(delay); if (view.hasOverlappingRendering()) { view.animate().withLayer(); } return view.animate(); if (endRunnable != null) { view.animate().withEndAction(endRunnable); } private long calculateDelay(int row, int col) { return (long) (row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20); } public TimeInterpolator getInterpolator() { return mLinearOutSlowIn; } public float getStartTranslation() { return mStartTranslation; public class AppearAnimationProperties { public long[][] delays; public int maxDelayRowIndex; public int maxDelayColIndex; } }
packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +80 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; Loading @@ -28,10 +31,13 @@ import android.os.Bundle; import android.os.CountDownTimer; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.Button; import android.widget.LinearLayout; Loading @@ -41,7 +47,8 @@ import com.android.internal.widget.LockPatternView; import java.io.IOException; import java.util.List; public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, AppearAnimationCreator<LockPatternView.CellState> { private static final String TAG = "SecurityPatternView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; Loading @@ -59,6 +66,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final AppearAnimationUtils mAppearAnimationUtils; private CountDownTimer mCountdownTimer = null; private LockPatternUtils mLockPatternUtils; Loading Loading @@ -87,6 +95,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private SecurityMessageDisplay mSecurityMessageDisplay; private View mEcaView; private Drawable mBouncerFrame; private ViewGroup mKeyguardBouncerFrame; private KeyguardMessageArea mHelpMessage; enum FooterMode { Normal, Loading @@ -101,6 +111,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mAppearAnimationUtils = new AppearAnimationUtils(context, 1.5f /* delayScale */, 2.0f /* transitionScale */); } public void setKeyguardCallback(KeyguardSecurityCallback callback) { Loading Loading @@ -148,6 +160,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit if (bouncerFrameView != null) { mBouncerFrame = bouncerFrameView.getBackground(); } mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame); mHelpMessage = (KeyguardMessageArea) findViewById(R.id.keyguard_message_area); } private void updateFooter(FooterMode mode) { Loading Loading @@ -403,8 +418,69 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void startAppearAnimation() { // TODO: Fancy animation. setAlpha(0); animate().alpha(1).withLayer().setDuration(200); enableClipping(false); mAppearAnimationUtils.startAppearAnimation( mLockPatternView.getCellStates(), new Runnable() { @Override public void run() { enableClipping(true); } }, this); if (!TextUtils.isEmpty(mHelpMessage.getText())) { mAppearAnimationUtils.createAnimation(mHelpMessage, 0, AppearAnimationUtils.APPEAR_DURATION, mAppearAnimationUtils.getStartTranslation(), mAppearAnimationUtils.getInterpolator(), null /* finishRunnable */); } } private void enableClipping(boolean enable) { setClipChildren(enable); mKeyguardBouncerFrame.setClipToPadding(enable); mKeyguardBouncerFrame.setClipChildren(enable); } @Override public void createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float startTranslationY, Interpolator interpolator, final Runnable finishListener) { animatedCell.scale = 0.0f; animatedCell.translateY = startTranslationY; ValueAnimator animator = ValueAnimator.ofFloat(startTranslationY, 0.0f); animator.setInterpolator(interpolator); animator.setDuration(duration); animator.setStartDelay(delay); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedFraction = animation.getAnimatedFraction(); animatedCell.scale = animatedFraction; animatedCell.translateY = (float) animation.getAnimatedValue(); mLockPatternView.invalidate(); } }); if (finishListener != null) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishListener.run(); } }); // Also animate the Emergency call mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, startTranslationY, interpolator, null); // And the forgot pattern button if (mForgotPatternButton.getVisibility() == View.VISIBLE) { mAppearAnimationUtils.createAnimation(mForgotPatternButton, delay, duration, startTranslationY, interpolator, null); } } animator.start(); mLockPatternView.invalidate(); } }