Add more gestures and animations in FilmStripView.
Now supports fullscreen mode and filmstrip mode.
Change-Id: I4c568e37f3306d970b3439f86ac6757b15c7cd4f
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 5d10953..7a75a6d 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -33,6 +33,7 @@
import com.android.camera.Storage;
import com.android.camera.ui.FilmStripView;
+import com.android.camera.ui.FilmStripView.ImageData;
import java.util.ArrayList;
import java.util.List;
@@ -79,7 +80,7 @@
private List<LocalImageData> mImages;
- private FilmStripView mFilmStripView;
+ private Listener mListener;
private View mCameraPreviewView;
private ColorDrawable mPlaceHolder;
@@ -101,7 +102,7 @@
}
@Override
- public FilmStripView.ImageData getImageData(int id) {
+ public ImageData getImageData(int id) {
if (id >= mImages.size()) return null;
return mImages.get(id);
}
@@ -142,8 +143,8 @@
}
@Override
- public void setDataListener(FilmStripView v) {
- mFilmStripView = v;
+ public void setListener(Listener listener) {
+ mListener = listener;
}
private LocalImageData buildCameraImageData(int width, int height, int orientation) {
@@ -152,6 +153,7 @@
d.height = height;
d.orientation = orientation;
d.isCameraData = true;
+ d.supportedAction = ImageData.ACTION_NONE;
return d;
}
@@ -179,6 +181,7 @@
d.orientation = c.getInt(COL_ORIENTATION);
d.width = c.getInt(COL_WIDTH);
d.height = c.getInt(COL_HEIGHT);
+ d.supportedAction = ImageData.ACTION_PROMOTE | ImageData.ACTION_DEMOTE;
if (d.width <= 0 || d.height <= 0) {
Log.v(TAG, "warning! zero dimension for "
+ d.path + ":" + d.width + "x" + d.height);
@@ -229,6 +232,7 @@
// width and height should be adjusted according to orientation.
public int width;
public int height;
+ public int supportedAction;
@Override
public int getWidth() {
@@ -241,6 +245,17 @@
}
@Override
+ public int getType() {
+ if (isCameraData) return ImageData.TYPE_CAMERA_PREVIEW;
+ return ImageData.TYPE_PHOTO;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return ((action & supportedAction) != 0);
+ }
+
+ @Override
public String toString() {
return "LocalImageData:" + ",data=" + path + ",mimeType=" + mimeType
+ "," + width + "x" + height + ",orientation=" + orientation;
@@ -281,7 +296,7 @@
mImages = l;
if (first != null) addOrReplaceCameraData(first);
// both might be null.
- if (changed) mFilmStripView.onDataChanged();
+ if (changed && mListener != null) mListener.onDataLoaded();
}
}
diff --git a/src/com/android/camera/ui/FilmStripGestureRecognizer.java b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
new file mode 100644
index 0000000..f0e2534
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
@@ -0,0 +1,107 @@
+/*
+ * 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.ui;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+// This class aggregates three gesture detectors: GestureDetector,
+// ScaleGestureDetector.
+public class FilmStripGestureRecognizer {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FilmStripGestureRecognizer";
+
+ public interface Listener {
+ boolean onSingleTapUp(float x, float y);
+ boolean onDoubleTap(float x, float y);
+ boolean onScroll(float x, float y, float dx, float dy);
+ boolean onFling(float velocityX, float velocityY);
+ boolean onScaleBegin(float focusX, float focusY);
+ boolean onScale(float focusX, float focusY, float scale);
+ boolean onDown(float x, float y);
+ void onScaleEnd();
+ }
+
+ private final GestureDetector mGestureDetector;
+ private final ScaleGestureDetector mScaleDetector;
+ private final Listener mListener;
+
+ public FilmStripGestureRecognizer(Context context, Listener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new MyGestureListener(),
+ null, true /* ignoreMultitouch */);
+ mScaleDetector = new ScaleGestureDetector(
+ context, new MyScaleListener());
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return mGestureDetector.onTouchEvent(event) || mScaleDetector.onTouchEvent(event);
+ }
+
+ private class MyGestureListener
+ extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mListener.onSingleTapUp(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return mListener.onDoubleTap(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onScroll(
+ MotionEvent e1, MotionEvent e2, float dx, float dy) {
+ return mListener.onScroll(e2.getX(), e2.getY(), dx, dy);
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return mListener.onFling(velocityX, velocityY);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ mListener.onDown(e.getX(), e.getY());
+ return super.onDown(e);
+ }
+ }
+
+ private class MyScaleListener
+ extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return mListener.onScaleBegin(
+ detector.getFocusX(), detector.getFocusY());
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ return mListener.onScale(detector.getFocusX(),
+ detector.getFocusY(), detector.getScaleFactor());
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mListener.onScaleEnd();
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 326e969..f2ffa87 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -16,46 +16,72 @@
package com.android.camera.ui;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
public class FilmStripView extends ViewGroup {
-
private static final String TAG = "FilmStripView";
private static final int BUFFER_SIZE = 5;
// Horizontal padding of children.
private static final int H_PADDING = 50;
// Duration to go back to the first.
- private static final int BACK_SCROLL_DURATION = 500;
- private static final float MIN_SCALE = 0.7f;
+ private static final int DURATION_BACK_ANIM = 500;
+ private static final int DURATION_SCROLL_TO_FILMSTRIP = 350;
+ private static final int DURATION_GEOMETRY_ADJUST = 200;
+ private static final float FILM_STRIP_SCALE = 0.6f;
+ private static final float MAX_SCALE = 1f;
private Context mContext;
- private GestureDetector mGestureDetector;
+ private FilmStripGestureRecognizer mGestureRecognizer;
private DataAdapter mDataAdapter;
private final Rect mDrawArea = new Rect();
private int mCurrentInfo;
- private Scroller mScroller;
- private boolean mIsScrolling;
+ private float mScale;
+ private GeometryAnimator mGeometryAnimator;
private int mCenterPosition = -1;
private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE];
public interface ImageData {
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_CAMERA_PREVIEW = 1;
+ public static final int TYPE_PHOTO = 2;
+ public static final int TYPE_VIDEO = 3;
+ public static final int TYPE_PHOTOSPHERE = 4;
+
+ // The actions are defined bit-wise so we can use bit operations like
+ // | and &.
+ public static final int ACTION_NONE = 0;
+ public static final int ACTION_PROMOTE = 1;
+ public static final int ACTION_DEMOTE = 2;
+
+ // SIZE_FULL means disgard the width or height when deciding the view size
+ // of this ImageData, just use full screen size.
+ public static final int SIZE_FULL = -2;
+
// The values returned by getWidth() and getHeight() will be used for layout.
public int getWidth();
public int getHeight();
+ public int getType();
+ public boolean isActionSupported(int action);
}
public interface DataAdapter {
+ public interface Listener {
+ public void onDataLoaded();
+ public void onDataInserted(int dataID);
+ public void onDataRemoved(int dataID);
+ }
public int getTotalNumber();
public View getView(Context context, int id);
@@ -63,22 +89,27 @@
public void suggestSize(int w, int h);
public void requestLoad(ContentResolver r);
- public void setDataListener(FilmStripView v);
+ public void setListener(Listener listener);
}
+ // A helper class to tract and calculate the view coordination.
private static class ViewInfo {
private int mDataID;
// the position of the left of the view in the whole filmstrip.
private int mLeftPosition;
- private View mView;
+ private View mView;
+ private float mOffsetY;
public ViewInfo(int id, View v) {
+ v.setPivotX(0f);
+ v.setPivotY(0f);
mDataID = id;
mView = v;
mLeftPosition = -1;
+ mOffsetY = 0;
}
- public int getId() {
+ public int getID() {
return mDataID;
}
@@ -90,7 +121,15 @@
return mLeftPosition;
}
- public int getCenterPosition() {
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
+ public void setOffsetY(float offset) {
+ mOffsetY = offset;
+ }
+
+ public int getCenterX() {
return mLeftPosition + mView.getWidth() / 2;
}
@@ -98,15 +137,20 @@
return mView;
}
- private void layoutAt(int l, int t) {
- mView.layout(l, t, l + mView.getMeasuredWidth(), t + mView.getMeasuredHeight());
+ private void layoutAt(int left, int top) {
+ mView.layout(left, top, left + mView.getMeasuredWidth(),
+ top + mView.getMeasuredHeight());
}
- public void layoutIn(Rect drawArea, int refCenter) {
+ public void layoutIn(Rect drawArea, int refCenter, float scale) {
// drawArea is where to layout in.
// refCenter is the absolute horizontal position of the center of drawArea.
- layoutAt(drawArea.centerX() + mLeftPosition - refCenter,
- drawArea.centerY() - mView.getMeasuredHeight() / 2);
+ int left = (int) (drawArea.centerX() + (mLeftPosition - refCenter) * scale);
+ int top = (int) (drawArea.centerY() - (mView.getMeasuredHeight() / 2) * scale
+ + mOffsetY);
+ layoutAt(left, top);
+ mView.setScaleX(scale);
+ mView.setScaleY(scale);
}
}
@@ -129,15 +173,33 @@
mCurrentInfo = (BUFFER_SIZE - 1) / 2;
setWillNotDraw(false);
mContext = context;
- mScroller = new Scroller(context);
- mGestureDetector =
- new GestureDetector(context, new MyGestureListener(),
- null, true /* ignoreMultitouch */);
+ mScale = 1.0f;
+ mGeometryAnimator = new GeometryAnimator(context);
+ mGestureRecognizer =
+ new FilmStripGestureRecognizer(context, new MyGestureReceiver());
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public boolean isAnchoredTo(int id) {
+ if (mViewInfo[mCurrentInfo].getID() == id
+ && mViewInfo[mCurrentInfo].getCenterX() == mCenterPosition) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getCurrentType() {
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ if (curr == null) return ImageData.TYPE_NONE;
+ return mDataAdapter.getImageData(curr.getID()).getType();
}
@Override
public void onDraw(Canvas c) {
- if (mIsScrolling) {
+ if (mGeometryAnimator.hasNewGeometry()) {
layoutChildren();
}
}
@@ -146,13 +208,11 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int w = MeasureSpec.getSize(widthMeasureSpec);
- int h = MeasureSpec.getSize(heightMeasureSpec);
- float scale = MIN_SCALE;
- if (mDataAdapter != null) mDataAdapter.suggestSize(w / 2, h / 2);
-
- int boundWidth = (int) (w * scale);
- int boundHeight = (int) (h * scale);
+ int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (mDataAdapter != null) {
+ mDataAdapter.suggestSize(boundWidth / 2, boundHeight / 2);
+ }
int wMode = View.MeasureSpec.EXACTLY;
int hMode = View.MeasureSpec.EXACTLY;
@@ -161,22 +221,25 @@
ViewInfo info = mViewInfo[i];
if (mViewInfo[i] == null) continue;
- int imageWidth = mDataAdapter.getImageData(info.getId()).getWidth();
- int imageHeight = mDataAdapter.getImageData(info.getId()).getHeight();
+ int imageWidth = mDataAdapter.getImageData(info.getID()).getWidth();
+ int imageHeight = mDataAdapter.getImageData(info.getID()).getHeight();
+ if (imageWidth == ImageData.SIZE_FULL) imageWidth = boundWidth;
+ if (imageHeight == ImageData.SIZE_FULL) imageHeight = boundHeight;
int scaledWidth = boundWidth;
int scaledHeight = boundHeight;
+
if (imageWidth * scaledHeight > scaledWidth * imageHeight) {
scaledHeight = imageHeight * scaledWidth / imageWidth;
} else {
scaledWidth = imageWidth * scaledHeight / imageHeight;
}
- scaledWidth += H_PADDING * 2 * scale;
+ scaledWidth += H_PADDING * 2;
mViewInfo[i].getView().measure(
View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode)
, View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode));
}
- setMeasuredDimension(w, h);
+ setMeasuredDimension(boundWidth, boundHeight);
}
private int findTheNearestView(int pointX) {
@@ -188,14 +251,14 @@
nearest++);
// no existing available ViewInfo
if (nearest == BUFFER_SIZE) return -1;
- int min = Math.abs(pointX - mViewInfo[nearest].getCenterPosition());
+ int min = Math.abs(pointX - mViewInfo[nearest].getCenterX());
for (int infoID = nearest + 1;
infoID < BUFFER_SIZE && mViewInfo[infoID] != null; infoID++) {
// not measured yet.
if (mViewInfo[infoID].getLeftPosition() == -1) continue;
- int c = mViewInfo[infoID].getCenterPosition();
+ int c = mViewInfo[infoID].getCenterX();
int dist = Math.abs(pointX - c);
if (dist < min) {
min = dist;
@@ -205,6 +268,14 @@
return nearest;
}
+ private ViewInfo buildInfoFromData(int dataID) {
+ View v = mDataAdapter.getView(mContext, dataID);
+ if (v == null) return null;
+ v.setPadding(H_PADDING, 0, H_PADDING, 0);
+ addView(v);
+ return new ViewInfo(dataID, v);
+ }
+
// We try to keep the one closest to the center of the screen at position mCurrentInfo.
private void stepIfNeeded() {
int nearest = findTheNearestView(mCenterPosition);
@@ -223,7 +294,8 @@
}
for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
mViewInfo[k] = null;
- if (mViewInfo[k - 1] != null) getInfo(k, mViewInfo[k - 1].getId() + 1);
+ if (mViewInfo[k - 1] != null)
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k - 1].getID() + 1);
}
} else {
for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
@@ -236,47 +308,46 @@
}
for (int k = -1 - adjust; k >= 0; k--) {
mViewInfo[k] = null;
- if (mViewInfo[k + 1] != null) getInfo(k, mViewInfo[k + 1].getId() - 1);
+ if (mViewInfo[k + 1] != null)
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1);
}
}
}
- private void stopScroll() {
- mScroller.forceFinished(true);
- mIsScrolling = false;
- }
-
+ // Don't go out of bound.
private void adjustCenterPosition() {
ViewInfo curr = mViewInfo[mCurrentInfo];
if (curr == null) return;
- if (curr.getId() == 0 && mCenterPosition < curr.getCenterPosition()) {
- mCenterPosition = curr.getCenterPosition();
- if (mIsScrolling) stopScroll();
+ if (curr.getID() == 0 && mCenterPosition < curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
}
- if (curr.getId() == mDataAdapter.getTotalNumber() - 1
- && mCenterPosition > curr.getCenterPosition()) {
- mCenterPosition = curr.getCenterPosition();
- if (mIsScrolling) stopScroll();
+ if (curr.getID() == mDataAdapter.getTotalNumber() - 1
+ && mCenterPosition > curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
}
}
private void layoutChildren() {
- mIsScrolling = mScroller.computeScrollOffset();
-
- if (mIsScrolling) mCenterPosition = mScroller.getCurrX();
+ if (mGeometryAnimator.hasNewGeometry()) {
+ mCenterPosition = mGeometryAnimator.getNewPosition();
+ mScale = mGeometryAnimator.getNewScale();
+ }
adjustCenterPosition();
- mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition);
+ mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition, mScale);
// images on the left
for (int infoID = mCurrentInfo - 1; infoID >= 0; infoID--) {
ViewInfo curr = mViewInfo[infoID];
if (curr != null) {
ViewInfo next = mViewInfo[infoID + 1];
- curr.setLeftPosition(next.getLeftPosition() - curr.getView().getMeasuredWidth());
- curr.layoutIn(mDrawArea, mCenterPosition);
+ curr.setLeftPosition(
+ next.getLeftPosition() - curr.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
}
}
@@ -285,8 +356,9 @@
ViewInfo curr = mViewInfo[infoID];
if (curr != null) {
ViewInfo prev = mViewInfo[infoID - 1];
- curr.setLeftPosition(prev.getLeftPosition() + prev.getView().getMeasuredWidth());
- curr.layoutIn(mDrawArea, mCenterPosition);
+ curr.setLeftPosition(
+ prev.getLeftPosition() + prev.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
}
}
@@ -306,24 +378,43 @@
layoutChildren();
}
- public void setDataAdapter(
- DataAdapter adapter, ContentResolver resolver) {
+ public void setDataAdapter(DataAdapter adapter) {
mDataAdapter = adapter;
mDataAdapter.suggestSize(getMeasuredWidth(), getMeasuredHeight());
- mDataAdapter.setDataListener(this);
- mDataAdapter.requestLoad(resolver);
+ mDataAdapter.setListener(new DataAdapter.Listener() {
+ @Override
+ public void onDataLoaded() {
+ reload();
+ }
+
+ @Override
+ public void onDataInserted(int dataID) {
+ }
+
+ @Override
+ public void onDataRemoved(int dataID) {
+ }
+ });
}
- private void getInfo(int infoID, int dataID) {
- View v = mDataAdapter.getView(mContext, dataID);
- if (v == null) return;
- v.setPadding(H_PADDING, 0, H_PADDING, 0);
- addView(v);
- ViewInfo info = new ViewInfo(dataID, v);
- mViewInfo[infoID] = info;
+ public boolean isInCameraFullscreen() {
+ return (isAnchoredTo(0) && mScale == 1f
+ && getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW);
}
- public void onDataChanged() {
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (isInCameraFullscreen()) return false;
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mGestureRecognizer.onTouchEvent(ev);
+ return true;
+ }
+
+ public void reload() {
removeAllViews();
int dataNumber = mDataAdapter.getTotalNumber();
if (dataNumber == 0) return;
@@ -333,89 +424,315 @@
// previous data exists.
if (mViewInfo[mCurrentInfo] != null) {
currentLeft = mViewInfo[mCurrentInfo].getLeftPosition();
- currentData = mViewInfo[mCurrentInfo].getId();
+ currentData = mViewInfo[mCurrentInfo].getID();
}
- getInfo(mCurrentInfo, currentData);
+ mViewInfo[mCurrentInfo] = buildInfoFromData(currentData);
mViewInfo[mCurrentInfo].setLeftPosition(currentLeft);
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW
+ && currentLeft == 0) {
+ // we are in camera mode by default.
+ mGeometryAnimator.lockPosition(currentLeft);
+ }
for (int i = 1; mCurrentInfo + i < BUFFER_SIZE || mCurrentInfo - i >= 0; i++) {
int infoID = mCurrentInfo + i;
if (infoID < BUFFER_SIZE && mViewInfo[infoID - 1] != null) {
- getInfo(infoID, mViewInfo[infoID - 1].getId() + 1);
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID - 1].getID() + 1);
}
infoID = mCurrentInfo - i;
if (infoID >= 0 && mViewInfo[infoID + 1] != null) {
- getInfo(infoID, mViewInfo[infoID + 1].getId() - 1);
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID + 1].getID() - 1);
}
}
layoutChildren();
}
- private void movePositionTo(int position) {
- mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
- 0, BACK_SCROLL_DURATION);
- layoutChildren();
- }
+ // GeometryAnimator controls all the geometry animations. It passively
+ // tells the geometry information on demand.
+ private class GeometryAnimator implements
+ ValueAnimator.AnimatorUpdateListener,
+ Animator.AnimatorListener {
- public void goToFirst() {
- movePositionTo(0);
- }
+ private ValueAnimator mScaleAnimator;
+ private boolean mHasNewScale;
+ private float mNewScale;
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mGestureDetector.onTouchEvent(ev);
- }
+ private Scroller mScroller;
+ private boolean mHasNewPosition;
+ private DecelerateInterpolator mDecelerateInterpolator;
- private class MyGestureListener
- extends GestureDetector.SimpleOnGestureListener {
+ private boolean mCanStopScroll;
+ private boolean mCanStopScale;
+
+ private boolean mIsPositionLocked;
+ private int mLockedPosition;
+
+ private Runnable mPostAction;
+
+ GeometryAnimator(Context context) {
+ mScroller = new Scroller(context);
+ mHasNewPosition = false;
+ mScaleAnimator = new ValueAnimator();
+ mScaleAnimator.addUpdateListener(GeometryAnimator.this);
+ mScaleAnimator.addListener(GeometryAnimator.this);
+ mDecelerateInterpolator = new DecelerateInterpolator();
+ mCanStopScroll = true;
+ mCanStopScale = true;
+ mHasNewScale = false;
+ }
+
+ boolean hasNewGeometry() {
+ mHasNewPosition = mScroller.computeScrollOffset();
+ if (!mHasNewPosition) {
+ mCanStopScroll = true;
+ }
+ // If the position is locked, then we always return true to force
+ // the position value to use the locked value.
+ return (mHasNewPosition || mHasNewScale || mIsPositionLocked);
+ }
+
+ // Always call hasNewGeometry() before getting the new scale value.
+ float getNewScale() {
+ if (!mHasNewScale) return mScale;
+ mHasNewScale = false;
+ return mNewScale;
+ }
+
+ // Always call hasNewGeometry() before getting the new position value.
+ int getNewPosition() {
+ if (mIsPositionLocked) return mLockedPosition;
+ if (!mHasNewPosition) return mCenterPosition;
+ return mScroller.getCurrX();
+ }
+
+ void lockPosition(int pos) {
+ mIsPositionLocked = true;
+ mLockedPosition = pos;
+ }
+
+ void unlockPosition() {
+ if (mIsPositionLocked) {
+ // only when the position is previously locked we set the current
+ // position to make it consistent.
+ mCenterPosition = mLockedPosition;
+ mIsPositionLocked = false;
+ }
+ }
+
+ void fling(int velocityX, int minX, int maxX) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mScroller.fling(mCenterPosition, 0, velocityX, 0, minX, maxX, 0, 0);
+ }
+
+ boolean stopScroll() {
+ if (!mCanStopScroll) return false;
+ mScroller.forceFinished(true);
+ mHasNewPosition = false;
+ return true;
+ }
+
+ boolean stopScale() {
+ if (!mCanStopScale) return false;
+ mScaleAnimator.cancel();
+ mHasNewScale = false;
+ return true;
+ }
+
+ void stop() {
+ stopScroll();
+ stopScale();
+ }
+
+ void scrollTo(int position, int duration, boolean interruptible) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mCanStopScroll = interruptible;
+ stopScroll();
+ mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
+ 0, duration);
+ }
+
+ void scrollTo(int position, int duration) {
+ scrollTo(position, duration, true);
+ }
+
+ void scaleTo(float scale, int duration, boolean interruptible) {
+ if (!stopScale()) return;
+ mCanStopScale = interruptible;
+ mScaleAnimator.setDuration(duration);
+ mScaleAnimator.setFloatValues(mScale, scale);
+ mScaleAnimator.setInterpolator(mDecelerateInterpolator);
+ mScaleAnimator.start();
+ mHasNewScale = true;
+ }
+
+ void scaleTo(float scale, int duration) {
+ scaleTo(scale, duration, true);
+ }
+
+ void setPostAction(Runnable act) {
+ mPostAction = act;
+ }
@Override
- public boolean onDoubleTap(MotionEvent e) {
- float x = (float) e.getX();
- float y = (float) e.getY();
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mHasNewScale = true;
+ mNewScale = (Float) animation.getAnimatedValue();
+ layoutChildren();
+ }
+
+ @Override
+ public void onAnimationStart(Animator anim) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ if (mPostAction != null) {
+ mPostAction.run();
+ mPostAction = null;
+ }
+ mCanStopScale = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator anim) {
+ mPostAction = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator anim) {
+ }
+ }
+
+ private class MyGestureReceiver implements FilmStripGestureRecognizer.Listener {
+ // Indicating the current trend of scaling is up (>1) or down (<1).
+ private float mScaleTrend;
+
+ @Override
+ public boolean onSingleTapUp(float x, float y) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(float x, float y) {
+ return false;
+ }
+
+ @Override
+ public boolean onDown(float x, float y) {
+ mGeometryAnimator.stop();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(float x, float y, float dx, float dy) {
+ int deltaX = (int) (dx / mScale);
+ if (deltaX > 0 && isInCameraFullscreen() ) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
+
+ mCenterPosition += deltaX;
+
+ // Vertical part. Promote or demote.
+ int scaledDeltaY = (int) (dy / mScale);
+
for (int i = 0; i < BUFFER_SIZE; i++) {
if (mViewInfo[i] == null) continue;
+ Rect hitRect = new Rect();
View v = mViewInfo[i].getView();
- if (x >= v.getLeft() && x < v.getRight()
- && y >= v.getTop() && y < v.getBottom()) {
- Log.v(TAG, "l, r, t, b " + v.getLeft() + ',' + v.getRight()
- + ',' + v.getTop() + ',' + v.getBottom());
- movePositionTo(mViewInfo[i].getCenterPosition());
+ v.getHitRect(hitRect);
+ if (hitRect.contains((int) x, (int) y)) {
+ ImageData data = mDataAdapter.getImageData(mViewInfo[i].getID());
+ if ((data.isActionSupported(ImageData.ACTION_DEMOTE) && dy > 0)
+ || (data.isActionSupported(ImageData.ACTION_PROMOTE) && dy < 0)) {
+ mViewInfo[i].setOffsetY(mViewInfo[i].getOffsetY() - dy);
+ }
break;
}
}
- return true;
- }
- @Override
- public boolean onDown(MotionEvent ev) {
- if (mIsScrolling) stopScroll();
- return true;
- }
-
- @Override
- public boolean onScroll(
- MotionEvent e1, MotionEvent e2, float dx, float dy) {
- stopScroll();
- mCenterPosition += dx;
layoutChildren();
return true;
}
@Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
+ public boolean onFling(float velocityX, float velocityY) {
+ float scaledVelocityX = velocityX / mScale;
+ if (isInCameraFullscreen() && scaledVelocityX < 0) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
ViewInfo info = mViewInfo[mCurrentInfo];
int w = getWidth();
if (info == null) return true;
- mScroller.fling(mCenterPosition, 0, (int) -velocityX, (int) velocityY,
+ mGeometryAnimator.fling((int) -scaledVelocityX,
// estimation of possible length on the left
- info.getLeftPosition() - info.getId() * w * 2,
+ info.getLeftPosition() - info.getID() * w * 2,
// estimation of possible length on the right
info.getLeftPosition()
- + (mDataAdapter.getTotalNumber() - info.getId()) * w * 2,
- 0, 0);
+ + (mDataAdapter.getTotalNumber() - info.getID()) * w * 2);
layoutChildren();
return true;
}
+
+ @Override
+ public boolean onScaleBegin(float focusX, float focusY) {
+ if (isInCameraFullscreen()) return false;
+ mScaleTrend = 1f;
+ return true;
+ }
+
+ @Override
+ public boolean onScale(float focusX, float focusY, float scale) {
+ if (isInCameraFullscreen()) return false;
+
+ mScaleTrend = mScaleTrend * 0.5f + scale * 0.5f;
+ mScale *= scale;
+ if (mScale <= FILM_STRIP_SCALE) mScale = FILM_STRIP_SCALE;
+ if (mScale >= MAX_SCALE) mScale = MAX_SCALE;
+ layoutChildren();
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd() {
+ if (mScaleTrend >= 1f) {
+ if (mScale != 1f) {
+ mGeometryAnimator.scaleTo(1f, DURATION_GEOMETRY_ADJUST, false);
+ }
+
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW) {
+ if (isAnchoredTo(0)) {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ } else {
+ mGeometryAnimator.scrollTo(
+ mViewInfo[mCurrentInfo].getCenterX(),
+ DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mLockPositionRunnable);
+ }
+ }
+ } else {
+ // Scale down to film strip mode.
+ if (mScale == FILM_STRIP_SCALE) {
+ mGeometryAnimator.unlockPosition();
+ return;
+ }
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mUnlockPositionRunnable);
+ }
+ }
+
+ private Runnable mLockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ }
+ };
+
+ private Runnable mUnlockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.unlockPosition();
+ }
+ };
}
}