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("重置") }

}

}

}

​​​​

相关推荐
建群新人小猿26 分钟前
陀螺匠企业助手-我的日程
android·大数据·运维·开发语言·容器
_李小白27 分钟前
【Android FrameWork】第三十九天:DeviceStorageManagerService
android
不急不躁1231 小时前
Android16 给应用默认获取权限
android·java
用户41659673693552 小时前
拒绝 Race Condition:深入理解 StateFlow 的取值与更新
android
青莲8432 小时前
Kotlin Flow 深度探索与实践指南——上部:基础与核心篇
android·前端
恋猫de小郭2 小时前
2025 年终醒悟,AI 让我误以为自己很强,未来程序员的转型之路
android·前端·flutter
2501_915918413 小时前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张3 小时前
IOScer 开发环境证书包括哪些,证书、描述文件与 App ID 的协同管理实践
android·ios·小程序·https·uni-app·iphone·webview
aningxiaoxixi4 小时前
android AV 之 SimpleC2Component
android
TAEHENGV5 小时前
导入导出模块 Cordova 与 OpenHarmony 混合开发实战
android·javascript·数据库