在Android中,绘制一个可以缩放的ImageView上的矩形以突出显示

17

我参考了以下 SO 的例子:

1. 在 ImageView 上画线

2. 在 ImageView 上画图

3. 我使用了这个 ImageView 类来实现我的图片缩放效果:Image Pinch zoom code google

现在我想要做的是:

我想通过捏合屏幕来实现缩放,同时当我单击时,应该在 ImageView 上绘制一个矩形,我还希望这个矩形能够随着 ImageView 的缩放而缩放,并且我想使用 ScaleImageView 类来实现它。

输出应该如下图所示:

you can see when i make single tap over ImageViewit will draw rectangle like showing in image

我也知道,可以通过在 Android 中使用 RelativeLayout 或者 SurfaceView 来实现,但我对使用 Surface view 还不熟悉,我还担心如果我在 ImageView 上使用另一个 View 来绘制,这两个视图是否都可以缩放。如果我在 ImageView 上使用 SurfaceView,那么图片能否被缩放。

我使用了这里的捏合缩放示例:example that i use pinch zoom

现在,我通过以下代码在该示例中进行单点触摸绘制矩形:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.v("Log_tag", "Draw Image View");
        //Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.rect_image);
        //canvas.drawColor(Color.BLACK);
        //canvas.drawBitmap(_scratch, 10, 10, null);
        Drawable d = getDrawable();
        //Bitmap bitmap = ((BitmapDrawable)d).getBitmap();
        Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
        /*if(bitmap!=null){
            if (x1 > 0 || x2 > 0 || y1 > 0 || y2 > 0){
                  Log.v("Log_tag", "ImageBitmap is draw");
                //Canvas mCanvas=new Canvas(bitmap);
                //mCanvas.drawRect(x1, y1, x2, y2, mPaint);
                //  canvas.clipRect(left, top, right, bottom);
                   paint.setStyle(Paint.Style.FILL_AND_STROKE);
                    paint.setStrokeWidth(1);
                   paint.setColor(0xFF000000
                      + ((int)(PRESET_PRESSURE * pressure) <<16)
                      + ((int)(PRESET_PRESSURE * pressure) << 8)
                      + (int)(PRESET_PRESSURE * pressure));
                   //mCanvas.drawCircle(x1, y1, (PRESET_SIZE * size), paint);
            }

        }*/
        //canvas.save();
        //canvas.translate(mPosX, mPosY);
       // canvas.scale(mScaleFactor, mScaleFactor);
        mBitmapDrawable.draw(canvas);
        Paint myPaint = new Paint();
        myPaint.setColor(Color.GREEN);
        myPaint.setStyle(Paint.Style.STROKE);
        myPaint.setStrokeWidth(1);
        Log.v("Log_tag", "Redraw with this point");
        canvas.drawRect(rect_x1-30,rect_y1-30, rect_x1+30, rect_y1+30, myPaint);
        mCanvasMatrix=canvas.getMatrix();
        mImageCanvas=canvas;

        canvas.setMatrix(mCanvasMatrix);
        //canvas.restore();

    }

更新

以下是我用于ImageView缩放的类。

public class ImageViewScale extends ImageView implements OnTouchListener {
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //canvas.save();
        //canvas.scale(mScale, mScale);
        mCanvasMatrix=canvas.getMatrix();
        Paint myPaint = new Paint();
        myPaint.setColor(Color.GREEN);
        myPaint.setStyle(Paint.Style.STROKE);
        myPaint.setStrokeWidth(1);
        if(mCanvasMatrix!=null){
            if(orignalRect!=null)
                mCanvasMatrix.mapRect(orignalRect);
        }
        if(orignalRect!=null){
            canvas.drawRect(orignalRect,myPaint);
        }
        //canvas.drawRect(rect_x1-30,rect_y1-30, rect_x1+30, rect_y1+30, myPaint);
        int canavs_width=canvas.getWidth();
        int canavs_height=canvas.getHeight();
        canvas.setMatrix(mCanvasMatrix);
        //canvas.setMatrix(mMatrix);
        if(mDrawable!=null){
            //mDrawable.draw(canvas);
            Log.v("Log_tag", "draw with Canvas is done  W:"+ canavs_width+"H:"+ canavs_height);
        }
        //canvas.restore();
    }

    private float MAX_SCALE = 2f;
    private int DOUBLE_TAP_SECOND = 400;

    private float CANVAS_MAX_SCALE=2f;

    float rect_x1=50;
    float rect_y1=150;

    private Matrix mMatrix;
    private Matrix mCanvasMatrix;

    private final float[] mCanvasMatrixValues=new float[9];
    private final float[] mMatrixValues = new float[9];
    RectF orignalRect;
    private Drawable mDrawable;
    private ImageView mImageView;
    // display width height.
    private int mWidth;
    private int mHeight;

    private int mIntrinsicWidth;
    private int mIntrinsicHeight;

    private int mCanvasWidth;
    private int mCanvasHeight;


    private float mScale;
    private float mMinScale;

    private float mCanvasMinScale;

    // double tap for determining
    private long mLastTime = 0;
    private boolean isDoubleTap;
    private int mDoubleTapX;
    private int mDoubleTapY;

    private float mPrevDistance;
    private boolean isScaling;

    private int mPrevMoveX;
    private int mPrevMoveY;

    String TAG = "ScaleImageView";

    public ImageViewScale(Context context, AttributeSet attr) {
        super(context, attr);
        initialize();
    }

    public ImageViewScale(Context context) {
        super(context);
        initialize();
    }

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

    }

    private void initialize() {
        this.setScaleType(ScaleType.MATRIX);
        this.mMatrix = new Matrix();
        Drawable d = getDrawable();
        mDrawable=d;
        if (d != null) {
            mIntrinsicWidth = d.getIntrinsicWidth();
            mIntrinsicHeight = d.getIntrinsicHeight();
            setOnTouchListener(this);
        }
    }

    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        Log.v("Log_tag", "Size are here "+ l + t + r+ b);
        mWidth = r - l;
        mHeight = b - t;

        mMatrix.reset();
        mScale = (float) r / (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];
    }
    //New Added
    protected float getCanvasValue(Matrix matrix,int whichvalues){
        mCanvasMatrix.getValues(mCanvasMatrixValues);
        return mCanvasMatrixValues[whichvalues];
    }

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

    //New added  Method
    protected float getCanvasScale(){
        return getCanvasValue(mCanvasMatrix, Matrix.MSCALE_X);
    }

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

    //New added Method
    protected float getCanvasTranslateX(){
        return getCanvasValue(mCanvasMatrix, Matrix.MTRANS_X);
    }


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

    //New Added Method
    protected float getCanvasTranslateY(){
        return getCanvasValue(mCanvasMatrix, 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);
        }
    }




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


    protected void zoomToCanvas(float scale,int x,int y){
        if(getCanvasScale()* scale<mCanvasMinScale){
            return;
        }

        if(scale>=1 && getCanvasScale()*scale> CANVAS_MAX_SCALE){
            return;
        }
        mCanvasMatrix.postScale(scale, scale);



    }

    public void cutting() {
        int width = (int) (mIntrinsicWidth * getScale());
        int height = (int) (mIntrinsicHeight * getScale());
        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);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        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 {
                if (System.currentTimeMillis() <= mLastTime + DOUBLE_TAP_SECOND) {
                    if (30 > Math.abs(mPrevMoveX - event.getX())
                            + Math.abs(mPrevMoveY - event.getY())) {
                        isDoubleTap = true;
                        mDoubleTapX = (int) event.getX();
                        mDoubleTapY = (int) event.getY();
                    }
                }
                mLastTime = System.currentTimeMillis();
                mPrevMoveX = (int) event.getX();
                mPrevMoveY = (int) event.getY();
            }
            break;
        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;
                if (isDoubleTap) {
                    if (30 > Math.abs(mDoubleTapX - event.getX())
                            + Math.abs(mDoubleTapY - event.getY())) {
                        maxZoomTo(mDoubleTapX, mDoubleTapY);
                        cutting();
                    }
                }
            }
            isDoubleTap = false;
            break;
        }
        return true;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int count_touch=event.getPointerCount();
        switch(event.getAction()){
        case MotionEvent.ACTION_UP:
            float point_x=event.getX();
            float point_y=event.getY();
            rect_x1=point_x;
            rect_y1=point_y;
            if(count_touch==1){
                orignalRect=new RectF(rect_x1-30, rect_y1-30, rect_x1+30,  rect_y1+30);
                invalidate();
            }
            break;
        }

        return super.onTouchEvent(event);
    }
}

你想要做什么并不是很明显。你能再解释一下吗? - richardwiden
@Richard,目前我已经实现了可以通过手势缩放的ImageView,并且我已经在该ImageView上使用了@Override onDraw方法来绘制用户触摸时的矩形。它能够在ImageView上绘制一个矩形,但是我的问题是,ImageView可以进行缩放,但是矩形却不能跟随缩放。因此,我想要实现在矩形上同样的缩放功能。 - Herry
“它不起作用吗?”为什么不对矩形应用相同的转换? - richardwiden
是的。它不起作用,只有图像缩放,因为当我们在图像上绘制矩形时,它会在画布上绘制,大多数捏合缩放都使用Matrix缩放。 - Herry
只是为了澄清:您的意思是用户收缩手指时矩形会收缩,用户将触点滑开时矩形会扩大?并且矩形应该被包含在两个手指之间的区域内?矩形的边缘行为像是跟随手指移动? - Gunnar Karlsson
显示剩余3条评论
4个回答

4
你可能想要查看Matrix.mapRect。使用此方法,可以通过与图像视图中的图像相同的量来转换矩形。
boolean onTouch(MotionEvent ev) {
    ....
    // this rect dimensions should be initial values and should be a member.
    mOriginalRect = new RectF(rect_x1-30, rect_y1-30, rect_x1+30, rect_y1+30); 
    .....
}

@Override
protected void onDraw(Canvas canvas) {
    ....
    mCanvasMatrix = canvas.getMatrix();  ///matrix should have scale values..
    mCanvasMatrix.mapRect(tempRect, mOriginalRect); // mOriginalRect is src 
    canvas.drawRect(tempRect, myPaint);   // draw tempRect..
    ....
}

我不太清楚如何将Matrix.mapRect应用于ImagesView的Canvas。很抱歉向您提问,因为我对Canvas和图像缩放还比较陌生,而且我正在使用这种方法在单个触摸ImageView上绘制矩形: canvas.drawRect(rect_x1-30,rect_y1-30, rect_x1+30, rect_y1+30, myPaint); - Herry
让我们在聊天中继续这个讨论 - Ron
我看到了你更新的答案,但是tempRect是什么?我的意思是它一定是RectF,但是它包含什么? - Herry
让我们只初始化一次我们的“mOriginalRect”,然后我们在ImageView上绘制矩形,但问题仍然是我们没有得到与ImageView相同的矩形缩放。 - Herry
这意味着比例值不在该画布矩阵中。您应该在一个画布上执行缩放,然后从该画布中获取矩阵以进行映射...通过记录它们来检查矩阵值...看看缩放后的值是否存在其中... - Ron
显示剩余5条评论

1

您可能想要扩展ImageView,在视图的onDraw(Canvas)中,您将绘制矩形。

为了实现捏合缩放,可以扩展一次ImageView(应使用图像视图中的Matrix实现捏合缩放)

再次扩展它以获取矩形,使用图像矩阵进行变换,并在super.draw()调用后绘制它。


我已经有一个继承ImageView的类,并在super.draw()调用之后应用了Matrix,但它无法准确地使矩形缩放。您可以查看我的ImageViewScale以了解我尝试的内容。 - Herry
@HaMMedREd,你能解释一下你的答案吗?我真的不太明白。 - Herry
你有什么问题?你试图勾勒的矩形由4个点组成。如果你将矩形存储为RectF,并在其中定义单位为像素(例如,在x=10,y=40处的30x30矩形),然后通过getImageMatrix().mapRect(definedRect, translatedRect)传递它,然后你只需使用Canvas.drawRect(translatedRect, paint)绘制translatedRect,它应该根据图像变换矩阵在正确的位置上绘制。 - HaMMeReD
我的src/dest矩形转换搞乱了,你授予的父级解释得比我好。 - HaMMeReD

1
@ Herry - 我已经对这种问题进行了小型的POC,并发现我们必须像转换ImageView一样精确地转换画布。
例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Resources res = this.getResources();
Bitmap mBitmap = BitmapFactory.decodeResource(res, R.drawable.circlered);
canvas.save();
canvas.translate(1f, 1f);
canvas.restore();
canvas.save();
canvas.concat(mMatrix);
canvas.drawBitmap(mBitmap, 100, 150, null);
}

请试一试!然后告诉我。


你的意思是我必须在我用于捏合缩放的ImageView类的onDraw方法中编写这段代码。 - Herry
你不想在缩放时移动矩形吗? - Arpit
是的,一旦用户做出选择,它应该停留在图像上,就像两个视图的平面一样。 - Herry
是的,当我们应用缩放时,我不想移动React。 - Herry
你能否在单次触摸中绘制矩形覆盖整个图像区域? - Herry
在我的示例中,我正在所需位置上绘制一个位图到ImageView上。 - Arpit

0

绘制矩形ImageView

   public class RactangleImageView extends ImageView {
      private static final int strockwidth = 6;
      private Paint paintBorder;
      private Bitmap bitmap;
      private int strokeWidthPx;
      private RectF rectF;
      private RadialGradient radialGradient;
       public RactangleImageView(Context context) {
        super(context);
        init();
      }
     private void init() {
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.imageicon);
        strokeWidthPx = (int) (strockwidth * getResources().getDisplayMetrics().density);
        int halfStrokeWidthPx = strokeWidthPx / 2;
        paintBorder = new Paint();
        paintBorder.setStyle(Paint.Style.FILL);
        int totalWidth = bitmap.getWidth() + strokeWidthPx * 2;
        int totalHeight = bitmap.getHeight() + strokeWidthPx * 2;
        radialGradient = new RadialGradient(totalWidth /2, totalHeight /2, totalWidth /2, new int[]    {Color.BLACK, Color.GREEN}, null, Shader.TileMode.MIRROR);
        paintBorder.setShader(radialGradient);
        setImageBitmap(Bitmap.createBitmap(totalWidth, totalHeight,        Bitmap.Config.ARGB_8888));
          rectF = new RectF(halfStrokeWidthPx, halfStrokeWidthPx, totalWidth - halfStrokeWidthPx, totalHeight - halfStrokeWidthPx);
      }   
      @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRoundRect(rectF, 40, 40, paintBorder);
        canvas.drawBitmap(bitmap,strokeWidthPx, strokeWidthPx, null);
      }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接