【问题标题】:How to create a resizable rectangle with user touch events on Android?如何在 Android 上使用用户触摸事件创建可调整大小的矩形?
【发布时间】:2012-02-16 22:44:17
【问题描述】:

我想创建一个矩形形状,它会随着用户的触摸而调整大小。下图是我想做的一个很好的例子:

有没有这样的例子?我需要学习什么来实现它?

提前致谢,

【问题讨论】:

  • 答案取决于你已经知道什么。你有安卓经验吗?有 Java 经验吗?一般的 GUI 系统?有任何编程经验吗?我不是在刻薄,只是从问题中看不出来。
  • 是裁剪功能,你要实现吗?
  • @SevaAlekseyev 我有 Android 经验,但我从未实现过自定义视图。
  • @silent 是的,这正是我想要实现的裁剪功能,但我无法使用内置的图像裁剪器,因为我需要该活动中的一些其他功能。

标签: android shape user-interaction


【解决方案1】:

要实现自定义视图,您从 View 派生一个类 :) 覆盖 onDraw() 用于外观,覆盖 onTouchEvent() 用于输入处理。请注意,在 Android 中,您不能在 onDraw() 之外的视图上绘图;如果要刷新视图,请致电invalidate()

您可以将可拖动的角实现为单独的视图。对于外观,只需使用现成的图像(随意源自ImageView)。拖动实现为响应触摸事件移动视图。 RelativeLayout 是您任意视图定位的朋友。

您可以在布局中添加自制视图;只需转到 XML 编辑并键入 <com.mypackage.MyViewClass> 元素。

【讨论】:

  • 一个很好的解决方案,但是很难实现对自定义视图的缩放,因为相对布局中的子视图不会相应地调整大小/缩放。因此,大多数用户在 onTouchEvent 上手动实现 onDraw 触摸处理 - 就像另一个使用 DrawView 和 ColorBalls 的示例一样。
【解决方案2】:

以下代码是在触摸底座上绘制矩形。

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

以下类用于存储对象

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

【讨论】:

  • id动态变化是这样..?
  • @chintan-rathod 感谢您的回答。请告诉我如何停止边的重叠和交叉。
  • @Tauqir 这些天我只是因为这个问题推迟了开发。所以需要检查我的朋友。
  • @Tauqir 使用 Math.min 和 Math.max 来防止边变得太短或太长,并将整个视图保持在其范围内......请参阅我的答案。
  • 嗨@ChintanRathod 我怎样才能阻止它们重叠?
【解决方案3】:

Chintan Rathod 的回答是很好的解决方案,但是当他绘制矩形时出现了问题。我只是编辑了一些代码行以使其与用户触摸事件一起正常工作。现在,您可以将此视图添加到您的布局中,然后触摸进行绘制。

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

【讨论】:

  • +1 用于升级答案。我只是把它推迟,因为没有时间强调问题。 :)
  • 在计算 centerX 和 centerY 时,您是否应该使用 ball.getWidthOfBall()/2ball.getHeightOfBall()/2
  • @Nguyen Minh Binh 如何将我的图像添加到这个自定义视图(DrawView)中
  • 谢谢伙计。如果你能解释一下你在@ChintanRathod 的回答中到底发现了什么错误会更好?
  • 我已经为这个GIF link写了一个demo并上传了代码Code Link
【解决方案4】:

以下代码是所请求代码的 C# 版本(我目前正在 MonoDroid/Xamarin 上开发应用程序),但有一些改进和拖动矩形的能力。还想增加一些功能,以后再编辑。

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

【讨论】:

    【解决方案5】:

    Nguyen Minh Binh 的编辑答案对我有用。但我需要添加一个精简版修复程序以防止球 id 超出范围。如果我必须重新打开托管自定义视图的活动,就会发生这种情况。我修复了这些行:

    this.id = count++;

    到:

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

    【讨论】:

      【解决方案6】:

      工作演示 https://www.youtube.com/watch?v=BfYd7Xa-tCc

      出于几个原因,我对评分最高的答案不满意。

      1. 它们不容易用作 xml 中的视图——缺少属性,因此视图不容易回收。

      2. 当在 xml 中放入可绘制对象更容易时,制作位图似乎很愚蠢

      3. 他们不考虑视图的边缘

      4. 它们不会通过向左或顶部拖动太远来防止矩形反转

      5. 他们不考虑从触摸位置到角点中心的偏移

      6. 它们都依赖于折旧的位图方法

      7. 最重要的是,我无法让它们中的任何一个真正工作

      我承认我的解决方案专门用于方形视图,但它可以很容易地调整为具有独立的 X 和 Y 边长。

      将其放在 res/values 文件夹中:

      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>
      

      将此添加到 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);
      }
      }
      

      在您的布局 xml 中:

      1. 您必须指定可绘制的移动角

      2. 您必须为调整角指定一个可绘制对象

      3. 角落尺寸默认为20px

      4. minimumSide 必须是至少角尺寸--默认值为 20px

      5. 您必须在根视图中包含命名空间“http://schemas.android.com/apk/res-auto”(我使用了别名 xlmn:app="http:// /schemas.android.com/apk/res-auto")

      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"/>
      

      【讨论】:

      • 能否请您发送可绘制文件?
      • 我只是将 png 文件放在资源/drawables 文件夹中并在我的布局中使用它们......这就是我制作这个东西的方式的重点——你可以使用任何你想要创建的可绘制资源角落和整个东西很容易重复使用......
      • 谢谢 .. 我已经这样做了,但我还有其他问题 .. 我想根据移动方向缩放矩形 .. 意味着如果我水平拖动,那么矩形会水平重新缩放并且相同对于垂直..我们如何编辑代码来做到这一点??
      • 我将它设置为所有四个角之间的最小距离...您将不得不稍微重写数学函数(无论如何这有点草率,所以这不是一个坏主意)考虑最小垂直距离和最小水平距离......我想我设置了一个动态计算的最小值,这样它就不能小于一个角的大小——这样任何两个连续的角都可以在中间接触但从不重叠....对不起,我很久没看这个了,记不太清楚了
      • 我仔细研究了一下我在很久以前所做的事情......为了实现你想要的垂直和水平缩放,数学必须重写一点......我基本上做到了根据您当时触摸的任何点计算所有边长(除了左上角,只会重新定位框)...您只需要对垂直边和水平边进行单独计算.
      猜你喜欢
      • 1970-01-01
      • 2012-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-06
      • 2012-07-05
      • 1970-01-01
      相关资源
      最近更新 更多