/* * 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 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; 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; /** * 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 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); } 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()); 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() { getHierarchy().getActualImageBounds(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); } }