Skip to content

Instantly share code, notes, and snippets.

@Pooh3Mobi
Created July 8, 2023 09:24
Show Gist options
  • Save Pooh3Mobi/7baba4f3c53330a4cf1950f8e9e59f1f to your computer and use it in GitHub Desktop.
Save Pooh3Mobi/7baba4f3c53330a4cf1950f8e9e59f1f to your computer and use it in GitHub Desktop.
Half-Circle Progress Example in Compose
package com.example.halfcircleprogresscompose
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.halfcircleprogresscompose.ui.theme.HalfCircleProgressComposeTheme
import kotlinx.coroutines.delay
// orange to red gradient
val progressGradientBrush = Brush.sweepGradient(
colors = listOf(
Color(0xFFFF0000),
// orange
Color(0xFFFFA500),
// red
Color(0xFFFF0000),
)
)
// アイコンのレイアウトに対する比率
val iconRatio = 0.9f
@OptIn(ExperimentalTextApi::class)
@Composable
fun HalfCircleProgressBar(
modifier: Modifier = Modifier,
strokeWidth: Dp = 10.dp,
contentPadding: Dp = 10.dp,
progress: Float,
) {
val contentPaddingPixel = with(LocalDensity.current) {
contentPadding.toPx() + 10f
}
val textMeasure = rememberTextMeasurer()
val stroke = with(LocalDensity.current) {
Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Round)
}
val vector = ImageVector.vectorResource(id = R.drawable.ic_launcher_foreground)
val painter = rememberVectorPainter(image = vector)
Canvas(
modifier
.aspectRatio(1f)
.background(Color.LightGray)
) {
drawArc(
brush = progressGradientBrush,
startAngle = -180f,
sweepAngle = 180f * progress,
topLeft = Offset(
x = stroke.width + contentPaddingPixel,
y = stroke.width + contentPaddingPixel,
),
size = this.size.copy(
width = size.width - 2 * stroke.width - 2 * contentPaddingPixel,
height = size.height - 2 * stroke.width - 2 * contentPaddingPixel,
),
useCenter = false,
style = stroke,
)
val text = "${(progress * 100).toInt()}%"
val centerPercentTextX = size.width / 2 - (textMeasure.measure(text).size.width - textMeasure.measure("%").size.width)/ 2
drawText(
textMeasurer = textMeasure,
text = text,
topLeft = Offset(
x = centerPercentTextX,
y = size.height / 2 + textMeasure.measure(text).size.height / 2 + contentPaddingPixel,
)
)
// draw zero at arc start
drawText(
textMeasurer = textMeasure,
style = TextStyle.Default.copy(fontSize = 10.sp),
text = "0",
topLeft = Offset(
x = stroke.width + contentPaddingPixel - textMeasure.measure("0").size.width / 2 ,
y = size.height / 2 + textMeasure.measure("0").size.height - contentPaddingPixel,
)
)
// draw 100 at arc end
drawText(
textMeasurer = textMeasure,
style = TextStyle.Default.copy(fontSize = 10.sp),
text = "100",
topLeft = Offset(
x = size.width - stroke.width - textMeasure.measure("100").size.width / 2 - contentPaddingPixel/2,
y = size.height / 2 + textMeasure.measure("100").size.height - contentPaddingPixel,
)
)
// draw icon
val iconSize = (size.width * iconRatio).let { Size(width = it, height = it) }
translate(
left = size.width / 2 - iconSize.width / 2,
top = size.height / 2 - iconSize.height / 2,
) {
with(painter) {
draw(size = iconSize)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun HalfCircleProgressBarPreview() {
var increase by remember {
mutableStateOf(true)
}
var progress by remember {
mutableStateOf(0f)
}
HalfCircleProgressComposeTheme {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
HalfCircleProgressBar(
modifier = Modifier
.size(150.dp),
progress = progress,
)
HalfCircleProgressBar(
modifier = Modifier
.size(200.dp)
.offset(x = 0.dp, y = 150.dp)
.padding(20.dp),
progress = progress,
)
HalfCircleProgressBar(
modifier = Modifier
.size(300.dp)
.offset(x = 0.dp, y = 350.dp)
.padding(20.dp),
progress = progress,
)
}
}
LaunchedEffect(key1 = "test") {
while (true) {
if (increase) {
progress += 0.01f
} else {
progress -= 0.01f
}
if (progress >= 1f) {
increase = false
} else if (progress <= 0f) {
increase = true
}
delay(10)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment