开发者

How do I modify TouchImageView with double tap to zoom in and out?

I am modifying the TouchImageView (https://github.com/MikeOrtiz/TouchImageView/issues) to have zoom in and out for when you double tap. I have started as per this post - How does TouchImageView works? and have added the gesture detection.

Now I have to implement the zooming in and zooming out which I am not sure how to do. Here is the code I have so far with the zoomIn and zoomOut methods unimplemented. Anyone know how to do this? In addition I noticed that the pinch zoom doesn't really zoom to the location where you pinch, so I was hoping that this could be made to behave more like the gallery3D pinch zoom. Thanks.

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

public class TouchImageView extends ImageView {

private static final String TAG = TouchImageView.class.getSimpleName();

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;

float width, height;
static final int CLICK = 3;
float saveScale = 1f;
float right开发者_StackOverflow中文版, bottom, origWidth, origHeight, bmWidth, bmHeight;

ScaleGestureDetector mScaleDetector;

private GestureDetector gestureDetector;

Context context;

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

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

public void init(Context context) {
    gestureDetector = new GestureDetector(new DoubleTapGestureListener());

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

            if (gestureDetector.onTouchEvent(event)) {
                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
        }

    });
}

@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 SimpleOnScaleGestureListener {
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        mode = ZOOM;
        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {

        LogUtil.i(TAG, detector.getScaleFactor() + " " + detector.getFocusX() + " " + detector.getFocusY());

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

class DoubleTapGestureListener extends SimpleOnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    // event when double tap occurs
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();
        LogUtil.i(TAG, "Tapped at: (" + x + "," + y + ")");
        if (isZoomed()) {
            zoomOut();
        } else {
            zoomIn();
        }
        return true;
    }

}

public boolean isZoomed() {
    return saveScale > minScale; // this seems to work
}

public void zoomIn() {
    LogUtil.i(TAG, "Zooming in");
    // TODO: no idea how to do this

}

public void zoomOut() {
    LogUtil.i(TAG, "Zooming out");
    // TODO: no idea how to do this
}

}


My answer might be not exactly specific to your problem but it covers ImageView Zooom in/out, double tap, Bounding image in Imageview when its zoomed , In short is exactly like the default Gallery app of Android

http://blog.sephiroth.it/2011/04/04/imageview-zoom-and-scroll/

As the blog says:

As long as Android doesn’t have a built-in ImageView widget with zoom and scroll capabilities I tries to create one by myself starting from the google repository.

The result is pretty nice so I’m posting here the source code, if anyone is interested, or simply doesn’t want to waste the time creating a new one.


Doubles the current zoom to zoom in, then returns to original zoom level:

float oldScale = 1.0f;
public void zoomIn() {
    LogUtil.i(TAG, "Zooming in");
    oldScale = saveScale;
    saveScale *= 2;
    matrix.setScale(saveScale, saveScale);
    setImageMatrix(matrix);
    invalidate();
}

public void zoomOut() {
    LogUtil.i(TAG, "Zooming out");
    saveScale = oldScale;
    matrix.setScale(saveScale, saveScale);
    setImageMatrix(matrix);
    invalidate();
}

You might want to translate the matrix to centre on the point the user double clicked.


@sherlock,

If you want to zoom out when double tap again, you can write a method as below:

private void handleScaleWhenDoubleTap(float scaleFactor) {
    //set original scale
    float origScale = curScale;
    //update current scale
    curScale *= scaleFactor;
    //fix current scale if it greater than max scale, recalculate scale factor
    if (curScale > maxScale) {
        curScale = maxScale;
        scaleFactor = maxScale / origScale;
    }
    //fix current scale if it less than min scale, recalculate scale factor
    else if (curScale < minScale) {
        curScale = minScale;
        scaleFactor = minScale / origScale;
    }
    //calculate right point, bottom point
    right = (curScale - 1) * width - 2 * (redundantXSpace * curScale);
    bottom = (curScale - 1) * height - 2 * (redundantYSpace * curScale);
    //scale
    matrix.postScale(scaleFactor, scaleFactor, width / 2, height / 2);
    //translate
    matrix.getValues(m);
    float x = m[Matrix.MTRANS_X];
    float y = m[Matrix.MTRANS_Y];

    if (y + bottom < 0) {
        if (x >= 0) {
            matrix.postTranslate(-x + redundantXSpace, -(y + bottom + redundantYSpace));
        } else if (x + right < 0) {
            matrix.postTranslate(-(x + right + redundantXSpace), -(y + bottom + redundantYSpace));
        } else {
            matrix.postTranslate(0, -(y + bottom));
        }
    } else if (y >= 0) {
        if (x >= 0) {
            matrix.postTranslate(-x + redundantXSpace, -y + redundantYSpace - navBarHeight);
        } else if (x + right < 0) {
            matrix.postTranslate(-(x + right + redundantXSpace), -y + redundantYSpace - navBarHeight);
        } else {
            matrix.postTranslate(0, -y);
        }
    } else {
        if (x >= 0) {
            matrix.postTranslate(-x, redundantYSpace - navBarHeight);
        } else if (x + right < 0) {
            matrix.postTranslate(-(x + right + redundantXSpace), redundantYSpace - navBarHeight);
        } else {
            matrix.postTranslate(0, -navBarHeight);
        }
    }
}

Note: In case a phone has Soft Navigation Bar (as Nexus 5), you have to recalculate the distance to translate. In my example, navBarHeight is the variable that be set by the function:

 public int getNavBarHeight(Context ctx) {
        int id = ctx.getResources().getIdentifier("config_showNavigationBar", "bool", "android");
        boolean hasNavBar = id > 0 && ctx.getResources().getBoolean(id);
        if (hasNavBar) {
            int resourceId = ctx.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
            if (resourceId > 0) {
                return ctx.getResources().getDimensionPixelSize(resourceId);
            }
        }
        return 0;
    }

Let me know if it helps.


Double tapping and other gestures can be detected as follows: Fling gesture detection on grid layout

I hope this helps... Emmanuel


With the class TouchImageView above, if you want to apply Double Tap, you can do some steps:

  1. Declare variable:

    GestureDetector mDoubleTap;
    
  2. Init in constructor:

    mDoubleTap = new GestureDetector(new DoubleTapGestureListener());
    
  3. Register in onTouch():

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mDoubleTap.onTouchEvent(event);
        ...
    }
    
  4. Write the method handleScale() - coppied from onScale() method - as below:

    private void handleScale(float mScaleFactor) {
        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, mScaleDetector.getFocusX(), mScaleDetector.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);
            }
        }
    }
    
  5. Put it in zoomIn() method as:

    public void zoomIn() {
        handleScale(2.0f);// 2.0f is the scale ratio
    }
    
  6. In the override method onScale(), you can modify as:

     @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float mScaleFactor = detector.getScaleFactor();
            handleScale(mScaleFactor);
            return true;
        }
    

to follow DRY principle.

Note: double tap has only effect with small touched area.

Let me know if it works for you.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜