21

I want to implement Pinch Zoom on Imageview, with in View Pager similar to Default Android Gallery. I have found multiple source over GitHub, But the zoom and sliding just work for only first image.

What I have tried:

1.) TouchImageView

2.) PhotoView

3.) Android Touch Gallery

All the above links works fine for single image view. But when it comes to Images in View pager, They have some glitches and only works fine for first image in the View Pager. When we scroll over to 3rd 4th image in view pager, Dragging functionality not working as expected if the image is zoomed.

Please if any one knows any good library for doing this, then provide me the link for them.

Avtar Guleria
  • 2,016
  • 3
  • 19
  • 31

6 Answers6

43

EDIT 2: Example code has been pushed to the master branch of TouchImageView. Here is a link to the example activity and a link to the ExtendedViewPager.


EDIT: added code adapting the example link to TouchImageView. Note: you will need the latest code, which is currently in the dev branch. In the future, this will be included in v1.2.0. You know you have the latest code if TouchImageView overrides canScrollHorizontally.

Step 1: Extend ViewPager and override canScroll to call canScrollHorizontallyFroyo.

public class ExtendedViewPager extends ViewPager {

public ExtendedViewPager(Context context) {
    super(context);
}

public ExtendedViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if (v instanceof TouchImageView) {
        return ((TouchImageView) v).canScrollHorizontallyFroyo(-dx);
    } else {
        return super.canScroll(v, checkV, dx, x, y);
    }
}

}

Step 2: Modify TouchImageView by adding canScrollHorizontallyFroyo:

public boolean canScrollHorizontallyFroyo(int direction) {
    return canScrollHorizontally(direction);
}

Step 3: Your activity

public class TouchImageViewActivity extends Activity {

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ExtendedViewPager mViewPager = (ExtendedViewPager) findViewById(R.id.view_pager);
        setContentView(mViewPager);
        mViewPager.setAdapter(new TouchImageAdapter());
    }

    static class TouchImageAdapter extends PagerAdapter {

            private static int[] images = { R.drawable.img1, R.drawable.img2, R.drawable.img3 };

            @Override
            public int getCount() {
                    return images.length;
            }

            @Override
            public View instantiateItem(ViewGroup container, int position) {
                    TouchImageView img = new TouchImageView(container.getContext());
                    img.setImageResource(images[position]);
                    container.addView(img, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                    return img;
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                    container.removeView((View) object);
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                    return view == object;
            }

    }
}

Step 4: main.xml

<com.example.touch.ExtendedViewPager 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

TouchImageView is actually my project. I currently have a fix in the dev branch for integration with ViewPagers, which will be pushed to master in an upcoming release. Unfortunately, this fix is only applicable for API 14 and greater since honeycomb and earlier do not call canScrollHorizontally. If you need to support older APIs, then you will need to implement a workaround in your ViewPager. Here is an example.

Community
  • 1
  • 1
Mike Ortiz
  • 3,991
  • 4
  • 24
  • 52
  • 1
    That workaround didn't work for me..:(. If I scroll with higher velocity it moves me to the next slide. – Avtar Guleria Jan 06 '14 at 06:37
  • @AvtarGuleria Did the other workaround included in my edit help? I know I've successfully employed it in the past. – Mike Ortiz Jan 07 '14 at 09:22
  • 1
    No Second workaround too not worked for me. Whenever i scroll horizontally, It just moves me to the second slide. But if I scroll vertically or at some other angle other than horizontal it works..so, Again Horizontally it doesn't work... – Avtar Guleria Jan 07 '14 at 10:24
  • Ok, I will take a look at it again and provide more thorough example code. – Mike Ortiz Jan 07 '14 at 10:27
  • What is the API you are testing on? I know this works with API > 14, though I am having some issues with a gingerbread device. – Mike Ortiz Jan 07 '14 at 11:23
  • @AvtarGuleria The first workaround I linked was correct, it just needed to be adapted to work with TouchImageView. I've included my complete working code in the answer above. Tested on a gingerbread and ICS devices. Let me know if you still have troubles with it. – Mike Ortiz Jan 07 '14 at 11:55
  • Thanks for the updated code, I will try it and let you know. I am providing support for API >= 9. And For your first workaround I have also used with your Github Touch Image view project, But that didn't work for me. But as mentioned in my previous comment your second workaround seems worked but not 100% as explained in my previous comment. – Avtar Guleria Jan 07 '14 at 13:31
  • 1
    Im trying to implement your solution, with canScrollHorizontally() or with the solution in the last link on your answer, but it doesnt work. The ViewPager always moves to the next page when horizontally scrolled. – AsafK Jan 28 '14 at 23:31
  • The problem is that computeHorizontalScrollRange() and computeHorizontalScrollExtent() are always equal. – AsafK Jan 28 '14 at 23:41
  • @AsafK 1. Make sure you are using code from the dev branch (not master). You can confirm this by seeing that TouchImageView overrides `canScrollHorizontally`. 2. You can ignore the second link. My example code above is an adaptation of the code in that link for TouchImageView rather than WebView. 3. I and at two other StackOverflow users have confirmed the example code above works. Make sure your implementation exactly follows mine. – Mike Ortiz Jan 29 '14 at 04:53
  • @MikeOrtiz Ok, I took the dev branch version and it works. But now there's a different problem - with the master branch version zooming the image was also increasing the imageview size so that it fills the entire screen. Now when i zoom the image is scaling but the imageview stays the same size. is this configurable ? – AsafK Jan 29 '14 at 11:36
  • @MikeOrtiz do you see what im talking about ? – AsafK Jan 30 '14 at 10:57
  • @AsafK No, I'm not seeing your problem. For me, it works as expected. Can you post a bug on [github](https://github.com/MikeOrtiz/TouchImageView/issues?direction=desc&sort=created&state=open). Could you include all relevant code in separate pastebin links, as well as device and os version? Thanks. – Mike Ortiz Jan 30 '14 at 18:45
  • Posted - [https://github.com/MikeOrtiz/TouchImageView/issues/54](https://github.com/MikeOrtiz/TouchImageView/issues/54). – AsafK Jan 31 '14 at 00:58
  • @AsafK Thank you for the response. Your pastebin link is private. – Mike Ortiz Jan 31 '14 at 01:01
  • 1
    can someone please help me how to use this code so that i can show multiple images in Pager with zoom in and zoom out functionality using buttons (which is already working) but the only problem is to use ViewPager can someone provide me latest code – Erum Mar 20 '14 at 18:16
  • This solution didn't work for me either. The zooming in/out is very awkward, most of the time it doesn't do anything and when it does it isn't natural (doesn't follow the amount you moved your fingers.) Also, after resizing an image and going to next slide, after going back the image has disappeared. It's disappointing because I spent all morning porting the code from java to C# Xamarin. :( – Justin Apr 14 '14 at 15:57
  • Here is the Xamarin code in case anyone else wants to play with it further and try to get it working... https://gist.github.com/justintoth/10663869 – Justin Apr 14 '14 at 16:39
  • @Justin It sounds like you may be using the code on master which does not yet support view pager and has some other bugs in it. Did you try with the dev branch? Also, I will soon be adding other examples to the sample app including view pager. The examples should be on dev in the next couples days and master soon after. – Mike Ortiz Apr 14 '14 at 17:53
  • @MikeOrtiz can u please paste link here with TouchImageView with ViewPager i need this i have already worked on zoomin and zoomout btn part but the only thing left is to integrate paging with them can u please post touchimageview+pager dev branch link here – Erum May 14 '14 at 11:17
  • [Here is a link](https://github.com/MikeOrtiz/TouchImageView/blob/master/src/com/ortiz/touch/ViewPagerExampleActivity.java) to an example ViewPager implementation. It is currently on the master branch as of v1.2.0. – Mike Ortiz May 14 '14 at 16:15
  • @MikeOrtiz Thanks a lot.+1 for update with view pager. – Giru Bhai Jul 11 '14 at 10:21
  • @Mike Ortiz I successfully implement your library but i want few changes and i want that when image is zoom than i restrict the viewpager from swipping or when user press the back key and if image is in zoom state than image should come in default state – Muhammad Younas Feb 22 '16 at 11:28
4

I found pretty solution with ImageViewZoom library. In order to scroll zoomed image in ViewPager I created own ViewPager:

public class ExtendedViewPager extends ViewPager {

    public ExtendedViewPager(Context context) {
        super(context);
    }

    public ExtendedViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        if (v instanceof ImageViewTouch) {
            return ((ImageViewTouch) v).canScroll(dx);
        } else {
            return super.canScroll(v, checkV, dx, x, y);
        }
    }
}

See more https://gist.github.com/atermenji/3781644

Ruslan Mansurov
  • 1,204
  • 16
  • 21
  • After zooming and swiping the view pager to next image and if I swipe back to previous image, that image not appearing to original position – Shadow Sep 09 '14 at 14:41
  • ViewPager doesn't destroy previous image from memory, so when you are moving back ViewPager just shows object with saved state. In order to reset zoom you may use ImageViewTouch's method resetDisplay or any other suitable for you method. – Ruslan Mansurov Sep 10 '14 at 09:06
  • yes i used @Master but not worked. http://stackoverflow.com/questions/25760505/how-to-reset-imageview-to-original-position-in-view-pager – Shadow Sep 10 '14 at 09:13
3

After several hours of testing the solutions above I have finally found the awesome Subsampling Scale Image View library, which works even with standard ViewPager from Android Support Package.

lobzik
  • 10,427
  • 1
  • 25
  • 32
2

My solution using ImageViewZoom Library is based on this custom ViewPager:

public class ImageViewTouchViewPager extends ViewPager {

    public ImageViewTouchViewPager(Context context) {
        super(context);
    }

    public ImageViewTouchViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        if (v instanceof ImageViewTouch) {
            ImageViewTouch imageViewTouch = (ImageViewTouch)v;
            if (imageViewTouch.getScale() == imageViewTouch.getMinScale()) {
                return super.canScroll(v, checkV, dx, x, y);
            }
            return imageViewTouchCanScroll(imageViewTouch, dx);
        } else {
            return super.canScroll(v, checkV, dx, x, y);
        }
    }


    /**
     * Determines whether the ImageViewTouch can be scrolled.
     *
     * @param direction - positive direction value means scroll from right to left,
     *                  negative value means scroll from left to right
     * @return true if there is some more place to scroll, false - otherwise.
     */
    private boolean imageViewTouchCanScroll(ImageViewTouch v, int direction){
        RectF bitmapRect = v.getBitmapRect();
        Rect imageViewRect = new Rect();
        getGlobalVisibleRect(imageViewRect);

        if (null == bitmapRect) {
            return false;
        }

        if (direction < 0) {
            return Math.abs(bitmapRect.right - imageViewRect.right) > 1.0f;
        }else {
            return Math.abs(bitmapRect.left - imageViewRect.left) > 1.0f;
        }

    }
}
moondroid
  • 1,670
  • 17
  • 20
0

I corrected the previous solution . You can scroll page , when ImageViewTouch is mode zoom.

public class ImageViewTouchViewPager extends ViewPager {

public ImageViewTouchViewPager(Context context) {
    super(context);
}

public ImageViewTouchViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if (v instanceof ImageViewTouch) {
        ImageViewTouch imageViewTouch = (ImageViewTouch)v;
        return imageViewTouchCanScroll(imageViewTouch, dx);
    } else {
        return super.canScroll(v, checkV, dx, x, y);
    }
}


/**
 * Determines whether the ImageViewTouch can be scrolled.
 *
 * @param direction - positive direction value means scroll from right to left,
 *                  negative value means scroll from left to right
 * @return true if there is some more place to scroll, false - otherwise.
 */
private boolean imageViewTouchCanScroll(ImageViewTouch imageViewTouch, int direction){
    int widthScreen = getWidthScreen();

    RectF bitmapRect = imageViewTouch.getBitmapRect();
    Rect imageViewRect = new Rect();
    getGlobalVisibleRect(imageViewRect);

    int widthBitmapViewTouch = (int)bitmapRect.width();

    if (null == bitmapRect) {
        return false;
    }

    if(widthBitmapViewTouch < widthScreen){
        return false;
    }

    if (direction < 0) {
        return Math.abs(bitmapRect.right - imageViewRect.right) > 1.0f;
    }else {
        return Math.abs(bitmapRect.left - imageViewRect.left) > 1.0f;
    }

}

private int getWidthScreen(){
    WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    Point size = new Point();
    display.getSize(size);
    return size.x;
}

}

0

For Those who are struggling to disable viewpager when the image is in pinched to zoom state & enable when the image is in original state. I just made some changes as answered by Mike.

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.ViewPager


class DCExtendedViewPager : ViewPager {


    private val TAG = DCExtendedViewPager::class.java.simpleName

    private var onImageState: OnImageState? = null

    private var touchImageViewCustom: DCTouchImageViewLatest? = null


    var isScroll: Boolean = true

    interface OnImageState {
        fun checkImageState(isImageInOriginalState: Boolean)
    }


    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)


    override fun canScroll(view: View, checkV: Boolean, dx: Int, x: Int, y: Int): Boolean {
        return if (view is DCTouchImageViewLatest) {
            //   touchImageView=view
            // canScrollHorizontally is not supported for Api < 14. To get around this issue,
            // ViewPager is extended and canScrollHorizontallyFroyo, a wrapper around
            // canScrollHorizontally supporting Api >= 8, is called.
            Log.e("ExtendedViewPager", "canScroll zoomedRect" + view.zoomedRect)

            view.canScrollHorizontallyFroyo(-dx)

        } else {
            super.canScroll(view, checkV, dx, x, y)
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        Log.e(TAG, "onTouchEventenable" + isScroll)
        return if (isScroll) {
            super.onTouchEvent(event)
        } else false

    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {

        Log.e(TAG, "onInterceptTouchEvent")

        Log.e(TAG, "currenrLayoutView called")
        val currenrLayoutView = getCurrentParentView()
        getTouchImageViewInstance(currenrLayoutView!!)

        return if (isScroll) {
            super.onInterceptTouchEvent(event)
        } else false

    }


    fun isViewPagerScrollValid(): Boolean {

        Log.e(TAG, "getFocusedChild()" + focusedChild)


        val currenrLayoutView = getCurrentParentView()


        var zoomRect = getTouchImageViewInstance(currenrLayoutView!!)?.zoomedRect
        var orgzoomRect = getTouchImageViewInstance(currenrLayoutView)?.originalRectF


        Log.e(TAG, "onInterceptTouchEvent zoomRect" + zoomRect)
        Log.e(TAG, "onInterceptTouchEvent orgzoomRect" + orgzoomRect)
        Log.e(TAG, "onInterceptTouchEvent onImageState" + onImageState)
        var scrollEnable = (zoomRect == orgzoomRect)


        // postLater(getTouchImageViewInstance(currenrLayoutView!!)!!)

        onImageState?.checkImageState(scrollEnable)
        Log.e(TAG, "onInterceptTouchEvent" + scrollEnable)


        return scrollEnable

    }


    fun setImageStateListner(onImageState: OnImageState) {
        this.onImageState = onImageState
    }


    fun getTouchImageViewInstance(accessingView: View): DCTouchImageViewLatest? {


        if (touchImageViewCustom == null) {

            try {
                for (index in 0 until (accessingView as ViewGroup).childCount) {

                    var nextChild = accessingView.getChildAt(index)

                    Log.e(TAG, "nextChild" + nextChild)

                    if (nextChild is ViewGroup) {
                        getTouchImageViewInstance(nextChild)
                    } else if (nextChild is View) {
                        if (nextChild is DCTouchImageViewLatest) {
                            touchImageViewCustom = nextChild
                            setListner()
                            break
                        }
                    }
                }

            } catch (ex: Exception) {
                ex.printStackTrace()
            }


        }


        Log.e(TAG, "getTouchImageViewInstance" + touchImageViewCustom)

        return touchImageViewCustom
    }

    private fun setListner() {

        touchImageViewCustom?.setOnDCTouchImageViewLatestListener(object : DCTouchImageViewLatest.OnDCTouchImageViewLatestListener {
            override fun onMove() {
                Log.e(TAG, "onMove Called")
                isScroll = isViewPagerScrollValid()
            }

        })

    }


    //Call this method from onPageSelected of viewpager
    fun viewPageChanged() {
        Log.e(TAG, "viewPageChanged called")
        touchImageViewCustom = null
    }


    fun getCurrentParentView(): View? {
        try {
            Log.e(TAG, "getCurrentView called")
            val currentItem = currentItem
            for (i in 0 until childCount) {
                val child = getChildAt(i)
                val layoutParams = child.layoutParams as ViewPager.LayoutParams

                val f = layoutParams.javaClass.getDeclaredField("position") //NoSuchFieldException
                f.isAccessible = true
                val position = f.get(layoutParams) as Int //IllegalAccessException

                Log.e(TAG, "currentItem" + currentItem)

                if (!layoutParams.isDecor && currentItem == position) {
                    Log.e(TAG, "getCurrentView" + child)
                    return child
                }
            }
        } catch (e: NoSuchFieldException) {
            Log.e(TAG, e.toString())
        } catch (e: IllegalArgumentException) {
            Log.e(TAG, e.toString())
        } catch (e: IllegalAccessException) {
            Log.e(TAG, e.toString())
        }

        return null
    }


}

In TouchImageView class, call getOriginalRectF() from setImageBitmap, setImageDrawable & setImageURI.

 public RectF getOriginalRectF(){

        Log.e(TAG,"getOriginalRectF called viewWidth"+viewWidth);
        Log.e(TAG,"getOriginalRectF called viewHeight"+viewHeight);

        if(originalRectF==null && viewHeight>0 && viewWidth>0){
            if (mScaleType == ScaleType.FIT_XY) {
                throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY");
            }
            PointF topLeft = transformCoordTouchToBitmap(0, 0, true);
            PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true);

            float w = getDrawableWidth(getDrawable());
            float h = getDrawableHeight(getDrawable());


            Log.e(TAG,"getOriginalRectF height"+h);
            Log.e(TAG,"getOriginalRectF width"+w);

            Log.e("getOriginalRectF","getZoomedRect topLeft"+topLeft.x +"-"+topLeft.y);
            Log.e("getOriginalRectF","getZoomedRect bottomRight"+bottomRight.x +"-"+bottomRight.y);


            originalRectF=new  RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h);


        }


        return originalRectF;

    }
Vikas Rai
  • 139
  • 1
  • 8