Implementing magnifier/loupe for custom dynamic view on android

1.1k views Asked by At

I'm looking for a way to implement Magnifier/Loupe for custom view on android which is dynamically rebuilding.

2

There are 2 answers

0
Sergey Bondarenko On BEST ANSWER

I've figured it out Myself. So here is variant for dynamic changing view.

1) At first we are Creating new bitmap(whole) with same Height and Width in Overriden on Draw method;

2) create Canvas(temporalCanvas) from our bitmap(whole)

3) draw everything that we need on canvas(temporalCanvas)

4) draw out bitmap to original canvas(canvas) that is given in onDraw income

5) take coordinates from MotionEvent (it should be made global static on ACTION_DOWN) so this would be the SAME MotionEvent after invalidating and drawing view

6) Cut bitmap around selected(MotionEvent.GetX/Y) position

7) Draw trimmed bitmap on original (canvas) canvas

Possible difficulties: -Motion event returns different coordinates X,Y when it's called either from overriden onTouchEvent or from static event that where made on ACTION_DOWN; (just make x,y coordinate static) and don't forget to remove static touch event, and x,y coordinates on action UP

-2 canvas should be used or you can magnifier magnifier)

1
Vladimir Dimov On

I made this class just to answer your question. This is the most basic implementation of magnifier, hope it helps. Good luck:

public class ExampleMagnifierView extends View {

Matrix matrix = new Matrix();
Paint shaderPaint = new Paint();
BitmapShader shader = null;
//start the magnifier
boolean zooming;
//capture a new bitmap form a view
boolean isFirstTouch = true;
//magnifier position
static final PointF zoomPos = new PointF(0, 0);

private float magnifierSize = 75;
private Context context = null;
private Bitmap someBitmap = null;

ArrayList<Element> elements = new ArrayList<Element>();

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

    this.context = context;

    //Just for the example
    someBitmap = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_delete);

    //Just for the example
    elements = prepareElementsMatrix(5, 6);
}

@Override
protected void onDraw(Canvas canvas) {

    drawSomethingToBeMagnifiedNoMatterWhat(canvas);

    if (zooming) {
        matrix.reset();
        matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
        shaderPaint.getShader().setLocalMatrix(matrix);
        canvas.drawCircle(zoomPos.x, zoomPos.y, convertDpToPixel(magnifierSize, this.getContext()), shaderPaint);
    }

}

private void drawSomethingToBeMagnifiedNoMatterWhat(Canvas canvas){

    for (Element el : elements){
        canvas.drawBitmap(someBitmap, el.x , el.y , null);
    }

}

@Override
public boolean onTouchEvent(MotionEvent event) {

    if (isFirstTouch) {
        //we set the zooming to false because we want a image of the view without magnifier
        zooming = false;

        shader = null;
        shaderPaint = null;
        shaderPaint = new Paint();

        //get a fresh bitmap from the view
        shader = new BitmapShader(getBitmapFromView(this), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        isFirstTouch = false;
    }

    shaderPaint.setShader(shader);
    matrix.reset();
    matrix.postTranslate(-zoomPos.x, -zoomPos.y - convertDpToPixel(magnifierSize, context));
    shader.setLocalMatrix(matrix);

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:

            zoomPos.x = event.getX();
            zoomPos.y = event.getY() - convertDpToPixel(magnifierSize, context);

            //this flag starts drawing the magnifier
            zooming = true;
            invalidate();

            isFirstTouch = true;


            break;
        case MotionEvent.ACTION_MOVE:

            zoomPos.x = event.getX();
            zoomPos.y = event.getY() - convertDpToPixel(magnifierSize, context);

            //this flag starts drawing the magnifier
            zooming = true;
            invalidate();

            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:

            isFirstTouch = true;
            zooming = false;
            invalidate();

            break;
    }

    return true;

}

/**
 * This method get the bitmap form a view.
 *
 * @param view The view who we want as a bitmap
 * @return The view's bitmap
 */
public static Bitmap getBitmapFromView(View view) {
    //Define a bitmap with the same size as the view ( Use RGB_565 for better performance )
    Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
    //Bind a canvas to it
    Canvas canvas = new Canvas(returnedBitmap);
    //Get the view's background
    Drawable bgDrawable = view.getBackground();
    if (bgDrawable != null) {
        //has background drawable, then draw it on the canvas
        bgDrawable.draw(canvas);
    } else {
        //does not have background drawable, then draw white background on the canvas
        canvas.drawColor(Color.WHITE);
    }
    // draw the view on the canvas
    view.draw(canvas);

    //erase the drawable
    bgDrawable = null;

    //return the bitmap
    return returnedBitmap;
}

/**
 * This method converts dp unit to equivalent pixels, depending on device density.
 *
 * @param dp      A value in dp (density independent pixels) unit. Which we need to convert into pixels
 * @param context Context to get resources and device specific display metrics
 * @return A float value to represent px equivalent to dp depending on device density
 */
public static float convertDpToPixel(float dp, Context context) {
    Resources resources = context.getResources();
    DisplayMetrics metrics = resources.getDisplayMetrics();
    float px = dp * (metrics.densityDpi / 160f);
    return px;
}

private ArrayList<Element> prepareElementsMatrix(int rows, int columns){
    ArrayList<Element> elementsMatrix = new ArrayList<Element>();
    int offsetX = 0;
    int offsetY = 0;

    for (int i = 0; i<=columns; i++){

        offsetX += someBitmap.getWidth() + 5;
        elementsMatrix.add(new Element(offsetX, offsetY));
        for (int j = 0; j<=rows; j++){
            elementsMatrix.add(new Element(offsetX, offsetY));
            offsetY += someBitmap.getHeight() + 5;
        }

        offsetY = 0;
    }

    return elementsMatrix;
}

private class Element{
    int x;
    int y;

    public Element(int x, int y){
        this.x = x;
        this.y = y;
    }

}