简体   繁体   English

"Android 图像视图捏缩放"

[英]Android Image View Pinch Zooming

I am using code sample from Making Sense of Multitouch for zooming image view.我正在使用Make Sense of Multitouch中的代码示例来缩放图像视图。 On ScaleListener I added ScaleGestureDetector.getFocusX() and getFocusY() for content to zoom about the focal point of the gesture.在 ScaleListener 上,我添加了ScaleGestureDetector.getFocusX() and getFocusY()用于缩放手势焦点的内容。 It is working fine.它工作正常。

The problem is, on first multitouch the entire Image drawing position is changing to the current touch point and zooming it from there.问题是,在第一次多点触摸时,整个图像绘制位置正在更改为当前触摸点并从那里缩放。 Could you help me to resolve this issue?你能帮我解决这个问题吗?

Here is My Code Sample For TouchImageView.这是我的 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;
    }
}

And here how I used it within my activity.以及我如何在我的活动中使用它。

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

and dont forget to set scaleType property to matrix of ImageView tag like:并且不要忘记将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>

and the variables used are:使用的变量是:

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

你可以使用这个类: TouchImageView

Using a ScaleGestureDetector使用ScaleGestureDetector

When learning a new concept I don't like using libraries or code dumps.在学习新概念时,我不喜欢使用库或代码转储。 I found a good description here and in the documentation of how to resize an image by pinching.我在此处和有关如何通过捏合调整图像大小的文档中找到了很好的描述。 This answer is a slightly modified summary.这个答案是一个稍微修改的总结。 You will probably want to add more functionality later, but it will help you get started.您可能希望稍后添加更多功能,但它会帮助您入门。

动画 gif:缩放图像示例

Layout布局

The ImageView just uses the app logo since it is already available. ImageView仅使用应用程序徽标,因为它已经可用。 You can replace it with any image you like, though.不过,您可以将其替换为您喜欢的任何图像。

<?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>

Activity活动

We use a ScaleGestureDetector on the activity to listen to touch events.我们在活动上使用ScaleGestureDetector来监听触摸事件。 When a scale (ie, pinch) gesture is detected, then the scale factor is used to resize the ImageView .当检测到缩放(即捏合)手势时,缩放因子用于调整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;
        }
    }
}

Notes笔记

  • Although the activity had the gesture detector in the example above, it could have also been set on the image view itself.尽管 Activity 在上面的示例中具有手势检测器,但它也可以设置在图像视图本身上。
  • You can limit the size of the scaling with something like您可以使用类似的方法限制缩放的大小

    mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
  • Thanks again to Pinch-to-zoom with multi-touch gestures In Android再次感谢Android 中的多点触控手势捏合缩放

  • Documentation 文档
  • Use Ctrl + mouse drag to simulate a pinch gesture in the emulator.使用Ctrl + 鼠标拖动在模拟器中模拟捏合手势。

Going on继续

You will probably want to do other things like panning and scaling to some focus point.您可能想要做其他事情,例如平移和缩放到某个焦点。 You can develop these things yourself, but if you would like to use a pre-made custom view, copy TouchImageView.java into your project and use it like a normal ImageView .您可以自己开发这些东西,但是如果您想使用预制的自定义视图, TouchImageView.java复制到您的项目中并像普通ImageView一样使用它。 It worked well for me and I only ran into one bug .它对我来说效果很好,我只遇到了一个错误 I plan to further edit the code to remove the warning and the parts that I don't need.我打算进一步编辑代码以删除警告和我不需要的部分。 You can do the same.你也可以做到的。

I made my own custom imageview with pinch to zoom.我用捏缩放制作了我自己的自定义图像视图。 There is no limits/borders on Chirag Raval s code, so user can drag the image off the screen. Chirag Raval的代码没有限制/边界,因此用户可以将图像拖出屏幕。 This will fix it.这将修复它。

Here is the CustomImageView class:这是 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);
    }


}

This is how you can use it in your activity:这是您在活动中使用它的方式:

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

And layout:和布局:

<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

Add bellow line in build.gradle:在 build.gradle 中添加以下行:

compile 'com.commit451:PhotoView:1.2.4'

or要么

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

In Java file:在 Java 文件中:

PhotoViewAttacher photoAttacher;
photoAttacher= new PhotoViewAttacher(Your_Image_View);
photoAttacher.update();

Custom zoom view in Kotlin 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
}
 }

I made code for imageview with pinch to zoom using zoomageview.我为图像视图制作了代码,使用缩放视图进行缩放。 so user can drag the image off the screen and zoom-In , zoom-out the image.因此用户可以将图像拖离屏幕并放大、缩小图像。

You can follow this link to get the Step By Step Code and also given Output Screenshot.您可以按照此link获取分Step By Step代码以及输出屏幕截图。

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

In the TouchImageViewSample<\/code> class scaling happens around the pivot point.TouchImageViewSample<\/code>类中,缩放发生在枢轴点周围。 Pixel belong to the pivot point doesn't get effected by scaling of the image.属于轴心点的像素不受图像缩放的影响。 When you are changing the pivot point, the view get redrawn and scaling happens around the new pivot point.当您更改轴心点时,视图会重新绘制,并且会围绕新的轴心点进行缩放。 This changes the location of previous pivot point and you see this as the image get shifted every time you touching down on the image.这会改变前一个枢轴点的位置,您会看到这一点,因为每次您触碰图像时图像都会发生变化。 You have to compensate this shifting error by translating the image.您必须通过平移图像来补偿这种移动误差。 See how this is done in my ZoomGestureDetector.updatePivotPoint()<\/code> method.看看这是如何在我的ZoomGestureDetector.updatePivotPoint()<\/code>方法中完成的。

ZoomGestureDetector缩放手势检测器<\/h3>

I created custom zoom gesture detector class.我创建了自定义缩放手势检测器类。 It can do scaling, translation and rotation at the same time.它可以同时进行缩放、平移和旋转。 It also supports fling animation.它还支持投掷动画。

 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><\/code><\/pre>

I used ZoomGestureDetector<\/code> in a FrameLayout<\/code> as below.我在FrameLayout<\/code>中使用了ZoomGestureDetector<\/code> ,如下所示。

 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() } }<\/code><\/pre>"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM