73

我正在使用Make Sense of Multitouch中的代码示例 来缩放图像视图。在 ScaleListener 上,我添加ScaleGestureDetector.getFocusX() and getFocusY()了内容以围绕手势的焦点进行缩放。它工作正常。

问题是,在第一次多点触摸时,整个图像绘制位置正在更改为当前触摸点并从那里缩放。你能帮我解决这个问题吗?

这是我的 TouchImageView 代码示例。

public class TouchImageViewSample extends ImageView {

private Paint borderPaint = null;
private Paint backgroundPaint = null;

private float mPosX = 0f;
private float mPosY = 0f;

private float mLastTouchX;
private float mLastTouchY;
private static final int INVALID_POINTER_ID = -1;
private static final String LOG_TAG = "TouchImageView";

// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;

public TouchImageViewSample(Context context) {
    this(context, null, 0);
}

public TouchImageViewSample(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

// Existing code ...
public TouchImageViewSample(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // Create our ScaleGestureDetector
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

    borderPaint = new Paint();
    borderPaint.setARGB(255, 255, 128, 0);
    borderPaint.setStyle(Paint.Style.STROKE);
    borderPaint.setStrokeWidth(4);

    backgroundPaint = new Paint();
    backgroundPaint.setARGB(32, 255, 255, 255);
    backgroundPaint.setStyle(Paint.Style.FILL);

}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);

    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN: {
        final float x = ev.getX();
        final float y = ev.getY();

        mLastTouchX = x;
        mLastTouchY = y;

        mActivePointerId = ev.getPointerId(0);
        break;
    }

    case MotionEvent.ACTION_MOVE: {
        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
        final float x = ev.getX(pointerIndex);
        final float y = ev.getY(pointerIndex);

        // Only move if the ScaleGestureDetector isn't processing a gesture.
        if (!mScaleDetector.isInProgress()) {
            final float dx = x - mLastTouchX;
            final float dy = y - mLastTouchY;

            mPosX += dx;
            mPosY += dy;

            invalidate();
        }

        mLastTouchX = x;
        mLastTouchY = y;
        break;
    }

    case MotionEvent.ACTION_UP: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_CANCEL: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_POINTER_UP: {
        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int pointerId = ev.getPointerId(pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastTouchX = ev.getX(newPointerIndex);
            mLastTouchY = ev.getY(newPointerIndex);
            mActivePointerId = ev.getPointerId(newPointerIndex);
        }
        break;
    }
    }

    return true;
}

/*
 * (non-Javadoc)
 * 
 * @see android.view.View#draw(android.graphics.Canvas)
 */
@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
    canvas.drawRect(0, 0, getWidth() - 1, getHeight() - 1, borderPaint);
}

@Override
public void onDraw(Canvas canvas) {
    canvas.drawRect(0, 0, getWidth() - 1, getHeight() - 1, backgroundPaint);
    if (this.getDrawable() != null) {
        canvas.save();
        canvas.translate(mPosX, mPosY);

        Matrix matrix = new Matrix();
        matrix.postScale(mScaleFactor, mScaleFactor, pivotPointX,
                pivotPointY);
        // canvas.setMatrix(matrix);

        canvas.drawBitmap(
                ((BitmapDrawable) this.getDrawable()).getBitmap(), matrix,
                null);

        // this.getDrawable().draw(canvas);
        canvas.restore();
    }
}

/*
 * (non-Javadoc)
 * 
 * @see
 * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable
 * )
 */
@Override
public void setImageDrawable(Drawable drawable) {
    // Constrain to given size but keep aspect ratio
    int width = drawable.getIntrinsicWidth();
    int height = drawable.getIntrinsicHeight();
    mLastTouchX = mPosX = 0;
    mLastTouchY = mPosY = 0;

    int borderWidth = (int) borderPaint.getStrokeWidth();
    mScaleFactor = Math.min(((float) getLayoutParams().width - borderWidth)
            / width, ((float) getLayoutParams().height - borderWidth)
            / height);
    pivotPointX = (((float) getLayoutParams().width - borderWidth) - (int) (width * mScaleFactor)) / 2;
    pivotPointY = (((float) getLayoutParams().height - borderWidth) - (int) (height * mScaleFactor)) / 2;
    super.setImageDrawable(drawable);
}

float pivotPointX = 0f;
float pivotPointY = 0f;

private class ScaleListener extends
        ScaleGestureDetector.SimpleOnScaleGestureListener {

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();

        pivotPointX = detector.getFocusX();
        pivotPointY = detector.getFocusY();

        Log.d(LOG_TAG, "mScaleFactor " + mScaleFactor);
        Log.d(LOG_TAG, "pivotPointY " + pivotPointY + ", pivotPointX= "
                + pivotPointX);
        mScaleFactor = Math.max(0.05f, mScaleFactor);

        invalidate();
        return true;
    }
}

以及我如何在我的活动中使用它。

ImageView imageView = (ImageView) findViewById(R.id.imgView);

int hMargin = (int) (displayMetrics.widthPixels * .10);
int vMargin = (int) (displayMetrics.heightPixels * .10);

RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(displayMetrics.widthPixels - (hMargin * 2), (int)(displayMetrics.heightPixels - btnCamera.getHeight()) - (vMargin * 2));
params.leftMargin = hMargin;
params.topMargin =  vMargin;
imageView.setLayoutParams(params);
imageView.setImageDrawable(drawable);
4

8 回答 8

86
@Override
public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub

    ImageView view = (ImageView) v;
    dumpEvent(event);

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

    view.setImageMatrix(matrix);
    return true;
}

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("]");
    Log.d(TAG, sb.toString());
}

/** Determine the space between the first two fingers */
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);
}

/** Calculate the mid point of the first two fingers */
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);
}

并且不要忘记将scaleType属性设置为ImageView标记矩阵,例如:

<ImageView
    android:id="@+id/imageEnhance"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginBottom="15dp"
    android:layout_marginLeft="15dp"
    android:layout_marginRight="15dp"
    android:layout_marginTop="15dp"
    android:background="@drawable/enhanceimageframe"
    android:scaleType="matrix" >
</ImageView>

使用的变量是:

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

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

// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
String savedItemClicked;
于 2012-05-17T08:44:45.383 回答
48

你可以使用这个类:TouchImageView

于 2013-09-16T07:18:10.703 回答
23

使用ScaleGestureDetector

在学习新概念时,我不喜欢使用库或代码转储。我在这里和如何通过捏合调整图像大小的文档中找到了很好的描述。这个答案是一个稍微修改的摘要。稍后您可能希望添加更多功能,但它会帮助您入门。

动画 gif:缩放图像示例

布局

只是使用应用程序徽标,ImageView因为它已经可用。不过,您可以将其替换为您喜欢的任何图像。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher"
        android:layout_centerInParent="true"/>

</RelativeLayout>

活动

我们ScaleGestureDetector在活动上使用 a 来监听触摸事件。当检测到缩放(即捏合)手势时,缩放因子用于调整ImageView.

public class MainActivity extends AppCompatActivity {

    private ScaleGestureDetector mScaleGestureDetector;
    private float mScaleFactor = 1.0f;
    private ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // initialize the view and the gesture detector
        mImageView = findViewById(R.id.imageView);
        mScaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
    }

    // this redirects all touch events in the activity to the gesture detector
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mScaleGestureDetector.onTouchEvent(event);
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        // when a scale gesture is detected, use it to resize the image
        @Override
        public boolean onScale(ScaleGestureDetector scaleGestureDetector){
            mScaleFactor *= scaleGestureDetector.getScaleFactor();
            mImageView.setScaleX(mScaleFactor);
            mImageView.setScaleY(mScaleFactor);
            return true;
        }
    }
}

笔记

  • 尽管 Activity 在上面的示例中具有手势检测器,但它也可以在图像视图本身上设置。
  • 您可以使用类似的东西来限制缩放的大小

    mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
    
  • 再次感谢在 Android 中使用多点触控手势进行捏合缩放

  • 文档
  • 在模拟器中使用Ctrl+ 鼠标拖动来模拟捏合手势。

继续

您可能想要做其他事情,例如平移和缩放到某个焦点。您可以自己开发这些东西,但如果您想使用预制的自定义视图,请将其复制TouchImageView.java到您的项目中并像普通ImageView. 它对我来说效果很好,我只遇到了一个错误。我计划进一步编辑代码以删除警告和我不需要的部分。你也可以做到的。

于 2018-09-18T02:41:31.687 回答
16

我用捏缩放制作了自己的自定义图像视图。Chirag Raval的代码没有限制/边界,因此用户可以将图像拖出屏幕。这将解决它。

这是 CustomImageView 类:

    public class CustomImageVIew extends ImageView implements OnTouchListener {


    private Matrix matrix = new Matrix();
    private Matrix savedMatrix = new Matrix();

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

    private int mode = NONE;

    private PointF mStartPoint = new PointF();
    private PointF mMiddlePoint = new PointF();
    private Point mBitmapMiddlePoint = new Point();

    private float oldDist = 1f;
    private float matrixValues[] = {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f};
    private float scale;
    private float oldEventX = 0;
    private float oldEventY = 0;
    private float oldStartPointX = 0;
    private float oldStartPointY = 0;
    private int mViewWidth = -1;
    private int mViewHeight = -1;
    private int mBitmapWidth = -1;
    private int mBitmapHeight = -1;
    private boolean mDraggable = false;


    public CustomImageVIew(Context context) {
        this(context, null, 0);
    }

    public CustomImageVIew(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomImageVIew(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.setOnTouchListener(this);
    }

    @Override
    public void onSizeChanged (int w, int h, int oldw, int oldh){
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;
    }

    public void setBitmap(Bitmap bitmap){
        if(bitmap != null){
            setImageBitmap(bitmap);

            mBitmapWidth = bitmap.getWidth();
            mBitmapHeight = bitmap.getHeight();
            mBitmapMiddlePoint.x = (mViewWidth / 2) - (mBitmapWidth /  2);
            mBitmapMiddlePoint.y = (mViewHeight / 2) - (mBitmapHeight / 2);

            matrix.postTranslate(mBitmapMiddlePoint.x, mBitmapMiddlePoint.y);
            this.setImageMatrix(matrix);
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event){
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            savedMatrix.set(matrix);
            mStartPoint.set(event.getX(), event.getY());
            mode = DRAG;
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            oldDist = spacing(event);
            if(oldDist > 10f){
                savedMatrix.set(matrix);
                midPoint(mMiddlePoint, event);
                mode = ZOOM;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
            mode = NONE;
            break;
        case MotionEvent.ACTION_MOVE:
            if(mode == DRAG){
                drag(event);
            } else if(mode == ZOOM){
                zoom(event);
            } 
            break;
        }

        return true;
    }



   public void drag(MotionEvent event){
       matrix.getValues(matrixValues);

       float left = matrixValues[2];
       float top = matrixValues[5];
       float bottom = (top + (matrixValues[0] * mBitmapHeight)) - mViewHeight;
       float right = (left + (matrixValues[0] * mBitmapWidth)) -mViewWidth;

       float eventX = event.getX();
       float eventY = event.getY();
       float spacingX = eventX - mStartPoint.x;
       float spacingY = eventY - mStartPoint.y;
       float newPositionLeft = (left  < 0 ? spacingX : spacingX * -1) + left;
       float newPositionRight = (spacingX) + right;
       float newPositionTop = (top  < 0 ? spacingY : spacingY * -1) + top;
       float newPositionBottom = (spacingY) + bottom;
       boolean x = true;
       boolean y = true;

       if(newPositionRight < 0.0f || newPositionLeft > 0.0f){
           if(newPositionRight < 0.0f && newPositionLeft > 0.0f){
               x = false;
           } else{
               eventX = oldEventX;
               mStartPoint.x = oldStartPointX;
           }
       }
       if(newPositionBottom < 0.0f || newPositionTop > 0.0f){
           if(newPositionBottom < 0.0f && newPositionTop > 0.0f){
               y = false;
           } else{
               eventY = oldEventY;
               mStartPoint.y = oldStartPointY;
           }
       }

       if(mDraggable){
           matrix.set(savedMatrix);
           matrix.postTranslate(x? eventX - mStartPoint.x : 0, y? eventY - mStartPoint.y : 0);
           this.setImageMatrix(matrix);
           if(x)oldEventX = eventX;
           if(y)oldEventY = eventY;
           if(x)oldStartPointX = mStartPoint.x;
           if(y)oldStartPointY = mStartPoint.y;
       }

   }

   public void zoom(MotionEvent event){
       matrix.getValues(matrixValues);

       float newDist = spacing(event);
       float bitmapWidth = matrixValues[0] * mBitmapWidth;
       float bimtapHeight = matrixValues[0] * mBitmapHeight;
       boolean in = newDist > oldDist;

       if(!in && matrixValues[0] < 1){
           return;
       }
       if(bitmapWidth > mViewWidth || bimtapHeight > mViewHeight){
           mDraggable = true;
       } else{
           mDraggable = false;
       }

       float midX = (mViewWidth / 2);
       float midY = (mViewHeight / 2);

       matrix.set(savedMatrix);
       scale = newDist / oldDist;
       matrix.postScale(scale, scale, bitmapWidth > mViewWidth ? mMiddlePoint.x : midX, bimtapHeight > mViewHeight ? mMiddlePoint.y : midY); 

       this.setImageMatrix(matrix);


   }





    /** Determine the space between the first two fingers */
    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);

        return (float)Math.sqrt(x * x + y * y);
    }

    /** Calculate the mid point of the first two fingers */
    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);
    }


}

这是您可以在活动中使用它的方式:

CustomImageVIew mImageView = (CustomImageVIew)findViewById(R.id.customImageVIew1);
mImage.setBitmap(your bitmap);

和布局:

<your.package.name.CustomImageVIew
        android:id="@+id/customImageVIew1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_marginBottom="15dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="15dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" 
        android:scaleType="matrix"/> // important
于 2013-06-01T23:05:35.250 回答
16

在 build.gradle 中添加以下行:

compile 'com.commit451:PhotoView:1.2.4'

或者

compile 'com.github.chrisbanes:PhotoView:1.3.0'

在 Java 文件中:

PhotoViewAttacher photoAttacher;
photoAttacher= new PhotoViewAttacher(Your_Image_View);
photoAttacher.update();
于 2017-05-16T05:52:16.490 回答
1

Kotlin 中的自定义缩放视图

 import android.content.Context
 import android.graphics.Matrix
 import android.graphics.PointF
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
 import android.view.ScaleGestureDetector
 import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
 import androidx.appcompat.widget.AppCompatImageView

 class ZoomImageview : AppCompatImageView {
var matri: Matrix? = null
var mode = NONE

// Remember some things for zooming
var last = PointF()
var start = PointF()
var minScale = 1f
var maxScale = 3f
lateinit var m: FloatArray
var viewWidth = 0
var viewHeight = 0
var saveScale = 1f
protected var origWidth = 0f
protected var origHeight = 0f
var oldMeasuredWidth = 0
var oldMeasuredHeight = 0
var mScaleDetector: ScaleGestureDetector? = null
var contex: Context? = null

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

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

private fun sharedConstructing(context: Context) {
    super.setClickable(true)
    this.contex= context
    mScaleDetector = ScaleGestureDetector(context, ScaleListener())
    matri = Matrix()
    m = FloatArray(9)
    imageMatrix = matri
    scaleType = ScaleType.MATRIX
    setOnTouchListener { v, event ->
        mScaleDetector!!.onTouchEvent(event)
        val curr = PointF(event.x, event.y)
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                last.set(curr)
                start.set(last)
                mode = DRAG
            }
            MotionEvent.ACTION_MOVE -> if (mode == DRAG) {
                val deltaX = curr.x - last.x
                val deltaY = curr.y - last.y
                val fixTransX = getFixDragTrans(deltaX, viewWidth.toFloat(), origWidth * saveScale)
                val fixTransY = getFixDragTrans(deltaY, viewHeight.toFloat(), origHeight * saveScale)
                matri!!.postTranslate(fixTransX, fixTransY)
                fixTrans()
                last[curr.x] = curr.y
            }
            MotionEvent.ACTION_UP -> {
                mode = NONE
                val xDiff = Math.abs(curr.x - start.x).toInt()
                val yDiff = Math.abs(curr.y - start.y).toInt()
                if (xDiff < CLICK && yDiff < CLICK) performClick()
            }
            MotionEvent.ACTION_POINTER_UP -> mode = NONE
        }
        imageMatrix = matri
        invalidate()
        true // indicate event was handled
    }
}

fun setMaxZoom(x: Float) {
    maxScale = x
}

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

    override fun onScale(detector: ScaleGestureDetector): Boolean {
        var mScaleFactor = detector.scaleFactor
        val 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) matri!!.postScale(mScaleFactor, mScaleFactor, viewWidth / 2.toFloat(), viewHeight / 2.toFloat()) else matri!!.postScale(mScaleFactor, mScaleFactor, detector.focusX, detector.focusY)
        fixTrans()
        return true
    }
}

fun fixTrans() {
    matri!!.getValues(m)
    val transX = m[Matrix.MTRANS_X]
    val transY = m[Matrix.MTRANS_Y]
    val fixTransX = getFixTrans(transX, viewWidth.toFloat(), origWidth * saveScale)
    val fixTransY = getFixTrans(transY, viewHeight.toFloat(), origHeight * saveScale)
    if (fixTransX != 0f || fixTransY != 0f) matri!!.postTranslate(fixTransX, fixTransY)
}

fun getFixTrans(trans: Float, viewSize: Float, contentSize: Float): Float {
    val minTrans: Float
    val maxTrans: Float
    if (contentSize <= viewSize) {
        minTrans = 0f
        maxTrans = viewSize - contentSize
    } else {
        minTrans = viewSize - contentSize
        maxTrans = 0f
    }
    if (trans < minTrans) return -trans + minTrans
    if (trans > maxTrans) return -trans + maxTrans
    return 0f
}

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

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    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 == 1f) {
        //Fit to screen.
        val scale: Float
        val drawable = drawable
        if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) return
        val bmWidth = drawable.intrinsicWidth
        val bmHeight = drawable.intrinsicHeight
        Log.d("bmSize", "bmWidth: $bmWidth bmHeight : $bmHeight")
        val scaleX = viewWidth.toFloat() / bmWidth.toFloat()
        val scaleY = viewHeight.toFloat() / bmHeight.toFloat()
        scale = Math.min(scaleX, scaleY)
        matri!!.setScale(scale, scale)
        // Center the image
        var redundantYSpace = viewHeight.toFloat() - scale * bmHeight.toFloat()
        var redundantXSpace = viewWidth.toFloat() - scale * bmWidth.toFloat()
        redundantYSpace /= 2.toFloat()
        redundantXSpace /= 2.toFloat()
        matri!!.postTranslate(redundantXSpace, redundantYSpace)
        origWidth = viewWidth - 2 * redundantXSpace
        origHeight = viewHeight - 2 * redundantYSpace
        imageMatrix = matri
    }
    fixTrans()
}

companion object {
    // We can be in one of these 3 states
    const val NONE = 0
    const val DRAG = 1
    const val ZOOM = 2
    const val CLICK = 3
}
 }
于 2020-07-27T18:35:27.707 回答
1

TouchImageViewSample类中,缩放发生在枢轴点周围。属于轴心点的像素不受图像缩放的影响。当您更改轴心点时,视图会重新绘制,并且会围绕新的轴心点进行缩放。这会改变前一个枢轴点的位置,您会看到这一点,因为每次您触碰图像时图像都会发生变化。您必须通过平移图像来补偿这种移动误差。看看我的ZoomGestureDetector.updatePivotPoint()方法是如何做到的。

缩放手势检测器

我创建了自定义缩放手势检测器类。它可以同时进行缩放、平移和旋转。它还支持投掷动画。

import android.graphics.Canvas
import android.graphics.Matrix
import android.view.MotionEvent
import android.view.VelocityTracker
import androidx.core.math.MathUtils
import androidx.dynamicanimation.animation.FlingAnimation
import androidx.dynamicanimation.animation.FloatValueHolder
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan2

class ZoomGestureDetector(private val listener: Listener) {

    companion object {
        const val MIN_SCALE = 0.01f
        const val MAX_SCALE = 100f
        const val MIN_FLING_VELOCITY = 50f
        const val MAX_FLING_VELOCITY = 8000f
    }

    // public
    var isZoomEnabled: Boolean = true
    var isScaleEnabled: Boolean = true
    var isRotationEnabled: Boolean = true
    var isTranslationEnabled: Boolean = true
    var isFlingEnabled: Boolean = true

    // local
    private val mDrawMatrix: Matrix = Matrix()
    private val mTouchMatrix: Matrix = Matrix()
    private val mPointerMap: HashMap<Int, Position> = HashMap()
    private val mTouchPoint: FloatArray = floatArrayOf(0f, 0f)
    private val mPivotPoint: FloatArray = floatArrayOf(0f, 0f)

    // transformations
    private var mTranslationX: Float = 0f
    private var mTranslationY: Float = 0f
    private var mScaling: Float = 1f
    private var mPivotX: Float = 0f
    private var mPivotY: Float = 0f
    private var mRotation: Float = 0f

    // previous values
    private var mPreviousFocusX: Float = 0f
    private var mPreviousFocusY: Float = 0f
    private var mPreviousTouchSpan: Float = 1f

    // fling related
    private var mVelocityTracker: VelocityTracker? = null
    private var mFlingAnimX: FlingAnimation? = null
    private var mFlingAnimY: FlingAnimation? = null

    fun updateTouchLocation(event: MotionEvent) {
        mTouchPoint[0] = event.x
        mTouchPoint[1] = event.y
        mTouchMatrix.mapPoints(mTouchPoint)
        event.setLocation(mTouchPoint[0], mTouchPoint[1])
    }

    fun updateCanvasMatrix(canvas: Canvas) {
        canvas.setMatrix(mDrawMatrix)
    }

    fun onTouchEvent(event: MotionEvent): Boolean {
        if (isZoomEnabled) {
            // update velocity tracker
            if (isFlingEnabled) {
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain()
                }
                mVelocityTracker?.addMovement(event)
            }
            // handle touch events
            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    // update focus point
                    mPreviousFocusX = event.x
                    mPreviousFocusY = event.y
                    event.savePointers()
                    // cancel ongoing fling animations
                    if (isFlingEnabled) {
                        mFlingAnimX?.cancel()
                        mFlingAnimY?.cancel()
                    }
                }
                MotionEvent.ACTION_POINTER_DOWN -> {
                    updateTouchParameters(event)
                }
                MotionEvent.ACTION_POINTER_UP -> {
                    // Check the dot product of current velocities.
                    // If the pointer that left was opposing another velocity vector, clear.
                    if (isFlingEnabled) {
                        mVelocityTracker?.let { tracker ->
                            tracker.computeCurrentVelocity(1000, MAX_FLING_VELOCITY)
                            val upIndex: Int = event.actionIndex
                            val id1: Int = event.getPointerId(upIndex)
                            val x1 = tracker.getXVelocity(id1)
                            val y1 = tracker.getYVelocity(id1)
                            for (i in 0 until event.pointerCount) {
                                if (i == upIndex) continue
                                val id2: Int = event.getPointerId(i)
                                val x = x1 * tracker.getXVelocity(id2)
                                val y = y1 * tracker.getYVelocity(id2)
                                val dot = x + y
                                if (dot < 0) {
                                    tracker.clear()
                                    break
                                }
                            }
                        }
                    }
                    updateTouchParameters(event)
                }
                MotionEvent.ACTION_UP -> {
                    // do fling animation
                    if (isFlingEnabled) {
                        mVelocityTracker?.let { tracker ->
                            val pointerId: Int = event.getPointerId(0)
                            tracker.computeCurrentVelocity(1000, MAX_FLING_VELOCITY)
                            val velocityY: Float = tracker.getYVelocity(pointerId)
                            val velocityX: Float = tracker.getXVelocity(pointerId)
                            if (abs(velocityY) > MIN_FLING_VELOCITY || abs(velocityX) > MIN_FLING_VELOCITY) {
                                val translateX = mTranslationX
                                val translateY = mTranslationY
                                val valueHolder = FloatValueHolder()
                                mFlingAnimX = FlingAnimation(valueHolder).apply {
                                    setStartVelocity(velocityX)
                                    setStartValue(0f)
                                    addUpdateListener { _, value, _ ->
                                        mTranslationX = translateX + value
                                        updateDrawMatrix()
                                        listener.onZoom(mScaling, mRotation, mTranslationX to mTranslationY, mPivotX to mPivotY)
                                    }
                                    addEndListener { _, _, _, _ ->
                                        updateTouchMatrix()
                                    }
                                    start()
                                }
                                mFlingAnimY = FlingAnimation(valueHolder).apply {
                                    setStartVelocity(velocityY)
                                    setStartValue(0f)
                                    addUpdateListener { _, value, _ ->
                                        mTranslationY = translateY + value
                                        updateDrawMatrix()
                                        listener.onZoom(mScaling, mRotation, mTranslationX to mTranslationY, mPivotX to mPivotY)
                                    }
                                    addEndListener { _, _, _, _ ->
                                        updateTouchMatrix()
                                    }
                                    start()
                                }
                            }
                            tracker.recycle()
                            mVelocityTracker = null
                        }
                    }
                }
                MotionEvent.ACTION_MOVE -> {
                    val (focusX, focusY) = event.focalPoint()
                    if (event.pointerCount > 1) {
                        if (isScaleEnabled) {
                            val touchSpan = event.touchSpan(focusX, focusY)
                            mScaling *= scaling(touchSpan)
                            mScaling = MathUtils.clamp(mScaling, MIN_SCALE, MAX_SCALE)
                            mPreviousTouchSpan = touchSpan
                        }
                        if (isRotationEnabled) {
                            mRotation += event.rotation(focusX, focusY)
                        }
                        if (isTranslationEnabled) {
                            val (translationX, translationY) = translation(focusX, focusY)
                            mTranslationX += translationX
                            mTranslationY += translationY
                        }
                    } else {
                        if (isTranslationEnabled) {
                            val (translationX, translationY) = translation(focusX, focusY)
                            mTranslationX += translationX
                            mTranslationY += translationY
                        }
                    }
                    mPreviousFocusX = focusX
                    mPreviousFocusY = focusY
                    updateTouchMatrix()
                    updateDrawMatrix()
                    event.savePointers()
                    listener.onZoom(mScaling, mRotation, mTranslationX to mTranslationY, mPivotX to mPivotY)
                }
            }
            return true
        }
        return false
    }

    // update focus point, touch span and pivot point
    private fun updateTouchParameters(event: MotionEvent) {
        val (focusX, focusY) = event.focalPoint()
        mPreviousFocusX = focusX
        mPreviousFocusY = focusY
        mPreviousTouchSpan = event.touchSpan(focusX, focusY)
        updatePivotPoint(focusX, focusY)
        updateTouchMatrix()
        updateDrawMatrix()
        event.savePointers()
        listener.onZoom(mScaling, mRotation, mTranslationX to mTranslationY, mPivotX to mPivotY)
    }

    // touch matrix is used to transform touch points
    // on the child view and to find pivot point
    private fun updateTouchMatrix() {
        mTouchMatrix.reset()
        mTouchMatrix.preTranslate(-mTranslationX, -mTranslationY)
        mTouchMatrix.postRotate(-mRotation, mPivotX, mPivotY)
        mTouchMatrix.postScale(1f / mScaling, 1f / mScaling, mPivotX, mPivotY)
    }

    // draw matrix is used to transform child view when drawing on the canvas
    private fun updateDrawMatrix() {
        mDrawMatrix.reset()
        mDrawMatrix.preScale(mScaling, mScaling, mPivotX, mPivotY)
        mDrawMatrix.preRotate(mRotation, mPivotX, mPivotY)
        mDrawMatrix.postTranslate(mTranslationX, mTranslationY)
    }

    // this updates the pivot point and translation error caused by changing the pivot point
    private fun updatePivotPoint(focusX: Float, focusY: Float) {
        // update point
        mPivotPoint[0] = focusX
        mPivotPoint[1] = focusY
        mTouchMatrix.mapPoints(mPivotPoint)
        mPivotX = mPivotPoint[0]
        mPivotY = mPivotPoint[1]
        // correct pivot error
        mDrawMatrix.mapPoints(mPivotPoint)
        mTranslationX -= mTranslationX + mPivotX - mPivotPoint[0]
        mTranslationY -= mTranslationY + mPivotY - mPivotPoint[1]
    }

    private fun MotionEvent.focalPoint(): Pair<Float, Float> {
        val upIndex = if (actionMasked == MotionEvent.ACTION_POINTER_UP) actionIndex else -1
        var sumX = 0f
        var sumY = 0f
        var sumCount = 0
        for (pointerIndex in 0 until pointerCount) {
            if (pointerIndex == upIndex) continue
            sumX += getX(pointerIndex)
            sumY += getY(pointerIndex)
            sumCount++
        }
        val focusX = sumX / sumCount
        val focusY = sumY / sumCount
        return focusX to focusY
    }

    private fun MotionEvent.touchSpan(
        currentFocusX: Float,
        currentFocusY: Float
    ): Float {
        var spanSumX = 0f
        var spanSumY = 0f
        var sumCount = 0
        val ignoreIndex = if (actionMasked == MotionEvent.ACTION_POINTER_UP) actionIndex else -1
        for (pointerIndex in 0 until pointerCount) {
            if (pointerIndex == ignoreIndex) continue
            spanSumX += abs(currentFocusX - getX(pointerIndex))
            spanSumY += abs(currentFocusY - getY(pointerIndex))
            sumCount++
        }
        if (sumCount > 1) {
            val spanX = spanSumX / sumCount
            val spanY = spanSumY / sumCount
            return spanX + spanY
        }
        return mPreviousTouchSpan
    }

    private fun scaling(currentTouchSpan: Float): Float {
        return currentTouchSpan / mPreviousTouchSpan
    }

    private fun MotionEvent.rotation(
        currentFocusX: Float,
        currentFocusY: Float
    ): Float {
        var rotationSum = 0f
        var weightSum = 0f
        for (pointerIndex in 0 until pointerCount) {
            val pointerId = getPointerId(pointerIndex)
            val x1 = getX(pointerIndex)
            val y1 = getY(pointerIndex)
            val (x2, y2) = mPointerMap[pointerId] ?: continue
            val dx1 = x1 - currentFocusX
            val dy1 = y1 - currentFocusY
            val dx2 = x2 - currentFocusX
            val dy2 = y2 - currentFocusY
            // dot product is proportional to the cosine of the angle
            // the determinant is proportional to its sine
            // sign of the rotation tells if it is clockwise or counter-clockwise
            val dot = dx1 * dx2 + dy1 * dy2
            val det = dy1 * dx2 - dx1 * dy2
            val rotation = atan2(det, dot)
            val weight = abs(dx1) + abs(dy1)
            rotationSum += rotation * weight
            weightSum += weight
        }
        if (weightSum > 0f) {
            val rotation = rotationSum / weightSum
            return rotation * 180f / PI.toFloat()
        }
        return 0f
    }

    private fun translation(
        currentFocusX: Float,
        currentFocusY: Float
    ): Pair<Float, Float> {
        return (currentFocusX - mPreviousFocusX) to (currentFocusY - mPreviousFocusY)
    }

    private fun MotionEvent.savePointers() {
        mPointerMap.clear()
        for (pointerIndex in 0 until pointerCount) {
            val id = getPointerId(pointerIndex)
            val x = getX(pointerIndex)
            val y = getY(pointerIndex)
            mPointerMap[id] = x to y
        }
    }

    interface Listener {
        fun onZoom(scaling: Float, rotation: Float, translation: Position, pivot: Position)
    }

}

typealias Position = Pair<Float, Float>

我使用ZoomGestureDetector如下FrameLayout

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout

class ZoomLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
    ZoomGestureDetector.Listener {

    private val gestureDetector = ZoomGestureDetector(this)

    var isZoomEnabled
        get() = gestureDetector.isZoomEnabled
        set(value) {
            gestureDetector.isZoomEnabled = value
        }

    var isScaleEnabled
        get() = gestureDetector.isScaleEnabled
        set(value) {
            gestureDetector.isScaleEnabled = value
        }

    var isRotationEnabled
        get() = gestureDetector.isRotationEnabled
        set(value) {
            gestureDetector.isRotationEnabled = value
        }

    var isTranslationEnabled
        get() = gestureDetector.isTranslationEnabled
        set(value) {
            gestureDetector.isTranslationEnabled = value
        }

    var isFlingEnabled
        get() = gestureDetector.isFlingEnabled
        set(value) {
            gestureDetector.isFlingEnabled = value
        }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        if (isZoomEnabled) return true
        gestureDetector.updateTouchLocation(event)
        return super.onInterceptTouchEvent(event)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return gestureDetector.onTouchEvent(event)
    }

    override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
        gestureDetector.updateCanvasMatrix(canvas)
        return super.drawChild(canvas, child, drawingTime)
    }

    override fun onZoom(scaling: Float, rotation: Float, translation: Position, pivot: Position) {
        invalidate()
    }

}
于 2021-10-06T09:57:53.153 回答
0

我使用zoomageview为imageview制作了捏缩放的代码。因此用户可以将图像拖出屏幕并放大,缩小图像。

您可以按照此link获取Step By Step代码并给出输出屏幕截图。

https://stackoverflow.com/a/58074642/11613683

于 2019-09-24T13:52:43.700 回答