Once in a while i would post non-android article and here is one of them.
Anyone watch the big bang theory must know this song, if not here is a video
And here is a guitar tab, ahhah
2 or B |--8-5-5--6-3-3--1-3-5-6-8---8-5-5--6-3-3--1-3-1--| (repeat nth times)
:)
Sunday, November 28, 2010
Thursday, November 25, 2010
Using library with resources in IntelliJ and Android
As my previous articles indicates, im coding more Android in IntelliJ then eclipse (main reason is that it will let you forget you have a mouse). For those new to IntelliJ, like me, here is how i set up the library in IntelliJ so that both resources and code could be used.
Notes
• Im new to IntelliJ so if im doing something wrong, kindly comment below :)
• As of writing the resources between library cant be handle properly when compiled (you can still do the coding with it) thus we need the help of command line (ant install, adb install/uninstall), on the Bug tracker it was reported to be fine in nightly.
What Do I Need
• As of now you need the EAP of IntelliJ
• Lastest Android SDK
• You need to know how to create a project
• You need to know how to create a library
Instruction
1) Open your project's module settings

Command line
So you can code with your favorite IDE and now you want to debug it on the emulator. you could do (please refer to Developing in other IDE (You must have ant in your OS)
It will then tell you that where the apk is at
Now you have to install it on the emulator, please refer to Android Debug Bridge page
adb uninstall com.almondmendoza.shootAndLearn
We have to uninstall the apk from the emulator first before
adb install bin/shootAndLearn-debug.apk
installing it
Conclusion
I know the debugging part is kinda hard right now but the development/coding part is certainly a lot better (got to love the ctrl+w, ctrl+shift+a shortcuts) :)
Notes
• Im new to IntelliJ so if im doing something wrong, kindly comment below :)
• As of writing the resources between library cant be handle properly when compiled (you can still do the coding with it) thus we need the help of command line (ant install, adb install/uninstall), on the Bug tracker it was reported to be fine in nightly.
What Do I Need
• As of now you need the EAP of IntelliJ
• Lastest Android SDK
• You need to know how to create a project
• You need to know how to create a library
Instruction
1) Open your project's module settings
2) In Modules, click the add new module (see image below)
3) Select your library if you had previously created one
4) Browse to your libraries path/directory. and select the file with the extension .iml
5) First click on your project (the red circle) then on the dependencies tab, click on add then Module dependencies (it shall have only one module)
Command line
So you can code with your favorite IDE and now you want to debug it on the emulator. you could do (please refer to Developing in other IDE (You must have ant in your OS)
cd /path/to/your/project
ant debugIt will then tell you that where the apk is at
debug:
[echo] Running zip align on final apk...
[echo] Debug Package: /path/to/your/project/bin/shootAndLearn-debug.apkNow you have to install it on the emulator, please refer to Android Debug Bridge page
adb uninstall com.almondmendoza.shootAndLearn
adb install bin/shootAndLearn-debug.apkadb uninstall com.almondmendoza.shootAndLearn
We have to uninstall the apk from the emulator first before
adb install bin/shootAndLearn-debug.apk
installing it
Conclusion
I know the debugging part is kinda hard right now but the development/coding part is certainly a lot better (got to love the ctrl+w, ctrl+shift+a shortcuts) :)
Labels:
IntelliJ
Sunday, November 21, 2010
Drawing with Canvas in Android, Saving your drawings
Continuing with our Drawing in Canvas series, let us see how to save your drawings
Update:
May 5, 2011 - If saving the image is not that fine, this could come in handy http://stackoverflow.com/questions/5243547/decode-byte-array-to-bitmap-that-has-been-compressed-in-java
Introduction
We draw and saw our drawings are good, now lets save them
Notes
• The files are uploaded in http://goo.gl/ecHpE
• The project was build in IntelliJ and it should be easy to import to Eclipse
•
What Do I Need
DrawingSurface
DrawingActivity
Quick Explanation
if(mBitmap == null){
mBitmap = Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);;
}
final Canvas c = new Canvas (mBitmap);
c.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(c);
This is the core of the saving part, in order for our drawing to be exported we have to create a canvas with bitmap to draw upon to, See Canvas Class for more info.
canvas.drawBitmap (mBitmap, 0, 0,null);
We put our bitmap to our original canvas (the surfaceView's canvas) so that we can see our drawings
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
}
We would have to recreate our bitmap when we change the orientation (The drawings will currently be removed)
final Activity currentActivity = this;
Handler saveHandler = new Handler(){ ... }
We cant call the UI tread from another thread so we pass a handler to our AsyncTask, see Using Handler in Android for more information on Handlers
private class ExportBitmapToFile extends AsyncTask {
private Context mContext;
private Handler mHandler;
private Bitmap nBitmap;
public ExportBitmapToFile(Context context,Handler handler,Bitmap bitmap) {
mContext = context;
nBitmap = bitmap;
mHandler = handler;
}
@Override
protected Boolean doInBackground(Intent... arg0) {
try {
if (!APP_FILE_PATH.exists()) {
APP_FILE_PATH.mkdirs();
}
final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
return true;
}catch (Exception e) {
e.printStackTrace();
}
//mHandler.post(completeRunnable);
return false;
}
@Override
protected void onPostExecute(Boolean bool) {
super.onPostExecute(bool);
if ( bool ){
mHandler.sendEmptyMessage(1);
}
}
}
In order for us to export the bitmap and not hard the UX of our app, we need to create a background thread for it, for more information on AsyncTask see the AsyncTask Class and Painless Threading
final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
This is default Java feature, if your now from Java then this just mean that you create a buffer where when we use that in mBitmap.compress the compressed bitmap would be buffered into that FileOutputStream (i hope i was clear in this. haha)
Reference
Most of the stuff here were back in 2009, most of them are in Google Groups.
Update:
May 5, 2011 - If saving the image is not that fine, this could come in handy http://stackoverflow.com/questions/5243547/decode-byte-array-to-bitmap-that-has-been-compressed-in-java
Introduction
We draw and saw our drawings are good, now lets save them
Notes
• The files are uploaded in http://goo.gl/ecHpE
• The project was build in IntelliJ and it should be easy to import to Eclipse
•
What Do I Need
DrawingSurface
public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {
private Bitmap mBitmap;
private ...
class DrawThread extends Thread{
...
@Override
public void run() {
Canvas canvas = null;
while (_run){
try{
canvas = mSurfaceHolder.lockCanvas(null);
if(mBitmap == null){
mBitmap = Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);;
}
final Canvas c = new Canvas (mBitmap);
c.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(c);
canvas.drawBitmap (mBitmap, 0, 0,null);
} finally {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public Bitmap getBitmap(){
return mBitmap;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
}
}DrawingActivity
public class DrawingActivity extends Activity implements View.OnTouchListener{
...
private static File APP_FILE_PATH = new File("/sdcard/TutorialForAndroidDrawings");
public void onClick(View view){
switch (view.getId()){
...
case R.id.saveBtn:
final Activity currentActivity = this;
Handler saveHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
final AlertDialog alertDialog = new AlertDialog.Builder(currentActivity).create();
alertDialog.setTitle("Saved 1");
alertDialog.setMessage("Your drawing had been saved :)");
alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
alertDialog.show();
}
} ;
new ExportBitmapToFile(this,saveHandler, drawingSurface.getBitmap()).execute();
break;
}
}
private class ExportBitmapToFile extends AsyncTask {
private Context mContext;
private Handler mHandler;
private Bitmap nBitmap;
public ExportBitmapToFile(Context context,Handler handler,Bitmap bitmap) {
mContext = context;
nBitmap = bitmap;
mHandler = handler;
}
@Override
protected Boolean doInBackground(Intent... arg0) {
try {
if (!APP_FILE_PATH.exists()) {
APP_FILE_PATH.mkdirs();
}
final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
return true;
}catch (Exception e) {
e.printStackTrace();
}
//mHandler.post(completeRunnable);
return false;
}
@Override
protected void onPostExecute(Boolean bool) {
super.onPostExecute(bool);
if ( bool ){
mHandler.sendEmptyMessage(1);
}
}
}
} Quick Explanation
Ads from Amazon:
Explanationif(mBitmap == null){
mBitmap = Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);;
}
final Canvas c = new Canvas (mBitmap);
c.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(c);
This is the core of the saving part, in order for our drawing to be exported we have to create a canvas with bitmap to draw upon to, See Canvas Class for more info.
canvas.drawBitmap (mBitmap, 0, 0,null);
We put our bitmap to our original canvas (the surfaceView's canvas) so that we can see our drawings
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
}
We would have to recreate our bitmap when we change the orientation (The drawings will currently be removed)
final Activity currentActivity = this;
Handler saveHandler = new Handler(){ ... }
We cant call the UI tread from another thread so we pass a handler to our AsyncTask, see Using Handler in Android for more information on Handlers
private class ExportBitmapToFile extends AsyncTask
private Context mContext;
private Handler mHandler;
private Bitmap nBitmap;
public ExportBitmapToFile(Context context,Handler handler,Bitmap bitmap) {
mContext = context;
nBitmap = bitmap;
mHandler = handler;
}
@Override
protected Boolean doInBackground(Intent... arg0) {
try {
if (!APP_FILE_PATH.exists()) {
APP_FILE_PATH.mkdirs();
}
final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
return true;
}catch (Exception e) {
e.printStackTrace();
}
//mHandler.post(completeRunnable);
return false;
}
@Override
protected void onPostExecute(Boolean bool) {
super.onPostExecute(bool);
if ( bool ){
mHandler.sendEmptyMessage(1);
}
}
}
In order for us to export the bitmap and not hard the UX of our app, we need to create a background thread for it, for more information on AsyncTask see the AsyncTask Class and Painless Threading
final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
This is default Java feature, if your now from Java then this just mean that you create a buffer where when we use that in mBitmap.compress the compressed bitmap would be buffered into that FileOutputStream (i hope i was clear in this. haha)
Reference
Most of the stuff here were back in 2009, most of them are in Google Groups.
Wednesday, November 17, 2010
Drawing with Canvas in Android, Undo/Redo with Command Pattern
Update - Nov 21, 2010
Created a series over this topic over at Drawing with Canvas Series, more articles would appear in the future :)
Continuing our Drawing with Canvas in Android, lets implement Undo and Redo with Command Pattern
Introduction
We draw and make mistake, and when we do we may want to undo it and when we undo we might want to redo it. Lets use a common design pattern in dealing with undo (I believe this is a modified command pattern for the command manager but i might be wrong)
Notes
• This is a continuation of Drawing with Canvas in Android Renewed
• The files are uploaded in http://goo.gl/ecHpE
• The project was build in IntelliJ and it should be easy to import to Eclipse
•
What Do I Need
ICanvasCommand
DrawingPath
CommandManager
DrawingSurface
DrawingActivity
See the source should be simple enough to be understood :)
public interface ICanvasCommand {
public void draw(Canvas canvas);
public void undo();
}
In undo command pattern based we need need 2 methods, one is execute and the other is undo (we wont use undo currently)
public class DrawingPath implements ICanvasCommand{
public Path path;
public Paint paint;
public void draw(Canvas canvas) {
canvas.drawPath( path, paint );
}
public void undo() {
//Todo this would be changed later
}
}
Let us now implement our interface, the code should be easy to understand. canvas.drawPath( path, paint) would draw our path with the object's paint into our class.
currentStack = Collections.synchronizedList(new ArrayList());
redoStack = Collections.synchronizedList(new ArrayList());
We create 2 List (Its actually a Stack but seems Stack cannot be thread-safe and I dont freakin understand why there are a lot of Array variation in Java, its so messy), one called currentStack for the current paths, another is redoStack where we put the undo commands so we could do redos.
public void addCommand(DrawingPath command){
redoStack.clear();
currentStack.add(command);
}
Everytime we add a new command, we should clear the redoStack
final int length = currentStackLength();
if ( length > 0) {
final DrawingPath undoCommand = currentStack.get( length - 1 );
currentStack.remove( length - 1 );
undoCommand.undo();
redoStack.add( undoCommand );
}
}
When we do an undo, we pop the last command from the currentStack and push it to our redoStack. As you can see we implemented undoCommand.undo() we did this incase in the future we have some logics added to our undo function
public void executeAll( Canvas canvas){
if( currentStack != null ){
synchronized( currentStack ) {
final Iterator i = currentStack.iterator();
while ( i.hasNext() ){
final DrawingPath drawingPath = (DrawingPath) i.next();
drawingPath.draw( canvas );
}
}
}
}
We loop through our currentStack and do the execute part (.draw) from our command pattern.
canvas = mSurfaceHolder.lockCanvas(null);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(canvas);
In order for our undo/redo to work we have to somehow clear the canvas and drawColor(0, PorterDuff.Mode.CLEAR) helps us with that
Some of the codes are, i believe, logical thus i skip the explanation on them.
Created a series over this topic over at Drawing with Canvas Series, more articles would appear in the future :)
Continuing our Drawing with Canvas in Android, lets implement Undo and Redo with Command Pattern
Introduction
We draw and make mistake, and when we do we may want to undo it and when we undo we might want to redo it. Lets use a common design pattern in dealing with undo (I believe this is a modified command pattern for the command manager but i might be wrong)
Notes
• This is a continuation of Drawing with Canvas in Android Renewed
• The files are uploaded in http://goo.gl/ecHpE
• The project was build in IntelliJ and it should be easy to import to Eclipse
•
What Do I Need
ICanvasCommand
public interface ICanvasCommand {
public void draw(Canvas canvas);
public void undo();
}DrawingPath
public class DrawingPath implements ICanvasCommand{
public Path path;
public Paint paint;
public void draw(Canvas canvas) {
canvas.drawPath( path, paint );
}
public void undo() {
//Todo this would be changed later
}
}CommandManager
public class CommandManager {
private List currentStack;
private List redoStack;
public CommandManager(){
currentStack = Collections.synchronizedList(new ArrayList());
redoStack = Collections.synchronizedList(new ArrayList());
}
public void addCommand(DrawingPath command){
redoStack.clear();
currentStack.add(command);
}
public void undo (){
final int length = currentStackLength();
if ( length > 0) {
final DrawingPath undoCommand = currentStack.get( length - 1 );
currentStack.remove( length - 1 );
undoCommand.undo();
redoStack.add( undoCommand );
}
}
public int currentStackLength(){
final int length = currentStack.toArray().length;
return length;
}
public void executeAll( Canvas canvas){
if( currentStack != null ){
synchronized( currentStack ) {
final Iterator i = currentStack.iterator();
while ( i.hasNext() ){
final DrawingPath drawingPath = (DrawingPath) i.next();
drawingPath.draw( canvas );
}
}
}
}
public boolean hasMoreRedo(){
return redoStack.toArray().length > 0;
}
public boolean hasMoreUndo(){
return currentStack.toArray().length > 0;
}
public void redo(){
final int length = redoStack.toArray().length;
if ( length > 0) {
final DrawingPath redoCommand = redoStack.get( length - 1 );
redoStack.remove( length - 1 );
currentStack.add( redoCommand );
}
}
} DrawingSurface
public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {
private CommandManager commandManager;
public DrawingSurface(Context context, AttributeSet attrs) {
...
commandManager = new CommandManager();
...
}
class DrawThread extends Thread{
...
@Override
public void run() {
Canvas canvas = null;
while (_run){
try{
canvas = mSurfaceHolder.lockCanvas(null);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(canvas);
....
}
public void addDrawingPath (DrawingPath drawingPath){
commandManager.addCommand(drawingPath);
}
public boolean hasMoreRedo(){
return commandManager.hasMoreRedo();
}
public void redo(){
commandManager.redo();
}
public void undo(){
commandManager.undo();
}
public boolean hasMoreUndo(){
return commandManager.hasMoreUndo();
}
}DrawingActivity
See the source should be simple enough to be understood :)
Ads from Amazon:
Explanationpublic interface ICanvasCommand {
public void draw(Canvas canvas);
public void undo();
}
In undo command pattern based we need need 2 methods, one is execute and the other is undo (we wont use undo currently)
public class DrawingPath implements ICanvasCommand{
public Path path;
public Paint paint;
public void draw(Canvas canvas) {
canvas.drawPath( path, paint );
}
public void undo() {
//Todo this would be changed later
}
}
Let us now implement our interface, the code should be easy to understand. canvas.drawPath( path, paint) would draw our path with the object's paint into our class.
currentStack = Collections.synchronizedList(new ArrayList
redoStack = Collections.synchronizedList(new ArrayList
We create 2 List (Its actually a Stack but seems Stack cannot be thread-safe and I dont freakin understand why there are a lot of Array variation in Java, its so messy), one called currentStack for the current paths, another is redoStack where we put the undo commands so we could do redos.
public void addCommand(DrawingPath command){
redoStack.clear();
currentStack.add(command);
}
Everytime we add a new command, we should clear the redoStack
Ads from Amazon:
public void undo (){final int length = currentStackLength();
if ( length > 0) {
final DrawingPath undoCommand = currentStack.get( length - 1 );
currentStack.remove( length - 1 );
undoCommand.undo();
redoStack.add( undoCommand );
}
}
When we do an undo, we pop the last command from the currentStack and push it to our redoStack. As you can see we implemented undoCommand.undo() we did this incase in the future we have some logics added to our undo function
public void executeAll( Canvas canvas){
if( currentStack != null ){
synchronized( currentStack ) {
final Iterator i = currentStack.iterator();
while ( i.hasNext() ){
final DrawingPath drawingPath = (DrawingPath) i.next();
drawingPath.draw( canvas );
}
}
}
}
We loop through our currentStack and do the execute part (.draw) from our command pattern.
canvas = mSurfaceHolder.lockCanvas(null);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(canvas);
In order for our undo/redo to work we have to somehow clear the canvas and drawColor(0, PorterDuff.Mode.CLEAR) helps us with that
Some of the codes are, i believe, logical thus i skip the explanation on them.
Labels:
Canvas,
Design Patterns,
Drawing
Saturday, November 13, 2010
Drawing with Canvas in Android Renewed
Update - Nov 21, 2010
Created a series over this topic over at Drawing with Canvas Series, more articles would appear in the future :)
Back one year ago, i blogged about Drawing with Canvas in Android, at that time i was creating a drawing app that never get released (lazy me). Now im planning to redo everything and share my learnings as i go on (I'm still doing the Shoot and Learn on the side). From the simple tutorial on how to draw canvas in android, lets explode this area further.
Introduction
Based on the Drawing with Canvas in Android lets put the SurfaceView into a separate class (you should know how to use custom view), create a DrawingPath class which would contain our Path and Paint object, and create a DrawingActivity where our touch events, buttons to change colors are located.
Notes
• The package i would use through this tutorial is com.almondmendoza.drawings
• The individual source are located at http://goo.gl/5GulF while the full ones is at https://sites.google.com/site/tutorialsformobileprogramming/android-tutorials/android-files
• In this tutorial we wont handle clearing up the images, undos/redos and screen rotations (all in future articles :) )
What Do I Need
Drawing Activity
Main Activity
DrawingPath.java
Drawing Activity
Drawing Surface
Explanation
<com.almondmendoza.drawings.DrawingSurface
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/drawingSurface"
/>
We start off by using our custom view in our xml, its as simple as creating a class and using it on the layout.
public class DrawingPath {
public Path path;
public Paint paint;
}
From Drawing with Canvas in Android we learn to put an ArrayList of Path which have a single color, for us to have different colors on different path, we create a class which would be an List in our DrawThread later on.
private Paint currentPaint;
....
private void setCurrentPaint(){
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFFFF00);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
}
Here we create a Paint that would serve as our current paint, and set the currentPaint at the start of our activity.
public void onClick(View view){
switch (view.getId()){
case R.id.colorRedBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFF0000);
...
break;
case R.id.colorBlueBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFF00FF00);
...
This is how we change the currentPaint's color, since we use the Paint object, you could use any of its property/methods thus making every Path beautiful :)
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
currentDrawingPath = new DrawingPath();
currentDrawingPath.paint = currentPaint;
currentDrawingPath.path = new Path();
....
drawingSurface.addDrawingPath(currentDrawingPath);
}
return true;
}
In the previous tutorial, we bind our onTouch on the surfaceView directly, now we bind it in our activity, this way we could control the logic of the touches better (where you would apply your own shared touch library) and you could do things that doesnt harm the SurfaceView/Canvas.
public DrawThread(SurfaceHolder surfaceHolder){
mSurfaceHolder = surfaceHolder;
mDrawingPaths = Collections.synchronizedList(new ArrayList());
}
As stated earlier we would create a List of DrawingPath, but in able to manipulate it while our thread is running we need to make sure that its thread-safe thus we call Collections.synchronizedList(new ArrayList()); (I'm not sure about this since i never used threads in other language besides Javascript's webworker)
public void addDrawingPath (DrawingPath drawingPath){
thread.addDrawingPath(drawingPath);
}
From our activity we would pass a DrawingPath to our surfaceView which we would then pass it to our thread, we do this so that our thread would not be accessible to the outside world of our DrawingSurface, which i believe is better for we can inject logics that would deal with our SurfaceView before passing it to our thread.
public void addDrawingPath(DrawingPath drawingPath){
mDrawingPaths.add( drawingPath );
}
From our DrawingSurface to our DrawThread, we pass the a DrawingPath and add it to our list
synchronized(mDrawingPaths) {
Iterator i = mDrawingPaths.iterator();
while (i.hasNext()){
final DrawingPath drawingPath = (DrawingPath) i.next();
canvas.drawPath(drawingPath.path, drawingPath.paint);
}
}
Here we use the Iterator class to iterate over our DrawingPath list and draw our Path on the canvas.
Conclusion
Now we learn how to better put our codes and make sure it can be scale up (im a webdeveloper, ahha) in the future.
Hope this helps :)
Created a series over this topic over at Drawing with Canvas Series, more articles would appear in the future :)
Back one year ago, i blogged about Drawing with Canvas in Android, at that time i was creating a drawing app that never get released (lazy me). Now im planning to redo everything and share my learnings as i go on (I'm still doing the Shoot and Learn on the side). From the simple tutorial on how to draw canvas in android, lets explode this area further.
Introduction
Based on the Drawing with Canvas in Android lets put the SurfaceView into a separate class (you should know how to use custom view), create a DrawingPath class which would contain our Path and Paint object, and create a DrawingActivity where our touch events, buttons to change colors are located.
Notes
• The package i would use through this tutorial is com.almondmendoza.drawings
• The individual source are located at http://goo.gl/5GulF while the full ones is at https://sites.google.com/site/tutorialsformobileprogramming/android-tutorials/android-files
• In this tutorial we wont handle clearing up the images, undos/redos and screen rotations (all in future articles :) )
What Do I Need
Drawing Activity
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" .... > <com.almondmendoza.drawings.DrawingSurface android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drawingSurface" /> <LinearLayout ..... > ...... </LinearLayout> </RelativeLayout>
Main Activity
Intent drawIntent = new Intent(this, DrawingActivity.class); startActivity( drawIntent);
DrawingPath.java
public class DrawingPath {
public Path path;
public Paint paint;
}Drawing Activity
public class DrawingActivity extends Activity implements View.OnTouchListener{
private DrawingSurface drawingSurface;
private DrawingPath currentDrawingPath;
private Paint currentPaint;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawing_activity);
setCurrentPaint();
drawingSurface = (DrawingSurface) findViewById(R.id.drawingSurface);
drawingSurface.setOnTouchListener(this);
}
private void setCurrentPaint(){
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFFFF00);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
}
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
currentDrawingPath = new DrawingPath();
currentDrawingPath.paint = currentPaint;
currentDrawingPath.path = new Path();
currentDrawingPath.path.moveTo(motionEvent.getX(), motionEvent.getY());
currentDrawingPath.path.lineTo(motionEvent.getX(), motionEvent.getY());
}else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){
currentDrawingPath.path.lineTo(motionEvent.getX(), motionEvent.getY());
}else if(motionEvent.getAction() == MotionEvent.ACTION_UP){
currentDrawingPath.path.lineTo(motionEvent.getX(), motionEvent.getY());
drawingSurface.addDrawingPath(currentDrawingPath);
}
return true;
}
public void onClick(View view){
switch (view.getId()){
case R.id.colorRedBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFF0000);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
break;
case R.id.colorBlueBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFF00FF00);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
break;
case R.id.colorGreenBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFF0000FF);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
break;
}
}
}Drawing Surface
public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {
private Boolean _run;
protected DrawThread thread;
public DrawingSurface(Context context, AttributeSet attrs) {
...
}
public void addDrawingPath (DrawingPath drawingPath){
thread.addDrawingPath(drawingPath);
}
class DrawThread extends Thread{
private SurfaceHolder mSurfaceHolder;
private List mDrawingPaths;
public DrawThread(SurfaceHolder surfaceHolder){
mSurfaceHolder = surfaceHolder;
mDrawingPaths = Collections.synchronizedList(new ArrayList());
}
...
public void addDrawingPath(DrawingPath drawingPath){
mDrawingPaths.add( drawingPath );
}
@Override
public void run() {
Canvas canvas = null;
while (_run){
try{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized(mDrawingPaths) {
Iterator i = mDrawingPaths.iterator();
while (i.hasNext()){
final DrawingPath drawingPath = (DrawingPath) i.next();
canvas.drawPath(drawingPath.path, drawingPath.paint);
}
}
} finally {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
....
} Explanation
<com.almondmendoza.drawings.DrawingSurface
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/drawingSurface"
/>
We start off by using our custom view in our xml, its as simple as creating a class and using it on the layout.
public class DrawingPath {
public Path path;
public Paint paint;
}
From Drawing with Canvas in Android we learn to put an ArrayList of Path which have a single color, for us to have different colors on different path, we create a class which would be an List in our DrawThread later on.
private Paint currentPaint;
....
private void setCurrentPaint(){
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFFFF00);
currentPaint.setStyle(Paint.Style.STROKE);
currentPaint.setStrokeJoin(Paint.Join.ROUND);
currentPaint.setStrokeCap(Paint.Cap.ROUND);
currentPaint.setStrokeWidth(3);
}
Here we create a Paint that would serve as our current paint, and set the currentPaint at the start of our activity.
public void onClick(View view){
switch (view.getId()){
case R.id.colorRedBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFFFF0000);
...
break;
case R.id.colorBlueBtn:
currentPaint = new Paint();
currentPaint.setDither(true);
currentPaint.setColor(0xFF00FF00);
...
This is how we change the currentPaint's color, since we use the Paint object, you could use any of its property/methods thus making every Path beautiful :)
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
currentDrawingPath = new DrawingPath();
currentDrawingPath.paint = currentPaint;
currentDrawingPath.path = new Path();
....
drawingSurface.addDrawingPath(currentDrawingPath);
}
return true;
}
In the previous tutorial, we bind our onTouch on the surfaceView directly, now we bind it in our activity, this way we could control the logic of the touches better (where you would apply your own shared touch library) and you could do things that doesnt harm the SurfaceView/Canvas.
public DrawThread(SurfaceHolder surfaceHolder){
mSurfaceHolder = surfaceHolder;
mDrawingPaths = Collections.synchronizedList(new ArrayList
}
As stated earlier we would create a List of DrawingPath, but in able to manipulate it while our thread is running we need to make sure that its thread-safe thus we call Collections.synchronizedList(new ArrayList
public void addDrawingPath (DrawingPath drawingPath){
thread.addDrawingPath(drawingPath);
}
From our activity we would pass a DrawingPath to our surfaceView which we would then pass it to our thread, we do this so that our thread would not be accessible to the outside world of our DrawingSurface, which i believe is better for we can inject logics that would deal with our SurfaceView before passing it to our thread.
public void addDrawingPath(DrawingPath drawingPath){
mDrawingPaths.add( drawingPath );
}
From our DrawingSurface to our DrawThread, we pass the a DrawingPath and add it to our list
synchronized(mDrawingPaths) {
Iterator i = mDrawingPaths.iterator();
while (i.hasNext()){
final DrawingPath drawingPath = (DrawingPath) i.next();
canvas.drawPath(drawingPath.path, drawingPath.paint);
}
}
Here we use the Iterator class to iterate over our DrawingPath list and draw our Path on the canvas.
Conclusion
Now we learn how to better put our codes and make sure it can be scale up (im a webdeveloper, ahha) in the future.
Hope this helps :)
Subscribe to:
Posts (Atom)




