Fresco: Pinch to Zoom and ScaleType.MATRIX support

Created on 3 Apr 2015  路  20Comments  路  Source: facebook/fresco

Hi,
I'm trying to have Pinch to Zoom working with Fresco, but pinch to zoom implementations usually rely on the MATRIX scale type, which is not supported by Fresco

See a manual implementation here:
http://stackoverflow.com/a/10632396/488054
Or a library that does something similar:
https://github.com/chrisbanes/PhotoView/blob/master/library/src/main/java/uk/co/senab/photoview/PhotoView.java

Is there a way to make this work with Fresco?
Thanks!

Most helpful comment

Here is the code for smooth double tap zoom and pinch to zoom
https://github.com/arlindiDev/FrescoDoubleTapZoom

All 20 comments

You can implement your own zoomable view by extending SimpleDraweeView and overriding its onDraw and onTouchEvent methods:

@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) {
  return mZoomableController.onTouchEvent(event) || super.onTouchEvent(event);
}

All you need to do now is to implement your zoomable controller that, based on touch events, image bounds and view bounds, computes the transformation matrix. Unfortunately, I can't provide more information on how to do that, but it seems that the link you provided does something similar...

Getting the view bounds is pretty straightforward: (0, 0, getWidth(), getHeight()). To get the image bounds, Drawee provides mSimpleDraweeView.getHierarchy().getActualImageBounds(RectF outBounds) which will populate the given rect with the image bounds. You should do that only after the image is set, of course, and you can use controller listener to detect when this happens.

Shame this was closed. Wouldn't it benefit everyone to have this as part of the library so we don't all have to implement this?

@laurencedawson
Here is my implementation of it in case that helps, feel free to use it:
https://gist.github.com/nbarraille/eb5d0da20bc813969b08

Then how does Facebook do pinch to zoom and touch to zoom if they use Fresco? There's obviously something different, and I don't see why can't you share Facebook implementation as open source.

Just to share my final solution with everybody, as I didn't have time to work on custom multi-touch to zoom implementation on SimpleDraweeView, I decided to directly call Frescos Image Pipeline to get a raw Bitmap, as it is possible, and set it on a normal ImageView. If you do this, make sure you manually destroy your view and free Bitmap memory to prevent memory leaks..

On to the point, if anybody for any reason wants to use Fresco to load image into a normal ImageView, here is the full code to do it:

        DataSource <CloseableReference<CloseableImage>> dataSource = Fresco.getImagePipeline().fetchDecodedImage(ImageRequest.fromUri(Uri.parse(IMAGE_URL)),getApplicationContext());

        dataSource.subscribe(new BaseBitmapDataSubscriber() {
                                 @Override
                                 public void onNewResultImpl(@Nullable final Bitmap bitmap) {
                                     IMAGEVIEW.post(new Runnable() {
                                         public void run() {
                                             IMAGEVIEW.setImageBitmap(bitmap);
                                         }
                                     });
                                 }

                                 @Override
                                 public void onFailureImpl(DataSource dataSource) {
                                     // No cleanup required here.
                                 }
                             },
                CallerThreadExecutor.getInstance());

I had to use the post Runnable thread, as it seems the "onNewResultImpl" method is not called from the UI thread, so it wouldn't work otherwise.

Facebook now open-sourced component that supports pinch-to-zoom.
See commit 4e516e7 for the details, and those who are interested in its usage, see ZoomableDraweeView-sample on GitHub.

Excellent, thanks for the update.

@kunny Any pointers on how to write double-tap-to-zoom functionality in provided sample? If someone has done it already, share the code please. Thanks

To make the kunny's zoomable sample enable doubleTap,

(1) on /com.facebook.samples.zoomable/DefaultZoomableController.java,

  public void zoomToImagePoint(float scale, PointF imagePoint) {
    .....
    limitTranslation();

    // add below code...
    if (mListener != null) {
      mListener.onTransformChanged(mActiveTransform);
    }
  }

(2) on /com.facebook.samples.zoomable/ZoomableDraweeView.java,

  // change DefaultZoomableController to DoubleTapZoomableController...
  //  private ZoomableController mZoomableController = DefaultZoomableController.newInstance();
  private ZoomableController mZoomableController 
                = DoubleTapZoomableController.newInstance(getContext());

(3) create /com.facebook.samples.zoomable/DoubleTapZoomableController.java file.

 public class DoubleTapZoomableController extends DefaultZoomableController 
                implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    private GestureDetector mGestureDetector;

    public DoubleTapZoomableController(TransformGestureDetector gestureDetector, Context context) {
        super(gestureDetector);
        mGestureDetector = new GestureDetector(context, this);
        mGestureDetector.setOnDoubleTapListener(this);
    }

    public static DoubleTapZoomableController newInstance(Context context) {
        return new DoubleTapZoomableController(TransformGestureDetector.newInstance(), context);
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        PointF point = mapViewToImage(new PointF(event.getX(), event.getY()));
        if (getScaleFactor() > 1.0f) {
            zoomToImagePoint(1.0f, point);
        } else {
            zoomToImagePoint(2.0f, point);
        }
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        return false;
    }

    @Override
    public boolean onDown(MotionEvent event) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent event) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent event, MotionEvent event1, float v, float v1) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent event) {
    }

    @Override
    public boolean onFling(MotionEvent event, MotionEvent event1, float v, float v1) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isEnabled()) {
                mGestureDetector.onTouchEvent(event);
                return super.onTouchEvent(event);
        }
        return false;
    }

 }

now you can even enable doubleTap gesture for zooming.

@kyungin-park :trophy: nice! now we just need to add animations, double tap scroll zoom, and fling support. If I end up having time for that I'll post back here.

Is anyone able to use ZoomableDraweeView?
It crashes

java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
        at java.util.ArrayList.get(ArrayList.java:308)
        at com.facebook.drawee.controller.ForwardingControllerListener.onFinalImageSet(ForwardingControllerListener.java:91)
        at com.facebook.drawee.controller.AbstractDraweeController.onNewResultInternal(AbstractDraweeController.java:468)
        at com.facebook.drawee.controller.AbstractDraweeController.access$000(AbstractDraweeController.java:47)
        at com.facebook.drawee.controller.AbstractDraweeController$1.onNewResultImpl(AbstractDraweeController.java:413)
        at com.facebook.datasource.BaseDataSubscriber.onNewResult(BaseDataSubscriber.java:43)
        at com.facebook.datasource.AbstractDataSource$1.run(AbstractDataSource.java:181)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:5832)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

I'm displaying it in a fragment which is a page in ViewPager using basic controller setup.

if I'm seting the hierarchy after calling "setController" i'm not getting any crashes, but image is not set and "onFinalImageSet" is not called, if there is something wrong "onFailure" could be called, but it doesn' t.

protected GenericDraweeHierarchy getHierarchy() {
    return new GenericDraweeHierarchyBuilder(context.getResources())
            .setFadeDuration(400)
            .build();
}

If i'm calling "setHierarchy" before "setController" I'm getting another crash.

Process: com.namshi.android.debug, PID: 22148
java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
        at java.util.ArrayList.get(ArrayList.java:308)
        at com.facebook.drawee.controller.ForwardingControllerListener.onFinalImageSet(ForwardingControllerListener.java:91)
        at com.facebook.drawee.controller.AbstractDraweeController.onNewResultInternal(AbstractDraweeController.java:468)
        at com.facebook.drawee.controller.AbstractDraweeController.access$000(AbstractDraweeController.java:47)
        at com.facebook.drawee.controller.AbstractDraweeController$1.onNewResultImpl(AbstractDraweeController.java:413)
        at com.facebook.datasource.BaseDataSubscriber.onNewResult(BaseDataSubscriber.java:43)
        at com.facebook.datasource.AbstractDataSource$1.run(AbstractDataSource.java:181)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:5832)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

Can anyone provide an example of how to use the ZoomableDraweeView properly?

Is there chance that license of ZoomableDraweeView changes to commercial usage too?

No, this is not expected to change.

Hi, i am trying tow implements ZoomableDraweeView within my app images gallery. So basically it is a view pager with a set of fragment to display the images. The problem i am facing is that the images does not show and the controller listener does not even get notified !! No way to debug.

Here is a piece of code responsible of displaying the image within a fragment:

    ControllerListener listener=new BaseControllerListener<ImageInfo>(){

        @Override
        public void onFinalImageSet(
                String id,
                @Nullable ImageInfo imageInfo,
                @Nullable Animatable anim) {
            if (imageInfo == null) {
                return;
            }
            QualityInfo qualityInfo = imageInfo.getQualityInfo();
            Log.d("Galleryfragment", String.format("Final image received! " +
                            "Size %d x %d",
                    "Quality level %d, good enough: %s, full quality: %s",
                    imageInfo.getWidth(),
                    imageInfo.getHeight(),
                    qualityInfo.getQuality(),
                    qualityInfo.isOfGoodEnoughQuality(),
                    qualityInfo.isOfFullQuality()));
        }

        @Override
        public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
        }

        @Override
        public void onFailure(String id, Throwable throwable) {
            Toast.makeText(mContext,
                    R.string.photo_loading_error,
                    Toast.LENGTH_SHORT )
                    .show();
        }

    };
    adPictureView.setController(
            Fresco.newDraweeControllerBuilder()
                    .setUri(Uri.parse(adPicture.getFullImage()))
                    .setControllerListener(listener)
                    .build());

    GenericDraweeHierarchy hierarchy =
            new GenericDraweeHierarchyBuilder(getActivity().getResources())
                    .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
                    .setPlaceholderImage(mContext.getResources().getDrawable(R.drawable.ic_img_loading), ScalingUtils.ScaleType.CENTER_INSIDE)
                    .build();

    adPictureView.setHierarchy(hierarchy);

and below the fragment layout:

xmlns:gallery="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">

<fresco.zoomable.ZoomableDraweeView
    android:id="@+id/pictureView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

if anyone can help me please , it has been 2 days since i am stuck in this.

@derekcsm @kyungin-park how make zoom animation in zoomable sample?

@kyungin-park I added your double tap code but the zoomToImagePoint function seems to be gone in the latest DefaultZoomableController. Should I just set the scale to the scale being passed in?

@kyungin-park I add your double tap code it seems some problem when I tap it the image zoom not smooth ,it zoom with jitter,so I think it need handle touchEvent to eliminate jitter

I also tried to use ZoomableDraweeView in Fragment, but getting NPE error:

java.lang.NullPointerException
at com.facebook.common.internal.Preconditions.checkNotNull(Preconditions.java:210)
at com.facebook.drawee.view.DraweeHolder.getHierarchy(DraweeHolder.java:251)
at com.facebook.drawee.view.DraweeView.getHierarchy(DraweeView.java:96)
at com.some.app.ui.extensions.FrescoZoomableDraweeView.getPlainBounds(ZoomableDraweeView.java:110)
at com.some.app.ui.extensions.FrescoZoomableDraweeView.updateZoomableControllerBounds(ZoomableDraweeView.java:221)
at com.some.app.ui.extensions.FrescoZoomableDraweeView.onLayout(ZoomableDraweeView.java:197)

Here is my code

<com.some.app.ui.extensions.ZoomableDraweeView
        android:id="@+id/drawee_view"
        android:layout_width="500dp"
        android:layout_height="500dp"
        fresco:actualImageScaleType="centerCrop"
        />

ZoomableDraweeView imageView = (ZoomableDraweeView) rootView.findViewById(R.id.drawee_view);
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setUri(Uri.parse(getMediaPath()))
                .build();
        imageView.setController(controller);

Can somebody provide sample code how should we use ZoomableDraweeView.

EDIT

Sorry, found sample above in comments https://github.com/kunny/ZoomableDraweeView-sample

I implemented successfully ZoomableDraweeView. But have problem when doing zoom, and the rotating device.
I disabled recreate Activity in Manifest file.

android:configChanges="screenSize|orientation|keyboardHidden"

To reproduce issue: Rotate device to landscape, zoom a bit, then rotate it to portait and you should see image moved to bottom(like in image below). If you touch image it will jump to correct position(will center image position).

screenshot_20160225-144807

Here is the code for smooth double tap zoom and pinch to zoom
https://github.com/arlindiDev/FrescoDoubleTapZoom

Was this page helpful?
0 / 5 - 0 ratings