Search code examples
androidandroid-canvasandroid-imageviewtouch-eventzooming

Android - How to circular zoom/magnify part of image?


I am trying to allow the user to touch the image and then basically a cirular magnifier will show that will allow the user to better select a certain area on the image. When the user releases the touch the magnified portion will dissapear. This is used on several photo editing apps and I am trying to implement my own version of it. The code I have below does magnify a circular portion of the imageview but does not delete or clear the zoom once I release my finger. I currently set a bitmap to a canvas using canvas = new Canvas(bitMap); and then set the imageview using takenPhoto.setImageBitmap(bitMap); I am not sure if I am going about it the right way. The onTouch code is below:

zoomPos = new PointF(0,0);
        takenPhoto.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                 int action = event.getAction(); 
                    switch (action) { 
                        case MotionEvent.ACTION_DOWN: 
                            zoomPos.x = event.getX();
                            zoomPos.y = event.getY();
                            matrix.reset();
                            matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
                            shader.setLocalMatrix(matrix);
                            canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
                            takenPhoto.invalidate();
                            break; 
                        case MotionEvent.ACTION_MOVE: 
                            zoomPos.x = event.getX();
                            zoomPos.y = event.getY();
                            matrix.reset();
                            matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
                            canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
                            takenPhoto.invalidate();
                            break; 
                        case MotionEvent.ACTION_UP:   
                            //clear zoom here?

                            break; 
                        case MotionEvent.ACTION_CANCEL: 
                            break; 
                        default: 
                            break; 
            }
                    return true; 
            } 
            });

Solution

  • Adapting your code, I was able to get the following approach working.

    In the onTouch function, set a global point for determining where the user has touched, and set a boolean to indicate whether zooming is currently active or not:

    @Override
    public boolean onTouch(View view, MotionEvent event) {
    
        int action = event.getAction(); 
    
        zoomPos.x = event.getX();
        zoomPos.y = event.getY();
    
        switch (action) { 
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            zooming = true;
            this.invalidate();
            break; 
        case MotionEvent.ACTION_UP:   
        case MotionEvent.ACTION_CANCEL:
            zooming = false;
            this.invalidate();
            break; 
    
        default: 
            break; 
        }
    
        return true; 
    }
    

    Then, in the onDraw method, you use your code for drawing the zoomed in portion:

    @Override
    protected void onDraw(Canvas canvas) {
    
        super.onDraw(canvas);
    
        if (zooming) {
            matrix.reset();
            matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
            mPaint.getShader().setLocalMatrix(matrix);
    
            canvas.drawCircle(zoomPos.x, zoomPos.y, 100, mPaint);
        }
    }
    

    Note that for the shader, I used a bitmap shader as described here, which was created with:

    mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
    mShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
    
    mPaint = new Paint();
    mPaint.setShader(mShader);