Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Pinch-zoomable Android frame layout
package au.id.alexn;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.FrameLayout;
/**
* Layout that provides pinch-zooming of content. This view should have exactly one child
* view containing the content.
*/
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
}
@jpfolador

This comment has been minimized.

Copy link

commented Sep 25, 2014

Do you have some example of how use your class?
Does it work with all layout? When you pinch the screen all objects get zoom (text, images, ...) ?
Thanks for your attention.

@madhan123

This comment has been minimized.

Copy link

commented Nov 21, 2014

When adding custom view to framelayout zoomlayout class not firing

@timarevalo

This comment has been minimized.

Copy link

commented Mar 16, 2015

Hi anorth, how do you use your layout? I tried putting it as in activity_main.xml but my program stops when I run it. Does it also zoom in text, buttons etc?

@frlgrd

This comment has been minimized.

Copy link

commented May 6, 2015

Hi, I think its better to set mode to Mode.NONE on ACTION_POINTER_UP instead of Mode.DRAG

@Yaelo

This comment has been minimized.

Copy link

commented May 20, 2015

Just need to copy & paste the code over there in a class of your project, and add it when initializing your view in activity onCreate or in the onCreateView in case of the fragments, something like:
@override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View infl = inflater.inflate(R.layout.myLayout, container, false);
ZoomView myZoomView = new ZoomView(getActivity());
myZoomView.addView(infl);
return myZoomView;
}
in the onCreate, you only need to pass the ZoomView with the layout that you want to zoom, and set it in the content view;
setContentView(myZoomView);
and its done~

@Reegan01

This comment has been minimized.

Copy link

commented May 28, 2015


| LinearLayout |
| ____________________ |
| | RelativeLayout | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| |___________________ | |
| _________ _________ |
| | cancel btn| | savebtn | |
| |________ | |_______ | |

bro I am getting dynamic image from gallery and add in the RelativeLayout for add the pin mark images. So I want zoom the RelativeLayout and mark the pin So How to add your code in this scenario.Please reply me ..

@Collin7

This comment has been minimized.

Copy link

commented Jul 29, 2015

Hi,

How do i use this call.
My onCreate looks like this

@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playgame);
. . .

Can someone please help with this.

Thank you

@raizal

This comment has been minimized.

Copy link

commented Aug 18, 2015

It works :D
thanks

if you want to put it in your xml layout , just use it like this

<your.package.where.you.put.the.class.ZoomLayout
android:layout_width="match_parent"
android:layout_height="fill_parent">

</your.package.where.you.put.the.class.ZoomLayout>

@KarthikaSankaran

This comment has been minimized.

Copy link

commented Oct 13, 2015

i didnt work :(

@SANTOSHBHARATI

This comment has been minimized.

Copy link

commented Dec 29, 2015

Hi Alex,
I am using ZoomLayout like that manner

<------->
Imageview and textview
<-------->

I am not able to zoom the desire layout.
Can you pls help how I can use so that I can get zoom features.
Thanks in Advance.

@nebyan

This comment has been minimized.

Copy link

commented Mar 30, 2016

İt crashes :(

@Agrebennicov

This comment has been minimized.

Copy link

commented Jun 3, 2016

Hi,
Thank you very much, I saved a lot of time because of this.
Also I found if you will change

case MotionEvent.ACTION_POINTER_UP: mode = Mode.DRAG; break;

to

case MotionEvent.ACTION_POINTER_UP: mode = Mode.NONE;

dragging would be much smoother.

Thank you again

@zxc85

This comment has been minimized.

Copy link

commented Jun 14, 2016

Hi,

I am using this code as a custom layout but with this code it only can pinch-to-zoom into point 0,0. But I want it to zoom within finger point.
Could anyone help me.

Thank you.

@kikani89

This comment has been minimized.

Copy link

commented Jun 21, 2016

hi,

can you tell me how to disable moving layout in your code
i want to disable it.

@vashisthumesh

This comment has been minimized.

Copy link

commented Jul 5, 2016

hi thanks for this code ..........its working with my xml....but when i run the project ...........first i have to zoom it to my text view then its movable............its not movable on start .......pls tell me how to solve the this ....i want when i run .....code its movable as well as resizable....sorry for my bad grammer...i hope u understand it.thanks again.

@Naresh22

This comment has been minimized.

Copy link

commented Sep 3, 2016

is there possible to implement double tap to zoom?

@zonuptest

This comment has been minimized.

Copy link

commented Sep 8, 2016

how to use this class file can any one help me..

@sergioturcheniuk

This comment has been minimized.

Copy link

commented Nov 28, 2016

it works!
thank you.

@Kenkeh

This comment has been minimized.

Copy link

commented Dec 8, 2016

This code is so good! But it needs a change to properly make the current layout to zoom and it is to erase declarations of getting childs of the layout... if you erase that and apply that changes to current layout it works amazingly! im now trying to reduce stacking... i think im gonna try to change that switch for a ifelse annidation.. posting here results in a moment

@Kenkeh

This comment has been minimized.

Copy link

commented Dec 8, 2016

k talked to much dont know much about motion events yet and i cant figure right now how to do what i said

@aprabhakar-eoxys

This comment has been minimized.

Copy link

commented Jan 20, 2017

Hi anorth Thank you so much. You reduced my lot of time.

Is there possible to implement double tap to zoom?

@venkateshj99

This comment has been minimized.

Copy link

commented Feb 10, 2017

Hi Anorth, Thanks for the turorial, It works like charm.
To help beginners, who have tried and request How to set ZoomLayout in class,
In main activity,
public class Zoom extends AppCompatActivity {
FrameLayout fl;
@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.zoom);

    ZoomLayout myZoomView = new ZoomLayout(Zoom.this);

    fl = (FrameLayout)findViewById(R.id.zoom);
    fl.addView(myZoomView);
}

}
Xml
<your.package.ZoomLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/ic_launcher"
    android:layout_gravity="center"
    android:id="@+id/zoom"/>

</your.package..ZoomLayout>

In FrameLayout i have set image as bg to experience the image get zooming.

@ashutoshsrivastavaroot

This comment has been minimized.

Copy link

commented Mar 9, 2017

Hello ,
Do you have some example? how use your class?
Does it work with all layout? When you pinch the screen all objects get zoom ( images) ?
Thanks for your attention.

@KilrSabr

This comment has been minimized.

Copy link

commented Jun 19, 2017

It worked PERFECTLY for my app (zooms and pans!!).

I simply pulled this class into my project and linked to it via xml (no other code required - a simple xml tag change is all that was needed)...and it worked like a charm.

NOTE: when using it via xml (as I did) only the first child within the ZoomLayout is affected by the "zoom" and "pan" functionality - as is defined by the "getChildAt(0)" method in the "View child()" object at the end of the ZoomLayout class. All child views AFTER the 1st view are not zoomed or panned. I found this to be true at the time when I initially applied it to my project [i.e. 19 June 2017].

<my.app.path.ZoomLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent">
</my.app.path.ZoomLayout>

I will recommend this to anybody needing this fix in an Android project. This is a single class (ONE!!) that does exactly the same thing as other similar libraries (that have tens of classes and files) here on GitHub do.

Thanks for this plug-&-play solution, anorth.

@vipulyaara

This comment has been minimized.

Copy link

commented Jul 21, 2017

It works great. This is the only layout I have found that also works in a scrollView. But if you put the layout in a scrollView and zoom and move/pan the layout, it only pans to the height of screen and not beyond that (however layout's height is more than screen's height as it is in a scrollView).
So you can not pan it to the bottom of content.

Anyone found a solution to that?

@akshay-shah

This comment has been minimized.

Copy link

commented Jul 27, 2017

@vipulyaara did u find any solution to this ?

@ghamoron

This comment has been minimized.

Copy link

commented Aug 2, 2017

Hi,

i would like to give initial zoom for my layout. how can i do that? please help. thanks

@lakshparnami

This comment has been minimized.

Copy link

commented Aug 11, 2017

CHANGE

case MotionEvent.ACTION_POINTER_UP: mode = Mode.DRAG;
break;

TO
case MotionEvent.ACTION_POINTER_UP: mode = Mode.NONE;

remove auto repositioning.

@HungTDO

This comment has been minimized.

Copy link

commented Aug 16, 2017

@alexGrebennikov10 Thanks! It's worked perfectly!

@cami7ord

This comment has been minimized.

Copy link

commented Sep 20, 2017

I made a modification of the class to support double tap to zoom in and out, and removed the auto repositioning on the ACTION_POINTER_UP event.

Hope it would help someone: https://gist.github.com/cami7ord/0ce6f36a28d36bf17d96284f2cf75ae9

@Naresh22
@aprabhakar-eoxys

@monowar1993

This comment has been minimized.

Copy link

commented Sep 27, 2017

Hello, need some help. I want zoom the view like in which portion of the layout I put my fingers and zoom that portion will be in center of the layout.

@ghozimahdi

This comment has been minimized.

Copy link

commented Dec 18, 2017

To drag without having to zoom

You have to change

 case MotionEvent.ACTION_DOWN:
                        Log.i(TAG, "DOWN");
                        if (scale > MIN_ZOOM) {
                            Log.e("DOWN","Drag");
                            mode = Mode.DRAG;
                            startX = motionEvent.getX() - prevDx;
                            startY = motionEvent.getY() - prevDy;
                        }
                        break;

To

                        Log.i(TAG, "DOWN");
                        // TODO ORGINAL CODE UNCOMMENT BELOW CODE
                        /*if (scale > MIN_ZOOM) {*/
                            Log.e("DOWN","Drag");
                            mode = Mode.DRAG;
                            startX = motionEvent.getX() - prevDx;
                            startY = motionEvent.getY() - prevDy;
                        /*}*/
                        break;

And

                    getParent().requestDisallowInterceptTouchEvent(true);
                    float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
                    float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
                    dx = Math.min(Math.max(dx, -maxDx), maxDx);
                    dy = Math.min(Math.max(dy, -maxDy), maxDy);
                    Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
                            + ", max " + maxDx);
                    applyScaleAndTranslation();
                }

To

if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
                    float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
                    /*dx = Math.min(Math.max(dx, -maxDx), maxDx);
                    dy = Math.min(Math.max(dy, -maxDy), maxDy);*/
                    Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
                            + ", max " + maxDx);
                    applyScaleAndTranslation();
                }

Full Code

public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
                    case MotionEvent.ACTION_DOWN:
                        Log.i(TAG, "DOWN");
                        // TODO ORGINAL CODE UNCOMMENT BELOW CODE
                        /*if (scale > MIN_ZOOM) {*/
                            Log.e("DOWN","Drag");
                            mode = Mode.DRAG;
                            startX = motionEvent.getX() - prevDx;
                            startY = motionEvent.getY() - prevDy;
                        /*}*/
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (mode == Mode.DRAG) {
                            Log.e("MOVE","Drag");
                            dx = motionEvent.getX() - startX;
                            dy = motionEvent.getY() - startY;
                        }
                        break;
                    case MotionEvent.ACTION_POINTER_DOWN:
                        mode = Mode.ZOOM;
                        break;
                    // TODO ORGINAL CODE UNCOMMENT BELOW CODE
                    /*case MotionEvent.ACTION_POINTER_UP:mode = Mode.DRAG;*/
                    case MotionEvent.ACTION_POINTER_UP: mode = Mode.NONE;
                        Log.e("ACTION_POINTER_UP","Drag");
                        break;

                    case MotionEvent.ACTION_UP:
                        Log.i(TAG, "UP");
                        Log.e("ACTION_UP","None");
                        mode = Mode.NONE;
                        prevDx = dx;
                        prevDy = dy;
                        break;
                }
                scaleDetector.onTouchEvent(motionEvent);

                if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
                    float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
                    // TODO ORGINAL CODE UNCOMMENT BELOW CODE
                    /*dx = Math.min(Math.max(dx, -maxDx), maxDx);
                    dy = Math.min(Math.max(dy, -maxDy), maxDy);*/
                    Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
                            + ", max " + maxDx);
                    applyScaleAndTranslation();
                }

                return true;
            }
@gnarbaiz

This comment has been minimized.

Copy link

commented Mar 6, 2018

Hi, I would like to know if anyone else had this issue, when I make the first gesture to zoom, the relative that is contained by ZoomLayout moves to left and up. Why could it be?

@siddhi212

This comment has been minimized.

Copy link

commented Mar 30, 2018

Hi can anyone tell me how to zoomout when double tap on screen using above code

@amitaggrawal

This comment has been minimized.

Copy link

commented Apr 3, 2018

Thank you very much. It is working like a charm.

For all those who are still facing problem in using above code please try this. (It worked for me this way):

After keeping the code in your project (I kept it in separate java file), open your layout file (xml) and make

<your.package.ZoomLayout android:layout_width="match_parent" android:layout_height="match_parent">

</your.package..ZoomLayout>

your parent layout. All other layouts should be children of this layout. Even if you are using scroll view this should be its only child.

Try It. All the best!

@amitaggrawal

This comment has been minimized.

Copy link

commented Apr 3, 2018

@gnarbaiz, This is called auto re positioning.

CHANGE
case MotionEvent.ACTION_POINTER_UP: mode = Mode.DRAG; break;

TO
case MotionEvent.ACTION_POINTER_UP: mode = Mode.NONE;

This will solve your issue.

@amitaggrawal

This comment has been minimized.

Copy link

commented Apr 3, 2018

@siddhi212, Please refer to @cami7ord comment above or Use this link to achieve your result. https://gist.github.com/cami7ord/0ce6f36a28d36bf17d96284f2cf75ae9
And don't forget to thank @cami7ord.

@RPCarter53

This comment has been minimized.

Copy link

commented Apr 7, 2018

gnarbaiz, I did find on certain Android versions, the very first zoom would in some way confuse the positioning/drag mechanism. I found the solution was to add the following to the xml definition of the zooming child. I came across this from a Google search so not my own work.
android:transformPivotX="0dp"
android:transformPivotY="0dp"

@ashwinisawanth412

This comment has been minimized.

Copy link

commented May 2, 2018

I am trying to use this zoomlayout inside which is a video view...My code is as follows

<com.packagename.ZoomLayout
android:id="@+id/zoom_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">

    <com.packagename.MyVideoView
        android:id="@+id/video_view"
        android:layout_width="200dp"
        android:layout_height="150dp"
        android:layout_gravity="center"/>
    
</compackagename.ZoomLayout>

Although my zoom layout works like a charm upon pinching, the video view inside it doesn't. Please help. Also instead of a video view if i replace it with an image view with the same code, it does work. Thanks in advance

@HBiSoft

This comment has been minimized.

Copy link

commented May 20, 2018

@amitaggrawal changing mode = DRAG to NONE did not fix my view from moving to the top left corner, please look at the link I posted above. Can someone please help me resolve this issue?

@HBiSoft

This comment has been minimized.

Copy link

commented May 20, 2018

@gnarbaiz did you resolve this issue?

@Rantea

This comment has been minimized.

Copy link

commented Jun 18, 2018

thank you!

@Chirag117

This comment has been minimized.

Copy link

commented Aug 24, 2018

i have added table layout in this ZoomLayout class, now i want to perform click or touch event for custom view, and zoom in-out won't stop working, please help

@adityasonel

This comment has been minimized.

Copy link

commented Sep 11, 2018

Any one knows how to add click event on child views of this "ZoomLayout" ?

@chensmic

This comment has been minimized.

Copy link

commented Feb 18, 2019

@Chirag117
In the ZoomLayout, change

FROM
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;

TO

case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}else{
//write your code here
}
break;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.