2013-05-09 68 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