Open In App

How to Create a Paint Application in Android?

Last Updated : 16 Feb, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

We all have once used the MS-Paint in our childhood, and when the system was shifted from desks to our palms, we started doodling on Instagram Stories, Hike, WhatsApp, and many more such apps. But have you ever thought about how these functionalities were brought to life? So, In this article, we will be discussing the basic approach used by such apps and will create a basic replica of such apps. A sample video is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Java language. 

The Approach

  1. In day-to-day life, if we want to create a drawing we firstly require a Canvas to work upon. So, in our app, we will firstly create a canvas where the user can draw his drawings. And for that, we need to create a custom view where the user could simply drag the finger to draw the strokes. In order to achieve this, we create a DrawView class which extends the View class from the standard Android SDK.
  2. Then we will require a brush that acts as a tool to help us draw on the canvas. Now since we need different brushes for different colors and different widths of the stroke, we will create a blueprint i.e. a class named Stroke with attributes like the color of the stroke, the width of the stroke, visibility of stroke, etc. Each object of this class will represent a distinct brush that draws a unique stroke on the canvas.
  3. In order to keep a record of each and every stroke the user has drawn on the Canvas, we will create an ArrayList of type Stroke. This ArrayList will help us in undoing the Stroke which the user has drawn mistakenly on the Canvas.
  4. Now, at the end when the user is done with the drawing, he might want to save that painting for any further use. So, we provide the Save option, which will allow the user to save the current canvas in form of a PNG or JPEG.

List of Methods

Before jumping to the code here are a few of the methods which we will be using in building our app:

Type

Method

Description

void setDither(boolean dither)

Dithering affects the down-sampling of colors that are of

higher precision than the device’s accuracy.

void setAntiAlias (boolean aa)

AntiAliasing smooths out the edges of what is drawn but

has little effect on the shape’s interior.

void setStyle(Paint.Style style) This method controls the 
void setStrokeCap (Paint.Cap cap)

This method changes the geometry of the endpoint of the

line as per the argument For example, ROUND, SQUARE, BUTT.

void void setStrokeJoin (Paint.Join join) This method sets the paint to join to either ROUND, BEVEL, MITER
void setAlpha (int a)

It is a helper method that only assigns the color’s 

alpha value, leaving its r,g,b values unchanged. 

Results are undefined if the alpha value is outside of the range [0..255]

void invalidate()

This method calls the overridden onDraw() method. 

Whenever we want to update the screen, in our case the Canvas,

we call invalidate() which further internally calls the onDraw() method.

int Canvas.save()

This method saves the current state of the Canvas 

so that we can go back to it later

void Canvas.restore()

This method reverts the Canvas’s adjustments back to 

the last time the was canvas.save() called.

void Path.quadTo (float x1,float y1, float x2, float y2)

This method smoothens the curves using a quadratic line.

(x1,y1) is the control point on a quadratic curve and (x2,y2) 

are the endpoint on a quadratic curve.

Now, let us start building the app. This app doesn’t require any special permissions. So leave the AndroidManifest.xml as default.

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Java as the programming language.

Step 2: Adding the dependency in gradle.build

This library is used to add a color palette to our app so that the user can select any color of his choice

implementation ‘petrov.kristiyan:colorpicker-library:1.1.10’

Step 3: Working with the activity_main.xml file

Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. 

XML




<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
     
    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <ImageButton
                android:id="@+id/btn_undo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:src="@drawable/ic_undo"
                android:text="Undo" />
 
            <ImageButton
                android:id="@+id/btn_save"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:src="@drawable/ic_floppy_disk"
                android:text="Save" />
 
            <ImageButton
                android:id="@+id/btn_color"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:src="@drawable/ic_colorpicker"
                android:text="Color" />
 
            <ImageButton
                android:id="@+id/btn_stroke"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:src="@drawable/ic_paint_brush"
                android:text="Stroke" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
 
            <com.google.android.material.slider.RangeSlider
                android:id="@+id/rangebar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone" />
           
        </LinearLayout>
       
    </LinearLayout>
 
    <com.raghav.paint.DrawView
        android:id="@+id/draw_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/linear"
        android:layout_centerInParent="true" />
     
</RelativeLayout>


Step 4: Create the Stroke class

Refer to the How to Create Classes in Android Studio. And name the class as Stroke. Below is the code for the Stroke.java file. 

Java




import android.graphics.Path;
 
public class Stroke {
 
    // color of the stroke
    public int color;
     
    // width of the stroke
    public int strokeWidth;
     
    // a Path object to
    // represent the path drawn
    public Path path;
 
    // constructor to initialise the attributes
    public Stroke(int color, int strokeWidth, Path path) {
        this.color = color;
        this.strokeWidth = strokeWidth;
        this.path = path;
    }
}


Step 5: Create the DrawView class

Similarly, create a new java class and name the class as DrawView. Below is the code for the DrawView.java file. 

Java




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
 
import java.util.ArrayList;
 
public class DrawView extends View {
 
    private static final float TOUCH_TOLERANCE = 4;
    private float mX, mY;
    private Path mPath;
     
    // the Paint class encapsulates the color
    // and style information about
    // how to draw the geometries,text and bitmaps
    private Paint mPaint;
    
    // ArrayList to store all the strokes
    // drawn by the user on the Canvas
    private ArrayList<Stroke> paths = new ArrayList<>();
    private int currentColor;
    private int strokeWidth;
    private Bitmap mBitmap;
    private Canvas mCanvas;
    private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
 
    // Constructors to initialise all the attributes
    public DrawView(Context context) {
        this(context, null);
    }
 
    public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
         
        // the below methods smoothens
        // the drawings of the user
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        
        // 0xff=255 in decimal
        mPaint.setAlpha(0xff);
 
    }
 
    // this method instantiate the bitmap and object
    public void init(int height, int width) {
 
        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
 
        // set an initial color of the brush
        currentColor = Color.GREEN;
         
        // set an initial brush size
        strokeWidth = 20;
    }
 
    // sets the current color of stroke
    public void setColor(int color) {
        currentColor = color;
    }
 
    // sets the stroke width
    public void setStrokeWidth(int width) {
        strokeWidth = width;
    }
 
    public void undo() {
        // check whether the List is empty or not
        // if empty, the remove method will return an error
        if (paths.size() != 0) {
            paths.remove(paths.size() - 1);
            invalidate();
        }
    }
 
    // this methods returns the current bitmap
    public Bitmap save() {
        return mBitmap;
    }
 
    // this is the main method where
    // the actual drawing takes place
    @Override
    protected void onDraw(Canvas canvas) {
        // save the current state of the canvas before,
        // to draw the background of the canvas
        canvas.save();
         
        // DEFAULT color of the canvas
        int backgroundColor = Color.WHITE;
        mCanvas.drawColor(backgroundColor);
 
        // now, we iterate over the list of paths
        // and draw each path on the canvas
        for (Stroke fp : paths) {
            mPaint.setColor(fp.color);
            mPaint.setStrokeWidth(fp.strokeWidth);
            mCanvas.drawPath(fp.path, mPaint);
        }
        canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
        canvas.restore();
    }
 
    // the below methods manages the touch
    // response of the user on the screen
 
    // firstly, we create a new Stroke
    // and add it to the paths list
    private void touchStart(float x, float y) {
        mPath = new Path();
        Stroke fp = new Stroke(currentColor, strokeWidth, mPath);
        paths.add(fp);
 
        // finally remove any curve
        // or line from the path
        mPath.reset();
         
        // this methods sets the starting
        // point of the line being drawn
        mPath.moveTo(x, y);
        
        // we save the current
        // coordinates of the finger
        mX = x;
        mY = y;
    }
 
    // in this method we check
    // if the move of finger on the
    // screen is greater than the
    // Tolerance we have previously defined,
    // then we call the quadTo() method which
    // actually smooths the turns we create,
    // by calculating the mean position between
    // the previous position and current position
    private void touchMove(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
 
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }
 
    // at the end, we call the lineTo method
    // which simply draws the line until
    // the end position
    private void touchUp() {
        mPath.lineTo(mX, mY);
    }
 
    // the onTouchEvent() method provides us with
    // the information about the type of motion
    // which has been taken place, and according
    // to that we call our desired methods
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
 
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStart(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                touchMove(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                touchUp();
                invalidate();
                break;
        }
        return true;
    }
}


Step 6: Working with the MainActivity.java file

Go to the MainActivity.java file and refer to the following code. Below is the code for the MainActivity.java file. Comments are added inside the code to understand the code in more detail. 

Java




import android.content.ContentValues;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageButton;
 
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
 
import com.google.android.material.slider.RangeSlider;
 
import java.io.OutputStream;
 
import petrov.kristiyan.colorpicker.ColorPicker;
 
public class MainActivity extends AppCompatActivity {
     
    // creating the object of type DrawView
    // in order to get the reference of the View
    private DrawView paint;
     
    // creating objects of type button
    private ImageButton save, color, stroke, undo;
     
    // creating a RangeSlider object, which will
    // help in selecting the width of the Stroke
    private RangeSlider rangeSlider;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // getting the reference of the views from their ids
        paint = (DrawView) findViewById(R.id.draw_view);
        rangeSlider = (RangeSlider) findViewById(R.id.rangebar);
        undo = (ImageButton) findViewById(R.id.btn_undo);
        save = (ImageButton) findViewById(R.id.btn_save);
        color = (ImageButton) findViewById(R.id.btn_color);
        stroke = (ImageButton) findViewById(R.id.btn_stroke);
 
        // creating a OnClickListener for each button,
        // to perform certain actions
 
        // the undo button will remove the most
        // recent stroke from the canvas
        undo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                paint.undo();
            }
        });
         
        // the save button will save the current
        // canvas which is actually a bitmap
        // in form of PNG, in the storage
        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 
                // getting the bitmap from DrawView class
                Bitmap bmp = paint.save();
                 
                // opening a OutputStream to write into the file
                OutputStream imageOutStream = null;
 
                ContentValues cv = new ContentValues();
                 
                // name of the file
                cv.put(MediaStore.Images.Media.DISPLAY_NAME, "drawing.png");
                 
                // type of the file
                cv.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
                 
                // location of the file to be saved
                cv.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
 
                // get the Uri of the file which is to be created in the storage
                Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv);
                try {
                    // open the output stream with the above uri
                    imageOutStream = getContentResolver().openOutputStream(uri);
                     
                    // this method writes the files in storage
                    bmp.compress(Bitmap.CompressFormat.PNG, 100, imageOutStream);
                     
                    // close the output stream after use
                    imageOutStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        // the color button will allow the user
        // to select the color of his brush
        color.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final ColorPicker colorPicker = new ColorPicker(MainActivity.this);
                colorPicker.setOnFastChooseColorListener(new ColorPicker.OnFastChooseColorListener() {
                    @Override
                    public void setOnFastChooseColorListener(int position, int color) {
                        // get the integer value of color
                        // selected from the dialog box and
                        // set it as the stroke color
                        paint.setColor(color);
                    }
                    @Override
                    public void onCancel() {
                        colorPicker.dismissDialog();
                    }
                })
                        // set the number of color columns
                        // you want  to show in dialog.
                        .setColumns(5)
                        // set a default color selected
                        // in the dialog
                        .setDefaultColorButton(Color.parseColor("#000000"))
                        .show();
            }
        });
        // the button will toggle the visibility of the RangeBar/RangeSlider
        stroke.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (rangeSlider.getVisibility() == View.VISIBLE)
                    rangeSlider.setVisibility(View.GONE);
                else
                    rangeSlider.setVisibility(View.VISIBLE);
            }
        });
 
        // set the range of the RangeSlider
        rangeSlider.setValueFrom(0.0f);
        rangeSlider.setValueTo(100.0f);
         
        // adding a OnChangeListener which will
        // change the stroke width
        // as soon as the user slides the slider
        rangeSlider.addOnChangeListener(new RangeSlider.OnChangeListener() {
            @Override
            public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) {
                paint.setStrokeWidth((int) value);
            }
        });
 
        // pass the height and width of the custom view
        // to the init method of the DrawView object
        ViewTreeObserver vto = paint.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                paint.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int width = paint.getMeasuredWidth();
                int height = paint.getMeasuredHeight();
                paint.init(height, width);
            }
        });
    }
}


Note: 

For drawable resource files, you may find them in the following GitHub link.

Github Project Link: https://github.com/raghavtilak/Paint

Output:

Future Scope

There are plenty of things you can add to this project like:- 

  1. Adding a mask to the painted object, i.e. creating a blur or emboss effect on the stroke.
  2. Adding animations to the app.
  3. Adding a color selector for canvas, i.e. changing the color of canvas from the default White color as per the user requirement.
  4. Adding a sharing button, to directly share the drawing on various apps.
  5. Adding an eraser functionality that clears the specific path/stroke on which the eraser is dragged.
  6. Adding a shape picker, by which a user can directly select any particular shape from the list and can drag on the screen to create that shape.
  7. Enhancing UI, by adding a BottomSheet, vectors, etc.

“Anyone can put paint on a canvas, but only a true master can bring the painting to life.”, we finish building our app, now draw some awesome paintings on this canvas and become a “true master”. 

Note:

  1. Do not directly call the getMeasuredWidth(), getMeasuredHeight() method as these might return value 0. Because a View has its own lifecycle, Attached->Measured->Layout->Draw, so by the time you call these methods the view might have not been initialised completely. Hence, it is recommended to use the ViewTreeObserver which fires at the very moment the View has been Laid out or Drawn on screen.
  2. In case you have enabled the dark mode on your testing device, you might see a small glitch effect of switching of custom view from dark to light mode. Since the app is not optimised for DarkMode.

 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads