compose UI(四)图形与动画

本文示例代码API基于compose UI 1.0.0-bate08

图形

官网说明
官网说明比较简单,一共2个Canvas和DrawScope。

Canvas

Canvas 是一个封装过的对象,点开源码其实就是一个Spacer:

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))

Canvas 封装了Spacer在它的约束条件上drawBehind(),入参就是Canvas的 DrawScope.() -> Unit。
引申一下,Modifier接口都可以drawBehind(DrawScope.() -> Unit),Canvas用Spacer封装是因为Spacer是一个无布局的Composable。所以,我们可以在任何的Modifier接口后面使用drawBehind函数,达到绘制背景的效果。比如: Text

Text(text = "markrenChina",modifier = Modifier
        .size(100.dp)
        .drawBehind {
            drawRect(color = Color(0xFF00FF00))
        })

drawRect可能跟Modifier.background没什么区别,大家可以换成drawImage,drawCircle 等,或者换成一个复杂的背景,想象空间非常大。

DrawScope

这个是图形的关键,androidx.compose.ui.graphics.drawscope包下提供了很多已经封装过的函数,如果有java Canvas Painter等知识,上手会比较快一点。自学java时写的一个游戏,全程painter画面
我们举例一个自定义背景,屏幕一点是长方形,所以用drawRect,Brush 参数,背景我们用三个颜色渐变(orange,lightPink,lightPurple):

//去掉了Canvas封装,如果全屏背景,不要放在任何布局下面
Spacer(
        Modifier
            .fillMaxSize()
            .drawBehind {
                val brushBackground = Brush.verticalGradient(
                    listOf(orange, lightPink, lightPurple),
                    0f,
                    size.height.toDp().toPx(),
                    TileMode.Mirror
                )
                drawRect(brush = brushBackground)
            })

效果图:
渐变色效果图
更复杂的效果我们结合动画来讲。

动画

官网说明

官网示例要像看效果,举其中Animatable示例,我们可以这样:

		var ok by remember { mutableStateOf(false) }
        val color = remember { Animatable(Color.Gray) }
        //利用协程延时2秒,死循环改变
        LaunchedEffect(ok) {
            color.animateTo(if (ok) Color.Green else Color.Red)
            delay(2000)
            ok = !ok
        }
        Box(
            Modifier
                .size(100.dp)
                .background(color.value)
        )

自定义动画

通过自定义animate*AsState 的AnimationSpec来实现。
具体看官方介绍,简单举例,我们用infiniteRepeatable来改写上面的举例,同样达到死循环的效果,并且整加了动画渐变。

	val infiniteTransition = rememberInfiniteTransition()
    val value by infiniteTransition.animateColor(
        initialValue = Color.Red,
        targetValue = Color.Green,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 2000,easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    Box(
        Modifier
            .size(100.dp)
            .background(value)
    )

结合Canvas ,我们作一些有趣的动画,比如这样:
逆时针动画
非实心圆,我们用到DrawScope的drawArc() 这个函数有一句说明很重要@param startAngle Starting angle in degrees. 0 represents 3 o’clock,起始位置是3点钟方向,绘制方式是drawn clockwise (顺时针)。
首先,我们先用自定义协程(比较好理解),我们需要一个progress进度状态,一个协程去改变这个状态。然后Canvas把自定义圆圈画出来。因为本身是顺时针效果做逆时针,需要从满圈往0画。

var progress by remember { mutableStateOf(360f) }
LaunchedEffect(key1 = progress) {
	//死循环放开注释
  	//if (progress <= 0F) { progress = 360F }
  	 delay(100)
  	 progress -= 1F
}
Canvas(
      modifier = Modifier
           .padding(80.dp)
           .size(180.dp)
   ) {
   	// 背景图示中的淡黑色,如果没有,就是白色
       drawArc(
           brush = SolidColor(Color.Black.copy(alpha = 0.2f)),
           startAngle = 90F,
           //满圈
           sweepAngle = 360F,
           useCenter = false,
           style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
       )
       drawArc(
           brush = SolidColor(orange),
           //从12点钟方向开始
           startAngle = 270F,
           //进度 360F -> 0F
           sweepAngle = progress,
           useCenter = false,
           style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
       )
   }

试着引入animateFloatAsState()平滑动画效果,为跟上面有区别,我们放大progress 变化率为1秒10F,并且引入布局方便同时查看2种效果:

   var progress by remember { mutableStateOf(360f) }
   val animateCurrentProgress by animateFloatAsState(
       targetValue = progress,
       animationSpec = tween(durationMillis = 1000, easing = LinearEasing)
   )
   LaunchedEffect(key1 = progress) {
       if (progress <= 0F) { progress = 360F }
       delay(1000)
       progress -= 10F
   }
   Row {
       Canvas(
           modifier = Modifier
               .padding(40.dp)
               .size(180.dp)
       ) {
           drawArc(
               brush = SolidColor(Color.Black.copy(alpha = 0.2f)),
               startAngle = 90F,
               sweepAngle = 360F,
               useCenter = false,
               style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
           )
           drawArc(
               brush = SolidColor(orange),
               startAngle = 270F,
               sweepAngle = progress,
               useCenter = false,
               style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
           )
       }
       Canvas(
           modifier = Modifier
               .padding(40.dp)
               .size(180.dp)
       ) {
           drawArc(
               brush = SolidColor(Color.Black.copy(alpha = 0.2f)),
               startAngle = 90F,
               sweepAngle = 360F,
               useCenter = false,
               style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
           )
           drawArc(
               brush = SolidColor(orange),
               startAngle = 270F,
               //观察animateCurrentProgress 更平滑
               sweepAngle = animateCurrentProgress,
               useCenter = false,
               style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
           )
       }
   }

效果如下:
动画对比图
简单放入一个Text就是一个倒计时(计时器用delay是不科学的,这里只做示例):

	var progress by remember { mutableStateOf(360f) }
    var timer by remember { mutableStateOf(36) }
    val animateCurrentProgress by animateFloatAsState(
        targetValue = progress,
        animationSpec = tween(durationMillis = 1000, easing = LinearEasing)
    )
    LaunchedEffect(key1 = progress) {
        if (progress <= 0F) {
            progress = 360F
            timer = 36
        }
        delay(1000)
        progress -= 10F
        timer -= 1
    }
    Box(
        modifier = Modifier
            .padding(40.dp)
            .size(180.dp)
    ) {
        Canvas(Modifier.fillMaxSize()) {
            drawArc(
                brush = SolidColor(Color.Black.copy(alpha = 0.2f)),
                startAngle = 90F,
                sweepAngle = 360F,
                useCenter = false,
                style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
            )
            drawArc(
                brush = SolidColor(orange),
                startAngle = 270F,
                sweepAngle = animateCurrentProgress,
                useCenter = false,
                style = Stroke(width = 20.dp.value, cap = StrokeCap.Round)
            )
        }
        Box(modifier = Modifier.align(Alignment.Center)) {
            Text(text = timer.toString())
        }
    }

效果图:
倒计时效果
再回到我们的自定义背景项目,我们引入动画去改变背景色:

	val infiniteTransition = rememberInfiniteTransition()
    val animationSpec: InfiniteRepeatableSpec<Color> = infiniteRepeatable(
        animation = tween(3000, easing = FastOutSlowInEasing),
        repeatMode = RepeatMode.Reverse
    )
    val colorFirst by infiniteTransition.animateColor(
        initialValue = orange,
        targetValue = lightPink,
        animationSpec = animationSpec
    )
    val colorSecond by infiniteTransition.animateColor(
        initialValue = lightPink,
        targetValue = lightPurple,
        animationSpec = animationSpec
    )
    val colorThird by infiniteTransition.animateColor(
        initialValue = lightPurple,
        targetValue = orange,
        animationSpec = animationSpec
    )
    Spacer(
        Modifier
            .fillMaxSize()
            .drawBehind {
                val brushBackground = Brush.verticalGradient(
                    listOf(colorFirst, colorSecond, colorThird),
                    0f,
                    size.height
                        .toDp()
                        .toPx(),
                    TileMode.Mirror
                )
                drawRect(brush = brushBackground)
            })

效果图:
背景动画渐变
再做一些有趣的事情,比如再背景上加入冒泡效果:
冒气泡效果
这个效果可以继续优化,比如加入碰撞检测合并气泡,加入根据大小线性拟合出速度!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值