diff options
author | Spike Sprague <spikuru@google.com> | 2014-08-13 21:14:50 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-08-13 16:52:03 +0000 |
commit | eb9443226ee117779e2396c2f7cee9789af80cf8 (patch) | |
tree | 72bd740d96c75a26d56d390a1a277abd0bb6b3ff | |
parent | 62c367f2199d826646cd2c665ed358b339c4b313 (diff) | |
parent | 0496fcaa51f88f6c753975473b971941e4090cfa (diff) | |
download | android_packages_apps_Camera2-eb9443226ee117779e2396c2f7cee9789af80cf8.tar.gz android_packages_apps_Camera2-eb9443226ee117779e2396c2f7cee9789af80cf8.tar.bz2 android_packages_apps_Camera2-eb9443226ee117779e2396c2f7cee9789af80cf8.zip |
Merge "update mode switch transition anims" into ub-camera-glacier
-rw-r--r-- | res/values/colors.xml | 2 | ||||
-rw-r--r-- | src/com/android/camera/app/CameraAppUI.java | 1 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeIconView.java | 86 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeListView.java | 243 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeSelectorItem.java | 26 | ||||
-rw-r--r-- | src/com/android/camera/ui/TouchCircleDrawable.java | 310 |
6 files changed, 477 insertions, 191 deletions
diff --git a/res/values/colors.xml b/res/values/colors.xml index f8bd430a8..a8ee96b61 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -102,4 +102,6 @@ <color name="dialog_text_color">#6D6D6D</color> <color name="settings_cling_color">#2962FF</color> + + <color name="mode_icon_hover_highlight">#2DFFFFFF</color> </resources> diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java index 43e207c25..8a10e2cb6 100644 --- a/src/com/android/camera/app/CameraAppUI.java +++ b/src/com/android/camera/app/CameraAppUI.java @@ -1396,6 +1396,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener, mModeCoverState = COVER_SHOWN; int lastIndex = mController.getCurrentModuleIndex(); + // Actual mode teardown / new mode initialization happens here mController.onModeSelected(modeIndex); int currentIndex = mController.getCurrentModuleIndex(); diff --git a/src/com/android/camera/ui/ModeIconView.java b/src/com/android/camera/ui/ModeIconView.java index 42389f842..e7e2701c6 100644 --- a/src/com/android/camera/ui/ModeIconView.java +++ b/src/com/android/camera/ui/ModeIconView.java @@ -16,8 +16,6 @@ package com.android.camera.ui; -import android.animation.Animator; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -36,19 +34,16 @@ import com.android.camera2.R; * whereas a state list drawable would require a different drawable for each state. */ public class ModeIconView extends View { - - private static final int SELECTION_ANIMATION_DURATION_MS = 500; - private static final int HIGHLIGHT_STATE_ALPHA = 0x4C; private boolean mHighlightIsOn = false; private final GradientDrawable mBackground; - private final GradientDrawable mHighlightDrawable; + private final GradientDrawable mHoverDrawable; + private final int mIconBackgroundSize; private int mHighlightColor; private final int mBackgroundDefaultColor; private final int mIconDrawableSize; private Drawable mIconDrawable = null; private boolean mSelected = false; - private ValueAnimator mSelectionAnimation; public ModeIconView(Context context, AttributeSet attrs) { super(context, attrs); @@ -58,11 +53,13 @@ public class ModeIconView extends View { mBackground = (GradientDrawable) getResources() .getDrawable(R.drawable.mode_icon_background).mutate(); mBackground.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize); - mHighlightDrawable = (GradientDrawable) getResources() + mHoverDrawable = (GradientDrawable) getResources() .getDrawable(R.drawable.mode_icon_background).mutate(); - mHighlightDrawable.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize); + mHoverDrawable.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize); mIconDrawableSize = getResources().getDimensionPixelSize( R.dimen.mode_selector_icon_drawable_size); + + mHoverDrawable.setColor(getResources().getColor(R.color.mode_icon_hover_highlight)); } /** @@ -72,6 +69,7 @@ public class ModeIconView extends View { */ public void setIconDrawable(Drawable drawable) { mIconDrawable = drawable; + // Center icon in the background. if (mIconDrawable != null) { mIconDrawable.setBounds(mIconBackgroundSize / 2 - mIconDrawableSize / 2, @@ -86,14 +84,27 @@ public class ModeIconView extends View { public void draw(Canvas canvas) { super.draw(canvas); if (mHighlightIsOn && !mSelected) { - mHighlightDrawable.draw(canvas); + mHoverDrawable.draw(canvas); } else { mBackground.draw(canvas); } if (mIconDrawable != null) { mIconDrawable.draw(canvas); } + } + /** + * @return A clone of the icon drawable associated with this view. + */ + public Drawable getIconDrawableClone() { + return mIconDrawable.getConstantState().newDrawable(); + } + + /** + * @return The size of the icon drawable. + */ + public int getIconDrawableSize() { + return mIconDrawableSize; } /** @@ -102,6 +113,7 @@ public class ModeIconView extends View { * * @param selected true when selected, false otherwise. */ + @Override public void setSelected(boolean selected) { if (selected) { mBackground.setColor(mHighlightColor); @@ -109,55 +121,12 @@ public class ModeIconView extends View { } else { mBackground.setColor(mBackgroundDefaultColor); } + mSelected = selected; invalidate(); } /** - * Animate mode icon background from highlight state to selected state. - * TODO: Remove the selection animation if UX agrees to do so. - */ - public void selectWithAnimation() { - mSelected = true; - mHighlightIsOn = false; - // Animate alpha between highlight alpha to selected state alpha. - mSelectionAnimation = ValueAnimator.ofInt(HIGHLIGHT_STATE_ALPHA, 255); - mSelectionAnimation.setDuration(SELECTION_ANIMATION_DURATION_MS); - mSelectionAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int alpha = (Integer) animation.getAnimatedValue(); - int backgroundColor = (mHighlightColor & 0xffffff) | (alpha << 24); - mBackground.setColor(backgroundColor); - invalidate(); - } - }); - mSelectionAnimation.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - // Do nothing. - } - - @Override - public void onAnimationEnd(Animator animation) { - mSelectionAnimation = null; - invalidate(); - } - - @Override - public void onAnimationCancel(Animator animation) { - // Do nothing. - } - - @Override - public void onAnimationRepeat(Animator animation) { - // Do nothing. - } - }); - mSelectionAnimation.start(); - } - - /** * This gets called when the highlighted state is changed. When highlighted, * a ring shaped drawable of a solid pre-defined color will be drawn on top * of the background drawable to indicate highlight state. @@ -176,7 +145,12 @@ public class ModeIconView extends View { */ public void setHighlightColor(int highlightColor) { mHighlightColor = highlightColor; - highlightColor = (highlightColor & 0xffffff) | 0x4C000000; - mHighlightDrawable.setColor(highlightColor); + } + + /** + * @return The highlightColor color the the highlight state. + */ + public int getHighlightColor() { + return mHighlightColor; } } diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java index bca4c3f8f..78ab9863c 100644 --- a/src/com/android/camera/ui/ModeListView.java +++ b/src/com/android/camera/ui/ModeListView.java @@ -17,6 +17,7 @@ package com.android.camera.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -25,13 +26,13 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; -import android.os.AsyncTask; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -826,7 +827,6 @@ public class ModeListView extends FrameLayout * mode list will transition into fully hidden state. */ private class SelectedState extends ModeListState { - public SelectedState(ModeSelectorItem selectedItem) { final int modeId = selectedItem.getModeId(); // Un-highlight all the modes. @@ -834,8 +834,7 @@ public class ModeListView extends FrameLayout mModeSelectorItems[i].setHighlighted(false); mModeSelectorItems[i].setSelected(false); } - // Select the focused item. - selectedItem.setSelected(true); + PeepholeAnimationEffect effect = new PeepholeAnimationEffect(); effect.setSize(mWidth, mHeight); @@ -853,6 +852,7 @@ public class ModeListView extends FrameLayout iconY -= location[1]; effect.setAnimationStartingPosition(iconX, iconY); + effect.setModeSpecificColor(selectedItem.getHighlightColor()); if (mScreenShotProvider != null) { effect.setBackground(mScreenShotProvider .getPreviewFrame(PREVIEW_DOWN_SAMPLE_FACTOR), @@ -860,17 +860,8 @@ public class ModeListView extends FrameLayout effect.setBackgroundOverlay(mScreenShotProvider.getPreviewOverlayAndControls()); } mCurrentAnimationEffects = effect; + effect.startFadeoutAnimation(null, selectedItem, iconX, iconY, modeId); invalidate(); - - // Post mode selection runnable to the end of the message queue - // so that current UI changes can finish before mode initialization - // clogs up UI thread. - post(new Runnable() { - @Override - public void run() { - onModeSelected(modeId); - } - }); } @Override @@ -880,27 +871,12 @@ public class ModeListView extends FrameLayout @Override public void startModeSelectionAnimation() { - mCurrentAnimationEffects.startAnimation(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - + mCurrentAnimationEffects.startAnimation(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCurrentAnimationEffects = null; mCurrentStateManager.setCurrentState(new FullyHiddenState()); } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } }); } @@ -1137,7 +1113,7 @@ public class ModeListView extends FrameLayout // Mark the supported modes in a boolean array to preserve the // sequence of the modes - SparseArray<Boolean> modeIsSupported = new SparseArray<Boolean>(); + SparseBooleanArray modeIsSupported = new SparseBooleanArray(); for (int i = 0; i < modeIndexList.size(); i++) { int mode = modeIndexList.get(i); modeIsSupported.put(mode, true); @@ -1668,6 +1644,7 @@ public class ModeListView extends FrameLayout * When visible width of list is changed, the background of the list needs * to darken/lighten correspondingly. */ + @Override public void onVisibleWidthChanged(int visibleWidth) { mVisibleWidth = visibleWidth; @@ -1835,7 +1812,6 @@ public class ModeListView extends FrameLayout animateModeItemsInOrder = false; delay *= -1; } - int focusItem = mFocusItem == NO_ITEM_SELECTED ? 0 : mFocusItem; for (int i = 0; i < mTotalModes; i++) { ObjectAnimator animator; if (animateModeItemsInOrder) { @@ -1908,11 +1884,11 @@ public class ModeListView extends FrameLayout float position; int slowZone = (int) (maxWidth * SLOW_ZONE_PERCENTAGE); if (lastVisibleWidth < (maxWidth - slowZone)) { - position = VELOCITY_THRESHOLD * (float) timeElapsed + lastVisibleWidth; + position = VELOCITY_THRESHOLD * timeElapsed + lastVisibleWidth; } else { float percentageIntoSlowZone = (lastVisibleWidth - (maxWidth - slowZone)) / slowZone; float velocity = (1 - percentageIntoSlowZone) * VELOCITY_THRESHOLD; - position = velocity * (float) timeElapsed + lastVisibleWidth; + position = velocity * timeElapsed + lastVisibleWidth; } position = Math.min(maxWidth, position); return position; @@ -1924,22 +1900,29 @@ public class ModeListView extends FrameLayout private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 300; private final Paint mMaskPaint = new Paint(); - private final Paint mBackgroundPaint = new Paint(); private final RectF mBackgroundDrawArea = new RectF(); - private int mWidth; - private int mHeight; private int mPeepHoleCenterX = UNSET; private int mPeepHoleCenterY = UNSET; private float mRadius = 0f; private ValueAnimator mPeepHoleAnimator; private Bitmap mBackground; - private Bitmap mBlurredBackground; private Bitmap mBackgroundOverlay; + private Paint mCirclePaint = new Paint(); + private Paint mCoverPaint = new Paint(); + + private TouchCircleDrawable mCircleDrawable; + public PeepholeAnimationEffect() { mMaskPaint.setAlpha(0); mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + mCirclePaint.setColor(0); + mCirclePaint.setAlpha(0); + + mCoverPaint.setColor(0); + mCoverPaint.setAlpha(0); } @Override @@ -1953,24 +1936,18 @@ public class ModeListView extends FrameLayout return true; } - @Override - public void drawForeground(Canvas canvas) { - // Draw the circle in clear mode - if (mPeepHoleAnimator != null) { - // Draw a transparent circle using clear mode - canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mMaskPaint); - } - } - public void setAnimationStartingPosition(int x, int y) { mPeepHoleCenterX = x; mPeepHoleCenterY = y; } + public void setModeSpecificColor(int color) { + mCirclePaint.setColor(color & 0x00ffffff); + } + /** * Sets the bitmap to be drawn in the background and the drawArea to draw - * the bitmap. In the meantime, start processing the image in a background - * thread to get a blurred background image. + * the bitmap. * * @param background image to be drawn in the background * @param drawArea area to draw the background image @@ -1978,8 +1955,6 @@ public class ModeListView extends FrameLayout public void setBackground(Bitmap background, RectF drawArea) { mBackground = background; mBackgroundDrawArea.set(drawArea); - new BlurTask().execute(Bitmap.createScaledBitmap(background, background.getWidth(), - background.getHeight(), true)); } /** @@ -1989,37 +1964,26 @@ public class ModeListView extends FrameLayout mBackgroundOverlay = overlay; } - /** - * This gets called when a blurred image of the background is generated. - * Start an animation to fade in the blur. - * - * @param blur blurred image of the background. - */ - public void setBlurredBackground(Bitmap blur) { - mBlurredBackground = blur; - // Start fade in. - ObjectAnimator alpha = ObjectAnimator.ofInt(mBackgroundPaint, "alpha", 80, 255); - alpha.setDuration(250); - alpha.setInterpolator(Gusterpolator.INSTANCE); - alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - invalidate(); - } - }); - alpha.start(); - invalidate(); - } - @Override public void drawBackground(Canvas canvas) { if (mBackground != null && mBackgroundOverlay != null) { - canvas.drawARGB(255, 0, 0, 0); canvas.drawBitmap(mBackground, null, mBackgroundDrawArea, null); - if (mBlurredBackground != null) { - canvas.drawBitmap(mBlurredBackground, null, mBackgroundDrawArea, mBackgroundPaint); - } + canvas.drawPaint(mCoverPaint); canvas.drawBitmap(mBackgroundOverlay, 0, 0, null); + + if (mCircleDrawable != null) { + mCircleDrawable.draw(canvas); + } + } + } + + @Override + public void drawForeground(Canvas canvas) { + // Draw the circle in clear mode + if (mPeepHoleAnimator != null) { + // Draw a transparent circle using clear mode + canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mMaskPaint); + canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mCirclePaint); } } @@ -2030,6 +1994,62 @@ public class ModeListView extends FrameLayout return (mBackground == null || mBackgroundOverlay == null); } + public void startFadeoutAnimation(Animator.AnimatorListener listener, + final ModeSelectorItem selectedItem, + int x, int y, final int modeId) { + mCoverPaint.setColor(0); + mCoverPaint.setAlpha(0); + + ValueAnimator alphaAnimator = ValueAnimator.ofInt(0, 255); + alphaAnimator.setDuration(100); + alphaAnimator.setInterpolator(Gusterpolator.INSTANCE); + alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mCoverPaint.setAlpha((Integer) animation.getAnimatedValue()); + invalidate(); + } + }); + if (listener != null) { + alphaAnimator.addListener(listener); + } + + int size = getContext().getResources() + .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width); + mCircleDrawable = new TouchCircleDrawable(getContext().getResources()); + mCircleDrawable.setIconDrawable( + selectedItem.getIcon().getIconDrawableClone(), + selectedItem.getIcon().getIconDrawableSize()); + mCircleDrawable.setSize(size, size); + mCircleDrawable.setCenter(new Point(x, y)); + mCircleDrawable.setColor(selectedItem.getHighlightColor()); + mCircleDrawable.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + invalidate(); + } + }); + + mCircleDrawable.setAnimatorListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Post mode selection runnable to the end of the message queue + // so that current UI changes can finish before mode initialization + // clogs up UI thread. + post(new Runnable() { + @Override + public void run() { + // Select the focused item. + selectedItem.setSelected(true); + onModeSelected(modeId); + } + }); + } + }); + mCircleDrawable.animate(); + alphaAnimator.start(); + } + @Override public void startAnimation(Animator.AnimatorListener listener) { if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) { @@ -2047,7 +2067,7 @@ public class ModeListView extends FrameLayout int startRadius = getResources().getDimensionPixelSize( R.dimen.mode_selector_icon_block_width) / 2; - mPeepHoleAnimator = ValueAnimator.ofFloat(0, endRadius); + mPeepHoleAnimator = ValueAnimator.ofFloat(startRadius, endRadius); mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS); mPeepHoleAnimator.setInterpolator(Gusterpolator.INSTANCE); mPeepHoleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -2063,7 +2083,7 @@ public class ModeListView extends FrameLayout mPeepHoleAnimator.addListener(listener); } - mPeepHoleAnimator.addListener(new Animator.AnimatorListener() { + mPeepHoleAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { // Sets a HW layer on the view for the animation. @@ -2075,19 +2095,38 @@ public class ModeListView extends FrameLayout // Sets the layer type back to NONE as a workaround for b/12594617. setLayerType(LAYER_TYPE_NONE, null); } + }); + mCirclePaint.setAlpha(255); + mCoverPaint.setAlpha(255); + ValueAnimator alphaAnimator = ValueAnimator.ofInt(255, 0); + alphaAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS); + alphaAnimator.setInterpolator(Gusterpolator.INSTANCE); + alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override - public void onAnimationCancel(Animator animation) { - + public void onAnimationUpdate(ValueAnimator animation) { + int alpha = (Integer) animation.getAnimatedValue(); + mCirclePaint.setAlpha(alpha); + mCoverPaint.setAlpha(alpha); } - + }); + alphaAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationRepeat(Animator animation) { + public void onAnimationStart(Animator animation) { + // Sets a HW layer on the view for the animation. + setLayerType(LAYER_TYPE_HARDWARE, null); + } + @Override + public void onAnimationEnd(Animator animation) { + // Sets the layer type back to NONE as a workaround for b/12594617. + setLayerType(LAYER_TYPE_NONE, null); } }); + mPeepHoleAnimator.start(); + alphaAnimator.start(); } @Override @@ -2103,41 +2142,5 @@ public class ModeListView extends FrameLayout return true; } } - - private class BlurTask extends AsyncTask<Bitmap, Integer, Bitmap> { - - // Gaussian blur mask size. - private static final int MASK_SIZE = 7; - @Override - protected Bitmap doInBackground(Bitmap... params) { - - Bitmap intermediateBitmap = params[0]; - int factor = 4; - Bitmap lowResPreview = Bitmap.createScaledBitmap(intermediateBitmap, - intermediateBitmap.getWidth() / factor, - intermediateBitmap.getHeight() / factor, true); - - int width = lowResPreview.getWidth(); - int height = lowResPreview.getHeight(); - - if (mInputPixels == null || mInputPixels.length < width * height) { - mInputPixels = new int[width * height]; - mOutputPixels = new int[width * height]; - } - lowResPreview.getPixels(mInputPixels, 0, width, 0, 0, width, height); - CameraUtil.blur(mInputPixels, mOutputPixels, width, height, MASK_SIZE); - lowResPreview.setPixels(mOutputPixels, 0, width, 0, 0, width, height); - - intermediateBitmap.recycle(); - return Bitmap.createScaledBitmap(lowResPreview, width * factor, - height * factor, true); - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - setBlurredBackground(bitmap); - } - }; } - } diff --git a/src/com/android/camera/ui/ModeSelectorItem.java b/src/com/android/camera/ui/ModeSelectorItem.java index c23641d64..8b535b4c5 100644 --- a/src/com/android/camera/ui/ModeSelectorItem.java +++ b/src/com/android/camera/ui/ModeSelectorItem.java @@ -22,7 +22,6 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; -import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; @@ -48,19 +47,13 @@ class ModeSelectorItem extends FrameLayout { public static final int FLY_IN = 1; public static final int FLY_OUT = 2; - private static final int SHADE_WIDTH_PIX = 100; - private TextView mText; private ModeIconView mIcon; private int mVisibleWidth = 0; private final int mMinVisibleWidth; private VisibleWidthChangedListener mListener = null; - private int mDrawingMode = FLY_IN; - private int mHeight; private int mWidth; - private int mDefaultBackgroundColor; - private int mDefaultTextColor; private int mModeId; /** @@ -92,11 +85,9 @@ class ModeSelectorItem extends FrameLayout { "Roboto-Medium.ttf"); } mText.setTypeface(typeface); - mDefaultTextColor = mText.getCurrentTextColor(); } public void setDefaultBackgroundColor(int color) { - mDefaultBackgroundColor = color; setBackgroundColor(color); } @@ -112,6 +103,7 @@ class ModeSelectorItem extends FrameLayout { mIcon.setHighlighted(highlighted); } + @Override public void setSelected(boolean selected) { mIcon.setSelected(selected); } @@ -145,7 +137,6 @@ class ModeSelectorItem extends FrameLayout { * to right) */ public void onSwipeModeChanged(boolean swipeIn) { - mDrawingMode = swipeIn ? FLY_IN : FLY_OUT; mText.setTranslationX(0); } @@ -157,11 +148,9 @@ class ModeSelectorItem extends FrameLayout { public void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mWidth = right - left; - mHeight = bottom - top; if (changed && mVisibleWidth > 0) { // Reset mode list to full screen setVisibleWidth(mWidth); - mDrawingMode = FLY_OUT; } } @@ -244,6 +233,13 @@ class ModeSelectorItem extends FrameLayout { } /** + * @return highlightColor color for the highlight state + */ + public int getHighlightColor() { + return mIcon.getHighlightColor(); + } + + /** * Gets the maximum visible width of the mode icon. The mode item will be * full shown when the mode icon has max visible width. */ @@ -279,10 +275,10 @@ class ModeSelectorItem extends FrameLayout { } /** - * Animate mode icon to selected state. + * @return The {@link ModeIconView} attached to this item. */ - public void selectWithAnimation() { - mIcon.selectWithAnimation(); + public ModeIconView getIcon() { + return mIcon; } /** diff --git a/src/com/android/camera/ui/TouchCircleDrawable.java b/src/com/android/camera/ui/TouchCircleDrawable.java new file mode 100644 index 000000000..f177a1767 --- /dev/null +++ b/src/com/android/camera/ui/TouchCircleDrawable.java @@ -0,0 +1,310 @@ +/* + * 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.camera.ui; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import com.android.camera.util.Gusterpolator; +import com.android.camera2.R; + +/** + * This class implements a circular drawable that starts with a zero radius + * and can be triggered to animate expand to a given radius. + * <p> + * There are three colors associated with this drawable: + * <p> + * A background color, which is loaded from a resource + * R.color.mode_icon_hover_highlight. + * <p> + * A base color, which is attached a circle that is animate expanded first and + * drawn underneath the main color. + * <p> + * And, a main color, which is attached to the main circle that is expanded last + * and is drawn on top of the other colors. + * <p> + * The driving purpose for this class is to implement a Material-like look and + * feel for mode switcher touch events. + */ +public class TouchCircleDrawable extends Drawable { + private static final int BASE_CIRCLE_ANIM_DURATION_MS = 200; + private static final int CIRCLE_ANIM_DURATION_MS = 130; + private static final int CIRCLE_ANIM_DURATION_DELAY_MS = 100; + + private Paint mColorPaint = new Paint(); + private Paint mBasePaint = new Paint(); + private Paint mBackgroundPaint = new Paint(); + private int mColor; + private int mBaseColor; + private int mColorAlpha = 0xff; + private int mBaseAlpha = 0x4b; + private int mColorRadius; + private int mBaseRadius; + private int mBackgroundRadius; + private Drawable mIconDrawable; + private int mIconDrawableSize; + private boolean mDrawBackground; + + private Animator.AnimatorListener mAnimatorListener; + private ValueAnimator.AnimatorUpdateListener mUpdateListener; + + private static final int INVALID = -1; + private int mW = INVALID; + private int mH = INVALID; + private Point mCenter; + + /** + * Constructor + * + * @param resources Resources, needed to poke around for the background + * color value. + * @param color The main this circle drawable expands to. + * @param baseColor The color of the initial expanded circle + * (draws behind the main color). + */ + public TouchCircleDrawable(Resources resources, int color, int baseColor) { + super(); + + mColorPaint.setAntiAlias(true); + mBasePaint.setAntiAlias(true); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setColor(resources.getColor(R.color.mode_icon_hover_highlight)); + + setColor(color); + setBaseColor(baseColor); + } + + /** + * Constructor + * + * @param resources Resources, needed to poke around for the background color value. + */ + public TouchCircleDrawable(Resources resources) { + this(resources, 0xffffff, 0xffffff); + } + + /** + * Set the size of this drawable. + * + * @param w Width to set. + * @param h Height to set. + */ + public void setSize(int w, int h) { + mW = w; + mH = h; + } + + /** + * Set the center of the circle for this drawable. + * + * @param p The center point. + */ + public void setCenter(Point p) { + mCenter = p; + updateIconBounds(); + } + + /** + * @return The center of this drawable. + */ + public Point getCenter() { + return mCenter; + } + + @Override + public void draw(Canvas canvas) { + int w = mW; + int h = mH; + + if (w == INVALID || h == INVALID || mCenter == null) { + return; + } + + if (mDrawBackground) { + canvas.drawCircle(mCenter.x, mCenter.y, mBackgroundRadius, mBackgroundPaint); + } + canvas.drawCircle(mCenter.x, mCenter.y, mBaseRadius, mBasePaint); + canvas.drawCircle(mCenter.x, mCenter.y, mColorRadius, mColorPaint); + if (mIconDrawable != null) { + mIconDrawable.draw(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + mColorAlpha = alpha; + } + + @Override + public void setColorFilter(ColorFilter cf) { + mColorPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + /** + * Set the main color. + * + * @param color The main color. + */ + public void setColor(int color) { + mColor = color; + mColorPaint.setColor(mColor); + mColorPaint.setAlpha(mColorAlpha); + } + + /** + * Set the base color + * + * @param color The color of the initial expanded circle (draws behind the main color). + */ + public void setBaseColor(int color) { + mBaseColor = color; + mBasePaint.setColor(mBaseColor); + mBasePaint.setAlpha(mBaseAlpha); + } + + public void setIconDrawable(Drawable d, int size) { + mIconDrawable = d; + mIconDrawableSize = size; + updateIconBounds(); + } + + private void updateIconBounds() { + if (mCenter != null) { + mIconDrawable.setBounds( + mCenter.x - mIconDrawableSize/2, mCenter.y - mIconDrawableSize/2, + mCenter.x + mIconDrawableSize/2, mCenter.y + mIconDrawableSize/2); + } + } + + /** + * Start the expand animation. + */ + public void animate() { + mBackgroundRadius = Math.min(mW/2, mH/2); + + final ValueAnimator baseAnimator = + ValueAnimator.ofInt(0, Math.min(mW/2, mH/2)); + baseAnimator.setDuration(BASE_CIRCLE_ANIM_DURATION_MS); + baseAnimator.setInterpolator(Gusterpolator.INSTANCE); + baseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mBaseRadius = (Integer) animation.getAnimatedValue(); + invalidateSelf(); + if (mUpdateListener != null) { + mUpdateListener.onAnimationUpdate(animation); + } + } + }); + + final ValueAnimator colorAnimator = + ValueAnimator.ofInt(0, Math.min(mW/2, mH/2)); + colorAnimator.setDuration(CIRCLE_ANIM_DURATION_MS); + colorAnimator.setStartDelay(CIRCLE_ANIM_DURATION_DELAY_MS); + colorAnimator.setInterpolator(Gusterpolator.INSTANCE); + colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mColorRadius = (Integer) animation.getAnimatedValue(); + invalidateSelf(); + if (mUpdateListener != null) { + mUpdateListener.onAnimationUpdate(animation); + } + } + }); + + AnimatorSet s = new AnimatorSet(); + s.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mDrawBackground = true; + + if (mAnimatorListener != null) { + mAnimatorListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + mDrawBackground = false; + + if (mAnimatorListener != null) { + mAnimatorListener.onAnimationEnd(animation); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mDrawBackground = false; + + if (mAnimatorListener != null) { + mAnimatorListener.onAnimationCancel(animation); + } + } + + @Override + public void onAnimationRepeat(Animator animation) { + if (mAnimatorListener != null) { + mAnimatorListener.onAnimationRepeat(animation); + } + } + }); + s.playTogether(baseAnimator, colorAnimator); + s.start(); + } + + /** + * Reset this drawable to its initial, preanimated state. + */ + public void reset() { + mBaseRadius = mColorRadius = 0; + } + + /** + * Set an {@link android.animation.Animator.AnimatorListener} to be + * attached to the animation. + * + * @param listener The listener. + */ + public void setAnimatorListener(Animator.AnimatorListener listener) { + mAnimatorListener = listener; + } + + /** + * Set an {@link android.animation.ValueAnimator} to be + * attached to the animation. + * + * @param listener The listener. + */ + public void setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) { + mUpdateListener = listener; + } +} |