summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/PhotoView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/PhotoView.java')
-rw-r--r--src/com/android/gallery3d/ui/PhotoView.java1858
1 files changed, 0 insertions, 1858 deletions
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
deleted file mode 100644
index 7afa20348..000000000
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ /dev/null
@@ -1,1858 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Message;
-import android.util.FloatMath;
-import android.view.MotionEvent;
-import android.view.View.MeasureSpec;
-import android.view.animation.AccelerateInterpolator;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.AbstractGalleryActivity;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.glrenderer.GLCanvas;
-import com.android.gallery3d.glrenderer.RawTexture;
-import com.android.gallery3d.glrenderer.ResourceTexture;
-import com.android.gallery3d.glrenderer.StringTexture;
-import com.android.gallery3d.glrenderer.Texture;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.RangeArray;
-import com.android.gallery3d.util.UsageStatistics;
-
-public class PhotoView extends GLView {
- @SuppressWarnings("unused")
- private static final String TAG = "PhotoView";
- private final int mPlaceholderColor;
-
- public static final int INVALID_SIZE = -1;
- public static final long INVALID_DATA_VERSION =
- MediaObject.INVALID_DATA_VERSION;
-
- public static class Size {
- public int width;
- public int height;
- }
-
- public interface Model extends TileImageView.TileSource {
- public int getCurrentIndex();
- public void moveTo(int index);
-
- // Returns the size for the specified picture. If the size information is
- // not avaiable, width = height = 0.
- public void getImageSize(int offset, Size size);
-
- // Returns the media item for the specified picture.
- public MediaItem getMediaItem(int offset);
-
- // Returns the rotation for the specified picture.
- public int getImageRotation(int offset);
-
- // This amends the getScreenNail() method of TileImageView.Model to get
- // ScreenNail at previous (negative offset) or next (positive offset)
- // positions. Returns null if the specified ScreenNail is unavailable.
- public ScreenNail getScreenNail(int offset);
-
- // Set this to true if we need the model to provide full images.
- public void setNeedFullImage(boolean enabled);
-
- // Returns true if the item is the Camera preview.
- public boolean isCamera(int offset);
-
- // Returns true if the item is the Panorama.
- public boolean isPanorama(int offset);
-
- // Returns true if the item is a static image that represents camera
- // preview.
- public boolean isStaticCamera(int offset);
-
- // Returns true if the item is a Video.
- public boolean isVideo(int offset);
-
- // Returns true if the item can be deleted.
- public boolean isDeletable(int offset);
-
- public static final int LOADING_INIT = 0;
- public static final int LOADING_COMPLETE = 1;
- public static final int LOADING_FAIL = 2;
-
- public int getLoadingState(int offset);
-
- // When data change happens, we need to decide which MediaItem to focus
- // on.
- //
- // 1. If focus hint path != null, we try to focus on it if we can find
- // it. This is used for undo a deletion, so we can focus on the
- // undeleted item.
- //
- // 2. Otherwise try to focus on the MediaItem that is currently focused,
- // if we can find it.
- //
- // 3. Otherwise try to focus on the previous MediaItem or the next
- // MediaItem, depending on the value of focus hint direction.
- public static final int FOCUS_HINT_NEXT = 0;
- public static final int FOCUS_HINT_PREVIOUS = 1;
- public void setFocusHintDirection(int direction);
- public void setFocusHintPath(Path path);
- }
-
- public interface Listener {
- public void onSingleTapUp(int x, int y);
- public void onFullScreenChanged(boolean full);
- public void onActionBarAllowed(boolean allowed);
- public void onActionBarWanted();
- public void onCurrentImageUpdated();
- public void onDeleteImage(Path path, int offset);
- public void onUndoDeleteImage();
- public void onCommitDeleteImage();
- public void onFilmModeChanged(boolean enabled);
- public void onPictureCenter(boolean isCamera);
- public void onUndoBarVisibilityChanged(boolean visible);
- }
-
- // The rules about orientation locking:
- //
- // (1) We need to lock the orientation if we are in page mode camera
- // preview, so there is no (unwanted) rotation animation when the user
- // rotates the device.
- //
- // (2) We need to unlock the orientation if we want to show the action bar
- // because the action bar follows the system orientation.
- //
- // The rules about action bar:
- //
- // (1) If we are in film mode, we don't show action bar.
- //
- // (2) If we go from camera to gallery with capture animation, we show
- // action bar.
- private static final int MSG_CANCEL_EXTRA_SCALING = 2;
- private static final int MSG_SWITCH_FOCUS = 3;
- private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
- private static final int MSG_DELETE_ANIMATION_DONE = 5;
- private static final int MSG_DELETE_DONE = 6;
- private static final int MSG_UNDO_BAR_TIMEOUT = 7;
- private static final int MSG_UNDO_BAR_FULL_CAMERA = 8;
-
- private static final float SWIPE_THRESHOLD = 300f;
-
- private static final float DEFAULT_TEXT_SIZE = 20;
- private static float TRANSITION_SCALE_FACTOR = 0.74f;
- private static final int ICON_RATIO = 6;
-
- // whether we want to apply card deck effect in page mode.
- private static final boolean CARD_EFFECT = true;
-
- // whether we want to apply offset effect in film mode.
- private static final boolean OFFSET_EFFECT = true;
-
- // Used to calculate the scaling factor for the card deck effect.
- private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);
-
- // Used to calculate the alpha factor for the fading animation.
- private AccelerateInterpolator mAlphaInterpolator =
- new AccelerateInterpolator(0.9f);
-
- // We keep this many previous ScreenNails. (also this many next ScreenNails)
- public static final int SCREEN_NAIL_MAX = 3;
-
- // These are constants for the delete gesture.
- private static final int SWIPE_ESCAPE_VELOCITY = 500; // dp/sec
- private static final int MAX_DISMISS_VELOCITY = 2500; // dp/sec
- private static final int SWIPE_ESCAPE_DISTANCE = 150; // dp
-
- // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
- // SCREEN_NAIL_MAX.
- private final RangeArray<Picture> mPictures =
- new RangeArray<Picture>(-SCREEN_NAIL_MAX, SCREEN_NAIL_MAX);
- private Size[] mSizes = new Size[2 * SCREEN_NAIL_MAX + 1];
-
- private final MyGestureListener mGestureListener;
- private final GestureRecognizer mGestureRecognizer;
- private final PositionController mPositionController;
-
- private Listener mListener;
- private Model mModel;
- private StringTexture mNoThumbnailText;
- private TileImageView mTileView;
- private EdgeView mEdgeView;
- private UndoBarView mUndoBar;
- private Texture mVideoPlayIcon;
-
- private SynchronizedHandler mHandler;
-
- private boolean mCancelExtraScalingPending;
- private boolean mFilmMode = false;
- private boolean mWantPictureCenterCallbacks = false;
- private int mDisplayRotation = 0;
- private int mCompensation = 0;
- private boolean mFullScreenCamera;
- private Rect mCameraRelativeFrame = new Rect();
- private Rect mCameraRect = new Rect();
- private boolean mFirst = true;
-
- // [mPrevBound, mNextBound] is the range of index for all pictures in the
- // model, if we assume the index of current focused picture is 0. So if
- // there are some previous pictures, mPrevBound < 0, and if there are some
- // next pictures, mNextBound > 0.
- private int mPrevBound;
- private int mNextBound;
-
- // This variable prevents us doing snapback until its values goes to 0. This
- // happens if the user gesture is still in progress or we are in a capture
- // animation.
- private int mHolding;
- private static final int HOLD_TOUCH_DOWN = 1;
- private static final int HOLD_CAPTURE_ANIMATION = 2;
- private static final int HOLD_DELETE = 4;
-
- // mTouchBoxIndex is the index of the box that is touched by the down
- // gesture in film mode. The value Integer.MAX_VALUE means no box was
- // touched.
- private int mTouchBoxIndex = Integer.MAX_VALUE;
- // Whether the box indicated by mTouchBoxIndex is deletable. Only meaningful
- // if mTouchBoxIndex is not Integer.MAX_VALUE.
- private boolean mTouchBoxDeletable;
- // This is the index of the last deleted item. This is only used as a hint
- // to hide the undo button when we are too far away from the deleted
- // item. The value Integer.MAX_VALUE means there is no such hint.
- private int mUndoIndexHint = Integer.MAX_VALUE;
-
- private Context mContext;
-
- public PhotoView(AbstractGalleryActivity activity) {
- mTileView = new TileImageView(activity);
- addComponent(mTileView);
- mContext = activity.getAndroidContext();
- mPlaceholderColor = mContext.getResources().getColor(
- R.color.photo_placeholder);
- mEdgeView = new EdgeView(mContext);
- addComponent(mEdgeView);
- mUndoBar = new UndoBarView(mContext);
- addComponent(mUndoBar);
- mUndoBar.setVisibility(GLView.INVISIBLE);
- mUndoBar.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(GLView v) {
- mListener.onUndoDeleteImage();
- hideUndoBar();
- }
- });
- mNoThumbnailText = StringTexture.newInstance(
- mContext.getString(R.string.no_thumbnail),
- DEFAULT_TEXT_SIZE, Color.WHITE);
-
- mHandler = new MyHandler(activity.getGLRoot());
-
- mGestureListener = new MyGestureListener();
- mGestureRecognizer = new GestureRecognizer(mContext, mGestureListener);
-
- mPositionController = new PositionController(mContext,
- new PositionController.Listener() {
-
- @Override
- public void invalidate() {
- PhotoView.this.invalidate();
- }
-
- @Override
- public boolean isHoldingDown() {
- return (mHolding & HOLD_TOUCH_DOWN) != 0;
- }
-
- @Override
- public boolean isHoldingDelete() {
- return (mHolding & HOLD_DELETE) != 0;
- }
-
- @Override
- public void onPull(int offset, int direction) {
- mEdgeView.onPull(offset, direction);
- }
-
- @Override
- public void onRelease() {
- mEdgeView.onRelease();
- }
-
- @Override
- public void onAbsorb(int velocity, int direction) {
- mEdgeView.onAbsorb(velocity, direction);
- }
- });
- mVideoPlayIcon = new ResourceTexture(mContext, R.drawable.ic_control_play);
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
- if (i == 0) {
- mPictures.put(i, new FullPicture());
- } else {
- mPictures.put(i, new ScreenNailPicture(i));
- }
- }
- }
-
- public void stopScrolling() {
- mPositionController.stopScrolling();
- }
-
- public void setModel(Model model) {
- mModel = model;
- mTileView.setModel(mModel);
- }
-
- class MyHandler extends SynchronizedHandler {
- public MyHandler(GLRoot root) {
- super(root);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_CANCEL_EXTRA_SCALING: {
- mGestureRecognizer.cancelScale();
- mPositionController.setExtraScalingRange(false);
- mCancelExtraScalingPending = false;
- break;
- }
- case MSG_SWITCH_FOCUS: {
- switchFocus();
- break;
- }
- case MSG_CAPTURE_ANIMATION_DONE: {
- // message.arg1 is the offset parameter passed to
- // switchWithCaptureAnimation().
- captureAnimationDone(message.arg1);
- break;
- }
- case MSG_DELETE_ANIMATION_DONE: {
- // message.obj is the Path of the MediaItem which should be
- // deleted. message.arg1 is the offset of the image.
- mListener.onDeleteImage((Path) message.obj, message.arg1);
- // Normally a box which finishes delete animation will hold
- // position until the underlying MediaItem is actually
- // deleted, and HOLD_DELETE will be cancelled that time. In
- // case the MediaItem didn't actually get deleted in 2
- // seconds, we will cancel HOLD_DELETE and make it bounce
- // back.
-
- // We make sure there is at most one MSG_DELETE_DONE
- // in the handler.
- mHandler.removeMessages(MSG_DELETE_DONE);
- Message m = mHandler.obtainMessage(MSG_DELETE_DONE);
- mHandler.sendMessageDelayed(m, 2000);
-
- int numberOfPictures = mNextBound - mPrevBound + 1;
- if (numberOfPictures == 2) {
- if (mModel.isCamera(mNextBound)
- || mModel.isCamera(mPrevBound)) {
- numberOfPictures--;
- }
- }
- showUndoBar(numberOfPictures <= 1);
- break;
- }
- case MSG_DELETE_DONE: {
- if (!mHandler.hasMessages(MSG_DELETE_ANIMATION_DONE)) {
- mHolding &= ~HOLD_DELETE;
- snapback();
- }
- break;
- }
- case MSG_UNDO_BAR_TIMEOUT: {
- checkHideUndoBar(UNDO_BAR_TIMEOUT);
- break;
- }
- case MSG_UNDO_BAR_FULL_CAMERA: {
- checkHideUndoBar(UNDO_BAR_FULL_CAMERA);
- break;
- }
- default: throw new AssertionError(message.what);
- }
- }
- }
-
- public void setWantPictureCenterCallbacks(boolean wanted) {
- mWantPictureCenterCallbacks = wanted;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Data/Image change notifications
- ////////////////////////////////////////////////////////////////////////////
-
- public void notifyDataChange(int[] fromIndex, int prevBound, int nextBound) {
- mPrevBound = prevBound;
- mNextBound = nextBound;
-
- // Update mTouchBoxIndex
- if (mTouchBoxIndex != Integer.MAX_VALUE) {
- int k = mTouchBoxIndex;
- mTouchBoxIndex = Integer.MAX_VALUE;
- for (int i = 0; i < 2 * SCREEN_NAIL_MAX + 1; i++) {
- if (fromIndex[i] == k) {
- mTouchBoxIndex = i - SCREEN_NAIL_MAX;
- break;
- }
- }
- }
-
- // Hide undo button if we are too far away
- if (mUndoIndexHint != Integer.MAX_VALUE) {
- if (Math.abs(mUndoIndexHint - mModel.getCurrentIndex()) >= 3) {
- hideUndoBar();
- }
- }
-
- // Update the ScreenNails.
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
- Picture p = mPictures.get(i);
- p.reload();
- mSizes[i + SCREEN_NAIL_MAX] = p.getSize();
- }
-
- boolean wasDeleting = mPositionController.hasDeletingBox();
-
- // Move the boxes
- mPositionController.moveBox(fromIndex, mPrevBound < 0, mNextBound > 0,
- mModel.isCamera(0), mSizes);
-
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
- setPictureSize(i);
- }
-
- boolean isDeleting = mPositionController.hasDeletingBox();
-
- // If the deletion is done, make HOLD_DELETE persist for only the time
- // needed for a snapback animation.
- if (wasDeleting && !isDeleting) {
- mHandler.removeMessages(MSG_DELETE_DONE);
- Message m = mHandler.obtainMessage(MSG_DELETE_DONE);
- mHandler.sendMessageDelayed(
- m, PositionController.SNAPBACK_ANIMATION_TIME);
- }
-
- invalidate();
- }
-
- public boolean isDeleting() {
- return (mHolding & HOLD_DELETE) != 0
- && mPositionController.hasDeletingBox();
- }
-
- public void notifyImageChange(int index) {
- if (index == 0) {
- mListener.onCurrentImageUpdated();
- }
- mPictures.get(index).reload();
- setPictureSize(index);
- invalidate();
- }
-
- private void setPictureSize(int index) {
- Picture p = mPictures.get(index);
- mPositionController.setImageSize(index, p.getSize(),
- index == 0 && p.isCamera() ? mCameraRect : null);
- }
-
- @Override
- protected void onLayout(
- boolean changeSize, int left, int top, int right, int bottom) {
- int w = right - left;
- int h = bottom - top;
- mTileView.layout(0, 0, w, h);
- mEdgeView.layout(0, 0, w, h);
- mUndoBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- mUndoBar.layout(0, h - mUndoBar.getMeasuredHeight(), w, h);
-
- GLRoot root = getGLRoot();
- int displayRotation = root.getDisplayRotation();
- int compensation = root.getCompensation();
- if (mDisplayRotation != displayRotation
- || mCompensation != compensation) {
- mDisplayRotation = displayRotation;
- mCompensation = compensation;
-
- // We need to change the size and rotation of the Camera ScreenNail,
- // but we don't want it to animate because the size doen't actually
- // change in the eye of the user.
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
- Picture p = mPictures.get(i);
- if (p.isCamera()) {
- p.forceSize();
- }
- }
- }
-
- updateCameraRect();
- mPositionController.setConstrainedFrame(mCameraRect);
- if (changeSize) {
- mPositionController.setViewSize(getWidth(), getHeight());
- }
- }
-
- // Update the camera rectangle due to layout change or camera relative frame
- // change.
- private void updateCameraRect() {
- // Get the width and height in framework orientation because the given
- // mCameraRelativeFrame is in that coordinates.
- int w = getWidth();
- int h = getHeight();
- if (mCompensation % 180 != 0) {
- int tmp = w;
- w = h;
- h = tmp;
- }
- int l = mCameraRelativeFrame.left;
- int t = mCameraRelativeFrame.top;
- int r = mCameraRelativeFrame.right;
- int b = mCameraRelativeFrame.bottom;
-
- // Now convert it to the coordinates we are using.
- switch (mCompensation) {
- case 0: mCameraRect.set(l, t, r, b); break;
- case 90: mCameraRect.set(h - b, l, h - t, r); break;
- case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
- case 270: mCameraRect.set(t, w - r, b, w - l); break;
- }
-
- Log.d(TAG, "compensation = " + mCompensation
- + ", CameraRelativeFrame = " + mCameraRelativeFrame
- + ", mCameraRect = " + mCameraRect);
- }
-
- public void setCameraRelativeFrame(Rect frame) {
- mCameraRelativeFrame.set(frame);
- updateCameraRect();
- // Originally we do
- // mPositionController.setConstrainedFrame(mCameraRect);
- // here, but it is moved to a parameter of the setImageSize() call, so
- // it can be updated atomically with the CameraScreenNail's size change.
- }
-
- // Returns the rotation we need to do to the camera texture before drawing
- // it to the canvas, assuming the camera texture is correct when the device
- // is in its natural orientation.
- private int getCameraRotation() {
- return (mCompensation - mDisplayRotation + 360) % 360;
- }
-
- private int getPanoramaRotation() {
- // This function is magic
- // The issue here is that Pano makes bad assumptions about rotation and
- // orientation. The first is it assumes only two rotations are possible,
- // 0 and 90. Thus, if display rotation is >= 180, we invert the output.
- // The second is that it assumes landscape is a 90 rotation from portrait,
- // however on landscape devices this is not true. Thus, if we are in portrait
- // on a landscape device, we need to invert the output
- int orientation = mContext.getResources().getConfiguration().orientation;
- boolean invertPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT
- && (mDisplayRotation == 90 || mDisplayRotation == 270));
- boolean invert = (mDisplayRotation >= 180);
- if (invert != invertPortrait) {
- return (mCompensation + 180) % 360;
- }
- return mCompensation;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Pictures
- ////////////////////////////////////////////////////////////////////////////
-
- private interface Picture {
- void reload();
- void draw(GLCanvas canvas, Rect r);
- void setScreenNail(ScreenNail s);
- boolean isCamera(); // whether the picture is a camera preview
- boolean isDeletable(); // whether the picture can be deleted
- void forceSize(); // called when mCompensation changes
- Size getSize();
- }
-
- class FullPicture implements Picture {
- private int mRotation;
- private boolean mIsCamera;
- private boolean mIsPanorama;
- private boolean mIsStaticCamera;
- private boolean mIsVideo;
- private boolean mIsDeletable;
- private int mLoadingState = Model.LOADING_INIT;
- private Size mSize = new Size();
-
- @Override
- public void reload() {
- // mImageWidth and mImageHeight will get updated
- mTileView.notifyModelInvalidated();
-
- mIsCamera = mModel.isCamera(0);
- mIsPanorama = mModel.isPanorama(0);
- mIsStaticCamera = mModel.isStaticCamera(0);
- mIsVideo = mModel.isVideo(0);
- mIsDeletable = mModel.isDeletable(0);
- mLoadingState = mModel.getLoadingState(0);
- setScreenNail(mModel.getScreenNail(0));
- updateSize();
- }
-
- @Override
- public Size getSize() {
- return mSize;
- }
-
- @Override
- public void forceSize() {
- updateSize();
- mPositionController.forceImageSize(0, mSize);
- }
-
- private void updateSize() {
- if (mIsPanorama) {
- mRotation = getPanoramaRotation();
- } else if (mIsCamera && !mIsStaticCamera) {
- mRotation = getCameraRotation();
- } else {
- mRotation = mModel.getImageRotation(0);
- }
-
- int w = mTileView.mImageWidth;
- int h = mTileView.mImageHeight;
- mSize.width = getRotated(mRotation, w, h);
- mSize.height = getRotated(mRotation, h, w);
- }
-
- @Override
- public void draw(GLCanvas canvas, Rect r) {
- drawTileView(canvas, r);
-
- // We want to have the following transitions:
- // (1) Move camera preview out of its place: switch to film mode
- // (2) Move camera preview into its place: switch to page mode
- // The extra mWasCenter check makes sure (1) does not apply if in
- // page mode, we move _to_ the camera preview from another picture.
-
- // Holdings except touch-down prevent the transitions.
- if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
-
- if (mWantPictureCenterCallbacks && mPositionController.isCenter()) {
- mListener.onPictureCenter(mIsCamera);
- }
- }
-
- @Override
- public void setScreenNail(ScreenNail s) {
- mTileView.setScreenNail(s);
- }
-
- @Override
- public boolean isCamera() {
- return mIsCamera;
- }
-
- @Override
- public boolean isDeletable() {
- return mIsDeletable;
- }
-
- private void drawTileView(GLCanvas canvas, Rect r) {
- float imageScale = mPositionController.getImageScale();
- int viewW = getWidth();
- int viewH = getHeight();
- float cx = r.exactCenterX();
- float cy = r.exactCenterY();
- float scale = 1f; // the scaling factor due to card effect
-
- canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
- float filmRatio = mPositionController.getFilmRatio();
- boolean wantsCardEffect = CARD_EFFECT && !mIsCamera
- && filmRatio != 1f && !mPictures.get(-1).isCamera()
- && !mPositionController.inOpeningAnimation();
- boolean wantsOffsetEffect = OFFSET_EFFECT && mIsDeletable
- && filmRatio == 1f && r.centerY() != viewH / 2;
- if (wantsCardEffect) {
- // Calculate the move-out progress value.
- int left = r.left;
- int right = r.right;
- float progress = calculateMoveOutProgress(left, right, viewW);
- progress = Utils.clamp(progress, -1f, 1f);
-
- // We only want to apply the fading animation if the scrolling
- // movement is to the right.
- if (progress < 0) {
- scale = getScrollScale(progress);
- float alpha = getScrollAlpha(progress);
- scale = interpolate(filmRatio, scale, 1f);
- alpha = interpolate(filmRatio, alpha, 1f);
-
- imageScale *= scale;
- canvas.multiplyAlpha(alpha);
-
- float cxPage; // the cx value in page mode
- if (right - left <= viewW) {
- // If the picture is narrower than the view, keep it at
- // the center of the view.
- cxPage = viewW / 2f;
- } else {
- // If the picture is wider than the view (it's
- // zoomed-in), keep the left edge of the object align
- // the the left edge of the view.
- cxPage = (right - left) * scale / 2f;
- }
- cx = interpolate(filmRatio, cxPage, cx);
- }
- } else if (wantsOffsetEffect) {
- float offset = (float) (r.centerY() - viewH / 2) / viewH;
- float alpha = getOffsetAlpha(offset);
- canvas.multiplyAlpha(alpha);
- }
-
- // Draw the tile view.
- setTileViewPosition(cx, cy, viewW, viewH, imageScale);
- renderChild(canvas, mTileView);
-
- // Draw the play video icon and the message.
- canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
- int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
- if (mIsVideo) drawVideoPlayIcon(canvas, s);
- if (mLoadingState == Model.LOADING_FAIL) {
- drawLoadingFailMessage(canvas);
- }
-
- // Draw a debug indicator showing which picture has focus (index ==
- // 0).
- //canvas.fillRect(-10, -10, 20, 20, 0x80FF00FF);
-
- canvas.restore();
- }
-
- // Set the position of the tile view
- private void setTileViewPosition(float cx, float cy,
- int viewW, int viewH, float scale) {
- // Find out the bitmap coordinates of the center of the view
- int imageW = mPositionController.getImageWidth();
- int imageH = mPositionController.getImageHeight();
- int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f);
- int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f);
-
- int inverseX = imageW - centerX;
- int inverseY = imageH - centerY;
- int x, y;
- switch (mRotation) {
- case 0: x = centerX; y = centerY; break;
- case 90: x = centerY; y = inverseX; break;
- case 180: x = inverseX; y = inverseY; break;
- case 270: x = inverseY; y = centerX; break;
- default:
- throw new RuntimeException(String.valueOf(mRotation));
- }
- mTileView.setPosition(x, y, scale, mRotation);
- }
- }
-
- private class ScreenNailPicture implements Picture {
- private int mIndex;
- private int mRotation;
- private ScreenNail mScreenNail;
- private boolean mIsCamera;
- private boolean mIsPanorama;
- private boolean mIsStaticCamera;
- private boolean mIsVideo;
- private boolean mIsDeletable;
- private int mLoadingState = Model.LOADING_INIT;
- private Size mSize = new Size();
-
- public ScreenNailPicture(int index) {
- mIndex = index;
- }
-
- @Override
- public void reload() {
- mIsCamera = mModel.isCamera(mIndex);
- mIsPanorama = mModel.isPanorama(mIndex);
- mIsStaticCamera = mModel.isStaticCamera(mIndex);
- mIsVideo = mModel.isVideo(mIndex);
- mIsDeletable = mModel.isDeletable(mIndex);
- mLoadingState = mModel.getLoadingState(mIndex);
- setScreenNail(mModel.getScreenNail(mIndex));
- updateSize();
- }
-
- @Override
- public Size getSize() {
- return mSize;
- }
-
- @Override
- public void draw(GLCanvas canvas, Rect r) {
- if (mScreenNail == null) {
- // Draw a placeholder rectange if there should be a picture in
- // this position (but somehow there isn't).
- if (mIndex >= mPrevBound && mIndex <= mNextBound) {
- drawPlaceHolder(canvas, r);
- }
- return;
- }
- int w = getWidth();
- int h = getHeight();
- if (r.left >= w || r.right <= 0 || r.top >= h || r.bottom <= 0) {
- mScreenNail.noDraw();
- return;
- }
-
- float filmRatio = mPositionController.getFilmRatio();
- boolean wantsCardEffect = CARD_EFFECT && mIndex > 0
- && filmRatio != 1f && !mPictures.get(0).isCamera();
- boolean wantsOffsetEffect = OFFSET_EFFECT && mIsDeletable
- && filmRatio == 1f && r.centerY() != h / 2;
- int cx = wantsCardEffect
- ? (int) (interpolate(filmRatio, w / 2, r.centerX()) + 0.5f)
- : r.centerX();
- int cy = r.centerY();
- canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
- canvas.translate(cx, cy);
- if (wantsCardEffect) {
- float progress = (float) (w / 2 - r.centerX()) / w;
- progress = Utils.clamp(progress, -1, 1);
- float alpha = getScrollAlpha(progress);
- float scale = getScrollScale(progress);
- alpha = interpolate(filmRatio, alpha, 1f);
- scale = interpolate(filmRatio, scale, 1f);
- canvas.multiplyAlpha(alpha);
- canvas.scale(scale, scale, 1);
- } else if (wantsOffsetEffect) {
- float offset = (float) (r.centerY() - h / 2) / h;
- float alpha = getOffsetAlpha(offset);
- canvas.multiplyAlpha(alpha);
- }
- if (mRotation != 0) {
- canvas.rotate(mRotation, 0, 0, 1);
- }
- int drawW = getRotated(mRotation, r.width(), r.height());
- int drawH = getRotated(mRotation, r.height(), r.width());
- mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
- if (isScreenNailAnimating()) {
- invalidate();
- }
- int s = Math.min(drawW, drawH);
- if (mIsVideo) drawVideoPlayIcon(canvas, s);
- if (mLoadingState == Model.LOADING_FAIL) {
- drawLoadingFailMessage(canvas);
- }
- canvas.restore();
- }
-
- private boolean isScreenNailAnimating() {
- return (mScreenNail instanceof TiledScreenNail)
- && ((TiledScreenNail) mScreenNail).isAnimating();
- }
-
- @Override
- public void setScreenNail(ScreenNail s) {
- mScreenNail = s;
- }
-
- @Override
- public void forceSize() {
- updateSize();
- mPositionController.forceImageSize(mIndex, mSize);
- }
-
- private void updateSize() {
- if (mIsPanorama) {
- mRotation = getPanoramaRotation();
- } else if (mIsCamera && !mIsStaticCamera) {
- mRotation = getCameraRotation();
- } else {
- mRotation = mModel.getImageRotation(mIndex);
- }
-
- if (mScreenNail != null) {
- mSize.width = mScreenNail.getWidth();
- mSize.height = mScreenNail.getHeight();
- } else {
- // If we don't have ScreenNail available, we can still try to
- // get the size information of it.
- mModel.getImageSize(mIndex, mSize);
- }
-
- int w = mSize.width;
- int h = mSize.height;
- mSize.width = getRotated(mRotation, w, h);
- mSize.height = getRotated(mRotation, h, w);
- }
-
- @Override
- public boolean isCamera() {
- return mIsCamera;
- }
-
- @Override
- public boolean isDeletable() {
- return mIsDeletable;
- }
- }
-
- // Draw a gray placeholder in the specified rectangle.
- private void drawPlaceHolder(GLCanvas canvas, Rect r) {
- canvas.fillRect(r.left, r.top, r.width(), r.height(), mPlaceholderColor);
- }
-
- // Draw the video play icon (in the place where the spinner was)
- private void drawVideoPlayIcon(GLCanvas canvas, int side) {
- int s = side / ICON_RATIO;
- // Draw the video play icon at the center
- mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
- }
-
- // Draw the "no thumbnail" message
- private void drawLoadingFailMessage(GLCanvas canvas) {
- StringTexture m = mNoThumbnailText;
- m.draw(canvas, -m.getWidth() / 2, -m.getHeight() / 2);
- }
-
- private static int getRotated(int degree, int original, int theother) {
- return (degree % 180 == 0) ? original : theother;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Gestures Handling
- ////////////////////////////////////////////////////////////////////////////
-
- @Override
- protected boolean onTouch(MotionEvent event) {
- mGestureRecognizer.onTouchEvent(event);
- return true;
- }
-
- private class MyGestureListener implements GestureRecognizer.Listener {
- private boolean mIgnoreUpEvent = false;
- // If we can change mode for this scale gesture.
- private boolean mCanChangeMode;
- // If we have changed the film mode in this scaling gesture.
- private boolean mModeChanged;
- // If this scaling gesture should be ignored.
- private boolean mIgnoreScalingGesture;
- // whether the down action happened while the view is scrolling.
- private boolean mDownInScrolling;
- // If we should ignore all gestures other than onSingleTapUp.
- private boolean mIgnoreSwipingGesture;
- // If a scrolling has happened after a down gesture.
- private boolean mScrolledAfterDown;
- // If the first scrolling move is in X direction. In the film mode, X
- // direction scrolling is normal scrolling. but Y direction scrolling is
- // a delete gesture.
- private boolean mFirstScrollX;
- // The accumulated Y delta that has been sent to mPositionController.
- private int mDeltaY;
- // The accumulated scaling change from a scaling gesture.
- private float mAccScale;
- // If an onFling happened after the last onDown
- private boolean mHadFling;
-
- @Override
- public boolean onSingleTapUp(float x, float y) {
- // On crespo running Android 2.3.6 (gingerbread), a pinch out gesture results in the
- // following call sequence: onDown(), onUp() and then onSingleTapUp(). The correct
- // sequence for a single-tap-up gesture should be: onDown(), onSingleTapUp() and onUp().
- // The call sequence for a pinch out gesture in JB is: onDown(), then onUp() and there's
- // no onSingleTapUp(). Base on these observations, the following condition is added to
- // filter out the false alarm where onSingleTapUp() is called within a pinch out
- // gesture. The framework fix went into ICS. Refer to b/4588114.
- if (Build.VERSION.SDK_INT < ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) {
- if ((mHolding & HOLD_TOUCH_DOWN) == 0) {
- return true;
- }
- }
-
- // We do this in addition to onUp() because we want the snapback of
- // setFilmMode to happen.
- mHolding &= ~HOLD_TOUCH_DOWN;
-
- if (mFilmMode && !mDownInScrolling) {
- switchToHitPicture((int) (x + 0.5f), (int) (y + 0.5f));
-
- // If this is a lock screen photo, let the listener handle the
- // event. Tapping on lock screen photo should take the user
- // directly to the lock screen.
- MediaItem item = mModel.getMediaItem(0);
- int supported = 0;
- if (item != null) supported = item.getSupportedOperations();
- if ((supported & MediaItem.SUPPORT_ACTION) == 0) {
- setFilmMode(false);
- mIgnoreUpEvent = true;
- return true;
- }
- }
-
- if (mListener != null) {
- // Do the inverse transform of the touch coordinates.
- Matrix m = getGLRoot().getCompensationMatrix();
- Matrix inv = new Matrix();
- m.invert(inv);
- float[] pts = new float[] {x, y};
- inv.mapPoints(pts);
- mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f));
- }
- return true;
- }
-
- @Override
- public boolean onDoubleTap(float x, float y) {
- if (mIgnoreSwipingGesture) return true;
- if (mPictures.get(0).isCamera()) return false;
- PositionController controller = mPositionController;
- float scale = controller.getImageScale();
- // onDoubleTap happened on the second ACTION_DOWN.
- // We need to ignore the next UP event.
- mIgnoreUpEvent = true;
- if (scale <= .75f || controller.isAtMinimalScale()) {
- controller.zoomIn(x, y, Math.max(1.0f, scale * 1.5f));
- } else {
- controller.resetToFullView();
- }
- return true;
- }
-
- @Override
- public boolean onScroll(float dx, float dy, float totalX, float totalY) {
- if (mIgnoreSwipingGesture) return true;
- if (!mScrolledAfterDown) {
- mScrolledAfterDown = true;
- mFirstScrollX = (Math.abs(dx) > Math.abs(dy));
- }
-
- int dxi = (int) (-dx + 0.5f);
- int dyi = (int) (-dy + 0.5f);
- if (mFilmMode) {
- if (mFirstScrollX) {
- mPositionController.scrollFilmX(dxi);
- } else {
- if (mTouchBoxIndex == Integer.MAX_VALUE) return true;
- int newDeltaY = calculateDeltaY(totalY);
- int d = newDeltaY - mDeltaY;
- if (d != 0) {
- mPositionController.scrollFilmY(mTouchBoxIndex, d);
- mDeltaY = newDeltaY;
- }
- }
- } else {
- mPositionController.scrollPage(dxi, dyi);
- }
- return true;
- }
-
- private int calculateDeltaY(float delta) {
- if (mTouchBoxDeletable) return (int) (delta + 0.5f);
-
- // don't let items that can't be deleted be dragged more than
- // maxScrollDistance, and make it harder and harder to drag.
- int size = getHeight();
- float maxScrollDistance = 0.15f * size;
- if (Math.abs(delta) >= size) {
- delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
- } else {
- delta = maxScrollDistance *
- FloatMath.sin((delta / size) * (float) (Math.PI / 2));
- }
- return (int) (delta + 0.5f);
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (mIgnoreSwipingGesture) return true;
- if (mModeChanged) return true;
- if (swipeImages(velocityX, velocityY)) {
- mIgnoreUpEvent = true;
- } else {
- flingImages(velocityX, velocityY, Math.abs(e2.getY() - e1.getY()));
- }
- mHadFling = true;
- return true;
- }
-
- private boolean flingImages(float velocityX, float velocityY, float dY) {
- int vx = (int) (velocityX + 0.5f);
- int vy = (int) (velocityY + 0.5f);
- if (!mFilmMode) {
- return mPositionController.flingPage(vx, vy);
- }
- if (Math.abs(velocityX) > Math.abs(velocityY)) {
- return mPositionController.flingFilmX(vx);
- }
- // If we scrolled in Y direction fast enough, treat it as a delete
- // gesture.
- if (!mFilmMode || mTouchBoxIndex == Integer.MAX_VALUE
- || !mTouchBoxDeletable) {
- return false;
- }
- int maxVelocity = GalleryUtils.dpToPixel(MAX_DISMISS_VELOCITY);
- int escapeVelocity = GalleryUtils.dpToPixel(SWIPE_ESCAPE_VELOCITY);
- int escapeDistance = GalleryUtils.dpToPixel(SWIPE_ESCAPE_DISTANCE);
- int centerY = mPositionController.getPosition(mTouchBoxIndex)
- .centerY();
- boolean fastEnough = (Math.abs(vy) > escapeVelocity)
- && (Math.abs(vy) > Math.abs(vx))
- && ((vy > 0) == (centerY > getHeight() / 2))
- && dY >= escapeDistance;
- if (fastEnough) {
- vy = Math.min(vy, maxVelocity);
- int duration = mPositionController.flingFilmY(mTouchBoxIndex, vy);
- if (duration >= 0) {
- mPositionController.setPopFromTop(vy < 0);
- deleteAfterAnimation(duration);
- // We reset mTouchBoxIndex, so up() won't check if Y
- // scrolled far enough to be a delete gesture.
- mTouchBoxIndex = Integer.MAX_VALUE;
- return true;
- }
- }
- return false;
- }
-
- private void deleteAfterAnimation(int duration) {
- MediaItem item = mModel.getMediaItem(mTouchBoxIndex);
- if (item == null) return;
- mListener.onCommitDeleteImage();
- mUndoIndexHint = mModel.getCurrentIndex() + mTouchBoxIndex;
- mHolding |= HOLD_DELETE;
- Message m = mHandler.obtainMessage(MSG_DELETE_ANIMATION_DONE);
- m.obj = item.getPath();
- m.arg1 = mTouchBoxIndex;
- mHandler.sendMessageDelayed(m, duration);
- }
-
- @Override
- public boolean onScaleBegin(float focusX, float focusY) {
- if (mIgnoreSwipingGesture) return true;
- // We ignore the scaling gesture if it is a camera preview.
- mIgnoreScalingGesture = mPictures.get(0).isCamera();
- if (mIgnoreScalingGesture) {
- return true;
- }
- mPositionController.beginScale(focusX, focusY);
- // We can change mode if we are in film mode, or we are in page
- // mode and at minimal scale.
- mCanChangeMode = mFilmMode
- || mPositionController.isAtMinimalScale();
- mAccScale = 1f;
- return true;
- }
-
- @Override
- public boolean onScale(float focusX, float focusY, float scale) {
- if (mIgnoreSwipingGesture) return true;
- if (mIgnoreScalingGesture) return true;
- if (mModeChanged) return true;
- if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
-
- int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
-
- // We wait for a large enough scale change before changing mode.
- // Otherwise we may mistakenly treat a zoom-in gesture as zoom-out
- // or vice versa.
- mAccScale *= scale;
- boolean largeEnough = (mAccScale < 0.97f || mAccScale > 1.03f);
-
- // If mode changes, we treat this scaling gesture has ended.
- if (mCanChangeMode && largeEnough) {
- if ((outOfRange < 0 && !mFilmMode) ||
- (outOfRange > 0 && mFilmMode)) {
- stopExtraScalingIfNeeded();
-
- // Removing the touch down flag allows snapback to happen
- // for film mode change.
- mHolding &= ~HOLD_TOUCH_DOWN;
- if (mFilmMode) {
- UsageStatistics.setPendingTransitionCause(
- UsageStatistics.TRANSITION_PINCH_OUT);
- } else {
- UsageStatistics.setPendingTransitionCause(
- UsageStatistics.TRANSITION_PINCH_IN);
- }
- setFilmMode(!mFilmMode);
-
-
- // We need to call onScaleEnd() before setting mModeChanged
- // to true.
- onScaleEnd();
- mModeChanged = true;
- return true;
- }
- }
-
- if (outOfRange != 0) {
- startExtraScalingIfNeeded();
- } else {
- stopExtraScalingIfNeeded();
- }
- return true;
- }
-
- @Override
- public void onScaleEnd() {
- if (mIgnoreSwipingGesture) return;
- if (mIgnoreScalingGesture) return;
- if (mModeChanged) return;
- mPositionController.endScale();
- }
-
- private void startExtraScalingIfNeeded() {
- if (!mCancelExtraScalingPending) {
- mHandler.sendEmptyMessageDelayed(
- MSG_CANCEL_EXTRA_SCALING, 700);
- mPositionController.setExtraScalingRange(true);
- mCancelExtraScalingPending = true;
- }
- }
-
- private void stopExtraScalingIfNeeded() {
- if (mCancelExtraScalingPending) {
- mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
- mPositionController.setExtraScalingRange(false);
- mCancelExtraScalingPending = false;
- }
- }
-
- @Override
- public void onDown(float x, float y) {
- checkHideUndoBar(UNDO_BAR_TOUCHED);
-
- mDeltaY = 0;
- mModeChanged = false;
-
- if (mIgnoreSwipingGesture) return;
-
- mHolding |= HOLD_TOUCH_DOWN;
-
- if (mFilmMode && mPositionController.isScrolling()) {
- mDownInScrolling = true;
- mPositionController.stopScrolling();
- } else {
- mDownInScrolling = false;
- }
- mHadFling = false;
- mScrolledAfterDown = false;
- if (mFilmMode) {
- int xi = (int) (x + 0.5f);
- int yi = (int) (y + 0.5f);
- // We only care about being within the x bounds, necessary for
- // handling very wide images which are otherwise very hard to fling
- mTouchBoxIndex = mPositionController.hitTest(xi, getHeight() / 2);
-
- if (mTouchBoxIndex < mPrevBound || mTouchBoxIndex > mNextBound) {
- mTouchBoxIndex = Integer.MAX_VALUE;
- } else {
- mTouchBoxDeletable =
- mPictures.get(mTouchBoxIndex).isDeletable();
- }
- } else {
- mTouchBoxIndex = Integer.MAX_VALUE;
- }
- }
-
- @Override
- public void onUp() {
- if (mIgnoreSwipingGesture) return;
-
- mHolding &= ~HOLD_TOUCH_DOWN;
- mEdgeView.onRelease();
-
- // If we scrolled in Y direction far enough, treat it as a delete
- // gesture.
- if (mFilmMode && mScrolledAfterDown && !mFirstScrollX
- && mTouchBoxIndex != Integer.MAX_VALUE) {
- Rect r = mPositionController.getPosition(mTouchBoxIndex);
- int h = getHeight();
- if (Math.abs(r.centerY() - h * 0.5f) > 0.4f * h) {
- int duration = mPositionController
- .flingFilmY(mTouchBoxIndex, 0);
- if (duration >= 0) {
- mPositionController.setPopFromTop(r.centerY() < h * 0.5f);
- deleteAfterAnimation(duration);
- }
- }
- }
-
- if (mIgnoreUpEvent) {
- mIgnoreUpEvent = false;
- return;
- }
-
- if (!(mFilmMode && !mHadFling && mFirstScrollX
- && snapToNeighborImage())) {
- snapback();
- }
- }
-
- public void setSwipingEnabled(boolean enabled) {
- mIgnoreSwipingGesture = !enabled;
- }
- }
-
- public void setSwipingEnabled(boolean enabled) {
- mGestureListener.setSwipingEnabled(enabled);
- }
-
- private void updateActionBar() {
- boolean isCamera = mPictures.get(0).isCamera();
- if (isCamera && !mFilmMode) {
- // Move into camera in page mode, lock
- mListener.onActionBarAllowed(false);
- } else {
- mListener.onActionBarAllowed(true);
- if (mFilmMode) mListener.onActionBarWanted();
- }
- }
-
- public void setFilmMode(boolean enabled) {
- if (mFilmMode == enabled) return;
- mFilmMode = enabled;
- mPositionController.setFilmMode(mFilmMode);
- mModel.setNeedFullImage(!enabled);
- mModel.setFocusHintDirection(
- mFilmMode ? Model.FOCUS_HINT_PREVIOUS : Model.FOCUS_HINT_NEXT);
- updateActionBar();
- mListener.onFilmModeChanged(enabled);
- }
-
- public boolean getFilmMode() {
- return mFilmMode;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Framework events
- ////////////////////////////////////////////////////////////////////////////
-
- public void pause() {
- mPositionController.skipAnimation();
- mTileView.freeTextures();
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
- mPictures.get(i).setScreenNail(null);
- }
- hideUndoBar();
- }
-
- public void resume() {
- mTileView.prepareTextures();
- mPositionController.skipToFinalPosition();
- }
-
- // move to the camera preview and show controls after resume
- public void resetToFirstPicture() {
- mModel.moveTo(0);
- setFilmMode(false);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Undo Bar
- ////////////////////////////////////////////////////////////////////////////
-
- private int mUndoBarState;
- private static final int UNDO_BAR_SHOW = 1;
- private static final int UNDO_BAR_TIMEOUT = 2;
- private static final int UNDO_BAR_TOUCHED = 4;
- private static final int UNDO_BAR_FULL_CAMERA = 8;
- private static final int UNDO_BAR_DELETE_LAST = 16;
-
- // "deleteLast" means if the deletion is on the last remaining picture in
- // the album.
- private void showUndoBar(boolean deleteLast) {
- mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
- mUndoBarState = UNDO_BAR_SHOW;
- if(deleteLast) mUndoBarState |= UNDO_BAR_DELETE_LAST;
- mUndoBar.animateVisibility(GLView.VISIBLE);
- mHandler.sendEmptyMessageDelayed(MSG_UNDO_BAR_TIMEOUT, 3000);
- if (mListener != null) mListener.onUndoBarVisibilityChanged(true);
- }
-
- private void hideUndoBar() {
- mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
- mListener.onCommitDeleteImage();
- mUndoBar.animateVisibility(GLView.INVISIBLE);
- mUndoBarState = 0;
- mUndoIndexHint = Integer.MAX_VALUE;
- mListener.onUndoBarVisibilityChanged(false);
- }
-
- // Check if the one of the conditions for hiding the undo bar has been
- // met. The conditions are:
- //
- // 1. It has been three seconds since last showing, and (a) the user has
- // touched, or (b) the deleted picture is the last remaining picture in the
- // album.
- //
- // 2. The camera is shown in full screen.
- private void checkHideUndoBar(int addition) {
- mUndoBarState |= addition;
- if ((mUndoBarState & UNDO_BAR_SHOW) == 0) return;
- boolean timeout = (mUndoBarState & UNDO_BAR_TIMEOUT) != 0;
- boolean touched = (mUndoBarState & UNDO_BAR_TOUCHED) != 0;
- boolean fullCamera = (mUndoBarState & UNDO_BAR_FULL_CAMERA) != 0;
- boolean deleteLast = (mUndoBarState & UNDO_BAR_DELETE_LAST) != 0;
- if ((timeout && deleteLast) || fullCamera || touched) {
- hideUndoBar();
- }
- }
-
- public boolean canUndo() {
- return (mUndoBarState & UNDO_BAR_SHOW) != 0;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Rendering
- ////////////////////////////////////////////////////////////////////////////
-
- @Override
- protected void render(GLCanvas canvas) {
- if (mFirst) {
- // Make sure the fields are properly initialized before checking
- // whether isCamera()
- mPictures.get(0).reload();
- }
- // Check if the camera preview occupies the full screen.
- boolean full = !mFilmMode && mPictures.get(0).isCamera()
- && mPositionController.isCenter()
- && mPositionController.isAtMinimalScale();
- if (mFirst || full != mFullScreenCamera) {
- mFullScreenCamera = full;
- mFirst = false;
- mListener.onFullScreenChanged(full);
- if (full) mHandler.sendEmptyMessage(MSG_UNDO_BAR_FULL_CAMERA);
- }
-
- // Determine how many photos we need to draw in addition to the center
- // one.
- int neighbors;
- if (mFullScreenCamera) {
- neighbors = 0;
- } else {
- // In page mode, we draw only one previous/next photo. But if we are
- // doing capture animation, we want to draw all photos.
- boolean inPageMode = (mPositionController.getFilmRatio() == 0f);
- boolean inCaptureAnimation =
- ((mHolding & HOLD_CAPTURE_ANIMATION) != 0);
- if (inPageMode && !inCaptureAnimation) {
- neighbors = 1;
- } else {
- neighbors = SCREEN_NAIL_MAX;
- }
- }
-
- // Draw photos from back to front
- for (int i = neighbors; i >= -neighbors; i--) {
- Rect r = mPositionController.getPosition(i);
- mPictures.get(i).draw(canvas, r);
- }
-
- renderChild(canvas, mEdgeView);
- renderChild(canvas, mUndoBar);
-
- mPositionController.advanceAnimation();
- checkFocusSwitching();
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Film mode focus switching
- ////////////////////////////////////////////////////////////////////////////
-
- // Runs in GL thread.
- private void checkFocusSwitching() {
- if (!mFilmMode) return;
- if (mHandler.hasMessages(MSG_SWITCH_FOCUS)) return;
- if (switchPosition() != 0) {
- mHandler.sendEmptyMessage(MSG_SWITCH_FOCUS);
- }
- }
-
- // Runs in main thread.
- private void switchFocus() {
- if (mHolding != 0) return;
- switch (switchPosition()) {
- case -1:
- switchToPrevImage();
- break;
- case 1:
- switchToNextImage();
- break;
- }
- }
-
- // Returns -1 if we should switch focus to the previous picture, +1 if we
- // should switch to the next, 0 otherwise.
- private int switchPosition() {
- Rect curr = mPositionController.getPosition(0);
- int center = getWidth() / 2;
-
- if (curr.left > center && mPrevBound < 0) {
- Rect prev = mPositionController.getPosition(-1);
- int currDist = curr.left - center;
- int prevDist = center - prev.right;
- if (prevDist < currDist) {
- return -1;
- }
- } else if (curr.right < center && mNextBound > 0) {
- Rect next = mPositionController.getPosition(1);
- int currDist = center - curr.right;
- int nextDist = next.left - center;
- if (nextDist < currDist) {
- return 1;
- }
- }
-
- return 0;
- }
-
- // Switch to the previous or next picture if the hit position is inside
- // one of their boxes. This runs in main thread.
- private void switchToHitPicture(int x, int y) {
- if (mPrevBound < 0) {
- Rect r = mPositionController.getPosition(-1);
- if (r.right >= x) {
- slideToPrevPicture();
- return;
- }
- }
-
- if (mNextBound > 0) {
- Rect r = mPositionController.getPosition(1);
- if (r.left <= x) {
- slideToNextPicture();
- return;
- }
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Page mode focus switching
- //
- // We slide image to the next one or the previous one in two cases: 1: If
- // the user did a fling gesture with enough velocity. 2 If the user has
- // moved the picture a lot.
- ////////////////////////////////////////////////////////////////////////////
-
- private boolean swipeImages(float velocityX, float velocityY) {
- if (mFilmMode) return false;
-
- // Avoid swiping images if we're possibly flinging to view the
- // zoomed in picture vertically.
- PositionController controller = mPositionController;
- boolean isMinimal = controller.isAtMinimalScale();
- int edges = controller.getImageAtEdges();
- if (!isMinimal && Math.abs(velocityY) > Math.abs(velocityX))
- if ((edges & PositionController.IMAGE_AT_TOP_EDGE) == 0
- || (edges & PositionController.IMAGE_AT_BOTTOM_EDGE) == 0)
- return false;
-
- // If we are at the edge of the current photo and the sweeping velocity
- // exceeds the threshold, slide to the next / previous image.
- if (velocityX < -SWIPE_THRESHOLD && (isMinimal
- || (edges & PositionController.IMAGE_AT_RIGHT_EDGE) != 0)) {
- return slideToNextPicture();
- } else if (velocityX > SWIPE_THRESHOLD && (isMinimal
- || (edges & PositionController.IMAGE_AT_LEFT_EDGE) != 0)) {
- return slideToPrevPicture();
- }
-
- return false;
- }
-
- private void snapback() {
- if ((mHolding & ~HOLD_DELETE) != 0) return;
- if (mFilmMode || !snapToNeighborImage()) {
- mPositionController.snapback();
- }
- }
-
- private boolean snapToNeighborImage() {
- Rect r = mPositionController.getPosition(0);
- int viewW = getWidth();
- // Setting the move threshold proportional to the width of the view
- int moveThreshold = viewW / 5 ;
- int threshold = moveThreshold + gapToSide(r.width(), viewW);
-
- // If we have moved the picture a lot, switching.
- if (viewW - r.right > threshold) {
- return slideToNextPicture();
- } else if (r.left > threshold) {
- return slideToPrevPicture();
- }
-
- return false;
- }
-
- private boolean slideToNextPicture() {
- if (mNextBound <= 0) return false;
- switchToNextImage();
- mPositionController.startHorizontalSlide();
- return true;
- }
-
- private boolean slideToPrevPicture() {
- if (mPrevBound >= 0) return false;
- switchToPrevImage();
- mPositionController.startHorizontalSlide();
- return true;
- }
-
- private static int gapToSide(int imageWidth, int viewWidth) {
- return Math.max(0, (viewWidth - imageWidth) / 2);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Focus switching
- ////////////////////////////////////////////////////////////////////////////
-
- public void switchToImage(int index) {
- mModel.moveTo(index);
- }
-
- private void switchToNextImage() {
- mModel.moveTo(mModel.getCurrentIndex() + 1);
- }
-
- private void switchToPrevImage() {
- mModel.moveTo(mModel.getCurrentIndex() - 1);
- }
-
- private void switchToFirstImage() {
- mModel.moveTo(0);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Opening Animation
- ////////////////////////////////////////////////////////////////////////////
-
- public void setOpenAnimationRect(Rect rect) {
- mPositionController.setOpenAnimationRect(rect);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Capture Animation
- ////////////////////////////////////////////////////////////////////////////
-
- public boolean switchWithCaptureAnimation(int offset) {
- GLRoot root = getGLRoot();
- if(root == null) return false;
- root.lockRenderThread();
- try {
- return switchWithCaptureAnimationLocked(offset);
- } finally {
- root.unlockRenderThread();
- }
- }
-
- private boolean switchWithCaptureAnimationLocked(int offset) {
- if (mHolding != 0) return true;
- if (offset == 1) {
- if (mNextBound <= 0) return false;
- // Temporary disable action bar until the capture animation is done.
- if (!mFilmMode) mListener.onActionBarAllowed(false);
- switchToNextImage();
- mPositionController.startCaptureAnimationSlide(-1);
- } else if (offset == -1) {
- if (mPrevBound >= 0) return false;
- if (mFilmMode) setFilmMode(false);
-
- // If we are too far away from the first image (so that we don't
- // have all the ScreenNails in-between), we go directly without
- // animation.
- if (mModel.getCurrentIndex() > SCREEN_NAIL_MAX) {
- switchToFirstImage();
- mPositionController.skipToFinalPosition();
- return true;
- }
-
- switchToFirstImage();
- mPositionController.startCaptureAnimationSlide(1);
- } else {
- return false;
- }
- mHolding |= HOLD_CAPTURE_ANIMATION;
- Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
- mHandler.sendMessageDelayed(m, PositionController.CAPTURE_ANIMATION_TIME);
- return true;
- }
-
- private void captureAnimationDone(int offset) {
- mHolding &= ~HOLD_CAPTURE_ANIMATION;
- if (offset == 1 && !mFilmMode) {
- // Now the capture animation is done, enable the action bar.
- mListener.onActionBarAllowed(true);
- mListener.onActionBarWanted();
- }
- snapback();
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Card deck effect calculation
- ////////////////////////////////////////////////////////////////////////////
-
- // Returns the scrolling progress value for an object moving out of a
- // view. The progress value measures how much the object has moving out of
- // the view. The object currently displays in [left, right), and the view is
- // at [0, viewWidth].
- //
- // The returned value is negative when the object is moving right, and
- // positive when the object is moving left. The value goes to -1 or 1 when
- // the object just moves out of the view completely. The value is 0 if the
- // object currently fills the view.
- private static float calculateMoveOutProgress(int left, int right,
- int viewWidth) {
- // w = object width
- // viewWidth = view width
- int w = right - left;
-
- // If the object width is smaller than the view width,
- // |....view....|
- // |<-->| progress = -1 when left = viewWidth
- // |<-->| progress = 0 when left = viewWidth / 2 - w / 2
- // |<-->| progress = 1 when left = -w
- if (w < viewWidth) {
- int zx = viewWidth / 2 - w / 2;
- if (left > zx) {
- return -(left - zx) / (float) (viewWidth - zx); // progress = (0, -1]
- } else {
- return (left - zx) / (float) (-w - zx); // progress = [0, 1]
- }
- }
-
- // If the object width is larger than the view width,
- // |..view..|
- // |<--------->| progress = -1 when left = viewWidth
- // |<--------->| progress = 0 between left = 0
- // |<--------->| and right = viewWidth
- // |<--------->| progress = 1 when right = 0
- if (left > 0) {
- return -left / (float) viewWidth;
- }
-
- if (right < viewWidth) {
- return (viewWidth - right) / (float) viewWidth;
- }
-
- return 0;
- }
-
- // Maps a scrolling progress value to the alpha factor in the fading
- // animation.
- private float getScrollAlpha(float scrollProgress) {
- return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
- 1 - Math.abs(scrollProgress)) : 1.0f;
- }
-
- // Maps a scrolling progress value to the scaling factor in the fading
- // animation.
- private float getScrollScale(float scrollProgress) {
- float interpolatedProgress = mScaleInterpolator.getInterpolation(
- Math.abs(scrollProgress));
- float scale = (1 - interpolatedProgress) +
- interpolatedProgress * TRANSITION_SCALE_FACTOR;
- return scale;
- }
-
-
- // This interpolator emulates the rate at which the perceived scale of an
- // object changes as its distance from a camera increases. When this
- // interpolator is applied to a scale animation on a view, it evokes the
- // sense that the object is shrinking due to moving away from the camera.
- private static class ZInterpolator {
- private float focalLength;
-
- public ZInterpolator(float foc) {
- focalLength = foc;
- }
-
- public float getInterpolation(float input) {
- return (1.0f - focalLength / (focalLength + input)) /
- (1.0f - focalLength / (focalLength + 1.0f));
- }
- }
-
- // Returns an interpolated value for the page/film transition.
- // When ratio = 0, the result is from.
- // When ratio = 1, the result is to.
- private static float interpolate(float ratio, float from, float to) {
- return from + (to - from) * ratio * ratio;
- }
-
- // Returns the alpha factor in film mode if a picture is not in the center.
- // The 0.03 lower bound is to make the item always visible a bit.
- private float getOffsetAlpha(float offset) {
- offset /= 0.5f;
- float alpha = (offset > 0) ? (1 - offset) : (1 + offset);
- return Utils.clamp(alpha, 0.03f, 1f);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Simple public utilities
- ////////////////////////////////////////////////////////////////////////////
-
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- public Rect getPhotoRect(int index) {
- return mPositionController.getPosition(index);
- }
-
- public PhotoFallbackEffect buildFallbackEffect(GLView root, GLCanvas canvas) {
- Rect location = new Rect();
- Utils.assertTrue(root.getBoundsOf(this, location));
-
- Rect fullRect = bounds();
- PhotoFallbackEffect effect = new PhotoFallbackEffect();
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
- MediaItem item = mModel.getMediaItem(i);
- if (item == null) continue;
- ScreenNail sc = mModel.getScreenNail(i);
- if (!(sc instanceof TiledScreenNail)
- || ((TiledScreenNail) sc).isShowingPlaceholder()) continue;
-
- // Now, sc is BitmapScreenNail and is not showing placeholder
- Rect rect = new Rect(getPhotoRect(i));
- if (!Rect.intersects(fullRect, rect)) continue;
- rect.offset(location.left, location.top);
-
- int width = sc.getWidth();
- int height = sc.getHeight();
-
- int rotation = mModel.getImageRotation(i);
- RawTexture texture;
- if ((rotation % 180) == 0) {
- texture = new RawTexture(width, height, true);
- canvas.beginRenderTarget(texture);
- canvas.translate(width / 2f, height / 2f);
- } else {
- texture = new RawTexture(height, width, true);
- canvas.beginRenderTarget(texture);
- canvas.translate(height / 2f, width / 2f);
- }
-
- canvas.rotate(rotation, 0, 0, 1);
- canvas.translate(-width / 2f, -height / 2f);
- sc.draw(canvas, 0, 0, width, height);
- canvas.endRenderTarget();
- effect.addEntry(item.getPath(), rect, texture);
- }
- return effect;
- }
-}