Amazon Gift Cards

Monday, June 8, 2009

Drawing with Canvas in Android

Update - Nov 21, 2010
Created a series over this topic over at Drawing with Canvas Series, more articles would appear in the future :)

Update - Nov 13, 2010
There is a renewed/enchanced version of this tutorial though i still recommend to read this article if you're completely new to Android or Canvas in Android, for we reference back to this article. The updated one is at Drawing with Canvas in Android Renewed

Introduction
If you had been reading previous post then you'll know i'm not a java guy, i literally spend hours trying to figure out how canvas works perfectly on java. Then i remember that javascript/html5 has canvas too, so i revisited this one (haven't visited it for almost half a year) https://developer.mozilla.org/en/drawing_graphics_with_canvas and see that i must use paths (2nd example, beginPath) rather than drawing point by point. So together with this article http://www.droidnova.com/playing-with-graphics-in-android-part-iv,182.html. I come up with a pretty descend drawing app.


To do this:
Copy the whole code from droidnova the replace the following.

On your Activity
private ArrayList _graphics = new ArrayList();
private Paint mPaint;
@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(new DrawingPanel(this));
  mPaint = new Paint();
  mPaint.setDither(true);
  mPaint.setColor(0xFFFFFF00);
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setStrokeJoin(Paint.Join.ROUND);
  mPaint.setStrokeCap(Paint.Cap.ROUND);
  mPaint.setStrokeWidth(3);
}


On your SurfaceView
@Override
public boolean onTouchEvent(MotionEvent event) {
  synchronized (_thread.getSurfaceHolder()) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
      path = new Path();
      path.moveTo(event.getX(), event.getY());
      path.lineTo(event.getX(), event.getY());
    }else if(event.getAction() == MotionEvent.ACTION_MOVE){
      path.lineTo(event.getX(), event.getY());
    }else if(event.getAction() == MotionEvent.ACTION_UP){
      path.lineTo(event.getX(), event.getY());
      _graphics.add(path);
    }
    return true;
  }
}
@Override
public void onDraw(Canvas canvas) {
  for (Path path : _graphics) {
    //canvas.drawPoint(graphic.x, graphic.y, mPaint);
    canvas.drawPath(path, mPaint);
  }
}


Explanation
We create a path and start that path when the MotionEvent is down, or when the user first touch the screen, then add a lineTo, the x and y when the user moves his fingers. Then stop and push the path we had build to our array of Paths.
public boolean onTouchEvent(MotionEvent event) { .... }

Then during the draw function we loop through the array and print them on our canvas.
public void onDraw(Canvas canvas) { ... }


References
http://www.droidnova.com/playing-with-graphics-in-android-part-iv,182.html
https://developer.mozilla.org/en/drawing_graphics_with_canvas
Android IRC - irc://freenode/android-dev

39 comments:

Rolli said...

Thanks, great tutorial!
I would like to ask if it's a way to draw while moving
I'v tried to modify the code writing this:

public boolean onTouchEvent(MotionEvent event) {
synchronized (_thread.getSurfaceHolder()) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
path = new Path();
path.moveTo(event.getX(), event.getY());
path.lineTo(event.getX(), event.getY());
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
path.lineTo(event.getX(), event.getY());
_graphics.add(path);
path = new Path();
path.moveTo(event.getX(), event.getY());
}else if(event.getAction() == MotionEvent.ACTION_UP){
path.lineTo(event.getX(), event.getY());
_graphics.add(path);
}

return true;
}
}

this let to draw every time we do an ACTION_MOVE instead only on ACTION_UP.. but I've noticed a slowly progressive loss of performance.. what do you think about?
Rolli

stu said...

Thanks, great tutorial!
i have been any question
why do you used it(surfaceView)?
i need View

Hollowback said...

Re: Rolli
Hi, I need solution for that too :). Please let me know, if you resolve it.
Thanks

Zingo said...

Rolli:
I think you get you path stuffed into the path array many times (one per move) try to move _graphics.add(path); to ACTION_DOWN and remove it from the other places. This will add one path per down event and make the path longer while moving.

e.g.

@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (_thread.getSurfaceHolder()) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
path = new Path();
path.moveTo(event.getX(), event.getY());
path.lineTo(event.getX(), event.getY());
_graphics.add(path);
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
path.lineTo(event.getX(), event.getY());
}else if(event.getAction() == MotionEvent.ACTION_UP){
path.lineTo(event.getX(), event.getY());
}

return true;
}
}

Anonymous said...

Hello, I can't get the code to compile. Would it be too much to ask for, if you'd could paste the whole source code somewhere? Have been scratchin my head for quite som time now:)

Sincerely Oskar

monmonja said...

@Oskar Here you go http://sites.google.com/site/tutorialsformobileprogramming/android-tutorials/android-files you can download the zip file there :)

Anonymous said...

Thanks a lot, my friend! You made my day:D

//Oskar

Anonymous said...

Hi, is there any way to reset the canvas? To delete all of the previously painted stuff to a blank start screen whithout having to restart the app?:)

Otherwise a truly great tutorial mate!

/Peter

monmonja said...

There are a lot of ways to delete or reset the canvas but the reset is kinda not directly clear it. If your canvas came with bitmap, you could recycle the bitmap and redraw the bitmap. If you canvas is purely a background color, then you could just drawColor on your canvas. Hope this link helps http://stackoverflow.com/questions/2423327/android-view-ondraw-always-has-a-clean-canvas

Anonymous said...

Can't get this to work at all. My screen stays black with nothing ever changing, even though I do c.drawARGB(255, 255, 255, 255); in onDraw().

Anonymous said...

Thank you Almond and Zingo, this is most helpfull.

I to change position of object. I set new position to my rectangle when user "drags it".

However for some reason, I can see all old positions of retangle.

How could I show only current position?

Anonymous said...

i think it loses performance because at every onDraw all the old paths are redrawn. i tried with this:
if(_graphics.size() >= 1)
canvas.drawPath(_graphics.get(_graphics.size()-1), mPaint);

and it keeps smooth.

andrea

Anonymous said...

andrea: I attempted to do this but in a different way. at the end of my onDraw method, i cleared the array (_graphics), but for some reason I cannot draw anything

monmonja said...

@anonymous if you have been following the drawing in android series the last one is one http://www.tutorialforandroid.com/2010/11/drawing-with-canvas-in-android-saving.html, on the first code block you can insert something like isDrawing or hasDrawn boolean variable (before the try catch, inside the while) on where the paths would only be drawn when an event is trigger, which would make the whole experience smooth.

Anonymous said...

There is a problem... the path is shown only when we leave the touch from the screen. It should be shown along the finger moving on screen. Other wise we would have no idea from where the lines are originating if we have to draw something continuously. It will just be a wild guess.

monmonja said...

The preview of the line u draw/drawing are part of the drawing in canvas series, i had already prepared all the codes, just dont have time right now to blog about it but ill commit ill do it within this week, so stay posted ;-)

Somenath Mukhopadhyay said...

As i was trying to play around with Android graphics, i got this reference. Then i created the AndroidPaint freeware app. Please have a look at it at AndroidPaint

Anonymous said...

Thanks, great tutorial. iam trying save the path but i couldnt. how to handle change orientation, when we change orientation i want to draw same path again. how can i achieve this

thanks in advance

James said...

Gread Post. I also have a android canvas example. Mine is to draw a bitmap from image. Share with u:

Android Canvas Example

justin said...

Thanks for the tip android that's what I needed to speed up my app.

midlet said...

This is using view only , define this activity in your manifest


public class DrawingActivity extends Activity {
private ArrayList _graphics = new ArrayList();
private Paint mPaint;
Handler myHandler = new Handler() {

public void handleMessage(android.os.Message msg) {
myview.invalidate();

};
};
Myview myview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(new DrawingPanel(this));
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setColor(0xFFFFFF00);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(3);
myview = new Myview(this);
myview.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
setContentView(myview);
myHandler = new Handler();

}

class Myview extends View {

public Myview(Context context) {
super(context);

}

Path path;

@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
path = new Path();
path.moveTo(event.getX(), event.getY());
path.lineTo(event.getX(), event.getY());
_graphics.add(path);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
path.lineTo(event.getX(), event.getY());
} else if (event.getAction() == MotionEvent.ACTION_UP) {
path.lineTo(event.getX(), event.getY());
}
invalidate();
return true;
}

@Override
protected void onDraw(Canvas canvas) {
for (Path path : _graphics) {
// canvas.drawPoint(graphic.x, graphic.y, mPaint);
canvas.drawPath(path, mPaint);
}
}
}

}

Ryan said...

Anybody know why this code crashes onResume? Works fine until you back out and try to reopen

Anonymous said...

Thanks, Nice tutorial

How can i add a button and listener to it?

monmonja said...

to create button and listener you should see the updated tutorial, see above, it would use the extended SurfaceView in an xml thus you can use core android stuff

Anonymous said...

HI
How do i erase or clear the canvas after draw the the line

monmonja said...

see the undo and redo tutorial

ProjectJourneyman said...

Excellent post - thanks for spelling out some of the options for Paint and adding this fun bit of functionality. It got me going quickly on my newest prototype!

I'll get working on the rest of your Canvas series shortly.

-ProjectJourneyman

Anonymous said...

Hi Sir, how do I make the canvas background as an image? Thank you very much :)

k18 said...

For those who want to paint while moving without making it slow, check this code modification at onTouchEvent. @Rolli, take a look if you didn't get it working:

@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (_thread.getSurfaceHolder()) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
path = new Path();
path.moveTo(event.getX(), event.getY());
path.lineTo(event.getX(), event.getY());
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
path.lineTo(event.getX(), event.getY());
if(_graphics.size() > 0) {
_graphics.remove(_graphics.size() - 1);
}
_graphics.add(path);
}else if(event.getAction() == MotionEvent.ACTION_UP){
path.lineTo(event.getX(), event.getY());
_graphics.remove(_graphics.size() - 1);
_graphics.add(path);
}
return true;
}
}

Raj said...

Hi All,

I basically want to draw a map on the screen, this will all be done using draw on canvas (this i can do).

However the problem I am having is my overall map is 1000 x 1000, however I only ever want the user to see 10x10. Then every time the user moves (arrows or touch & drag) the app automatically draws the next bitmap within the 1000 x 1000 sequence until they reach the end.

I would also like to add a birdseye view where the view is zoomed in filling up 70% of the Android Screen.

Can anyone point me in the right direction to solve this?

Thanks

XaeroDegreaz said...

I really appreciate the shedding of light on this. I too am new to the workflow of Android applications, so this really helped out a lot for me to be able to look at how it should be done, and implement the same fundamentals.

However, I have a couple of things to point out:

1. With your code, if you just tap on the screen before anything is drawn, it will throw an IndexOutOfRangeException, and cause a force close. You can remedy that by adding a try/catch block to the MouseEvent.ACTION_UP conditions.

2. If you change the background colour of the DrawingPanel, you are no longer able to draw (no matter the combination of pen/background colours chosen). This, of course, may have something to do with my newness to the API, and not calling the background changing method in the proper area. If you had any ideas on this one, that would be greatly appreciated :)

Anonymous said...

Excellent tutorials. Thanks!

afnan khan said...

Thanks a lot i have solve my

afnan khan said...

thanks

afnan khan said...

Thanks

p srinivas said...

I tried this tutorial code but its force closing,not clearing surface and continuous drawing is not there since drawing captured in ACTION_UP..please provide solutions.

Rahul@appy said...

I want to draw some thing at my image that is at my surface.simply i fetch it from my drawbale folder and on button click i found it at my surface. so plzz suggest the code when i clicked at image button.i fould the image and i also can draw the any drawing.

Wasantha Herath said...

Thank u vry much sir... it helped me lot.. :)

JoseCarlos said...

how i can put some image on the background and then draw the lines over the image?