How to create a resizable rectangle with user touch events on Android?
Asked Answered
B

6

28

I want to create a rectangular shape that will be resized with the touches of the user. Below image is a good example of what i want to do:

Resizable Rectangle

Is there any example like that? What do I need to study to implement this?

Thanks in advance,

Beauty answered 23/1, 2012 at 15:29 Comment(6)
The answer would depend on what do you already know. Do you have any Android experience? Any Java experience? GUI systems in general? Any programming experience at all? I'm not being snarky, it's just not obvious from the question.Worker
is that crop functionality, you want to achieve?Salmi
@SevaAlekseyev I have Android experience, but I never implemented a custom View.Beauty
@silent yes it is exactly the crop functionality I want to achieve, but I cannot use the built in image cropper since I need some other functionalities in that Acivity.Beauty
try this: github.com/biokys/cropimageSalmi
@ipman, can you share source code.Sargeant
W
8

To implement a custom view, you derive a class from View :) Override onDraw() for looks, override onTouchEvent() for input processing. Note that in Android, you cannot draw on view outside onDraw(); if you want to refresh the view, call invalidate().

You can implement draggable corners as separate views. For looks, just use ready-made images (feel free to derive from ImageView). Dragging is implemented as moving your view in response to touch events. RelativeLayout is your friend for arbitrary view positining.

You can add homemade views to the layout; just go to XML editing and type a <com.mypackage.MyViewClass> element.

Worker answered 23/1, 2012 at 19:27 Comment(1)
A good solution but makes it hard to implement zooming to your custom view because child-views in a relative layout won't resize/zoom accordingly. Thererfore most users implement onDraw on touch handling on onTouchEvent manually - like the other example with the DrawView and ColorBalls.Gurney
I
26

Chintan Rathod's answer was great solution but there are something wrong when He draws the rectangle. I just edit some lines of code to make it works correctly with user touch event. Now, you can add this view to your layout then touch to draw.

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.ihnel.englishpronounciation.R;

public class DrawView extends View {

    Point[] points = new Point[4];

    /**
     * point1 and point 3 are of same group and same as point 2 and point4
     */
    int groupId = -1;
    private ArrayList<ColorBall> colorballs = new ArrayList<ColorBall>();
    // array that holds the balls
    private int balID = 0;
    // variable to know what ball is being dragged
    Paint paint;
    Canvas canvas;

    public DrawView(Context context) {
        super(context);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
    }

    public DrawView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
    }

    // the method that draws the balls
    @Override
    protected void onDraw(Canvas canvas) {
        if(points[3]==null) //point4 null when user did not touch and move on screen.
            return;
        int left, top, right, bottom;
        left = points[0].x;
        top = points[0].y;
        right = points[0].x;
        bottom = points[0].y;
        for (int i = 1; i < points.length; i++) {
            left = left > points[i].x ? points[i].x:left;
            top = top > points[i].y ? points[i].y:top;
            right = right < points[i].x ? points[i].x:right;
            bottom = bottom < points[i].y ? points[i].y:bottom;
        }
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5);

        //draw stroke
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#AADB1255"));
        paint.setStrokeWidth(2);
        canvas.drawRect(
                    left + colorballs.get(0).getWidthOfBall() / 2,
                    top + colorballs.get(0).getWidthOfBall() / 2, 
                    right + colorballs.get(2).getWidthOfBall() / 2, 
                    bottom + colorballs.get(2).getWidthOfBall() / 2, paint);
        //fill the rectangle
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#55DB1255"));
        paint.setStrokeWidth(0);
        canvas.drawRect(
                left + colorballs.get(0).getWidthOfBall() / 2,
                top + colorballs.get(0).getWidthOfBall() / 2, 
                right + colorballs.get(2).getWidthOfBall() / 2, 
                bottom + colorballs.get(2).getWidthOfBall() / 2, paint);

        //draw the corners
        BitmapDrawable bitmap = new BitmapDrawable();
        // draw the balls on the canvas
        paint.setColor(Color.BLUE);
        paint.setTextSize(18);
        paint.setStrokeWidth(0);
        for (int i =0; i < colorballs.size(); i ++) {
            ColorBall ball = colorballs.get(i);
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    paint);

            canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
        }
    }

    // events when touching the screen
    public boolean onTouchEvent(MotionEvent event) {
        int eventaction = event.getAction();

        int X = (int) event.getX();
        int Y = (int) event.getY();

        switch (eventaction) {

        case MotionEvent.ACTION_DOWN: // touch down so check if the finger is on
                                        // a ball
            if (points[0] == null) {
                //initialize rectangle.
                points[0] = new Point();
                points[0].x = X;
                points[0].y = Y;

                points[1] = new Point();
                points[1].x = X;
                points[1].y = Y + 30;

                points[2] = new Point();
                points[2].x = X + 30;
                points[2].y = Y + 30;

                points[3] = new Point();
                points[3].x = X +30;
                points[3].y = Y;

                balID = 2;
                groupId = 1;
                 // declare each ball with the ColorBall class
                for (Point pt : points) {
                     colorballs.add(new ColorBall(getContext(), R.drawable.ic_circle, pt));
                }
            } else {
                //resize rectangle
                balID = -1;
                groupId = -1;
                for (int i = colorballs.size()-1; i>=0; i--) {
                    ColorBall ball = colorballs.get(i);
                    // check if inside the bounds of the ball (circle)
                    // get the center for the ball
                    int centerX = ball.getX() + ball.getWidthOfBall();
                    int centerY = ball.getY() + ball.getHeightOfBall();
                    paint.setColor(Color.CYAN);
                    // calculate the radius from the touch to the center of the
                    // ball
                    double radCircle = Math
                            .sqrt((double) (((centerX - X) * (centerX - X)) + (centerY - Y)
                                    * (centerY - Y)));

                    if (radCircle < ball.getWidthOfBall()) {

                        balID = ball.getID();
                        if (balID == 1 || balID == 3) {
                            groupId = 2;
                        } else {
                            groupId = 1;
                        }
                        invalidate();
                        break;
                    }
                    invalidate();
                }
            }
            break;

        case MotionEvent.ACTION_MOVE: // touch drag with the ball


            if (balID > -1) {
                // move the balls the same as the finger
                colorballs.get(balID).setX(X);
                colorballs.get(balID).setY(Y);

                paint.setColor(Color.CYAN);
                if (groupId == 1) {
                    colorballs.get(1).setX(colorballs.get(0).getX());
                    colorballs.get(1).setY(colorballs.get(2).getY());
                    colorballs.get(3).setX(colorballs.get(2).getX());
                    colorballs.get(3).setY(colorballs.get(0).getY());
                } else {
                    colorballs.get(0).setX(colorballs.get(1).getX());
                    colorballs.get(0).setY(colorballs.get(3).getY());
                    colorballs.get(2).setX(colorballs.get(3).getX());
                    colorballs.get(2).setY(colorballs.get(1).getY());
                }

                invalidate();
            }

            break;

        case MotionEvent.ACTION_UP:
            // touch drop - just do things here after dropping

            break;
        }
        // redraw the canvas
        invalidate();
        return true;

    }


    public static class ColorBall {

        Bitmap bitmap;
        Context mContext;
        Point point;
        int id;
        static int count = 0;

        public ColorBall(Context context, int resourceId, Point point) {
            this.id = count++;
            bitmap = BitmapFactory.decodeResource(context.getResources(),
                    resourceId);
            mContext = context;
            this.point = point;
        }

        public int getWidthOfBall() {
            return bitmap.getWidth();
        }

        public int getHeightOfBall() {
            return bitmap.getHeight();
        }

        public Bitmap getBitmap() {
            return bitmap;
        }

        public int getX() {
            return point.x;
        }

        public int getY() {
            return point.y;
        }

        public int getID() {
            return id;
        }

        public void setX(int x) {
            point.x = x;
        }

        public void setY(int y) {
            point.y = y;
        }
    }
}
Immigrate answered 23/7, 2013 at 10:22 Comment(10)
+1 for upgrading answer. I just put it off as don't have time to emphasize on problem. :)Sargeant
Shouldnt you be using ball.getWidthOfBall()/2 and ball.getHeightOfBall()/2 when computing centerX and centerY.Agonist
Could you please give me more resonable explaination, user2309862?Immigrate
@ Nguyen Minh Binh how can I add my Image into this custom View(DrawView)Gothar
Thanks man. Would be better, if you explain what exactly you found wrong on @ChintanRathod 's answer?Hollah
@NguyenMinhBinh Thanks for this answer. Please let me know how to stop the overlapping and crossing of sides.Deckard
I posted this answer very long time ago and really hard to investigate back for now. If you find any way to improve it, feel free to edit my answer.Immigrate
@NguyenMinhBinh could you tell me that how to put that image behind custom view ?Xenolith
@SandeepDhami It's the basic Android UI layout, you can use Framelayout or RelativeLayout for thisImmigrate
i have write a demo for this GIF link and upload the code Code LinkPortsmouth
W
8

To implement a custom view, you derive a class from View :) Override onDraw() for looks, override onTouchEvent() for input processing. Note that in Android, you cannot draw on view outside onDraw(); if you want to refresh the view, call invalidate().

You can implement draggable corners as separate views. For looks, just use ready-made images (feel free to derive from ImageView). Dragging is implemented as moving your view in response to touch events. RelativeLayout is your friend for arbitrary view positining.

You can add homemade views to the layout; just go to XML editing and type a <com.mypackage.MyViewClass> element.

Worker answered 23/1, 2012 at 19:27 Comment(1)
A good solution but makes it hard to implement zooming to your custom view because child-views in a relative layout won't resize/zoom accordingly. Thererfore most users implement onDraw on touch handling on onTouchEvent manually - like the other example with the DrawView and ColorBalls.Gurney
S
7

Following code is to draw rectangle on touch base.

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.common.Utils;
import com.example.rectangleoverlay.R;

public class DrawView extends View {

    Point point1, point3;
    Point point2, point4;

    /**
     * point1 and point 3 are of same group and same as point 2 and point4
     */
    int groupId = -1;
    private ArrayList<ColorBall> colorballs = new ArrayList<ColorBall>();
    // array that holds the balls
    private int balID = 0;
    // variable to know what ball is being dragged
    Paint paint;
    Canvas canvas;

    public DrawView(Context context) {
        super(context);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
        // setting the start point for the balls
        point1 = new Point();
        point1.x = 50;
        point1.y = 20;

        point2 = new Point();
        point2.x = 150;
        point2.y = 20;

        point3 = new Point();
        point3.x = 150;
        point3.y = 120;

        point4 = new Point();
        point4.x = 50;
        point4.y = 120;

        // declare each ball with the ColorBall class
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point1));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point2));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point3));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point4));

    }

    public DrawView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
        // setting the start point for the balls
        point1 = new Point();
        point1.x = 50;
        point1.y = 20;

        point2 = new Point();
        point2.x = 150;
        point2.y = 20;

        point3 = new Point();
        point3.x = 150;
        point3.y = 120;

        point4 = new Point();
        point4.x = 50;
        point4.y = 120;

        // declare each ball with the ColorBall class
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point1));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point2));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point3));
        colorballs.add(new ColorBall(context, R.drawable.gray_circle, point4));

    }

    // the method that draws the balls
    @Override
    protected void onDraw(Canvas canvas) {
        // canvas.drawColor(0xFFCCCCCC); //if you want another background color

        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setColor(Color.parseColor("#55000000"));
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeJoin(Paint.Join.ROUND);
        // mPaint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeWidth(5);

        canvas.drawPaint(paint);
        paint.setColor(Color.parseColor("#55FFFFFF"));

        if (groupId == 1) {
            canvas.drawRect(point1.x + colorballs.get(0).getWidthOfBall() / 2,
                    point3.y + colorballs.get(2).getWidthOfBall() / 2, point3.x
                            + colorballs.get(2).getWidthOfBall() / 2, point1.y
                            + colorballs.get(0).getWidthOfBall() / 2, paint);
        } else {
            canvas.drawRect(point2.x + colorballs.get(1).getWidthOfBall() / 2,
                    point4.y + colorballs.get(3).getWidthOfBall() / 2, point4.x
                            + colorballs.get(3).getWidthOfBall() / 2, point2.y
                            + colorballs.get(1).getWidthOfBall() / 2, paint);
        }
        BitmapDrawable mBitmap;
        mBitmap = new BitmapDrawable();

        // draw the balls on the canvas
        for (ColorBall ball : colorballs) {
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    new Paint());
        }
    }

    // events when touching the screen
    public boolean onTouchEvent(MotionEvent event) {
        int eventaction = event.getAction();

        int X = (int) event.getX();
        int Y = (int) event.getY();

        switch (eventaction) {

        case MotionEvent.ACTION_DOWN: // touch down so check if the finger is on
                                        // a ball
            balID = -1;
            groupId = -1;
            for (ColorBall ball : colorballs) {
                // check if inside the bounds of the ball (circle)
                // get the center for the ball
                Utils.logd("Id : " + ball.getID());
                Utils.logd("getX : " + ball.getX() + " getY() : " + ball.getY());
                int centerX = ball.getX() + ball.getWidthOfBall();
                int centerY = ball.getY() + ball.getHeightOfBall();
                paint.setColor(Color.CYAN);
                // calculate the radius from the touch to the center of the ball
                double radCircle = Math
                        .sqrt((double) (((centerX - X) * (centerX - X)) + (centerY - Y)
                                * (centerY - Y)));

                Utils.logd("X : " + X + " Y : " + Y + " centerX : " + centerX
                        + " CenterY : " + centerY + " radCircle : " + radCircle);

                if (radCircle < ball.getWidthOfBall()) {

                    balID = ball.getID();
                    Utils.logd("Selected ball : " + balID);
                    if (balID == 1 || balID == 3) {
                        groupId = 2;
                        canvas.drawRect(point1.x, point3.y, point3.x, point1.y,
                                paint);
                    } else {
                        groupId = 1;
                        canvas.drawRect(point2.x, point4.y, point4.x, point2.y,
                                paint);
                    }
                    invalidate();
                    break;
                }
                invalidate();
            }

            break;

        case MotionEvent.ACTION_MOVE: // touch drag with the ball
            // move the balls the same as the finger
            if (balID > -1) {
                Utils.logd("Moving Ball : " + balID);

                colorballs.get(balID).setX(X);
                colorballs.get(balID).setY(Y);

                paint.setColor(Color.CYAN);

                if (groupId == 1) {
                    colorballs.get(1).setX(colorballs.get(0).getX());
                    colorballs.get(1).setY(colorballs.get(2).getY());
                    colorballs.get(3).setX(colorballs.get(2).getX());
                    colorballs.get(3).setY(colorballs.get(0).getY());
                    canvas.drawRect(point1.x, point3.y, point3.x, point1.y,
                            paint);
                } else {
                    colorballs.get(0).setX(colorballs.get(1).getX());
                    colorballs.get(0).setY(colorballs.get(3).getY());
                    colorballs.get(2).setX(colorballs.get(3).getX());
                    colorballs.get(2).setY(colorballs.get(1).getY());
                    canvas.drawRect(point2.x, point4.y, point4.x, point2.y,
                            paint);
                }

                invalidate();
            }

            break;

        case MotionEvent.ACTION_UP:
            // touch drop - just do things here after dropping

            break;
        }
        // redraw the canvas
        invalidate();
        return true;

    }

    public void shade_region_between_points() {
        canvas.drawRect(point1.x, point3.y, point3.x, point1.y, paint);
    }
}

Following class is used to store objects

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

public class ColorBall {

    Bitmap bitmap;
    Context mContext;
    Point point;
    int id;
    static int count = 0;

    public ColorBall(Context context, int resourceId, Point point) {
        this.id = count++;
        bitmap = BitmapFactory.decodeResource(context.getResources(),
                resourceId);
        mContext = context;
        this.point = point;
    }

    public int getWidthOfBall() {
        return bitmap.getWidth();
    }

    public int getHeightOfBall() {
        return bitmap.getHeight();
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public int getX() {
        return point.x;
    }

    public int getY() {
        return point.y;
    }

    public int getID() {
        return id;
    }

    public void setX(int x) {
        point.x = x;
    }

    public void setY(int y) {
        point.y = y;
    }
}
Sargeant answered 3/5, 2013 at 12:46 Comment(6)
the id dynamically changing is that so..?Shellback
@chintan-rathod Thanks for this answer. Please let me know how to stop the overlapping and crossing of sides.Deckard
@Tauqir I just put off development these days for this problem. So need to check my friend.Sargeant
@Tauqir use Math.min and Math.max to prevent sides from becoming too short or too long, and to keep the entire view inside its bounds... please see my answer.Nanceenancey
Hi @ChintanRathod How can I stop them from overlapping ?Shorter
@Shorter Check the coordinates in onTouchEvent method. Like in if condition, before drawing, it checks the coordinates and place the desired circle image.Sargeant
N
4

Working Demonstration https://www.youtube.com/watch?v=BfYd7Xa-tCc

I wasn't happy with the highest rated answers for a couple of reasons.

  1. they were not easily usable as a view in xml--the attributes are missing so the view is not easily recyclable.

  2. it seemed silly to be making bitmaps when dropping in a drawable in xml is easier

  3. they don't account for the edges of the view

  4. they don't prevent inversion of the rectangle by dragging too far to the left or top

  5. they don't account for the offset from the touch position to the center of the corner points

  6. they both depend on a depreciated bitmap method

  7. and most importantly, I couldn't get either of them to actually work

I grant that my solution is specialized to a square view, but it can be adjusted fairly easily to have independent X and Y side lengths.

place this in the res/values folder:

custom_attributes.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="IconCropView">
        <attr name="minimumSide" format="dimension"/>
        <attr name="resizeCornerDrawable" format="reference"/>
        <attr name="moveCornerDrawable" format="reference"/>
        <attr name="cornerColor" format="color"/>
        <attr name="edgeColor" format="color" />
        <attr name="outsideCropColor" format="color" />
        <attr name="cornerSize" format="dimension" />
    </declare-styleable>
</resources>

Add this to a java package IconCropView.java

package your_package;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import your_package.R;

public class IconCropView extends View {

//contants strings
private static final String TAG = "IconCropView";

//drawing objects
private Paint paint;

//point objects
private Point[] points;
private Point start;
private Point offset;

//variable ints
private int minimumSideLength;
private int side;
private int halfCorner;
private int cornerColor;
private int edgeColor;
private int outsideColor;
private int corner = 5;

//variable booleans
private boolean initialized = false;

//drawables
private Drawable moveDrawable;
private Drawable resizeDrawable1, resizeDrawable2, resizeDrawable3;

//context
Context context;

public IconCropView(Context context) {
    super(context);
    this.context = context;
    init(null);
}

public IconCropView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    this.context = context;
    init(attrs);
}

public IconCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.context = context;
    init(attrs);
}

public IconCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    this.context = context;
    init(attrs);
}

private void init(@Nullable AttributeSet attrs){

    paint = new Paint();
    start = new Point();
    offset = new Point();

    TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.IconCropView, 0, 0);

    //initial dimensions
    minimumSideLength = ta.getDimensionPixelSize(R.styleable.IconCropView_minimumSide, 20);
    side = minimumSideLength;
    halfCorner = (ta.getDimensionPixelSize(R.styleable.IconCropView_cornerSize, 20))/2;

    //colors
    cornerColor = ta.getColor(R.styleable.IconCropView_cornerColor, Color.BLACK);
    edgeColor = ta.getColor(R.styleable.IconCropView_edgeColor, Color.WHITE);
    outsideColor = ta.getColor(R.styleable.IconCropView_outsideCropColor, Color.parseColor("#00000088"));

    //initialize corners;
    points = new Point[4];

    points[0] = new Point();
    points[1] = new Point();
    points[2] = new Point();
    points[3] = new Point();

    //init corner locations;
    //top left
    points[0].x = 0;
    points[0].y = 0;

    //top right
    points[1].x = minimumSideLength;
    points[1].y = 0;

    //bottom left
    points[2].x = 0;
    points[2].y = minimumSideLength;

    //bottom right
    points[3].x = minimumSideLength;
    points[3].y = minimumSideLength;

    //init drawables
    moveDrawable = ta.getDrawable(R.styleable.IconCropView_moveCornerDrawable);
    resizeDrawable1 = ta.getDrawable(R.styleable.IconCropView_resizeCornerDrawable);
    resizeDrawable2 = ta.getDrawable(R.styleable.IconCropView_resizeCornerDrawable);
    resizeDrawable3 = ta.getDrawable(R.styleable.IconCropView_resizeCornerDrawable);

    //set drawable colors
    moveDrawable.setTint(cornerColor);
    resizeDrawable1.setTint(cornerColor);
    resizeDrawable2.setTint(cornerColor);
    resizeDrawable3.setTint(cornerColor);

    //recycle attributes
    ta.recycle();

    //set initialized to true
    initialized = true;

}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //set paint to draw edge, stroke
    if(initialized) {
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setColor(edgeColor);
        paint.setStrokeWidth(4);

        //crop rectangle
        canvas.drawRect(points[0].x + halfCorner,points[0].y + halfCorner, points[0].x + halfCorner + side, points[0].y + halfCorner + side, paint);

        //set paint to draw outside color, fill
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(outsideColor);

        //top rectangle
        canvas.drawRect(0, 0, canvas.getWidth(), points[0].y + halfCorner, paint);
        //left rectangle
        canvas.drawRect(0, points[0].y + halfCorner, points[0].x + halfCorner, canvas.getHeight(), paint);
        //right rectangle
        canvas.drawRect(points[0].x + halfCorner + side, points[0].y + halfCorner, canvas.getWidth(), points[0].y + halfCorner + side, paint);
        //bottom rectangle
        canvas.drawRect(points[0].x + halfCorner, points[0].y + halfCorner + side, canvas.getWidth(), canvas.getHeight(), paint);

        //set bounds of drawables
        moveDrawable.setBounds(points[0].x, points[0].y, points[0].x + halfCorner*2, points[0].y + halfCorner*2);
        resizeDrawable1.setBounds(points[1].x, points[1].y, points[1].x + halfCorner*2, points[1].y + halfCorner*2);
        resizeDrawable2.setBounds(points[2].x, points[2].y, points[2].x + halfCorner*2, points[2].y + halfCorner*2);
        resizeDrawable3.setBounds(points[3].x, points[3].y, points[3].x + halfCorner*2, points[3].y+ halfCorner*2);

        //place corner drawables
        moveDrawable.draw(canvas);
        resizeDrawable1.draw(canvas);
        resizeDrawable2.draw(canvas);
        resizeDrawable3.draw(canvas);

    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    //return super.onTouchEvent(event);
    switch(event.getActionMasked()){
        case MotionEvent.ACTION_DOWN:{

            //get the coordinates
            start.x = (int)event.getX();
            start.y = (int)event.getY();

            //get the corner touched if any
            corner = getCorner(start.x, start.y);

            //get the offset of touch(x,y) from corner top-left point
            offset = getOffset(start.x, start.y, corner);

            //account for touch offset in starting point
            start.x = start.x - offset.x;
            start.y = start.y - offset.y;

            break;
        }
        case MotionEvent.ACTION_UP:{

        }
        case MotionEvent.ACTION_MOVE:{
            if(corner == 0) {
                points[0].x = Math.max(points[0].x + (int) Math.min(Math.floor((event.getX() - start.x - offset.x)), Math.floor(getWidth() - points[0].x - 2*halfCorner - side)), 0);
                points[1].x = Math.max(points[1].x + (int) Math.min(Math.floor((event.getX() - start.x - offset.x)), Math.floor(getWidth() - points[1].x - 2*halfCorner)), side);
                points[2].x = Math.max(points[2].x + (int) Math.min(Math.floor((event.getX() - start.x - offset.x)), Math.floor(getWidth() - points[2].x - 2*halfCorner - side)), 0);
                points[3].x = Math.max(points[3].x + (int) Math.min(Math.floor((event.getX() - start.x - offset.x)), Math.floor(getWidth() - points[3].x - 2*halfCorner)), side);

                points[0].y = Math.max(points[0].y + (int) Math.min(Math.floor((event.getY() - start.y - offset.y)), Math.floor(getHeight() - points[0].y - 2*halfCorner - side)), 0);
                points[1].y = Math.max(points[1].y + (int) Math.min(Math.floor((event.getY() - start.y - offset.y)), Math.floor(getHeight() - points[1].y - 2*halfCorner - side)), 0);
                points[2].y = Math.max(points[2].y + (int) Math.min(Math.floor((event.getY() - start.y - offset.y)), Math.floor(getHeight() - points[2].y - 2*halfCorner)), side);
                points[3].y = Math.max(points[3].y + (int) Math.min(Math.floor((event.getY() - start.y - offset.y)), Math.floor(getHeight() - points[3].y - 2*halfCorner)), side);

                start.x = points[0].x;
                start.y = points[0].y;
                invalidate();
            }else if (corner == 1){
                side = Math.min((Math.min((Math.max(minimumSideLength, (int)(side + Math.floor(event.getX()) - start.x - offset.x))), side + (getWidth() - points[1].x - 2* halfCorner ))),side + (getHeight() - points[2].y - 2* halfCorner ));
                points[1].x = points[0].x + side;
                points[3].x = points[0].x + side;
                points[3].y = points[0].y + side;
                points[2].y = points[0].y + side;
                start.x = points[1].x;
                invalidate();
            }else if (corner == 2){
                side =  Math.min((Math.min((Math.max(minimumSideLength, (int)(side + Math.floor(event.getY()) - start.y - offset.y))), side + (getHeight() - points[2].y - 2* halfCorner ))),side + (getWidth() - points[1].x - 2* halfCorner ));
                points[2].y = points[0].y + side;
                points[3].y = points[0].y + side;
                points[3].x = points[0].x + side;
                points[1].x = points[0].x + side;
                start.y = points[2].y;
                invalidate();

            }else if (corner == 3){
                side = Math.min((Math.min((Math.min((Math.max(minimumSideLength, (int)(side + Math.floor(event.getX()) - start.x - offset.x))), side + (getWidth() - points[3].x - 2* halfCorner ))),side + (getHeight() - points[3].y - 2* halfCorner ))), Math.min((Math.min((Math.max(minimumSideLength, (int)(side + Math.floor(event.getY()) - start.y - offset.y))), side + (getHeight() - points[3].y - 2* halfCorner ))),side + (getWidth() - points[3].x - 2* halfCorner )));
                points[1].x = points[0].x + side;
                points[3].x = points[0].x + side;
                points[3].y = points[0].y + side;
                points[2].y = points[0].y + side;
                start.x = points[3].x;

                points[2].y = points[0].y + side;
                points[3].y = points[0].y + side;
                points[3].x = points[0].x + side;
                points[1].x = points[0].x + side;
                start.y = points[3].y;
                invalidate();

            }
            break;
        }
    }
    return true;
}

private int getCorner(float x, float y){
    int corner = 5;
    for (int i = 0; i < points.length; i++){
        float dx = x - points[i].x;
        float dy = y - points[i].y;
        int max = halfCorner * 2;
        if(dx <= max && dx >= 0 && dy <= max && dy >= 0){
            return i;
        }
    }
    return corner;
}

private Point getOffset(int left, int top, int corner){
    Point offset = new Point();
    if(corner == 5){
        offset.x = 0;
        offset.y = 0;
    }else{
        offset.x = left - points[corner].x;
        offset.y = top - points[corner].y;
    }
    return offset;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

In your your layout xml:

  1. You must specify a drawable move corner

  2. You must specify a drawable for the resize corners

  3. corner size defaults to 20px

  4. minimumSide must be at least corner size--default value is 20px

  5. You must include the namespace "http://schemas.android.com/apk/res-auto" in your root view (I used the alias, xlmn:app="http://schemas.android.com/apk/res-auto")

example xml layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/primary"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


<ImageView
    android:id="@+id/crop_image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginStart="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginEnd="10dp"
    android:layout_marginBottom="10dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="1.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.0"
    app:srcCompat="@mipmap/ic_launcher" />

<your_package.IconCropView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/transparent"
    app:minimumSide="60dp"
    app:resizeCornerDrawable="@drawable/adjust_edge_circle"
    app:moveCornerDrawable="@drawable/move_box_circle"
    app:cornerColor="@color/turq"
    app:edgeColor="@color/colorPrimary"
    app:outsideCropColor="@color/transparent_50"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:cornerSize="20dp"/>
Nanceenancey answered 9/12, 2018 at 8:14 Comment(6)
Could you please send drawables files?Cand
I just put png files in the resources/drawables folder and used them in my layout... that's the point of the way i made this thing--you can use any drawable resource you want to create the corners and the whole thing is reusable easily...Nanceenancey
thanks .. i have already did that but i have some another question .. i want to scale the rectangle depending on the direction of movement .. means if i drag horizontally then the rect rescaled horizontally and same for vertical .. how can we edit code to do that??Cand
i set it up with minimum distance between all four of the corners... you would have to rewrite the math function a little bit (it's a little sloppy anyway so that's not a bad idea to begin with) to account for a minimum vertical and a minumum horizontal distance... i think i set a dynamically calculated minimum so that it can't be smaller than size of one corner--that way any two contiguous corners could touch in the middle of but never overlap.... sorry i haven't looked at this in a long time and don't remember very wellNanceenancey
i took a little bit of a closer look at what I did way back when... to make the vertical and horizontal scaling you want, the math must be rewritten a little... i basically did one calculation for all side lengths based on whatever point you are touching at the time (except for the top left corner, that one will only reposition the box)... you just need to do a seperate calculation for the vertical sides and horizontal sides.Nanceenancey
i tried that but it didn't give me the correct result .. please check my question here .. stackoverflow.com/q/62976624/1731660 .. i shared my customized code and it only work with one point so it is easy to be customized .. you will need only to edit one line to be work .. please try to help me because i tried much .. you can add your answer there and i will accept it as right answer .. it will be kind of you .. thanks in advanceCand
T
2

The edited answer of Nguyen Minh Binh worked for me. But I needed to add a lite fix to prevent the ball id to get out of range. This occurred for me if I had to reopen the activity hosting the custom view. I fixed these line:

this.id = count++;

to:

if (count > 3) count = 0; 
this.id = count++;
Trammel answered 7/7, 2015 at 16:45 Comment(0)
S
0

The following code is a C# version (I am currently developing app on MonoDroid/Xamarin) of the requested code, but with some improvements and the ability to drag the rectangle. Still want to add some features, will edit it later.

namespace ImagePlayground
{
[Activity (Label = "MyView2")]          
public class MyView2 : View
{
    Graphics.Point[] points = new Graphics.Point[4];

    // Array that hold the circle
    private List<ResizeCircle> circles = new List<ResizeCircle>();

    // Variable to keep tracking of which circle is being dragged
    private int circleId = -1;

    // Points are grouped in groups of two so there's always only one fixed point       
    // groupId = 0 > Touch Inside the Rectangle
    // groupId = 1 > Points 0 and 2
    // groupId = 2 > Points 1 and 3
    int groupId = -1;

    // FirstTouch's Coordinate for Tracking on Dragging
    int xFirstTouch = 0;
    int yFirstTouch = 0;

    /** Main Bitmap **/
    private Bitmap mBitmap = null;

    /** Measured Size of the View **/
    private Rect mMeasuredRect;

    /** Paint to Draw Rectangles **/
    private Paint mRectPaint;

    public MyView2(Context ctx) : base (ctx){
        init (ctx);
    }

    public MyView2 (Context ctx, IAttributeSet attrs) : base (ctx, attrs){
        init (ctx);
    }

    public MyView2 (Context ctx, IAttributeSet attrs, int defStyle) : base(ctx,attrs,defStyle){
        init (ctx);
    }

    private void init(Context ctx){
        // For Touch Events
        Focusable = true;
        // Draw the Image on the Background
        mBitmap = BitmapFactory.DecodeResource(ctx.Resources, Resource.Drawable.bg);

        // Sets up the paint for the Drawable Rectangles
        mRectPaint = new Paint ();
        mRectPaint.Color = Android.Graphics.Color.Aqua;
        mRectPaint.StrokeWidth = 4;
        mRectPaint.SetStyle (Paint.Style.Stroke);
    }

    protected override void OnDraw(Canvas canvas){
        // Background Bitmap to Cover all Area
        canvas.DrawBitmap(mBitmap, null, mMeasuredRect, null);

        // Just draw the points only if it has already been initiated
        if (points [3] != null) {               
            int left, top, right, bottom;
            left = points [0].X;
            top = points [0].Y;
            right = points [0].X;
            bottom = points [0].Y;

            // Sets the circles' locations
            for (int i = 1; i < points.Length; i++) {
                left = left > points [i].X ? points [i].X : left;
                top = top > points [i].Y ? points [i].Y : top;
                right = right < points [i].X ? points [i].X : right;
                bottom = bottom < points [i].Y ? points [i].Y : bottom;
            }

            mRectPaint.AntiAlias = true;
            mRectPaint.Dither = true;
            mRectPaint.StrokeJoin = Paint.Join.Round;
            mRectPaint.StrokeWidth = 5;
            mRectPaint.SetStyle (Paint.Style.Stroke);
            mRectPaint.Color = Graphics.Color.ParseColor ("#0079A3");

            canvas.DrawRect (
                left + circles [0].GetCircleWidth () / 2,
                top + circles [0].GetCircleWidth () / 2, 
                right + circles [2].GetCircleWidth () / 2, 
                bottom + circles [2].GetCircleWidth () / 2, mRectPaint);

            // Fill The Rectangle
            mRectPaint.SetStyle (Paint.Style.Fill);
            mRectPaint.Color = Graphics.Color.ParseColor ("#B2D6E3");
            mRectPaint.Alpha = 75;
            mRectPaint.StrokeWidth = 0;

            canvas.DrawRect (
                left + circles [0].GetCircleWidth () / 2,
                top + circles [0].GetCircleWidth () / 2, 
                right + circles [2].GetCircleWidth () / 2, 
                bottom + circles [2].GetCircleWidth () / 2, mRectPaint);

            // DEBUG
            mRectPaint.Color = Graphics.Color.Red;
            mRectPaint.TextSize = 18;
            mRectPaint.StrokeWidth = 0;

            // Draw every circle on the right position
            for (int i = 0; i < circles.Count (); i++) {
                ResizeCircle circle = circles [i];
                float x = circle.GetX ();
                float y = circle.GetY ();
                canvas.DrawBitmap (circle.GetBitmap (), x, y,
                    mRectPaint);

                // DEBUG
  //                    canvas.DrawText ("" + (i + 1), circle.GetX (), circle.GetY (), mRectPaint);
            }
        }
    }

    public override bool OnTouchEvent(MotionEvent e){

        // Get the Coordinates of Touch
        int xTouch = (int) e.GetX ();
        int yTouch = (int) e.GetY ();
        int actionIndex = e.ActionIndex;

        switch (e.ActionMasked) {
        // In case user touch the screen
        case MotionEventActions.Down:

            // If no points were created
            if (points [0] == null) {
                // Offset to create the points
                int offset = 60;
                // Initialize a new Rectangle.
                points [0] = new Graphics.Point ();
                points [0].X = xTouch;
                points [0].Y = yTouch;

                points [1] = new Graphics.Point ();
                points [1].X = xTouch;
                points [1].Y = yTouch + offset;

                points [2] = new Graphics.Point ();
                points [2].X = xTouch + offset;
                points [2].Y = yTouch + offset;

                points [3] = new Graphics.Point ();
                points [3].X = xTouch + offset;
                points [3].Y = yTouch;

                // Add each circle to circles array
                foreach (Graphics.Point pt in points) {
                    circles.Add (new ResizeCircle (Context, Resource.Drawable.circle, pt));
                }
            } else {
                // Register Which Circle (if any) th user has touched
                groupId = getTouchedCircle (xTouch, yTouch);
                xFirstTouch = xTouch;
                yFirstTouch = yTouch;

            }
            break;
        case MotionEventActions.PointerDown:
            break;

        case MotionEventActions.Move:                                               
            if (groupId == 1 || groupId == 2) {

                // Move touched Circle as the finger moves
                circles[circleId].SetX(xTouch);
                circles[circleId].SetY(yTouch);

                // Move the two other circles accordingly
                if (groupId == 1) {
                    circles[1].SetX(circles[0].GetX());
                    circles[1].SetY(circles[2].GetY());
                    circles[3].SetX(circles[2].GetX());
                    circles[3].SetY(circles[0].GetY());
                } else {                        
                    circles[0].SetX(circles[1].GetX());
                    circles[0].SetY(circles[3].GetY());
                    circles[2].SetX(circles[3].GetX());
                    circles[2].SetY(circles[1].GetY());
                }
                Invalidate();
            } else if (groupId == 0){
                // Calculate the delta for the dragging
                int xDelta =  (xTouch-xFirstTouch);
                int yDelta =  (yTouch-yFirstTouch);
                xFirstTouch = xTouch;
                yFirstTouch = yTouch;

                // Move each circle accordingly
                foreach (ResizeCircle circle in circles) {
                    circle.SetX (circle.GetX () + xDelta);
                    circle.SetY (circle.GetY () + yDelta);
                }

                // Redraw the view
                Invalidate ();              
            }
            break;

        case MotionEventActions.Up:
            break;
        default:
            break;          
        }
        Invalidate ();
        return true;
    }


    private int getTouchedCircle(int xTouch, int yTouch){
        int groupId = -1;
        for (int i = 0; i < circles.Count; i++) {
            ResizeCircle circle = circles [i];

            // Check if the touch was inside the bounds of the circle
            int centerX = circle.GetX () + circle.GetCircleWidth ();
            int centerY = circle.GetY () + circle.GetCircleHeight ();

            // Calculate the radius from the touch to the center of the circle
            double radCircle = Math.Sqrt ((double)(((centerX - xTouch) * (centerX - xTouch)) + (centerY - yTouch)
                * (centerY - yTouch)));

            // If the touch was on one of the circles
            if (radCircle < circle.GetCircleWidth ()) {
                circleId = circle.GetID ();
                if (circleId == 1 || circleId == 3) {
                    groupId = 2;
                    break;
                } else {
                    groupId = 1;
                    break;
                }
            } else {
                // User didn't touch any of the circles nor the inside area
                groupId = -1;
            }
        }
        // If the touch wasn't on one of the circles, check if it was inside the rectangle
        if (groupId == -1) {
            List<int> xCoords = new List<int> ();
            List<int> yCoords = new List<int> ();

            // Gather Coordinates from all circles      
            foreach (ResizeCircle circle in circles){
                xCoords.Add (circle.GetX());
                yCoords.Add (circle.GetY());
            }

            // Store the max and min coordinates
            int minX = xCoords.Min ();
            int maxX = xCoords.Max ();
            int minY = yCoords.Min ();
            int maxY = yCoords.Max ();

            // Check if user has touched inside the rectangle
            if ((xTouch > minX && xTouch < maxX) && (yTouch > minY && yTouch < maxY)) {
                // User has touched inside the Rectangle
                groupId = 0;
            }
        }

        return groupId;
    }

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec){
        base.OnMeasure (widthMeasureSpec, heightMeasureSpec);

        mMeasuredRect = new Rect (0, 0, MeasuredWidth, MeasuredHeight);
    }

    public class ResizeCircle {

        Bitmap bitmap;
        Graphics.Point point;
        int id;
        static int count = 0;

        public ResizeCircle(Context context, int resourceId, Graphics.Point point) {
            this.id = count++;
            bitmap = BitmapFactory.DecodeResource(context.Resources,
                resourceId);
            Log.Debug("BITMAP" , bitmap.Height.ToString());
            this.point = point;
        }

        public int GetCircleWidth() {
            return bitmap.Width;
        }

        public int GetCircleHeight() {
            return bitmap.Height;
        }

        public Bitmap GetBitmap() {
            return bitmap;
        }

        public int GetX() {
            return point.X;
        }

        public int GetY() {
            return point.Y;
        }

        public int GetID() {
            return id;
        }

        public void SetX(int x) {
            point.X = x;
        }

        public void SetY(int y) {
            point.Y = y;
        }
    }
   }
  }
Shoulder answered 29/6, 2015 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.