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

}

}

}

​​​​

相关推荐
疯华整猫4 小时前
否决 Flutter 提案那天,我发现自己也说不清 Framework 管什么
android
传奇开心果编程16 小时前
【Flutter踩坑排雷】第25课:ScrollController 误用坑|跳转失效、监听泄漏与“控制器未附加”
android·flutter·ios·移动开发·跨平台·dart·声明式ui
慕容雪_20 小时前
Capacitor8-Android打包完整手册
android·打包·capacitor
阿杰m1 天前
Android源码第十三章:Bluetooth——沿一次发现走到控制器
android
booboosui1 天前
派活给另一个 AI:前后两次文件夹快照,就是交付清单
android
普通人61 天前
MySQL_FIND_IN_SET函数详解
android·数据库·mysql
好的佩奇2 天前
Dart 之集合元素
android·flutter·dart
Easyhood2 天前
Android 开发入门实践(十四):Compose 副作用入门与四个常用 API
android·开发语言·kotlin·android jetpack·compose
FlightYe2 天前
音视频修炼之容器与协议(四):RTSP详解
android·linux·ffmpeg·音视频·dash
疯华整猫2 天前
删掉 LeakCanary 那天,我用 Trae 自己撸了一套:最难的不是分析堆,是决定什么时候 dump
android·人工智能·性能优化