How can I get zoom functionality for images?
Asked Answered
I

13

209

Is there a common way to show a big image and enable the user to zoom in and out and pan the image?

Until now I found two ways:

  1. overwriting ImageView, that seems a little bit too much for such a common problem.
  2. using a webview but with less control over the overall layout etc.
Inextirpable answered 29/3, 2010 at 10:36 Comment(3)
There is a ZOOM CONTROL (Widget) and you can listen to the OnTouch event to handle the panning.Selfemployed
A similar question #2537896, has a link to this tutorial anddev.org/…. You might find that useful to pan your iamge. I haven't read it in detail, but it might also give you some ideas on how to do the zoom function as well.Dobson
Have anyone tried to save the image when zooming? I want the saved image on a default state instead of the zoomed state. Please see my question : #24731293 ThanksUlceration
I
60

I used a WebView and loaded the image from the memory via

webview.loadUrl("file://...")

The WebView handles all the panning zooming and scrolling. If you use wrap_content the webview won't be bigger then the image and no white areas are shown. The WebView is the better ImageView ;)

Inextirpable answered 29/3, 2010 at 14:23 Comment(8)
I'm using the same approach. I have a large subway map that I want the user to be able to zoom and scroll around. I noticed though that if you have a quite large image (i.e. 1000 or 3000 pixels wide), the image gets blurry once you zoom in. It seems coliris cannot display a large zoomed image very sharp. Even though the original image is uncompressed and very sharp. Therefore I ended up cutting the one large image into smaller slices and putting them together again via HTML. This way the image stays sharp when zooming in. (I'm on Nexus One, 2.1update before and now on 2.2)Bowie
@Mathias Lin: if a large image is sent over the wire, i've heard carriers compress large images. will this use-case suit you or did you load the image locally.Dollarfish
@Sam Quest: loading it locallyBowie
Mathias, I am very interested by your tiles approach. Would you agree to share some pieces of code? Seem really interesting.Siward
much better to use webview's built in zoom buttons and support for pinch to zoom in/out than to write a completely new algo which may not work across different phones and future android platform releasesKenway
I tiled a large image like Mathias suggested, but when the resulting page is 3000 pixels wide, the browser refuses to zoom out far enough to see the whole page. This has me reconsidering the TouchImageView approach.Frobisher
I apply your solution but white background is still showing.. Also I given background @null then also its showing................. <WebView android:id="@+id/imageview_thumbnail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:background="@null" android:scrollbars="none" />Eurypterid
this solution can only be applied if you happen to have the image sitting around on disk, or the image is small enough such that you can base 64 encode is and pass the string value into loadUrlWithData().Caudad
M
209

UPDATE

I've just given TouchImageView a new update. It now includes Double Tap Zoom and Fling in addition to Panning and Pinch Zoom. The code below is very dated. You can check out the github project to get the latest code.

USAGE

Place TouchImageView.java in your project. It can then be used the same as ImageView. Example:

TouchImageView img = (TouchImageView) findViewById(R.id.img);

If you are using TouchImageView in xml, then you must provide the full package name, because it is a custom view. Example:

<com.example.touch.TouchImageView
    android:id="@+id/img”
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Note: I've removed my prior answer, which included some very old code and now link straight to the most updated code on github.

ViewPager

If you are interested in putting TouchImageView in a ViewPager, refer to this answer.

Mikes answered 18/9, 2011 at 1:20 Comment(52)
@Tsunaze Copy this file into your project and use it basically the same as ImageView. I edited the post to add usage details. You can also check out the code on Github and look into TouchImageViewActivity.java for more help, but the code I provided above is exactly how it's used.Mikes
Hi Mike, your solution seems much more elegant then the normal one, but at least here on my Xoom, the zooming is very slow, compared to the original code. Do you see that too?Proofread
Paulo, I did not run into performance issues, but I didn't get to test on a tablet. By slow, do you mean laggy? I set a max zoom factor of 1.05 at the beginning of onScale. Is this what you're talking about? If not, try the following: 1. Are you in debug mode? This would slow it down significantly. 2. What size images are you setting. I didn't test with very large (8mp) images, but this might slow it down. 3. Do you have a phone you could test on? 4. If all else fails, see if multiplying mScaleFactor by 2 (if > 1) or 0.5 (if < 1) helps your situation.Mikes
Just to follow up, the issue seems to be in the limits set on mScaleFactor. These limits are too small for the tablet. A quick solution is to get rid of the max and min and change the line to float mScaleFactor = detector.getScaleFactor();.Mikes
@MikeOrtiz : thanks for your solution. However, if I try to use TouchImageView in the layout file, I get the following error : Error inflating class TouchImageView ..any help ?Markhor
@Markhor Change the View constructor to: TouchImageView(Context context, AttributeSet attrs) and call super(context, attrs); This is because when you inflate the custom view, it is constructed with two parameters, rather than just one. When I get around to it, I will fix TouchImageView to support the three view constructors and drawables.Mikes
@MikeOrtiz Sorry, didnt work out. Is there something else that I am missing ?Markhor
@Markhor Because it is a custom view, you need to write out the whole name in the XML file, ie <com.example.TouchImageView android:id="@+id/img" />. Did you do that?Mikes
@MikeOrtiz : Thanks got it working.. looks like an error on my behalf...extremely sorry :(Markhor
@MikeOrtiz : Also, is there a way to programmatically zoom the image in this way ?Markhor
@MikeOrtiz i have these class and used for my project it's working. but when i am using through xml i am not able to getting these things please help me.Hypallage
@Raja Look at the previous two responses to Ahsan. You must change the View constructor and call the view by it's full name, because it is a custom view. I have not updated the code, because I don't have access to a device at the moment and I don't want to update with untested code. I will, however, make a note of it in the description.Mikes
@MikeOrtiz i followed those instruction, i am not getting an error but i am unalbe to do zoom an psnning.<com.colors.photo.edit.TouchImageView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/image"/> in java TouchImageView mWebView = (TouchImageView) findViewById(R.id.image); mWebView.setImageBitmap(originalBitmap); mWebView.setMaxZoom(4f);Hypallage
Sorry, I'm not sure what the issue is then. This is the first complaint I've heard like this. I assume you are testing on a device, right? It probably won't work on an emulator. Also make sure your project is for 2.2 or above. Some debugging tips: put Logcat print statements in the MotionEvent switch statement to make sure touch events are registering correctly. In onScale, make sure mScaleFactor is varying. The scaling stuff is mostly solid, so my hunch is that there's an issue with your view registering touch events. Please post an update here when you figure it out.Mikes
Also, is this TouchImageView the only view occupying the screen? I did not test for multiple views in one screen or know of anyone who has used my code that way.Mikes
@MikeOrtiz thank you, nice solution for panning and zooming. my problem solved i tried with in layout through that i cat't get this.. now i am using TouchImageView only in total screen.Hypallage
It's not working for me in a Fragment. Nothing is rendered, either when configured via XML (with constructor changes) or adding dynamically. :-( public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.rx_info_type1, container, false); ViewGroup content = (ViewGroup)view.findViewById(R.id.content);TouchImageView image = new TouchImageView(getActivity());image.setImageResource(R.drawable.esc2010);image.setMaxZoom(4f);content.addView(image);return view; }Topheavy
The problem was setImageResource() - it didn't work for me. I had to load the bitmap from resource into a Bitmap object, and use setImageBitmap() instead.Topheavy
@MikeOrtiz I have just tried your code and it works pretty well. But I have noticed that it's not possible to move the image around when it's zoomed out. Any idea how to change this (using your code)?Elflock
@MikeOrtiz Can u pls help me? I need to set buttons on the touchImageView. Is there a way to resize/move the button within the touchimageview so the buttons are always on the same location on the image.. (Like map annotation on map, but this time on a image) see: #9767794Folly
Hi @MikeOrtiz It works fine,Only problem that I am facing is onTap action. When on tap action is performed I wants setVisible for layout.I am using as XML<com.android.utils.TouchImageView android:id="@+id/Image1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:adjustViewBounds="true" android:background="@drawable/map" android:clickable="true" android:scaleType="center" />Predisposition
Hi @Mike Ortiz, I tried your code, it works but it isn't smooth and not elastic like Gallery app. Can you teach me fix it for smooth.External
Have just tesed. It doesn't work in Fragment, image is displayed but it cannot be zoomed.External
This is great stuff, been looking for this for ages. Do use the code from github as it is more recent and just works betterAeniah
@MikeOrtiz Great custom ImageView class. One issue I have found: I have the TouchImageView as a child in a RelativeLayout, unfortunately it seems, android:layout_height="wrap_content" cuts off the bottom of the image when it is loaded. Of course when I use "match_parent" it works fine by filling the height of the screen, however, I have other views that need to also be on the screen without overlaying the image. Anyway, I took a look at the Source for ImageView, link particularly the onMeasure function. And made some changes to yours. See answer below.Ehman
But if my image is in gallery then it could not swipe the image in gallery ..... so how to detect swipe effect in order to swipe image in gallery. i.e. i want zooming effect and swipe effect for image in gallery simultaneously.............Armington
@Armington TouchImageView does not currently support usage in a gallery. Here is a link to someone who forked my code so that it could work in a gallery. I haven't personally tested it yet, so let us know if it works!Mikes
@Mike i have tried this code but custom gallery doesn't work. Is there any trick work around this problem.Armington
This code seems to somehow get over the issue "Bitmap too large to be uploaded into a texture". I tried the github code with 12MP (4000+ pixels) bitmap and it just works! But once I use the same library in my own app, and "Bitmap too large..." error is back. Where is the trick? I've been googleing for days.Whereto
@MikeOrtiz Will this support double tap zoom-in?? Because I could only get the imageview to zoom-in using pinch-out. And that too doesn't work well while pinching-out horizontally. vertically it works fine.Bladdernut
@MikeOrtiz how to reset zoom and translation.. I mean original one when display first time. I tried with resetting matrix but failed to get. Can you?Turbid
@sunshine Check out this answer, which adapts TouchImageView to implement double tap zoom (ie resetting zoom and translation).Mikes
@MikeOrtiz I had already found your answer today. You got upvote today :D. Thanks for reply.Turbid
@Mike, any suggestions on making your code available for drawables as well? I already added public void setImageDrawable (Drawable drawable) to the code setting bmWidth and bmHeight, the drawable is shown, but the zooming seems to be very slow. Ideas what else I have to adjust? Thanks!Estovers
@MikeOrtiz: Thanks for the code. I've made several improvements, including making it compatible with API level 4 (Android 1.6) and adding long touch detection. My code is available on GitHub.Applique
@TobiN I'd recommend checking out the code on github. It is more up to date with support for drawables. Also, are you using a tablet? The above code placed a limitation on zoom speed which was very noticeable on tablets, but the code on github should've fixed this issue.Mikes
Very outdated. No pinching to zoom.Talya
@AndroidDev Pinch zoom is supported. Check the github link for the most updated code. Are you having issues with pinch zoom when using the github code?Mikes
OK, the version I got didn't have it. But with your updated version it is clear that pinch zooming only works when the device is held in portrait mode. If you turn the device to landscape mode and pinch vertically, it won't zoom. But if you pinch horizontally it will. Pinching should zoom in or out smoothly regardless what the orientation of the device is.Talya
@AndroidDev That's unexpected as I use ScaleGestureDetector to detect pinch zoom. I haven't run into this issue so it could be phone specific. What kind of device are you using?Mikes
Huawei Y200 and Huawei Y300. I doubt it has to do with the device. People mostly take photos in landscape mode, so pinching in landscape mode to zoom is actually the norm.Talya
@MikeOrtiz: Hi Mike! I used your pinch-zoom library (TouchImageView.java). Amazing work! But, i'm facing a little problem with your Github code. I want my imageview to fit the screen. "android:scaleType:fitxy" in the layout xml was taking care of that before i had integrated pinch-zoom functionality. Now that I'm using <com.myproject.TouchImageView .../> in my layout instead of normal <ImageView ../>, scaleType:fitxy probably gets overridden by scaleType.MATRIX in your TouchImageView.java code. I changed it to FIT_XY (in your code) but pinch-zoom stopped working. Please help .Premeditation
@MikeOrtiz I found that when the screen rotates, the image doesn't get resized maintaining aspect ratio. it has blank area on the top, that's why it creates problem positioning the image. Probably have to fix onMeasure method. github.com/MikeOrtiz/TouchImageView/issues/40Calumny
@MikeOrtiz this works beautifully! However now I'm struggling to make doubletap to zoom in to maximum scale and centered on the double tap point.. help!Weinert
@Weinert You're in luck. I've just implemented this feature. You can check it out on github!Mikes
@Calumny Thank you for pointing this out. This problem has been fixed in the latest code on github.Mikes
@MikeOrtiz The double tap has introduced another BUG. If you double tap on an ImageView to zoom it, then set another picture on that ImageView dynamically, the images doesn't show up.Calumny
@MikeOrtiz I created an issue on your git repo: github.com/MikeOrtiz/TouchImageView/issues/41Calumny
@Calumny Thanks, I've noticed this bug as well and I am working on it. I responded to the git issue.Mikes
might seem stupid question but where are your constants defined? NONE, FLING etc EDIT: Oh, they are an ENUM. I actually had to go through and prepend all of them with State. ie, State.NONESqualor
Unfortunately, this lib does not seem to work properly in scrollview Related issue...Mutz
@Mike how can I set fixed size for imageview? So if I pinch in/out, image size will not change.Koral
S
80

I adapted some code to create a TouchImageView that supports multitouch (>2.1). It is inspired by the book Hello, Android! (3rd edition)

It is contained within the following 3 files TouchImageView.java WrapMotionEvent.java EclairMotionEvent.java

TouchImageView.java

import se.robertfoss.ChanImageBrowser.Viewer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;

public class TouchImageView extends ImageView {

    private static final String TAG = "Touch";
    // These matrices will be used to move and zoom image
    Matrix matrix = new Matrix();
    Matrix savedMatrix = new Matrix();

    // We can be in one of these 3 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    int mode = NONE;

    // Remember some things for zooming
    PointF start = new PointF();
    PointF mid = new PointF();
    float oldDist = 1f;

    Context context;


    public TouchImageView(Context context) {
        super(context);
        super.setClickable(true);
        this.context = context;

        matrix.setTranslate(1f, 1f);
        setImageMatrix(matrix);
        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent rawEvent) {
                WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);

                // Dump touch event to log
                if (Viewer.isDebug == true){
                    dumpEvent(event);
                }

                // Handle touch events here...
                switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    savedMatrix.set(matrix);
                    start.set(event.getX(), event.getY());
                    Log.d(TAG, "mode=DRAG");
                    mode = DRAG;
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    oldDist = spacing(event);
                    Log.d(TAG, "oldDist=" + oldDist);
                    if (oldDist > 10f) {
                        savedMatrix.set(matrix);
                        midPoint(mid, event);
                        mode = ZOOM;
                        Log.d(TAG, "mode=ZOOM");
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    int xDiff = (int) Math.abs(event.getX() - start.x);
                    int yDiff = (int) Math.abs(event.getY() - start.y);
                    if (xDiff < 8 && yDiff < 8){
                        performClick();
                    }
                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    Log.d(TAG, "mode=NONE");
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == DRAG) {
                        // ...
                        matrix.set(savedMatrix);
                        matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
                    } else if (mode == ZOOM) {
                        float newDist = spacing(event);
                        Log.d(TAG, "newDist=" + newDist);
                        if (newDist > 10f) {
                            matrix.set(savedMatrix);
                            float scale = newDist / oldDist;
                            matrix.postScale(scale, scale, mid.x, mid.y);
                        }
                    }
                    break;
                }

                setImageMatrix(matrix);
                return true; // indicate event was handled
            }

        });
    }


    public void setImage(Bitmap bm, int displayWidth, int displayHeight) { 
        super.setImageBitmap(bm);

        //Fit to screen.
        float scale;
        if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())){
            scale =  (float)displayWidth / (float)bm.getWidth();
        } else {
            scale = (float)displayHeight / (float)bm.getHeight();
        }

        savedMatrix.set(matrix);
        matrix.set(savedMatrix);
        matrix.postScale(scale, scale, mid.x, mid.y);
        setImageMatrix(matrix);


        // Center the image
        float redundantYSpace = (float)displayHeight - (scale * (float)bm.getHeight()) ;
        float redundantXSpace = (float)displayWidth - (scale * (float)bm.getWidth());

        redundantYSpace /= (float)2;
        redundantXSpace /= (float)2;


        savedMatrix.set(matrix);
        matrix.set(savedMatrix);
        matrix.postTranslate(redundantXSpace, redundantYSpace);
        setImageMatrix(matrix);
    }


    /** Show an event in the LogCat view, for debugging */
    private void dumpEvent(WrapMotionEvent event) {
        // ...
        String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE",
            "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
        StringBuilder sb = new StringBuilder();
        int action = event.getAction();
        int actionCode = action & MotionEvent.ACTION_MASK;
        sb.append("event ACTION_").append(names[actionCode]);
        if (actionCode == MotionEvent.ACTION_POINTER_DOWN
                || actionCode == MotionEvent.ACTION_POINTER_UP) {
            sb.append("(pid ").append(
                    action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
            sb.append(")");
        }
        sb.append("[");
        for (int i = 0; i < event.getPointerCount(); i++) {
            sb.append("#").append(i);
            sb.append("(pid ").append(event.getPointerId(i));
            sb.append(")=").append((int) event.getX(i));
            sb.append(",").append((int) event.getY(i));
            if (i + 1 < event.getPointerCount())
            sb.append(";");
        }
        sb.append("]");
        Log.d(TAG, sb.toString());
    }

    /** Determine the space between the first two fingers */
    private float spacing(WrapMotionEvent event) {
        // ...
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return FloatMath.sqrt(x * x + y * y);
    }

    /** Calculate the mid point of the first two fingers */
    private void midPoint(PointF point, WrapMotionEvent event) {
        // ...
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
    }
}

WrapMotionEvent.java

import android.view.MotionEvent;

public class WrapMotionEvent {
protected MotionEvent event;




    protected WrapMotionEvent(MotionEvent event) {
        this.event = event;
    }

    static public WrapMotionEvent wrap(MotionEvent event) {
            try {
                return new EclairMotionEvent(event);
            } catch (VerifyError e) {
                return new WrapMotionEvent(event);
            }
    }



    public int getAction() {
            return event.getAction();
    }

    public float getX() {
            return event.getX();
    }

    public float getX(int pointerIndex) {
            verifyPointerIndex(pointerIndex);
            return getX();
    }

    public float getY() {
            return event.getY();
    }

    public float getY(int pointerIndex) {
            verifyPointerIndex(pointerIndex);
            return getY();
    }

    public int getPointerCount() {
            return 1;
    }

    public int getPointerId(int pointerIndex) {
            verifyPointerIndex(pointerIndex);
            return 0;
    }

    private void verifyPointerIndex(int pointerIndex) {
            if (pointerIndex > 0) {
                throw new IllegalArgumentException(
                    "Invalid pointer index for Donut/Cupcake");
            }
    }

}

EclairMotionEvent.java

import android.view.MotionEvent;

public class EclairMotionEvent extends WrapMotionEvent {

    protected EclairMotionEvent(MotionEvent event) {
            super(event);
    }

    public float getX(int pointerIndex) {
            return event.getX(pointerIndex);
    }

    public float getY(int pointerIndex) {
            return event.getY(pointerIndex);
    }

    public int getPointerCount() {
            return event.getPointerCount();
    }

    public int getPointerId(int pointerIndex) {
            return event.getPointerId(pointerIndex);
    }
}
Scarcely answered 29/3, 2010 at 10:36 Comment(9)
Robert Foss,if this add boundary judge ,it can fell more well.thank you your code very wellDecease
It works, but I don't see the point in WrapMotionEvent and EclairMotionEvent... anyway, +1.Decile
Multitouch for phones that support it. An regular touch for Android <2.0Scarcely
Nice example it work fine but i did not get what is Viewer in if (Viewer.isDebug == true){ dumpEvent(event); }Registrant
What is this? >> se.robertfoss.ChanImageBrowser.ViewerExternal
to get it to work via xml... add the following code: public TouchImageView(Context context, AttributeSet attrs) { super(context, attrs); initAll(context); } public TouchImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initAll(context); } public TouchImageView(Context context) { super(context); initAll(context); } private void initAll(Context context) { super.setClickable(true); this.context = context; ... //other init stuff from original contructor}Iatrics
It works like a charm even if I use SurfaceView! Thank you. +1Szabadka
@RobertFoss How do I restrict moving/panning only to bounds of view, something like Gallery app does? Currently I can move the image to the extreme sides of the view and so view will not show almost no image.Jury
I don't know :F I haven't done any android development since writing this answer pretty much.Scarcely
I
60

I used a WebView and loaded the image from the memory via

webview.loadUrl("file://...")

The WebView handles all the panning zooming and scrolling. If you use wrap_content the webview won't be bigger then the image and no white areas are shown. The WebView is the better ImageView ;)

Inextirpable answered 29/3, 2010 at 14:23 Comment(8)
I'm using the same approach. I have a large subway map that I want the user to be able to zoom and scroll around. I noticed though that if you have a quite large image (i.e. 1000 or 3000 pixels wide), the image gets blurry once you zoom in. It seems coliris cannot display a large zoomed image very sharp. Even though the original image is uncompressed and very sharp. Therefore I ended up cutting the one large image into smaller slices and putting them together again via HTML. This way the image stays sharp when zooming in. (I'm on Nexus One, 2.1update before and now on 2.2)Bowie
@Mathias Lin: if a large image is sent over the wire, i've heard carriers compress large images. will this use-case suit you or did you load the image locally.Dollarfish
@Sam Quest: loading it locallyBowie
Mathias, I am very interested by your tiles approach. Would you agree to share some pieces of code? Seem really interesting.Siward
much better to use webview's built in zoom buttons and support for pinch to zoom in/out than to write a completely new algo which may not work across different phones and future android platform releasesKenway
I tiled a large image like Mathias suggested, but when the resulting page is 3000 pixels wide, the browser refuses to zoom out far enough to see the whole page. This has me reconsidering the TouchImageView approach.Frobisher
I apply your solution but white background is still showing.. Also I given background @null then also its showing................. <WebView android:id="@+id/imageview_thumbnail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:background="@null" android:scrollbars="none" />Eurypterid
this solution can only be applied if you happen to have the image sitting around on disk, or the image is small enough such that you can base 64 encode is and pass the string value into loadUrlWithData().Caudad
E
7

In Response to Janusz original question, there are several ways to achieve this all of which vary in their difficulty level and have been stated below. Using a web view is good, but it is very limited in terms of look and feel and controllability. If you are drawing a bitmap from a canvas, the most versatile solutions that have been proposed seems to be MikeOrtiz's, Robert Foss's and/or what Jacob Nordfalk suggested. There is a great example for incorporating the android-multitouch-controller by PaulBourke, and is great for having the multi-touch support and alltypes of custom views.

Personally, if you are simply drawing a canvas to a bitmap and then displaying it inside and ImageView and want to be able to zoom into and move around using multi touch, I find MikeOrtiz's solution as the easiest. However, for my purposes the code from the Git that he has provided seems to only work when his TouchImageView custom ImageView class is the only child or provide the layout params as:

android:layout_height="match_parent"
android:layout_height="match_parent"

Unfortunately due to my layout design, I needed "wrap_content" for "layout_height". When I changed it to this the image was cropped at the bottom and I couldn't scroll or zoom to the cropped region. So I took a look at the Source for ImageView just to see how Android implemented "onMeasure" and changed MikeOrtiz's to suit.

   @Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  //**** ADDED THIS ********/////
      int  w = (int) bmWidth;
      int  h = (int) bmHeight;
     width = resolveSize(w, widthMeasureSpec);  
     height = resolveSize(h, heightMeasureSpec);
  //**** END ********///   

   // width = MeasureSpec.getSize(widthMeasureSpec);   // REMOVED
   // height = MeasureSpec.getSize(heightMeasureSpec); // REMOVED

    //Fit to screen.
    float scale;
    float scaleX =  (float)width / (float)bmWidth;
    float scaleY = (float)height / (float)bmHeight;

    scale = Math.min(scaleX, scaleY);
    matrix.setScale(scale, scale);
    setImageMatrix(matrix);
    saveScale = 1f;

    // Center the image
    redundantYSpace = (float)height - (scale * (float)bmHeight) ;
    redundantXSpace = (float)width - (scale * (float)bmWidth);
    redundantYSpace /= (float)2;
    redundantXSpace /= (float)2;

    matrix.postTranslate(redundantXSpace, redundantYSpace);

    origWidth = width - 2 * redundantXSpace;
    origHeight = height - 2 * redundantYSpace;
   // origHeight = bmHeight;
    right = width * saveScale - width - (2 * redundantXSpace * saveScale);
    bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);

    setImageMatrix(matrix);
}

Here resolveSize(int,int) is a "Utility to reconcile a desired size with constraints imposed by a MeasureSpec, where :

Parameters:

 - size How big the view wants to be
 - MeasureSpec Constraints imposed by the parent

Returns:

 - The size this view should be."

So essentially providing a behaviour a little more similar to the original ImageView class when the image is loaded. Some more changes could be made to support a greater variety of screens which modify the aspect ratio. But for now I Hope this helps. Thanks to MikeOrtiz for his original code, great work.

Ehman answered 23/7, 2012 at 8:55 Comment(1)
Has this fix been incorporated into Mike's github repo?Idolum
P
6

You could also try out http://code.google.com/p/android-multitouch-controller/

The library is really great, although initially a little hard to grasp.

Pizarro answered 13/4, 2011 at 0:14 Comment(0)
S
6

I just integrated Robert Foss's TouchImageView: it worked perfectly out of the box! Thanks!

I just modified a bit the code so I could be able to instantiate it from my layout.xml.

Just add two constructors

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

public TouchImageView(Context context) {
    super(context);
    init(context);
}

and transform the old constructor into an init method:

private void init(Context context){
    //...old code ofconstructor of Robert Moss's code
}
Showcase answered 11/12, 2012 at 11:13 Comment(0)
R
3

@Robert Foss, @Mike Ortiz, thank you very much for your work. I merged your work, and completed Robert classes for android > 2.0 with Mike additional work.

As result of my work I present Android Touch Gallery, based on ViewPager and used modificated TouchImageView. Images loading by URL and you can zoom and drag them. You can find it here https://github.com/Dreddik/AndroidTouchGallery

Roxane answered 3/7, 2012 at 2:54 Comment(0)
A
2

Try using ZoomView for zooming any other view.

http://code.google.com/p/android-zoom-view/ it's easy, free and fun to use!

Armalda answered 8/1, 2013 at 14:20 Comment(1)
This repository is no longer maintained.Mutz
T
2

Adding to @Mike's answer. I also needed double tap to restore the image to the original dimensions when first viewed. So I added a whole heap of "orig..." instance variables and added the SimpleOnGestureListener which did the trick.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;

public class TouchImageView extends ImageView {

    Matrix matrix = new Matrix();

    // We can be in one of these 3 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    int mode = NONE;

    // Remember some things for zooming
    PointF last = new PointF();
    PointF start = new PointF();
    float minScale = 1f;
    float maxScale = 3f;
    float[] m;

    float redundantXSpace, redundantYSpace, origRedundantXSpace, origRedundantYSpace;;

    float width, height;
    static final int CLICK = 3;
    static final float SAVE_SCALE = 1f;
    float saveScale = SAVE_SCALE;

    float right, bottom, origWidth, origHeight, bmWidth, bmHeight, origScale, origBottom,origRight;

    ScaleGestureDetector mScaleDetector;
    GestureDetector mGestureDetector;

    Context context;

    public TouchImageView(Context context) {
        super(context);
        super.setClickable(true);
        this.context = context;
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

        matrix.setTranslate(1f, 1f);
        m = new float[9];
        setImageMatrix(matrix);
        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                boolean onDoubleTapEvent = mGestureDetector.onTouchEvent(event);
                if (onDoubleTapEvent) {
                    // Reset Image to original scale values
                    mode = NONE;
                    bottom = origBottom;
                    right = origRight;
                    last = new PointF();
                    start = new PointF();
                    m = new float[9];
                    saveScale = SAVE_SCALE;
                    matrix = new Matrix();
                    matrix.setScale(origScale, origScale);
                    matrix.postTranslate(origRedundantXSpace, origRedundantYSpace);
                    setImageMatrix(matrix);
                    invalidate();
                    return true;
                } 


                mScaleDetector.onTouchEvent(event);

                matrix.getValues(m);
                float x = m[Matrix.MTRANS_X];
                float y = m[Matrix.MTRANS_Y];
                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    last.set(event.getX(), event.getY());
                    start.set(last);
                    mode = DRAG;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == DRAG) {
                        float deltaX = curr.x - last.x;
                        float deltaY = curr.y - last.y;
                        float scaleWidth = Math.round(origWidth * saveScale);
                        float scaleHeight = Math.round(origHeight * saveScale);
                        if (scaleWidth < width) {
                            deltaX = 0;
                            if (y + deltaY > 0)
                                deltaY = -y;
                            else if (y + deltaY < -bottom)
                                deltaY = -(y + bottom);
                        } else if (scaleHeight < height) {
                            deltaY = 0;
                            if (x + deltaX > 0)
                                deltaX = -x;
                            else if (x + deltaX < -right)
                                deltaX = -(x + right);
                        } else {
                            if (x + deltaX > 0)
                                deltaX = -x;
                            else if (x + deltaX < -right)
                                deltaX = -(x + right);

                            if (y + deltaY > 0)
                                deltaY = -y;
                            else if (y + deltaY < -bottom)
                                deltaY = -(y + bottom);
                        }
                        matrix.postTranslate(deltaX, deltaY);
                        last.set(curr.x, curr.y);
                    }
                    break;

                case MotionEvent.ACTION_UP:
                    mode = NONE;
                    int xDiff = (int) Math.abs(curr.x - start.x);
                    int yDiff = (int) Math.abs(curr.y - start.y);
                    if (xDiff < CLICK && yDiff < CLICK)
                        performClick();
                    break;

                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    break;
                }

                setImageMatrix(matrix);
                invalidate();

                return true; // indicate event was handled
            }

        });

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                return true;
            }
        });
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        bmWidth = bm.getWidth();
        bmHeight = bm.getHeight();
    }

    public void setMaxZoom(float x) {
        maxScale = x;
    }

    private class ScaleListener extends
            ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            mode = ZOOM;
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float mScaleFactor = (float) Math.min(
                    Math.max(.95f, detector.getScaleFactor()), 1.05);
            float origScale = saveScale;
            saveScale *= mScaleFactor;
            if (saveScale > maxScale) {
                saveScale = maxScale;
                mScaleFactor = maxScale / origScale;
            } else if (saveScale < minScale) {
                saveScale = minScale;
                mScaleFactor = minScale / origScale;
            }
            right = width * saveScale - width
                    - (2 * redundantXSpace * saveScale);
            bottom = height * saveScale - height
                    - (2 * redundantYSpace * saveScale);
            if (origWidth * saveScale <= width
                    || origHeight * saveScale <= height) {
                matrix.postScale(mScaleFactor, mScaleFactor, width / 2,
                        height / 2);
                if (mScaleFactor < 1) {
                    matrix.getValues(m);
                    float x = m[Matrix.MTRANS_X];
                    float y = m[Matrix.MTRANS_Y];
                    if (mScaleFactor < 1) {
                        if (Math.round(origWidth * saveScale) < width) {
                            if (y < -bottom)
                                matrix.postTranslate(0, -(y + bottom));
                            else if (y > 0)
                                matrix.postTranslate(0, -y);
                        } else {
                            if (x < -right)
                                matrix.postTranslate(-(x + right), 0);
                            else if (x > 0)
                                matrix.postTranslate(-x, 0);
                        }
                    }
                }
            } else {
                matrix.postScale(mScaleFactor, mScaleFactor,
                        detector.getFocusX(), detector.getFocusY());
                matrix.getValues(m);
                float x = m[Matrix.MTRANS_X];
                float y = m[Matrix.MTRANS_Y];
                if (mScaleFactor < 1) {
                    if (x < -right)
                        matrix.postTranslate(-(x + right), 0);
                    else if (x > 0)
                        matrix.postTranslate(-x, 0);
                    if (y < -bottom)
                        matrix.postTranslate(0, -(y + bottom));
                    else if (y > 0)
                        matrix.postTranslate(0, -y);
                }
            }
            return true;

        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        // Fit to screen.
        float scale;
        float scaleX = (float) width / (float) bmWidth;
        float scaleY = (float) height / (float) bmHeight;
        scale = Math.min(scaleX, scaleY);
        matrix.setScale(scale, scale);
        setImageMatrix(matrix);
        saveScale = SAVE_SCALE;
        origScale = scale;

        // Center the image
        redundantYSpace = (float) height - (scale * (float) bmHeight);
        redundantXSpace = (float) width - (scale * (float) bmWidth);
        redundantYSpace /= (float) 2;
        redundantXSpace /= (float) 2;

        origRedundantXSpace = redundantXSpace;
        origRedundantYSpace = redundantYSpace;

        matrix.postTranslate(redundantXSpace, redundantYSpace);

        origWidth = width - 2 * redundantXSpace;
        origHeight = height - 2 * redundantYSpace;
        right = width * saveScale - width - (2 * redundantXSpace * saveScale);
        bottom = height * saveScale - height
                - (2 * redundantYSpace * saveScale);
        origRight = right;
        origBottom = bottom;
        setImageMatrix(matrix);
    }

}
Taw answered 1/5, 2013 at 6:46 Comment(0)
R
2

This is a very late addition to this thread but I've been working on an image view that supports zoom and pan and has a couple of features I haven't found elsewhere. This started out as a way of displaying very large images without causing OutOfMemoryErrors, by subsampling the image when zoomed out and loading higher resolution tiles when zoomed in. It now supports use in a ViewPager, rotation manually or using EXIF information (90° stops), override of selected touch events using OnClickListener or your own GestureDetector or OnTouchListener, subclassing to add overlays, pan while zooming, and fling momentum.

It's not intended as a general use replacement for ImageView so doesn't extend it, and doesn't support display of images from resources, only assets and external files. It requires SDK 10.

Source is on GitHub, and there's a sample that illustrates use in a ViewPager.

https://github.com/davemorrissey/subsampling-scale-image-view

Reorientation answered 29/5, 2014 at 23:56 Comment(0)
B
1

You can try using the LayoutParams for this

public void zoom(boolean flag){
    if(flag){
        int width=40;
        int height=40;
    }
    else{
        int width=20;
        int height=20;
    }
    RelativeLayout.LayoutParams param=new RelativeLayout.LayoutParams(width,height); //use the parent layout of the ImageView;
    imageView.setLayoutParams(param); //imageView is the view which needs zooming.
}

ZoomIn = zoom(true); ZoomOut = zoom(false);

Boehm answered 17/2, 2012 at 17:19 Comment(0)
C
0
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    imageDetail = (ImageView) findViewById(R.id.imageView1);
    imageDetail.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            ImageView view = (ImageView) v;
            System.out.println("matrix=" + savedMatrix.toString());
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    savedMatrix.set(matrix);
                    startPoint.set(event.getX(), event.getY());
                    mode = DRAG;
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    oldDist = spacing(event);
                    if (oldDist > 10f) {
                        savedMatrix.set(matrix);
                        midPoint(midPoint, event);
                        mode = ZOOM;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == DRAG) {
                        matrix.set(savedMatrix);
                        matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);
                    } else if (mode == ZOOM) {
                        float newDist = spacing(event);
                        if (newDist > 10f) {
                            matrix.set(savedMatrix);
                            float scale = newDist / oldDist;
                            matrix.postScale(scale, scale, midPoint.x, midPoint.y);
                        }
                    }
                    break;
            }
            view.setImageMatrix(matrix);
            return true;

        }

        @SuppressLint("FloatMath")
        private float spacing(MotionEvent event) {
            float x = event.getX(0) - event.getX(1);
            float y = event.getY(0) - event.getY(1);
            return FloatMath.sqrt(x * x + y * y);
        }

        private void midPoint(PointF point, MotionEvent event) {
            float x = event.getX(0) + event.getX(1);
            float y = event.getY(0) + event.getY(1);
            point.set(x / 2, y / 2);
        }
    });
}

and drawable folder should have bticn image file. perfectly works :)

Cero answered 21/2, 2014 at 11:6 Comment(0)
N
0

Something like below will do it.

@Override public boolean onTouch(View v,MotionEvent e)
{

    tap=tap2=drag=pinch=none;
    int mask=e.getActionMasked();
    posx=e.getX();posy=e.getY();

    float midx= img.getWidth()/2f;
    float midy=img.getHeight()/2f;
    int fingers=e.getPointerCount();

    switch(mask)
    {
        case MotionEvent.ACTION_POINTER_UP:
            tap2=1;break;

        case MotionEvent.ACTION_UP:
            tap=1;break;

        case MotionEvent.ACTION_MOVE:
            drag=1;
    }
    if(fingers==2){nowsp=Math.abs(e.getX(0)-e.getX(1));}
    if((fingers==2)&&(drag==0)){ tap2=1;tap=0;drag=0;}
    if((fingers==2)&&(drag==1)){ tap2=0;tap=0;drag=0;pinch=1;}

    if(pinch==1)

    {
        if(nowsp>oldsp)scale+=0.1;
        if(nowsp<oldsp)scale-=0.1;
        tap2=tap=drag=0;    
    }
    if(tap2==1)
        {
            scale-=0.1;
            tap=0;drag=0;
        }
    if(tap==1)
        {
            tap2=0;drag=0;
            scale+=0.1;
        }
    if(drag==1)
        {
            movx=posx-oldx;
            movy=posy-oldy;
            x+=movx;
            y+=movy;
            tap=0;tap2=0;
        }
    m.setTranslate(x,y);
    m.postScale(scale,scale,midx,midy);
    img.setImageMatrix(m);img.invalidate();
    tap=tap2=drag=none;
    oldx=posx;oldy=posy;
    oldsp=nowsp;
    return true;
}


public void onCreate(Bundle b)
{
        super.onCreate(b);

    img=new ImageView(this);
    img.setScaleType(ImageView.ScaleType.MATRIX);
    img.setOnTouchListener(this);

    path=Environment.getExternalStorageDirectory().getPath();   
    path=path+"/DCIM"+"/behala.jpg";
    byte[] bytes;
    bytes=null;
    try{
        FileInputStream fis;
        fis=new FileInputStream(path);
        BufferedInputStream bis;
        bis=new BufferedInputStream(fis);
        bytes=new byte[bis.available()];
        bis.read(bytes);
        if(bis!=null)bis.close();
        if(fis!=null)fis.close();

     }
    catch(Exception e)
        {
        ret="Nothing";
        }
    Bitmap bmp=BitmapFactory.decodeByteArray(bytes,0,bytes.length);

    img.setImageBitmap(bmp);

    setContentView(img);
}

For viewing complete program see here: Program to zoom image in android

Nebulize answered 23/9, 2017 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.