在安卓中使Canvas上的圆形可拖动。

6 浏览
0 Comments

在安卓中使Canvas上的圆形可拖动。

我正在学习自定义视图,并成功创建了三个圆和它们之间的连线。我想知道如何使这些圆可拖动。

首先,我想知道我如何使用onTouch()点击圆内部,然后根据点击位置更新这些圆的位置。

我的自定义视图CustomDrawing:

public class CustomDrawing extends View {

private static final String TAG = "CustomDrawing";

private Paint circlePaint;

private Paint linePaint;

private Paint textPaint;

private int centerX, centerY;

private float circleSize = 80;

public CustomDrawing(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

setFocusable(true);

setFocusableInTouchMode(true);

setupPaint();

}

private void setupPaint() {

circlePaint = new Paint();

circlePaint.setColor(Color.BLACK);

circlePaint.setAntiAlias(true);

circlePaint.setStrokeWidth(5);

circlePaint.setStyle(Paint.Style.STROKE);

circlePaint.setStrokeJoin(Paint.Join.ROUND);

circlePaint.setStrokeCap(Paint.Cap.ROUND);

linePaint = new Paint();

linePaint.setColor(Color.WHITE);

linePaint.setAntiAlias(true);

linePaint.setStrokeWidth((float) 1.5);

textPaint = new Paint();

textPaint.setColor(Color.WHITE);

textPaint.setTextSize(60);

textPaint.setTextAlign(Paint.Align.CENTER);

textPaint.setFakeBoldText(true);

}

@Override

protected void onDraw(Canvas canvas) {

centerX = canvas.getWidth()/2;

centerY = canvas.getHeight()/2;

//绘制左上角圆

canvas.drawCircle(circleSize, circleSize, 80, circlePaint);

canvas.drawText("LC", circleSize, getyPositionOfText(circleSize, textPaint), textPaint);

//绘制中心圆

circlePaint.setColor(Color.GREEN);

canvas.drawCircle(centerX, centerY, circleSize, circlePaint);

canvas.drawText("CC", centerX, getyPositionOfText(canvas.getHeight()/2, textPaint), textPaint);

//绘制右下角圆

circlePaint.setColor(Color.BLACK);

canvas.drawCircle(canvas.getWidth() - circleSize, canvas.getHeight() - circleSize, 80, circlePaint);

//绘制中心到左上角和中心到右上角的连线

canvas.drawLine(centerX, centerY, circleSize, circleSize, linePaint);

canvas.drawLine(centerX, centerY, canvas.getWidth() - circleSize, circleSize, linePaint);

//绘制中心到左下角和中心到右下角的连线

linePaint.setColor(Color.BLACK);

canvas.drawLine(centerX, centerY, circleSize, canvas.getHeight() - circleSize, linePaint);

canvas.drawLine(centerX, centerY, canvas.getWidth() - circleSize, canvas.getHeight() - circleSize, linePaint);

linePaint.setColor(Color.WHITE);

canvas.drawLine(centerX, centerY, circleSize, canvas.getHeight()/2, linePaint);

linePaint.setColor(Color.BLACK);

canvas.drawLine(centerX, centerY, canvas.getWidth() - circleSize, canvas.getHeight()/2, linePaint);

//绘制左上角到左下角的连线

canvas.drawLine(circleSize, circleSize, circleSize, canvas.getHeight() - circleSize, linePaint);

//绘制右上角到右下角的连线

canvas.drawLine(canvas.getWidth() - circleSize, circleSize, canvas.getWidth() - circleSize, canvas.getHeight() - circleSize, linePaint);

linePaint.setColor(Color.GREEN);

canvas.drawLine(circleSize, circleSize, canvas.getWidth()-circleSize, circleSize, linePaint);

canvas.drawLine(circleSize, canvas.getHeight() -circleSize, canvas.getWidth()-circleSize, canvas.getHeight() -circleSize, linePaint);

}

private int getyPositionOfText(float yPositionOfText, Paint mPaint){

return (int) ((yPositionOfText) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

float pointX = event.getX();

float pointY = event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

return true;

case MotionEvent.ACTION_MOVE:

break;

default:

return false;

}

postInvalidate();

return true;

}

}

此外,建议改进如下:

要使一个视图可拖动,我使用以下代码:

@Override

public boolean onTouch(View v, MotionEvent event) {

switch (event.getAction() & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

dX = v.getX() - event.getRawX();

dY = v.getY() - event.getRawY();

break;

case MotionEvent.ACTION_POINTER_UP:

break;

case MotionEvent.ACTION_MOVE:

v.animate()

.x(event.getRawX() + dX)

.y(event.getRawY() + dY)

.setDuration(0)

.start();

break;

}

invalidate(); //重新绘制

return true;

}

以上代码适用于视图。我如何将其用于动画(拖动)圆圈?

为了检测任何位置是否在圆内,可以使用以下代码:

Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r

0
0 Comments

问题的出现原因:

可能在处理多点触控/绘图时遇到了问题。

解决方法:

参考Android开发者网站和Android博客上关于处理多点触控/绘图的教程。

根据这些教程,我创建了一个示例,我认为这个示例与您尝试实现的内容相似(不完整的圆形绘制 - 圆形由单个触摸生成)。

代码如下:

public class CirclesDrawingView extends View {
    private static final String TAG = "CirclesDrawingView";
    /** Main bitmap */
    private Bitmap mBitmap = null;
    private Rect mMeasuredRect;
    
    /** Stores data about single circle */
    private static class CircleArea {
        int radius;
        int centerX;
        int centerY;
        
        CircleArea(int centerX, int centerY, int radius) {
            this.radius = radius;
            this.centerX = centerX;
            this.centerY = centerY;
        }
        
        public String toString() {
            return "Circle[" + centerX + ", " + centerY + ", " + radius + "]";
        }
    }
    
    /** Paint to draw circles */
    private Paint mCirclePaint;
    private final Random mRadiusGenerator = new Random();
    // Radius limit in pixels
    private final static int RADIUS_LIMIT = 100;
    private static final int CIRCLES_LIMIT = 3;
    /** All available circles */
    private HashSet mCircles = new HashSet(CIRCLES_LIMIT);
    private SparseArray mCirclePointer = new SparseArray(CIRCLES_LIMIT);
    
    /**
     * Default constructor
     *
     * @param ct {android.content.Context}
     */
    public CirclesDrawingView(final Context ct) {
        super(ct);
        init(ct);
    }
    
    public CirclesDrawingView(final Context ct, final AttributeSet attrs) {
        super(ct, attrs);
        init(ct);
    }
    
    public CirclesDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) {
        super(ct, attrs, defStyle);
        init(ct);
    }
    
    private void init(final Context ct) {
        // Generate bitmap used for background
        mBitmap = BitmapFactory.decodeResource(ct.getResources(), R.drawable.up_image);
        mCirclePaint = new Paint();
        mCirclePaint.setColor(Color.BLUE);
        mCirclePaint.setStrokeWidth(40);
        mCirclePaint.setStyle(Paint.Style.FILL);
    }
    
    public void onDraw(final Canvas canv) {
        // background bitmap to cover all area
        canv.drawBitmap(mBitmap, null, mMeasuredRect, null);
        for (CircleArea circle : mCircles) {
            canv.drawCircle(circle.centerX, circle.centerY, circle.radius, mCirclePaint);
        }
    }
    
    public boolean onTouchEvent(final MotionEvent event) {
        boolean handled = false;
        CircleArea touchedCircle;
        int xTouch;
        int yTouch;
        int pointerId;
        int actionIndex = event.getActionIndex();
        
        // get touch event coordinates and make transparent circle from it
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // it's the first pointer, so clear all existing pointers data
                clearCirclePointer();
                xTouch = (int) event.getX(0);
                yTouch = (int) event.getY(0);
                // check if we've touched inside some circle
                touchedCircle = obtainTouchedCircle(xTouch, yTouch);
                touchedCircle.centerX = xTouch;
                touchedCircle.centerY = yTouch;
                mCirclePointer.put(event.getPointerId(0), touchedCircle);
                invalidate();
                handled = true;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.w(TAG, "Pointer down");
                // It secondary pointers, so obtain their ids and check circles
                pointerId = event.getPointerId(actionIndex);
                xTouch = (int) event.getX(actionIndex);
                yTouch = (int) event.getY(actionIndex);
                // check if we've touched inside some circle
                touchedCircle = obtainTouchedCircle(xTouch, yTouch);
                mCirclePointer.put(pointerId, touchedCircle);
                touchedCircle.centerX = xTouch;
                touchedCircle.centerY = yTouch;
                invalidate();
                handled = true;
                break;
            case MotionEvent.ACTION_MOVE:
                final int pointerCount = event.getPointerCount();
                Log.w(TAG, "Move");
                for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) {
                    // Some pointer has moved, search it by pointer id
                    pointerId = event.getPointerId(actionIndex);
                    xTouch = (int) event.getX(actionIndex);
                    yTouch = (int) event.getY(actionIndex);
                    touchedCircle = mCirclePointer.get(pointerId);
                    if (null != touchedCircle) {
                        touchedCircle.centerX = xTouch;
                        touchedCircle.centerY = yTouch;
                    }
                }
                invalidate();
                handled = true;
                break;
            case MotionEvent.ACTION_UP:
                clearCirclePointer();
                invalidate();
                handled = true;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // not general pointer was up
                pointerId = event.getPointerId(actionIndex);
                mCirclePointer.remove(pointerId);
                invalidate();
                handled = true;
                break;
            case MotionEvent.ACTION_CANCEL:
                handled = true;
                break;
            default:
                // do nothing
                break;
        }
        
        return super.onTouchEvent(event) || handled;
    }
    
    /**
     * Clears all CircleArea - pointer id relations
     */
    private void clearCirclePointer() {
        Log.w(TAG, "clearCirclePointer");
        mCirclePointer.clear();
    }
    
    /**
     * Search and creates new (if needed) circle based on touch area
     *
     * @param xTouch int x of touch
     * @param yTouch int y of touch
     *
     * @return {CircleArea} obtained circle
     */
    private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) {
        CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch);
        if (null == touchedCircle) {
            touchedCircle = new CircleArea(xTouch, yTouch, mRadiusGenerator.nextInt(RADIUS_LIMIT) + RADIUS_LIMIT);
            if (mCircles.size() == CIRCLES_LIMIT) {
                Log.w(TAG, "Clear all circles, size is " + mCircles.size());
                // remove first circle
                mCircles.clear();
            }
            Log.w(TAG, "Added circle " + touchedCircle);
            mCircles.add(touchedCircle);
        }
        return touchedCircle;
    }
    
    /**
     * Determines touched circle
     *
     * @param xTouch int x touch coordinate
     * @param yTouch int y touch coordinate
     *
     * @return {CircleArea} touched circle or null if no circle has been touched
     */
    private CircleArea getTouchedCircle(final int xTouch, final int yTouch) {
        CircleArea touched = null;
        for (CircleArea circle : mCircles) {
            if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
                touched = circle;
                break;
            }
        }
        return touched;
    }
    
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mMeasuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
    }
}

Activity中只包含`setContentView(R.layout.main)`,其中main.xml的内容如下:


    

这样就可以在Android中实现可拖动的圆形画布。

0