问题
I am using android Canvas
class from creating drawing application. This is my first attempt to work with Canvas class. So far the code that i used is working fine and the drawing is working fine. But what i realized in this code is that, it allow user to draw with one finger only, i mean to say if the user used more then one finger to draw on canvas it doesn't allow the user to draw with multiple finger. I go through several documentation regarding multiple touch event but failed to implement it in my code. So can anyone help me to figure this out.
The code i used for drawing on canvas is
public class DrawView extends View implements OnTouchListener
{
private Canvas m_Canvas;
private Path m_Path;
private Paint m_Paint;
ArrayList<Pair<Path, Paint>> arrayListPaths = new ArrayList<Pair<Path, Paint>>();
ArrayList<Pair<Path, Paint>> undonePaths = new ArrayList<Pair<Path, Paint>>();
private float mX, mY;
private Bitmap bitmapToCanvas;
private static final float TOUCH_TOLERANCE = 4;
public DrawView(Context context)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
onCanvasInitialization();
}
public void onCanvasInitialization()
{
m_Paint = new Paint();
m_Paint.setAntiAlias(true);
m_Paint.setDither(true);
m_Paint.setColor(Color.parseColor("#37A1D1"));
m_Paint.setStyle(Paint.Style.STROKE);
m_Paint.setStrokeJoin(Paint.Join.ROUND);
m_Paint.setStrokeCap(Paint.Cap.ROUND);
m_Paint.setStrokeWidth(2);
m_Path = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
bitmapToCanvas = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
m_Canvas = new Canvas(bitmapToCanvas);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmapToCanvas, 0f, 0f, null);
canvas.drawPath(m_Path, m_Paint);
}
public boolean onTouch(View arg0, MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
{
touch_move(x, y);
invalidate();
break;
}
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
private void touch_start(float x, float y)
{
undonePaths.clear();
m_Path.reset();
m_Path.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y)
{
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
m_Path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
}
private void touch_up()
{
m_Path.lineTo(mX, mY);
// commit the path to our offscreen
m_Canvas.drawPath(m_Path, m_Paint);
// kill this so we don't double draw
Paint newPaint = new Paint(m_Paint); // Clones the mPaint object
arrayListPaths.add(new Pair<Path, Paint>(m_Path, newPaint));
m_Path = new Path();
}
}
I tried making changes in my code to support multiple touch, but it doesn't work properly. This is my change code http://pastebin.com/W6qvpYGW.
回答1:
See Making Sense of Multitouch, it helped me a lot. It explanes how to handle multi touches
Points to remember
1.Make sure that you switch on action & MotionEvent.ACTION_MASK
2.if you want to draw multiple lines at same time, follow PointerId of each pointer which comes in MotionEvent.ACTION_POINTER_DOWN and release it in MotionEvent.ACTION_POINTER_UP by comparing pointer ids.
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
// Existing code ...
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
// Save the ID of this pointer
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
mLastTouchX = x;
mLastTouchY = y;
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
// Extract the index of the pointer that left the touch sensor
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
Edit
Please see this code... This still has some issues but i think you can debug it and fix those ... Also the logic is not there persisting lines please implement that...
package com.example.stackgmfdght;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
public class JustDoIt extends View
{
private Canvas m_Canvas;
// private Path m_Path;
int current_path_count=-1;
ArrayList <Path> m_Path_list = new ArrayList<Path>();
ArrayList <Float> mX_list = new ArrayList<Float>();
ArrayList <Float> mY_list = new ArrayList<Float>();
ArrayList <Integer> mActivePointerId_list = new ArrayList<Integer>();
private Paint m_Paint;
ArrayList<Pair<Path, Paint>> arrayListPaths = new ArrayList<Pair<Path, Paint>>();
//ArrayList<Pair<Path, Paint>> undonePaths = new ArrayList<Pair<Path, Paint>>();
private float mX, mY;
private Bitmap bitmapToCanvas;
private static final float TOUCH_TOLERANCE = 4;
public JustDoIt (Context context)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
onCanvasInitialization();
}
public JustDoIt(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setFocusable(true);
setFocusableInTouchMode(true);
onCanvasInitialization();
}
public void onCanvasInitialization()
{
m_Paint = new Paint();
m_Paint.setAntiAlias(true);
m_Paint.setDither(true);
m_Paint.setColor(Color.parseColor("#37A1D1"));
m_Paint.setStyle(Paint.Style.STROKE);
m_Paint.setStrokeJoin(Paint.Join.ROUND);
m_Paint.setStrokeCap(Paint.Cap.ROUND);
m_Paint.setStrokeWidth(2);
// m_Path = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
bitmapToCanvas = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
m_Canvas = new Canvas(bitmapToCanvas);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmapToCanvas, 0f, 0f, null);
for(int i=0;i<=current_path_count;i++)
{
canvas.drawPath(m_Path_list.get(i), m_Paint);
}
}
public void onDrawCanvas()
{
for (Pair<Path, Paint> p : arrayListPaths)
{
m_Canvas.drawPath(p.first, p.second);
}
}
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
@Override
public boolean onTouchEvent(MotionEvent event)
{
super.onTouchEvent(event);
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
float x = event.getX();
float y = event.getY();
current_path_count=0;
mActivePointerId_list.add ( event.getPointerId(0),current_path_count);
touch_start((x ),(y ),current_path_count );
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
{
if(event.getPointerCount()>current_path_count)
{
current_path_count++;
float x = event.getX(current_path_count);
float y = event.getY(current_path_count);
mActivePointerId_list.add ( event.getPointerId(current_path_count),current_path_count);
touch_start((x ),(y ),current_path_count);
}
}
break;
case MotionEvent.ACTION_MOVE:
{
for(int i=0;i<=current_path_count;i++)
{ try{
int pointerIndex = event
.findPointerIndex(mActivePointerId_list.get(i));
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
touch_move((x ),(y ),i);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
break;
case MotionEvent.ACTION_UP:
{ current_path_count=-1;
for(int i=0;i<=current_path_count;i++)
{
touch_up(i);
}
mActivePointerId_list = new ArrayList<Integer>();
}
break;
case MotionEvent.ACTION_CANCEL:
{
mActivePointerId = INVALID_POINTER_ID;
current_path_count=-1;
}
break;
case MotionEvent.ACTION_POINTER_UP:
{
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
for(int i=0;i<=current_path_count;i++)
{
if (pointerId == mActivePointerId_list.get(i))
{
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
mActivePointerId_list.remove(i);
touch_up(i);
break;
}
}
}
break;
case MotionEvent.ACTION_OUTSIDE:
break;
}
invalidate();
return true;
}
private void touch_start(float x, float y, int count)
{
// undonePaths.clear();
Path m_Path=new Path();
m_Path_list.add(count,m_Path);
m_Path_list.get(count).reset();
m_Path_list.get(count).moveTo(x, y);
mX_list.add(count,x);
mY_list.add(count,y);
}
private void touch_move(float x, float y,int count)
{
float dx = Math.abs(x - mX_list.get(count));
float dy = Math.abs(y - mY_list.get(count));
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
m_Path_list.get(count).quadTo(mX_list.get(count), mY_list.get(count), (x + mX_list.get(count))/2, (y + mY_list.get(count))/2);
try{
mX_list.remove(count);
mY_list.remove(count);
}
catch(Exception e)
{
e.printStackTrace();
}
mX_list.add(count,x);
mY_list.add(count,y);
}
}
private void touch_up(int count)
{
m_Path_list.get(count).lineTo(mX_list.get(count), mY_list.get(count));
// commit the path to our offscreen
m_Canvas.drawPath( m_Path_list.get(count), m_Paint);
// kill this so we don't double draw
Paint newPaint = new Paint(m_Paint); // Clones the mPaint object
arrayListPaths.add(new Pair<Path, Paint>( m_Path_list.get(count), newPaint));
m_Path_list.remove(count);
mX_list.remove(count);
mY_list.remove(count);
}
}
回答2:
Since there is no answer with working code, I can share a working example. The key is to have an array of currently active pointer ids and their paths. It is also important to know that in case of multiple moving pointers, onTouchEvent gets called only once for all of them and you need to iterate through all of the pointers to draw their new positions.
public class DrawView extends View {
private Paint drawPaint, canvasPaint;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private SparseArray<Path> paths;
public DrawingView(Context context) {
super(context);
setupDrawing();
}
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
public DrawingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupDrawing();
}
private void setupDrawing() {
paths = new SparseArray<>();
drawPaint = new Paint();
drawPaint.setColor(Color.RED);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(20);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
for (int i=0; i<paths.size(); i++) {
canvas.drawPath(paths.valueAt(i), drawPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int id = event.getPointerId(index);
Path path;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
path = new Path();
path.moveTo(event.getX(index), event.getY(index));
paths.put(id, path);
break;
case MotionEvent.ACTION_MOVE:
for (int i=0; i<event.getPointerCount(); i++) {
id = event.getPointerId(i);
path = paths.get(id);
if (path != null) path.lineTo(event.getX(i), event.getY(i));
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
path = paths.get(id);
if (path != null) {
drawCanvas.drawPath(path, drawPaint);
paths.remove(id);
}
break;
default:
return false;
}
invalidate();
return true;
}
}
回答3:
Here example for copy-paste. Just create class that extend View and implement following methods.
private final Paint paint = new Paint(); // Don't forgot to init color, form etc.
@Override
protected void onDraw(Canvas canvas) {
for (int size = paths.size(), i = 0; i < size; i++) {
Path path = paths.get(i);
if (path != null) {
canvas.drawPath(path, paint);
}
}
}
private HashMap<Integer, Float> mX = new HashMap<Integer, Float>();
private HashMap<Integer, Float> mY = new HashMap<Integer, Float>();
private HashMap<Integer, Path> paths = new HashMap<Integer, Path>();
@Override
public boolean onTouchEvent(MotionEvent event) {
int maskedAction = event.getActionMasked();
Log.d(TAG, "onTouchEvent");
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
for (int size = event.getPointerCount(), i = 0; i < size; i++) {
Path p = new Path();
p.moveTo(event.getX(i), event.getY(i));
paths.put(event.getPointerId(i), p);
mX.put(event.getPointerId(i), event.getX(i));
mY.put(event.getPointerId(i), event.getY(i));
}
break;
}
case MotionEvent.ACTION_MOVE: {
for (int size = event.getPointerCount(), i = 0; i < size; i++) {
Path p = paths.get(event.getPointerId(i));
if (p != null) {
float x = event.getX(i);
float y = event.getY(i);
p.quadTo(mX.get(event.getPointerId(i)), mY.get(event.getPointerId(i)), (x + mX.get(event.getPointerId(i))) / 2,
(y + mY.get(event.getPointerId(i))) / 2);
mX.put(event.getPointerId(i), event.getX(i));
mY.put(event.getPointerId(i), event.getY(i));
}
}
invalidate();
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
for (int size = event.getPointerCount(), i = 0; i < size; i++) {
Path p = paths.get(event.getPointerId(i));
if (p != null) {
p.lineTo(event.getX(i), event.getY(i));
invalidate();
paths.remove(event.getPointerId(i));
mX.remove(event.getPointerId(i));
mY.remove(event.getPointerId(i));
}
}
break;
}
}
return true;
}
来源:https://stackoverflow.com/questions/16454974/how-to-draw-with-multiple-fingers-in-canvas