ZoomView canvas矩阵Android

时间:2013-11-15 16:56:29

标签: android android-canvas pinchzoom

我正在使用我在Stack和其他网站上找到的示例实现可缩放的视图。我确实想在视图中添加绘图功能。我能够获得显示的路径,但缩放距离它背后的图像有所不同。我使用了一个示例android图像,我沿着形状跟踪,路径出现在左侧和上方。此外,缩放会使其移动(下图)。

我一直在使用onDraw方法来绘制画布,但收效甚微。我基本上使用一个开关,onTouch是否响应移位/缩放或绘制路径。

protected void onDraw(Canvas canvas) {               
    if(imgBitmap != null && canvas != null)
    {                            
        canvas.drawBitmap(imgBitmap, matrix, background);
        canvas.setMatrix(matrix);
        for (int i = 0; i < colors.size(); i++) {
            canvas.drawPath(paths.get(i), colors.get(i));
        }
        canvas.drawPath(drawPath, drawPaint);

    }
}

以下是下面的图片及其余代码:

Drawing along the android causes the paths to be translated

enter image description here

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

private Bitmap imgBitmap = null;

private int containerWidth;
private int containerHeight;

Paint background;   

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

PointF start = new PointF();       

float currentScale;
float curX;
float curY;

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

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

float easing = 0.2f;   
boolean isAnimating = false;

float scaleDampingFactor = 0.5f;

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

private Handler mHandler = new Handler();       

float minScale;
float maxScale = 3.0f;

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

float screenDensity;

private GestureDetector gestureDetector;

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

private int defaultScale;


// Drawing path
private Path drawPath;
// Drawing and canvas paint
public Paint drawPaint;
// Canvas
private Canvas drawCanvas;
// Canvas bitmap
private Bitmap canvasBitmap;
// Counts how many fingers are on the screen
int pointerCount;
// Detects pinch to zoom activity
 // Bounds used to draw paths when zoomed in
Rect clipBounds;
// List of saved paths
 public ArrayList<Path> paths = new ArrayList<Path>();
 // List of saved colors
 public ArrayList<Paint> colors = new ArrayList<Paint>();

 // Creates Path and Paint for drawing
public void setUpDrawing() {
    System.out.println("--setUpDrawing--");
    drawPath = new Path();
    drawPaint = new Paint();

    drawPaint.setColor(Color.RED);
    drawPaint.setAntiAlias(true);
    drawPaint.setStrokeWidth(8);
    drawPaint.setStyle(Paint.Style.STROKE);
    drawPaint.setStrokeJoin(Paint.Join.ROUND);
    drawPaint.setStrokeCap(Paint.Cap.ROUND);
}

public int getDefaultScale() {
    return defaultScale;
}

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

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

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

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

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

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

    defaultScale = ZoomImage.DEFAULT_SCALE_FIT_INSIDE;
    setUpDrawing();
}

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

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

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

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

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

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

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

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

            curX = initX;
            curY = initY;

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

            curX = initX;
            curY = initY;

            currentScale = 1.0f;
            minScale = 1.0f;               
        }


        invalidate();           
    }
}

@Override
protected void onDraw(Canvas canvas) {               
    if(imgBitmap != null && canvas != null)
    {                            
        canvas.drawBitmap(imgBitmap, matrix, background);
        canvas.setMatrix(matrix);
        for (int i = 0; i < colors.size(); i++) {
            canvas.drawPath(paths.get(i), colors.get(i));
        }
        canvas.drawPath(drawPath, drawPaint);

    }
}

public void reDrawUndo() {
    System.out.println("paths.size" + paths.size());
     if (paths.size() > 0) {
         paths.remove(paths.size() - 1);
         colors.remove(colors.size() - 1);
         invalidate();
     }
}

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

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

    currentScale = mvals[0];

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

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

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


    boolean toMoveX = false;
    boolean toMoveY = false;   

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

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

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

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


@Override
public boolean onTouchEvent(MotionEvent event) {  
    float touchX = event.getX();
    System.out.println("touchX: " + event.getX());
    float touchY = event.getY();
    System.out.println("touchY: " + event.getY());

    // Is drawing mode on?
    if (Deal.on){
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                drawPath.moveTo(touchX, touchY);
                break;
            case MotionEvent.ACTION_MOVE:
                drawPath.lineTo(touchX, touchY);
                break;
            case MotionEvent.ACTION_UP:
                Paint newPaint = new Paint();
                newPaint.set(drawPaint);
                colors.add(newPaint);
                paths.add(drawPath);
                drawPath = new Path();
                drawPath.reset();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                System.out.println("bleh");
                break;
            default:
                return false;
        }
    }
    else {
        if(gestureDetector.onTouchEvent(event)) {
            return true;
        }

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

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

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

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

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

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

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

                matrix.postTranslate(diffX, diffY);

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

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


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

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

private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return FloatMath.sqrt(x * x + y * y);
}

private void midPoint(PointF point, MotionEvent event) {
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x/2, y/2);
}

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

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

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

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

        matrix.reset();

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

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

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

            curX = initX;
            curY = initY;

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

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

            curX = initX;
            curY = initY;

            currentScale = 1.0f;
            minScale = 1.0f;               
        }

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

public Bitmap getPhotoBitmap() {       
    return imgBitmap;
}


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

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

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

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

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

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

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

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

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

        invalidate();           
    }
};

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

                currentScale *= scaleChange;

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

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


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

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

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

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

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

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

}

编辑: 添加canvas.concat(matrix)修复了缩放。仍在努力修复偏移...

onDraw已更改为:

    super.onDraw(canvas);
    canvas.save();
    if(imgBitmap != null && canvas != null){     
        if(drawCanvas == null)
            drawCanvas = new Canvas(imgBitmap);

        clipBounds = canvas.getClipBounds();
        canvas.drawBitmap(imgBitmap, matrix, background);
        canvas.concat(matrix);
        for (int i = 0; i < colors.size(); i++) {
            canvas.drawPath(paths.get(i), colors.get(i));
        }
        canvas.drawPath(drawPath, drawPaint);

    }
    canvas.restore();
    invalidate();

1 个答案:

答案 0 :(得分:5)

知道了!获取比例和偏移的矩阵值,并相应地调整touchX和touchY。 mv [4]是标度,而mv [2]和mv [5]分别是x和y的偏移量。

float[] mv = new float[9];

@Override
public boolean onTouchEvent(MotionEvent event) {

    // Get the values from the matrix into the float array
    matrix.getValues(mv);

    float touchX = (event.getX()*(1/mv[4]) - (mv[2]/mv[4]));
    float touchY = (event.getY()*(1/mv[4]) - (mv[5]/mv[4]));