How to Build A Drawing Android App

All the kids love drawing, so let’s see how to make such an app for them.

Project Setup

Start by creating a new Android Studio project, I’ll name it AndroidDrawing. Use the API 15 as the minimum SDK and the Phone & Tablet form factor. We’ll only have one, empty, activity, the MainActivity.

The layout of our app will be quite simple: we’ll have some colors to choose from and a white canvas to draw on. Below is the activity_main.xml layout file:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:background="#AFAFAF"
    tools:context=".MainActivity">
    <View
        android:id="@+id/canvas"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/colorPicker"
        android:background="#FFFFFF"/>
    <LinearLayout
        android:id="@+id/colorPicker"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#FF0000"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#00FF00"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#0000FF"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#000000"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

As you can see, we started with four colors, red, green, blue and black, and an empty white view as our canvas.

Check the code on GitHub.

Drawing Paths

The drawing process works as follows: every time the users touches the canvas view, we will create a new path and, with every user move, we will add a line from the previous point to the current one. The path will be drawn to the canvas as soon as possible by the system. We tell Android that it needs to redraw the canvas view by calling the invalidate method each time we make a change to the path. You should not call the onDraw method of the canvas view, that’s the system’s job.

Let’s start implementing the above by creating a TouchListener class to handle user touches:

public class TouchListener implements View.OnTouchListener {

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        DrawingView drawingView = (DrawingView) view;
        Path path;

        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                path = new Path();
                path.moveTo(x, y);
                drawingView.addPath(path);
                break;
            case MotionEvent.ACTION_MOVE:
                path = drawingView.getLastPath();
                if (path != null) {
                    path.lineTo(x, y);
                }
                break;
        }

        drawingView.invalidate();

        return true;
    }
}

This TouchListener class needs a “special” view to be able to create and alter paths in it as the user interacts with the screen. We will create a new DrawingView class for this purpose:

public class DrawingView extends View {

    private ArrayList<Path> paths = new ArrayList<>();

    public DrawingView(Context context) {
        super(context);
    }

    public DrawingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public DrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public DrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void addPath(Path path) {
        paths.add(path);
    }

    public Path getLastPath() {
        if (paths.size() > 0) {
            return paths.get(paths.size() - 1);
        }

        return null;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        for (Path path : paths) {
            Paint paint = new Paint();
            paint.setColor(0X80000000);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(3f);
            canvas.drawPath(path, paint);
        }
    }
}

This new DrawigView class extents the base Android View class and adds some methods we need to create new paths. It also has to define new constructors that simply call the View‘s constructors. The drawing of out custom path objects is done in the onDraw method that will be called by the system when needed.

Next, we need to change the canvas view in our layout to be this new DrawingView class:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ...>
    <com.dragosholban.androiddrawing.DrawingView
        ... />
    <LinearLayout ...>
        ...
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

Finally, to be able to draw, we need to add the touch listener class to our view, in the ManActivity class:

public class MainActivity extends AppCompatActivity {

    DrawingView drawingView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawingView = findViewById(R.id.canvas);
        drawingView.setOnTouchListener(new TouchListener());
    }
}

Run the app now and draw something on the screen using your finger. It should work like you can see below:

 

Check the code on GitHub.

Changing the Color

To change the color we need to add a new method in our MainActivity class, to be called when the user taps on one of the color buttons:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ...>
    <com.dragosholban.androiddrawing.DrawingView .../>
    <LinearLayout ...>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#FF0000"
            android:onClick="setColor"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#00FF00"
            android:onClick="setColor"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#0000FF"
            android:onClick="setColor"/>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#000000"
            android:onClick="setColor"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>
public class MainActivity extends AppCompatActivity {

    DrawingView drawingView;

    // ...

    public void setColor(View view) {
        ColorDrawable buttonColor = (ColorDrawable) view.getBackground();
        drawingView.setCurrentColor(buttonColor.getColor());
    }
}

This method will get the background color of the view and set it as the curent color of our drawingView. The drawingView will need to use the currentColor variable to draw the paths, until a new color is set.

public class DrawingView extends View {

    private ArrayList<Path> paths = new ArrayList<>();
    private ArrayList<Integer> colors = new ArrayList<>();
    private int currentColor = 0xFF000000;

    // ...

    public void addPath(Path path) {
        paths.add(path);
        colors.add(currentColor);
    }

    // ...

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int i = 0;
        for (Path path : paths) {
            Paint paint = new Paint();
            paint.setColor(colors.get(i));
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(3f);
            canvas.drawPath(path, paint);
            i++;
        }
    }

    public void setCurrentColor(int color) {
        currentColor = color;
    }
}

Run the app again now and start using the power of colors in your drawings:

Check the code on GitHub.

Changing the Brush Size

Now, that we can draw using multiple colors, let’s add a way to change the thickness of the paths we draw. To set this new parameter of our app, we will use an Android SeekBar widget:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ...>
    <com.dragosholban.androiddrawing.DrawingView .../>
    <LinearLayout
        android:id="@+id/colorPicker"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toTopOf="@+id/seekBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">
        ...
    </LinearLayout>

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:max="9"
        android:progress="2"
        app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>

The next code will set our line width to the value of the seekBar‘s current progress, using an OnSeekBarChangeListener class:

public class MainActivity extends AppCompatActivity {

    DrawingView drawingView;
    SeekBar seekBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        seekBar = findViewById(R.id.seekBar);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                drawingView.setCurrentWidth(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    // ...
}

The DrawingView class will use the width values set by the user to draw of each of the paths. To make things a little more visible, we will add 1 to the raw value of the width and multiply the result by 2:

public class DrawingView extends View {

    // ...
    private ArrayList<Integer> widths = new ArrayList<>();
    private int currentWidth = 6;

    // ...

    public void addPath(Path path) {
        paths.add(path);
        colors.add(currentColor);
        widths.add(currentWidth);
    }

    // ...

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int i = 0;
        for (Path path : paths) {
            Paint paint = new Paint();
            paint.setColor(colors.get(i));
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(widths.get(i));
            canvas.drawPath(path, paint);
            i++;
        }
    }

    // ...

    public void setCurrentWidth(int width) {
        currentWidth = (width + 1) * 2;
    }
}

You can run the app again now and make more complex drawings, using different widths for the brush:

Check the code on GitHub.

Adding an Eraser and Starting a New Drawing

In the last part of this tutorial, we will add an eraser brush to delete parts of the drawing and a new button to delete everything and start over.

Both are quite simple to implement. Let’s start with the eraser. This will be just another color button using white. This way, every time the user draws using it, it will just cover whatever is behind it with the background color and it will look like it was erased.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ...>
    <com.dragosholban.androiddrawing.DrawingView
        ... />
    <LinearLayout ...>
        ...
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="1dp"
            android:background="#FFFFFF"
            android:onClick="setColor"/>
    </LinearLayout>

    <SeekBar
        ... />
</android.support.constraint.ConstraintLayout>

If you try to use it now, you will notice that it is quite thin for an eraser. Let’s make it a lot thicker by giving it a tag and making the width four times bigger in it’s case.

<View
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:layout_margin="1dp"
    android:background="#FFFFFF"
    android:onClick="setColor"
    android:tag="eraser" />

The setColor method from the MainActivity will check for the above eraser tag and set the current width of the drawing accordingly:

public void setColor(View view) {
    ColorDrawable buttonColor = (ColorDrawable) view.getBackground();
    drawingView.setCurrentColor(buttonColor.getColor());
    if (view.getTag() != null && view.getTag().equals("eraser")) {
        drawingView.setCurrentWidth(seekBar.getProgress() * 4);
    } else {
        drawingView.setCurrentWidth(seekBar.getProgress());
    }
}

Try now and you should be able to use the eraser to fix small imperfections of your drawing.

The last button will clear everything so the user can start over with a new drawing:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ...>
    <com.dragosholban.androiddrawing.DrawingView
        .../>
    <LinearLayout ...>
        ...
        <Button
            android:id="@+id/button"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@android:drawable/ic_menu_delete"
            android:onClick="deleteDrawing" />
    </LinearLayout>
    ...
</android.support.constraint.ConstraintLayout>

The deleteDrawing method from the MainActivity, will show an alert asking the user to confirm the deletion of the current drawing, and delete everything if the user confirms.

public void deleteDrawing(View view) {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Are you sure you want to erase everything?")
            .setTitle("Delete Drawing")
            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    drawingView.erase();
                }
            })
            .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {

                }
            });
    AlertDialog dialog = builder.create();
    dialog.show();
}

The erase method from our DrawingView, will just clear all the paths, colors and widths and call the invalidate method to inform the system that the view needs to be redrawn.

public void erase() {
    paths.clear();
    colors.clear();
    widths.clear();
    invalidate();
}

That’s it! Try to run the app one more time and enjoy making drawings after drawings now that you are able to erase everything and start over any time you want.

 

Check the code on GitHub.

I hope you enjoyed building this and learned a lot along the way. Please let me know in the comments how it was for you, if you found any problems or if you have any improvements to suggest. Also, if this tutorial helped you build some other Android apps, please add some links in the comments and tell all of us about them.

You can get the final code from GitHub, in case something goes terribly wrong and you don’t manage to fix it. See you at the next Android tutorial!

Leave a Reply

Your email address will not be published. Required fields are marked *