5

I have good working TouchImageView and i want to know how does it work in a code : What i just want to do is be able to pinch to zoom, or use double tap to zoom on any imageview i choose, and when i zoom back i return to the original size of the image. TouchImageView.java :

public class TouchImageView extends ImageView {

Matrix matrix = new Matrix();

static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;

PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;

float redundantXSpace, redundantYSpace;

float width, height;
static final int CLICK = 3;
float saveScale = 1f;
float right, bottom, origWidth, origHeight, bmWidth, bmHeight;

ScaleGestureDetector mScaleDetector;

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) {
        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
    }

});
}

@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 = 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;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}

}

To use is i created a private class :

private class CreateImage extends AsyncTask<String, Void, Drawable> {
    protected void onPreExecute() {
    }

    protected Drawable doInBackground(String... urls) {
        InputStream is;
        Drawable d = null ;
        try {
            is = (InputStream)new URL(urls[0]).getContent();
            d = Drawable.createFromStream(is, "Image");
            return d;
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return d;
    }
    protected void onPostExecute(Drawable d) {
        touch.setMaxZoom(4f);
        touch.setImageDrawable(d);
        setContentView(touch);
    }
}
public void createUrlImage(String url){
    new CreateImage().execute(url);
}

and in the onCreate() i put createUrlImage(url). I modified TouchImageView by adding :

public void setImageDrawable(Drawable dr) { 
super.setImageDrawable(dr);
bmWidth = dr.getIntrinsicWidth();
bmHeight = dr.getIntrinsicHeight();
}
Tsunaze
  • 3,204
  • 7
  • 44
  • 81

1 Answers1

6

EDIT: Double Tap Zoom, Fling and other features have been added to TouchImageView since I originally answered this question. You can check it out on github here.


I added some usage details to the original post here. The code already has pinch zoom and panning, along with boundaries. Also, zooming out will return you to the original sized image.

Adding double tap zoom will take more work. You'll have to use a GestureDetector and override onDoubleTap and onSingleTapConfirmed. You'll then want to make sure you pass your touch events to gestureDetector, without interfering with the rest of the code (see how events are passed to mScaleDetector at the beginning of onTouch). You'll want to get rid of the call to performClick() in ACTION_UP and instead place it in onSingleTapConfirmed. You can check this answer for some skeletal code to get you started in implementing GestureDetector.

Let me know if you manage to get double tap zoom solid and I'll add your changes to the original post and the Github repo.

Community
  • 1
  • 1
Mike Ortiz
  • 4,031
  • 4
  • 27
  • 54
  • But i can't put TouchImageView in the Xml layout then ? – Tsunaze Sep 21 '11 at 09:49
  • And the image i want to pass is a drawable that i get from Asynctask. – Tsunaze Sep 21 '11 at 09:59
  • I modified it a little bit to put a drawable instead of a bitmap and it's working, but i can't pass a drawable through a Asynctask. – Tsunaze Sep 21 '11 at 10:16
  • You can use `BitmapFactory.decodeResource(getResources(), R.drawable.icon)` to convert your drawable into a bitmap. You can use TouchImageView in XML, but because it is a custom view, you will have to enter the entire name: `` – Mike Ortiz Sep 21 '11 at 10:54
  • Sorry, on second thought, the current implementation does not work in XML. What you need to do is 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. – Mike Ortiz Sep 21 '11 at 11:29
  • No, wait, it's working if i want to pass a Drawable, and i found a way to pass it through the Asynctask. The only problem is that the image is not full screen. – Tsunaze Sep 21 '11 at 11:49
  • Can you share the relevant code that you used to get TouchImageView to display? Did you have to alter TouchImageView besides getting it to accept drawable? The code in TouchImageView is designed to take up the entire parent view (like ImageView). If the image is not full screen, the view must not be full screen. – Mike Ortiz Sep 21 '11 at 12:16
  • I modified my question, you can take a look at it. – Tsunaze Sep 21 '11 at 17:48
  • @MikeOrtiz, Hi Mike, I tried to use your TouchImageView today. but it wasn't working, Took me a while to figure out, it was a statement i wrote which changed the scaletype of the ImgView. I suggest you add a comment specifying this condition for future users :) or override the `setScaletype` to ignore new changes. Regards. – st0le Jan 05 '12 at 06:13
  • @Mike Ortiz I've an issue using it in pager. When I zoom image and swipe left or right it is showing next item in the pager instead of dragging the image. I want that when the image is zoomed user cannot swipe the pager. I tried many solution but none helps me to get this behaviour. Is there any way to do that? Btw, thanks for the library, It's the best one so far. – SkyWalker Jan 07 '14 at 10:37
  • 1
    @Hardik I've actually just answered this question [here](http://stackoverflow.com/a/20944068/854864). You will need to extend ViewPager and add a method to TouchImageView to make it work. Let me know if you have any questions. – Mike Ortiz Jan 07 '14 at 11:57
  • Hi Mike how can I remove the borders? – Amanni Feb 28 '14 at 09:49
  • @Amanni, not sure what you mean. Maybe you want to set the width and height to `wrap_content`. Or, set the scaleType to `CENTER_CROP`. – Mike Ortiz Feb 28 '14 at 19:29
  • @MikeOrtiz how you have able to develop such good library.From where you learnt all that? – TechChain Apr 28 '15 at 12:40
  • Hey! Was double-tap to zoom finally implemented? I was just getting started on the library – Zen Feb 01 '16 at 19:58
  • Yes, refer to the edit at the top of the answer. It links to the project on github. – Mike Ortiz Feb 01 '16 at 23:17