summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoris Liu <tianliu@google.com>2013-08-23 13:35:24 -0700
committerDoris Liu <tianliu@google.com>2013-08-29 14:36:40 -0700
commit8de13111cc4e62da3462ea321d18c7951282e0d0 (patch)
tree6b16c5e85fd5dc5d8cbd8792c060c4043ed0fb0e
parent0df03318d36f77aaec11238854921bd45f50c035 (diff)
downloadandroid_packages_apps_Snap-8de13111cc4e62da3462ea321d18c7951282e0d0.tar.gz
android_packages_apps_Snap-8de13111cc4e62da3462ea321d18c7951282e0d0.tar.bz2
android_packages_apps_Snap-8de13111cc4e62da3462ea321d18c7951282e0d0.zip
1:1 zoom integration
Bug: 10308195 Change-Id: I940891f6aa3e24575174ff888c384fddaaa5b3c8
-rw-r--r--src/com/android/camera/data/LocalData.java7
-rw-r--r--src/com/android/camera/data/LocalMediaData.java3
-rw-r--r--src/com/android/camera/ui/FilmStripView.java279
-rw-r--r--src/com/android/camera/ui/ZoomView.java424
4 files changed, 351 insertions, 362 deletions
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index 45bcb9ffa..2db4ac577 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -126,13 +126,6 @@ public interface LocalData extends FilmStripView.ImageData {
String getPath();
/**
- * Returns the content URI of this data item.
- *
- * @return {@code Uri.EMPTY} if not valid.
- */
- Uri getContentUri();
-
- /**
* @return The mimetype of this data item, or null, if this item has no
* mimetype associated with it.
*/
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index f692cbcc0..071a9ca52 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -313,7 +313,8 @@ public abstract class LocalMediaData implements LocalData {
private static final int mSupportedUIActions =
FilmStripView.ImageData.ACTION_DEMOTE
- | FilmStripView.ImageData.ACTION_PROMOTE;
+ | FilmStripView.ImageData.ACTION_PROMOTE
+ | FilmStripView.ImageData.ACTION_ZOOM;
private static final int mSupportedDataActions =
LocalData.ACTION_DELETE;
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 0c7aa9a0f..dfb219a58 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -30,7 +31,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
import android.widget.Scroller;
import com.android.camera.CameraActivity;
@@ -64,6 +64,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
private ViewItem[] mViewItem = new ViewItem[BUFFER_SIZE];
private Listener mListener;
+ private ZoomView mZoomView = null;
private MotionEvent mDown;
private boolean mCheckToIntercept = true;
@@ -89,6 +90,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
* Common interface for all images in the filmstrip.
*/
public interface ImageData {
+
/**
* Interface that is used to tell the caller whether an image is a photo
* sphere.
@@ -116,6 +118,11 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
public static final int ACTION_NONE = 0;
public static final int ACTION_PROMOTE = 1;
public static final int ACTION_DEMOTE = (1 << 1);
+ /**
+ * For image data that supports zoom, it should also provide a valid
+ * content uri.
+ */
+ public static final int ACTION_ZOOM = (1 << 2);
/**
* SIZE_FULL can be returned by {@link ImageData#getWidth()} and
@@ -193,6 +200,13 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
/** Whether this item is a photo. */
public boolean isPhoto();
+
+ /**
+ * Returns the content URI of this data item.
+ *
+ * @return {@code Uri.EMPTY} if not valid.
+ */
+ public Uri getContentUri();
}
/**
@@ -336,7 +350,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
* An interface which defines the controller of {@link FilmStripView}.
*/
public interface Controller {
- public boolean isScalling();
+ public boolean isScaling();
public void scroll(float deltaX);
@@ -403,15 +417,38 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
}
/** Returns the translation of Y regarding the view scale. */
- public float getTranslationY(float scale) {
+ public float getScaledTranslationY(float scale) {
return mView.getTranslationY() / scale;
}
/** Returns the translation of X regarding the view scale. */
- public float getTranslationX(float scale) {
+ public float getScaledTranslationX(float scale) {
return mView.getTranslationX() / scale;
}
+ /**
+ * The horizontal location of this view relative to its left position.
+ * This position is post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @return The horizontal position of this view relative to its left position, in pixels.
+ */
+ public float getTranslationX() {
+ return mView.getTranslationX();
+ }
+
+ /**
+ * The vertical location of this view relative to its top position.
+ * This position is post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @return The vertical position of this view relative to its top position,
+ * in pixels.
+ */
+ public float getTranslationY() {
+ return mView.getTranslationY();
+ }
+
/** Sets the translation of Y regarding the view scale. */
public void setTranslationY(float transY, float scale) {
mView.setTranslationY(transY * scale);
@@ -436,12 +473,38 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
return mView;
}
+ /**
+ * The visual x position of this view, in pixels.
+ */
+ public float getX() {
+ return mView.getX();
+ }
+
+ /**
+ * The visual y position of this view, in pixels.
+ */
+ public float getY() {
+ return mView.getY();
+ }
+
private void layoutAt(int left, int top) {
mView.layout(left, top, left + mView.getMeasuredWidth(),
top + mView.getMeasuredHeight());
}
/**
+ * The bounding rect of the view.
+ */
+ public RectF getViewRect() {
+ RectF r = new RectF();
+ r.left = mView.getX();
+ r.top = mView.getY();
+ r.right = r.left + mView.getWidth() * mView.getScaleX();
+ r.bottom = r.top + mView.getHeight() * mView.getScaleY();
+ return r;
+ }
+
+ /**
* Layouts the view in the area assuming the center of the area is at a
* specific point of the whole filmstrip.
*
@@ -470,12 +533,37 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
return mViewArea.contains(x, y);
}
+ /**
+ * Return the width of the view.
+ */
+ public int getWidth() {
+ return mView.getWidth();
+ }
+
public void copyGeometry(ViewItem item) {
setLeftPosition(item.getLeftPosition());
View v = item.getView();
mView.setTranslationY(v.getTranslationY());
mView.setTranslationX(v.getTranslationX());
}
+
+
+ void updateTransform(float transX, float transY, float scaleX, float scaleY,
+ int viewportWidth, int viewportHeight) {
+ int left = (int) transX + mView.getLeft();
+ int top = (int) transY + mView.getTop();
+ Rect r = ZoomView.adjustToFitInBounds(new Rect(left, top,
+ left + (int) (mView.getWidth() * scaleX),
+ top + (int) (mView.getHeight() * scaleY)),
+ viewportWidth, viewportHeight);
+ mView.setScaleX(scaleX);
+ mView.setScaleY(scaleY);
+ transX = r.left - mView.getLeft();
+ transY = r.top - mView.getTop();
+ mView.setTranslationX(transX);
+ mView.setTranslationY(transY);
+ }
+
}
public FilmStripView(Context context) {
@@ -506,6 +594,10 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
mScale = 1.0f;
mController = new MyController(cameraActivity);
mViewAnimInterpolator = new DecelerateInterpolator();
+ mZoomView = new ZoomView(cameraActivity);
+ mZoomView.setVisibility(GONE);
+ addView(mZoomView);
+
mGestureRecognizer =
new FilmStripGestureRecognizer(cameraActivity, new MyGestureReceiver());
mSlop = (int) getContext().getResources().getDimension(R.dimen.pie_touch_slop);
@@ -547,9 +639,6 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
}
private int getCurrentViewType() {
- if (mDataAdapter == null) {
- return ImageData.TYPE_NONE;
- }
ViewItem curr = mViewItem[mCurrentItem];
if (curr == null) {
return ImageData.TYPE_NONE;
@@ -623,6 +712,12 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
measureViewItem(item, boundWidth, boundHeight);
}
}
+ // Measure zoom view
+ mZoomView.measure(
+ MeasureSpec.makeMeasureSpec(
+ widthMeasureSpec, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ heightMeasureSpec, MeasureSpec.EXACTLY));
}
@Override
@@ -868,7 +963,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
snapInTime, false);
}
if (getCurrentViewType() == ImageData.TYPE_STICKY_VIEW
- && !mController.isScalling()
+ && !mController.isScaling()
&& mScale != FULL_SCREEN_SCALE) {
mController.goToFullScreen();
}
@@ -998,7 +1093,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
// The normal filmstrip has no translation for the current item. If it has
// translation before, gradually set it to zero.
mViewItem[mCurrentItem].setTranslationX(
- mViewItem[mCurrentItem].getTranslationX(mScale) * scaleFraction,
+ mViewItem[mCurrentItem].getScaledTranslationX(mScale) * scaleFraction,
mScale);
mViewItem[mCurrentItem].layoutIn(mDrawArea, mCenterX, mScale);
}
@@ -1062,6 +1157,10 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
updateBottomControls();
mLastItemId = getCurrentId();
+ // Layout zoom view
+ mZoomView.layout(mDrawArea.left, mDrawArea.top, mDrawArea.right, mDrawArea.bottom);
+ bringChildToFront(mZoomView);
+
invalidate();
}
@@ -1192,7 +1291,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
// Now, slide every one back.
for (int i = 0; i < BUFFER_SIZE; i++) {
if (mViewItem[i] != null
- && mViewItem[i].getTranslationX(mScale) != 0f) {
+ && mViewItem[i].getScaledTranslationX(mScale) != 0f) {
slideViewBack(mViewItem[i].getView());
}
}
@@ -1560,8 +1659,8 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
}
@Override
- public boolean isScalling() {
- return (mScaleAnimator.isRunning());
+ public boolean isScaling() {
+ return mScaleAnimator.isRunning();
}
boolean hasNewGeometry() {
@@ -1716,6 +1815,11 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
GEOMETRY_ADJUST_TIME_MS, false);
}
enterFullScreen();
+
+ if (inFullScreen()) {
+ return;
+ }
+ scaleTo(1f, GEOMETRY_ADJUST_TIME_MS);
}
private void enterFullScreen() {
@@ -1727,10 +1831,70 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
mListener.onSwitchMode(true);
mBottomControls.setVisibility(View.GONE);
}
- if (inFullScreen()) {
+ }
+
+ private void setSurroundingViewsVisible(boolean visible) {
+ // Hide everything on the left
+ // TODO: Need to find a better way to toggle the visibility of views around
+ // the current view.
+ for (int i = 0; i < mCurrentItem; i++) {
+ if (i == mCurrentItem || mViewItem[i] == null) {
+ continue;
+ }
+ mViewItem[i].getView().setVisibility(visible ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private void leaveFullScreen() {
+ if (mListener != null) {
+ mListener.onSwitchMode(false);
+ mBottomControls.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private Uri getCurrentContentUri() {
+ ViewItem curr = mViewItem[mCurrentItem];
+ if (curr == null) {
+ return Uri.EMPTY;
+ }
+ return mDataAdapter.getImageData(curr.getID()).getContentUri();
+ }
+
+ /**
+ * Here we only support up to 1:1 image zoom (i.e. a 100% view of the
+ * actual pixels). The max scale that we can apply on the view should
+ * make the view same size as the image, in pixels.
+ */
+ private float getCurrentDataMaxScale() {
+ ViewItem curr = mViewItem[mCurrentItem];
+ ImageData imageData = mDataAdapter.getImageData(curr.getID());
+ if (curr == null || !imageData
+ .isUIActionSupported(ImageData.ACTION_ZOOM)) {
+ return FULL_SCREEN_SCALE;
+ }
+ float imageWidth = (float) imageData.getWidth();
+ return imageWidth / (float) curr.getWidth();
+ }
+
+ private void loadZoomedImage() {
+ if (!isZoomStarted()) {
return;
}
- scaleTo(1f, GEOMETRY_ADJUST_TIME_MS);
+ ViewItem curr = mViewItem[mCurrentItem];
+ if(curr == null || !mDataAdapter.getImageData(curr.getID())
+ .isUIActionSupported(ImageData.ACTION_ZOOM)) {
+ return;
+ }
+ Uri uri = getCurrentContentUri();
+ RectF viewRect = curr.getViewRect();
+ if (uri == null || uri == Uri.EMPTY) {
+ return;
+ }
+ mZoomView.loadBitmap(uri, viewRect);
+ }
+
+ private void cancelLoadingZoomedImage() {
+ mZoomView.cancelPartialDecodingTask();
}
@Override
@@ -1782,11 +1946,16 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
@Override
public void onAnimationRepeat(Animator anim) {
}
+
+ public boolean isZoomStarted() {
+ return mScale > FULL_SCREEN_SCALE;
+ }
}
private class MyGestureReceiver implements FilmStripGestureRecognizer.Listener {
// Indicating the current trend of scaling is up (>1) or down (<1).
private float mScaleTrend;
+ private float mMaxScale;
@Override
public boolean onSingleTapUp(float x, float y) {
@@ -1819,13 +1988,17 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
@Override
public boolean onUp(float x, float y) {
+ if (mController.isZoomStarted()) {
+ mController.loadZoomedImage();
+ return true;
+ }
float halfH = getHeight() / 2;
mIsUserScrolling = false;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (mViewItem[i] == null) {
continue;
}
- float transY = mViewItem[i].getTranslationY(mScale);
+ float transY = mViewItem[i].getScaledTranslationY(mScale);
if (transY == 0) {
continue;
}
@@ -1857,6 +2030,16 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
if (mViewItem[mCurrentItem] == null) {
return false;
}
+ // When image is zoomed in to be bigger than the screen
+ if (mController.isZoomStarted()) {
+ mController.cancelLoadingZoomedImage();
+ ViewItem curr = mViewItem[mCurrentItem];
+ float transX = curr.getTranslationX() - dx;
+ float transY = curr.getTranslationY() - dy;
+ curr.updateTransform(transX, transY, mScale, mScale, mDrawArea.width(),
+ mDrawArea.height());
+ return true;
+ }
mIsUserScrolling = true;
int deltaX = (int) (dx / mScale);
if (inFilmStrip()) {
@@ -1886,7 +2069,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
}
ImageData data = mDataAdapter.getImageData(mViewItem[hit].getID());
- float transY = mViewItem[hit].getTranslationY(mScale) - dy / mScale;
+ float transY = mViewItem[hit].getScaledTranslationY(mScale) - dy / mScale;
if (!data.isUIActionSupported(ImageData.ACTION_DEMOTE) && transY > 0f) {
transY = 0f;
}
@@ -1916,7 +2099,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
if (mScale != FILM_STRIP_SCALE) {
// No fling in other modes.
- return true;
+ return false;
}
mController.fling(velocityX);
return true;
@@ -1924,10 +2107,12 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
@Override
public boolean onScaleBegin(float focusX, float focusY) {
+ mController.cancelLoadingZoomedImage();
if (inCameraFullscreen()) {
return false;
}
mScaleTrend = 1f;
+ mMaxScale = mController.getCurrentDataMaxScale();
return true;
}
@@ -1938,20 +2123,66 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
}
mScaleTrend = mScaleTrend * 0.3f + scale * 0.7f;
- mScale *= scale;
- if (mScale <= FILM_STRIP_SCALE) {
- mScale = FILM_STRIP_SCALE;
- }
- if (mScale >= FULL_SCREEN_SCALE) {
+ if ((mScale < FULL_SCREEN_SCALE) && (mScale * scale < FULL_SCREEN_SCALE)) {
+ // Scaled view is smaller than or equal to screen size both before
+ // and after scaling
+ mScale *= scale;
+ if (mScale <= FILM_STRIP_SCALE) {
+ mScale = FILM_STRIP_SCALE;
+ }
+ layoutChildren();
+ } else if ((mScale < FULL_SCREEN_SCALE) && (mScale * scale >= FULL_SCREEN_SCALE)) {
+ // Going from smaller than screen size to bigger than or equal to screen size
mScale = FULL_SCREEN_SCALE;
+ mController.enterFullScreen();
+ layoutChildren();
+ mController.setSurroundingViewsVisible(false);
+ } else if ((mScale >= FULL_SCREEN_SCALE) && (mScale * scale < FULL_SCREEN_SCALE)) {
+ // Going from bigger than or equal to screen size to smaller than screen size
+ mScale *= scale;
+ mController.leaveFullScreen();
+ layoutChildren();
+ mController.setSurroundingViewsVisible(true);
+ } else {
+ // Scaled view bigger than or equal to screen size both before
+ // and after scaling
+ if (!mController.isZoomStarted()) {
+ mController.setSurroundingViewsVisible(false);
+ }
+ ViewItem curr = mViewItem[mCurrentItem];
+ // Make sure the image is not overly scaled
+ if (mScale * scale > mMaxScale) {
+ scale = mMaxScale / mScale;
+ }
+ mScale *= scale;
+ float transX = curr.getTranslationX();
+ float transY = curr.getTranslationY();
+ // Pivot point is top left of the view, so we need to translate
+ // to scale around focus point
+ transX -= (focusX - curr.getX()) * (scale - 1f);
+ transY -= (focusY - curr.getY()) * (scale - 1f);
+ curr.updateTransform(transX, transY, mScale, mScale, mDrawArea.width(),
+ mDrawArea.height());
}
- layoutChildren();
return true;
}
+
@Override
public void onScaleEnd() {
- if (mScaleTrend >= 1f) {
+ float tolerance = 0.1f;
+ if (mScale > FULL_SCREEN_SCALE + tolerance) {
+ return;
+ }
+ mController.setSurroundingViewsVisible(true);
+ if (mScale <= FILM_STRIP_SCALE + tolerance) {
+ mController.goToFilmStrip();
+ } else if (mScaleTrend > 1f || mScale > FULL_SCREEN_SCALE - tolerance) {
+ if (mController.isZoomStarted()) {
+ mScale = FULL_SCREEN_SCALE;
+ mViewItem[mCurrentItem].updateTransform(0f, 0f, 1f, 1f, mDrawArea.width(),
+ mDrawArea.height());
+ }
mController.goToFullScreen();
} else {
mController.goToFilmStrip();
diff --git a/src/com/android/camera/ui/ZoomView.java b/src/com/android/camera/ui/ZoomView.java
index edf151367..7f338ff46 100644
--- a/src/com/android/camera/ui/ZoomView.java
+++ b/src/com/android/camera/ui/ZoomView.java
@@ -16,9 +16,6 @@
package com.android.camera.ui;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -28,171 +25,32 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
+import android.util.AttributeSet;
import android.util.Log;
-import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-public class ZoomView extends FrameLayout {
+public class ZoomView extends ImageView {
private static final String TAG = "ZoomView";
- // Gesture statuses
- private static final int IDLE = 0;
- private static final int SCALE = 1;
- private static final int SCROLL = 2;
-
- // When the image is zoomed in to within TOLERANCE pixels of its original size,
- // we consider it all the way zoomed in
- private static final int TOLERANCE = 10;
-
- private static final int ANIMATION_DURATION_MS = 200;
-
private int mViewportWidth = 0;
private int mViewportHeight = 0;
- private ScaleGestureDetector mScaleDetector;
- private GestureDetector mGesturesDetector;
- private int mGestureState = IDLE;
-
- private ImageView mPartialImage;
- private ImageView mFullImage;
-
private RectF mInitialRect;
private int mFullResImageWidth;
private int mFullResImageHeight;
private BitmapRegionDecoder mRegionDecoder;
private DecodePartialBitmap mPartialDecodingTask;
- private LoadBitmapTask mFullImageDecodingTask;
- private RectF mBitmapRect;
- private float mLastScalePivotX;
- private float mLastScalePivotY;
-
- private ObjectAnimator mAnimator;
private Uri mUri;
- private TypeEvaluator<Matrix> mEvaluator = new TypeEvaluator<Matrix>() {
- @Override
- public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
-
- RectF startRect = new RectF();
- startValue.mapRect(startRect, mBitmapRect);
- RectF endRect = new RectF();
- endValue.mapRect(endRect, mBitmapRect);
-
- float top = startRect.top + (endRect.top - startRect.top) * fraction;
- float left = startRect.left + (endRect.left - startRect.left) * fraction;
- float right = startRect.right + (endRect.right - startRect.right) * fraction;
- float bottom = startRect.bottom + (endRect.bottom - startRect.bottom) * fraction;
- RectF currentRect = new RectF(left, top, right, bottom);
-
- Matrix m = new Matrix();
- m.setRectToRect(mBitmapRect, currentRect, Matrix.ScaleToFit.CENTER);
- return m;
- }
- };
-
- private GestureDetector.SimpleOnGestureListener mGestureListener
- = new GestureDetector.SimpleOnGestureListener(){
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- zoomAt(e.getX(), e.getY());
- return true;
- }
-
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- if (mGestureState == SCALE) {
- return false;
- }
- mGestureState = SCROLL;
-
- // Translate image matrix
- Matrix m = new Matrix(mFullImage.getImageMatrix());
- m.postTranslate(-distanceX, -distanceY);
- mFullImage.setImageMatrix(m);
- return true;
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent ev) {
- showPartiallyDecodedImage(true);
- return true;
- }
- };
-
- private ScaleGestureDetector.OnScaleGestureListener mScaleListener
- = new ScaleGestureDetector.OnScaleGestureListener() {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- float scaleFactor = detector.getScaleFactor();
- mLastScalePivotX = detector.getFocusX();
- mLastScalePivotY = detector.getFocusY();
-
- // Scale image matrix
- Matrix m = new Matrix(mFullImage.getImageMatrix());
- m.postScale(scaleFactor, scaleFactor, mLastScalePivotX, mLastScalePivotY);
- mFullImage.setImageMatrix(m);
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- mGestureState = SCALE;
- cancelPartialDecodingTask();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mGestureState = IDLE;
- snapBack();
- }
- };
-
- private void cancelPartialDecodingTask() {
- if (mPartialDecodingTask != null) {
- mPartialDecodingTask.cancel(true);
- }
- }
-
- private class LoadBitmapTask extends AsyncTask<Object, Void, Bitmap> {
- @Override
- protected Bitmap doInBackground(Object... params) {
- // Params[0]: BitmapFactory.options
- if (params.length < 1) {
- return null;
- }
- InputStream is = getInputStream();
- if (isCancelled()) {
- return null;
- }
- BitmapFactory.Options options = (BitmapFactory.Options) params[0];
- return BitmapFactory.decodeStream(is, null, options);
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Failed to load bitmap");
- return;
- }
- initFullImageView(bitmap);
- mFullImageDecodingTask = null;
- }
- }
-
private class DecodePartialBitmap extends AsyncTask<RectF, Void, Bitmap> {
@Override
@@ -226,30 +84,15 @@ public class ZoomView extends FrameLayout {
if (b == null) {
return;
}
- mPartialImage.setImageBitmap(b);
+ setImageBitmap(b);
showPartiallyDecodedImage(true);
mPartialDecodingTask = null;
}
}
- public ZoomView(Context context, Uri uri) {
+ public ZoomView(Context context) {
super(context);
- mUri = uri;
- mPartialImage = new ImageView(context);
- mFullImage = new ImageView(context);
- LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- addView(mPartialImage, lp);
- addView(mFullImage, lp);
- mFullImage.setScaleType(ImageView.ScaleType.MATRIX);
- InputStream is = getInputStream();
- try {
- mRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
- is.close();
- } catch (IOException e) {
- Log.e(TAG, "Fail to instantiate region decoder");
- }
-
+ setScaleType(ScaleType.CENTER_INSIDE);
addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
@@ -259,129 +102,131 @@ public class ZoomView extends FrameLayout {
if (mViewportHeight != h || mViewportWidth != w) {
mViewportWidth = w;
mViewportHeight = h;
- loadBitmap();
}
}
});
- mGesturesDetector = new GestureDetector(getContext(), mGestureListener);
- mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener);
- }
-
- private void initFullImageView(Bitmap bitmap) {
- mFullImage.setImageBitmap(bitmap);
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- mBitmapRect = new RectF(0, 0, w, h);
- Matrix initialMatrix = new Matrix();
- initialMatrix.setRectToRect(mBitmapRect, mInitialRect, Matrix.ScaleToFit.CENTER);
- mFullImage.setImageMatrix(initialMatrix);
}
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- // TODO: The touch event handling could use some refinement
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- endAnimation();
- cancelPartialDecodingTask();
- // Show down-sampled full image when there is touch interaction
- showPartiallyDecodedImage(false);
- mGestureState = IDLE;
- } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
- && mGestureState == SCROLL) {
- snapBack();
- mGestureState = IDLE;
- } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- mGestureState = SCALE;
+ public void loadBitmap(Uri uri, RectF imageRect) {
+ mUri = uri;
+ mFullResImageHeight = 0;
+ mFullResImageWidth = 0;
+ InputStream is = getInputStream();
+ try {
+ mRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
+ is.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to instantiate region decoder");
}
- boolean ret = mGesturesDetector.onTouchEvent(ev);
- return mScaleDetector.onTouchEvent(ev) || ret;
+ decodeImageSize();
+ startPartialDecodingTask(imageRect);
}
private void showPartiallyDecodedImage(boolean show) {
if (show) {
- mPartialImage.setVisibility(View.VISIBLE);
- mFullImage.setVisibility(View.GONE);
+ setVisibility(View.VISIBLE);
} else {
- mFullImage.setVisibility(View.VISIBLE);
- mPartialImage.setVisibility(View.GONE);
+ setVisibility(View.GONE);
}
+ mPartialDecodingTask = null;
+ }
+
+ public boolean onTouchEvent(MotionEvent e) {
+ setVisibility(GONE);
+ return false;
+ }
+
+ public void cancelPartialDecodingTask() {
+ if (mPartialDecodingTask != null && !mPartialDecodingTask.isCancelled()) {
+ mPartialDecodingTask.cancel(true);
+ setVisibility(GONE);
+ }
+ mPartialDecodingTask = null;
}
/**
- * Snap back to the screen bounds from current position
+ * snap back to the screen bounds from current position
*/
private void snapBack() {
- RectF endRect = new RectF();
- mFullImage.getImageMatrix().mapRect(endRect, mBitmapRect);
- snapBack(endRect);
}
/**
- * Snap back to the screen bounds from given position
+ * snap back to the screen bounds from given position
+ * @param rect
+ * @return resulting rect after snapping back
*/
- private void snapBack(RectF endRect) {
-
- if (endRect.width() < mViewportWidth && endRect.height() < mViewportHeight) {
- snapToInitialRect(true);
- return;
+ private RectF snapBack(RectF rect) {
+ RectF newRect = new RectF(rect);
+ if (rect.width() < mViewportWidth && rect.height() < mViewportHeight) {
+ newRect = mInitialRect;
+ return newRect;
}
float dx = 0, dy = 0;
- Matrix startMatrix = mFullImage.getImageMatrix();
- Matrix endMatrix = new Matrix(startMatrix);
- boolean needsSnapping = false;
-
- if (endRect.width() > mFullResImageWidth) {
- needsSnapping = true;
- float x = mScaleDetector.getFocusX();
- float y = mScaleDetector.getFocusY();
- float scale = mFullResImageWidth / endRect.width();
- endMatrix.postScale(scale, scale, x, y);
- endMatrix.mapRect(endRect, mBitmapRect);
- }
- if (endRect.width() < mViewportWidth) {
+ if (newRect.width() < mViewportWidth) {
// Center it
- dx = mViewportWidth / 2 - (endRect.left + endRect.right) / 2;
+ dx = mViewportWidth / 2 - (newRect.left + newRect.right) / 2;
} else {
- if (endRect.left > 0) {
- dx = -endRect.left;
- } else if (endRect.right < mViewportWidth) {
- dx = mViewportWidth - endRect.right;
+ if (newRect.left > 0) {
+ dx = -newRect.left;
+ } else if (newRect.right < mViewportWidth) {
+ dx = mViewportWidth - newRect.right;
}
}
- if (endRect.height() < mViewportHeight) {
- dy = mViewportHeight / 2 - (endRect.top + endRect.bottom) / 2;
+ if (newRect.height() < mViewportHeight) {
+ dy = mViewportHeight / 2 - (newRect.top + newRect.bottom) / 2;
} else {
- if (endRect.top > 0) {
- dy = -endRect.top;
- } else if (endRect.bottom < mViewportHeight) {
- dy = mViewportHeight - endRect.bottom;
+ if (newRect.top > 0) {
+ dy = -newRect.top;
+ } else if (newRect.bottom < mViewportHeight) {
+ dy = mViewportHeight - newRect.bottom;
}
}
- if (dx != 0 || dy != 0 || needsSnapping) {
- endRect.offset(dx, dy);
- endMatrix.postTranslate(dx, dy);
- startAnimation(startMatrix, endMatrix);
+ if (dx != 0 || dy != 0) {
+ newRect.offset(dx, dy);
}
-
- startPartialDecodingTask(endRect);
+ return newRect;
}
- private void snapToInitialRect(boolean withAnimation) {
- // Restore to initial rect
- Matrix endMatrix = new Matrix();
- endMatrix.setRectToRect(mBitmapRect, mInitialRect, Matrix.ScaleToFit.CENTER);
- if (withAnimation) {
- startAnimation(mFullImage.getImageMatrix(), endMatrix);
+ /**
+ * If the given rect is smaller than viewport on x or y axis, center rect within
+ * viewport on the corresponding axis. Otherwise, make sure viewport is within
+ * the bounds of the rect.
+ */
+ public static Rect adjustToFitInBounds(Rect rect, int viewportWidth, int viewportHeight) {
+ int dx = 0, dy = 0;
+ Rect newRect = new Rect(rect);
+ if (newRect.width() < viewportWidth) {
+ dx = viewportWidth / 2 - (newRect.left + newRect.right) / 2;
+ } else {
+ if (newRect.left > 0) {
+ dx = -newRect.left;
+ } else if (newRect.right < viewportWidth) {
+ dx = viewportWidth - newRect.right;
+ }
+ }
+
+ if (newRect.height() < viewportHeight) {
+ dy = viewportHeight / 2 - (newRect.top + newRect.bottom) / 2;
} else {
- mFullImage.setImageMatrix(endMatrix);
+ if (newRect.top > 0) {
+ dy = -newRect.top;
+ } else if (newRect.bottom < viewportHeight) {
+ dy = viewportHeight - newRect.bottom;
+ }
}
+
+ if (dx != 0 || dy != 0) {
+ newRect.offset(dx, dy);
+ }
+ return newRect;
}
private void zoomAt(float x, float y) {
+ /* TODO: double tap to zoom
Matrix startMatrix = mFullImage.getImageMatrix();
Matrix endMatrix = new Matrix();
RectF currentImageRect = new RectF();
@@ -392,63 +237,22 @@ public class ZoomView extends FrameLayout {
float scale = ((float) mFullResImageWidth) / currentImageRect.width();
endMatrix.set(startMatrix);
endMatrix.postScale(scale, scale, x, y);
- // Start animation
- startAnimation(startMatrix, endMatrix);
-
RectF endRect = new RectF();
endMatrix.mapRect(endRect, mBitmapRect);
- startPartialDecodingTask(endRect);
+ RectF snapBackRect = snapBack(endRect);
+ endMatrix.setRectToRect(mBitmapRect, snapBackRect, Matrix.ScaleToFit.CENTER);
+ // Start animation
+ startAnimation(startMatrix, endMatrix);
+ startPartialDecodingTask(snapBackRect);
} else {
// Zoom out
endMatrix.setRectToRect(mBitmapRect, mInitialRect, Matrix.ScaleToFit.CENTER);
// Start animation
startAnimation(startMatrix, endMatrix);
- }
+ } */
}
- private void startAnimation(Matrix startMatrix, final Matrix endMatrix) {
- endAnimation();
- showPartiallyDecodedImage(false);
- mAnimator = ObjectAnimator.ofObject(mFullImage, "imageMatrix", mEvaluator,
- startMatrix, endMatrix)
- .setDuration(ANIMATION_DURATION_MS);
- mAnimator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
-
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // Set end value
- mFullImage.setImageMatrix(endMatrix);
- mAnimator.removeAllListeners();
- mAnimator = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- mAnimator.start();
- }
-
- private void endAnimation() {
- if (mAnimator == null) {
- return;
- }
- if (mAnimator.isRunning()) {
- mAnimator.end();
- }
- }
-
private void startPartialDecodingTask(RectF endRect) {
// Cancel on-going partial decoding tasks
cancelPartialDecodingTask();
@@ -456,46 +260,6 @@ public class ZoomView extends FrameLayout {
mPartialDecodingTask.execute(endRect);
}
- private void loadBitmap() {
- if (mFullResImageHeight == 0 || mFullResImageWidth == 0) {
- decodeImageSize();
- }
-
- if (mViewportHeight != 0 && mViewportWidth != 0) {
- // Calculate where the bitmap rect should be positioned based on viewport size
- calculateInitialRect();
- if (mBitmapRect != null && mBitmapRect.width() > mInitialRect.width()
- && mBitmapRect.height() > mInitialRect.height()) {
- // No need to reload bitmap
- snapToInitialRect(false);
- } else {
- BitmapFactory.Options option = new BitmapFactory.Options();
- // Down-sample the bitmap whenever possible to be efficient
- int sampleFactor = getSampleFactor(mFullResImageWidth, mFullResImageHeight);
- option.inSampleSize = sampleFactor;
- if (mFullImageDecodingTask != null) {
- mFullImageDecodingTask.cancel(true);
- }
- mFullImageDecodingTask = new LoadBitmapTask();
- mFullImageDecodingTask.execute(option);
- }
- }
- }
-
- private void calculateInitialRect() {
- float fitWidthScale = ((float) mViewportWidth) / ((float) mFullResImageWidth);
- float fitHeightScale = ((float) mViewportHeight) / ((float) mFullResImageHeight);
- float scale = Math.min(fitHeightScale, fitWidthScale);
-
- int centerX = mViewportWidth / 2;
- int centerY = mViewportHeight / 2;
- int width = (int) (scale * mFullResImageWidth);
- int height = (int) (scale * mFullResImageHeight);
-
- mInitialRect = new RectF(centerX - width / 2, centerY - height / 2,
- centerX + width / 2, centerY + height / 2);
- }
-
private void decodeImageSize() {
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true;