Skip to content

Instantly share code, notes, and snippets.

@nikhil-mandlik-dev
Last active June 26, 2023 07:11
Show Gist options
  • Save nikhil-mandlik-dev/6b79071bdde77df792ba9b32941c174b to your computer and use it in GitHub Desktop.
Save nikhil-mandlik-dev/6b79071bdde77df792ba9b32941c174b to your computer and use it in GitHub Desktop.
Drawing Steps along the circle
//data class for wrapping dial customization
data class DialStyle(
val stepsWidth: Dp = 1.2.dp,
val stepsColor: Color = Color.Black,
val normalStepsLineHeight: Dp = 8.dp,
val fiveStepsLineHeight: Dp = 16.dp,
val stepsTextStyle: TextStyle = TextStyle(),
val stepsLabelTopPadding: Dp = 12.dp,
)
data class ClockStyle(
val secondsDialStyle: DialStyle = DialStyle(),
)
@OptIn(ExperimentalTextApi::class)
@Composable
fun Clock(
modifier: Modifier = Modifier.size(320.dp),
clockStyle: ClockStyle = ClockStyle()
) {
val textMeasurer = rememberTextMeasurer()
var minuteRotation by remember { mutableStateOf(0f) }
var secondRotation by remember { mutableStateOf(0f) }
//secondRotation is updated by 6 degree clockwise every one second
//here rotation is in negative, in order to get clockwise rotation
LaunchedEffect(key1 = true) {
while (true) {
//in-order to get smooth transition we are updating rotation angle every 16ms
//1000ms -> 6 degree
//16ms -> 0.096
delay(16)
secondRotation -= 0.096f
}
}
//minuteRotation is updated by 0.1 degree clockwise every one second
//here rotation is in negative, in order to get clockwise rotation
LaunchedEffect(key1 = true) {
while (true) {
delay(1000)
minuteRotation -= 0.1f
}
}
Canvas(
modifier = modifier
) {
val outerRadius = minOf(this.size.width, this.size.height) / 2f
val innerRadius = outerRadius - 60.dp.toPx()
//Seconds Dial
dial(
radius = outerRadius,
rotation = secondRotation,
textMeasurer = textMeasurer,
dialStyle = clockStyle.secondsDialStyle
)
//Minute Dial
dial(
radius = innerRadius,
rotation = minuteRotation,
textMeasurer = textMeasurer,
dialStyle = clockStyle.minutesDialStyle
)
}
}
@OptIn(ExperimentalTextApi::class)
fun DrawScope.dial(
radius: Float,
rotation: Float,
textMeasurer: TextMeasurer,
dialStyle: DialStyle = DialStyle()
) {
var stepsAngle = 0
//this will draw 60 steps
repeat(60) { steps ->
//fiveStep lineHeight > normalStep lineHeight
val stepsHeight = if (steps % 5 == 0) {
dialStyle.fiveStepsLineHeight.toPx()
} else {
dialStyle.normalStepsLineHeight.toPx()
}
//calculate steps, start and end offset
val stepsStartOffset = Offset(
x = center.x + (radius * cos((stepsAngle + rotation) * (Math.PI / 180f))).toFloat(),
y = center.y - (radius * sin((stepsAngle + rotation) * (Math.PI / 180))).toFloat()
)
val stepsEndOffset = Offset(
x = center.x + (radius - stepsHeight) * cos(
(stepsAngle + rotation) * (Math.PI / 180)
).toFloat(),
y = center.y - (radius - stepsHeight) * sin(
(stepsAngle + rotation) * (Math.PI / 180)
).toFloat()
)
//draw step
drawLine(
color = dialStyle.stepsColor,
start = stepsStartOffset,
end = stepsEndOffset,
strokeWidth = dialStyle.stepsWidth.toPx(),
cap = StrokeCap.Round
)
//draw steps labels
if (steps % 5 == 0) {
//measure the given label width and height
val stepsLabel = String.format("%02d", steps)
val stepsLabelTextLayout = textMeasurer.measure(
text = buildAnnotatedString { append(stepsLabel) },
style = dialStyle.stepsTextStyle
)
//calculate the offset
val stepsLabelOffset = Offset(
x = center.x + (radius - stepsHeight - dialStyle.stepsLabelTopPadding.toPx()) * cos(
(stepsAngle + rotation) * (Math.PI / 180)
).toFloat(),
y = center.y - (radius - stepsHeight - dialStyle.stepsLabelTopPadding.toPx()) * sin(
(stepsAngle + rotation) * (Math.PI / 180)
).toFloat()
)
//subtract the label width and height to position label at the center of the step
val stepsLabelTopLeft = Offset(
stepsLabelOffset.x - ((stepsLabelTextLayout.size.width) / 2f),
stepsLabelOffset.y - (stepsLabelTextLayout.size.height / 2f)
)
drawText(
textMeasurer = textMeasurer,
text = stepsLabel,
topLeft = stepsLabelTopLeft,
style = dialStyle.stepsTextStyle
)
}
stepsAngle += 6
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment