개발관련일지

안드로이드 캔버스에 도형(원) 생성하고 움직이기 본문

개발기록/안드로이드

안드로이드 캔버스에 도형(원) 생성하고 움직이기

BEECHANGBOT 2021. 5. 29. 04:57

화면에 원을 생성하고 드래그로 이동시키기 위해 만든 코드이다. 

 

아웃풋 

 

뷰에서 원을 생성하고 터치 위치를 감지해 원을 움직이도록 구현하였다.

 

버튼을 클릭 -> 원이 화면중앙에 생성 -> 원을 클릭 후 드래그 -> 드래그 포인트를 따라서 원이 이동

 

원을 그리기위해선 좌표를 알아야 했고 클릭 여부를 좌표로 파악했다

도형을 생성하기 위해서 Canvas , Paint의 대한 이해가 필요한데 아래의 블로그가 가장 큰 도움이 되었음

https://www.charlezz.com/?p=1433 

 

Android의 Canvas에 그려보자 : 선, 도형 그리고 그림까지!? | 찰스의 안드로이드

원문 : https://medium.com/over-engineering/getting-started-with-drawing-on-the-android-canvas-621cf512f4c7 안드로이드 Canvas 클래스에 빠지면 당신이 몰랐던 수퍼파워매직한 일들을 할 수 있습니다. 마음속에 있는 도형,

www.charlezz.com

 

원을 움직인 방법은 View의 onTouchEvent -> MotionEvent.ACTION_MOVE 상황에서 원 안을 클릭 후 드래그의 위치가 변하면 원의 좌표를 변경시켜주고 invalidate()로 원을 다시 그려주었다. 원을 드래그포인트에 따라서 계속 다시 그려지면서 원이 따라오는 거처럼 표현이 되다 보니 드래그를 빠르게 하면 원이 제대로 따라오지 못하는 문제가 생김

 

DrawingView - 해당 뷰위에 원을 생성하고 터치를 감지해 원을 움직이도록 하는 도화지 같은 뷰이다

ShapeClickInterface - 생성한 원을 클릭했을 경우 뷰를 사용하고 있는 엑티비티 or 프래그먼트에 전달해주는 리스너이다

CircleInfoData - 원을 구성하는데 필요한 정보이다.  원을 그리기 위해서 onDraw(뷰를 그리는 메서드)에서 canvas.drawcircle(x좌표 , y좌표 , 반지름 , Paint(객체)) 로 원을 생성하였고 여기에 필요한 데이터이다. 

 

아래부터 전체코드 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    package com.example.drawingcircle;
 
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
 
import androidx.annotation.Nullable;
 
import java.util.ArrayList;
 
public
class DrawingView extends View {
 
    //    생성된 원들을 담아놓는 리스트이다
//    원의 정보가 들어가있는 CircleInfoData(원정보) 를 담고있다
//    원이 생성될 시 drawnCircleList에 원들이 추가된다
//    원을 클릭 한지 확인하기 위해 onTouchEvent - ACTION_MOVE 에서 포문으로 각각의 원들을 불러온다
//    원을 그리기 위해서 onDraw에서 drawnCircleList에서 원의 대한 정보를 가져와 그리도록한다
    private ArrayList<CircleInfoData> drawnCircleList = new ArrayList<>();
 
    //    원에서 공용으로 사용하기 위한 페인트 , 색상이나 두께를 해당 프로젝트에 바꾸지않기에 한번 만들고 계속 사용하도록한다.
    private Paint paint;
 
 
 
//    유저가 화면을 클릭 했을 때 사용하는 엑티비티 혹은 프래그먼트에 전달하는 리스너
    private ShapeClickInterface clickListener;
 
    public void setClickListener(ShapeClickInterface listener) {
        this.clickListener = listener;
    }
 
 
    public DrawingView(Context context) {
        super(context);
        init();
    }
 
    public DrawingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public DrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
    public DrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }
 
    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
 
    }
 
 
    /*
     * ACTION_MOVE :
     *  유저가 원 안을 클릭 했을 경우를 확인한다
     *  원내부를 클릭하고 움직일 경우 해당원의 위치를 유저의 드래그를 따라가도록 이동시켜준다
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
 
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
 
                float x = event.getX();
                float y = event.getY();
                for (int i = 0; i < drawnCircleList.size(); i++) {
//                   시작표시 원을 선택하고 움직인 경우인지 확인
                    float circleXFloat = drawnCircleList.get(i).getCircleX();
                    float circleYFloat = drawnCircleList.get(i).getCircleY();
                    int circleRadius = drawnCircleList.get(i).getRadius();
                    double dx = Math.pow(x - circleXFloat, 2);
                    double dy = Math.pow(y - circleYFloat, 2);
                    if (dx + dy < Math.pow(circleRadius, 2)) {
//                    시작표시 원안에 터치됨
                        moveCircleShape(x, y, i);
                        clickListener.onCircleClick();
                        return true;
                    }
                }
 
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
 
 
//       터치이벤트가 겹쳐져있는 뷰가 없으므로 상위뷰들의 onTouchEvent를 호출하지않기 위해 true로함
        return true;
 
    }
 
 
    //    그리는 곳
    @Override
    protected void onDraw(Canvas canvas) {
//        원을 담고있는 리스트에서 하나씩 돌려서 원을 생성해주도록함
        for (CircleInfoData circle : drawnCircleList) {
            canvas.drawCircle(circle.getCircleX(),
                    circle.getCircleY(),
                    circle.getRadius(),
                    paint);
        }
 
        super.onDraw(canvas);
    }
 
    /**
     * 원을 드래그해서 움직이면 동작하는 함수이다
     * circleInfoData (원정보)를 변경시키고 다시 화면을 그리도록함
     *
     * @param x         = 최종 x좌표 - 원의 x값이 된다.
     * @param y         = 최종 y좌표 - 원의 y값이 된다.
     * @param listIndex = drawnCircleList의 들어있는 인덱스값
     */
    private void moveCircleShape(float x, float y, int listIndex) {
 
        CircleInfoData circleInfoData = drawnCircleList.get(listIndex);
 
        circleInfoData.setCircleX(Math.round(x));
        circleInfoData.setCircleY(Math.round(y));
 
        this.invalidate();
    }
 
 
    /**
     * 원을 추가해주는 코드
     * 처음 생성된 원은 화면의 정가운대에 추가되며 반지름 값은 100이다.
     * 원의 정보를 drawnCircleList에 담아주고 다시 화면을 로드해 원이 추가된걸 보여주도록한다
     */
    public void createCircle() {
        CircleInfoData circleInfoData = new CircleInfoData(
                getWidth() / 2,
                getHeight() / 2,
                100);
 
        drawnCircleList.add(circleInfoData);
        this.invalidate();
 
    }
 
 
}
cs

 

1
2
3
4
public interface ShapeClickInterface {
    void onCircleClick();
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public
class CircleInfoData {
 
    int circleX;
    int circleY;
    int radius ;
 
    /**
     *  원을 캔버스에 그리기 위해 필요한 데이터
     * @param circleX - 현재 원이 위치하고 있는 x좌표값
     * @param circleY - 현재 원이 위치하고 있는 y좌표값
     * @param radius - 현재 원의 반지름값 ( 원 크기를 결정 )
     *
     */
    public CircleInfoData(int circleX, int circleY, int radius) {
        this.circleX = circleX;
        this.circleY = circleY;
        this.radius = radius;
    }
 
    public int getCircleX() {
        return circleX;
    }
 
    public void setCircleX(int circleX) {
        this.circleX = circleX;
    }
 
    public int getCircleY() {
        return circleY;
    }
 
    public void setCircleY(int circleY) {
        this.circleY = circleY;
    }
 
    public int getRadius() {
        return radius;
    }
 
    public void setRadius(int radius) {
        this.radius = radius;
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends AppCompatActivity implements ShapeClickInterface {
 
    DrawingView drawingView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        ActionBar actionBar = getSupportActionBar();
        actionBar.hide();
 
        drawingView = findViewById(R.id.drawing_view);
        drawingView.setClickListener(this);
 
        Button createCircleBtn = findViewById(R.id.create_circle_btn);
        createCircleBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                drawingView.createCircle();
            }
        });
 
 
    }
 
 
    @Override
    public void onCircleClick() {
//        화면을 선택 했을 경우 리스너로 넘어오는 곳
//        TODO 드로잉 뷰 클릭했을 시 작업하는 곳 (반지름 크기변경 , 원삭제 등등)
 
    }
 
}
cs

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/create_circle_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#03A9F4"
        android:text="원생성"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.drawingcircle.DrawingView
        android:id="@+id/drawing_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constrainedHeight="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/create_circle_btn" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

해당 코드의 깃허브

https://github.com/BeeChang/DrawingCircle.git

 

BeeChang/DrawingCircle

Contribute to BeeChang/DrawingCircle development by creating an account on GitHub.

github.com