{"id":46187,"date":"2022-08-06T01:00:01","date_gmt":"2022-08-05T16:00:01","guid":{"rendered":"https:\/\/www.charlezz.com\/?p=46187"},"modified":"2022-08-06T01:00:03","modified_gmt":"2022-08-05T16:00:03","slug":"compose-canvas%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ec%8b%9c%ea%b3%84-%eb%a7%8c%eb%93%a4%ea%b8%b0","status":"publish","type":"post","link":"https:\/\/charlezz.com\/?p=46187","title":{"rendered":"Compose Canvas\ub97c \ud65c\uc6a9\ud55c \uc2dc\uacc4 \ub9cc\ub4e4\uae30"},"content":{"rendered":"\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui---2022-08-05--8.24.48.mov\"><\/video><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Prerequisite<\/h3>\n\n\n\n<ul><li><a href=\"https:\/\/www.charlezz.com\/?p=46165\">\uc218\ud559\uc801\uc73c\ub85c \uc6d0\uc744 \uadf8\ub9ac\ub824\uba74 \uc5b4\ub5bb\uac8c \ud574\uc57c\ud560\uae4c?<\/a><\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ucef4\ud3ec\uc988\ub85c \uc2dc\uacc4 \ub9cc\ub4e4\uae30<\/h2>\n\n\n\n<p>\uc704 \uc601\uc0c1\uacfc \uac19\uc740 \uc2dc\uacc4 UI\ub97c \ub9cc\ub4e4\ub824\uba74 \uc5b4\ub5bb\uac8c \ud574\uc57c\ud560\uae4c? <\/p>\n\n\n\n<p>\ubcf5\uc7a1\ud558\ub2e4\uace0 \ub290\ub084\uc218\ub85d \uc798\uac8c \ub098\ub204\uba74 \ub2f5\uc774 \ubcf4\uc77c \ub54c\uac00 \uc788\ub2e4. \uc2dc\uacc4\ub97c \uad6c\uc131\ud558\ub294 \uc694\uc18c\ub97c \uc815\ub9ac\ud558\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n\n\n\n<ul><li>\uc2dc\uacc4 \ubc30\uacbd\uc5d0 \ud574\ub2f9\ud558\ub294 \ucee4\ub2e4\ub780 \uc6d0<\/li><li>\uc2dc\uce68, \ubd84\uce68, \ucd08\uce68<\/li><li>\ub208\uae08 \ubc0f \uc2dc\uac04\uc744 \ub098\ud0c0\ub0b4\ub294 \ud14d\uc2a4\ud2b8<\/li><\/ul>\n\n\n\n<p>\uc6b0\uc120 \uac01 \uc694\uc18c\uc758 \uc2a4\ud0c0\uc77c\uc744 \ub2e4\uc74c\uacfc \uac19\uc774 \uc815\ub9ac\ud560 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>data class ClockStyle(\n    val hourHandWidth: Dp = 5.dp, \/\/ \uc2dc\uce68 \ub450\uaed8\n    val minuteHandWidth: Dp = 5.dp, \/\/ \ubd84\uce68 \ub450\uaed8\n    val secondHandWidth: Dp = 3.dp, \/\/ \ucd08\uce68 \ub450\uaed8\n    val hourHandLength: Dp = 80.dp, \/\/ \uc2dc\uce68 \uae38\uc774\n    val minuteHandLength: Dp = 120.dp, \/\/ \ubd84\uce68 \uae38\uc774\n    val secondHandLength: Dp = 120.dp, \/\/ \ucd08\uce68 \uae38\uc774\n    val hourHandColor: Color = Color.Black, \/\/ \uc2dc\uce68 \uc0c9\uc0c1\n    val minuteHandColor: Color = Color.Black, \/\/ \ubd84\uce68 \uc0c9\uc0c1\n    val secondHandColor: Color = Color.Red, \/\/ \ucd08\uce68 \uc0c9\uc0c1\n    val textColor: Color = Color.Black, \/\/ 1~12 \ud14d\uc2a4\ud2b8 \uc0c9\uc0c1\n    val textSize: Dp = 20.dp, \/\/ 1~12 \ud14d\uc2a4\ud2b8 \ud06c\uae30\n    val hourGradationWidth: Dp = 2.dp, \/\/ \uc2dc\uac04 \ub208\uae08 \ub450\uaed8\n    val minuteGradationWidth: Dp = 1.dp, \/\/ \ubd84 \ub208\uae08 \ub450\uaed8\n    val hourGradationColor: Color = Color.Black, \/\/ \uc2dc\uac04 \ub208\uae08 \uc0c9\uc0c1\n    val minuteGradationColor: Color = Color.Black, \/\/ \ubd84 \ub208\uae08 \uc0c9\uc0c1\n    val hourGradationLength: Dp = 20.dp, \/\/ \uc2dc\uac04 \ub208\uae08 \uae38\uc774\n    val minuteGradationLength: Dp = 10.dp, \/\/ \ubd84 \ub208\uae08 \uae38\uc774\n    val shadowRadius:Dp = 15.dp,  \/\/ \ubc30\uacbd \uadf8\ub9bc\uc790 \ud06c\uae30\n    val shadowColor:Color = Color.Black.copy(alpha = 0.5f), \/\/ \ubc30\uacbd \uadf8\ub9bc\uc790 \uc0c9\uc0c1\n    val centerCircleSize:Dp = 3.dp, \/\/ \uc911\uc2ec\uc810 \ud06c\uae30\n    val centerCircleColor:Color = Color.Black \/\/ \uc911\uc2ec\uc810 \uc0c9\uc0c1 \n)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\uc2dc\uacc4 \ubc30\uacbd \uadf8\ub9ac\uae30<\/h2>\n\n\n\n<p>\ub2e4\uc74c\uacfc \uac19\uc774 \uc6d0\ud615\uc758 \uc2dc\uacc4 \ubc30\uacbd\uc744 \ud55c\ubc88 \uadf8\ub824\ubcf4\uc790<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"898\" height=\"902\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.54.47.png\" alt=\"\" class=\"wp-image-46190\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.54.47.png 898w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.54.47-300x300.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.54.47-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.54.47-768x771.png 768w\" sizes=\"(max-width: 898px) 100vw, 898px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Clock(\n    modifier: Modifier = Modifier,\n    clockStyle: ClockStyle = ClockStyle()\n) {\n    Canvas(modifier = modifier) {\n        \/\/ \uc2dc\uacc4 \ubc30\uacbd\n        val radius: Float = size.minDimension \/ 2.0f \/\/ \uc6d0\uc758 \ubc18\uc9c0\ub984\n        drawContext.canvas.nativeCanvas.apply {\n            this.drawCircle(\n                center.x,\n                center.y,\n                radius,\n                Paint().apply {\n                    color = android.graphics.Color.WHITE\n                    setShadowLayer(\n                        clockStyle.shadowRadius.toPx(),\n                        0f,\n                        0f,\n                        clockStyle.shadowColor.toArgb()\n                    )\n                }\n            )\n        }\n        \/\/ \uc911\uc2ec\uc810\n        drawCircle(\n            radius = clockStyle.centerCircleSize.toPx(),\n            color = clockStyle.centerCircleColor\n        )\n    }\n\n}\n\n@Preview\n@Composable\nprivate fun ClockPreview() {\n    Surface(color = Color.White) {\n        Clock(\n            modifier = Modifier.size(300.dp),\n            clockStyle = ClockStyle()\n        )\n    }\n\n}<\/code><\/pre>\n\n\n\n<p>\uc6d0\uc744 \uadf8\ub9ac\uae30 \uc704\ud574 \ucef4\ud3ec\uc988\uc758 <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/graphics\/drawscope\/DrawScope#drawCircle(androidx.compose.ui.graphics.Brush,kotlin.Float,androidx.compose.ui.geometry.Offset,kotlin.Float,androidx.compose.ui.graphics.drawscope.DrawStyle,androidx.compose.ui.graphics.ColorFilter,androidx.compose.ui.graphics.BlendMode)\">drawCircle()<\/a> \ud568\uc218\ub97c \ud638\ucd9c \ud560 \uc218\ub3c4 \uc788\uc9c0\ub9cc, \uadf8\ub9bc\uc790\ub97c \uc544\uc9c1 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc73c\ubbc0\ub85c \ub124\uc774\ud2f0\ube0c \uce94\ubc84\uc2a4(Android View \uce94\ubc84\uc2a4)\uc5d0 \uc9c1\uc811 \uc811\uadfc\ud558\uc5ec \ub610 \ub2e4\ub978 <a href=\"https:\/\/developer.android.com\/reference\/android\/graphics\/Canvas#drawCircle(float,%20float,%20float,%20android.graphics.Paint)\">drawCircle()<\/a> \ud568\uc218\ub97c \ud638\ucd9c\ud55c\ub2e4. <\/p>\n\n\n\n<p>\uc6d0\uc744 \uadf8\ub9ac\uae30 \uc704\ud574\uc11c\ub294 \uc6d0\uc758 \uc911\uc2ec\uc810\uacfc \uc6d0\uc758 \ubc18\uc9c0\ub984 \uac12\uc774 \ud544\uc694\ud55c\ub370, \uce94\ubc84\uc2a4\uc5d0 \uaf49\ucc28\uac8c \uadf8\ub9ac\uae30 \uc704\ud574 \uce94\ubc84\uc2a4 \uc911\uc2ec\uc810\uacfc \uce94\ubc84\uc2a4 \ucd5c\uc18c \uc0ac\uc774\uc988\uc758 \uc808\ubc18\uc5d0 \ud574\ub2f9\ud558\ub294 \uae38\uc774\ub97c \uc6d0\uc758 \ubc18\uc9c0\ub984\uc73c\ub85c \uacb0\uc815\ud588\ub2e4.<\/p>\n\n\n\n<p>drawCircle\uc744 \ud638\ucd9c\ud560 \ub54c \ub9c8\uc9c0\ub9c9 \ud30c\ub77c\ubbf8\ud130 \uc778 <a href=\"https:\/\/developer.android.com\/reference\/android\/graphics\/Paint#setShadowLayer(float,%20float,%20float,%20int)\">Paint\uc758 setShadowLayer<\/a>\ub97c \ud638\ucd9c\ud558\uba74 \ubc30\uacbd\uc5d0 \uadf8\ub9bc\uc790\ub97c \uadf8\ub9b4 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<p>\uc2dc\uacc4 \uc911\uc2ec\uc810\uc744 \uadf8\ub9ac\uae30 \uc704\ud574 \uc9c0\uc815\ud55c \uc0ac\uc774\uc988\ub85c drawCircle() \ud55c\ubc88 \ub354 \ud638\ucd9c \ud55c\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\uc2dc\uacc4 \ub208\uae08 \uadf8\ub9ac\uae30<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"898\" height=\"898\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.58.13.png\" alt=\"\" class=\"wp-image-46191\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.58.13.png 898w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.58.13-300x300.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.58.13-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-05--8.58.13-768x768.png 768w\" sizes=\"(max-width: 898px) 100vw, 898px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Clock(\n    modifier: Modifier = Modifier,\n    clockStyle: ClockStyle = ClockStyle()\n) {\n    Canvas(modifier = modifier) {\n        ...\n        \/\/ \uc2dc\uacc4 \ub208\uae08 \uadf8\ub9ac\uae30\n        val gradationCount = 60 \/\/ \uc2dc\uacc4\ub208\uae08 \uac2f\uc218\n        repeat(gradationCount) { index ->\n            val angleInDegree = (index * 360 \/ gradationCount).toDouble()\n            val angleInRadian = Math.toRadians(angleInDegree)\n\n            val isHourGradation = index % 5 == 0\n            val length = if (isHourGradation) {\n                clockStyle.hourGradationLength.toPx()\n            } else {\n                clockStyle.minuteGradationLength.toPx()\n            }\n\n            val start = Offset(\n                x = (center.x + (radius - length) * cos(angleInRadian)).toFloat(),\n                y = (center.y + (radius - length) * sin(angleInRadian)).toFloat()\n            )\n            val end = Offset(\n                x = (center.x + radius * cos(angleInRadian)).toFloat(),\n                y = (center.y + radius * sin(angleInRadian)).toFloat()\n            )\n            val gradationColor: Color = if (isHourGradation) {\n                clockStyle.hourGradationColor\n            } else {\n                clockStyle.minuteGradationColor\n            }\n\n            val gradationWidth: Dp = if (isHourGradation) {\n                clockStyle.hourGradationWidth\n            } else {\n                clockStyle.minuteGradationWidth\n            }\n\n            drawLine(\n                color = gradationColor,\n                start = start,\n                end = end,\n                strokeWidth = gradationWidth.toPx()\n            )\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>\uadf8\ub9ac\uace0\uc790 \ud558\ub294 \uc2dc\uacc4\uc758 \ub208\uae08\uc740 60\uac1c\ub2e4. \uc815\ud655\ud788 6\u00b0\u00a0 \uac04\uaca9\uc73c\ub85c \ub208\uae08\uc744 \ud558\ub098\uc529 \uadf8\ub9ac\uba74 60\uac1c\ub97c \uadf8\ub9b4 \uc218 \uc788\ub2e4. (60 * 6\u00b0\u00a0 = 360\u00b0\u00a0)<\/p>\n\n\n\n<p>\ub208\uae08\uc744 \uadf8\ub9ac\uae30 \uc704\ud574 \ud544\uc694\ud55c \uac01\ub3c4(degree)\uac00 \uc815\ud574\uc84c\uc73c\ub2c8 \ubc18\uc9c0\ub984(radius)\ub9cc \uc815\ud574\uc9c0\uba74 \uc911\uc2ec\uc810\uc744 \uae30\uc900\uc73c\ub85c \uc6d0\ud615\uc744 \uadf8\ub9ac\uba70 \uc2dc\uacc4 \ub208\uae08\uc744 \uadf8\ub9b4 \uc218 \uc788\ub2e4. \ub208\uae08\uc740 \uc120\ubd84\uc774\uba70, \uc120\ubd84\uc740 \ub450\uac1c\uc758 \uc810\uc744 \uc787\ub294 \uc9c1\uc120\uc774\ub2e4. drawLine() \ud568\uc218 \ud638\ucd9c\uc744 \ud1b5\ud574 \uc9c1\uc120\uc744 \uadf8\ub9b4 \uc218 \uc788\ub2e4. <\/p>\n\n\n\n<p>\uc774\uc81c \ub450 \uc810\uc744 \uacc4\uc0b0\ud574\uc57c\ud558\ub294\ub370 \uc5b4\ub5bb\uac8c \ud558\uba74 \ub450 \uc810\uc758 \uc704\uce58\ub97c \uc54c \uc218 \uc788\uc744\uae4c? \ub2e4\uc74c \uadf8\ub9bc\uc744 \uc0b4\ud3b4\ubcf4\uc790<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"898\" height=\"898\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui-www.charlezz.com-compose-canvas-custom-ui-2022-08-05-8.58.13-1.png\" alt=\"\" class=\"wp-image-46193\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui-www.charlezz.com-compose-canvas-custom-ui-2022-08-05-8.58.13-1.png 898w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui-www.charlezz.com-compose-canvas-custom-ui-2022-08-05-8.58.13-1-300x300.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui-www.charlezz.com-compose-canvas-custom-ui-2022-08-05-8.58.13-1-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui-www.charlezz.com-compose-canvas-custom-ui-2022-08-05-8.58.13-1-768x768.png 768w\" sizes=\"(max-width: 898px) 100vw, 898px\" \/><\/figure>\n\n\n\n<p>2\uc2dc\uc5d0 \ud574\ub2f9 \ud558\ub294 \ub208\uae08\uc744 \uadf8\ub9b0\ub2e4\uace0 \uac00\uc815\ud558\uba74, \ub450 \uc810 (x1, y1), (x2, y2)\ub97c \uc9c1\uc120\uc73c\ub85c \uc774\uc73c\uba74 \ub41c\ub2e4. \ub450\uc810\uc740 \ub3d9\uc2ec\uc6d0\uc778 \ube68\uac04\uc6d0\uacfc \ud30c\ub780\uc6d0\uc758 \ub458\ub808\uc5d0 \ud574\ub2f9\ud558\ub294 \uc815\uc810\uc774\ub2e4. \uc774 \uc810\ub4e4\uc758 \uc88c\ud45c\ub97c \uacc4\uc0b0\ud558\uae30 \uc704\ud574\uc11c \uc0bc\uac01\ud568\uc218, \uc6d0\uc758 radius(\ubc18\uc9c0\ub984)\uc640 radian(\ud638\ub3c4)\uc774 \uc788\uc73c\uba74 \ub41c\ub2e4. \uac01\ub3c4(degree)\ub97c \ud638\ub3c4\ub85c \ubcc0\uacbd\ud558\ub294 \ud568\uc218\ub97c Math \ud074\ub798\uc2a4\uc5d0\uc11c \uc81c\uacf5\ud55c\ub2e4. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 \uc774\uc804 \ud3ec\uc2a4\ud305\uc744 \ucc38\uc870\ud558\uc790.<\/p>\n\n\n\n<p>\uc2dc(hour)\uc5d0 \ud574\ub2f9\ud558\ub294 \ub208\uae08(Index, 1~12)\uc740 \ud2b9\ubcc4\ud788 \uad75\uc740 \uc120\uc73c\ub85c \uadf8\ub838\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\uc2dc(hour) \ud14d\uc2a4\ud2b8 \uadf8\ub9ac\uae30<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"922\" height=\"922\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.29.35.png\" alt=\"\" class=\"wp-image-46194\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.29.35.png 922w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.29.35-300x300.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.29.35-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.29.35-768x768.png 768w\" sizes=\"(max-width: 922px) 100vw, 922px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Clock(\n    modifier: Modifier = Modifier,\n    clockStyle: ClockStyle = ClockStyle()\n) {\n    Canvas(modifier = modifier) {\n        ...\n        repeat(gradationCount) { index ->\n            val angleInDegree = (index * 360 \/ gradationCount).toDouble()\n            val angleInRadian = Math.toRadians(angleInDegree)\n            \/\/\uc2dc,\ubd84 \ub208\uae08 \uadf8\ub9ac\uae30\n            ...\n\n            \/\/ 1~12\uc2dc \ud14d\uc2a4\ud2b8 \uadf8\ub9ac\uae30\n            drawContext.canvas.nativeCanvas.apply {\n                if (index % 5 == 0) {\n                    var hourText = (index \/ 5 + 3) % 12\n                    if (hourText == 0) {\n                        hourText = 12\n                    }\n                    val textSize = clockStyle.textSize.toPx()\n                    val textRadius = radius - length - textSize\n                    val x = textRadius * cos(angleInRadian) + center.x\n                    val y = textRadius * sin(angleInRadian) + center.y + textSize \/ 2f\n                    drawText(\n                        \"$hourText\",\n                        x.toFloat(),\n                        y.toFloat(),\n                        Paint().apply {\n                            this.color = clockStyle.textColor.toArgb()\n                            this.textSize = textSize\n                            this.textAlign = Paint.Align.CENTER\n                        }\n                    )\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>\ud14d\uc2a4\ud2b8\ub294 \ucef4\ud3ec\uc988 \uce94\ubc84\uc2a4\uc5d0\uc11c \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc73c\ubbc0\ub85c \ub124\uc774\ud2f0\ube0c \uce94\ubc84\uc2a4\uc5d0 \uc811\uadfc\ud574\uc11c drawText()\ub97c \ud638\ucd9c\ud55c\ub2e4. \uc2dc\uacc4 \ub208\uae08\uc744 \uadf8\ub9b4 \ub54c\uc640 \ub3d9\uc77c\ud55c \uc811\uadfc \ubc29\uc2dd\uc73c\ub85c \ud14d\uc2a4\ud2b8\uc758 \uc88c\ud45c\ub97c \uacc4\uc0b0\ud560 \uc218 \uc788\ub2e4. \uc8fc\uc758\ud574\uc57c \ud560 \uc810\uc740 \uac01\ub3c4\ub97c 0<meta charset=\"utf-8\">\u00b0 \ubd80\ud130 \uace0\ub824\ud558\uba74\uc11c \ud14d\uc2a4\ud2b8\ub97c \uadf8\ub9ac\uba74 3\uc2dc \ubc29\ud5a5\ubd80\ud130 \ud14d\uc2a4\ud2b8\uac00 \uadf8\ub824\uc9c0\uae30 \uc2dc\uc791\ud558\ubbc0\ub85c \uc774\ub97c \uc720\uc758\ud558\uace0 \uadf8\ub824\uc57c \ud55c\ub2e4. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\uc2dc\uce68, \ubd84\uce68, \ucd08\uce68 \uadf8\ub9ac\uae30<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"922\" height=\"922\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.40.07.png\" alt=\"\" class=\"wp-image-46195\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.40.07.png 922w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.40.07-300x300.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.40.07-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/08\/www.charlezz.com-compose-canvas-custom-ui--2022-08-06--12.40.07-768x768.png 768w\" sizes=\"(max-width: 922px) 100vw, 922px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Clock(\n    modifier: Modifier = Modifier,\n    clockStyle: ClockStyle = ClockStyle(),\n    hour: Int,\n    minute: Int,\n    second: Int\n) {\n    Canvas(modifier = modifier) {\n        ... \n        \/\/ \uc2dc\uce68 \uadf8\ub9ac\uae30(Hour hand)\n        val hourAngleIncrement = 360.0\/12.0\n        val hourHandInDegree = (\n                -90 \/\/ 12\uc2dc\ubd80\ud130 \uc2dc\uc791\ud558\ub3c4\ub85d 90\ub3c4 \uaebe\uc74c\n                + hour * hourAngleIncrement\n                + hourAngleIncrement * minute.toDouble() \/ TimeUnit.HOURS.toMinutes(1)\n                + hourAngleIncrement * second.toDouble() \/ TimeUnit.HOURS.toSeconds(1)\n            )\n        val hourHandInRadian = Math.toRadians(hourHandInDegree)\n        val hourHandStart = Offset(\n            x = center.x,\n            y = center.y\n        )\n        val hourHandEnd = Offset(\n            x = (center.x + clockStyle.hourHandLength.toPx() * cos(hourHandInRadian)).toFloat(),\n            y = (center.y + clockStyle.hourHandLength.toPx() * sin(hourHandInRadian)).toFloat()\n        )\n        drawLine(\n            color = clockStyle.hourHandColor,\n            start = hourHandStart,\n            end = hourHandEnd,\n            strokeWidth = clockStyle.hourHandWidth.toPx(),\n            cap = StrokeCap.Round\n        )\n\n        \/\/ \ubd84\uce68 \uadf8\ub9ac\uae30(Minute hand)\n        ...\n\n        \/\/ \ucd08\uce68 \uadf8\ub9ac\uae30 (Second hand)\n        ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>\uc2dc\uce68, \ubd84\uce68, \ucd08\uce68\uc744 \uadf8\ub9ac\ub294 \uc6d0\ub9ac\ub294 \ub3d9\uc77c\ud558\ubbc0\ub85c \uc2dc\uce68\uc744 \uadf8\ub9ac\ub294 \ubc29\ubc95\uc5d0 \ub300\ud574\uc11c\ub9cc \uc124\uba85\ud55c\ub2e4. \uc774\ubbf8 \ub208\uce58 \ucc58\uaca0\uc9c0\ub9cc \uc2dc\uce68\uc744 \uadf8\ub9ac\ub294 \ud2b9\ubcc4\ud55c \ubc29\ubc95\uc740 \uc5c6\ub2e4. \b\uc911\uc2ec\uc810(center)\uacfc \uc2dc\uce68\uc758 \uae38\uc774\ub97c \ubc18\uc9c0\ub984\uc758 \uac16\ub294 \uac00\uc0c1\uc758 \uc6d0\uc744 \uc0dd\uac01\ud558\uba70 \uc801\uc808\ud55c angle\uc744 \ub300\uc785\ud574 \uc815\uc810\uc744 \uad6c\ud55c \ub4a4 \ub450\uac1c\uc758 \uc810\uc744  \uc9c1\uc120\uc73c\ub85c \uadf8\uc73c\uba74 \ub41c\ub2e4. \uc774\ud574\ud558\uae30 \uc5b4\ub835\ub2e4\uba74 \uc2dc\uacc4 \ub208\uae08 \uadf8\ub9ac\uae30\ubd80\ud130 \ub2e4\uc2dc \uc77d\uc5b4\ubcf4\ub3c4\ub85d \ud558\uc790.<\/p>\n\n\n\n<p>\ub9c8\ucc2c\uac00\uc9c0\ub85c \uc2dc\uce68\uc744 \uadf8\ub9b4 \ub54c \uc8fc\uc758 \ud574\uc57c\ud560 \uac83\uc740 angle\uc744 <meta charset=\"utf-8\">0<meta charset=\"utf-8\">\u00b0 \uae30\uc900\uc73c\ub85c \uacc4\uc0b0\ud560 \uc2dc 3\uc2dc\ubc29\ud5a5\ubd80\ud130 \uc2dc\uc791\ud558\ubbc0\ub85c, 12\uc2dc\ub97c \uae30\uc900\uc73c\ub85c \uc2dc\uc791\ud558\uae30 \uc704\ud574 angle\uc744 -90<meta charset=\"utf-8\">\u00b0 \uaebe\uace0 \uacc4\uc0b0\ud55c\ub2e4.<\/p>\n\n\n\n<p>\uc2dc\uce68\uc774 \uc62c\ubc14\ub974\uac8c \uadf8\ub824\uc84c\ub2e4\uba74, \ubd84\uce68\uacfc \ucd08\uce68\ub3c4 \uac19\uc740 \ubc29\ubc95\uc73c\ub85c \uadf8\ub9ac\ub3c4\ub85d \ud55c\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\uc804\uccb4 \ucf54\ub4dc<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">import android.graphics.Paint\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material3.Surface\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.<em>nativeCanvas\n<\/em>import androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.time.LocalTime\nimport java.util.concurrent.TimeUnit\nimport kotlin.math.cos\nimport kotlin.math.sin\n\n@Composable\nfun Clock(\n    modifier: Modifier = Modifier,\n    clockStyle: ClockStyle = ClockStyle(),\n    hour: Int,\n    minute: Int,\n    second: Int\n) {\n    <em>Canvas<\/em>(modifier = modifier) <strong>{\n        <\/strong>\/\/ \uc2dc\uacc4 \ubc30\uacbd\n        val radius: Float = size.minDimension \/ 2.0f \/\/ \uc6d0\uc758 \ubc18\uc9c0\ub984\n        drawContext.canvas.<em>nativeCanvas<\/em>.<em>apply <\/em><strong>{\n            <\/strong>this.drawCircle(\n                center.x,\n                center.y,\n                radius,\n                Paint().<em>apply <\/em><strong>{\n                    <\/strong><em>color <\/em>= android.graphics.Color.<em>WHITE\n                    <\/em>setShadowLayer(\n                        clockStyle.shadowRadius.<em>toPx<\/em>(),\n                        0f,\n                        0f,\n                        clockStyle.shadowColor.<em>toArgb<\/em>()\n                    )\n                <strong>}\n            <\/strong>)\n        <strong>}\n\n        <\/strong>\/\/ \uc911\uc2ec\uc810\n        drawCircle(\n            radius = clockStyle.centerCircleSize.<em>toPx<\/em>(),\n            color = clockStyle.centerCircleColor\n        )\n\n        \/\/ \uc2dc\uacc4 \ub208\uae08\n        val gradationCount = 60 \/\/ \uc2dc\uacc4\ub208\uae08 \uac2f\uc218\n        <em>repeat<\/em>(gradationCount) <strong>{ <\/strong>index <strong>->\n            <\/strong>val angleInDegree = (index * 360 \/ gradationCount).toDouble()\n            val angleInRadian = Math.toRadians(angleInDegree)\n\n            val isHourGradation = index % 5 == 0\n            val length = if (isHourGradation) {\n                clockStyle.hourGradationLength.<em>toPx<\/em>()\n            } else {\n                clockStyle.minuteGradationLength.<em>toPx<\/em>()\n            }\n\n            val start = <em>Offset<\/em>(\n                x = (center.x + (radius - length) * <em>cos<\/em>(angleInRadian)).toFloat(),\n                y = (center.y + (radius - length) * <em>sin<\/em>(angleInRadian)).toFloat()\n            )\n            val end = <em>Offset<\/em>(\n                x = (center.x + radius * <em>cos<\/em>(angleInRadian)).toFloat(),\n                y = (center.y + radius * <em>sin<\/em>(angleInRadian)).toFloat()\n            )\n            val gradationColor: Color = if (isHourGradation) {\n                clockStyle.hourGradationColor\n            } else {\n                clockStyle.minuteGradationColor\n            }\n\n            val gradationWidth: Dp = if (isHourGradation) {\n                clockStyle.hourGradationWidth\n            } else {\n                clockStyle.minuteGradationWidth\n            }\n\n            \/\/\uc2dc,\ubd84 \ub208\uae08 \uadf8\ub9ac\uae30\n            drawLine(\n                color = gradationColor,\n                start = start,\n                end = end,\n                strokeWidth = gradationWidth.<em>toPx<\/em>()\n            )\n\n            \/\/ 1~12\uc2dc \ud14d\uc2a4\ud2b8 \uadf8\ub9ac\uae30\n            drawContext.canvas.<em>nativeCanvas<\/em>.<em>apply <\/em><strong>{\n                <\/strong>if (index % 5 == 0) {\n                    var hourText = (index \/ 5 + 3) % 12\n                    if (hourText == 0) {\n                        hourText = 12\n                    }\n                    val textSize = clockStyle.textSize.<em>toPx<\/em>()\n                    val textRadius = radius - length - textSize\n                    val x = textRadius * <em>cos<\/em>(angleInRadian) + center.x\n                    val y = textRadius * <em>sin<\/em>(angleInRadian) + center.y + textSize \/ 2f\n                    drawText(\n                        \"$hourText\",\n                        x.toFloat(),\n                        y.toFloat(),\n                        Paint().<em>apply <\/em><strong>{\n                            <\/strong>this.<em>color <\/em>= clockStyle.textColor.<em>toArgb<\/em>()\n                            this.<em>textSize <\/em>= textSize\n                            this.<em>textAlign <\/em>= Paint.Align.<em>CENTER\n                        <\/em><strong>}\n                    <\/strong>)\n                }\n            <strong>}\n        }\n\n        <\/strong>\/\/ \uc2dc\uce68 \uadf8\ub9ac\uae30(Hour hand)\n        val hourAngleIncrement = 360.0\/12.0\n        val hourHandInDegree = (\n                -90 \/\/ 12\uc2dc\ubd80\ud130 \uc2dc\uc791\ud558\ub3c4\ub85d 90\ub3c4 \uaebe\uc74c\n                        + hour * hourAngleIncrement\n                        + hourAngleIncrement * minute.toDouble() \/ TimeUnit.<em>HOURS<\/em>.toMinutes(1)\n                        + hourAngleIncrement * second.toDouble() \/ TimeUnit.<em>HOURS<\/em>.toSeconds(1)\n                )\n        val hourHandInRadian = Math.toRadians(hourHandInDegree)\n        val hourHandStart = <em>Offset<\/em>(\n            x = center.x,\n            y = center.y\n        )\n        val hourHandEnd = <em>Offset<\/em>(\n            x = (center.x + clockStyle.hourHandLength.<em>toPx<\/em>() * <em>cos<\/em>(hourHandInRadian)).toFloat(),\n            y = (center.y + clockStyle.hourHandLength.<em>toPx<\/em>() * <em>sin<\/em>(hourHandInRadian)).toFloat()\n        )\n        drawLine(\n            color = clockStyle.hourHandColor,\n            start = hourHandStart,\n            end = hourHandEnd,\n            strokeWidth = clockStyle.hourHandWidth.<em>toPx<\/em>(),\n            cap = StrokeCap.Round\n        )\n\n        \/\/ \ubd84\uce68 \uadf8\ub9ac\uae30(Minute hand)\n        val minuteAngleIncrement = 360.0\/60.0\n        val minuteHandInDegree = (\n                -90 \/\/ 90\ub3c4 \uaebe\uc74c\n                        + minuteAngleIncrement * minute.toDouble()\n                        + minuteAngleIncrement * second.toDouble() \/ TimeUnit.<em>MINUTES<\/em>.toSeconds(1)\n                )\n\n        val minuteHandInRadian = Math.toRadians(minuteHandInDegree)\n\n        val minuteLineStart = <em>Offset<\/em>(\n            x = center.x,\n            y = center.y\n        )\n        val minuteLineEnd = <em>Offset<\/em>(\n            x = (center.x + clockStyle.minuteHandLength.<em>toPx<\/em>() * <em>cos<\/em>(minuteHandInRadian)).toFloat(),\n            y = (center.y + clockStyle.minuteHandLength.<em>toPx<\/em>() * <em>sin<\/em>(minuteHandInRadian)).toFloat()\n        )\n\n        drawLine(\n            color = clockStyle.minuteHandColor,\n            start = minuteLineStart,\n            end = minuteLineEnd,\n            strokeWidth = clockStyle.minuteHandWidth.<em>toPx<\/em>(),\n            cap = StrokeCap.Round\n        )\n\n        \/\/ \ucd08\uce68 \uadf8\ub9ac\uae30 (Second hand)\n        val secondAngleIncrement = 360.0\/60.0\n        val secondInDegree = -90 + secondAngleIncrement * second.toDouble()\n        val secondInRadian = Math.toRadians(secondInDegree)\n        val secondLineStart = <em>Offset<\/em>(\n            x = center.x,\n            y = center.y\n        )\n        val secondLineEnd = <em>Offset<\/em>(\n            x = (center.x + clockStyle.secondHandLength.<em>toPx<\/em>() * <em>cos<\/em>(secondInRadian)).toFloat(),\n            y = (center.y + clockStyle.secondHandLength.<em>toPx<\/em>() * <em>sin<\/em>(secondInRadian)).toFloat()\n        )\n\n        drawLine(\n            color = clockStyle.secondHandColor,\n            start = secondLineStart,\n            end = secondLineEnd,\n            strokeWidth = clockStyle.secondHandWidth.<em>toPx<\/em>(),\n            cap = StrokeCap.Round\n        )\n    <strong>}\n<\/strong>}\n\n@Preview\n@Composable\nprivate fun ClockPreview() {\n    var currentTime by <em>remember <\/em><strong>{ <\/strong><em>mutableStateOf<\/em>(LocalTime.now()) <strong>}\n    <\/strong>val coroutineScope = <em>rememberCoroutineScope<\/em>()\n    coroutineScope.<em>launch<\/em>(Dispatchers.IO) <strong>{\n        <\/strong>while (true){\n            delay(500)\n            currentTime = LocalTime.now()\n        }\n    <strong>}\n    <\/strong><em>Surface<\/em>(color = Color.White) <strong>{\n        <\/strong><em>Clock<\/em>(\n            modifier = Modifier.<em>size<\/em>(300.<em>dp<\/em>),\n            clockStyle = ClockStyle(),\n            hour = currentTime.<em>hour<\/em>,\n            minute = currentTime.<em>minute<\/em>,\n            second = currentTime.<em>second\n        <\/em>)\n    <strong>}\n\n<\/strong>}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Prerequisite \uc218\ud559\uc801\uc73c\ub85c \uc6d0\uc744 \uadf8\ub9ac\ub824\uba74 \uc5b4\ub5bb\uac8c \ud574\uc57c\ud560\uae4c? \ucef4\ud3ec\uc988\ub85c \uc2dc\uacc4 \ub9cc\ub4e4\uae30 \uc704 \uc601\uc0c1\uacfc \uac19\uc740 \uc2dc\uacc4 UI\ub97c \ub9cc\ub4e4\ub824\uba74 \uc5b4\ub5bb\uac8c \ud574\uc57c\ud560\uae4c? \ubcf5\uc7a1\ud558\ub2e4\uace0 \ub290\ub084\uc218\ub85d \uc798\uac8c \ub098\ub204\uba74 \ub2f5\uc774 \ubcf4\uc77c \ub54c\uac00 \uc788\ub2e4. \uc2dc\uacc4\ub97c \uad6c\uc131\ud558\ub294 \uc694\uc18c\ub97c \uc815\ub9ac\ud558\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4. \uc2dc\uacc4 \ubc30\uacbd\uc5d0 \ud574\ub2f9\ud558\ub294 \ucee4\ub2e4\ub780 \uc6d0 \uc2dc\uce68, \ubd84\uce68, \ucd08\uce68 \ub208\uae08 \ubc0f \uc2dc\uac04\uc744 \ub098\ud0c0\ub0b4\ub294 \ud14d\uc2a4\ud2b8 \uc6b0\uc120 \uac01 \uc694\uc18c\uc758 \uc2a4\ud0c0\uc77c\uc744 \ub2e4\uc74c\uacfc \uac19\uc774 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[38],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46187"}],"collection":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=46187"}],"version-history":[{"count":2,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46187\/revisions"}],"predecessor-version":[{"id":46197,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46187\/revisions\/46197"}],"wp:attachment":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=46187"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=46187"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=46187"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}