开启成长之旅!这是我参加「日新计划 2 月更文应战」的第 2 天,点击检查活动详情。

缘起

2.14 是众所周知的节日,很显然,与我无关,甚至垃圾桶里的二手玫瑰都被大佬们抢啦。

Jetpack Compose 实现鸿蒙引力加载 Loading

可是!!!我去看了流浪地球2,有一说一,国产科幻起飞!电影里未来程序员都赋闲啦,一个量子计算机都能实时生成系统啦(更甭说程序了)。正在我担心300年后的程序员孙辈们还能不能端起咱们得饭碗时,看到了这个(剧透警告):

Jetpack Compose 实现鸿蒙引力加载 Loading

咱地球的仅有卫星–月球被 big mama炸了。

所以,小小安卓写一个赛博月球以纪念她!

开始

这是鸿蒙的引力加载动作用,一个小卫星绕着一个上下移动的主星在公转,似乎很简单:

Jetpack Compose 实现鸿蒙引力加载 Loading

Step 1 : 制作主星

Compose 给咱们供给了丰富的 canvas 制作 api ,制作一个圆不多说:

Canvas(
    modifier = Modifier
        .align(Alignment.Center)
) {
    drawCircle(
        color = Color.Gray, radius = 50f, style = Stroke(width = 10f)
    )
}

Jetpack Compose 实现鸿蒙引力加载 Loading

然后给主星添加上下移动动画,咱们需求运用无限循环动画来完结,运用 infinitRepeatable 轻轻松松:


val infiniteTransition = rememberInfiniteTransition()
val offsetY by infiniteTransition.animateFloat(
    initialValue = 0f,
    targetValue = 5f,
    animationSpec = infiniteRepeatable(
        animation = keyframes {
            durationMillis = 1500
            0f at 0
            10f at 725
            0f at 1500
        }
    )
)
Canvas(
    modifier = Modifier
        .align(Alignment.Center)
        .offset(y = LocalDensity.current.run { offsetY.toDp() }) // 运用动画
) {
        ...
  }

看看作用:

Jetpack Compose 实现鸿蒙引力加载 Loading

Step 2 : 制作卫星

观察原型图,可以发现卫星的运动轨道是环绕一个椭圆进行旋转往复运动,这样在视觉上可以以后立体的感觉。如图:

Jetpack Compose 实现鸿蒙引力加载 Loading

运用 Path 描述椭圆途径

如果需求让方针沿着一个特殊的轨道进行运动的话,运用 path 动画仍是比较便利的。

  Canvas(
        modifier = Modifier.align(Alignment.Center)
    ) {
        val path = Path()
        path.addOval(Rect(-75f, -35f, 75f, 35f))
    rotate(-20f) {
                // *坑点1,需求旋转画布来完结斜向椭圆
                }
        }
    }
}

坑点 1 出现 :制作 api 只能制作横着的或者竖着的椭圆(椭圆的两个定点在x/y轴上),不能制作斜的,咱们需求经过几许变换来完结

在椭圆 path 上制作卫星

现在咱们已经有了斜向椭圆的 path , 接着经过一个奇特的东西PathMeasure,咱们可以的到 path 的有用信息。

pathMeasuregetPosTan 方法是用来获取坐标点的,咱们供给两个数组分别保存postan

其间pos参数是获取Path指定distance方位的坐标点,也是就指图中的A点,而tan参数是指圆心点:

Jetpack Compose 实现鸿蒙引力加载 Loading


val animatePer by infiniteTransition.animateFloat(initialValue = 0f,
    targetValue = 1f,
    animationSpec = infiniteRepeatable(animation = keyframes {
        durationMillis = 1300
        0f at 0
        1f at 1300
    }
    ))
val pathMeasure = android.graphics.PathMeasure()
val pos = FloatArray(2)
val tan = FloatArray(2)
path.addOval(Rect(-75f, -35f, 75f, 35f))
rotate(-20f) {
    pathMeasure.setPath(path.asAndroidPath(), true) // * 坑点2 compose下PathMeasure并没有获取方位的方法
    pathMeasure.getPosTan(pathMeasure.length * animatePer, pos, tan)
    ...
    }

坑点 2 :由于在composePathMeasure并没有获取方位的方法,所以咱们运用android.graphics.PathMeasure(),然后path也需求转换成 android.graphics.Path

一起,咱们预备一个控制进展的动画,运用pathMeasure.length * animatePer动态获取到整个path的一切点的坐标。

测验1:drawPoint来完结

在咱们已经得到path上得一切坐标之后,咱们将坐标变成一个drawpoint的坐标来制作出来,composedrawpoint比较费事,这儿咱们运用原生的canvas来制作更加便利:

val paint = Paint().asFrameworkPaint().apply {
    isAntiAlias = true
    strokeWidth = 25f
   color = android.graphics.Color.GRAY
    strokeCap = android.graphics.Paint.Cap.ROUND
    style = android.graphics.Paint.Style.STROKE
}
// 经过 drawIntoCanvas可以获取原生的 canvas
drawIntoCanvas {
    it.nativeCanvas.drawPoint(pos[0], pos[1], paint)
}

Jetpack Compose 实现鸿蒙引力加载 Loading

可以看到,已经有个模样啦,可是仔细观察原素材,可以发现卫星并不是一个简单的点,而且有一个拖尾作用:

Jetpack Compose 实现鸿蒙引力加载 Loading

测验2:drawArc完结

咱们可以沿着path制作一段弧形,并给弧形添加渐变来模仿卫星的拖尾作用,同样的这儿用到了原生的canvas api ( 用compose的也可以 )。

别的为了避免内存抖动,咱们运用 drawWithCachepath 对象的创立放在其间

有些时分咱们制作一些比较复杂的UI作用时,不期望当 Recompose 发生时一切绘画所用的一切实例都从头构建一次(类似Path),这可能会产生内存抖动。在 Compose 中咱们一般可以想到运用 remember 进行缓存,但是咱们所制作的作用域是 DrawScope 并不是 Composable,所以无法运用 remember,那咱们该怎么办呢?drawWithCache 供给了这个能力。— 摘录与社区大佬们维护的 compose 博物馆

val paint = Paint().asFrameworkPaint()
Spacer(modifier = Modifier
    .align(Alignment.Center)
    .drawWithCache {
        val path = Path()
        val pathMeasure = android.graphics.PathMeasure()
        val pos = FloatArray(2)
        val tan = FloatArray(2)
        onDrawBehind {
            path.addOval(Rect(-75f, -35f, 75f, 35f))
            rotate(-20f) {
                pathMeasure.setPath(path.asAndroidPath(), true)
                pathMeasure.getPosTan(pathMeasure.length * animatePer, pos, tan)
                paint.apply {
                    isAntiAlias = true
                    strokeWidth = 25f
                    strokeCap = android.graphics.Paint.Cap.ROUND
                    style = android.graphics.Paint.Style.STROKE
                    shader = android.graphics.RadialGradient(
                        pos[0],
                        pos[1],
                        80f,
                        android.graphics.Color.parseColor("#666466"),
                        android.graphics.Color.parseColor("#e5e3e5"),
                        Shader.TileMode.CLAMP
                    )
                }
                drawIntoCanvas {
                    it.nativeCanvas.drawArc(
                        RectF(-80f, -20f, 80f, 20f),
                        (atan2(pos[1], pos[0])) * 180 / PI.toFloat(),
                        20f,
                        false,
                        paint
                    )
                }
            }
        }
    })

坑点 3 :制作圆弧需求用到角度知识,由于咱们已经经过pathMeasure得到了椭圆上的点的x,y,所以咱们可以经过反三角函数获取 startAngle,而且留意需求运用 atan2 而不是 atan

完结,看看作用!

Jetpack Compose 实现鸿蒙引力加载 Loading

总结

原本是作业完结之后,无聊找个玩具练练手,可是实际上手之后,才发现完结进程并没有自己想象的那么简单,原本打算1~2h 完结之后等下班,结果下班的时分才完结了drawpoint 那个版别,直到第二天又敲了几个小时才完结,而且作用也差强人意。所以有些时分仍是不要想当然觉得So easy,仍是需求认真去编码。

参考资料

android高档UI之PathMeasure

Android动画 – PathMeasure打造不一样的动画