studio - layer list android
¿Cómo crear un rectángulo redimensionable con eventos táctiles de usuario en Android? (6)
Quiero crear una forma rectangular que se redimensionará con los toques del usuario. La imagen de abajo es un buen ejemplo de lo que quiero hacer:
¿Hay algún ejemplo así? ¿Qué necesito estudiar para implementar esto?
Gracias por adelantado,
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<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);
}
}
La siguiente clase se utiliza 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 quieres 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<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;
}
}
}
}
La respuesta de Chintan Rathod fue una gran solución, pero hay algo malo cuando Él dibuja el rectángulo. Acabo de editar 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<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;
}
}
}
La respuesta editada de Nguyen Minh Binh funcionó para mí. Pero necesitaba agregar una solución liviana para evitar que la identificación de la pelota saliera del alcance. Esto ocurrió para mí si tuviera que volver a abrir la actividad que aloja la vista personalizada. He arreglado estas líneas:
esto.id = cuenta ++;
a:
if (count > 3) count = 0;
this.id = count++;
Demostración de trabajo https://www.youtube.com/watch?v=BfYd7Xa-tCc
No estuve contento con las respuestas mejor calificadas por un par de razones.
1) no se podían usar fácilmente como una vista en xml; faltan los atributos, por lo que la vista no es fácilmente reciclable.
2) Parecía una tontería hacer mapas de bits al colocar un dibujo en xml es más fácil
3) No tienen en cuenta los bordes de la vista.
4) no evitan la inversión del rectángulo arrastrando demasiado hacia la izquierda o hacia arriba
5) no tienen en cuenta el desplazamiento desde la posición táctil hasta el centro de los puntos de esquina
6) ambos dependen de un método de mapa de bits depreciado
7) y lo más importante, no pude hacer que ninguno de ellos realmente funcione
Admito que mi solución está especializada para una vista cuadrada, pero se puede ajustar con bastante facilidad para tener un mínimo de X y un mínimo de Y.
colóquelo en la carpeta res / valores:
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>
Añade esto a un paquete java 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);
}
}
En tu diseño xml:
1) Debes especificar una esquina de movimiento dibujable
2) Debe especificar un dibujo para las esquinas de cambio de tamaño
3) el tamaño de la esquina por defecto es 20px
4) minimumSide debe tener al menos el tamaño de la esquina - el valor predeterminado es 20px
5) Debe incluir el espacio de nombres " http://schemas.android.com/apk/res-auto " en su vista raíz (usé el alias, xlmn: app = "http://schemas.android.com/apk / res-auto ")
ejemplo de diseño xml
<?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"/>
Para implementar una vista personalizada, deriva una clase de la Vista :) Sobrescriba onDraw()
para las apariencias, anule onTouchEvent()
para el procesamiento de entrada. Tenga en cuenta que en Android, no puede dibujar en la vista fuera de onDraw()
; Si desea actualizar la vista, llame a invalidate()
.
Puede implementar esquinas arrastrables como vistas separadas. Para las apariencias, solo use imágenes listas para usar (no dude en derivar de ImageView
). El arrastre se implementa como mover su vista en respuesta a eventos táctiles. RelativeLayout
es su amigo para la opinión arbitraria.
Puedes añadir vistas caseras al diseño; simplemente vaya a la edición XML y escriba un elemento <com.mypackage.MyViewClass>
.