summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoris Liu <tianliu@google.com>2013-12-11 18:39:14 -0800
committerDoris Liu <tianliu@google.com>2013-12-13 16:28:32 -0800
commitb6eaa8c70da19fb3233c3c4b1a2625ce90fc35af (patch)
tree7ce04801fff55e7c4654b87d3db01c43b276136e
parent83ebad2e83e9b0917b263d17623f43764665ad7b (diff)
downloadandroid_packages_apps_Camera2-b6eaa8c70da19fb3233c3c4b1a2625ce90fc35af.tar.gz
android_packages_apps_Camera2-b6eaa8c70da19fb3233c3c4b1a2625ce90fc35af.tar.bz2
android_packages_apps_Camera2-b6eaa8c70da19fb3233c3c4b1a2625ce90fc35af.zip
Mode selection animation
Change-Id: I3741f71add7e506a974602cefa6edfdbeb442fa0
-rw-r--r--res/layout/mode_list_layout.xml2
-rw-r--r--res/values/colors.xml1
-rw-r--r--src/com/android/camera/app/CameraAppUI.java56
-rw-r--r--src/com/android/camera/ui/ModeListView.java205
-rw-r--r--src/com/android/camera/ui/ModeSelectorItem.java25
-rw-r--r--src/com/android/camera/widget/AnimationEffects.java51
6 files changed, 322 insertions, 18 deletions
diff --git a/res/layout/mode_list_layout.xml b/res/layout/mode_list_layout.xml
index 9b2172e6d..24ddf95fe 100644
--- a/res/layout/mode_list_layout.xml
+++ b/res/layout/mode_list_layout.xml
@@ -20,6 +20,8 @@
android:fillViewport="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layerType="hardware"
+ android:background="@null"
android:visibility="invisible" >
<LinearLayout
android:id="@+id/mode_list"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 81c64c598..73f78bd07 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -74,6 +74,7 @@
<color name="mode_selector_text_color">#6d6d6d</color>
<color name="mode_selector_background_light">#f5f5f5</color>
<color name="mode_selector_background_dark">#e7e7e7</color>
+ <color name="mode_selector_text_highlight_color">#ffffffff</color>
<color name="mode_list_background">#00000000</color>
<color name="camera_mode_color">#76a7fa</color>
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index 46377810a..eeac0cada 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -194,6 +194,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
private int mModeCoverState = COVER_HIDDEN;
private FilmstripBottomControls mFilmstripBottomControls;
private FilmstripContentPanel mFilmstripPanel;
+ private Runnable mHideCoverRunnable;
// TODO this isn't used by all modules universally, should be part of a util class or something
/**
@@ -294,7 +295,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
|| mSwipeState != IDLE) {
- return true;
+ return false;
}
int deltaX = (int) (ev.getX() - mDown.getX());
@@ -429,19 +430,28 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
int colorId = ModeListView.getModeThemeColor(modeId);
int iconId = ModeListView.getModeIconResourceId(modeId);
mModeTransitionView.setupModeCover(colorId, iconId);
+ mHideCoverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
+ @Override
+ public void onAnimationFinished(boolean success) {
+ if (success) {
+ // Show shimmy in SHIMMY_DELAY_MS
+ mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
+ }
+ }
+ });
+ }
+ };
mModeCoverState = COVER_SHOWN;
}
private void hideModeCover() {
- mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
- @Override
- public void onAnimationFinished(boolean success) {
- if (success) {
- // Show shimmy in SHIMMY_DELAY_MS
- mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
- }
- }
- });
+ if (mHideCoverRunnable != null) {
+ mAppRootView.post(mHideCoverRunnable);
+ mHideCoverRunnable = null;
+ }
}
/**
@@ -542,9 +552,35 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
}
}
+ /**
+ * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
+ *
+ * @param modeIndex mode index of the selected mode
+ */
@Override
public void onModeSelected(int modeIndex) {
mController.onModeSelected(modeIndex);
+ mHideCoverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mModeListView.startModeSelectionAnimation();
+ }
+ };
+
+ if (mTextureView == null) {
+ // TODO: Remove this when all the modules use TextureView
+ int temporaryDelay = 600; // ms
+ mModeListView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ hideModeCover();
+ }
+ }, temporaryDelay);
+ } else if (mTextureView.getSurfaceTexture() != null) {
+ hideModeCover();
+ } else {
+ mModeCoverState = COVER_SHOWN;
+ }
}
/**
diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java
index 7f4671fcc..00baacea4 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -23,6 +23,10 @@ import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
@@ -33,6 +37,7 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import com.android.camera.util.Gusterpolator;
+import com.android.camera.widget.AnimationEffects;
import com.android.camera2.R;
import java.util.ArrayList;
@@ -76,6 +81,7 @@ public class ModeListView extends ScrollView {
private static final int FULLY_SHOWN = 1;
private static final int ACCORDION_ANIMATION = 2;
private static final int SCROLLING = 3;
+ private static final int MODE_SELECTED = 4;
// Scrolling delay between non-focused item and focused item
private static final int DELAY_MS = 25;
@@ -104,6 +110,7 @@ public class ModeListView extends ScrollView {
private ModeSelectorItem[] mModeSelectorItems;
private AnimatorSet mAnimatorSet;
private int mFocusItem = NO_ITEM_SELECTED;
+ private AnimationEffects mCurrentEffect;
// Width and height of this view. They get updated in onLayout()
// Unit for width and height are pixels.
@@ -224,6 +231,21 @@ public class ModeListView extends ScrollView {
// Validate the selection
if (index != NO_ITEM_SELECTED) {
int modeId = getModeIndex(index);
+ mModeSelectorItems[index].highlight();
+ mState = MODE_SELECTED;
+ PeepholeAnimationEffect effect = new PeepholeAnimationEffect();
+ effect.setSize(mWidth, mHeight);
+ effect.setAnimationEndAction(new Runnable() {
+ @Override
+ public void run() {
+ setVisibility(INVISIBLE);
+ mCurrentEffect = null;
+ snapBack(false);
+ }
+ });
+ effect.setAnimationStartingPosition((int) ev.getX(), (int) ev.getY());
+ mCurrentEffect = effect;
+
onModeSelected(modeId);
}
return true;
@@ -305,10 +327,10 @@ public class ModeListView extends ScrollView {
mListView.addView(selectorItem);
// Set alternating background color for each mode selector in the list
if (i % 2 == 0) {
- selectorItem.setBackgroundColor(getResources()
+ selectorItem.setDefaultBackgroundColor(getResources()
.getColor(R.color.mode_selector_background_light));
} else {
- selectorItem.setBackgroundColor(getResources()
+ selectorItem.setDefaultBackgroundColor(getResources()
.getColor(R.color.mode_selector_background_dark));
}
int modeId = getModeIndex(i);
@@ -347,9 +369,6 @@ public class ModeListView extends ScrollView {
if (mListener != null) {
mListener.onModeSelected(modeIndex);
}
- // TODO: There will be another animation indicating selection
- // for now, just snap back.
- snapBack();
}
/**
@@ -363,6 +382,10 @@ public class ModeListView extends ScrollView {
@Override
public boolean onTouchEvent(MotionEvent ev) {
+ if (mCurrentEffect != null) {
+ return mCurrentEffect.onTouchEvent(ev);
+ }
+
super.onTouchEvent(ev);
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
getParent().requestDisallowInterceptTouchEvent(true);
@@ -401,6 +424,9 @@ public class ModeListView extends ScrollView {
super.onLayout(changed, left, top, right, bottom);
mWidth = right - left;
mHeight = bottom - top - getPaddingTop() - getPaddingBottom();
+ if (mCurrentEffect != null) {
+ mCurrentEffect.setSize(mWidth, mHeight);
+ }
}
/**
@@ -439,11 +465,25 @@ public class ModeListView extends ScrollView {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ @Override
+ public void draw(Canvas canvas) {
+ if (mCurrentEffect != null) {
+ mCurrentEffect.drawBackground(canvas);
+ super.draw(canvas);
+ mCurrentEffect.drawForeground(canvas);
+ } else {
+ super.draw(canvas);
+ }
+ }
+
/**
* This starts the accordion animation, unless it's already running, in which
* case the start animation call will be ignored.
*/
public void startAccordionAnimation() {
+ if (mState != IDLE) {
+ return;
+ }
if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
return;
}
@@ -473,6 +513,7 @@ public class ModeListView extends ScrollView {
private void resetModeSelectors() {
for (int i = 0; i < mModeSelectorItems.length; i++) {
mModeSelectorItems[i].setVisibleWidth(0);
+ mModeSelectorItems[i].unHighlight();
}
}
@@ -611,6 +652,7 @@ public class ModeListView extends ScrollView {
resetModeSelectors();
mScrollTrendX = 0f;
mScrollTrendY = 0f;
+ mCurrentEffect = null;
setVisibility(INVISIBLE);
}
@@ -632,6 +674,16 @@ public class ModeListView extends ScrollView {
}
}
+ @Override
+ public void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility != VISIBLE) {
+ // Reset mode list if the window is no longer visible.
+ reset();
+ mState = IDLE;
+ }
+ }
+
/**
* The list view should either snap back or snap to full screen after a gesture.
* This function is called when an up or cancel event is received, and then based
@@ -651,9 +703,27 @@ public class ModeListView extends ScrollView {
}
}
+ /**
+ * Snaps back out of the screen.
+ *
+ * @param withAnimation whether snapping back should be animated
+ */
+ public void snapBack(boolean withAnimation) {
+ if (withAnimation) {
+ animateListToWidth(0);
+ mState = IDLE;
+ } else {
+ setVisibility(INVISIBLE);
+ resetModeSelectors();
+ mState = IDLE;
+ }
+ }
+
+ /**
+ * Snaps the mode list back out with animation.
+ */
private void snapBack() {
- animateListToWidth(0);
- mState = IDLE;
+ snapBack(true);
}
private void snapToFullScreen() {
@@ -771,4 +841,125 @@ public class ModeListView extends ScrollView {
return mIconResId[modeIndex];
}
}
+
+ public void startModeSelectionAnimation() {
+ if (mState != MODE_SELECTED || mCurrentEffect == null) {
+ setVisibility(INVISIBLE);
+ snapBack(false);
+ mCurrentEffect = null;
+ } else {
+ mCurrentEffect.startAnimation();
+ }
+
+ }
+
+ private class PeepholeAnimationEffect extends AnimationEffects {
+
+ private final static int UNSET = -1;
+ private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 650;
+
+ private int mWidth;
+ private int mHeight;
+
+ private int mPeepHoleCenterX = UNSET;
+ private int mPeepHoleCenterY = UNSET;
+ private float mRadius = 0f;
+ private ValueAnimator mPeepHoleAnimator;
+ private Runnable mEndAction;
+ private final Paint mMaskPaint = new Paint();
+
+ public PeepholeAnimationEffect() {
+ mMaskPaint.setAlpha(0);
+ mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ 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 startAnimation() {
+ if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) {
+ return;
+ }
+ if (mPeepHoleCenterY == UNSET || mPeepHoleCenterX == UNSET) {
+ mPeepHoleCenterX = mWidth / 2;
+ mPeepHoleCenterY = mHeight / 2;
+ }
+
+ int horizontalDistanceToFarEdge = Math.max(mPeepHoleCenterX, mWidth - mPeepHoleCenterX);
+ int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY);
+ int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge
+ + verticalDistanceToFarEdge * verticalDistanceToFarEdge));
+
+ mPeepHoleAnimator = ValueAnimator.ofFloat(0, endRadius);
+ mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
+ mPeepHoleAnimator.setInterpolator(Gusterpolator.INSTANCE);
+ mPeepHoleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ // Modify mask by enlarging the hole
+ mRadius = (Float) mPeepHoleAnimator.getAnimatedValue();
+ invalidate();
+ }
+ });
+
+ mPeepHoleAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mEndAction != null) {
+ post(mEndAction);
+ mEndAction = null;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mPeepHoleAnimator = null;
+ mRadius = 0;
+ mPeepHoleCenterX = UNSET;
+ mPeepHoleCenterY = UNSET;
+ }
+ });
+ } else {
+ mPeepHoleAnimator = null;
+ mRadius = 0;
+ mPeepHoleCenterX = UNSET;
+ mPeepHoleCenterY = UNSET;
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ });
+ mPeepHoleAnimator.start();
+ }
+
+ public void setAnimationEndAction(Runnable runnable) {
+ mEndAction = runnable;
+ }
+ }
}
diff --git a/src/com/android/camera/ui/ModeSelectorItem.java b/src/com/android/camera/ui/ModeSelectorItem.java
index eb7433e5a..bf89f2b09 100644
--- a/src/com/android/camera/ui/ModeSelectorItem.java
+++ b/src/com/android/camera/ui/ModeSelectorItem.java
@@ -59,9 +59,15 @@ class ModeSelectorItem extends FrameLayout {
private int mDrawingMode = TRUNCATE_TEXT_END;
private int mHeight;
private int mWidth;
+ private int mDefaultBackgroundColor;
+ private int mDefaultTextColor;
+ private int mIconBlockColor;
+ private final int mHighlightTextColor;
public ModeSelectorItem(Context context, AttributeSet attrs) {
super(context, attrs);
+ mHighlightTextColor = context.getResources()
+ .getColor(R.color.mode_selector_text_highlight_color);
}
@Override
@@ -79,6 +85,12 @@ class ModeSelectorItem extends FrameLayout {
mText.setTypeface(typeface);
mMinVisibleWidth = getResources()
.getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
+ mDefaultTextColor = mText.getCurrentTextColor();
+ }
+
+ public void setDefaultBackgroundColor(int color) {
+ mDefaultBackgroundColor = color;
+ setBackgroundColor(color);
}
@Override
@@ -90,6 +102,16 @@ class ModeSelectorItem extends FrameLayout {
new int[] {startColor, color});
}
+ public void highlight() {
+ mText.setTextColor(mHighlightTextColor);
+ setBackgroundColor(mIconBlockColor);
+ }
+
+ public void unHighlight() {
+ setBackgroundColor(mDefaultBackgroundColor);
+ mText.setTextColor(mDefaultTextColor);
+ }
+
/**
* When swiping in, we truncate the end of the item if the visible width
* is not enough to show the whole item. When swiping out, we truncate the
@@ -104,6 +126,7 @@ class ModeSelectorItem extends FrameLayout {
}
public void setIconBackgroundColor(int color) {
+ mIconBlockColor = color;
mIcon.setBackgroundColor(color);
}
@@ -189,7 +212,7 @@ class ModeSelectorItem extends FrameLayout {
canvas.clipRect(0, 0, width, height);
}
super.draw(canvas);
- if (shadeStart > 0) {
+ if (shadeStart > 0 && width < mWidth) {
mGradientShade.setBounds(shadeStart, 0, width, height);
mGradientShade.draw(canvas);
}
diff --git a/src/com/android/camera/widget/AnimationEffects.java b/src/com/android/camera/widget/AnimationEffects.java
new file mode 100644
index 000000000..8ed7e23b0
--- /dev/null
+++ b/src/com/android/camera/widget/AnimationEffects.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 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.widget;
+
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+
+/**
+ * This class aims to encapsulate animation internal states, so that different
+ * animations running on the same view are more independent.
+ */
+public abstract class AnimationEffects {
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ public abstract void setSize(int width, int height);
+
+ /**
+ * Draws what is needed for the animation in the background, before view calls
+ * super.draw().
+ *
+ * @param canvas canvas that the animation effects will draw on
+ */
+ public void drawBackground(Canvas canvas) {}
+
+ /**
+ * Draws what is needed for the animation in the foreground, after view calls
+ * super.draw().
+ *
+ * @param canvas canvas that the animation effects will draw on
+ */
+ public abstract void drawForeground(Canvas canvas);
+
+ public abstract void startAnimation();
+}