2013-05-09 72 views
1

我正在使用android Canvas类创建绘图应用程序。这是我第一次尝试使用Canvas类。到目前为止,我使用的代码工作正常,图形工作正常。但是我在这段代码中意识到的是,它允许用户只用一个手指来绘制,我的意思是说如果用户使用多于一个手指在画布上绘制,则不允许用户用多个手指绘制。我经历了多个关于多点触摸事件的文档,但是没有在我的代码中实现它。所以任何人都可以帮我弄清楚这一点。如何用多个手指在画布中绘制

我用于在画布上绘制的代码是

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

我想在我的代码,支持多点触摸进行更改,但它不能正常工作。这是我的更改代码http://pastebin.com/W6qvpYGW

回答

2

请参阅Making Sense of Multitouch,它帮了我很多。它explanes如何处理多触摸

要记住的要点

1.确保你打开action & MotionEvent.ACTION_MASK

2.如果你想画在同一时间多行,请按照每个指针的PointerId其中进入MotionEvent.ACTION_POINTER_DOWN并通过比较指针ID来在MotionEvent.ACTION_POINTER_UP中释放它。

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

编辑

请参阅此代码...这还是有一些问题,但我认为你可以调试和修复这些......而且逻辑是不是有持续的线条,请执行该...

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

其实我通过这个文件,但在doc他们提到注册多个触摸我们需要MotionEvent.ACTION_POINTER_DOWN,但在他们的示例代码,他们从未使用过,二来如果你不介意u能请帮我在我的代码中实现它。其实我试过,但它不起作用。 – AndroidDev 2013-05-09 05:39:51

+0

我在我的代码中进行了更改作为你的建议,但每当我用一根手指画出时,它就会崩溃http://pastebin.com/uKwz24y5 – AndroidDev 2013-05-09 05:48:03

+0

在你的代码中ACTION_DOWN为空,你可以从Action_down本身获得指针ID,因为ACTION_DOWN将成为任何触摸事件过程中的第一个事件,然后在每个其他手指的病房中调用ACTION_POINTER_DOWN。您需要获取指针的计数,然后计算指针的索引并在ACTION_POINTER_DOWN和ACTION_POINTER_UP中管理它们。它可能需要一些时间才能完美实现.... – 2013-05-09 06:05:06

0

复制粘贴示例。只需创建扩展View的类并实现以下方法即可。

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

为什么需要三个ACTION事件的for循环?我会认为每个手指都会独立激发这个听众? – 2016-11-07 07:48:03

3

由于没有工作代码的答案,我可以分享一个工作示例。关键是要有一个当前活动的指针ID及其路径的数组。知道在多个移动指针的情况下,onTouchEvent只会被调用一次,而且您需要遍历所有指针以绘制新的位置,这一点也很重要。

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

} 
+0

为什么ACTION_MOVE需要for循环?我会认为每个手指都会独立激发这个听众? – 2016-11-07 07:22:50

+2

这个监听器只会被独立地用于UP和DOWN事件,但不能用于MOVE事件。对于同时MOVE事件,它只会触发一次,这就是为什么你需要遍历所有指针。 – Ish 2016-12-04 08:41:14

+0

谢谢你,这是非常翔实的。 – 2016-12-04 18:32:03