18

我一直在寻找 iphone,比如使用 android ImageView 进行点击和捏合缩放。 Mike Ortiz在 TouchImageView 上做了一些出色的工作来检测边界。他的代码可以在这里找到。

这段代码只缺少一件事,即双击缩放。谁能帮助将此功能添加到 Mike Ortiz 代码中?

4

6 回答 6

12

它很旧,但由于没有选择正确的答案,我想分享它。

我在这个答案中调整了 TouchImageView 类,以支持双击以通过实现GestureDetector.OnGestureListenerGestureDetector.OnDoubleTapListener接口来放大/缩小

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
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 implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    Matrix 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;

    int viewWidth, viewHeight;
    static final int CLICK = 3;
    float saveScale = 1f;
    protected float origWidth, origHeight;
    int oldMeasuredWidth, oldMeasuredHeight;

    ScaleGestureDetector mScaleDetector;

    Context context;

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

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

    GestureDetector mGestureDetector;

    private void sharedConstructing(Context context) {
        super.setClickable(true);
        this.context = context;
        mGestureDetector = new GestureDetector(context, this);
        mGestureDetector.setOnDoubleTapListener(this);

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
        matrix = new Matrix();
        m = new float[9];
        setImageMatrix(matrix);
        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                mScaleDetector.onTouchEvent(event);
                mGestureDetector.onTouchEvent(event);

                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        last.set(curr);
                        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 fixTransX = getFixDragTrans(deltaX, viewWidth,
                                    origWidth * saveScale);
                            float fixTransY = getFixDragTrans(deltaY, viewHeight,
                                    origHeight * saveScale);
                            matrix.postTranslate(fixTransX, fixTransY);
                            fixTrans();
                            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
            }

        });
    }

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

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        // Double tap is detected
        Log.i("MAIN_TAG", "Double tap detected");
        float origScale = saveScale;
        float mScaleFactor;

        if (saveScale == maxScale) {
            saveScale = minScale;
            mScaleFactor = minScale / origScale;
        } else {
            saveScale = maxScale;
            mScaleFactor = maxScale / origScale;
        }

        matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2,
                viewHeight / 2);

        fixTrans();
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return false;
    }

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

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

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

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float mScaleFactor = detector.getScaleFactor();
            float origScale = saveScale;
            saveScale *= mScaleFactor;
            if (saveScale > maxScale) {
                saveScale = maxScale;
                mScaleFactor = maxScale / origScale;
            } else if (saveScale < minScale) {
                saveScale = minScale;
                mScaleFactor = minScale / origScale;
            }

            if (origWidth * saveScale <= viewWidth
                    || origHeight * saveScale <= viewHeight)
                matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2,
                        viewHeight / 2);
            else
                matrix.postScale(mScaleFactor, mScaleFactor,
                        detector.getFocusX(), detector.getFocusY());

            fixTrans();
            return true;
        }
    }

    void fixTrans() {
        matrix.getValues(m);
        float transX = m[Matrix.MTRANS_X];
        float transY = m[Matrix.MTRANS_Y];

        float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
        float fixTransY = getFixTrans(transY, viewHeight, origHeight
                * saveScale);

        if (fixTransX != 0 || fixTransY != 0)
            matrix.postTranslate(fixTransX, fixTransY);
    }

    float getFixTrans(float trans, float viewSize, float contentSize) {
        float minTrans, maxTrans;

        if (contentSize <= viewSize) {
            minTrans = 0;
            maxTrans = viewSize - contentSize;
        } else {
            minTrans = viewSize - contentSize;
            maxTrans = 0;
        }

        if (trans < minTrans)
            return -trans + minTrans;
        if (trans > maxTrans)
            return -trans + maxTrans;
        return 0;
    }

    float getFixDragTrans(float delta, float viewSize, float contentSize) {
        if (contentSize <= viewSize) {
            return 0;
        }
        return delta;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        viewWidth = MeasureSpec.getSize(widthMeasureSpec);
        viewHeight = MeasureSpec.getSize(heightMeasureSpec);

        //
        // Rescales image on rotation
        //
        if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
                || viewWidth == 0 || viewHeight == 0)
            return;
        oldMeasuredHeight = viewHeight;
        oldMeasuredWidth = viewWidth;

        if (saveScale == 1) {
            // Fit to screen.
            float scale;

            Drawable drawable = getDrawable();
            if (drawable == null || drawable.getIntrinsicWidth() == 0
                    || drawable.getIntrinsicHeight() == 0)
                return;
            int bmWidth = drawable.getIntrinsicWidth();
            int bmHeight = drawable.getIntrinsicHeight();

            Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);

            float scaleX = (float) viewWidth / (float) bmWidth;
            float scaleY = (float) viewHeight / (float) bmHeight;
            scale = Math.min(scaleX, scaleY);
            matrix.setScale(scale, scale);

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

            matrix.postTranslate(redundantXSpace, redundantYSpace);

            origWidth = viewWidth - 2 * redundantXSpace;
            origHeight = viewHeight - 2 * redundantYSpace;
            setImageMatrix(matrix);
        }
        fixTrans();
    }
}

用法: 您可以ImageView在 XML 和 java 中用 TouchImageView替换您的

1. 对于 XML

<?xml version="1.0" encoding="utf-8"?>
<com.example.android.myapp.TouchImageView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/imViewedImage"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusable="true" />

2.对于Java

TouchImageView imViewedImage = findViewById(R.id.imViewedImage);
于 2019-02-01T06:37:58.863 回答
5

使用方向处理在 Android 中的 ImageView 上捏缩放

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.SimpleOnGestureListener;

public class ZoomableImageView extends View {
    private static final String TAG = "ZoomableImageView";       

    private Bitmap imgBitmap = null;

    private int containerWidth;
    private int containerHeight;

    Paint background;   

    //Matrices will be used to move and zoom image
    Matrix matrix = new Matrix();
    Matrix savedMatrix = new Matrix();

    PointF start = new PointF();       

    float currentScale;
    float curX;
    float curY;

    //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;

    //For animating stuff   
    float targetX;
    float targetY;
    float targetScale;
    float targetScaleX;
    float targetScaleY;
    float scaleChange;
    float targetRatio;
    float transitionalRatio;

    float easing = 0.2f;   
    boolean isAnimating = false;

    float scaleDampingFactor = 0.5f;

    //For pinch and zoom
    float oldDist = 1f;   
    PointF mid = new PointF();

    private Handler mHandler = new Handler();       

    float minScale;
    float maxScale = 8.0f;

    float wpRadius = 25.0f;
    float wpInnerRadius = 20.0f;

    float screenDensity;

    private GestureDetector gestureDetector;

    public static final int DEFAULT_SCALE_FIT_INSIDE = 0;
    public static final int DEFAULT_SCALE_ORIGINAL = 1;

    private int defaultScale;

    public int getDefaultScale() {
        return defaultScale;
    }

    public void setDefaultScale(int defaultScale) {
        this.defaultScale = defaultScale;
    }

    public ZoomableImageView(Context context) {
        super(context);       
        setFocusable(true);
        setFocusableInTouchMode(true);

        screenDensity = context.getResources().getDisplayMetrics().density;

        initPaints();
        gestureDetector = new GestureDetector(new MyGestureDetector());       
    }

    public ZoomableImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        screenDensity = context.getResources().getDisplayMetrics().density;       
        initPaints();
        gestureDetector = new GestureDetector(new MyGestureDetector());

        defaultScale = ZoomableImageView.DEFAULT_SCALE_FIT_INSIDE;
    }

    private void initPaints() {
        background = new Paint();
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);

        //Reset the width and height. Will draw bitmap and change
        containerWidth = width;
        containerHeight = height;

        if(imgBitmap != null) {
            int imgHeight = imgBitmap.getHeight();
            int imgWidth = imgBitmap.getWidth();

            float scale;
            int initX = 0;
            int initY = 0;           

            if(defaultScale == ZoomableImageView.DEFAULT_SCALE_FIT_INSIDE) {               
                if(imgWidth > containerWidth) {           
                    scale = (float)containerWidth / imgWidth;           
                    float newHeight = imgHeight * scale;           
                    initY = (containerHeight - (int)newHeight)/2;

                    matrix.setScale(scale, scale);
                    matrix.postTranslate(0, initY);
                }
                else {           
                    scale = (float)containerHeight / imgHeight;
                    float newWidth = imgWidth * scale;
                    initX = (containerWidth - (int)newWidth)/2;

                    matrix.setScale(scale, scale);
                    matrix.postTranslate(initX, 0);
                }

                curX = initX;
                curY = initY;

                currentScale = scale;
                minScale = scale;
            }
            else {
                if(imgWidth > containerWidth) {                                   
                    initY = (containerHeight - (int)imgHeight)/2;                   
                    matrix.postTranslate(0, initY);
                }
                else {                               
                    initX = (containerWidth - (int)imgWidth)/2;                   
                    matrix.postTranslate(initX, 0);
                }

                curX = initX;
                curY = initY;

                currentScale = 1.0f;
                minScale = 1.0f;               
            }


            invalidate();           
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {               
        if(imgBitmap != null && canvas != null)
        {                                           
            canvas.drawBitmap(imgBitmap, matrix, background);                                                   
        }
    }

    //Checks and sets the target image x and y co-ordinates if out of bounds
    private void checkImageConstraints() {
        if(imgBitmap == null) {
            return;
        }

        float[] mvals = new float[9];
        matrix.getValues(mvals);

        currentScale = mvals[0];

        if(currentScale < minScale) {                               
            float deltaScale = minScale / currentScale;                   
            float px = containerWidth/2;
            float py = containerHeight/2;           
            matrix.postScale(deltaScale, deltaScale, px, py);
            invalidate();
        }       

        matrix.getValues(mvals);
        currentScale = mvals[0];
        curX = mvals[2];
        curY = mvals[5];

        int rangeLimitX = containerWidth - (int)(imgBitmap.getWidth() * currentScale);
        int rangeLimitY = containerHeight - (int)(imgBitmap.getHeight() * currentScale);


        boolean toMoveX = false;
        boolean toMoveY = false;   

        if(rangeLimitX < 0) {
            if(curX > 0) {
                targetX = 0;
                toMoveX = true;
            }
            else if(curX < rangeLimitX) {
                targetX = rangeLimitX;
                toMoveX = true;
            }
        }
        else {
            targetX = rangeLimitX / 2;
            toMoveX = true;
        }

        if(rangeLimitY < 0) {
            if(curY > 0) {
                targetY = 0;
                toMoveY = true;
            }
            else if(curY < rangeLimitY) {
                targetY = rangeLimitY;
                toMoveY = true;
            }
        }
        else {
            targetY = rangeLimitY / 2;
            toMoveY = true;
        }

        if(toMoveX == true || toMoveY == true) {
            if(toMoveY == false) {
                targetY = curY;
            }
            if(toMoveX == false) {
                targetX = curX;
            }           

            //Disable touch event actions
            isAnimating = true;
            //Initialize timer           
            mHandler.removeCallbacks(mUpdateImagePositionTask);
            mHandler.postDelayed(mUpdateImagePositionTask, 100);
        }
    }       


    @Override
    public boolean onTouchEvent(MotionEvent event) {       
        if(gestureDetector.onTouchEvent(event)) {
            return true;
        }

        if(isAnimating == true) {
            return true;
        }

        //Handle touch events here       
        float[] mvals = new float[9];
        switch(event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            if(isAnimating == false) {
                savedMatrix.set(matrix);
                start.set(event.getX(), event.getY());           
                mode = DRAG;               
            }
        break;

        case MotionEvent.ACTION_POINTER_DOWN:
            oldDist = spacing(event);           
            if(oldDist > 10f) {
                savedMatrix.set(matrix);
                midPoint(mid, event);
                mode = ZOOM;
            }
        break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
            mode = NONE;

            matrix.getValues(mvals);
            curX = mvals[2];
            curY = mvals[5];
            currentScale = mvals[0];

            if(isAnimating == false) {                                       
                checkImageConstraints();
            }
        break;

        case MotionEvent.ACTION_MOVE:           
            if(mode == DRAG && isAnimating == false) {
                matrix.set(savedMatrix);
                float diffX = event.getX() - start.x;
                float diffY = event.getY() - start.y;

                matrix.postTranslate(diffX, diffY);

                matrix.getValues(mvals);
                curX = mvals[2];
                curY = mvals[5];
                currentScale = mvals[0];
            }
            else if(mode == ZOOM && isAnimating == false) {
                float newDist = spacing(event);               
                if(newDist > 10f) {
                    matrix.set(savedMatrix);
                    float scale = newDist / oldDist;                   
                    matrix.getValues(mvals);
                    currentScale = mvals[0];

                    if(currentScale * scale <= minScale) {
                        matrix.postScale(minScale/currentScale, minScale/currentScale, mid.x, mid.y);
                    }                   
                    else if(currentScale * scale >= maxScale) {
                        matrix.postScale(maxScale/currentScale, maxScale/currentScale, mid.x, mid.y);
                    }
                    else {
                        matrix.postScale(scale, scale, mid.x, mid.y);
                    }


                    matrix.getValues(mvals);
                    curX = mvals[2];
                    curY = mvals[5];
                    currentScale = mvals[0];                                       
                }
            }

        break;                               
        }

        //Calculate the transformations and then invalidate
        invalidate();
        return true;
    }

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

    public void setImageBitmap(Bitmap b) {       
        if(b != null) {
            imgBitmap = b;               

            containerWidth = getWidth();
            containerHeight = getHeight();

            int imgHeight = imgBitmap.getHeight();
            int imgWidth = imgBitmap.getWidth();

            float scale;
            int initX = 0;
            int initY = 0;

            matrix.reset();

            if(defaultScale == ZoomableImageView.DEFAULT_SCALE_FIT_INSIDE) {               
                if(imgWidth > containerWidth) {           
                    scale = (float)containerWidth / imgWidth;           
                    float newHeight = imgHeight * scale;           
                    initY = (containerHeight - (int)newHeight)/2;

                    matrix.setScale(scale, scale);
                    matrix.postTranslate(0, initY);
                }
                else {           
                    scale = (float)containerHeight / imgHeight;
                    float newWidth = imgWidth * scale;
                    initX = (containerWidth - (int)newWidth)/2;

                    matrix.setScale(scale, scale);
                    matrix.postTranslate(initX, 0);
                }

                curX = initX;
                curY = initY;

                currentScale = scale;
                minScale = scale;
            }
            else {
                if(imgWidth > containerWidth) {
                    initX = 0;
                    if(imgHeight > containerHeight) {                       
                        initY = 0;
                    }
                    else {                       
                        initY = (containerHeight - (int)imgHeight)/2;
                    }

                    matrix.postTranslate(0, initY);
                }
                else {                               
                    initX = (containerWidth - (int)imgWidth)/2;
                    if(imgHeight > containerHeight) {
                        initY = 0;
                    }
                    else {
                        initY = (containerHeight - (int)imgHeight)/2;
                    }
                    matrix.postTranslate(initX, 0);
                }

                curX = initX;
                curY = initY;

                currentScale = 1.0f;
                minScale = 1.0f;               
            }

            invalidate();           
        }
        else {
            Log.d(TAG, "bitmap is null");
        }
    }

    public Bitmap getPhotoBitmap() {       
        return imgBitmap;
    }


    private Runnable mUpdateImagePositionTask = new Runnable() {
        public void run() {       
            float[] mvals;

            if(Math.abs(targetX - curX) < 5 && Math.abs(targetY - curY) < 5) {
                isAnimating = false;
                mHandler.removeCallbacks(mUpdateImagePositionTask);

                mvals = new float[9];
                matrix.getValues(mvals);

                currentScale = mvals[0];
                curX = mvals[2];
                curY = mvals[5];

                //Set the image parameters and invalidate display
                float diffX = (targetX - curX);
                float diffY = (targetY - curY);

                matrix.postTranslate(diffX, diffY);
            }
            else {
                isAnimating = true;
                mvals = new float[9];
                matrix.getValues(mvals);

                currentScale = mvals[0];
                curX = mvals[2];
                curY = mvals[5];

                //Set the image parameters and invalidate display
                float diffX = (targetX - curX) * 0.3f;
                float diffY = (targetY - curY) * 0.3f;

                matrix.postTranslate(diffX, diffY);               
                mHandler.postDelayed(this, 25);               
            }

            invalidate();           
        }
    };

    private Runnable mUpdateImageScale = new Runnable() {
        public void run() {           
            float transitionalRatio = targetScale / currentScale;           
            float dx;
            if(Math.abs(transitionalRatio - 1) > 0.05) {
                isAnimating = true;               
                if(targetScale > currentScale) {                                       
                    dx = transitionalRatio - 1;
                    scaleChange = 1 + dx * 0.2f;

                    currentScale *= scaleChange;

                    if(currentScale > targetScale) {
                        currentScale = currentScale / scaleChange;
                        scaleChange = 1;
                    }
                }
                else {                                   
                    dx = 1 - transitionalRatio;                   
                    scaleChange = 1 - dx * 0.5f;
                    currentScale *= scaleChange;

                    if(currentScale < targetScale) {
                        currentScale = currentScale / scaleChange;
                        scaleChange = 1;
                    }
                }


                if(scaleChange != 1) {
                    matrix.postScale(scaleChange, scaleChange, targetScaleX, targetScaleY);               
                    mHandler.postDelayed(mUpdateImageScale, 15);
                    invalidate();
                }
                else {
                    isAnimating = false;
                    scaleChange = 1;                   
                    matrix.postScale(targetScale/currentScale, targetScale/currentScale, targetScaleX, targetScaleY);
                    currentScale = targetScale;
                    mHandler.removeCallbacks(mUpdateImageScale);
                    invalidate();
                    checkImageConstraints();
                }               
            }
            else {
                isAnimating = false;
                scaleChange = 1;               
                matrix.postScale(targetScale/currentScale, targetScale/currentScale, targetScaleX, targetScaleY);
                currentScale = targetScale;
                mHandler.removeCallbacks(mUpdateImageScale);
                invalidate();
                checkImageConstraints();
            }                               
        }
    };

   /** Show an event in the LogCat view, for debugging */
   private void dumpEvent(MotionEvent 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("]");
   }

   class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onDoubleTap(MotionEvent event) {           
            if(isAnimating == true) {
                return true;
            }

            scaleChange = 1;
            isAnimating = true;
            targetScaleX = event.getX();
            targetScaleY = event.getY();

            if(Math.abs(currentScale - maxScale) > 0.1) {           
                targetScale = maxScale;
            }
            else {
                targetScale = minScale;
            }
            targetRatio = targetScale / currentScale;
            mHandler.removeCallbacks(mUpdateImageScale);
            mHandler.post(mUpdateImageScale);           
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }
    }
}
于 2013-09-17T08:21:24.563 回答
3

在这里,它适用于双指缩放和双击缩放:

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.OverScroller;
import android.widget.Scroller;
import androidx.appcompat.widget.AppCompatImageView;

public class TouchImageView extends AppCompatImageView {

    private static final String DEBUG = "DEBUG";

    //
    // SuperMin and SuperMax multipliers. Determine how much the image can be
    // zoomed below or above the zoom boundaries, before animating back to the
    // min/max zoom boundary.
    //
    private static final float SUPER_MIN_MULTIPLIER = .75f;
    private static final float SUPER_MAX_MULTIPLIER = 1.25f;

    //
    // Scale of image ranges from minScale to maxScale, where minScale == 1
    // when the image is stretched to fit view.
    //
    private float normalizedScale;

    //
    // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal.
    // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix
    // saved prior to the screen rotating.
    //
    private Matrix matrix, prevMatrix;

    private static enum State {NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM}

    ;
    private State state;

    private float minScale;
    private float maxScale;
    private float superMinScale;
    private float superMaxScale;
    private float[] m;

    private Context context;
    private Fling fling;

    private ScaleType mScaleType;

    private boolean imageRenderedAtLeastOnce;
    private boolean onDrawReady;

    private ZoomVariables delayedZoomVariables;

    //
    // Size of view and previous view size (ie before rotation)
    //
    private int viewWidth, viewHeight, prevViewWidth, prevViewHeight;

    //
    // Size of image when it is stretched to fit view. Before and After rotation.
    //
    private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight;

    private ScaleGestureDetector mScaleDetector;
    private GestureDetector mGestureDetector;
    private GestureDetector.OnDoubleTapListener doubleTapListener = null;
    private OnTouchListener userTouchListener = null;
    private OnTouchImageViewListener touchImageViewListener = null;

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

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

    public TouchImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        sharedConstructing(context);
    }

    private void sharedConstructing(Context context) {
        super.setClickable(true);
        this.context = context;
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
        mGestureDetector = new GestureDetector(context, new GestureListener());
        matrix = new Matrix();
        prevMatrix = new Matrix();
        m = new float[9];
        normalizedScale = 1;
        if (mScaleType == null) {
            mScaleType = ScaleType.FIT_CENTER;
        }
        minScale = 1;
        maxScale = 3;
        superMinScale = SUPER_MIN_MULTIPLIER * minScale;
        superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;
        setImageMatrix(matrix);
        setScaleType(ScaleType.MATRIX);
        setState(State.NONE);
        onDrawReady = false;
        super.setOnTouchListener(new PrivateOnTouchListener());
    }

    @Override
    public void setOnTouchListener(OnTouchListener l) {
        userTouchListener = l;
    }

    public void setOnTouchImageViewListener(OnTouchImageViewListener l) {
        touchImageViewListener = l;
    }

    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) {
        doubleTapListener = l;
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        savePreviousImageValues();
        fitImageToView();
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        savePreviousImageValues();
        fitImageToView();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        savePreviousImageValues();
        fitImageToView();
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        savePreviousImageValues();
        fitImageToView();
    }

    @Override
    public void setScaleType(ScaleType type) {
        if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) {
            throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END");
        }
        if (type == ScaleType.MATRIX) {
            super.setScaleType(ScaleType.MATRIX);

        } else {
            mScaleType = type;
            if (onDrawReady) {
                //
                // If the image is already rendered, scaleType has been called programmatically
                // and the TouchImageView should be updated with the new scaleType.
                //
                setZoom(this);
            }
        }
    }

    @Override
    public ScaleType getScaleType() {
        return mScaleType;
    }

    /**
     * Returns false if image is in initial, unzoomed state. False, otherwise.
     *
     * @return true if image is zoomed
     */
    public boolean isZoomed() {
        return normalizedScale != 1;
    }

    /**
     * Return a Rect representing the zoomed image.
     *
     * @return rect representing zoomed image
     */
    public RectF getZoomedRect() {
        if (mScaleType == ScaleType.FIT_XY) {
            throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY");
        }
        PointF topLeft = transformCoordTouchToBitmap(0, 0, true);
        PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true);

        float w = getDrawable().getIntrinsicWidth();
        float h = getDrawable().getIntrinsicHeight();
        return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h);
    }

    /**
     * Save the current matrix and view dimensions
     * in the prevMatrix and prevView variables.
     */
    private void savePreviousImageValues() {
        if (matrix != null && viewHeight != 0 && viewWidth != 0) {
            matrix.getValues(m);
            prevMatrix.setValues(m);
            prevMatchViewHeight = matchViewHeight;
            prevMatchViewWidth = matchViewWidth;
            prevViewHeight = viewHeight;
            prevViewWidth = viewWidth;
        }
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putFloat("saveScale", normalizedScale);
        bundle.putFloat("matchViewHeight", matchViewHeight);
        bundle.putFloat("matchViewWidth", matchViewWidth);
        bundle.putInt("viewWidth", viewWidth);
        bundle.putInt("viewHeight", viewHeight);
        matrix.getValues(m);
        bundle.putFloatArray("matrix", m);
        bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce);
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            normalizedScale = bundle.getFloat("saveScale");
            m = bundle.getFloatArray("matrix");
            prevMatrix.setValues(m);
            prevMatchViewHeight = bundle.getFloat("matchViewHeight");
            prevMatchViewWidth = bundle.getFloat("matchViewWidth");
            prevViewHeight = bundle.getInt("viewHeight");
            prevViewWidth = bundle.getInt("viewWidth");
            imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered");
            super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
            return;
        }

        super.onRestoreInstanceState(state);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        onDrawReady = true;
        imageRenderedAtLeastOnce = true;
        if (delayedZoomVariables != null) {
            setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType);
            delayedZoomVariables = null;
        }
        super.onDraw(canvas);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        savePreviousImageValues();
    }

    /**
     * Get the max zoom multiplier.
     *
     * @return max zoom multiplier.
     */
    public float getMaxZoom() {
        return maxScale;
    }

    /**
     * Set the max zoom multiplier. Default value: 3.
     *
     * @param max max zoom multiplier.
     */
    public void setMaxZoom(float max) {
        maxScale = max;
        superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;
    }

    /**
     * Get the min zoom multiplier.
     *
     * @return min zoom multiplier.
     */
    public float getMinZoom() {
        return minScale;
    }

    /**
     * Get the current zoom. This is the zoom relative to the initial
     * scale, not the original resource.
     *
     * @return current zoom multiplier.
     */
    public float getCurrentZoom() {
        return normalizedScale;
    }

    /**
     * Set the min zoom multiplier. Default value: 1.
     *
     * @param min min zoom multiplier.
     */
    public void setMinZoom(float min) {
        minScale = min;
        superMinScale = SUPER_MIN_MULTIPLIER * minScale;
    }

    /**
     * Reset zoom and translation to initial state.
     */
    public void resetZoom() {
        normalizedScale = 1;
        fitImageToView();
    }

    /**
     * Set zoom to the specified scale. Image will be centered by default.
     *
     * @param scale
     */
    public void setZoom(float scale) {
        setZoom(scale, 0.5f, 0.5f);
    }

    /**
     * Set zoom to the specified scale. Image will be centered around the point
     * (focusX, focusY). These floats range from 0 to 1 and denote the focus point
     * as a fraction from the left and top of the view. For example, the top left
     * corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
     *
     * @param scale
     * @param focusX
     * @param focusY
     */
    public void setZoom(float scale, float focusX, float focusY) {
        setZoom(scale, focusX, focusY, mScaleType);
    }

    /**
     * Set zoom to the specified scale. Image will be centered around the point
     * (focusX, focusY). These floats range from 0 to 1 and denote the focus point
     * as a fraction from the left and top of the view. For example, the top left
     * corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
     *
     * @param scale
     * @param focusX
     * @param focusY
     * @param scaleType
     */
    public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) {
        //
        // setZoom can be called before the image is on the screen, but at this point,
        // image and view sizes have not yet been calculated in onMeasure. Thus, we should
        // delay calling setZoom until the view has been measured.
        //
        if (!onDrawReady) {
            delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType);
            return;
        }

        if (scaleType != mScaleType) {
            setScaleType(scaleType);
        }
        resetZoom();
        scaleImage(scale, viewWidth / 2, viewHeight / 2, true);
        matrix.getValues(m);
        m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f));
        m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f));
        matrix.setValues(m);
        fixTrans();
        setImageMatrix(matrix);
    }

    /**
     * Set zoom parameters equal to another TouchImageView. Including scale, position,
     * and ScaleType.
     *
     * @param
     */
    public void setZoom(TouchImageView img) {
        PointF center = img.getScrollPosition();
        setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType());
    }

    /**
     * Return the point at the center of the zoomed image. The PointF coordinates range
     * in value between 0 and 1 and the focus point is denoted as a fraction from the left
     * and top of the view. For example, the top left corner of the image would be (0, 0).
     * And the bottom right corner would be (1, 1).
     *
     * @return PointF representing the scroll position of the zoomed image.
     */
    public PointF getScrollPosition() {
        Drawable drawable = getDrawable();
        if (drawable == null) {
            return null;
        }
        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();

        PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true);
        point.x /= drawableWidth;
        point.y /= drawableHeight;
        return point;
    }

    /**
     * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the
     * left and top of the view. The focus points can range in value between 0 and 1.
     *
     * @param focusX
     * @param focusY
     */
    public void setScrollPosition(float focusX, float focusY) {
        setZoom(normalizedScale, focusX, focusY);
    }

    /**
     * Performs boundary checking and fixes the image matrix if it
     * is out of bounds.
     */
    private void fixTrans() {
        matrix.getValues(m);
        float transX = m[Matrix.MTRANS_X];
        float transY = m[Matrix.MTRANS_Y];

        float fixTransX = getFixTrans(transX, viewWidth, getImageWidth());
        float fixTransY = getFixTrans(transY, viewHeight, getImageHeight());

        if (fixTransX != 0 || fixTransY != 0) {
            matrix.postTranslate(fixTransX, fixTransY);
        }
    }

    /**
     * When transitioning from zooming from focus to zoom from center (or vice versa)
     * the image can become unaligned within the view. This is apparent when zooming
     * quickly. When the content size is less than the view size, the content will often
     * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and
     * then makes sure the image is centered correctly within the view.
     */
    private void fixScaleTrans() {
        fixTrans();
        matrix.getValues(m);
        if (getImageWidth() < viewWidth) {
            m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2;
        }

        if (getImageHeight() < viewHeight) {
            m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2;
        }
        matrix.setValues(m);
    }

    private float getFixTrans(float trans, float viewSize, float contentSize) {
        float minTrans, maxTrans;

        if (contentSize <= viewSize) {
            minTrans = 0;
            maxTrans = viewSize - contentSize;

        } else {
            minTrans = viewSize - contentSize;
            maxTrans = 0;
        }

        if (trans < minTrans)
            return -trans + minTrans;
        if (trans > maxTrans)
            return -trans + maxTrans;
        return 0;
    }

    private float getFixDragTrans(float delta, float viewSize, float contentSize) {
        if (contentSize <= viewSize) {
            return 0;
        }
        return delta;
    }

    private float getImageWidth() {
        return matchViewWidth * normalizedScale;
    }

    private float getImageHeight() {
        return matchViewHeight * normalizedScale;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Drawable drawable = getDrawable();
        if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) {
            setMeasuredDimension(0, 0);
            return;
        }

        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        viewWidth = setViewSize(widthMode, widthSize, drawableWidth);
        viewHeight = setViewSize(heightMode, heightSize, drawableHeight);

        //
        // Set view dimensions
        //
        setMeasuredDimension(viewWidth, viewHeight);

        //
        // Fit content within view
        //
        fitImageToView();
    }

    /**
     * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise,
     * it is made to fit the screen according to the dimensions of the previous image matrix. This
     * allows the image to maintain its zoom after rotation.
     */
    private void fitImageToView() {
        Drawable drawable = getDrawable();
        if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) {
            return;
        }
        if (matrix == null || prevMatrix == null) {
            return;
        }

        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();

        //
        // Scale image for view
        //
        float scaleX = (float) viewWidth / drawableWidth;
        float scaleY = (float) viewHeight / drawableHeight;

        switch (mScaleType) {
            case CENTER:
                scaleX = scaleY = 1;
                break;

            case CENTER_CROP:
                scaleX = scaleY = Math.max(scaleX, scaleY);
                break;

            case CENTER_INSIDE:
                scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY));

            case FIT_CENTER:
                scaleX = scaleY = Math.min(scaleX, scaleY);
                break;

            case FIT_XY:
                break;

            default:
                //
                // FIT_START and FIT_END not supported
                //
                throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END");

        }

        //
        // Center the image
        //
        float redundantXSpace = viewWidth - (scaleX * drawableWidth);
        float redundantYSpace = viewHeight - (scaleY * drawableHeight);
        matchViewWidth = viewWidth - redundantXSpace;
        matchViewHeight = viewHeight - redundantYSpace;
        if (!isZoomed() && !imageRenderedAtLeastOnce) {
            //
            // Stretch and center image to fit view
            //
            matrix.setScale(scaleX, scaleY);
            matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);
            normalizedScale = 1;

        } else {
            //
            // These values should never be 0 or we will set viewWidth and viewHeight
            // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues
            // to set them equal to the current values.
            //
            if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) {
                savePreviousImageValues();
            }

            prevMatrix.getValues(m);

            //
            // Rescale Matrix after rotation
            //
            m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale;
            m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale;

            //
            // TransX and TransY from previous matrix
            //
            float transX = m[Matrix.MTRANS_X];
            float transY = m[Matrix.MTRANS_Y];

            //
            // Width
            //
            float prevActualWidth = prevMatchViewWidth * normalizedScale;
            float actualWidth = getImageWidth();
            translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth);

            //
            // Height
            //
            float prevActualHeight = prevMatchViewHeight * normalizedScale;
            float actualHeight = getImageHeight();
            translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight);

            //
            // Set the matrix to the adjusted scale and translate values.
            //
            matrix.setValues(m);
        }
        fixTrans();
        setImageMatrix(matrix);
    }

    /**
     * Set view dimensions based on layout params
     *
     * @param mode
     * @param size
     * @param drawableWidth
     * @return
     */
    private int setViewSize(int mode, int size, int drawableWidth) {
        int viewSize;
        switch (mode) {
            case MeasureSpec.EXACTLY:
                viewSize = size;
                break;

            case MeasureSpec.AT_MOST:
                viewSize = Math.min(drawableWidth, size);
                break;

            case MeasureSpec.UNSPECIFIED:
                viewSize = drawableWidth;
                break;

            default:
                viewSize = size;
                break;
        }
        return viewSize;
    }


    ........

参考:https ://gist.github.com/myinnos/e14b750be9503a2b2611f5a5a74a9e6c

由于 SOF 限制为 30000 个字符,它可能会错过最后一行代码,请从上面的链接中学习。

在您的 XML 中使用它,例如:

<com.mypackagename.TouchImageView
            android:id="@+id/productImage"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitCenter"/>
于 2019-01-13T18:58:24.613 回答
3

我知道这已经晚了,但我没有在 kotlin 中看到任何以前的答案

class ImageZoomingClass : AppCompatImageView, View.OnTouchListener,
GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
//shared constructing
private var mContext: Context? = null
private var mScaleDetector: ScaleGestureDetector? = null
private var mGestureDetector: GestureDetector? = null
var mMatrix: Matrix? = null
private var mMatrixValues: FloatArray? = null
var mode = NONE

// all possible state
companion object {
    const val NONE = 0
    const val DRAG = 1
    const val ZOOM = 2
}

// Scales
var mSaveScale = 1f
var mMinScale = 1f
var mMaxScale = 4f

// view dimensions
var origWidth = 0f
var origHeight = 0f
var viewWidth = 0
var viewHeight = 0
private var mLast = PointF()
private var mStart = PointF()

constructor(context: Context) : super(context) {
    sharedConstructing(context)
}

constructor(context: Context, @Nullable attrs: AttributeSet?) : super(context, attrs) {
    sharedConstructing(context)
}

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
    context!!,
    attrs,
    defStyleAttr
)

private fun sharedConstructing(context: Context) {
    super.setClickable(true)
    mContext = context
    mScaleDetector = ScaleGestureDetector(context, ScaleListener())
    mMatrix = Matrix()
    mMatrixValues = FloatArray(9)
    imageMatrix = mMatrix
    scaleType = ScaleType.MATRIX
    mGestureDetector = GestureDetector(context, this)
    setOnTouchListener(this)
}

private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
        mode = ZOOM
        return true
    }

    override fun onScale(detector: ScaleGestureDetector): Boolean {
        var mScaleFactor = detector.scaleFactor
        val prevScale = mSaveScale
        mSaveScale *= mScaleFactor
        if (mSaveScale > mMaxScale) {
            mSaveScale = mMaxScale
            mScaleFactor = mMaxScale / prevScale
        } else if (mSaveScale < mMinScale) {
            mSaveScale = mMinScale
            mScaleFactor = mMinScale / prevScale
        }
        if (origWidth * mSaveScale <= viewWidth
            || origHeight * mSaveScale <= viewHeight
        ) {
            mMatrix!!.postScale(
                mScaleFactor, mScaleFactor, viewWidth / 2.toFloat(),
                viewHeight / 2.toFloat()
            )
        } else {
            mMatrix!!.postScale(
                mScaleFactor, mScaleFactor,
                detector.focusX, detector.focusY
            )
        }
        fixTranslation()
        return true
    }
}

private fun fitToScreen() {
    mSaveScale = 1f
    val scale: Float
    val drawable = drawable
    if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) return
    val imageWidth = drawable.intrinsicWidth
    val imageHeight = drawable.intrinsicHeight
    val scaleX = viewWidth.toFloat() / imageWidth.toFloat()
    val scaleY = viewHeight.toFloat() / imageHeight.toFloat()
    scale = scaleX.coerceAtMost(scaleY)
    mMatrix!!.setScale(scale, scale)

    // Center the image
    var redundantYSpace = (viewHeight.toFloat()
            - scale * imageHeight.toFloat())
    var redundantXSpace = (viewWidth.toFloat()
            - scale * imageWidth.toFloat())
    redundantYSpace /= 2.toFloat()
    redundantXSpace /= 2.toFloat()
    mMatrix!!.postTranslate(redundantXSpace, redundantYSpace)
    origWidth = viewWidth - 2 * redundantXSpace
    origHeight = viewHeight - 2 * redundantYSpace
    imageMatrix = mMatrix
}

fun fixTranslation() {
    mMatrix!!.getValues(mMatrixValues) //put matrix values into a float array so we can analyze
    val transX =
        mMatrixValues!![Matrix.MTRANS_X] //get the most recent translation in x direction
    val transY =
        mMatrixValues!![Matrix.MTRANS_Y] //get the most recent translation in y direction
    val fixTransX = getFixTranslation(transX, viewWidth.toFloat(), origWidth * mSaveScale)
    val fixTransY = getFixTranslation(transY, viewHeight.toFloat(), origHeight * mSaveScale)
    if (fixTransX != 0f || fixTransY != 0f) mMatrix!!.postTranslate(fixTransX, fixTransY)
}

private fun getFixTranslation(trans: Float, viewSize: Float, contentSize: Float): Float {
    val minTrans: Float
    val maxTrans: Float
    if (contentSize <= viewSize) { // case: NOT ZOOMED
        minTrans = 0f
        maxTrans = viewSize - contentSize
    } else { //CASE: ZOOMED
        minTrans = viewSize - contentSize
        maxTrans = 0f
    }
    if (trans < minTrans) { // negative x or y translation (down or to the right)
        return -trans + minTrans
    }
    if (trans > maxTrans) { // positive x or y translation (up or to the left)
        return -trans + maxTrans
    }
    return 0F
}

private fun getFixDragTrans(delta: Float, viewSize: Float, contentSize: Float): Float {
    return if (contentSize <= viewSize) {
        0F
    } else delta
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    viewWidth = MeasureSpec.getSize(widthMeasureSpec)
    viewHeight = MeasureSpec.getSize(heightMeasureSpec)
    if (mSaveScale == 1f) {

        // Fit to screen.
        fitToScreen()
    }
}

//OnTouch
override fun onTouch(view: View?, event: MotionEvent): Boolean {
    mScaleDetector!!.onTouchEvent(event)
    mGestureDetector!!.onTouchEvent(event)
    val currentPoint = PointF(event.x, event.y)
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            mLast.set(currentPoint)
            mStart.set(mLast)
            mode = DRAG
        }
        MotionEvent.ACTION_MOVE -> if (mode == DRAG) {
            val dx = currentPoint.x - mLast.x
            val dy = currentPoint.y - mLast.y
            val fixTransX = getFixDragTrans(dx, viewWidth.toFloat(), origWidth * mSaveScale)
            val fixTransY = getFixDragTrans(dy, viewHeight.toFloat(), origHeight * mSaveScale)
            mMatrix!!.postTranslate(fixTransX, fixTransY)
            fixTranslation()
            mLast[currentPoint.x] = currentPoint.y
        }
        MotionEvent.ACTION_POINTER_UP -> mode = NONE
    }
    invalidate()
    imageMatrix = mMatrix
    return false
}

//GestureListener
override fun onDown(motionEvent: MotionEvent): Boolean {
    return false
}

override fun onShowPress(motionEvent: MotionEvent) {}
override fun onSingleTapUp(motionEvent: MotionEvent): Boolean {
    return false
}

override fun onScroll(
    motionEvent: MotionEvent,
    motionEvent1: MotionEvent,
    v: Float,
    v1: Float
): Boolean {
    return false
}

override fun onLongPress(motionEvent: MotionEvent) {}
override fun onFling(
    motionEvent: MotionEvent,
    motionEvent1: MotionEvent,
    v: Float,
    v1: Float
): Boolean {
    return false
}

//onDoubleTap
override fun onSingleTapConfirmed(motionEvent: MotionEvent): Boolean {
    return false
}

// zoom the image
// double tap again to return to normal
override fun onDoubleTap(e: MotionEvent): Boolean {
    // Double tap is detected
    Log.i("MAIN_TAG", "Double tap detected")
    val origScale = mSaveScale
    val mScaleFactor: Float
    if (mSaveScale == mMaxScale) {
        mSaveScale = mMinScale
        mScaleFactor = mMinScale / origScale
    } else {
        mSaveScale = mMaxScale
        mScaleFactor = mMaxScale / origScale
    }
    mMatrix?.postScale(
        mScaleFactor, mScaleFactor, (viewWidth / 2).toFloat(), (
                viewHeight / 2).toFloat()
    )
    fixTranslation()
    return false
}

override fun onDoubleTapEvent(motionEvent: MotionEvent): Boolean {
    return false
}

}

你的 xml 布局应该是这样的

<com.example.imagezooming.ImageZoomingClass
    android:id="@+id/largeImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="2dp"
    android:layout_marginEnd="2dp"
    android:clickable="true"
    android:focusable="true"
    android:src="@drawable/theImageYouWant"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

快乐编码!!!

于 2021-01-16T13:17:01.193 回答
1

到目前为止,我已经使用了近 15 个以上的库或 JAVA 类来实现 ImageView 上的捏合和双击缩放功能。但就功能和内存管理而言, PhotoView是我使用和实现的最好的。去检查一下。如果它对您有帮助,请点赞,以便其他人可以使用它。

于 2021-05-31T09:14:04.780 回答
-1

带有单点监听器的可缩放图像视图

import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import it.sephiroth.android.library.imagezoom.ImageViewTouch;

public class MyZoomableImageViewTouch extends ImageViewTouch
{

    static final float SCROLL_DELTA_THRESHOLD = 1.0f;

    public MyZoomableImageViewTouch(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init();
    }

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

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

    private void init() {
        View.OnTouchListener listener = new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                 if (getScale() > 1f) {
                 getParent().requestDisallowInterceptTouchEvent(true);
                 } else {
                 getParent().requestDisallowInterceptTouchEvent(false);
                 }
                 return false;
            }
           };
           setOnTouchListener(listener);
           setDisplayType(DisplayType.FIT_TO_SCREEN);
     }

     @Override
    protected float onDoubleTapPost(float scale, float maxZoom) {
        if (scale != 1f) {
           mDoubleTapDirection = 1;
           return 1f;
       }
       if (mDoubleTapDirection == 1) {
            mDoubleTapDirection = -1;
          if ((scale + (mScaleFactor * 2)) <= maxZoom) {
              return scale + mScaleFactor;
          }   else {
              mDoubleTapDirection = -1;
              return maxZoom;
          }
        } else {
          mDoubleTapDirection = 1;
          return 1f;
       }
  }

    @Override
    public boolean canScroll(int direction) {
       RectF bitmapRect = getBitmapRect();
       updateRect(bitmapRect, mScrollRect);
       Rect imageViewRect = new Rect();
       getGlobalVisibleRect(imageViewRect);

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

       if (Math.abs(bitmapRect.right - imageViewRect.right) < SCROLL_DELTA_THRESHOLD) {
           if (direction < 0) {
                return false;
           }
       }

       if (Math.abs(bitmapRect.left - mScrollRect.left) <  SCROLL_DELTA_THRESHOLD) {
           if (direction > 0) {
               return false;
           }
       }
       return true;
   }

   @Override
   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
   {
        if (getScale() == 1f) return false;
        if (distanceX != 0 && !canScroll((int) -distanceX)) {
             getParent().requestDisallowInterceptTouchEvent(false);
             return false;
        } else {
             getParent().requestDisallowInterceptTouchEvent(true);
             mUserScaled = true;
             scrollBy(-distanceX, -distanceY);
            invalidate();
            return true;
       }
   }
}

使用此方法进行单击

    imageView.setSingleTapListener(new ImageViewTouch.OnImageViewTouchSingleTapListener()
    {
        @Override
        public void onSingleTapConfirmed()
        {
        }
     }
于 2017-03-24T12:05:47.537 回答