在画布上绘制视图被拖动的任何位置
Posted
技术标签:
【中文标题】在画布上绘制视图被拖动的任何位置【英文标题】:Draw on canvas wherever view is dragged 【发布时间】:2018-11-21 04:43:46 【问题描述】:我正在尝试使用画布创建一个简单的绘画应用程序。最终产品将是用户选择一种工具(笔、记号笔、橡皮擦等)并在画布上拖动该工具的任何位置进行相应的绘制或擦除。
到目前为止,我只有一支笔,我正在尝试在画布中拖动笔的任何位置画一条线。但是,我发现这项任务具有挑战性。我可以拖动笔,我可以画线,但我不能在拖动笔时画画。我正在联系 SO 社区以弥合这两个功能。
以下是我所拥有的:
MainActivity.java
public class MainActivity extends AppCompatActivity
private ViewGroup rootLayout;
private int _xDelta;
private int _yDelta;
private RelativeLayout pl;
private ImageView w1;
private boolean clicked1 = false;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CanvasView canvasView = new CanvasView(MainActivity.this);
w1 = (ImageView) findViewById(R.id.wand1);
pl = (RelativeLayout) findViewById(R.id.coordAct);
RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.coordAct);
rootLayout.addView(canvasView);
RelativeLayout.LayoutParams layoutParams1w2 = new RelativeLayout.LayoutParams(getScreenWidth(), getScreenHeight() / 2);
layoutParams1w2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
canvasView.setLayoutParams(layoutParams1w2);
w1.setOnTouchListener(new ChoiceTouchListener());
public static int getScreenWidth()
return Resources.getSystem().getDisplayMetrics().widthPixels;
public static int getScreenHeight()
return Resources.getSystem().getDisplayMetrics().heightPixels;
public class ChoiceTouchListener implements View.OnTouchListener
public boolean onTouch(View view, MotionEvent event)
if (!clicked1)
rootLayout = (ViewGroup) w1.getParent();
if (rootLayout != null)
// detach the child from parent
rootLayout.removeView(w1);
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(300, 300);
pl.addView(w1);
w1.setLayoutParams(layoutParams1);
clicked1 = true;
final int X = (int) event.getRawX();
final int Y = (int) event.getRawY();
switch (event.getAction() & MotionEvent.ACTION_MASK)
case MotionEvent.ACTION_DOWN:
RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
_xDelta = X - lParams.leftMargin;
_yDelta = Y - lParams.topMargin;
break;
case MotionEvent.ACTION_MOVE:
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view
.getLayoutParams();
layoutParams.leftMargin = X - _xDelta;
layoutParams.topMargin = Y - _yDelta;
layoutParams.rightMargin = -250;
layoutParams.bottomMargin = -250;
view.setLayoutParams(layoutParams);
break;
rootLayout.invalidate();
return true;
CanvasView.java
public class CanvasView extends View
Context context;
int width, height;
Bitmap bitmap;
Path path;
Canvas canvas;
Paint paint;
float mX, mY;
static final float TOLERANCE=4;
public CanvasView(Context context)
super(context);
this.context=context;
path=new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(50);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w,h,oldw,oldh);
bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
canvas=new Canvas(bitmap);
public void startTouch(float x, float y)
path.moveTo(x, y);
mX = x;
mY = y;
public void moveTouch(float x, float y)
float dx = Math.abs(x-mX);
float dy = Math.abs(y-mY);
if(dx>=TOLERANCE || dy>= TOLERANCE)
path.quadTo(mX, mY, (x+mX)/2, (y+mY)/2);
mX=x;
mY=y;
//To clear canvas
public void clearCanvas()
path.reset();
invalidate();
public void upTouch()
path.lineTo(mX,mY);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.drawPath(path,paint);
@Override
public boolean onTouchEvent(MotionEvent event)
float x = event.getX();
float y = event.getY();
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
startTouch(x,y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x,y);
invalidate();
break;
case MotionEvent.ACTION_UP:
upTouch();
invalidate();
break;
default:
return false;
invalidate();
return true;
activity_main.xml
<RelativeLayout 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:layout_
android:layout_
android:id="@+id/coordAct"
tools:context="com.simplepaintapp.MainActivity">
<RelativeLayout
android:id="@+id/parLayout"
android:layout_
android:layout_
android:orientation="horizontal"
android:weightSum="3"
android:background="#0000ff">
<RelativeLayout
android:layout_
android:layout_
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="28dp"
android:background="#BA9DF7"
android:gravity="bottom"
android:orientation="vertical">
<HorizontalScrollView
android:id="@+id/backgd"
android:layout_
android:layout_
android:weightSum="1">
<LinearLayout
android:id="@+id/parentLL"
android:layout_
android:layout_
android:layout_weight="1"
android:orientation="horizontal"
android:weightSum="5">
<ImageView
android:id="@+id/wand1"
android:layout_
android:layout_
android:layout_centerInParent="true"
android:layout_gravity="center"
android:src="@drawable/pen" />
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
</RelativeLayout>
Current bug after implementing the dispatchTouchEvent() method inside onTouch()
【问题讨论】:
您有 2 个不同的地方处理 TouchEvents,并且在您关心的实例中都返回 true。返回您已使用TouchEvent
的真实信号,无需进一步处理。
@BryanDormaier 所以你是说只结合 onTouchEvent() 和 onTouch() 方法?
不,你有两个不同的视图来处理 Touch。如果您希望它们能够相互合作,您需要确定何时应该返回 true 以表示您确实在使用 TouchEvent
。另一种模式是 dispatchTouchEvent()
从一个视图到另一个视图,但就目前而言,您处理触摸的两个地方正在相互竞争。
@BryanDormaier 啊!我明白你在说什么。因此,因为我在 onTouch 和 onTouchEvent 上都返回 true,所以只有一个会执行……无论首先遇到哪个动作,绘制或拖动。如果我想实现 dispatchTouchEvent() 方法,我只需要调用 canvasView.dispatchTouchEvent() 吗?
如果你想从 w1 传递那个视图,它应该类似于 canvasView.dispatchTouchEvent(event)
【参考方案1】:
需要更新 MainActivity 和 CanvasView。请参阅下面的新源代码:
MainActivity.java
public class MainActivity extends AppCompatActivity
private ViewGroup rootLayout;
private int _xDelta;
private int _yDelta;
private RelativeLayout pl;
private ImageView w1;
private boolean clicked1 = false;
CanvasView canvasView;
public static int X;
public static int Y;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
canvasView = new CanvasView(MainActivity.this);
w1 = (ImageView) findViewById(R.id.wand1);
pl = (RelativeLayout) findViewById(R.id.coordAct);
RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.coordAct);
rootLayout.addView(canvasView);
RelativeLayout.LayoutParams layoutParams1w2 = new RelativeLayout.LayoutParams(getScreenWidth(), getScreenHeight() / 2);
layoutParams1w2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
canvasView.setLayoutParams(layoutParams1w2);
w1.setOnTouchListener(new ChoiceTouchListener());
public static int getScreenWidth()
return Resources.getSystem().getDisplayMetrics().widthPixels;
public static int getScreenHeight()
return Resources.getSystem().getDisplayMetrics().heightPixels;
public class ChoiceTouchListener implements View.OnTouchListener
public boolean onTouch(View view, MotionEvent event)
if (!clicked1)
rootLayout = (ViewGroup) w1.getParent();
if (rootLayout != null)
// detach the child from parent
rootLayout.removeView(w1);
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(300, 300);
pl.addView(w1);
w1.setLayoutParams(layoutParams1);
clicked1 = true;
X = (int) event.getRawX();
Y = (int) event.getRawY();
switch (event.getAction() & MotionEvent.ACTION_MASK)
case MotionEvent.ACTION_DOWN:
RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
_xDelta = X - lParams.leftMargin;
_yDelta = Y - lParams.topMargin;
canvasView.dispatchTouchEvent(event);
break;
case MotionEvent.ACTION_MOVE:
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view
.getLayoutParams();
layoutParams.leftMargin = X - _xDelta;
layoutParams.topMargin = Y - _yDelta;
layoutParams.rightMargin = -250;
layoutParams.bottomMargin = -250;
view.setLayoutParams(layoutParams);
canvasView.dispatchTouchEvent(event);
break;
rootLayout.invalidate();
return true;
CanvasView.java:
public class CanvasView extends View
Context context;
int width, height;
Bitmap bitmap;
Path path;
public Canvas canvas;
Paint paint;
float mX, mY;
static final float TOLERANCE=4;
public static float x;
public static float y;
public CanvasView(Context context)
super(context);
this.context=context;
path=new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(50);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w,h,oldw,oldh);
bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
canvas=new Canvas(bitmap);
public void startTouch(float x, float y)
path.moveTo(x, y);
mX = x;
mY = y;
public void moveTouch(float x, float y)
float dx = Math.abs(x-mX);
float dy = Math.abs(y-mY);
if(dx>=TOLERANCE || dy>= TOLERANCE)
path.quadTo(mX, mY, (x+mX)/2, (y+mY)/2);
mX=x;
mY=y;
//To clear canvas
public void clearCanvas()
path.reset();
invalidate();
public void upTouch()
path.lineTo(mX,mY);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.drawPath(path,paint);
@Override
public boolean onTouchEvent(MotionEvent event)
float x = event.getX();
float y = event.getY();
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
startTouch(MainActivity.X,MainActivity.Y-380);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
moveTouch(MainActivity.X,MainActivity.Y-380);
invalidate();
break;
case MotionEvent.ACTION_UP:
upTouch();
invalidate();
break;
default:
return false;
invalidate();
return true;
【讨论】:
以上是关于在画布上绘制视图被拖动的任何位置的主要内容,如果未能解决你的问题,请参考以下文章