In this blog post, I will show you how to create Android drawing app. This blog post is part one of a two part tutorial series where I will show you how to create simple but delightful Android drawing app. As the name indicates this drawing app will enable users to draw or sketch on their Android devices using their fingers. You can get the source code for this tutorial from here. The core features of this app will include:
- Draw – Users will be able to draw on a blank canvas (whiteboard) .
- Erase – Users will be able to erase what has been drawn.
- Undo – Users will be able to undo and redo drawing paths.
- Color – Users will be able to draw using a color of their choice from at least these colors: black, dark gray, light gray, blue, red, and green, orange, yellow .
- Share – Users will be able to capture a screen shot and email it to a friend.
Step 1: Create New Project – create a brand new Android Studio project, select the default minimum API which is 16, select the default Blank template. Leave the first Activity as MainActivity and click finish.
Step 2: Set Orientation – set the orientation of the MainActivity to portrait in the manifest. We will focus on creating a functional drawing app and we will skip the concerns of handling configurations changes for now.
Step 3: Add Dimensions – we will define the dimensions we will be using for this app in a single location which is res/values/dimen.xml, this file should already have been created for you by Android Studio, if not add one and then copy and paste the contents of this file into your dimens.xml file
Step 4: Add Colors – in a similar manner to the dimensions, copy and paste the contents of this file to your res/values/colors.xml file. Alternatively you can generate and download Material Design colors from materialpallete.com
Inheritance is one of the core pillars of object oriented programming in that it gives you a head start when creating qualifying objects instead of re-inventing the wheel. The core of the work that we need to do to create an Android drawing app happens in a class we will call CustomView.java. This class will derive from the android.view Class; by sub-classing the Android View class our Custom View class will at ounce inherit the potential to behave like every other view in Android and we will build upon this potential to create a drawing app. Follow the following steps to implement our Custom View class.
Step 1: Add Java Class – if you have not already done so, at the root of your project add standard Java class file named CustomView.java and then have this class extend the framework’s View class ( android.view)
Step 2: Implement Constructor – once your custom view class extends the framework View class you will be prompted to implement a constructor that matches the super class. Using Android Studio quick fix shortcut (alt + enter) add the second option in the list, the one that accepts two parameters a Context and an AttributeSet and once you do the re squiggly line will go away.
Step 3: Update Layout – even though all we have is a skeleton custom view, let us go ahead and make it the root view of our MainActivity. Change the root component of your content_main.xml layout file to your custom view. You will have to specify its package like below and you can go ahead and remove the default paddings added by the template.
<?xml version="1.0" encoding="utf-8"?>
<com.okason.drawingapp.CustomView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/custom_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main" />
Step 4: Test the View – You can now run your app to test the View. As you can expect, it will be a blank screen like the one below. However the good thing is that it did not give any error. We will now proceed to implement the use cases for this app which are draw, erase, undo, share and choose color.
Much of Android development consists of responding to events. You write code that will be executed when a particular event occurs. The system notifies you that this event has occurred through life-cycle events and listeners. To draw something on the screen, you have to respond to or override one of such events – the onDraw() event which is where every view draws itself.
In the onDraw() method you are passed the Canvas for the view that you have sub-classed, and what is a Canvas you may ask. Good thing that you asked because Canvas is one of the three components that you must understand to effectively create an Android drawing app. The other two are the Paint and the Path. So let us examine this core components, first the official definition of the Canvas.
The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
http://developer.android.com/reference/android/graphics/Canvas.html
- Canvas – as the name implies, the Canvas is what you draw upon. It is the drawing surface for what you are drawing and it knows how to transfer that drawing to the actual View. The Canvas is your convenient middle man that abstracts away the nuances of writing directly to the underlying view. The Android developer guide calls it an “interface” which pretends to be “the actual surface upon which your graphics will be drawn” while in reality , it holds all of your “draw” calls and then places them into the window for you. The TwoToasters development team gave a good description of a Canvas and the other two core components in this blog post.
- Paint – after you obtain the surface to draw upon – the Canvas, you also need something to draw with – the Paint. As the name implies the Paint object “holds the style and color information”. It defines the “color, style, font, and so forth of each shape you draw”.
- Path – now that you have the surface to draw upon and the drawing object to use to draw upon that, you need to decide what direction that you want to draw. Do you want to go up, down, left or right. This direction is know as the Path as in a path. It provides “geometric paths consisting of straight line segments, quadratic curves, and cubic curves.” To help you better understand the concept of Path, remember that at the beginning of this post I mentioned that we will implement “Undo” which is a common feature in most drawing apps. The way we will implement it is to keep a history of every path we draw, which is anytime we go from point A to point B. So if the user want to “Undo” their last drawing all we have to do is to remove the last entry in our list of Paths, wipe the screen clean and then re-draw everything again in that list except this time we will not re-draw that Path which have been removed. This happens at a blazing speed giving the user the illusion that we just erased the last thing they draw.
Let us now proceed to apply the concepts we learned above to our drawing. Before we start drawing, it is recommended by the Android developer guide to create the objects that you will need for drawing ahead of time. Because creating them within the onDraw() method will degrade your app performance.
Step 1 – Add Required Components – at the top of your CustomView.java class, declare the following instance variables to represent the different components we will use for this app. I have added comments to convey what each object is used for.
private Path drawPath;
private Paint canvasPaint;
private Paint drawPaint;
private int paintColor = 0xFF660000;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private float currentBrushSize, lastBrushSize;
Step 2 – Initialize Variables – initializing graphic objects is an expensive computational task so it is recommended that we do that before we start drawing and the best place to initialize our components is in the constructor. However instead of cluttering the constructor we can create a private method called init(). Go ahead and add the init() method to your CustomView.java class and below is the content of that method for now.
private void init(){
currentBrushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = currentBrushSize;
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(currentBrushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
Step 3 – Update Constructor – with the init method in place, we can now call it from the constructor like this
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
Step 4 – Override onDraw – drawing for all View objects happens in the onDraw(), if we had sub-classed a View sub-class like the Button for instance, then our Custom View will in the minimum know how to draw itself as a button. Since we sub classed the view directly we have to tell our Custom View how to draw itself via the onDraw(). Update your onDraw() method with the code below.
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0 , 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
You can get the source code for this Tutorial from here.
Step 5: Implement OnSizeChanged – this method is called during layout when the size of this view has changed. During creation the size of our view starts at 0 until the view rendering engine finishes calculating the size of your screen then it will call this method to update the size. Remember that when we added this view to our layout we declared the width and height of it to be match parent, well the height and width of that parent has to be calculated and the size of the view will adjust accordingly. You can essentially consider this method as part of the initialization routines of the view and it is inside this method that we initialize our bitmap and canvas object, if you don’t a Null Exception will be thrown when you try to use those objects in your onDraw method. Add this method below to your CustomView.java class file.
@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);
}
Step 6: Implement Touch Listener – at this point our custom view can draw something if we call it programmatically with the appropriate X and Y coordinates, however I suspect that you did not want to create an Android drawing app that can only be told to draw through code. The users of your app will want to move their finger on the screen and see something draw, in other for that to happen you have to register touch events as drawing actions. Your CustomView class has an onTouchEvent() method that you can override to accomplish this. Here is the code that accomplishes this:
@Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
invalidate();
return true;
}
Here is what was going on in the above code:
- We are passed an object of type MotionEvent
- Using this MotionEvent we get the X and Y coordinates of where the touch occurred in the screen. We store these coordinates in touchX and Y variables
- We then run a switch statement on the event.getAction() method which return a result of an int.
- If this action evaluates to a ACTION_DOWN constant of the MotionEvent class, we know that the user just touched the screen so we move to that point.
- If the int action evaluates to ACTION_MOVE, then we know that the user intents to draw, and we use our Path object to draw a line from point X to point Y
- If the action avaluates to ACTION_UP, then we know that the user is done, so time to transfer the drawing from the surface (Canvas) which we are drawing upon to the actual screen. And then we reset the Path object (drawPath) so that we can start afresh next time the screen is touched.
- When we are done, we have to notify the view that the content of what it is currently displaying has changed by calling invalidate(), this will call the onDraw() method and the screen will be repainted.
Go ahead and give it a try at this point, you should be able to draw to the screen using your finger or any other input.
There are two ways we can implement erasing our drawing: we can wipe off everything in the screen and start afresh or we can manually erase our drawing line by line. When we erase everything in the screen at once, what we are doing is essentially starting afresh and when we erase our drawing manually what we are doing in a nutshell is painting white color over our existing drawing to give it a resemblance of being “erased” .
There are three steps in implementing either types of our erase and for that matter any of the other operations that we want to implement such as undo, save, etc. First we have to create a method that accomplishes each use case, then we add an icon or button or something that the user will have to click to signal the action that they want to perform, then we attach a listener to each button and when clicked we call the appropriate method.
We will place all of our drawing related icons in a bottom toolbar. Our new project template already have a top toolbar, now we want to add another toolbar to the bottom and then we will add the icons to it.
Add Button Toolbar
Follow the steps below to add another material design toolbar to the bottom of your app.
Step 1: Update Your Layout – currently your MainActivity layout file is using Cordinator layout which then pulls in content_main.xml. We are going to change it to use RelativeLayout so we can position our bottom toolbar at the bottom. And we no longer need the Floating Action button. So go ahead and update your activity_main.xml with the layout code below. Remember to use your package name, here it the updated layout.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<com.okason.drawingapp.CustomView
android:id="@+id/custom_view"
android:layout_below="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/toolbar_bottom" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_bottom"
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</RelativeLayout>
Step 2: Add Icons – you will need to choose the icons you want to represent the actions you want to perform in your app. There are few sites where you can get great looking Android Icons and I will list a few below.
- http://www.androidicons.com/
- Android Asset Studio
- http://icons4android.com/
- https://materialdesignicons.com/
- Google Material Icons
The icons included in this tutorial’s source code are from https://materialdesignicons.com/ all of the above options are fine, choose which ever one you like and the color you like. After you choose your icons you have to download them and then add them to your res/resource folder.
Step 3: Add Menu – now that you have your icons you actually need to add them to a menu which will then inflate into our bottom toolbar. Add a menu to your res/menu folder and call it menu_drawing.xml and here is the content of the menu with entries covering the drawing operations that we want to cover.
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<item
android:id="@+id/action_erase"
android:title="@string/action_erase"
android:icon="@drawable/ic_eraser_white_24dp"
app:showAsAction="always" />
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
android:icon="@drawable/ic_delete_white_24dp"
app:showAsAction="always" />
<item
android:id="@+id/action_undo"
android:title="@string/action_undo"
android:icon="@drawable/ic_undo_variant_white_24dp"
app:showAsAction="always" />
<item
android:id="@+id/action_brush"
android:title="@string/action_brush"
android:icon="@drawable/ic_brush_white_24dp"
app:showAsAction="always" />
<item
android:id="@+id/action_color"
android:title="@string/action_color"
android:icon="@drawable/ic_eyedropper_variant_white_24dp"
app:showAsAction="always" />
<item
android:id="@+id/action_save"
android:title="@string/action_save"
android:icon="@drawable/ic_sd_white_24dp"
app:showAsAction="always" />
</menu>
Step 4: Add Bottom Toolbar Java Code – now that we have added a menu layout with the icons that we want to add, we need to go ahead and use Java code to inflate that menu into our bottom toolbar. Update your MainActivity.java with the following code.
public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar_top;
private Toolbar mToolbar_bottom;
private FloatingActionButton mFab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar_top = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar_top);
mToolbar_bottom = (Toolbar)findViewById(R.id.toolbar_bottom);
mToolbar_bottom.inflateMenu(R.menu.menu_drawing);
mToolbar_bottom.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
handleDrawingIconTouched(item.getItemId());
return false;
}
});
mFab = (FloatingActionButton) findViewById(R.id.fab);
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mToolbar_bottom.setVisibility(View.VISIBLE);
mFab.setVisibility(View.GONE);
}
});
}
private void handleDrawingIconTouched(int itemId) {
switch (itemId){
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
return super.onOptionsItemSelected(item);
}
}
Code Walkthrough – in the above code block, what we did was:
- Declare class instance variables for our top and button Toolbars as well as the Floating Action Button (FAB)
- You may want to change the Floating Action Button icon to a “+” instead of the main in XML layout
- We instantiated our bottom tool, added an OnMenuItemClicked listener but do not do anything with it at the moment
- When the FAB is clicked instead of showing the Snackbar that was added by the template we hide FAB and show our bottom toolbar
If you run your app now, it should look like this. In the next tutorial we will pick up from where we left.
If you like tutorial, please share with anyone that can benefit from it and do use the comment box to leave me a comment, ask questions or provide feedback.
Keep Coding!
The post Android Drawing App Tutorial – Pt. 1 appeared first on Val Okafor.