Drawing and Drawables in Android Canvas

Exclusive offer: get 50% off this eBook here
Learning Android Canvas

Learning Android Canvas — Save 50%

Develop and deploy graphics-rich Android applications using Android Canvas with this book and ebook

£11.99    £6.00
by Mir Nauman Tahir | November 2013 | Open Source Web Development

In this article by Mir Nauman Tahir, the author of the book Learning Android Canvas, our goal is to learn about the following:

  • Drawing on a Canvas
  • Drawing on a View
  • Drawing on a SurfaceView
  • Drawables
  • Drawables from resource images
  • Drawables from resource XML
  • Shape Drawables

(For more resources related to this topic, see here.)

Android provides us with 2D drawing APIs that enable us to draw our custom drawing on the Canvas. When working with 2D drawings, we will either draw on view or directly on the surface or Canvas. Using View for our graphics, the drawing is handled by the system's normal View hierarchy drawing process. We only define our graphics to be inserted in the View; the rest is done automatically by the system. While using the method to draw directly on the Canvas, we have to manually call the suitable drawing Canvas methods such as onDraw() or createBitmap(). This method requires more efforts and coding and is a bit more complicated, but we have everything in control such as the animation and everything else like being in control of the size and location of the drawing and the colors and the ability to move the drawing from its current location to another location through code. The implementation of the onDraw() method can be seen in the drawing on the view section and the code for createBitmap() is shown in the Drawing on a Canvas section.

We will use the drawing on the View method if we are dealing with static graphics–static graphics do not change dynamically during the execution of the application–or if we are dealing with graphics that are not resource hungry as we don't wish to put our application performance at stake. Drawing on a View can be used for designing eye-catching simple applications with static graphics and simple functionality–simple attractive backgrounds and buttons. It's perfectly okay to draw on View using the main UI thread as these graphics are not a threat to the overall performance of our application.

The drawing on a Canvas method should be used when working with heavy graphics that change dynamically like those in games. In this scenario, the Canvas will continuously redraw itself to keep the graphics updated. We can draw on a Canvas using the main UI thread, but when working with heavy, resource-hungry, dynamically changing graphics, the application will continuously redraw itself. It is better to use a separate thread to draw these graphics. Keeping such graphics on the main UI thread will not make them go into the non-responding mode, and after working so hard we certainly won't like this. So this choice should be made very carefully.

Drawing on a Canvas

A Canvas is an interface, a medium that enables us to actually access the surface, which we will use to draw our graphics. The Canvas contains all the necessary drawing methods needed to draw our graphics. The actual internal mechanism of drawing on a Canvas is that, whenever anything needs to be drawn on the Canvas, it's actually drawn on an underlying blank bitmap image. By default, this bitmap is automatically provided for us. But if we want to use a new Canvas, then we need to create a new bitmap image and then a new Canvas object while providing the already created bitmap to the constructor of the Canvas class. A sample code is explained as follows. Initially, the bitmap is drawn but not on the screen; it's actually drawn in the background on an internal Canvas. But to bring it to the front, we need to create a new Canvas object and provide the already created bitmap to it to be painted on the screen.

Bitmap ourNewBitmap = Bitmap.CreateBitmap(100,100,Bitmap.Config.ARGB_8888); Canvas ourNewCanvas = new Canvas(ourNewBitmap);

Drawing on a View

If our application does not require heavy system resources or fast frame rates, we should use View.onDraw(). The benefit in this case is that the system will automatically give the Canvas its underlying bitmap as well. All we need is to make our drawing calls and be done with our drawings.

We will create our class by extending it from the View class and will define the onDraw() method in it. The onDraw() method is where we will define whatever we want to draw on our Canvas. The Android framework will call the onDraw() method to ask our View to draw itself.

The onDraw() method will be called by the Android framework on a need basis; for example, whenever our application wants to draw itself, this method will be called. We have to call the invalidate() method whenever we want our view to redraw itself. This means that, whenever we want our application's view to be redrawn, we will call the invalidate() method and the Android framework will call the onDraw() method for us. Let's say we want to draw a line, then the code would be something like this:

class DrawView extends View { Paint paint = new Paint(); public DrawView(Context context) { super(context); paint.setColor(Color.BLUE); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawLine(10, 10, 90, 10, paint); } }

Inside the onDraw() method, we will use all kinds of facilities that are provided by the Canvas class such as the different drawing methods made available by the Canvas class. We can also use drawing methods from other classes as well. The Android framework will draw a bitmap on the Canvas for us once our onDraw() method is complete with all our desired functionality. If we are using the main UI thread, we will call the invalidate() method, but if we are using another thread, then we will call the postInvalidate() method.

Drawing on a SurfaceView

The View class provides a subclass SurfaceView that provides a dedicated drawing surface within the hierarchy of the View. The goal is to draw using a secondary thread so that the application won't wait for the resources to be free and ready to redraw. The secondary thread has access to the SurfaceView object that has the ability to draw on its own Canvas with its own redraw frequency.

We will start by creating a class that will extend the SurfaceView class. We should implement an interface SurfaceHolder.Callback. This interface is important in the sense that it will provide us with the information when a surface is created, modified, or destroyed. When we have timely information about the creation, change, or destruction of a surface, we can make a better decision on when to start drawing and when to stop. The secondary thread class that will perform all the drawing on our Canvas can also be defined in the SurfaceView class.

To get information, the Surface object should be handled through SurfaceHolder and not directly. To do this, we will get the Holder by calling the getHolder() method when the SurfaceView is initialized. We will then tell the SurfaceHolder object that we want to receive all the callbacks; to do this, we will call addCallBacks(). After this, we will override all the methods inside the SurfaceView class to get our job done according to our functionality.

The next step is to draw the surface's Canvas from inside the second thread; to do this, we will pass our SurfaceHandler object to the thread object and will get the Canvas using the lockCanvas() method. This will get the Canvas for us and will lock it for the drawing from the current thread only. We need to do this because we don't want an open Canvas that can be drawn by another thread; if this is the situation, it will disturb all our graphics and drawings on the Canvas. When we are done with drawing our graphics on the Canvas, we will unlock the Canvas by calling the unlockCanvasAndPost() method and will pass our Canvas object. To have a successful drawing, we will need repeated redraws; so we will repeat this locking and unlocking as needed and the surface will draw the Canvas.

To have a uniform and smooth graphic animation, we need to have the previous state of the Canvas; so we will retrieve the Canvas from the SurfaceHolder object every time and the whole surface should be redrawn each time. If we don't do so, for instance, not painting the whole surface, the drawing from the previous Canvas will persist and that will destroy the whole look of our graphic-intense application.

A sample code would be the following:

class OurGameView extends SurfaceView implements SurfaceHolder.Callback { Thread thread = null; SurfaceHolder surfaceHolder; volatile boolean running = false; public void OurGameView (Context context) { super(context); surfaceHolder = getHolder(); } public void onResumeOurGameView (){ running = true; thread = new Thread(this); thread.start(); } public void onPauseOurGameView(){ boolean retry = true; running = false; while(retry){ thread.join(); retry = false; } public void run() { while(running){ if(surfaceHolder.getSurface().isValid()){ Canvas canvas = surfaceHolder.lockCanvas(); //... actual drawing on canvas surfaceHolder.unlockCanvasAndPost(canvas); } } } }

Learning Android Canvas Develop and deploy graphics-rich Android applications using Android Canvas with this book and ebook
Published: November 2013
eBook Price: £11.99
Book Price: £18.99
See more
Select your format and quantity:

Drawables

The two-dimensional graphics and drawing library that Android provides is called Drawable. The exact package name is android.graphics.drawable. This package provides all the necessary classes for drawing our 2D graphics.

In general, a Drawable is an abstraction for something that can be drawn. Android provides a number of classes that extends the Drawable class to define special types of Drawable graphics. The complete list can be found at http://developer.android.com/reference/android/graphics/drawable/package-summary.html.

Drawables can be defined and instantiated in three ways:

  • From an image saved in the resource folder of our project
  • From an XML file
  • From the normal class constructor

In the context of this article, we will explain only the first two methods.

Drawables from a resource image

This is the quickest and simplest method to add graphics to our application. By the end of this article, we will know how to copy an image to the resource folder and where to find the resource folder.

We will use the image that we have already copied in the res/drawable folder in our applications project. The image name is lacm_5396_01_14.png and the exact location is res/drawable-xhdpi. One important point here is that the supported formats are PNG, JPEG, and GIF. The most preferable format to use is PNG and the least preferable is GIF. Whenever we put an image in the res/drawable folder, during the build process, the image will be compressed with lossless compression to save system memory; this process is automatic. The compressed images normally retain the same quality but of a much lesser size. If we don't want the system to compress our images, we should copy our images to the res/raw folder.

We will open our project MyFirstCanvasApp. This is the code before we make any changes:

package com.learningandroidcanvasmini.myfirstcanvasapp; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MyFirstCanvasAppMainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_first_canvas_app_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it //is present. getMenuInflater().inflate(R.menu.my_first_canvas_app_main,menu); return true; } }

We will open our layout file activity_my_first_canvas_app_main.xml in Design view. We will delete the already placed ImageView object from the Activity. Now we will open our code file again and will add the following lines of code to the preceding code step-by-step. In our main activity class, we will define a LinearLayout object:

LinearLayout myLinearLayout;

This will be our custom layout on which we want to display our image using this code. Then, inside our main activity class, we will instantiate the LinearLayout object:

myLinearLayout = new LinearLayout(this);

Next we will add the following lines of code to our file:

ImageView MySecondImageView = new ImageView(this); MySecondImageView.setImageResource(R.drawable.lacm_5396_01_14); MySecondImageView.setAdjustViewBounds(true); MySecondImageView.setLayoutParams(new ImageView.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); myLinearLayout.addView(MySecondImageView); setContentView(myLinearLayout);

In the preceding block of code, first, we have defined an ImageView object. Then we set the source to an image that we want our ImageView object to display. In the next line, we have adjusted the View bounds so that the ImageView bounds match the width and height of the source image. The setLayoutParams method will help us wrap the view borders around the image content even if there is a difference in the dimensions. After this, we will supply our ImageView control to our custom layout using the code line:

myLinearLayout.addView(MySecondImageView);

In the last line, we will set the activity layout to our custom layout. To do this, we will set the content view to our custom layout:

setContentView(myLinearLayout);

Now, we will test our application in the emulator and then we will see the following on the emulator screen:

 

We went through all the hard work and complicated code because we had hardcoded the ImageView object to display only one image that we have defined in the Properties tab in the Design view. Now, when we deleted the ImageView object from the screen in the Design view and started coding, there was nothing on the screen in the Design view at that time. What we did in the preceding example was create our own custom layout that will host our graphics and drawing. We created an ImageView object and supplied it with a source image and, set its other properties. Later on, we added the ImageView object to our custom layout and at the end, we asked the activity to appear on the screen without a custom-created layout and the automatic layout. The code gives us the flexibility to keep our graphics application dynamic. We can supply our application with run-time images controlled from our code logic.

The complete code looks like this now:

package com.learningandroidcanvasmini.myfirstcanvasapp; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.ImageView; import android.widget.LinearLayout; public class MyFirstCanvasAppMainActivity extends Activity { LinearLayout myLinearLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_first_canvas_app_main); myLinearLayout = new LinearLayout(this); ImageView MySecondImageView = new ImageView(this); MySecondImageView.setImageResource(R.drawable.lacm_5396_01_14); MySecondImageView.setAdjustViewBounds(true); MySecondImageView.setLayoutParams(new ImageView.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); myLinearLayout.addView(MySecondImageView); setContentView(myLinearLayout); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it //
is present. getMenuInflater().inflate(R.menu.my_first_canvas_app_main, menu); return true; } }

If we want our resource image to be handled as a Drawable, we will create a Drawable object from our resource image:

Resources myRes = mContext.getResources(); Drawable myImage = myRes.getDrawable(R.drawable.5396_01_14);

Here, we need to understand that each resource in our Resources object can maintain only one state at a time. If we are using the same resource image in two different instances and we update the property of one instance, the image in the second instance will also reflect that change. So, whenever we are dealing with multiple instances of our Drawable object, instead of changing the Drawable itself, we can create a tween animation.

Drawables from resource XML

For those developers who have a little background of Android development, know that every activity in our application has an XML layout file. In this file, every View or control that we drag-and-drop on the Activity has an XML markup defined. So, we assume that the developers that are reading this book know how the user interface works while developing for Android. Objects can be defined and initialized in XML. If we are dealing with a graphic whose properties do not depend on what we plan to do in our code, or perhaps the graphic is static, it's a good way to define the graphic object in XML. Once the graphic is instantiated, its properties can always be tweaked according to need.

We will save the file in res/drawable, define the Drawable in XML, and get the Drawable by calling Resouces.getDrawable(). This method will take the resource ID as an argument from our XML file.

To exemplify, and to understand which Drawable can use this method and how we can have a look at the menu that's automatically created in our application, note the method in the preceding code onCreateOptionMenu(). When we click on the Menu button on the screen or from the hardware keys, we see a small menu at the bottom of the screen, named Settings. The menu has no functionality at this point. Now if we check the code of onCreateOptionMenu(), we see a call to the inflate() method. We can define any Drawable in XML that supports the inflate() method. The previously mentioned menu is a simple example of this.

The Settings menu can be seen in the following screenshot:

Let's say we want to go for an expand-collapse transition Drawable; the following code will get the job done for us in XML. This XML code will be saved in the res/drawable expand_collapse.xml file.

<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/image_expand">
<item android:drawable="@drawable/image_collapse"></transition>

The expand and collapse files are two different images saved in the drawable folder in our project. Now to get this transition working, we will need the following code:

Resources myRes = mContext.getResources(); TransitionDrawable myTransition = (TransitionDrawable)res.getDrawable
(R.drawable.expand_collapse);ImageView
myImage = (ImageView) findViewById(R.id.toggle_image);
myImage.setImageDrawable(myTransition);

First, we created a resources object from resources and asked the object to get everything from these resources (the resources are all the images and XML files that we have saved in the subfolders of the res folder in our project). Then, we created a TransitionDrawable object and asked the object to get the expand_collapse file from the res/drawable folder. After this, we will create an ImageView object that will get another view named toggle_image. In the last line of the preceding code, we set the Drawable type to the already-created transition.

Now including the following line of code will run the transition with a speed of once per second:

myTransition.startTransition(1000);

We won't go into too much detail about these transitions and animations as animation by itself is a very lengthy topic. But I can explain some key types of animation that can be done in Android while working with graphics to give the reader an idea of the domain and what animation covers. The types of animation in Android are as follows:

  • Property animation
  • View animation
  • Drawable animation

Shape Drawables

Whenever we want to draw certain shapes dynamically or programmatically on our Canvas, Shape Drawables come in handy. With Shape Drawables, we can draw circles and all forms of circles such as ovals, squares, rectangles, and many other shapes. To explain Shape Drawables, we will start a new project. We will name our project MyShapeDrawablesApp and go through the same steps using a blank starting activity. Our objective of this exercise is to draw an oval on the screen with some color filled within it.

  1. To do this, we will add another class inside our main activity class just before the ending bracket. We will name our class MyCustomDrawableView that will extend the View class.

    public class MyCustomDrawableView extends View {.....

  2. Inside the constructor of this class, we will define our drawing. We will define a ShapeDrawable object and provide the OvalShape() method to its constructor as an argument to define the type of shape:

    myDrawable = new ShapeDrawable(new OvalShape());

  3. Next, we will get the paint object and set the color for our ShapeDrawable object:

    myDrawable.getPaint().setColor(0xff74fA23);

  4. After that, we will define the dimensions of the object that is to be drawn. Let's say we want to draw an oval shape. The first x, y are the points from where it will start and the next are the width and height of the oval, as shown:

    myDrawable.setBounds(x, y, x + width, y + height);

  5. We will close the constructor at this point and will define the onDraw() method for our object. In this method we will call the draw() method for our object.

    protected void onDraw(Canvas canvas) { myDrawable.draw(canvas); }

  6. The next step would be to create an object of our custom class in the main activity class and set the content view to our new custom class:

    MyCustomDrawableView myCustomDrawableView; . . . myCustomDrawableView = new MyCustomDrawableView(this); setContentView(myCustomDrawableView);

  7. We will run the application in the emulator.
  8. The following screenshot shows a green oval drawn on the Canvas:

The complete code for the MyShapeDrawablesMainActivity.java file is as follows:

package com.learningandroidcanvasmini.myshapedrawablesapp; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.view.Menu; import android.view.View; public class MyShapeDrawablesMainActivity extends Activity { MyCustomDrawableView myCustomDrawableView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_shape_drawables_main); myCustomDrawableView = new MyCustomDrawableView(this); setContentView(myCustomDrawableView); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is //present. getMenuInflater().inflate(R.menu.my_shape_drawables_main, menu); return true; } public class MyCustomDrawableView extends View { private ShapeDrawable myDrawable; public MyCustomDrawableView(Context context) { super(context); int x = 10; int y = 10; int width = 300; int height = 50; myDrawable = new ShapeDrawable(new OvalShape()); myDrawable.getPaint().setColor(0xff74fA23); myDrawable.setBounds(x, y, x + width, y + height); } protected void onDraw(Canvas canvas) { myDrawable.draw(canvas); } } }

Summary

In this article we have learned about the Canvas class, its methods, properties, and how we can use them to draw. We have also learned about the View and SurfaceView classes and learned how to draw using both of these classes and which one to use in which scenario. We have also learned about Drawables and some of the different ways of using them to draw such as from images in the resources or from the XML code in resources and drawing shapes using code. We also saw a functional example while working with Shape Drawables and Drawing on a Canvas. The source code for both the example applications is downloadable from the Packt Publishing website.

Resources for Article:


Further resources on this subject:


Learning Android Canvas Develop and deploy graphics-rich Android applications using Android Canvas with this book and ebook
Published: November 2013
eBook Price: £11.99
Book Price: £18.99
See more
Select your format and quantity:

About the Author :


Mir Nauman Tahir

Mir Nauman Tahir completed his MS in Information Technology from Khyber Pakhtoonkhwa, Pakistan. He specialized in web technologies. He is also a Microsoft Certified Technology Specialist. He started his professional programming career in 2004 and has worked on developing different systems, such as Library Management System, Procurement System, a lot of web applications, and dynamic websites with content management systems. Mir worked on the Android source code for almost 1.5 years in a research group as a developer and published a research paper on the basis of his research with that team. Mir has also worked on other open source technologies such as OpenERP, PostGre-SQL, and different flavors of Linux. Mir currently has more than eight years of industry experience consisting majorly of in-house development for some of Pakistan's most well reputed organizations and universities.

He started his career as a Software Engineer for COMSATS Institute of Information Technology, Abbottabad, Pakistan and has since worked with Security Engineering Research Group, Peshawar, Pakistan; UN-HABITAT, Pakistan; Save the Children, Pakistan; and currently working as an Information Management Officer in a well-reputed humanitarian organization in Pakistan.

This is Mir's first book, but he writes most of his technical articles on his personal blog, http://mirnauman.wordpress.com. The articles are mostly about Android and Microsoft .Net technologies. Mir loves sketching and playing computer games.

Books From Packt


Android Application Programming with OpenCV
Android Application Programming with OpenCV

Augmented Reality for Android Application Development
Augmented Reality for Android Application Development

Android User Interface Development: Beginner's Guide
Android User Interface Development: Beginner's Guide

 Android Application Security Essentials
Android Application Security Essentials

Instant Android Fragmentation Management How-to [Instant]
Instant Android Fragmentation Management How-to [Instant]

Android 4: New Features for Application Development
Android 4: New Features for Application Development

Android Development Tools for Eclipse
Android Development Tools for Eclipse

Android Database Programming
Android Database Programming


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software