Click here to Skip to main content
15,886,806 members
Articles / Mobile Apps / Android

Android Drawing App Tutorial – Pt. 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
4 Nov 2015CPOL6 min read 10.4K   6  
This blog post is part two of my Android Drawing App tutorials. It is strongly encouraged that you finish part one of this tutorial before proceeding with this one.

This blog post is part two of my Android Drawing App tutorials. It is strongly encouraged that you finish part one of this tutorial before proceeding with this one.  At the end of part one of this tutorial, we were able to draw something with our Android drawing app  and we added a number of Icons to the bottom toolbar. We will now proceed to implement the functionalities that each of those icons represent.

Implement Delete Drawing

The implementation for deleting or erasing everything in the screen is actually quite simple, all we need to do is to clear the screen like this:
/** Start new Drawing */
    public void eraseAll() {
        drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        invalidate();
    }
However before you call the above method, you may want to give the user the option to confirm the wipe the screen call. We accomplish this with an Alert dialog, go ahead and add this method to your MainActivity.
private void deleteDialog(){
        AlertDialog.Builder deleteDialog = new AlertDialog.Builder(this);
        deleteDialog.setTitle(getString(R.string.delete_drawing));
        deleteDialog.setMessage(getString(R.string.new_drawing_warning));
        deleteDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                mCustomView.eraseAll();
                dialog.dismiss();
            }
        });
        deleteDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        deleteDialog.show();
    }
You need to add an instance of the CustomView at the top of the MainActivity like this 
private CustomView mCustomView;
  and in the onCreate() method go ahead and instantiate this class like this 
mCustomView = (CustomView)findViewById(R.id.custom_view);

And with that you can now call the delete method whenever the user touches the icon you designate as the delete or erase icon. Update your handleDrawingIconTouched method like this:

private void handleDrawingIconTouched(int itemId) {
        switch (itemId){
              case R.id.action_delete:
                deleteDialog();
                break;
        }
    }

Implement Undo and Redo

Follow the steps below to implement redo and undo in your Android drawing app.

Step 1: Add Path List – near the top of your CustomView.java class, add two array list to hold the paths that is drawn on the screen and the paths that has been removed from the screen like this:

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

Step 2: Update Touch Event Handler – now that we need to record the history of the paths we draw, we need to handle our drawing differently. Instead of adding our updating our drawing in the onTouchEvent() method we can create separate private methods and then call them from the onTouchEvent() method like this:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float touchX = event.getX();
        float touchY = event.getY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                touch_start(touchX, touchY);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                touch_move(touchX, touchY);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                break;
            default:
                return false;
        }
        return true;
    }

Step 3: Create Touch Start Method – the touch start methods is called when the finger touches the screen which is considered a MotionEvent.Action_Down event and when this happens we want to clear the parts list and reset the Path object. Add the following method to your app:

private void touch_start(float x, float y) {
        undonePaths.clear();
        drawPath.reset();
        drawPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

Step 3: Create Touch Move Method – when the user moved from point A to point, we will need to evaluate the tolerance value that we set in the last step and then apply this method adapted from the  Android FingerPaint App.

private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            drawPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            mX = x;
            mY = y;
        }
    }

Step 4: Create Touch Up Method – when the user lifts up their finger from the screen, we want to paint what they have just drawn on the screen as well as save it to our path like like this:

private void touch_up() {
         drawPath.lineTo(mX, mY);
        drawCanvas.drawPath(drawPath, drawPaint);
        paths.add(drawPath);
        drawPath = new Path();

    }

Step 5: Update the onDrawMethod – we will need to update our onDraw() so that it draws from the path list.

protected void onDraw(Canvas canvas) {
        for (Path p : paths) {
            canvas.drawPath(p, drawPaint);
        }
        canvas.drawPath(drawPath, drawPaint);
    }

Step 6: Add Undo and Redo Methods  – now we need to add the methods that will trigger the undo and the redo.  Add following two methods to you custom view.

public void onClickUndo () {
       if (paths.size()>0)
        {
            undonePaths.add(paths.remove(paths.size()-1));
            invalidate();
        }

    }

    public void onClickRedo (){
       if (undonePaths.size()>0)
        {
            paths.add(undonePaths.remove(undonePaths.size()-1));
            invalidate();
        }

    }

Step 7: Call the Undo and Redo Methods – update your handleDrawingIconTouched() method in MainActivity to handle undo and redo button click.

private void handleDrawingIconTouched(int itemId) {
        switch (itemId){
            case R.id.action_delete:
                deleteDialog();
                break;
            case R.id.action_undo:
                mCustomView.onClickUndo();
                break;
            case R.id.action_redo:
                mCustomView.onClickRedo();
                break;
        }
    }

Implement Share Drawing

In this section of the tutorial we will implement the ability to share our drawing .  To share our drawing we will essentially take a screen shot of what we drew, save that as an image and then share it with share intent. Follow the steps below to share your drawing.

Step 1: Request Permission – we will save the drawing we wan to save as an image to the device external folder so we need write permission for the external folder. Add this line of code to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Step 2: Add Share Method – add the method that needs to be called to perform a share, we will leave this method blank for now and populate it shortly. Add the method private void shareDrawing() to your MainActivity.java file.

private void handleDrawingIconTouched(int itemId) {
    switch (itemId){
        case R.id.action_delete:
            deleteDialog();
            break;
        case R.id.action_undo:
            mCustomView.onClickUndo();
            break;
        case R.id.action_redo:
            mCustomView.onClickRedo();
            break;
        case R.id.action_share:
            shareImage();
            break;
    }
}

Step 3: Call Share Method – update the handleDrawingIconTouched() method to call the share method whenever the icon representing share is touched.

private void handleDrawingIconTouched(int itemId) {
        switch (itemId){
            case R.id.action_delete:
                deleteDialog();
                break;
            case R.id.action_undo:
                mCustomView.onClickUndo();
                break;
            case R.id.action_redo:
                mCustomView.onClickRedo();
                break;
            case R.id.action_share:
                shareImage();
                break;
        }
    }

Step 4: Populate Share Method – we can now populate the share method with the following code

private void shareDrawing() {
        mCustomView.setDrawingCacheEnabled(true);
        mCustomView.invalidate();
        String path = Environment.getExternalStorageDirectory().toString();
        OutputStream fOut = null;
        File file = new File(path,
                "android_drawing_app.png");
        file.getParentFile().mkdirs();

        try {
            file.createNewFile();
        } catch (Exception e) {
            Log.e(LOG_CAT, e.getCause() + e.getMessage());
        }

        try {
            fOut = new FileOutputStream(file);
        } catch (Exception e) {
            Log.e(LOG_CAT, e.getCause() + e.getMessage());
        }

        if (mCustomView.getDrawingCache() == null) {
            Log.e(LOG_CAT,"Unable to get drawing cache ");
        }

        mCustomView.getDrawingCache()
                .compress(Bitmap.CompressFormat.JPEG, 85, fOut);

        try {
            fOut.flush();
            fOut.close();
        } catch (IOException e) {
            Log.e(LOG_CAT, e.getCause() + e.getMessage());
        }

        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.putExtra(Intent.EXTRA_STREAM,Uri.fromFile(file));
        shareIntent.setType("image/png");
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(Intent.createChooser(shareIntent, "Share image"));


    }

Code Walk through 

Here is what is going on in the above code:

  1. First we enabled drawing cache on our custom view
  2. The we created a file with the android_drawing_app.png
  3. We then get the cache of our drawing and compress it into a JPEG
  4. Then we create a share intent to deliver our drawing.

Go ahead and run your app and make sure that you are able to share an image, if not use the comment box to let me know what you need help with.

Implement Change Brush Size

brush_size_picker_dialog_framed1

When we initiated our custom view, we set the brush size to 20. In this section we want to implement the code that will give the user the option to choose a different size. I have choose to use a Seekbar to implement this feature with values from 1 to 50. So the user will have a dialog like the one below to drag left of right to pick a brush size.

Follow the steps below to implement brush chooser to your Android drawing app.

Step 1: Add Set Brush Methods – in your CustomView.java class file add the following methods that will set and get the brush size

public void setBrushSize(float newSize) {
        float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                newSize, getResources().getDisplayMetrics());
        currentBrushSize = pixelAmount;
        canvasPaint.setStrokeWidth(newSize);
    }

    public void setLastBrushSize(float lastSize){
        lastBrushSize=lastSize;
    }

    public float getLastBrushSize(){
        return lastBrushSize;
    }

Step 2: Add Packages – the alert dialog that will show the choose new brush dialog will be defined in a separate file and when a new brush size is selected we will use a listener to tell the MainActivity that a new size has been selected. This listener will also be defined in a separate file. So for code organization let use go ahead and two packages: “dialogs” and “listeners”.

Step 3: Add Listener – in the listeners package add an Interface OnNewBrushSizeSelectedListener.java and here is the content of this Interface (take not that an Interface is not a class).

public interface OnNewBrushSizeSelectedListener {
    void onNewBrushSizeSelected(float newBrushSize);
}

Step 4: Add Dialog – In the dialogs package add a blank Fragment with the name BrushSizeChooserFragment.java and here is the content of the layout of this Fragment

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:gravity="center_horizontal"
    android:padding="@dimen/margin_padding_normal"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view_brush_size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"/>

    <TextView
        android:id="@+id/text_view_min_value"
        android:layout_below="@+id/text_view_brush_size"
        android:layout_alignParentLeft="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <SeekBar
        android:id="@+id/seek_bar_brush_size"
        android:layout_below="@+id/text_view_brush_size"
        android:layout_toRightOf="@+id/text_view_min_value"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:max="@integer/max_size"
        android:progress="2"/>

    <TextView
        android:id="@+id/text_view_max_value"
        android:layout_below="@+id/text_view_brush_size"
        android:layout_toRightOf="@+id/seek_bar_brush_size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

Step 5: Call the Dialog – before implementing the dialog fragment go ahead and add the code below to your MainActivity. This will be the method that calls the dialog, ignore the red squiggly lines for now.

private void brushSizePicker(){
        //Implement get/set brush size
        BrushSizeChooserFragment brushDialog = BrushSizeChooserFragment.NewInstance((int) mCustomView.getLastBrushSize());
        brushDialog.setOnNewBrushSizeSelectedListener(new OnNewBrushSizeSelectedListener() {
            @Override
            public void OnNewBrushSizeSelected(float newBrushSize) {
                mCustomView.setBrushSize(newBrushSize);
                mCustomView.setLastBrushSize(newBrushSize);
            }
        });
        brushDialog.show(getSupportFragmentManager(), "Dialog");
    }

And now you can update your handleDrawingIconTouched() method to call the method above when the icon representing brush size is selected. Here is the update method.

private void handleDrawingIconTouched(int itemId) {
        switch (itemId){
            case R.id.action_delete:
                deleteDialog();
                break;
            case R.id.action_undo:
                mCustomView.onClickUndo();
                break;
            case R.id.action_redo:
                mCustomView.onClickRedo();
                break;
            case R.id.action_share:
                shareDrawing();
                break;
            case R.id.action_brush:
                brushSizePicker();
                break;
        }
    }

Step 6: Implement Dialog Fragment – copy and paste the code below to your BrushSizeChooserFragment.java , you will need to add required text to your res/string folder.

public class BrushSizeChooserFragment extends DialogFragment {

    private float selectedBrushSize;
    private OnNewBrushSizeSelectedListener mListener;
    private SeekBar brushSizeSeekBar;
    private TextView minValue, maxValue, currentValue;
    private int currentBrushSize ;

    /**
     *
     * @param listener an implementation of the listener
     *
     */
    public void setOnNewBrushSizeSelectedListener(
            OnNewBrushSizeSelectedListener listener){
        mListener = listener;
    }

    public static BrushSizeChooserFragment NewInstance(int size){
        BrushSizeChooserFragment fragment = new BrushSizeChooserFragment();
        Bundle args = new Bundle();
        if (size > 0){
            args.putInt("current_brush_size", size);
            fragment.setArguments(args);
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();
        if (args != null && args.containsKey("current_brush_size")){
            int brushSize = args.getInt("current_brush_size", 0);
            if (brushSize > 0){
                currentBrushSize = brushSize;
            }
        }
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Begin building a new dialog.
        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        // Get the layout inflater.
        final LayoutInflater inflater = getActivity().getLayoutInflater();

        // Inflate the layout for this dialog.
        final View dialogView = inflater.inflate(R.layout.dialog_brush_size_chooser, null);
        if (dialogView != null) {
            //set the starting value of the seek bar for visual aide
            minValue = (TextView)dialogView.findViewById(R.id.text_view_min_value);
            int minSize = getResources().getInteger(R.integer.min_size);
            minValue.setText(minSize + "");

            maxValue = (TextView)dialogView.findViewById(R.id.text_view_max_value);
            maxValue.setText(String.valueOf(getResources().getInteger(R.integer.max_size)));


            currentValue = (TextView)dialogView.findViewById(R.id.text_view_brush_size);
            if (currentBrushSize > 0){
                currentValue.setText(getResources().getString(R.string.label_brush_size) + currentBrushSize);
            }

            brushSizeSeekBar = (SeekBar)dialogView.findViewById(R.id.seek_bar_brush_size);
            brushSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                int progressChanged = 0;

                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    progressChanged = progress;
                    currentValue.setText(getResources().getString(R.string.label_brush_size) + progress);

                }

                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {

                }

                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {
                    mListener.OnNewBrushSizeSelected(progressChanged);
                }
            });

        }

        builder.setTitle("Choose new Brush Size")
                .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                .setView(dialogView);


        return builder.create();

    }

 

In the code above we used newInstance method to pass in the currently selected brush size to the Fragment. This way we can set the Seekbar to the current size thereby giving the user a visual feedback on what size they currently have before they make new selection.

Run the app now and you should be able to change the size of the brush.

Conclusion

This concludes my two part tutorial series on creating Android drawing app. You should now have a functioning basic drawing app. If you want to expand this app further then you may want to take my course on the topic.

 

 

 

 

 

 

 

 

The post Android Drawing App Tutorial – Pt. 2 appeared first on Val Okafor.

This article was originally posted at http://valokafor.com/android-drawing-app-tutorial-pt-2

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ValOkafor.com
United States United States
My name is Val Okafor, I am a Senior Software Engineer with specialization in Android Development. Learning and problem solving is my passion and I share my Android development knowledge through my blog ValOkafor.com.

My Android courses are the courses I wish I had when I started. I teach Android development in the context of a fully developed Android app. I believe that new Android concepts are better understood if they are presented in the context of creating an app from scratch to finish.

I focus on creating Productivity Android apps and besides Android development I have 7 years’ experience as System Administrator supporting Enterprise applications and 2 years’ experience as a Web Developer building websites using PHP and ASP.Net.

I have worked for corporations such as The Home Depot, American Council on Exercise, Legend3D and HD Supply, Inc. I have a bachelor's degree in Information Technology from National University San Diego, California and a master's degree in Software Engineering from Regis University Denver, Colorado.

I enjoy sharing my extensive work experience through my blog, social media.

Comments and Discussions

 
-- There are no messages in this forum --