compose自定义控件

package com.example.ui.widget

import androidx.compose.animation.core.*

import androidx.compose.foundation.layout.size

import androidx.compose.material3.CircularProgressIndicator

import androidx.compose.material3.ProgressIndicatorDefaults

import androidx.compose.runtime.*

import androidx.compose.ui.Modifier

import androidx.compose.ui.draw.rotate

import androidx.compose.ui.graphics.Color

import androidx.compose.ui.graphics.StrokeCap

import androidx.compose.ui.unit.Dp

import kotlinx.coroutines.launch

/**

* 单圈旋转进度条(6 秒整圈,可暂停/继续/重置)

*

* @param modifier 外部修饰

* @param size 直径

* @param strokeWidth 线宽

* @param color 进度条颜色

* @param strokeCap 线头形状

* @param onFinish 自然结束回调

*/

@Composable

fun SingleTurnSpinner(

modifier: Modifier = Modifier,

size: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,

strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,

color: Color = Color.Blue,

strokeCap: StrokeCap = StrokeCap.Round,

onFinish: () -> Unit = {}

) {

val scope = rememberCoroutineScope()

val angle = remember { Animatable(0f) }

var isRunning by remember { mutableStateOf(false) }

// 对外暴露的控制器

val controller = remember {

object : SpinnerController {

override fun start() {

if (isRunning) return

isRunning = true

val remaining = 360f - angle.value

val remainingTime = (remaining / 360f * 6000).toLong().coerceAtLeast(0L)

scope.launch {

angle.animateTo(

targetValue = 360f,

animationSpec = tween(remainingTime.toInt(), easing = LinearEasing)

)

isRunning = false

onFinish()

}

}

override fun pause() {

if (!isRunning) return

angle.stop()

isRunning = false

}

override fun reset() {

angle.stop()

scope.launch { angle.snapTo(0f) }

isRunning = false

}

override val currentAngle: Float get() = angle.value

override val running: Boolean get() = isRunning

}

}

// 真正 UI

CircularProgressIndicator(

progress = 1f, // 始终画满环

modifier = modifier

.size(size)

.rotate(angle.value),

strokeWidth = strokeWidth,

color = color,

strokeCap = strokeCap

)

// 把控制器通过 CompositionLocal 暴露(可选)

SpinnerControllerProvider(controller) {

// 空容器,仅用于携带 LocalSpinnerController

}

}

/* -------------------- 控制接口 -------------------- */

interface SpinnerController {

fun start()

fun pause()

fun reset()

val currentAngle: Float

val running: Boolean

}

/* -------------------- CompositionLocal 方便获取 -------------------- */

val LocalSpinnerController = staticCompositionLocalOf<SpinnerController?> { null }

@Composable

private fun SpinnerControllerProvider(

controller: SpinnerController,

content: @Composable () -> Unit

) {

CompositionLocalProvider(LocalSpinnerController provides controller) {

content()

}

}

/* -------------------- 使用示例 -------------------- */

@Composable

fun DemoScreen() {

Column(horizontalAlignment = Alignment.CenterHorizontally) {

// 直接拿来用

val controller = LocalSpinnerController.current!!

SingleTurnSpinner(

size = 120.dp,

strokeWidth = 6.dp,

color = MaterialTheme.colorScheme.primary

)

Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {

Button(onClick = { controller.start() }) { Text("开始") }

Button(onClick = { controller.pause() }) { Text("暂停") }

Button(onClick = { controller.reset() }) { Text("重置") }

}

}

}

​​​​

相关推荐
一笑的小酒馆10 小时前
Android中WebRTC通信
android
KevinWang_13 小时前
Android Hilt 依赖注入
android
古法安卓13 小时前
Android-深入理解 Android 回调(Callback)机制
android·性能优化·android studio
古法安卓13 小时前
Android-车机 GNSS 定位数据接收问题排查
android·java·android studio
FungLeo15 小时前
Flutter 吸顶分组列表实战:语义桶分组 + 点击头平滑滚动
android·flutter
阿巴斯甜16 小时前
Android 自定义权限
android
FungLeo17 小时前
Flutter 超长 StatefulWidget 拆分术:part of + extension on State 实战
android·flutter·dart
小王C语言18 小时前
MySQL 数据类型:数值类型、字符串类型、日期和时间类型、enum 和 set、find_in_set 查询
android·数据库·mysql
我命由我1234518 小时前
Android 控件 - CardView(快速实现圆角)
android·java·java-ee·kotlin·android studio·android-studio·android runtime
码云数智-园园18 小时前
Android 全方位性能优化合集
android·性能优化