Support drawing in different orientation in Gallery.
Bug 6312994: Swipe UX: do not directly show the camera roll when camera starts
Bug 6313191: Swipe UX: Change swipe direction after the user rotated the device
Bug 6313192: Swiping UX: make Gallery display in rotated mode
Bug 6399447: Filmstrip: in Gallery, pressing Back from filmstrip doesn't perform the right animation
Bug 6399974: Filmstrip: when swiping from full-screen photo to filmstrip mode, camera view and the photo-roll don't align correctly
Bug 6400014: Swiping UX: in Camera portrait mode, tapping on the Thumbnail doesn't align camera view and the photo-roll correctly
Bug 6401075: Able to scroll through the gallery pics while capturing video.
Bug 6405087: Filmstrip does not change with orientation
Change-Id: I8c479d87800c63b7a95c199c0c1c3bc512d66d42
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index e6c132b..ac0d191 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -112,6 +112,26 @@
// comments above calculateStableBound() for details.
private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
+ // Constrained frame is a rectangle that the focused box should fit into if
+ // it is constrained. It has two effects:
+ //
+ // (1) In page mode, if the focused box is constrained, scaling for the
+ // focused box is adjusted to fit into the constrained frame, instead of the
+ // whole view.
+ //
+ // (2) In page mode, if the focused box is constrained, the mPlatform's
+ // default center (mDefaultX/Y) is moved to the center of the constrained
+ // frame, instead of the view center.
+ //
+ private Rect mConstrainedFrame = new Rect();
+
+ // Whether the focused box is constrained.
+ //
+ // Our current program's first call to moveBox() sets constrained = true, so
+ // we set the initial value of this variable to true, and we will not see
+ // see unwanted transition animation.
+ private boolean mConstrained = true;
+
//
// ___________________________________________________________
// | _____ _____ _____ _____ _____ |
@@ -123,7 +143,7 @@
//
// <-- Platform -->
//
- // The focused box (Box*) centers at mPlatform.mCurrentX
+ // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
private Platform mPlatform = new Platform();
private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
@@ -152,7 +172,8 @@
public PositionController(Context context, Listener listener) {
mListener = listener;
mPageScroller = new FlingScroller();
- mFilmScroller = new OverScroller(context);
+ mFilmScroller = new OverScroller(context,
+ null /* default interpolator */, false /* no flywheel */);
// Initialize the areas.
initPlatform();
@@ -186,7 +207,22 @@
snapAndRedraw();
}
- public void setImageSize(int index, int width, int height) {
+ public void setConstrainedFrame(Rect f) {
+ if (mConstrainedFrame.equals(f)) return;
+ mConstrainedFrame.set(f);
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ snapAndRedraw();
+ }
+
+ public void setImageSize(int index, int width, int height, boolean force) {
+ if (force) {
+ Box b = mBoxes.get(index);
+ b.mImageW = width;
+ b.mImageH = height;
+ return;
+ }
+
if (width == 0 || height == 0) {
initBox(index);
} else if (!setBoxSize(index, width, height, false)) {
@@ -216,11 +252,14 @@
float ratio = Math.min(
(float) b.mImageW / width, (float) b.mImageH / height);
+ b.mImageW = width;
+ b.mImageH = height;
+
// If this is the first time we receive an image size, we change the
// scale directly. Otherwise adjust the scales by a ratio, and snapback
// will animate the scale into the min/max bounds if necessary.
if (wasViewSize && !isViewSize) {
- b.mCurrentScale = getMinimalScale(width, height);
+ b.mCurrentScale = getMinimalScale(b);
b.mAnimationStartTime = NO_ANIMATION;
} else {
b.mCurrentScale *= ratio;
@@ -228,9 +267,6 @@
b.mToScale *= ratio;
}
- b.mImageW = width;
- b.mImageH = height;
-
if (i == 0) {
mFocusX /= ratio;
mFocusY /= ratio;
@@ -247,17 +283,19 @@
// Start animation from the saved rectangle if we have one.
Rect r = mOpenAnimationRect;
mOpenAnimationRect = null;
- mPlatform.mCurrentX = r.centerX();
- b.mCurrentY = r.centerY();
+ mPlatform.mCurrentX = r.centerX() - mViewW / 2;
+ b.mCurrentY = r.centerY() - mViewH / 2;
b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
r.height() / (float) b.mImageH);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_OPENING);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
+ ANIM_KIND_OPENING);
}
public void setFilmMode(boolean enabled) {
if (enabled == mFilmMode) return;
mFilmMode = enabled;
+ mPlatform.updateDefaultXY();
updateScaleAndGapLimit();
stopAnimation();
snapAndRedraw();
@@ -273,12 +311,12 @@
// This should be called whenever the scale range of boxes or the default
// gap size may change. Currently this can happen due to change of view
- // size, image size, and mode.
+ // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
private void updateScaleAndGapLimit() {
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
Box b = mBoxes.get(i);
- b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
- b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
+ b.mScaleMin = getMinimalScale(b);
+ b.mScaleMax = getMaximalScale(b);
}
for (int i = -BOX_MAX; i < BOX_MAX; i++) {
@@ -326,6 +364,7 @@
public void skipAnimation() {
if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
mPlatform.mCurrentX = mPlatform.mToX;
+ mPlatform.mCurrentY = mPlatform.mToY;
mPlatform.mAnimationStartTime = NO_ANIMATION;
}
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
@@ -353,14 +392,16 @@
////////////////////////////////////////////////////////////////////////////
public void zoomIn(float tapX, float tapY, float targetScale) {
+ tapX -= mViewW / 2;
+ tapY -= mViewH / 2;
Box b = mBoxes.get(0);
// Convert the tap position to distance to center in bitmap coordinates
float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
- int x = (int) (mViewW / 2 - tempX * targetScale + 0.5f);
- int y = (int) (mViewH / 2 - tempY * targetScale + 0.5f);
+ int x = (int) (-tempX * targetScale + 0.5f);
+ int y = (int) (-tempY * targetScale + 0.5f);
calculateStableBound(targetScale);
int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
@@ -372,10 +413,12 @@
public void resetToFullView() {
Box b = mBoxes.get(0);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_ZOOM);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
}
public void beginScale(float focusX, float focusY) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
Box b = mBoxes.get(0);
Platform p = mPlatform;
mInScale = true;
@@ -389,6 +432,8 @@
// 0 if the intended scale is in the stable range.
// -1 if the intended scale is too small for the stable range.
public int scaleBy(float s, float focusX, float focusY) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
Box b = mBoxes.get(0);
Platform p = mPlatform;
@@ -414,7 +459,7 @@
// Slide the focused box to the center of the view.
public void startHorizontalSlide() {
Box b = mBoxes.get(0);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_SLIDE);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
}
// Slide the focused box to the center of the view with the capture
@@ -426,9 +471,10 @@
Box n = mBoxes.get(offset); // the neighbor box
Gap g = mGaps.get(offset); // the gap between the two boxes
- mPlatform.doAnimation(mViewW / 2, ANIM_KIND_CAPTURE);
- b.doAnimation(mViewH / 2, b.mScaleMin, ANIM_KIND_CAPTURE);
- n.doAnimation(mViewH / 2, n.mScaleMin, ANIM_KIND_CAPTURE);
+ mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
+ ANIM_KIND_CAPTURE);
+ b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
+ n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
redraw();
}
@@ -484,17 +530,15 @@
// Horizontal direction: we show the edge effect when the scrolling
// tries to go left of the first image or go right of the last image.
- int cx = mViewW / 2;
- if (!mHasPrev && x > cx) {
- int pixels = x - cx;
- mListener.onPull(pixels, EdgeView.LEFT);
- x = cx;
- } else if (!mHasNext && x < cx) {
- int pixels = cx - x;
- mListener.onPull(pixels, EdgeView.RIGHT);
- x = cx;
+ x -= mPlatform.mDefaultX;
+ if (!mHasPrev && x > 0) {
+ mListener.onPull(x, EdgeView.LEFT);
+ x = 0;
+ } else if (!mHasNext && x < 0) {
+ mListener.onPull(-x, EdgeView.RIGHT);
+ x = 0;
}
-
+ x += mPlatform.mDefaultX;
startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
}
@@ -542,9 +586,9 @@
Platform p = mPlatform;
// If we are already at the edge, don't start the fling.
- int cx = mViewW / 2;
- if ((!mHasPrev && p.mCurrentX >= cx)
- || (!mHasNext && p.mCurrentX <= cx)) {
+ int defaultX = p.mDefaultX;
+ if ((!mHasPrev && p.mCurrentX >= defaultX)
+ || (!mHasNext && p.mCurrentX <= defaultX)) {
return false;
}
@@ -595,7 +639,7 @@
private void startAnimation(int targetX, int targetY, float targetScale,
int kind) {
boolean changed = false;
- changed |= mPlatform.doAnimation(targetX, kind);
+ changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
if (changed) redraw();
}
@@ -691,11 +735,11 @@
private void convertBoxToRect(int i) {
Box b = mBoxes.get(i);
Rect r = mRects.get(i);
- int y = b.mCurrentY;
+ int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
int w = widthOf(b);
int h = heightOf(b);
if (i == 0) {
- int x = mPlatform.mCurrentX;
+ int x = mPlatform.mCurrentX + mViewW / 2;
r.left = x - w / 2;
r.right = r.left + w;
} else if (i > 0) {
@@ -724,7 +768,9 @@
// Initialize the platform to be at the view center.
private void initPlatform() {
- mPlatform.mCurrentX = mViewW / 2;
+ mPlatform.updateDefaultXY();
+ mPlatform.mCurrentX = mPlatform.mDefaultX;
+ mPlatform.mCurrentY = mPlatform.mDefaultY;
mPlatform.mAnimationStartTime = NO_ANIMATION;
}
@@ -734,9 +780,9 @@
b.mImageW = mViewW;
b.mImageH = mViewH;
b.mUseViewSize = true;
- b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
- b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
- b.mCurrentY = mViewH / 2;
+ b.mScaleMin = getMinimalScale(b);
+ b.mScaleMax = getMaximalScale(b);
+ b.mCurrentY = 0;
b.mCurrentScale = b.mScaleMin;
b.mAnimationStartTime = NO_ANIMATION;
}
@@ -784,10 +830,16 @@
// -2 -1 0 1 2 3 N -- focus goes to the next box
// N -3 -2 -1 0 1 2 -- focuse goes to the previous box
// -3 -2 -1 1 2 3 N -- the focused box was deleted.
- public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext) {
+ //
+ // hasPrev/hasNext indicates if there are previous/next boxes for the
+ // focused box. constrained indicates whether the focused box should be put
+ // into the constrained frame.
+ public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
+ boolean constrained) {
//debugMoveBox(fromIndex);
mHasPrev = hasPrev;
mHasNext = hasNext;
+
RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
// 1. Get the absolute X coordiates for the boxes.
@@ -795,7 +847,7 @@
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
Box b = mBoxes.get(i);
Rect r = mRects.get(i);
- b.mAbsoluteX = r.centerX();
+ b.mAbsoluteX = r.centerX() - mViewW / 2;
}
// 2. copy boxes and gaps to temporary storage.
@@ -892,6 +944,12 @@
mPlatform.mToX += dx;
mPlatform.mFlingOffset += dx;
+ if (mConstrained != constrained) {
+ mConstrained = constrained;
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ }
+
snapAndRedraw();
}
@@ -899,34 +957,17 @@
// Public utilities
////////////////////////////////////////////////////////////////////////////
- public float getMinimalScale(int imageW, int imageH) {
- float wFactor = 1.0f;
- float hFactor = 1.0f;
-
- if (mFilmMode) {
- if (mViewH > mViewW) { // portrait
- wFactor = FILM_MODE_PORTRAIT_WIDTH;
- hFactor = FILM_MODE_PORTRAIT_HEIGHT;
- } else { // landscape
- wFactor = FILM_MODE_LANDSCAPE_WIDTH;
- hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
- }
- }
-
- float s = Math.min(wFactor * mViewW / imageW,
- hFactor * mViewH / imageH);
- return Math.min(SCALE_LIMIT, s);
- }
-
- public float getMaximalScale(int imageW, int imageH) {
- return mFilmMode ? getMinimalScale(imageW, imageH) : SCALE_LIMIT;
- }
-
public boolean isAtMinimalScale() {
Box b = mBoxes.get(0);
return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
}
+ public boolean isCenter() {
+ Box b = mBoxes.get(0);
+ return mPlatform.mCurrentX == mPlatform.mDefaultX
+ && b.mCurrentY == 0;
+ }
+
public int getImageWidth() {
Box b = mBoxes.get(0);
return b.mImageW;
@@ -967,11 +1008,35 @@
////////////////////////////////////////////////////////////////////////////
private float getMinimalScale(Box b) {
- return getMinimalScale(b.mImageW, b.mImageH);
+ float wFactor = 1.0f;
+ float hFactor = 1.0f;
+ int viewW, viewH;
+
+ if (!mFilmMode && mConstrained && b == mBoxes.get(0)) {
+ viewW = mConstrainedFrame.width();
+ viewH = mConstrainedFrame.height();
+ } else {
+ viewW = mViewW;
+ viewH = mViewH;
+ }
+
+ if (mFilmMode) {
+ if (mViewH > mViewW) { // portrait
+ wFactor = FILM_MODE_PORTRAIT_WIDTH;
+ hFactor = FILM_MODE_PORTRAIT_HEIGHT;
+ } else { // landscape
+ wFactor = FILM_MODE_LANDSCAPE_WIDTH;
+ hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
+ }
+ }
+
+ float s = Math.min(wFactor * viewW / b.mImageW,
+ hFactor * viewH / b.mImageH);
+ return Math.min(SCALE_LIMIT, s);
}
- private float getMaxmimalScale(Box b) {
- return getMaximalScale(b.mImageW, b.mImageH);
+ private float getMaximalScale(Box b) {
+ return mFilmMode ? getMinimalScale(b) : SCALE_LIMIT;
}
private static boolean isAlmostEqual(float a, float b) {
@@ -979,7 +1044,8 @@
return (diff < 0 ? -diff : diff) < 0.02f;
}
- // Calculates the stable region of mCurrent{X/Y}, where "stable" means
+ // Calculates the stable region of mPlatform.mCurrentX and
+ // mBoxes.get(0).mCurrentY, where "stable" means
//
// (1) If the dimension of scaled image >= view dimension, we will not
// see black region outside the image (at that dimension).
@@ -1002,20 +1068,20 @@
int h = heightOf(b, scale);
// When the edge of the view is aligned with the edge of the box
- mBoundLeft = (mViewW - horizontalSlack) - w / 2;
- mBoundRight = mViewW - mBoundLeft;
- mBoundTop = mViewH - h / 2;
- mBoundBottom = mViewH - mBoundTop;
+ mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
+ mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
+ mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
+ mBoundBottom = h / 2 - mViewH / 2;
// If the scaled height is smaller than the view height,
// force it to be in the center.
if (viewTallerThanScaledImage(scale)) {
- mBoundTop = mBoundBottom = mViewH / 2;
+ mBoundTop = mBoundBottom = 0;
}
// Same for width
if (viewWiderThanScaledImage(scale)) {
- mBoundLeft = mBoundRight = mViewW / 2;
+ mBoundLeft = mBoundRight = mPlatform.mDefaultX;
}
}
@@ -1049,13 +1115,6 @@
a.mAnimationKind == ANIM_KIND_FLING;
}
- // Returns the index of the anchor box.
- private int anchorIndex(int i) {
- if (i > 0) return i - 1;
- if (i < 0) return i + 1;
- throw new IllegalArgumentException();
- }
-
////////////////////////////////////////////////////////////////////////////
// Animatable: an thing which can do animation.
////////////////////////////////////////////////////////////////////////////
@@ -1127,10 +1186,11 @@
}
////////////////////////////////////////////////////////////////////////////
- // Platform: captures the global X movement.
+ // Platform: captures the global X/Y movement.
////////////////////////////////////////////////////////////////////////////
private class Platform extends Animatable {
- public int mCurrentX, mFromX, mToX;
+ public int mCurrentX, mFromX, mToX, mDefaultX;
+ public int mCurrentY, mFromY, mToY, mDefaultY;
public int mFlingOffset;
@Override
@@ -1146,25 +1206,47 @@
b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
int x = mCurrentX;
+ int y = mDefaultY;
if (mFilmMode) {
- if (!mHasNext) x = Math.max(x, mViewW / 2);
- if (!mHasPrev) x = Math.min(x, mViewW / 2);
+ int defaultX = mDefaultX;
+ if (!mHasNext) x = Math.max(x, defaultX);
+ if (!mHasPrev) x = Math.min(x, defaultX);
} else {
calculateStableBound(scale, HORIZONTAL_SLACK);
x = Utils.clamp(x, mBoundLeft, mBoundRight);
}
- if (mCurrentX != x) {
- return doAnimation(x, ANIM_KIND_SNAPBACK);
+ if (mCurrentX != x || mCurrentY != y) {
+ return doAnimation(x, y, ANIM_KIND_SNAPBACK);
}
return false;
}
+ // The updateDefaultXY() should be called whenever these variables
+ // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
+ // mFilmMode
+ public void updateDefaultXY() {
+ // We don't check mFilmMode and return 0 for mDefaultX. Because
+ // otherwise if we decide to leave film mode because we are
+ // centered, we will immediately back into film mode because we find
+ // we are not centered.
+ if (mConstrained && !mConstrainedFrame.isEmpty()) {
+ mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
+ mDefaultY = mFilmMode ? 0 :
+ mConstrainedFrame.centerY() - mViewH / 2;
+ } else {
+ mDefaultX = 0;
+ mDefaultY = 0;
+ }
+ }
+
// Starts an animation for the platform.
- public boolean doAnimation(int targetX, int kind) {
- if (mCurrentX == targetX) return false;
+ private boolean doAnimation(int targetX, int targetY, int kind) {
+ if (mCurrentX == targetX && mCurrentY == targetY) return false;
mAnimationKind = kind;
mFromX = mCurrentX;
+ mFromY = mCurrentY;
mToX = targetX;
+ mToY = targetY;
mAnimationStartTime = AnimationTime.startTime();
mAnimationDuration = ANIM_TIME[kind];
mFlingOffset = 0;
@@ -1188,11 +1270,11 @@
mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
int dir = EdgeView.INVALID_DIRECTION;
- if (mCurrentX < mViewW / 2) {
+ if (mCurrentX < mDefaultX) {
if (!mHasNext) {
dir = EdgeView.RIGHT;
}
- } else if (mCurrentX > mViewW / 2) {
+ } else if (mCurrentX > mDefaultX) {
if (!mHasPrev) {
dir = EdgeView.LEFT;
}
@@ -1201,7 +1283,7 @@
int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
mListener.onAbsorb(v, dir);
mFilmScroller.forceFinished(true);
- mCurrentX = mViewW / 2;
+ mCurrentX = mDefaultX;
}
return mFilmScroller.isFinished();
}
@@ -1230,15 +1312,18 @@
// Other animations
if (progress >= 1) {
mCurrentX = mToX;
+ mCurrentY = mToY;
return true;
} else {
if (mAnimationKind == ANIM_KIND_CAPTURE) {
progress = CaptureAnimation.calculateSlide(progress);
- mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ }
+ mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
return false;
} else {
- mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
- return (mCurrentX == mToX);
+ return (mCurrentX == mToX && mCurrentY == mToY);
}
}
}
@@ -1287,13 +1372,13 @@
mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
if (mFilmMode) {
- y = mViewH / 2;
+ y = 0;
} else {
calculateStableBound(scale, HORIZONTAL_SLACK);
y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
}
} else {
- y = mViewH / 2;
+ y = 0;
scale = mScaleMin;
}
@@ -1312,7 +1397,7 @@
// in the center. (We do this for height only, not width, because the
// user may want to scroll to the previous/next image.)
if (!mInScale && viewTallerThanScaledImage(targetScale)) {
- targetY = mViewH / 2;
+ targetY = 0;
}
if (mCurrentY == targetY && mCurrentScale == targetScale