Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we draw a circle that uses Path object? [in arguments, like drawPath()]

I have typed a program which draws on Canvas.

It provides a popup menu which gives 3 Drawing tools as options:

  1. Draw line while scratching over screen

  2. Draw line based on start and end points on screen

  3. Draw a circle

Further, there are options such as:

  1. Clear

  2. Undo

While performing undo on lines, there's no issue at all because both are based on path. (Uses List<Path>). But here starts the problem. The circle is drawn using Point object. So the issues are:

  1. I cannot make Android distinguish - the order of drawing lines and circles. Eg: I draw 5 lines, followed by 5 circles (or alternatively). There's no intelligence currently to follow their order of drawing. Hence undoing canvas with lines and circles drawn together leads to mess up.
  2. The current code (not meditated upon in depth yet) needs 2 clicks to undo a circle instead of 1.

Code shared below (is complex). I tried to dedicate a class to each drawing tool (line,circle) - it worked - except - it didn't draw anything on the Canvas. So, all packed in 1 class back again.

Code:

package com.example.orbit_.undofortouch;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.Toast;


import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    Button b1, b2, b3;
    PopupMenu popup;
    int dtool;
    boolean touch,circle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout linearLayout = (LinearLayout) findViewById(R.id.linearLayout2);
        final DrawPanel dp = new DrawPanel(this);
        linearLayout.addView(dp);

        b1 = (Button) findViewById(R.id.button1);
        b1.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dp.Clear();
            }
        });

        b2 = (Button) findViewById(R.id.button2);
        b2.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dp.Undo();
            }
        });

        b3 = (Button) findViewById(R.id.button3);
        b3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                popup = new PopupMenu(MainActivity.this, v);
                popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());

                popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {

                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.touch:
                                dtool = 1;
                                break;
                            case R.id.line:
                                dtool = 2;
                                break;
                            case R.id.circle:
                                dtool = 3;
                                break;
                        }

                        Log.v("EDITL:", "Drawtool:".concat(String.valueOf(item.getTitle())));

                        Toast.makeText(MainActivity.this,"Clicked popup menu item " + item.getTitle(),Toast.LENGTH_SHORT).show();
                        return true;
                    }

                });
                popup.show();
            }
        });

   }




class DrawPanel extends View implements View.OnTouchListener {

    Bitmap bmp;

    Canvas canvas;
    List<Path> paths, undone;
    List<Point> circlePoints,removeCircles;
    Paint paint;
    Path path;
    Point point;

    public DrawPanel(Context context, AttributeSet attributeSet, int defStyle) {
        super(context, attributeSet, defStyle);
    }

    public DrawPanel(Context context) {
        super(context);
        paint = new Paint();
        path = new Path();
        paths = new ArrayList<>();
        undone = new ArrayList<>();
        circlePoints = new ArrayList<>();
        removeCircles = new ArrayList<>();

        canvas = new Canvas();

        this.setOnTouchListener(this);

        paint.setStrokeWidth(3);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setDither(true);
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeJoin(Paint.Join.ROUND);



        bmp = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.desert);
        touch=false;
        circle=false;

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {

        for (Path p : paths)
            canvas.drawPath(p, paint);

        if (touch)
            canvas.drawPath(path, paint);
        touch = false;
        Log.v("Inside onDraw","Circle is".concat(String.valueOf(circle)));

            for (Point p : circlePoints)
                canvas.drawCircle(p.x, p.y, 5, paint);


    }


    float mX, mY,mx,my;
    final float TOUCH_TOLERANCE = 0;



    private void touch_start(float x, float y) {
        undone.clear();
        Log.v("ONTOUCH:", "Inside DOWN".concat("DOWN-X---:").concat(String.valueOf(x)).concat("**DOWN-Y---:").concat(String.valueOf(y)));
        path.reset();
        path.moveTo(x, y);
        mX = x;
        mY = y;
        mx = x;
        my = y;
    }

    private void touch_up() {
        paths.add(path);
        path = new Path();
    }

    private void touch_move(float x, float y) {
        path.moveTo(mX, mY);
        Log.v("ONTOUCH:", "Inside MOVE".concat("mX:").concat(String.valueOf(mX)).concat("mY:").concat(String.valueOf(mY)));
        Log.v("ONTOUCH:", "Inside MOVE".concat("MOVE-X---:").concat(String.valueOf(x)).concat("**MOVE-Y---:").concat(String.valueOf(y)));
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }

        path.lineTo(mX, mY);

        Log.v("MOVE:", " PATH ADDED & New Created");


    }




    @Override
    public boolean onTouch(View v, MotionEvent event) {

        float x = event.getX();
        float y = event.getY();

        switch (dtool) {

            case 1:
                touch=true;
                Touch(v, event, x, y);
                break;

            case 2:
                Line(v,event,x,y);
                break;

            case 3:
                Circle(v,event,x,y);
                break;

        }
        Log.v("ONTOUCH:", "OUTSIDE CASE");
        return true;
    }

    public void Line(View v, MotionEvent event, float x, float y) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                invalidate();
                break;

            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                break;

            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                invalidate();
                break;
        }
    }

    public void Touch(View v, MotionEvent event, float x, float y) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                invalidate();
                canvas.drawPath(path, paint);
                break;

            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                canvas.drawPath(path, paint);
                break;

            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                invalidate();

                break;
        }
    }

    public void Circle(View v, MotionEvent event, float x, float y)
    {   point = new Point();
        point.x = (int)x;
        point.y = (int)y;
        path.moveTo(x,y);
        circle=true;
       if(event.getAction()==MotionEvent.ACTION_DOWN) {

           circlePoints.add(new Point(Math.round(point.x), Math.round(point.y)));
           invalidate();
           Log.v("Circle", "Inside Circle");
           circlePoints.add(point);
            paths.add(path);
           
       }
    }

    public void Clear() {
        paths.clear(); //Needs to be experimented
        path.reset();
        invalidate();
    }

    public void Undo() {
        if (paths.size() > 0) {
            undone.add(paths.remove(paths.size() - 1));
            invalidate();
        }
        else if(circlePoints.size()>0)
        {
            removeCircles.add(circlePoints.remove(circlePoints.size()-1));
            invalidate();
        }
    }
  }
}

XML Layout Code:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >


    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_weight="0"
        android:layout_gravity="top">


    </LinearLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Undo"
        android:id="@+id/button2"
        android:layout_gravity="right"
        android:layout_marginTop="-50dp" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Clear"
        android:layout_gravity="center_vertical|bottom"
        android:layout_marginTop="-50dp"
        android:enabled="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tools"
        android:id="@+id/button3"
        android:layout_gravity="center_horizontal"
        android:layout_weight="0"
        android:layout_marginTop="-50dp" />

</LinearLayout>

XML Main Menu code:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">

    <item
        android:id="@+id/touch"
        android:title="Touch"/>
    <item
        android:id="@+id/circle"
        android:title="Circle"/>
    <item
        android:id="@+id/line"
        android:title="Line"/>



</menu>
like image 226
Shridhar Avatar asked Dec 25 '22 14:12

Shridhar


1 Answers

Path.addCircle(float x,float y,float radius, Path.Direction)

This simple code did the thing. Because the point the circle is being added will be included in the Path object. And paths.addPath(path) simply adds it to the previous list of paths (of drawn lines).

Hence undo becomes easy and natural as well. Hence the solution.

Thanks @pskink for the original solution.

P.S: Today I realized, a break from an unfinished project is not a good practice but in a way it sometimes is to some people, for you are out of familiarity and can now think the normal way which you couldn't before.

like image 151
Shridhar Avatar answered Dec 28 '22 08:12

Shridhar