Dbrant has uploaded a new change for review.
https://gerrit.wikimedia.org/r/269891
Change subject: Bring back pinch-to-zoom in gallery.
......................................................................
Bring back pinch-to-zoom in gallery.
So, it looks like the Fresco repo contains a "samples" directory that
contains a "ZoomableDraweeView" that precisely suits our needs.
Unfortunately, this is not yet part of the packaged distribution of
Fresco, so we need to package it ourselves for now, and keep an eye out
for updates.
Bug: T126164
Change-Id: I764a81e904f889318744a985fa197d4c1373c971
---
A
app/src/main/java/com/facebook/samples/gestures/MultiPointerGestureDetector.java
A app/src/main/java/com/facebook/samples/gestures/TransformGestureDetector.java
A app/src/main/java/com/facebook/samples/zoomable/DefaultZoomableController.java
A app/src/main/java/com/facebook/samples/zoomable/ZoomableController.java
A app/src/main/java/com/facebook/samples/zoomable/ZoomableDraweeView.java
M app/src/main/java/org/wikipedia/page/gallery/GalleryItemFragment.java
M app/src/main/res/layout/fragment_gallery_item.xml
7 files changed, 1,250 insertions(+), 10 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia
refs/changes/91/269891/1
diff --git
a/app/src/main/java/com/facebook/samples/gestures/MultiPointerGestureDetector.java
b/app/src/main/java/com/facebook/samples/gestures/MultiPointerGestureDetector.java
new file mode 100644
index 0000000..5ed4874
--- /dev/null
+++
b/app/src/main/java/com/facebook/samples/gestures/MultiPointerGestureDetector.java
@@ -0,0 +1,240 @@
+/*
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.facebook.samples.gestures;
+
+import android.view.MotionEvent;
+
+/**
+ * Component that detects and tracks multiple pointers based on touch events.
+ * <p>
+ * Each time a pointer gets pressed or released, the current gesture (if any)
will end, and a new
+ * one will be started (if there are still pressed pointers left). It is
guaranteed that the number
+ * of pointers within the single gesture will remain the same during the whole
gesture.
+ */
+public class MultiPointerGestureDetector {
+
+ /** The listener for receiving notifications when gestures occur. */
+ public interface Listener {
+ /** Responds to the beginning of a gesture. */
+ void onGestureBegin(MultiPointerGestureDetector detector);
+
+ /** Responds to the update of a gesture in progress. */
+ void onGestureUpdate(MultiPointerGestureDetector detector);
+
+ /** Responds to the end of a gesture. */
+ void onGestureEnd(MultiPointerGestureDetector detector);
+ }
+
+ private static final int MAX_POINTERS = 2;
+
+ private boolean mGestureInProgress;
+ private int mCount;
+ private final int[] mId = new int[MAX_POINTERS];
+ private final float[] mStartX = new float[MAX_POINTERS];
+ private final float[] mStartY = new float[MAX_POINTERS];
+ private final float[] mCurrentX = new float[MAX_POINTERS];
+ private final float[] mCurrentY = new float[MAX_POINTERS];
+
+ private Listener mListener = null;
+
+ public MultiPointerGestureDetector() {
+ reset();
+ }
+
+ /** Factory method that creates a new instance of
MultiPointerGestureDetector */
+ public static MultiPointerGestureDetector newInstance() {
+ return new MultiPointerGestureDetector();
+ }
+
+ /**
+ * Sets the listener.
+ * @param listener listener to set
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Resets the component to the initial state.
+ */
+ public void reset() {
+ mGestureInProgress = false;
+ mCount = 0;
+ for (int i = 0; i < MAX_POINTERS; i++) {
+ mId[i] = MotionEvent.INVALID_POINTER_ID;
+ }
+ }
+
+ /**
+ * This method can be overridden in order to perform threshold check or
something similar.
+ * @return whether or not to start a new gesture
+ */
+ protected boolean shouldStartGesture() {
+ return true;
+ }
+
+ private void startGesture() {
+ if (!mGestureInProgress) {
+ mGestureInProgress = true;
+ if (mListener != null) {
+ mListener.onGestureBegin(this);
+ }
+ }
+ }
+
+ private void stopGesture() {
+ if (mGestureInProgress) {
+ mGestureInProgress = false;
+ if (mListener != null) {
+ mListener.onGestureEnd(this);
+ }
+ }
+ }
+
+ /**
+ * Gets the index of the i-th pressed pointer.
+ * Normally, the index will be equal to i, except in the case when the
pointer is released.
+ * @return index of the specified pointer or -1 if not found (i.e. not
enough pointers are down)
+ */
+ private int getPressedPointerIndex(MotionEvent event, int i) {
+ final int count = event.getPointerCount();
+ final int action = event.getActionMasked();
+ final int index = event.getActionIndex();
+ if (action == MotionEvent.ACTION_UP || action ==
MotionEvent.ACTION_POINTER_UP) {
+ if (i >= index) {
+ i++;
+ }
+ }
+ return (i < count) ? i : -1;
+ }
+
+ /**
+ * Handles the given motion event.
+ * @param event event to handle
+ * @return whether or not the event was handled
+ */
+ public boolean onTouchEvent(final MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ // update pointers
+ for (int i = 0; i < MAX_POINTERS; i++) {
+ int index = event.findPointerIndex(mId[i]);
+ if (index != -1) {
+ mCurrentX[i] = event.getX(index);
+ mCurrentY[i] = event.getY(index);
+ }
+ }
+ // start a new gesture if not already started
+ if (!mGestureInProgress && shouldStartGesture()) {
+ startGesture();
+ }
+ // notify listener
+ if (mGestureInProgress && mListener != null) {
+ mListener.onGestureUpdate(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_UP:
+ // we'll restart the current gesture (if any) whenever the number of
pointers changes
+ // NOTE: we only restart existing gestures here, new gestures are
started in ACTION_MOVE
+ boolean wasGestureInProgress = mGestureInProgress;
+ stopGesture();
+ reset();
+ // update pointers
+ for (int i = 0; i < MAX_POINTERS; i++) {
+ int index = getPressedPointerIndex(event, i);
+ if (index == -1) {
+ break;
+ }
+ mId[i] = event.getPointerId(index);
+ mStartX[i] = event.getX(index);
+ mCurrentX[i] = mStartX[i];
+ mStartY[i] = event.getY(index);
+ mCurrentY[i] = mStartY[i];
+ mCount++;
+ }
+ // restart the gesture (if any) if there are still pointers left
+ if (wasGestureInProgress && mCount > 0) {
+ startGesture();
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ stopGesture();
+ reset();
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /** Restarts the current gesture */
+ public void restartGesture() {
+ if (!mGestureInProgress) {
+ return;
+ }
+ stopGesture();
+ for (int i = 0; i < MAX_POINTERS; i++) {
+ mStartX[i] = mCurrentX[i];
+ mStartY[i] = mCurrentY[i];
+ }
+ startGesture();
+ }
+
+ /** Gets whether gesture is in progress or not */
+ public boolean isGestureInProgress() {
+ return mGestureInProgress;
+ }
+
+ /** Gets the number of pointers in the current gesture */
+ public int getCount() {
+ return mCount;
+ }
+
+ /**
+ * Gets the start X coordinates for the all pointers
+ * Mutable array is exposed for performance reasons and is not to be
modified by the callers.
+ */
+ public float[] getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Gets the start Y coordinates for the all pointers
+ * Mutable array is exposed for performance reasons and is not to be
modified by the callers.
+ */
+ public float[] getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Gets the current X coordinates for the all pointers
+ * Mutable array is exposed for performance reasons and is not to be
modified by the callers.
+ */
+ public float[] getCurrentX() {
+ return mCurrentX;
+ }
+
+ /**
+ * Gets the current Y coordinates for the all pointers
+ * Mutable array is exposed for performance reasons and is not to be
modified by the callers.
+ */
+ public float[] getCurrentY() {
+ return mCurrentY;
+ }
+}
diff --git
a/app/src/main/java/com/facebook/samples/gestures/TransformGestureDetector.java
b/app/src/main/java/com/facebook/samples/gestures/TransformGestureDetector.java
new file mode 100644
index 0000000..d5baf79
--- /dev/null
+++
b/app/src/main/java/com/facebook/samples/gestures/TransformGestureDetector.java
@@ -0,0 +1,166 @@
+/*
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.facebook.samples.gestures;
+
+import android.view.MotionEvent;
+
+/**
+ * Component that detects translation, scale and rotation based on touch
events.
+ * <p>
+ * This class notifies its listeners whenever a gesture begins, updates or
ends.
+ * The instance of this detector is passed to the listeners, so it can be
queried
+ * for pivot, translation, scale or rotation.
+ */
+public class TransformGestureDetector implements
MultiPointerGestureDetector.Listener {
+
+ /** The listener for receiving notifications when gestures occur. */
+ public interface Listener {
+ /** Responds to the beginning of a gesture. */
+ void onGestureBegin(TransformGestureDetector detector);
+
+ /** Responds to the update of a gesture in progress. */
+ void onGestureUpdate(TransformGestureDetector detector);
+
+ /** Responds to the end of a gesture. */
+ void onGestureEnd(TransformGestureDetector detector);
+ }
+
+ private final MultiPointerGestureDetector mDetector;
+
+ private Listener mListener = null;
+
+ public TransformGestureDetector(MultiPointerGestureDetector
multiPointerGestureDetector) {
+ mDetector = multiPointerGestureDetector;
+ mDetector.setListener(this);
+ }
+
+ /** Factory method that creates a new instance of TransformGestureDetector */
+ public static TransformGestureDetector newInstance() {
+ return new
TransformGestureDetector(MultiPointerGestureDetector.newInstance());
+ }
+
+ /**
+ * Sets the listener.
+ * @param listener listener to set
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Resets the component to the initial state.
+ */
+ public void reset() {
+ mDetector.reset();
+ }
+
+ /**
+ * Handles the given motion event.
+ * @param event event to handle
+ * @return whether or not the event was handled
+ */
+ public boolean onTouchEvent(final MotionEvent event) {
+ return mDetector.onTouchEvent(event);
+ }
+
+ @Override
+ public void onGestureBegin(MultiPointerGestureDetector detector) {
+ if (mListener != null) {
+ mListener.onGestureBegin(this);
+ }
+ }
+
+ @Override
+ public void onGestureUpdate(MultiPointerGestureDetector detector) {
+ if (mListener != null) {
+ mListener.onGestureUpdate(this);
+ }
+ }
+
+ @Override
+ public void onGestureEnd(MultiPointerGestureDetector detector) {
+ if (mListener != null) {
+ mListener.onGestureEnd(this);
+ }
+ }
+
+ private float calcAverage(float[] arr, int len) {
+ float sum = 0;
+ for (int i = 0; i < len; i++) {
+ sum += arr[i];
+ }
+ return (len > 0) ? sum / len : 0;
+ }
+
+ /** Restarts the current gesture */
+ public void restartGesture() {
+ mDetector.restartGesture();
+ }
+
+ /** Gets whether gesture is in progress or not */
+ public boolean isGestureInProgress() {
+ return mDetector.isGestureInProgress();
+ }
+
+ /** Gets the X coordinate of the pivot point */
+ public float getPivotX() {
+ return calcAverage(mDetector.getStartX(), mDetector.getCount());
+ }
+
+ /** Gets the Y coordinate of the pivot point */
+ public float getPivotY() {
+ return calcAverage(mDetector.getStartY(), mDetector.getCount());
+ }
+
+ /** Gets the X component of the translation */
+ public float getTranslationX() {
+ return calcAverage(mDetector.getCurrentX(), mDetector.getCount())
+ - calcAverage(mDetector.getStartX(), mDetector.getCount());
+ }
+
+ /** Gets the Y component of the translation */
+ public float getTranslationY() {
+ return calcAverage(mDetector.getCurrentY(), mDetector.getCount())
+ - calcAverage(mDetector.getStartY(), mDetector.getCount());
+ }
+
+ /** Gets the scale */
+ public float getScale() {
+ if (mDetector.getCount() < 2) {
+ return 1;
+ } else {
+ float startDeltaX = mDetector.getStartX()[1] - mDetector.getStartX()[0];
+ float startDeltaY = mDetector.getStartY()[1] - mDetector.getStartY()[0];
+ float currentDeltaX = mDetector.getCurrentX()[1] -
mDetector.getCurrentX()[0];
+ float currentDeltaY = mDetector.getCurrentY()[1] -
mDetector.getCurrentY()[0];
+ float startDist = (float) Math.hypot(startDeltaX, startDeltaY);
+ float currentDist = (float) Math.hypot(currentDeltaX, currentDeltaY);
+ return currentDist / startDist;
+ }
+ }
+
+ /** Gets the rotation in radians */
+ public float getRotation() {
+ if (mDetector.getCount() < 2) {
+ return 0;
+ } else {
+ float startDeltaX = mDetector.getStartX()[1] - mDetector.getStartX()[0];
+ float startDeltaY = mDetector.getStartY()[1] - mDetector.getStartY()[0];
+ float currentDeltaX = mDetector.getCurrentX()[1] -
mDetector.getCurrentX()[0];
+ float currentDeltaY = mDetector.getCurrentY()[1] -
mDetector.getCurrentY()[0];
+ float startAngle = (float) Math.atan2(startDeltaY, startDeltaX);
+ float currentAngle = (float) Math.atan2(currentDeltaY, currentDeltaX);
+ return currentAngle - startAngle;
+ }
+ }
+}
diff --git
a/app/src/main/java/com/facebook/samples/zoomable/DefaultZoomableController.java
b/app/src/main/java/com/facebook/samples/zoomable/DefaultZoomableController.java
new file mode 100644
index 0000000..877e56a
--- /dev/null
+++
b/app/src/main/java/com/facebook/samples/zoomable/DefaultZoomableController.java
@@ -0,0 +1,487 @@
+/*
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.facebook.samples.zoomable;
+
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.support.annotation.Nullable;
+import android.view.MotionEvent;
+import android.view.animation.DecelerateInterpolator;
+
+import com.facebook.common.internal.Preconditions;
+import com.facebook.samples.gestures.TransformGestureDetector;
+
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.AnimatorListenerAdapter;
+import com.nineoldandroids.animation.ValueAnimator;
+
+/**
+ * Zoomable controller that calculates transformation based on touch events.
+ */
+public class DefaultZoomableController
+ implements ZoomableController, TransformGestureDetector.Listener {
+
+ private static final int MATRIX_SIZE = 9;
+ private static final RectF IDENTITY_RECT = new RectF(0, 0, 1, 1);
+
+ private TransformGestureDetector mGestureDetector;
+
+ private Listener mListener = null;
+
+ private boolean mIsEnabled = false;
+ private boolean mIsRotationEnabled = false;
+ private boolean mIsScaleEnabled = true;
+ private boolean mIsTranslationEnabled = true;
+
+ private float mMinScaleFactor = 1.0f;
+ private float mMaxScaleFactor = Float.POSITIVE_INFINITY;
+
+ private final RectF mViewBounds = new RectF();
+ private final RectF mImageBounds = new RectF();
+ private final RectF mTransformedImageBounds = new RectF();
+ private final Matrix mPreviousTransform = new Matrix();
+ private final Matrix mActiveTransform = new Matrix();
+ private final Matrix mActiveTransformInverse = new Matrix();
+ private final float[] mTempValues = new float[MATRIX_SIZE];
+ private final RectF mTempRect = new RectF();
+
+ private final ValueAnimator mValueAnimator;
+ private final float[] mAnimationStartValues = new float[MATRIX_SIZE];
+ private final float[] mAnimationDestValues = new float[MATRIX_SIZE];
+ private final float[] mAnimationCurrValues = new float[MATRIX_SIZE];
+ private final Matrix mNewTransform = new Matrix();
+
+ public DefaultZoomableController(TransformGestureDetector gestureDetector) {
+ mGestureDetector = gestureDetector;
+ mGestureDetector.setListener(this);
+ mValueAnimator = ValueAnimator.ofFloat(0, 1);
+ mValueAnimator.setInterpolator(new DecelerateInterpolator());
+ }
+
+ public static DefaultZoomableController newInstance() {
+ return new
DefaultZoomableController(TransformGestureDetector.newInstance());
+ }
+
+ @Override
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ /** Rests the controller. */
+ public void reset() {
+ mGestureDetector.reset();
+ mPreviousTransform.reset();
+ mActiveTransform.reset();
+ onTransformChanged();
+ }
+
+ /** Sets whether the controller is enabled or not. */
+ @Override
+ public void setEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ if (!enabled) {
+ reset();
+ }
+ }
+
+ /** Returns whether the controller is enabled or not. */
+ @Override
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /** Sets whether the rotation gesture is enabled or not. */
+ public void setRotationEnabled(boolean enabled) {
+ mIsRotationEnabled = enabled;
+ }
+
+ /** Gets whether the rotation gesture is enabled or not. */
+ public boolean isRotationEnabled() {
+ return mIsRotationEnabled;
+ }
+
+ /** Sets whether the scale gesture is enabled or not. */
+ public void setScaleEnabled(boolean enabled) {
+ mIsScaleEnabled = enabled;
+ }
+
+ /** Gets whether the scale gesture is enabled or not. */
+ public boolean isScaleEnabled() {
+ return mIsScaleEnabled;
+ }
+
+ /** Sets whether the translation gesture is enabled or not. */
+ public void setTranslationEnabled(boolean enabled) {
+ mIsTranslationEnabled = enabled;
+ }
+
+ /** Gets whether the translations gesture is enabled or not. */
+ public boolean isTranslationEnabled() {
+ return mIsTranslationEnabled;
+ }
+
+ /** Gets the image bounds before zoomable transformation is applied. */
+ public RectF getImageBounds() {
+ return mImageBounds;
+ }
+
+ protected RectF getTransformedImageBounds() {
+ return mTransformedImageBounds;
+ }
+
+ /** Sets the image bounds before zoomable transformation is applied. */
+ @Override
+ public void setImageBounds(RectF imageBounds) {
+ if (!imageBounds.equals(mImageBounds)) {
+ mImageBounds.set(imageBounds);
+ onTransformChanged();
+ }
+ }
+
+ /** Gets the view bounds. */
+ public RectF getViewBounds() {
+ return mViewBounds;
+ }
+
+ /** Sets the view bounds. */
+ @Override
+ public void setViewBounds(RectF viewBounds) {
+ mViewBounds.set(viewBounds);
+ }
+
+ /** Gets the minimum scale factor allowed. */
+ public float getMinScaleFactor() {
+ return mMinScaleFactor;
+ }
+
+ /**
+ * Sets the minimum scale factor allowed.
+ * <p>
+ * Note that the hierarchy performs scaling as well, which
+ * is not accounted here, so the actual scale factor may differ.
+ */
+ public void setMinScaleFactor(float minScaleFactor) {
+ mMinScaleFactor = minScaleFactor;
+ }
+
+ /** Gets the maximum scale factor allowed. */
+ public float getMaxScaleFactor() {
+ return mMaxScaleFactor;
+ }
+
+ /**
+ * Sets the maximum scale factor allowed.
+ * <p>
+ * Note that the hierarchy performs scaling as well, which
+ * is not accounted here, so the actual scale factor may differ.
+ */
+ public void setMaxScaleFactor(float maxScaleFactor) {
+ mMaxScaleFactor = maxScaleFactor;
+ }
+
+ /**
+ * Maps point from the view's to the image's relative coordinate system.
+ * This takes into account the zoomable transformation.
+ */
+ public PointF mapViewToImage(PointF viewPoint) {
+ float[] points = mTempValues;
+ points[0] = viewPoint.x;
+ points[1] = viewPoint.y;
+ mActiveTransform.invert(mActiveTransformInverse);
+ mActiveTransformInverse.mapPoints(points, 0, points, 0, 1);
+ mapAbsoluteToRelative(points, points, 1);
+ return new PointF(points[0], points[1]);
+ }
+
+ /**
+ * Maps point from the image's relative to the view's coordinate system.
+ * This takes into account the zoomable transformation.
+ */
+ public PointF mapImageToView(PointF imagePoint) {
+ float[] points = mTempValues;
+ points[0] = imagePoint.x;
+ points[1] = imagePoint.y;
+ mapRelativeToAbsolute(points, points, 1);
+ mActiveTransform.mapPoints(points, 0, points, 0, 1);
+ return new PointF(points[0], points[1]);
+ }
+
+ /**
+ * Maps array of 2D points from absolute to the image's relative coordinate
system,
+ * and writes the transformed points back into the array.
+ * Points are represented by float array of [x0, y0, x1, y1, ...].
+ *
+ * @param destPoints destination array (may be the same as source array)
+ * @param srcPoints source array
+ * @param numPoints number of points to map
+ */
+ private void mapAbsoluteToRelative(float[] destPoints, float[] srcPoints,
int numPoints) {
+ for (int i = 0; i < numPoints; i++) {
+ destPoints[i * 2 + 0] = (srcPoints[i * 2 + 0] - mImageBounds.left) /
mImageBounds.width();
+ destPoints[i * 2 + 1] = (srcPoints[i * 2 + 1] - mImageBounds.top) /
mImageBounds.height();
+ }
+ }
+
+ /**
+ * Maps array of 2D points from relative to the image's absolute coordinate
system,
+ * and writes the transformed points back into the array
+ * Points are represented by float array of [x0, y0, x1, y1, ...].
+ *
+ * @param destPoints destination array (may be the same as source array)
+ * @param srcPoints source array
+ * @param numPoints number of points to map
+ */
+ private void mapRelativeToAbsolute(float[] destPoints, float[] srcPoints,
int numPoints) {
+ for (int i = 0; i < numPoints; i++) {
+ destPoints[i * 2 + 0] = srcPoints[i * 2 + 0] * mImageBounds.width() +
mImageBounds.left;
+ destPoints[i * 2 + 1] = srcPoints[i * 2 + 1] * mImageBounds.height() +
mImageBounds.top;
+ }
+ }
+
+ /**
+ * Gets the zoomable transformation
+ * Internal matrix is exposed for performance reasons and is not to be
modified by the callers.
+ */
+ @Override
+ public Matrix getTransform() {
+ return mActiveTransform;
+ }
+
+ /**
+ * Returns the matrix that fully transforms the image from image-relative
coordinates
+ * to scaled view-absolute coordinates.
+ */
+ public void getImageRelativeToViewAbsoluteTransform(Matrix outMatrix) {
+ mActiveTransform.mapRect(mTempRect, mImageBounds);
+ outMatrix.setRectToRect(IDENTITY_RECT, mTempRect, Matrix.ScaleToFit.FILL);
+ }
+
+ // TODO(balazsbalazs) resolve issues with interrupting an existing
animation/gesture with
+ // a new animation or transform
+
+ /**
+ * Sets a new zoom transformation.
+ *
+ * <p>If this method is called while an animation or gesture is already in
progress,
+ * this will currently result in undefined behavior.
+ */
+ public void setTransform(Matrix newTransform) {
+ setTransform(newTransform, 0, null);
+ }
+
+ /**
+ * Sets a new zoomable transformation and animates to it if desired.
+ *
+ * <p>If this method is called while an animation or gesture is already in
progress,
+ * this will currently result in undefined behavior.
+ *
+ * @param newTransform new transform to make active
+ * @param durationMs duration of the animation, or 0 to not animate
+ * @param onAnimationComplete code to run when the animation completes.
Ignored if durationMs=0
+ */
+ public void setTransform(
+ Matrix newTransform,
+ long durationMs,
+ @Nullable Runnable onAnimationComplete) {
+ if (mGestureDetector.isGestureInProgress()) {
+ mGestureDetector.reset();
+ }
+ cancelAnimation();
+ if (durationMs <= 0) {
+ mActiveTransform.set(newTransform);
+ onTransformChanged();
+ } else {
+ setTransformAnimated(newTransform, durationMs, onAnimationComplete);
+ }
+ }
+
+ /** Do not call this method directly; call it only from setTransform. */
+ private void setTransformAnimated(
+ final Matrix newTransform,
+ long durationMs,
+ @Nullable final Runnable onAnimationComplete) {
+ Preconditions.checkArgument(durationMs > 0);
+ Preconditions.checkState(!mValueAnimator.isRunning());
+ mValueAnimator.setDuration(durationMs);
+ mActiveTransform.getValues(mAnimationStartValues);
+ newTransform.getValues(mAnimationDestValues);
+ mValueAnimator.addUpdateListener(new
ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float fraction = (float) valueAnimator.getAnimatedValue();
+ for (int i = 0; i < mAnimationCurrValues.length; i++) {
+ mAnimationCurrValues[i] = (1 - fraction) * mAnimationStartValues[i]
+ + fraction * mAnimationDestValues[i];
+ }
+ mActiveTransform.setValues(mAnimationCurrValues);
+ onTransformChanged();
+ }
+ });
+ if (onAnimationComplete != null) {
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationComplete.run();
+ }
+ });
+ }
+ mValueAnimator.start();
+ }
+
+ private void cancelAnimation() {
+ mValueAnimator.removeAllUpdateListeners();
+ mValueAnimator.removeAllListeners();
+ if (mValueAnimator.isRunning()) {
+ mValueAnimator.cancel();
+ }
+ }
+
+ /** Notifies controller of the received touch event. */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mIsEnabled) {
+ return mGestureDetector.onTouchEvent(event);
+ }
+ return false;
+ }
+
+ protected void onTransformChanged() {
+ mActiveTransform.mapRect(mTransformedImageBounds, mImageBounds);
+ if (mListener != null && isEnabled()) {
+ mListener.onTransformChanged(mActiveTransform);
+ }
+ }
+
+ /**
+ * Zooms to the desired scale and positions the view so that imagePoint is
in the center.
+ *
+ * <p>If this method is called while an animation or gesture is already in
progress,
+ * this will currently result in undefined behavior.
+ *
+ * @param scale desired scale, will be limited to {min, max} scale factor
+ * @param imagePoint 2D point in image's relative coordinate system (i.e. 0
<= x, y <= 1)
+ * @param viewPoint 2D point in view's absolute coordinate system
+ * @param limitTransX Whether to adjust the transform to prevent black bars
from appearing on
+ * the left or right.
+ * @param limitTransY Whether to adjust the transform to prevent black bars
from appearing on
+ * the top or bottom.
+ * @param durationMs length of animation of the zoom, or 0 if no animation
desired
+ * @param onAnimationComplete code to execute when the animation is complete.
+ * Ignored if durationMs=0
+ */
+ public void zoomToImagePoint(
+ float scale,
+ PointF imagePoint,
+ PointF viewPoint,
+ boolean limitTransX,
+ boolean limitTransY,
+ long durationMs,
+ @Nullable Runnable onAnimationComplete) {
+ scale = limit(scale, mMinScaleFactor, mMaxScaleFactor);
+ float[] viewAbsolute = mTempValues;
+ viewAbsolute[0] = imagePoint.x;
+ viewAbsolute[1] = imagePoint.y;
+ mapRelativeToAbsolute(viewAbsolute, viewAbsolute, 1);
+ float distanceX = viewPoint.x - viewAbsolute[0];
+ float distanceY = viewPoint.y - viewAbsolute[1];
+ mNewTransform.setScale(scale, scale, viewAbsolute[0], viewAbsolute[1]);
+ mNewTransform.postTranslate(distanceX, distanceY);
+ limitTranslation(mNewTransform, limitTransX, limitTransY);
+
+ setTransform(mNewTransform, durationMs, onAnimationComplete);
+ }
+
+ /* TransformGestureDetector.Listener methods */
+
+ @Override
+ public void onGestureBegin(TransformGestureDetector detector) {
+ mPreviousTransform.set(mActiveTransform);
+ // TODO(balazsbalazs): animation should be cancelled here
+ }
+
+ @Override
+ public void onGestureUpdate(TransformGestureDetector detector) {
+ mActiveTransform.set(mPreviousTransform);
+ if (mIsRotationEnabled) {
+ final int oneEighty = 180;
+ float angle = detector.getRotation() * (float) (oneEighty / Math.PI);
+ mActiveTransform.postRotate(angle, detector.getPivotX(),
detector.getPivotY());
+ }
+ if (mIsScaleEnabled) {
+ float scale = detector.getScale();
+ mActiveTransform.postScale(scale, scale, detector.getPivotX(),
detector.getPivotY());
+ }
+ limitScale(detector.getPivotX(), detector.getPivotY());
+ if (mIsTranslationEnabled) {
+ mActiveTransform.postTranslate(detector.getTranslationX(),
detector.getTranslationY());
+ }
+ if (limitTranslation(mActiveTransform, true, true)) {
+ mGestureDetector.restartGesture();
+ }
+ onTransformChanged();
+ }
+
+ @Override
+ public void onGestureEnd(TransformGestureDetector detector) {
+ mPreviousTransform.set(mActiveTransform);
+ }
+
+ /** Gets the current scale factor. */
+ @Override
+ public float getScaleFactor() {
+ mActiveTransform.getValues(mTempValues);
+ return mTempValues[Matrix.MSCALE_X];
+ }
+
+ private void limitScale(float pivotX, float pivotY) {
+ float currentScale = getScaleFactor();
+ float targetScale = limit(currentScale, mMinScaleFactor, mMaxScaleFactor);
+ if (targetScale != currentScale) {
+ float scale = targetScale / currentScale;
+ mActiveTransform.postScale(scale, scale, pivotX, pivotY);
+ }
+ }
+
+ /**
+ * Keeps the view inside the image if possible, if not (i.e. image is
smaller than view)
+ * centers the image.
+ * @param limitX whether to apply the limit on the x-axis
+ * @param limitY whether to apply the limit on the y-axis
+ * @return whether adjustments were needed or not
+ */
+ private boolean limitTranslation(Matrix newTransform, boolean limitX,
boolean limitY) {
+ RectF bounds = mTransformedImageBounds;
+ bounds.set(mImageBounds);
+ newTransform.mapRect(bounds);
+ float offsetLeft = limitX
+ ? getOffset(bounds.left, bounds.width(), mViewBounds.width()) :
bounds.left;
+ float offsetTop = limitY
+ ? getOffset(bounds.top, bounds.height(), mViewBounds.height()) :
bounds.top;
+ if (offsetLeft != bounds.left || offsetTop != bounds.top) {
+ newTransform.postTranslate(offsetLeft - bounds.left, offsetTop -
bounds.top);
+ return true;
+ }
+ return false;
+ }
+
+ private float getOffset(float offset, float imageDimension, float
viewDimension) {
+ float diff = viewDimension - imageDimension;
+ return (diff > 0) ? diff / 2 : limit(offset, diff, 0);
+ }
+
+ private float limit(float value, float min, float max) {
+ return Math.min(Math.max(min, value), max);
+ }
+
+}
diff --git
a/app/src/main/java/com/facebook/samples/zoomable/ZoomableController.java
b/app/src/main/java/com/facebook/samples/zoomable/ZoomableController.java
new file mode 100644
index 0000000..23d6cd5
--- /dev/null
+++ b/app/src/main/java/com/facebook/samples/zoomable/ZoomableController.java
@@ -0,0 +1,97 @@
+/*
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.facebook.samples.zoomable;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+
+/**
+ * Interface for implementing a controller that works with {@link
ZoomableDraweeView}
+ * to control the zoom.
+ */
+public interface ZoomableController {
+
+ /**
+ * Listener interface.
+ */
+ public interface Listener {
+
+ /**
+ * Notifies the view that the transform changed.
+ *
+ * @param transform the new matrix
+ */
+ void onTransformChanged(Matrix transform);
+ }
+
+ /**
+ * Enables the controller. The controller is enabled when the image has been
loaded.
+ *
+ * @param enabled whether to enable the controller
+ */
+ void setEnabled(boolean enabled);
+
+ /**
+ * Gets whether the controller is enabled. This should return the last value
passed to
+ * {@link #setEnabled}.
+ *
+ * @return whether the controller is enabled.
+ */
+ boolean isEnabled();
+
+ /**
+ * Sets the listener for the controller to call back when the matrix changes.
+ *
+ * @param listener the listener
+ */
+ void setListener(Listener listener);
+
+ /**
+ * Gets the current scale factor. A convenience method for calculating the
scale from the
+ * transform.
+ *
+ * @return the current scale factor
+ */
+ float getScaleFactor();
+
+ /**
+ * Gets the current transform.
+ *
+ * @return the transform
+ */
+ Matrix getTransform();
+
+ /**
+ * Sets the bounds of the image post transform prior to application of the
zoomable
+ * transformation.
+ *
+ * @param imageBounds the bounds of the image
+ */
+ void setImageBounds(RectF imageBounds);
+
+ /**
+ * Sets the bounds of the view.
+ *
+ * @param viewBounds the bounds of the view
+ */
+ void setViewBounds(RectF viewBounds);
+
+ /**
+ * Allows the controller to handle a touch event.
+ *
+ * @param event the touch event
+ * @return whether the controller handled the event
+ */
+ boolean onTouchEvent(MotionEvent event);
+}
diff --git
a/app/src/main/java/com/facebook/samples/zoomable/ZoomableDraweeView.java
b/app/src/main/java/com/facebook/samples/zoomable/ZoomableDraweeView.java
new file mode 100644
index 0000000..ef48379
--- /dev/null
+++ b/app/src/main/java/com/facebook/samples/zoomable/ZoomableDraweeView.java
@@ -0,0 +1,243 @@
+/*
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.facebook.samples.zoomable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Animatable;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.facebook.common.internal.Preconditions;
+import com.facebook.common.logging.FLog;
+import com.facebook.drawee.controller.AbstractDraweeController;
+import com.facebook.drawee.controller.BaseControllerListener;
+import com.facebook.drawee.controller.ControllerListener;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.interfaces.DraweeController;
+import com.facebook.drawee.view.DraweeView;
+
+/**
+ * DraweeView that has zoomable capabilities.
+ * <p>
+ * Once the image loads, pinch-to-zoom and translation gestures are enabled.
+ */
+public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
+ implements ZoomableController.Listener {
+
+ private static final Class<?> TAG = ZoomableDraweeView.class;
+
+ private static final float HUGE_IMAGE_SCALE_FACTOR_THRESHOLD = 1.1f;
+
+ private final RectF mImageBounds = new RectF();
+ private final RectF mViewBounds = new RectF();
+ private int touchSlop =
ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ private int startX;
+ private int startY;
+
+ private final ControllerListener mControllerListener = new
BaseControllerListener<Object>() {
+ @Override
+ public void onFinalImageSet(
+ String id,
+ @Nullable Object imageInfo,
+ @Nullable Animatable animatable) {
+ ZoomableDraweeView.this.onFinalImageSet();
+ }
+
+ @Override
+ public void onRelease(String id) {
+ ZoomableDraweeView.this.onRelease();
+ }
+ };
+
+ private DraweeController mHugeImageController;
+ private ZoomableController mZoomableController =
DefaultZoomableController.newInstance();
+
+ public ZoomableDraweeView(Context context) {
+ super(context);
+ init();
+ }
+
+ public ZoomableDraweeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ZoomableDraweeView(Context context, AttributeSet attrs, int defStyle)
{
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mZoomableController.setListener(this);
+ }
+
+ /**
+ * Returns the matrix that matches the zoom selected by user gestures,
+ * but does not include the base scaling of the image itself. Transforms
+ * from view-absolute to view-absolute coordinates.
+ */
+ public void getTransformMatrix(Matrix outMatrix) {
+ outMatrix.set(mZoomableController.getTransform());
+ }
+
+ /**
+ * Gets the bounds of the image, in view-absolute coordinates,
+ * including the effects of user gestures.
+ */
+ public void getTransformedBounds(RectF outBounds) {
+ getPlainBounds(outBounds);
+ Matrix matrix = mZoomableController.getTransform();
+ matrix.mapRect(outBounds);
+ }
+
+ /**
+ * Gets the bounds of the image, in view-absolute coordinates,
+ * but not including the effets of user gestures.
+ */
+ public void getPlainBounds(RectF outBounds) {
+ getHierarchy().getActualImageBounds(outBounds);
+ }
+
+ public void setZoomableController(ZoomableController zoomableController) {
+ Preconditions.checkNotNull(zoomableController);
+ mZoomableController.setListener(null);
+ mZoomableController = zoomableController;
+ mZoomableController.setListener(this);
+ }
+
+ @Override
+ public void setController(@Nullable DraweeController controller) {
+ setControllers(controller, null);
+ }
+
+ private void setControllersInternal(
+ @Nullable DraweeController controller,
+ @Nullable DraweeController hugeImageController) {
+ removeControllerListener(getController());
+ addControllerListener(controller);
+ mHugeImageController = hugeImageController;
+ super.setController(controller);
+ }
+
+ /**
+ * Sets the controllers for the normal and huge image.
+ *
+ * <p> IMPORTANT: in order to avoid a flicker when switching to the huge
image, the huge image
+ * controller should have the normal-image-uri set as its low-res-uri.
+ *
+ * @param controller controller to be initially used
+ * @param hugeImageController controller to be used after the client
starts zooming-in
+ */
+ public void setControllers(
+ @Nullable DraweeController controller,
+ @Nullable DraweeController hugeImageController) {
+ setControllersInternal(null, null);
+ mZoomableController.setEnabled(false);
+ setControllersInternal(controller, hugeImageController);
+ }
+
+ private void maybeSetHugeImageController() {
+ if (mHugeImageController != null
+ && mZoomableController.getScaleFactor() >
HUGE_IMAGE_SCALE_FACTOR_THRESHOLD) {
+ setControllersInternal(mHugeImageController, null);
+ }
+ }
+
+ private void removeControllerListener(DraweeController controller) {
+ if (controller instanceof AbstractDraweeController) {
+ ((AbstractDraweeController) controller)
+ .removeControllerListener(mControllerListener);
+ }
+ }
+
+ private void addControllerListener(DraweeController controller) {
+ if (controller instanceof AbstractDraweeController) {
+ ((AbstractDraweeController) controller)
+ .addControllerListener(mControllerListener);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int saveCount = canvas.save();
+ canvas.concat(mZoomableController.getTransform());
+ super.onDraw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mZoomableController.onTouchEvent(event)) {
+ if (mZoomableController.getScaleFactor() > 1.0f) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ FLog.v(TAG, "onTouchEvent: view %x, handled by zoomable controller",
this.hashCode());
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ startX = (int) event.getX();
+ startY = (int) event.getY();
+ } else if (event.getAction() == MotionEvent.ACTION_UP
+ && Math.abs((int) event.getX() - startX) < touchSlop
+ && Math.abs((int) event.getY() - startY) < touchSlop) {
+ this.performClick();
+ }
+ return true;
+ }
+ FLog.v(TAG, "onTouchEvent: view %x, handled by the super",
this.hashCode());
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int
bottom) {
+ FLog.v(TAG, "onLayout: view %x", this.hashCode());
+ super.onLayout(changed, left, top, right, bottom);
+ updateZoomableControllerBounds();
+ }
+
+ private void onFinalImageSet() {
+ FLog.v(TAG, "onFinalImageSet: view %x", this.hashCode());
+ if (!mZoomableController.isEnabled()) {
+ updateZoomableControllerBounds();
+ mZoomableController.setEnabled(true);
+ }
+ }
+
+ private void onRelease() {
+ FLog.v(TAG, "onRelease: view %x", this.hashCode());
+ mZoomableController.setEnabled(false);
+ }
+
+ @Override
+ public void onTransformChanged(Matrix transform) {
+ FLog.v(TAG, "onTransformChanged: view %x", this.hashCode());
+ maybeSetHugeImageController();
+ invalidate();
+ }
+
+ private void updateZoomableControllerBounds() {
+ getPlainBounds(mImageBounds);
+ mViewBounds.set(0, 0, getWidth(), getHeight());
+ mZoomableController.setImageBounds(mImageBounds);
+ mZoomableController.setViewBounds(mViewBounds);
+ FLog.v(
+ TAG,
+ "updateZoomableControllerBounds: view %x, view bounds: %s, image
bounds: %s",
+ this.hashCode(),
+ mViewBounds,
+ mImageBounds);
+ }
+}
diff --git
a/app/src/main/java/org/wikipedia/page/gallery/GalleryItemFragment.java
b/app/src/main/java/org/wikipedia/page/gallery/GalleryItemFragment.java
index 852eac0..f6700d9 100644
--- a/app/src/main/java/org/wikipedia/page/gallery/GalleryItemFragment.java
+++ b/app/src/main/java/org/wikipedia/page/gallery/GalleryItemFragment.java
@@ -33,8 +33,12 @@
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
+import com.facebook.samples.zoomable.ZoomableDraweeView;
import java.util.Map;
@@ -53,7 +57,7 @@
private String mimeType;
private ProgressBar progressBar;
- private SimpleDraweeView imageView;
+ private ZoomableDraweeView imageView;
private View videoContainer;
private VideoView videoView;
@@ -92,19 +96,22 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gallery_item,
container, false);
- View containerView =
rootView.findViewById(R.id.gallery_item_container);
- containerView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- parentActivity.toggleControls();
- }
- });
progressBar = (ProgressBar)
rootView.findViewById(R.id.gallery_item_progress_bar);
videoContainer = rootView.findViewById(R.id.gallery_video_container);
videoView = (VideoView) rootView.findViewById(R.id.gallery_video);
videoThumbnail = (SimpleDraweeView)
rootView.findViewById(R.id.gallery_video_thumbnail);
videoPlayButton =
rootView.findViewById(R.id.gallery_video_play_button);
- imageView = (SimpleDraweeView)
rootView.findViewById(R.id.gallery_image);
+ imageView = (ZoomableDraweeView)
rootView.findViewById(R.id.gallery_image);
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ parentActivity.toggleControls();
+ }
+ });
+ GenericDraweeHierarchy hierarchy = new
GenericDraweeHierarchyBuilder(getResources())
+ .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
+ .build();
+ imageView.setHierarchy(hierarchy);
return rootView;
}
diff --git a/app/src/main/res/layout/fragment_gallery_item.xml
b/app/src/main/res/layout/fragment_gallery_item.xml
index 9969208..023e330 100644
--- a/app/src/main/res/layout/fragment_gallery_item.xml
+++ b/app/src/main/res/layout/fragment_gallery_item.xml
@@ -35,7 +35,7 @@
android:contentDescription="@null"
/>
</FrameLayout>
- <com.facebook.drawee.view.SimpleDraweeView
+ <com.facebook.samples.zoomable.ZoomableDraweeView
android:id="@+id/gallery_image"
android:visibility="gone"
android:layout_width="match_parent"
--
To view, visit https://gerrit.wikimedia.org/r/269891
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I764a81e904f889318744a985fa197d4c1373c971
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits