Skia
Android Canvas๋ฅผ ์ค๋ช
ํ๊ธฐ์ ์์ ๋จผ์ Skia์ ๋ํด ์์์ผ ํ๋ค. Skia๋ ๋ค์ํ ํ๊ฒฝ(ํ๋์จ์ด ๋ฐ ์ํํธ์จ์ด)์์ ์๋ํ๋ 2D ๊ทธ๋ํฝ ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ฉฐ, ์ฃผ๋ก ๊ตฌ๊ธ์์ ๊ด๋ฆฌํ๋ค. ์ , ์ , ๋ฉด์ ๋ฌผ๋ก ๊ทธ๋ฆด ์ ์๊ณ ์ด๋ฏธ์ง๋ ํ
์คํธ ๋ฑ๋ ๊ทธ๋ฆด ์ ์๋ค.
์ง์ ํ๋ซํผ
โข
Windows, macOS
โข
iOS, Android, Flutter
โข
Ubuntu, Debian, openSUSE, Fedora
โข
Chrome, Firefox
โข
๋ฑ๋ฑ
Android Canvas
์ขํ๊ณ
Canvas(Skia)์ ์ขํ๊ณ๋ ์ข์๋จ ๊ผญ์ง์ ์ (0,0)์ผ๋ก x๋ ์ค๋ฅธ์ชฝ ๋ฐฉํฅ, y๋ ์๋ ๋ฐฉํฅ์ผ๋ก ์ฆ๊ฐํ๋ ์ขํ๊ณ์ด๋ค.
๋ฐฉ๋ฒ
๊ธฐ์กด์ xml ๋ฐฉ์์ผ๋ก ๋ทฐ๋ฅผ ๊ทธ๋ ค ๋๊ฐ ๋๋ Canvas๋ฅผ ์ฌ์ฉํ๋๊ฒ ๊ต์ฅํ ๋ถํธํ๋๊ฑฐ์ ๋นํด Compose๋ฅผ ์ฌ์ฉํ๋ฉฐ Canvas๊ฐ ์์ฃผ ๊ฐํธํด์ก๋ค. Canvas๋ฅผ ๊ทธ๋ฆฌ๋ ค๋ฉด ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
1.
Canvas()
@Composable
fun MyCanvas() {
Canvas(
modifier = Modifier
.size(300.dp)
) {
// TODO
}
}
Kotlin
๋ณต์ฌ
2.
Modifier.draw
โข
Modifier.drawBehind: ๊ธฐ์กด ์ปดํฌ๋ํธ์ ๋ค, ์ปดํฌ๋ํธ์ ๋ฐฐ๊ฒฝ์ ๊ทธ๋ฆฐ๋ค
โข
Modifier.drawWithContent: ๊ธฐ์กด ์ปดํฌ๋ํธ์ ์, ์ปดํฌ๋ํธ ์์ ๊ทธ๋ฆฐ๋ค
@Composable
fun MyCanvas2() {
Column(
modifier = Modifier.drawBehind{
// TODO
}
)
}
Kotlin
๋ณต์ฌ
ํต์ฌ
DrawScope ์์์ Canvas๋ฅผ ๊ทธ๋ ค ๋๊ฐํ
๋ฐ, ์ฌ๊ธฐ์ ๋ ๊ฐ์ง Parameter๋ง ์ ์ฌ์ฉํ๋ฉด ๋๋ค. center์ size์ด๋ค.
โข
size: Canvas์ size์ด๋ค.
โข
center: Canvas์ x,y ์ค์ Offset๊ฐ์ด๋ค.
์ด ๋ ๊ฐ์ง๋ฅผ ์ด์ฉํด ์ํ๋ ๋ชจ๋ ๊ฒ์ ๊ทธ๋ฆด ์ ์๋ค. ์์ธํ ํ์ฉ ๋ฐฉ๋ฒ์ ๋ฐ์ ์์ ์์ ๊ณ์๋๋ค.
ํ
3.dp.toPx()
Kotlin
๋ณต์ฌ
์ฌ์ด์ฆ์ ๊ฒฝ์ฐ 100dp์ ๊ฐ์ด dp ๊ฐ์ ์ค ์๋ ์์ง๋ง, 100f ์ฒ๋ผ ํฝ์
(Float) ๊ฐ์ผ๋ก๋ ์ค ์ ์๋ค. dp๋ฅผ ํฝ์
๋ก ๋ณํํ๊ณ ์ถ๋ค๋ฉด ์์ ๊ฐ์ด dp.toPx()๋ฅผ ์ฌ์ฉํ๊ธธ ๋ฐ๋๋ค.
์์
์์ ๊ท์ฝ๊ณ ๊น์ฐํ ์ผ๊ตด์ ๊ทธ๋ ค๋ณด์.
Canvas()
@Composable
fun MyFace() {
Canvas(modifier = Modifier.size(300.dp))
}
Kotlin
๋ณต์ฌ
์์ ์ค๋ช
ํ๋ฏ์ด Jetpack Compose์์ Canvas๋ฅผ ์ฌ์ฉํ๊ธฐ๋ ๊ต์ฅํ ์ฝ๋ค.
์ฌ๊ธฐ์ ์ ์ํด์ผํ ์ ์ ํฌ๊ธฐ๋ฅผ ๋ฐ๋์ ๋ช
์ํด์ค์ผ ํ๋ค. Modifier.size()๋ฅผ ํตํด ๋ช
์ํด์ฃผ๊ฑฐ๋, Modifier.fillMaxSize()๋ฅผ ํด์ฃผ๋ฉด ๋๋ค. (Modifier.fillMaxWidth()๋, Modifier.fillMaxHeight() ์ค์ ํ๋๋ง ์จ์๋ ์๋๋ค. ๊ฐ๋ก์ ์ธ๋ก์ ๋ํด ํ์คํ๊ฒ ๋ช
์ ํ์)
์ค๋น๋ฌผ
์ผ๊ตด์ ๊ทธ๋ฆฌ๊ธฐ ์์, ์ค๋น๋ฌผ์ ์ฑ๊ฒจ๋ณด์
โข
drawCircle(): ๋๊ทธ๋ผ๋ฏธ
โข
drawOval(): ํ์
โข
drawRect(): ์ฌ๊ฐํ
โข
drawArc(): ์์น, ํธ, ์ํธ ๋ฑ๋ฑ
ํ๋ ๋, ์ ์ค๋น๋ฌผ์ topLeft ํน์ center ๊ฐ์ ์ง์ ํด ์ค์ผ ํ๋๋ฐ ๋ ๋ค ๊ฒฐ๊ตญ Offset ๊ฐ์ ์ฃผ๋ฉด ๋๋ค.
size์ radius๋ ๊ฒฐ๊ตญ ํฌ๊ธฐ(Float, Size) ๊ฐ์ด๋ค.
Canvas ์ฌ์ด์ฆ ๋ฐ ๋ฐฐ๊ฒฝ์ ์ง์
@Composable
fun MyFace() {
Canvas(
modifier = Modifier
.background(color = Color.LightGray)
.fillMaxSize()
)
}
Kotlin
๋ณต์ฌ
ํ์์ผ๋ก ์ง์ ํด์ค ํ ์คํํด๋ณด๋ฉด ์๋์ ๊ฐ์ ์น์นํ ํ๋ฉด์ ๋ณผ ์ ์๋ค.
ํผ๋ถ์(?), ๋ฐฐ๊ฒฝ ์ ํ๊ธฐ
@Composable
fun MyFace() {
Canvas(
modifier = Modifier
.background(color = Color.Yellow)
.size(300.dp)
) {
drawRect(color = Color.Yellow, topLeft = Offset(0f, 0f), size = size)
}
}
Kotlin
๋ณต์ฌ
์ด์ง ์ต์ง์ค๋ฝ์ง๋ง size๋ฅผ ์ค๋ช
ํ๊ธฐ ์ํด Canvas ์์ drawRect()๋ฅผ ์ด์ฉํด Canvas ํฌ๊ธฐ์ ๋์ผํ ํฌ๊ธฐ์ ๋
ธ๋ ๋ฐฐ๊ฒฝ์ ์ฌ๋ ค๋ณธ๋ค.
Canvas์ ์ขํ๊ณ์ ๋ฐ๋ผ ์ข์๋จ(topLeft = Offset(0f,0f))์์ ๋ถํฐ ์์ํ๊ณ , size๋ (drawScope.)size๋ฅผ ์
๋ ฅํด์ฃผ๋ฉด Canvas์ ํฌ๊ธฐ์ ๊ฐ์ ๊ฐ์ผ๋ก ์ค์ ํ ์ ์๋ค. ์ ์ฉ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค.
์ผ๊ตด ์ค๊ณฝ ๋๋ก์
@Composable
fun MyFace() {
Canvas(
modifier = Modifier
.background(color = Color.Yellow)
.size(300.dp)
) {
drawCircle(
color = Color.Black,
center = center,
style = Stroke(
width = 3.dp.toPx(),
),
radius = 210f
)
// TODO
}
}
Kotlin
๋ณต์ฌ
drawCircle()์ ์ฌ์ฉํด ์ผ๊ตด ์ค๊ณฝ์ ์ก์๋ณด์. (์์ผ๋ก ๊ณ์ ์ถ๊ฐ๋๋ ์ฝ๋๋ ์์ ๋ณด์ด๋ ์ฝ๋์ โ// TODOโ ๋ถํฐ ์๋๋ก ์ด์ด ์์ฑํ๋ฉด ๋๋ค)
center ๊ฐ์ Offset(0f, 0f)๋ฅผ ์ฃผ๊ฒ ๋๋ฉด ์ข์ธก ์๋จ์ ๋๊ทธ๋ผ๋ฏธ๋ฅผ ๊ทธ๋ฆฌ๋๊ฑธ ๋ณผ ์ ์๋ค.
center ๊ฐ์ center๋ฅผ ์ฃผ๊ฒ ๋๋ฉด Canvas์ ์ ์ค์์ ์์นํ ๊ฒ์ ๋ณผ ์ ์๋ค.
style ๊ฐ์ ๋ฐ๋ก ๋ช
์ํ์ง ์์ผ๋ฉด Fill์ด ๊ธฐ๋ณธ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ์์ ๊ฐ์ ๋๊ทธ๋ผ๋ฏธ๊ฐ ๊ทธ๋ ค์ง๋ค.
์์ ์ค๋ช
ํ center ๊ฐ์ center(Offset)์ ์ง์ ํด์ฃผ๋ฉด ์ ์ค์์ ์์นํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
style์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ์ด Fill๋ก ๋์ด ์๊ธฐ ๋๋ฌธ์, ๋ฐ๋ก ๋ช
์ํ์ง ์์ผ๋ฉด ๊ฒ์์์ผ๋ก ๊ฐ๋ ์ฑ์์ง ๋๊ทธ๋ผ๋ฏธ๋ฅผ ๋ณผ ์ ์๋ค. ์ด๋ฒ ์์ ์์๋ Stroke๋ก ์ผ๊ตด ์ค๊ณฝ๋ง ์ก์์ค๋ค.
์ฝ ๋๋ก์
์ด์ ์ฝ๋ฅผ ๊ทธ๋ ค์คํ
๋ฐ, ์ฝ๋ฅผ ๊ธฐ์ค์ผ๋ก ๋, ๋์น, ์
, ๋จธ๋ฆฌ์นด๋ฝ์ ์์น๋ฅผ ์ ํด์ค ๊ฒ์ด๋ค.
@Composable
fun MyFace() {
Canvas(
modifier = Modifier
.background(color = Color.LightGray)
.fillMaxSize()
) {
val noseOffset = Offset(center.x - 25f, center.y - 90f)
val leftEyeOffset = Offset(center.x / 2 + (center.x / 4), center.y - 60f)
val rightEyeOffset = Offset(center.x + (center.x / 4), center.y - 60f)
...
Kotlin
๋ณต์ฌ
noseOffset์ด๋ผ๋ ๊ฐ์ ๋ง๋ค์ด ์ฝ์ ์์น๋ฅผ center.x ๋ณด๋ค 25 ์ผ์ชฝ์, center.y ๋ณด๋ค 90 ์์ ๊ทธ๋ ค์ค ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ x๊ฐ์ -25 ํด ์ฃผ๋ ์ด์ ๋ ์ฝ์ ํฌ๊ธฐ(width)๊ฐ 50์ด๊ธฐ ๋๋ฌธ์ ํฌ๊ธฐ(width)์ ์ ๋ฐ ๋งํผ ์ผ์ชฝ์ผ๋ก ์ด๋์์ผ ๋ฑ ์ ์ค์์ ์์นํ๋๋ก ํ๊ธฐ ์ํจ์ด๋ค. (์ด๋ฒ ์์ ์์ ์ฝ์ ํฌ๊ธฐ ๋ฑ์ ๋ณ์๋ ์์๋ก ์ง์ ํ์ง ์๊ณ ์งํํ๋ค.)
noseOffset์์ x๊ฐ์ center.x๋ก๋ง ์ฃผ์์ ๋ ๊ทธ๋ ค์ง๋ ์ฝ์ ์์น
noseOffset์์ x๊ฐ์ center.x - 25๋ก ์ฃผ์์ ๋ ๊ทธ๋ ค์ง๋ ์ฝ์ ์์น
์ผ์ชฝ๋(leftEyeOffset)๊ณผ ์ค๋ฅธ์ชฝ๋(rightEyeOffset)๋ center๊ฐ์ ๊ธฐ์ค์ผ๋ก ๊ณ์ฐํ๋ค.
๋ ๋๋ก์
drawCircle(
brush = Brush.radialGradient(
colors = listOf(
Color.White,
Color.Black
),
center = leftEyeOffset,
radius = 25f
),
radius = 25f,
center = leftEyeOffset
)
drawCircle(
brush = Brush.radialGradient(
colors = listOf(
Color.White,
Color.Black
),
center = rightEyeOffset,
radius = 25f
),
radius = 25f,
center = rightEyeOffset
)
Kotlin
๋ณต์ฌ
drawCircle() ๊ฐ์ ๊ฒฝ์ฐ๋ ๋ณด๋ฉด Brush๋ฅผ ์ฌ์ฉํด๋, Color๋ฅผ ์ฌ์ฉํด๋ ์ข๋ค. Color์ Style๋ฅผ ๊ฐ์ง๊ณ ๋์ ๊ทธ๋ ค๋ ์ข์ง๋ง, ์ด๋ฒ ์์ ์์๋ ๊ทธ๋ผ๋ฐ์ด์
์ ๋ฃ๊ธฐ ์ํด Brush๋ก ๋์ ๊ทธ๋ฆฐ๋ค.
radius๋ ๋ ๊ฐ๊ฐ ๋ณด์ด๋๋ฐ, ์์์ ๋ถํฐ ๊ฐ๊ฐ ๊ทธ๋ผ๋ฐ์ด์
์ ํฌ๊ธฐ, ๋๊ทธ๋ผ๋ฏธ์ ํฌ๊ธฐ๋ฅผ ์ง์ ํ๋ค. ์ฆ ๋์ ํฌ๊ธฐ๋ 25f์ด๋ค.
์ง๊ธ๊น์ง์ ๊ฒฐ๊ณผ๋ฌผ์ ์๋์ ๊ฐ๋ค.
๋์น ๋๋ก์
drawRect(
color = Color.Black,
topLeft = Offset((rightEyeOffset.x - 37.5f), rightEyeOffset.y - 50f),
size = Size(80f, 10f)
)
drawRect(
color = Color.Black,
topLeft = Offset((leftEyeOffset.x - 37.5f), leftEyeOffset.y - 50f),
size = Size(80f, 10f)
)
Kotlin
๋ณต์ฌ
๊ฒฐ๊ตญ ์ค์ํ ๊ฒ์ topLeft์ ์ง์ ํ Offset์ด๋ค. center๊ฐ๊ณผ ๊ทธ๋ฆฌ๋ ค๋ ์ฌ๊ฐํ์ ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํด์ ์ ๋นํ ์์น๋ก ์ง์ ํด ์ฃผ์. ์ฌ๊ฐํ์ ํฌ๊ธฐ๋ ์ ๋นํ ๊ฐ์ผ๋ก ์ง์ ํด ์ก์นํ ๋บจ์น๋ ์ก์ถฉ์ด ๋์น์ ๊ทธ๋ ค์ฃผ์. ๊ฒฐ๊ณผ๋ฌผ์ ์๋์ ๊ฐ๋ค.
์ ๋๋ก์
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 180f,
useCenter = true,
topLeft = Offset(center.x - 100f, noseOffset.y + 60f),
size = Size(200f, 200f),
style = Stroke(
width = 3.dp.toPx()
)
)
Kotlin
๋ณต์ฌ
์
๊ฐ์ ๊ฒฝ์ฐ์๋ drawArc()๋ฅผ ์ด์ฉํด ๊ทธ๋ ค์คํ
๋ฐ, ๋ญ๊ฐ ๋ณต์กํ๋ค. topLeft, size, style์ ๋ญ์ง ์์ ๋ค๋ค๋ค.
๊ทธ๋ ๋ค๋ฉด startAngle, sweepAngle, useCenter๋ ๋ฌด์์ธ๊ฐ? startAngle๊ณผ sweepAngle ๋ถํฐ ์ง๊ณ ๋์ด๊ฐ ๋ณด์.
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 90f,
useCenter = true,
topLeft = Offset(center.x - 100f, noseOffset.y + 60f),
size = Size(200f, 200f),
/*style = Stroke(
width = 3.dp.toPx()
)*/
)
Kotlin
๋ณต์ฌ
style์ ๊ธฐ๋ณธ ๊ฐ์ธ Fill๋ก ์ฃผ๋ฉด ์กฐ๊ธ ๋ ์ดํด๊ฐ ์ฝ๋ค. startAngle๋ ์์์ ์ด 0f๋ผ๋ ๊ฒ์ด์ง๋ง, ์ฌ๊ธฐ์ 0f๋ 3์ ๋ฐฉํฅ์ด๋ค. 3์ ๋ฐฉํฅ๋ถํฐ 90๋์ ๊ฐ์ ์ค ๊ฒฐ๊ณผ ํ๋ฉด์ด ๋ฐ๋ก ์์ ๊ฐ๋ค.
3์ ๋ฐฉํฅ ๋ถํฐ, 180๋
3์ ๋ฐฉํฅ ๋ถํฐ, 280๋
3์ ๋ฐฉํฅ ๋ถํฐ, 360๋
์ฌ๊ธฐ์ ์คํ์ผ์ Stroke๋ก ์ฃผ๊ฒ ๋๋ฉด?
Stroke๋ก 3์ ๋ฐฉํฅ ๋ถํฐ, 180๋
Stroke๋ก 3์ ๋ฐฉํฅ ๋ถํฐ, 280๋
Stroke๋ก 3์ ๋ฐฉํฅ ๋ถํฐ, 360๋
์ฌ๊ธฐ์ useCenter ๊ฐ์ true โ false๋ก ๋ณ๊ฒฝํ๊ฒ ๋๋ฉด?
90๋
180๋
280๋
360๋
useCenter๊ฐ์ true๋ก ์ฃผ๋ฉด ๋๊ทธ๋ผ๋ฏธ์ ์ค์ฌ์ ์ ์ฐ๊ฒฐํด ๊ทธ๋ ค์ฃผ์ง๋ง, false๋ก ์ฃผ๋ฉด ์ค์ฌ์ ๊ณผ ์ฐ๊ฒฐํด ์ฃผ์ง ์๋๋ค.
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 360f,
useCenter = true,
topLeft = Offset(center.x - 100f, noseOffset.y + 60f),
size = Size(200f, 200f),
style = Stroke(
width = 3.dp.toPx()
)
)
Kotlin
๋ณต์ฌ
์์ ๋๋ก ์
์ ๋๋ก์ํด๋ณด์๋ฉด, ๊ฒฐ๊ณผ๋ฌผ์ ์๋์ ๊ฐ๋ค.
๋์น ๋๋ก์
drawLine(
color = Color.Black,
start = Offset(center.x, center.y - 210f),
end = Offset(center.x, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
drawLine(
color = Color.Black,
start = Offset(center.x - 30f, center.y - 210f),
end = Offset(center.x - 30f, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
drawLine(
color = Color.Black,
start = Offset(center.x + 30f, center.y - 210f),
end = Offset(center.x + 30f, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
Kotlin
๋ณต์ฌ
๋์น์ ๊ทธ๋ฆฌ๊ธฐ ์ํ drawLine()์ start์ end๋ผ๋ Offset ๊ฐ๋ง ์ ์ง์ ํด์ฃผ๋ฉด ๋๋ค. ์ด๊ฒ ์ญ์ center ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์์น๋ฅผ ์ก๋๋ค.
์ต์ข ์ฝ๋ ๋ฐ ๊ฒฐ๊ณผ๋ฌผ
@Composable
fun MyFace() {
Canvas(
modifier = Modifier
.background(color = Color.LightGray)
.fillMaxSize()
) {
val noseOffset = Offset(center.x - 25f, center.y - 90f)
val leftEyeOffset = Offset(center.x / 2 + (center.x / 4), center.y - 60f)
val rightEyeOffset = Offset(center.x + (center.x / 4), center.y - 60f)
drawRect(color = Color.Yellow, topLeft = Offset(0f, 0f), size = size)
drawCircle(
color = Color.Black,
center = center,
style = Stroke(
width = 3.dp.toPx(),
),
radius = 210f
)
drawOval(
color = Color.Black,
size = Size(50f, 120f),
topLeft = noseOffset,
style = Stroke(
width = 3.dp.toPx(),
)
)
drawCircle(
brush = Brush.radialGradient(
colors = listOf(
Color.White,
Color.Black
),
center = leftEyeOffset,
radius = 25f
),
radius = 25f,
center = leftEyeOffset
)
drawCircle(
brush = Brush.radialGradient(
colors = listOf(
Color.White,
Color.Black
),
center = rightEyeOffset,
radius = 25f
),
radius = 25f,
center = rightEyeOffset
)
drawRect(
color = Color.Black,
topLeft = Offset((rightEyeOffset.x - 37.5f), rightEyeOffset.y - 50f),
size = Size(80f, 10f)
)
drawRect(
color = Color.Black,
topLeft = Offset((leftEyeOffset.x - 37.5f), leftEyeOffset.y - 50f),
size = Size(80f, 10f)
)
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 180f,
useCenter = true,
topLeft = Offset(center.x - 100f, noseOffset.y + 60f),
size = Size(200f, 200f),
style = Stroke(
width = 3.dp.toPx()
)
)
drawLine(
color = Color.Black,
start = Offset(center.x, center.y - 210f),
end = Offset(center.x, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
drawLine(
color = Color.Black,
start = Offset(center.x - 30f, center.y - 210f),
end = Offset(center.x - 30f, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
drawLine(
color = Color.Black,
start = Offset(center.x + 30f, center.y - 210f),
end = Offset(center.x + 30f, center.y - 210f - 80f),
strokeWidth = 4.dp.toPx()
)
}
}
Kotlin
๋ณต์ฌ
์ค์ผ์น
์ต์ข
๊ฒฐ๊ณผ๋ฌผ