원형으로 생긴 프로그레스(그림과 다르게 진행정도에 따라 한 칸씩 채워지는 효과)를
커스텀 뷰로 구현할 일이 생겼다.
기존 라이브러리를 써도 어찌저찌 되겠지만
변형이 많이 생길수도 있는 부분이라서 커스텀으로 구현해놓는게 속편하겠다고 판단했다.
1. 원에서 막대의 각 위치를 구하기 위한 각도 계산 정의
원의 중심을 기점으로 반지름과 프로그레스 막대의 길이가 정의됐다고 했을 때
반지름 = r
막대의 길이 = h
라고 하겠다.
여기서 radian 의 개념이 필요한데,
degree 단위가 평소에 쓰는 60분법,
radian 단위는 반지름 대비 호의 길이인 호도법이다.
반지름 대비 호의 길이에 따른 각이라서 호도법이라 한다.
만약 반지름과 특정 구간의 호 길이가 같다면 1 radian 이 된다.
서로 변환하는 식은 아래와 같다.
degree = (180 / PI) * radian
-> 180도의 각을 원주율로 나누면 반지름이 나오게 되고,
반지름과 radian 을 곱하면 60분법으로 표기된 각이 나온다.
radian = (PI / 180) * degree
-> 원주율을 180으로 나누고 60분법각을 곱하면 반지름 대비 호의 길이인 radian 이 나온다.
이 개념이 필요한 이유는 radian 으로 각도들을 구해서
삼각함수의 사인/코사인에 적용하려면 필요하기 때문이다.
sin = 삼각형의 높이 / 삼각형의 빗변
cos = 삼각형의 밑변 / 삼각형의 빗변
2. 다음 각도에서의 좌표 계산
반지름이 r 인 원이 있다고 했을 때
가장 윗부분 점의 좌표를 (x, y) 라고 했을 떄
특정 각도 다음의 좌표를 (x', y') 라고 하자.
x' = x(원의 중심) + cos(rad(degree)) * r
y' = y(원의 중심) + sin(rad(degree)) * r
이런 식이 나온다.
아래 블로그 글이 매우 잘 정리돼있어서 좋은 참고가 됐다.
https://3001ssw.tistory.com/154
3. 실제 적용
2번에서는 매우 간단하게 정리됐지만
실제로 정리하려면 아래와 같은 문제가 발생한다.
1) 원을 그릴 공간이 변칙적으로 변해도 딱 떨어지는 만큼의 사각형을 그려야 한다.
2) 좌표 하나가 아니라 사각형의 네 점 좌표를 모두 구해야 한다.
- 해당 커스텀뷰를 그릴 공간의 크기가 width * height 라고 하자.
- 정사각형의 공간으로 만들어줄 예정이므로 원의 반지름 r = width / 2 라고 할 수 있다.
- 원의 중심 좌표 = (r, r) 이다.
- 막대의 길이 = h
- 막대의 두께 = w 라고 정의한다
1번부터 계산하자.
- 원의 둘레 = r * PI 다.
- 막대 사이의 공간 (내원 기준) = h 라고 하자(적당해보이는 간격)
그러면 막대만큼의 공간이 있는 막대 배열이 된다.
그리고 한 막대가 차지하는 공간이 2h 가 된다(막대길이 + 공간길이)
결론적으로
원의 둘레에 2h 가 딱 맞아떨어지면 규칙적인 배치가 될 것이다.
하지만 부동소수점 오차와 적용했을 때를 생각하면 조금의 오차를 허용하는 것이 맞다.
코드로 표현했을 때 배치돼야 하는 막대의 수 = ((int)원의 둘레 / h) / 2 가 된다.
1번은 해결됐다.
2번도 웬만큼 된 것 같지만 그래도 해보자.
우선 2번을 해결하려면 원은 한 개가 아니고 두 개가 돼야한다.
막대의 길이만큼 들어가있는 내원의 존재다.
내원의 반지름은 r - h 다.
원의 중심으로부터 r - h 를 반지름으로 가지고 (r - h) * PI 를 원의 둘레로 가지는 내원이 있는 것이다.
원래의 원을 편의상 외원 이라고 하고,
막대의 길이만큼 패인 원을 내원이라고 하면
내원과 외원의 막대좌표를 하나씩 알아야 한다.
실제 숫자로 대입해보자.
width = 1000
height = 1000
r = width / 2
원의 중심 좌표 = (r, r)
막대 길이 = 200
막대 두께 = 20
막대 사이 공간 = 20
내원 r = r - 200
원의 둘레 = r * PI
내원의 둘레 = 내원 r * PI
나누어 떨어지는 막대 갯수 = (내원의 둘레 / (막대두께 + 막대 사이 공간))
각도단위 = 360 / 막대 갯수
맨 위 막대부터 구현해보겠다
..라고 생각했는데
생각해보니 이상했다.
안드로이드의 canvas 에서 사각형을 그리는 방식은 모두 똑같다.
왼쪽변 x, 윗변 y, 오른쪽 변 x, 밑변 y 를 인자로 주면
(왼쪽변 x, 윗변 y), (오른쪽변 x, 윗변 y), (오른쪽변 x, 밑변 y), (왼쪽변 x, 밑변 y)
의 네 점으로 사각형이 그려진다.
여기서의 문제는 "틀어진 사각형을 그릴 수 없다"
네 점의 좌표를 직접 인자로 받지 않기 때문이다.
해결책은 canvas 를 degree 만큼 rotate 시켜서 그리는 것이다.
고정된 좌표에 사각형을 갯수만큼 그리면서 각도단위만큼 캔버스를 회전시키면
끝인 것이다.
고정좌표에 그리는 방법은
left = r
top = 0
right = r + w
bottom = h
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float r = (float)canvasWidth / 2f;
int barCount = (int)(((r - barHeight) * Math.PI) / (barWidth + betweenBar));
float degree = 360f / barCount;
Log.e(getClass().getSimpleName(),
"\nCanvas width : " + canvasWidth
+ "\nCanvas height : " + canvasHeight
+ "\nBar width : " + barWidth
+ "\nBar height : " + barHeight
+ "\nBar between : " + betweenBar
+ "\nDegree : " + degree
+ "\nR : " + r
+ "\nCount : " + barCount
);
for (int n = 0; n < barCount; n++) {
RectF rectF = new RectF();
rectF.set(r, 0, r + barWidth, barHeight);
canvas.drawRect(rectF, progressRectPaint);
canvas.rotate(degree, r, r);
}
}
막대 하나 그리고, 원의 중심을 기준으로 캔버스를 각도만큼 돌리고
다시 그리고의 반복이다.
'Android' 카테고리의 다른 글
[Android] 라이브러리 급의 커스텀뷰 만들기 프로젝트(RulerPicker) (0) | 2022.11.03 |
---|---|
[Android] 진동컨트롤(Vibrator) (0) | 2022.11.03 |
[Firebase] Firestore 보안 규칙 이해하기 (0) | 2022.09.26 |
[Android] Snackbar(스낵바) 띄우기 (0) | 2022.09.05 |
[Android] Read contacts(연락처 가져오기) (0) | 2022.08.30 |