AndroidDev AndroidDev - 1 month ago 13
Android Question

How to draw with multiple fingers in canvas

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.

TCA TCA
Answer

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