¿Cómo crear un rectángulo redimensionable con eventos táctiles de usuario en Android?

Quiero crear una forma rectangular que se redimensionará con los toques del usuario. Debajo de la imagen hay un buen ejemplo de lo que quiero hacer:

Rectángulo redimensionable

¿Hay algún ejemplo así? ¿Qué necesito estudiar para implementar esto?

Gracias por adelantado,

Para implementar una vista personalizada, se deriva una clase de Vista 🙂 Ignorar onDraw() para apariencia, anular onTouchEvent() para procesamiento de entrada. Tenga en cuenta que en Android, no puede dibujar en vista fuera de onDraw() ; si desea actualizar la vista, llame a invalidate() .

Puede implementar esquinas arrastrables como vistas separadas. Para ver, simplemente use imágenes ya hechas (no dude en obtener de ImageView ). Arrastrar se implementa como mover su vista en respuesta a eventos táctiles. RelativeLayout es tu amigo para la vista arbitraria positining.

Puede agregar vistas hechas en casa al diseño; solo vaya a edición XML y escriba un elemento .

La respuesta de Chintan Rathod fue una gran solución, pero hay algo mal cuando dibuja el rectángulo. Solo edito algunas líneas de código para que funcione correctamente con el evento táctil del usuario. Ahora puede agregar esta vista a su diseño y luego tocar para dibujar.

 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 colorballs = new ArrayList(); // 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; } } } 

El siguiente código es dibujar un rectángulo en la base táctil.

 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 colorballs = new ArrayList(); // 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); } } 

La siguiente clase se usa para almacenar objetos

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

El siguiente código es una versión de C # (actualmente estoy desarrollando una aplicación en MonoDroid / Xamarin) del código solicitado, pero con algunas mejoras y la capacidad de arrastrar el rectángulo. Todavía desea agregar algunas características, las editará más tarde.

 namespace ImagePlayground { [Activity (Label = "MyView2")] public class MyView2 : View { Graphics.Point[] points = new Graphics.Point[4]; // Array that hold the circle private List circles = new List(); // 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 xCoords = new List (); List yCoords = new List (); // 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; } } } } 

La respuesta editada de Nguyen Minh Binh funcionó para mí. Pero necesitaba agregar una solución ligera para evitar que la identificación de la bola se salga del scope. Esto ocurrió para mí si tuviera que volver a abrir la actividad que aloja la vista personalizada. Yo arreglé esta línea:

this.id = count ++;

a:

 if (count > 3) count = 0; this.id = count++;