1

I'm creating android application for shapes on canvas onClick of button.

What I want:

I have three buttons 'Square','Circle','Triangle'. Each time I tap on button and object of that shape will be created & displayed in a random position on one canvas.(done)

On tap of each shape will cause it to become another shape. tapping on a square will make it a circle tapping on a circle will make it a triangle tapping on a triangle will make it a square (done)

On long tap on a shape can delete the shape.(done)

How to implement undo functionality!

Tried and Error: To achieve this functionality I tried so far following code in which I can create one shape on click of button using bitmap in java and image view in xml.

Update_29/10: I work on same code and create multiple shapes on canvas using dynamic relative layout adding views.

My Question:

Is this right way(bitmap and imageview) to create shape on canvas by clicking on button?

I can create multiple shapes on canvas now but every time I'm creating new canvas instance!any other way to achieve this?

Update_29_10:

How to get click of shape so I can delete shape and undo functionality as well.

XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context=".MainActivity">

<RelativeLayout
    android:id="@+id/relative4"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:layout_above="@+id/btnCircle"
    android:background="@color/colorAccent">

</RelativeLayout>
<Button
    android:id="@+id/btnSquare"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:text="@string/square" />

<Button
    android:id="@+id/btnCircle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@+id/btnSquare"
    android:text="@string/circle" />


<Button
android:id="@+id/btnTriangle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
    android:layout_toRightOf="@+id/btnCircle"
    android:text="@string/triangle" />

<Button
    android:id="@+id/btnUndo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@+id/btnTriangle"
    android:layout_alignParentBottom="true"
    android:text="@string/undo" />

MainActivity.java:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Context mContext;
    private Resources mResources;
    private RelativeLayout mRelativeLayout;
    private Button btnSquare, btnCircle, btnTriangle,btnUndo,btnState;
    private int mSuareCount=0,mCircleCount=0,mTriangelCount=0;


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

    private void initViews() {
        mContext = getApplicationContext();
        mResources = getResources();
        mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
        btnSquare = (Button) findViewById(R.id.btnSquare);
        btnCircle = (Button)findViewById(R.id.btnCircle);
        btnTriangle = (Button)findViewById(R.id.btnTriangle);
        btnUndo=(Button)findViewById(R.id.btnUndo);
        setOnClickListeners();
    }

    private void setOnClickListeners() {
        btnSquare.setOnClickListener(this);
        btnCircle.setOnClickListener(this);
        btnTriangle.setOnClickListener(this);
        btnUndo.setOnClickListener(this);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnSquare:
                drawSquare(null);
                mSuareCount++;
                break;

            case R.id.btnCircle:
                drawCircle(null);
                mCircleCount++;
                break;

            case R.id.btnTriangle:
                drawTriangle(null);
                mTriangelCount++;
                break;

            case R.id.btnUndo:

                break;

        }
    }

    private void drawSquare(ImageView imageView) {
        Bitmap bitmap = Bitmap.createBitmap(
                50, // Width
                50, // Height
                Bitmap.Config.ARGB_8888 // Config
        );

        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(Color.LTGRAY);
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.YELLOW);
        paint.setAntiAlias(true);

        int padding = 50;
        Rect rectangle = new Rect(
                padding, // Left
                padding, // Top
                canvas.getWidth() - padding, // Right
                canvas.getHeight() - padding // Bottom
        );
        canvas.drawRect(rectangle, paint);
        addViews(bitmap,imageView,1);

        // Display the newly created bitmap on app interface
        if (imageView == null) {
            imageView = new ImageView(this);
        }
        imageView.setImageBitmap(bitmap);

        final ImageView finalImageView = imageView;
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawCircle(finalImageView);
                mSuareCount--;
                mCircleCount++;
            }
        });
    }

    private void drawCircle(ImageView imageView) {
        Bitmap bitmap = Bitmap.createBitmap(
                50, // Width
                50, // Height
                Bitmap.Config.ARGB_8888 // Config
        );

        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);

        int radius = Math.min(canvas.getWidth(), canvas.getHeight() / 2);
        int padding = 5;
        canvas.drawCircle(
                canvas.getWidth() / 2, // cx
                canvas.getHeight() / 2, // cy
                radius - padding, // Radius
                paint // Paint
        );

        addViews(bitmap,imageView,2);

        // Display the newly created bitmap on app interface
        if (imageView == null) {
            imageView = new ImageView(this);
        }
        imageView.setImageBitmap(bitmap);

        final ImageView finalImageView = imageView;
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawTriangle(finalImageView);
                mCircleCount--;
                mTriangelCount++;
            }
        });

    }

    private void drawTriangle(ImageView imageView) {
        Bitmap bitmap = Bitmap.createBitmap(
                500, // Width
                500, // Height
                Bitmap.Config.ARGB_8888 // Config
        );

        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(),
                bitmap.getHeight());


        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.YELLOW);
        paint.setAntiAlias(true);
        Point point1_draw = new Point(90, 0);
        Point point2_draw = new Point(0, 180);
        Point point3_draw = new Point(180, 180);

        Path path = new Path();
        path.moveTo(point1_draw.x, point1_draw.y);
        path.lineTo(point2_draw.x, point2_draw.y);
        path.lineTo(point3_draw.x, point3_draw.y);
        path.lineTo(point1_draw.x, point1_draw.y);
        path.close();
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(Color.parseColor("#3F51B5"));
        canvas.drawPath(path, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);
        //addViews(bitmap,imageView);

        addViews(bitmap,imageView,3);

        if (imageView == null) {
            imageView = new ImageView(this);
        }
        imageView.setImageBitmap(bitmap);
        final ImageView finalImageView = imageView;
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawSquare(finalImageView);
                mSuareCount++;
                mTriangelCount--;
            }
        });

    }

    private void addViews(Bitmap bitmap, ImageView imageView, final int value) {
        final int min = 20;
        final int max = 80;


        Drawable d = getResources().getDrawable(R.mipmap.ic_launcher_round);
        final int w = d.getIntrinsicWidth();
        final int random = new Random().nextInt((max - min) + 1) + min;

        RelativeLayout relative4 = (RelativeLayout) findViewById(R.id.relative4);
        int width = relative4.getMeasuredWidth();
        int height = relative4.getMeasuredHeight();
        if (imageView == null) {
            imageView = new ImageView(this);
        }
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(100, 100);
        params.setMargins(new Random().nextInt((width - 0) + 1), new Random().nextInt((height - 0) + 1), 10, 10);
        imageView.setLayoutParams(params);
        imageView.setImageBitmap(bitmap);


        if (imageView != null) {
            ViewGroup parent = (ViewGroup) imageView.getParent();
            if (parent != null) {
                parent.removeView(imageView);
            }
        }

        relative4.addView(imageView);

        final ImageView finalImageView = imageView;


        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                switch (value) {
                    case 1:
                        drawCircle(finalImageView);
                        mSuareCount--;
                        mCircleCount++;
                        break;

                    case 2:
                        drawTriangle(finalImageView);
                        mCircleCount--;
                        mTriangelCount++;
                        break;

                    case 3:
                        drawSquare(finalImageView);
                        mTriangelCount--;
                        mSuareCount++;
                        break;

                }

            }
        });

        imageView.setOnLongClickListener(new View.OnLongClickListener(){


            @Override
            public boolean onLongClick(View v) {
                  switch (value) {
                case 1:
                    relative4.removeView(finalImageView);
                    mSquareCount--;
                    break;

                case 2:
                    relative4.removeView(finalImageView);
                    mCircleCount--;
                    break;

                case 3:
                    relative4.removeView(finalImageView);
                    mTriangleCount--;
                    break;
            }
            return true;
        });
    }

}

enter image description here

Rucha Bhatt Joshi
  • 822
  • 1
  • 15
  • 38
  • Creating bitmap cause an Oop memory allocation issue. – Piyush Oct 26 '18 at 11:10
  • @Piyush then how to achieve this fuctionality? – Rucha Bhatt Joshi Oct 26 '18 at 11:20
  • 1
    Why do you need to generate bitmap? Are you getting it from resource drawable or another folder? – Piyush Oct 26 '18 at 12:04
  • @Piyush how to draw shape on canvas without bitmap!? – Rucha Bhatt Joshi Oct 26 '18 at 12:17
  • You can go through [this](https://developer.android.com/reference/android/graphics/drawable/ShapeDrawable) too. – Piyush Oct 26 '18 at 12:20
  • Also in your code if you are not loading resource from anywhere then it will not cause any issue – Piyush Oct 26 '18 at 12:25
  • by using shapeDrawable I can create shapes only but how to create it multiple shapes and how to click on them!? even in this I have to set it on image view! so whats the point? – Rucha Bhatt Joshi Oct 26 '18 at 12:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/182586/discussion-between-piyush-and-rucha-bhatt-joshi). – Piyush Oct 26 '18 at 12:41
  • Hi your issue being solved? Sorry i was out of town till monday so could not connect with you. – Piyush Oct 31 '18 at 13:25
  • @Piyush no problem I updated my question I completed most of it now undo and delete is left. – Rucha Bhatt Joshi Oct 31 '18 at 17:20
  • For undo and delete you need to add your shape in to one array list while creating shape and while doing undo you need to remove shape from that list periodically. – Piyush Nov 01 '18 at 05:37
  • yeah I tried that but shape is not deleting by sequence but random shape deleted I used array list still. – Rucha Bhatt Joshi Nov 01 '18 at 09:11
  • I downvoted this because the question is a moving target. The title is about changing shapes onClick(), and you have about four different questions in the body of your question (none of which have to do with changing shapes) that have changed over time. – Bob Liberatore Nov 05 '18 at 17:41

2 Answers2

2

You may implement the functionality using with the following approach (psudo code):

Step 1:

interface ShapeInfo {
void drawShape(Canvas canvas);
}

Step 2: Implement the 3 different classes (Rect, Triangle, Circle) with this interface

Step 3: Set a background to RelativeLayout

Step 4: Add a CustomView in RelativeLayout make this custom view transparent.

    public class CustomView extends ImageView{

    ArrayList<ShapeInfo> alShapesInfo = new ArrayList();
            public CustomView(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
            }

            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                for ( ShapeInfo s:alShapesInfo)
                  s.drawShape(canvas);

            }
public void addShapeInfo(ShapeInfo s){alShapeinfo.add(s); invalidate();}

public void undo(){alShapeInfo.delete(alShapeInfo.size()-1); invalidate();}
        }

Spte 5: In main activity on button click of add triangle, circle, rect call customview.addshapeinfo

Step 6: to undo call

customview.undo
aanshu
  • 1,602
  • 12
  • 13
1

While aanshu's solution is probably the more efficient way to do this, his solution makes it difficult to recognize clicks on the single shapes.

Instead of making one big subclass of ImageView and overriding onDraw and drawing each of the sub-shapes, you could make a subclass of ImageView for each class of square, circle and triangle. Something like this:

public class SquareView extends ImageView {
    @Override
    protected void onDraw(Canvas canvas) {
        //draw your Square on the canvas given
    }
}

Same for Circle and triangle. Then just generate one of these views as you were generating image views before but do not set the Bitmap. You can add them to the layout and it will call the onDraw function and give it a canvas. Now you can register your onClickListener for each of these views as before. When the view is clicked you replace it with an instance of a different class, for example you replace SquareView with a CircleView.

For the undo operations, you could just make this. This is very pseudocode like, I also might be mixing some programming languages but the idea is everywhere the same. If something is unclear, please ask.

Stack<Runnable> undoStack;
//and now whenever you do something that should be undoable you just add a runnable that will undo it:
//for example if a user clicked a SquareView:
removeView(squareView);
CircleView circleView = new CircleView();
//take the information from the squareView that is needed
circleview.position = squareView.position;
addView(circleView);
undoStack.push(() -> removeView(circleView); addView(squareView););
//When you undo you just call
undoStack.pop().run();

A bit of background about your code, because I think it will help you understand. This is the source of android ImageView. When you call setImageBitmap(bitmap), it passes the bitmap to an instance of BitmapDrawable which it calls mDrawable. Then in the ImageViews onDraw(Canvas canvas) method it calls mDrawable.draw(canvas) which in case of BitmapDrawable (source here) after a lot of other stuff calls canvas.drawBitmap(bitmap). So basically what your code does: it creates a Bitmap from a canvas and then via ImageView and BitmapDrawable the bitmap is drawn back on a canvas. My and aanshu's solution draw directly on this final canvas. That is why they are better than your current solution.

Edit: While searching something else, I stumbled upon drawable shape resources. As with any drawable resource you can just pass them to ImageViews. This way you probably wouldn't have to override the onDraw function. But I never worked with them, so I'll just leave this here.

findusl
  • 2,454
  • 8
  • 32
  • 51
  • for daw square,circle and triangle same function I should use? – Rucha Bhatt Joshi Nov 07 '18 at 10:27
  • You make three classes SquareView, CircleView and TriangleView. In each of these classes you implement the drawing in the onDraw function. – findusl Nov 07 '18 at 10:28
  • which function I have to use to create that shape same function which I used before? I'll try your solution – Rucha Bhatt Joshi Nov 07 '18 at 10:38
  • Yes you draw the shapes the same way as you did before on the canvas that is passed to onDraw. Make sure the view has the right size then the canvas should have the size as well. – findusl Nov 07 '18 at 10:41
  • ok, I'm trying your code and thank you for help and I hope you'll help me if I'll stuck somewhere. :) – Rucha Bhatt Joshi Nov 07 '18 at 10:55
  • I don't want you to rewrite your code again, but while searching something else I stumbled upon this: https://developer.android.com/guide/topics/resources/drawable-resource#Shape This way you wouldn't need to make your own subclasses. You can just make Imageviews that reference a drawable shape resource. – findusl Nov 07 '18 at 15:36
  • 1
    A quick comment: you probably want to extend View instead of ImageView. Not only do you lose all of ImageView's functionality when you override onDraw(), but you're going to do something very different from an ImageView (drawing vectors vs. drawing bitmaps). – Bob Liberatore Nov 07 '18 at 19:50
  • @Project you are probably right, but I haven't done this recently and I wasn't sure enough whether View cares about everything else or some other functionality was implemented in ImageView that might come in handy to imitate the behavior that worked so far. If you are sure enough you can suggest an edit. – findusl Nov 08 '18 at 09:38