如何使用 MotionEvent.MOVE 在 canvas 位图上移动和缩放

How to move and scale added on canvas bitmaps with MotionEvent.MOVE

我有可以放大和缩小的自定义图像视图。但我在 onDraw 方法中也有“canvas.drawBitmap”:

@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        if (isPinShouldBeDrawn) {
            Bitmap marker = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pin_raster_64);
            canvas.drawBitmap(marker, lastTouchX, lastTouchY, null);
        }

        isPinShouldBeDrawn = false;
    }

在“onTouchEvent”方法中我可以移动图像:

@SuppressWarnings("deprecation")
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mDetector.onTouchEvent(event)) {
            return true;
        }
        int touchCount = event.getPointerCount();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_1_DOWN:
            case MotionEvent.ACTION_POINTER_2_DOWN:
                if (touchCount >= 2) {
                    float distance = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1));
                    mPrevDistance = distance;
                    isScaling = true;
                } else {
                    mPrevMoveX = (int) event.getX();
                    mPrevMoveY = (int) event.getY();
                }
            case MotionEvent.ACTION_MOVE:
                if (touchCount >= 2 && isScaling) {
                    float dist = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1));
                    float scale = (dist - mPrevDistance) / dispDistance();
                    mPrevDistance = dist;
                    scale += 1;
                    scale = scale * scale;
                    zoomTo(scale, mWidth / 2, mHeight / 2);
                    cutting();
                } else if (!isScaling) {
                    int distanceX = mPrevMoveX - (int) event.getX();
                    int distanceY = mPrevMoveY - (int) event.getY();
                    mPrevMoveX = (int) event.getX();
                    mPrevMoveY = (int) event.getY();
                    mMatrix.postTranslate(-distanceX, -distanceY);
                    cutting();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_POINTER_2_UP:
                if (event.getPointerCount() <= 1) {
                    isScaling = false;
                }
                break;
        }
        return true;
    }

但是来自 onDraw 的位图没有移动。如果我试图保存新的位图,它只会保存可见区域,之后我根本无法缩放或移动它;保存方式:

public void save() {
        Bitmap returnedBitmap = Bitmap.createBitmap(
                getWidth(),
                getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);

        draw(canvas);

        setImageBitmap(returnedBitmap);
    }

如何同步移动和缩放图像和添加的位图?请帮忙! 整个 class:

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import androidx.appcompat.widget.AppCompatImageView;

import com.signalsense.signalsenseapp.R;

public class ScaleImageView extends AppCompatImageView implements OnTouchListener {

    static final float STROKE_WIDTH = 10f;
    static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    public static Path path = new Path();
    public static int imageheight, imagewidth;
    private static Matrix mMatrix;
    final RectF dirtyRect = new RectF();
    private final Context mContext;
    private final float MAX_SCALE = 2f;
    private final float[] mMatrixValues = new float[9];
    float lastTouchX;
    float lastTouchY;
    Paint paint = new Paint();
    String TAG = "ScaleImageView";
    private boolean isPinShouldBeDrawn = false;
    // display width height.
    private int mWidth;
    private int mHeight;
    private int mIntrinsicWidth;
    private int mIntrinsicHeight;
    private float mScale;
    private float mMinScale;
    private float mPrevDistance;
    private boolean isScaling;
    private int mPrevMoveX;
    private int mPrevMoveY;
    private GestureDetector mDetector;

    public ScaleImageView(Context context, AttributeSet attr) {
        super(context, attr);
        this.mContext = context;
        initialize();
    }

    public ScaleImageView(Context context) {
        super(context);
        this.mContext = context;
        initialize();
    }

    private void resetDirtyRect(float eventX, float eventY) {
        dirtyRect.left = Math.min(lastTouchX, eventX);
        dirtyRect.right = Math.max(lastTouchX, eventX);
        dirtyRect.top = Math.min(lastTouchY, eventY);
        dirtyRect.bottom = Math.max(lastTouchY, eventY);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        this.initialize();
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        this.initialize();
    }

    private void initialize() {
        this.setScaleType(ScaleType.MATRIX);
        mMatrix = new Matrix();
        Drawable d = getDrawable();

        paint.setAntiAlias(true);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(STROKE_WIDTH);

        if (d != null) {
            mIntrinsicWidth = d.getIntrinsicWidth();
            mIntrinsicHeight = d.getIntrinsicHeight();
            setOnTouchListener(this);
        }
        mDetector = new GestureDetector(mContext,
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onDoubleTap(MotionEvent e) {
                        maxZoomTo((int) e.getX(), (int) e.getY());
                        cutting();
                        return super.onDoubleTap(e);
                    }

                    @Override
                    public void onLongPress(MotionEvent e) {
                        super.onLongPress(e);
                        lastTouchX = e.getX();
                        lastTouchY = e.getY();

                        isPinShouldBeDrawn = true;

                        invalidate();
                        save();
                    }
                });

    }

    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        mWidth = r - l;
        mHeight = b - t;

        mMatrix.reset();
        int r_norm = r - l;
        mScale = (float) r_norm / (float) mIntrinsicWidth;

        int paddingHeight = 0;
        int paddingWidth = 0;
        // scaling vertical
        if (mScale * mIntrinsicHeight > mHeight) {
            mScale = (float) mHeight / (float) mIntrinsicHeight;
            mMatrix.postScale(mScale, mScale);
            paddingWidth = (r - mWidth) / 2;
            paddingHeight = 0;
            // scaling horizontal
        } else {
            mMatrix.postScale(mScale, mScale);
            paddingHeight = (b - mHeight) / 2;
            paddingWidth = 0;
        }
        mMatrix.postTranslate(paddingWidth, paddingHeight);

        setImageMatrix(mMatrix);
        mMinScale = mScale;
        zoomTo(mScale, mWidth / 2, mHeight / 2);
        cutting();
        return super.setFrame(l, t, r, b);
    }

    protected float getValue(Matrix matrix, int whichValue) {
        matrix.getValues(mMatrixValues);
        return mMatrixValues[whichValue];
    }

    protected float getScale() {
        return getValue(mMatrix, Matrix.MSCALE_X);
    }

    public float getTranslateX() {
        return getValue(mMatrix, Matrix.MTRANS_X);
    }

    protected float getTranslateY() {
        return getValue(mMatrix, Matrix.MTRANS_Y);
    }

    protected void maxZoomTo(int x, int y) {
        if (mMinScale != getScale() && (getScale() - mMinScale) > 0.1f) {
            // threshold 0.1f
            float scale = mMinScale / getScale();
            zoomTo(scale, x, y);
        } else {
            float scale = MAX_SCALE / getScale();
            zoomTo(scale, x, y);
        }
    }

    public void zoomTo(float scale, int x, int y) {
        if (getScale() * scale < mMinScale) {
            return;
        }
        if (scale >= 1 && getScale() * scale > MAX_SCALE) {
            return;
        }
        mMatrix.postScale(scale, scale);
        // move to center
        mMatrix.postTranslate(-(mWidth * scale - mWidth) / 2,
                -(mHeight * scale - mHeight) / 2);

        // move x and y distance
        mMatrix.postTranslate(-(x - (mWidth / 2)) * scale, 0);
        mMatrix.postTranslate(0, -(y - (mHeight / 2)) * scale);
        setImageMatrix(mMatrix);
    }

    public void cutting() {
        int width = (int) (mIntrinsicWidth * getScale());
        int height = (int) (mIntrinsicHeight * getScale());

        imagewidth = width;
        imageheight = height;

        if (getTranslateX() < -(width - mWidth)) {
            mMatrix.postTranslate(-(getTranslateX() + width - mWidth), 0);
        }
        if (getTranslateX() > 0) {
            mMatrix.postTranslate(-getTranslateX(), 0);
        }
        if (getTranslateY() < -(height - mHeight)) {
            mMatrix.postTranslate(0, -(getTranslateY() + height - mHeight));
        }
        if (getTranslateY() > 0) {
            mMatrix.postTranslate(0, -getTranslateY());
        }
        if (width < mWidth) {
            mMatrix.postTranslate((mWidth - width) / 2, 0);
        }
        if (height < mHeight) {
            mMatrix.postTranslate(0, (mHeight - height) / 2);
        }
        setImageMatrix(mMatrix);
    }

    private float distance(float x0, float x1, float y0, float y1) {
        float x = x0 - x1;
        float y = y0 - y1;
        return (float) Math.sqrt(x * x + y * y);
    }

    private float dispDistance() {
        return (float) Math.sqrt(mWidth * mWidth + mHeight * mHeight);
    }

    public void clear() {
        path.reset();
        invalidate();
    }

    public void save() {
        Bitmap returnedBitmap = Bitmap.createBitmap(
                getWidth(),
                getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);

        draw(canvas);

        setImageBitmap(returnedBitmap);
    }

    @SuppressWarnings("deprecation")
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mDetector.onTouchEvent(event)) {
            return true;
        }
        int touchCount = event.getPointerCount();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_1_DOWN:
            case MotionEvent.ACTION_POINTER_2_DOWN:
                if (touchCount >= 2) {
                    float distance = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1));
                    mPrevDistance = distance;
                    isScaling = true;
                } else {
                    mPrevMoveX = (int) event.getX();
                    mPrevMoveY = (int) event.getY();
                }
            case MotionEvent.ACTION_MOVE:
                if (touchCount >= 2 && isScaling) {
                    float dist = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1));
                    float scale = (dist - mPrevDistance) / dispDistance();
                    mPrevDistance = dist;
                    scale += 1;
                    scale = scale * scale;
                    zoomTo(scale, mWidth / 2, mHeight / 2);
                    cutting();
                } else if (!isScaling) {
                    int distanceX = mPrevMoveX - (int) event.getX();
                    int distanceY = mPrevMoveY - (int) event.getY();
                    mPrevMoveX = (int) event.getX();
                    mPrevMoveY = (int) event.getY();
                    mMatrix.postTranslate(-distanceX, -distanceY);
                    cutting();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_POINTER_2_UP:
                if (event.getPointerCount() <= 1) {
                    isScaling = false;
                }
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        if (isPinShouldBeDrawn) {
            Bitmap marker = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pin_raster_64);
            canvas.drawBitmap(marker, lastTouchX, lastTouchY, null);
        }

        isPinShouldBeDrawn = false;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return super.onTouchEvent(event);
    }

}

对不起英语,这不是我的母语:)

记录触摸事件的归一化坐标,

public void onLongPress(MotionEvent e) {
    super.onLongPress(e);

    //lastTouchX = e.getX();
    //lastTouchY = e.getY();
    
    float[] touchCoords = new float[]{ e.getX(), e.getY()}; // Touch point in screen-X,Y coords.
    
    Matrix matrix_inverse = new Matrix();
    mMatrix.invert(matrix_inverse); // XY to UV mapping matrix.

    matrix_inverse.mapPoints(touchCoords); // Touch point in bitmap-U,V coords.

    lastTouchX = touchCoords[0];
    lastTouchY = touchCoords[1];

    isPinShouldBeDrawn = true;

    invalidate();
    save();
}

然后,通过应用变换绘制叠加位图。

protected void onDraw(Canvas canvas)
{
    // TODO Auto-generated method stub
    super.onDraw(canvas);
    if (isPinShouldBeDrawn)
    {
        Bitmap marker = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pin_raster_64);

        //canvas.drawBitmap(marker, lastTouchX, lastTouchY, null);

        Matrix matrix_marker = new Matrix();
        matrix_marker.setTranslate(lastTouchX, lastTouchY);
        matrix_marker.postConcat(mMatrix);

        canvas.drawBitmap(marker, matrix_marker, null);
    }
    isPinShouldBeDrawn = false;
}

如果您想将叠加层烘焙到位图上,请将逆矩阵设置为 canvas 并调用 draw()。

public void save()
{
    Bitmap returnedBitmap = Bitmap.createBitmap(
            mIntrinsicWidth,
            mIntrinsicHeight,
            Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(returnedBitmap);
    Matrix matrix_inverse = new Matrix();
    mMatrix.invert(matrix_inverse);
    canvas.setMatrix(matrix_inverse);
    draw(canvas);

    setImageBitmap(returnedBitmap);
}