在安卓中使Canvas上的圆形可拖动。
在安卓中使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
问题的出现原因:
可能在处理多点触控/绘图时遇到了问题。
解决方法:
参考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 HashSetmCircles = 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中实现可拖动的圆形画布。