Canvas - zooming in, shifting, and scaling on Android

僤鯓⒐⒋嵵緔 提交于 2019-12-02 17:17:55

I have implemented this behaviour, but in a slightly different way. I used a matrix to handle all the zooming and scrolling (and rotation, in my case). It makes for neat code and works like clockwork. I don't know what is causing your funky behaviour, though.

Store a Matrix and another path as class members:

Matrix drawMatrix = new Matrix();
Path transformedPath = new Path();

Replace your onScale:

@Override
public boolean onScale(ScaleGestureDetector detector) {
    Matrix transformationMatrix = new Matrix();

    //Zoom focus is where the fingers are centered, 
    transformationMatrix.postTranslate(-detector.getFocusX(), -detector.getFocusY());

    transformationMatrix.postScale(detector.getScaleFactor(), detector.getScaleFactor());

/* Using getFocuShift allows for scrolling with two pointers down. Remove it to skip this functionality */
    transformationMatrix.postTranslate(detector.getFocusX() + detector.getFocusShiftX(), detector.getFocusY() + detector.getFocusShiftY());

    drawMatrix.postConcat(transformationMatrix);
    invalidate();
    return true;
}

in onDraw; skip saving the canvas, instead:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.drawBitmap(canvasBitmap, drawMatrix, canvasPaint);
    transformedPath.rewind();
    transformedMatrix.addPath(drawPath);
    transformedPath.transform(drawMatrix, null);
    canvas.drawPath(transformedPath, drawPaint);
}

Happy coding!

A good way to accomplish all the different effects you are trying to do in a very simple manner is making use of the canvas method

canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint);

This method allow you, to take the whole source bitmap or just a small sample of it (Rect src), and project it as you wish (Rect dst) doing the scaling / translation of matrix calculations automatically for you as shown in the example below:

This example just scales / zooms the whole image...

     Canvas canvas = null;
     Bitmap elephantBmp = null;
     Rect src = new Rect();
     Rect postRect = new Rect();
     Paint paintIfAny = new Paint();

     //To scale the whole image, first take the whole source and then postRect with a bigger size... 
     src.top = src.left = 0;
     src.right = elephantBmp.getWidth();
     src.bottom = elephantBmp.getHeight();
     //Here you have the chance to translate / scale the image(in this case i will not translate it but just scale it...)
     postRect.top = postRect.left = 0;
     postRect.right = (int)(elephantBmp.getWidth() * 1.1F);
     postRect.bottom = (int)(elephantBmp.getHeight() * 1.1F);

     canvas.drawBitmap(elephantBmp, src, postRect, paintIfAny);

And this example takes just a sample of the image and shows it "inner zoom effect"

     Canvas canvas = null;
     Bitmap elephantBmp = null;
     Rect src = new Rect();
     Rect postRect = new Rect();
     Paint paintIfAny = new Paint();

    //To shift the whole image, first take the part of the original source image you want to show 
     src.top = topPosition;//Top coordinate of piece of image to show
     src.left = leftPosition;//Left coordinate of piece of image to show
     src.right = rightPosition;//Right coordinate of piece of image to show
     src.bottom = bottomPosition;//Bottom coordinate of piece of image to show
     //Here you have the chance to show it as big as you want...
     postRect.top = postRect.left = 0;
     postRect.right = (int)(src.width() * 1.1F);
     postRect.bottom = (int)(src.height() * 1.1F);

     canvas.drawBitmap(elephantBmp, src, postRect, paintIfAny);

By making use of these src/dst objects you can do pretty much any effect you want on android, is a simple and really powerful tool

Hope this helps.

Regards!

Can you try to draw paths only one time, and keep the zooming the responsibility of the canvas itself

private boolean pathsDrawn;

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    clipBounds = canvas.getClipBounds();
    canvas.save();
    drawPaint.setStrokeWidth(8/mScaleFactor);
    canvas.scale(mScaleFactor, mScaleFactor, 0, 0);

    if(!pathsDrawn) {
       canvas.drawPath(drawPath, drawPaint);
       pathsDrawn = true;
    }
    canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
    canvas.restore();
}
check below code will help you.

//CUSTOM IMAGEVIEW
import java.io.ByteArrayOutputStream;
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.util.FloatMath;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class ScaleImageView extends ImageView implements OnTouchListener {

static final float STROKE_WIDTH = 10f;
static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;

float lastTouchX;
float lastTouchY;
final RectF dirtyRect = new RectF();

private Context mContext;
private float MAX_SCALE = 2f;

private static Matrix mMatrix;
private final float[] mMatrixValues = new float[9];

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

Paint paint = new Paint();
public static Path path = new Path();

public static int imageheight, imagewidth;

String TAG = "ScaleImageView";

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);
this.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
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 FloatMath.sqrt(x * x + y * y);
}

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

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

public static void save() {

Bitmap returnedBitmap = Bitmap.createBitmap(
        ScaleImageViewActivity.imageview.getWidth(),
        ScaleImageViewActivity.imageview.getHeight(),
        Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);

Drawable bgDrawable = ScaleImageViewActivity.imageview.getDrawable();

if (bgDrawable != null)
    bgDrawable.draw(canvas);
else
    canvas.drawColor(Color.WHITE);

ScaleImageViewActivity.imageview.draw(canvas);

ByteArrayOutputStream bs = new ByteArrayOutputStream();
returnedBitmap.compress(Bitmap.CompressFormat.PNG, 50, bs);

Bitmap FinalBitmap = BitmapFactory.decodeByteArray(bs.toByteArray(), 0,
        bs.toByteArray().length);

ScaleImageViewActivity.imageview.setImageBitmap(FinalBitmap);
path.reset();

// ScaleImageViewActivity.imageview.setImageMatrix(mMatrix);

}

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!ScaleImageViewActivity.flag) {

    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;
    }
} else {
    float eventX = event.getX();
    float eventY = event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        path.moveTo(eventX, eventY);
        lastTouchX = eventX;
        lastTouchY = eventY;
        return true;

    case MotionEvent.ACTION_MOVE:

    case MotionEvent.ACTION_UP:

        resetDirtyRect(eventX, eventY);
        int historySize = event.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            float historicalX = event.getHistoricalX(i);
            float historicalY = event.getHistoricalY(i);
            path.lineTo(historicalX, historicalY);
        }
        path.lineTo(eventX, eventY);
        break;
    }

    invalidate((int) (dirtyRect.left - HALF_STROKE_WIDTH),
            (int) (dirtyRect.top - HALF_STROKE_WIDTH),
            (int) (dirtyRect.right + HALF_STROKE_WIDTH),
            (int) (dirtyRect.bottom + HALF_STROKE_WIDTH));

    lastTouchX = eventX;
    lastTouchY = eventY;
}
return true;
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (ScaleImageViewActivity.flag)
    canvas.drawPath(path, paint);
}

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

}

//ACTIVITY

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class ScaleImageViewActivity extends Activity implements OnClickListener {

Button btndraw, btnzoom, btnsave;

public static ScaleImageView imageview;
public static boolean flag = true;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initwidget();
}

private void initwidget() {

imageview = (ScaleImageView) findViewById(R.id.image);

btnsave = (Button) findViewById(R.id.activity_main_save);
btndraw = (Button) findViewById(R.id.activity_main_zoom_draw);
btnzoom = (Button) findViewById(R.id.activity_main_zoom_zoom);

btndraw.setOnClickListener(this);
btnzoom.setOnClickListener(this);
btnsave.setOnClickListener(this);
}

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if (btndraw.equals(arg0)) {
    flag = true;
} else if (btnzoom.equals(arg0)) {
    flag = false;
} else if (btnsave.equals(arg0)) {
    ScaleImageView.save();
}
}
}
main.xml

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

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<Button
    android:id="@+id/activity_main_zoom_zoom"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Zoom" />

<Button
    android:id="@+id/activity_main_zoom_draw"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Draw" />

<Button
    android:id="@+id/activity_main_save"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Save" />
</LinearLayout>

<com.matabii.dev.scaleimageview.ScaleImageView
android:id="@+id/image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/sample" />

</LinearLayout>

Take a look at this tutorial. Here the author uses two Matrix objects to manage translations and scalling. I suggest you to use matrices, otherwise calculating new coordinates can be tricky and tedious. Your problem is caused by not managing translations correctly. Note: in the tutorial, inside OnDraw(), it misses the canvas.concat(matrix), put it right after canvas.drawBitmap(...)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!