我正在参加「启航计划」

前语

前不久,在Stack Overflow上用自己半瓢水的动画常识,协助发问者处理了一个问题 的一起,看到许多眼生的办法,比方composed,于是趁便学习了一波自定义 Modifier。回过头来总结时,发现处理此问题的过程,非常适合作为一个事例去由浅入深的把握自定义Modifier~

步入正题!

相信咱们已然现已在学习Compose了,那想必也非常熟悉怎么运用 Modifer 了,由于Compose 被Android 团推规划的非常简单上手,所以有不了解怎么运用的朋友能够去看看 文档 ,即可轻松把握根底的运用!

拥有一个与众不同的Modifier,其实便是完结一个特别功能的Modifier,然后运用它去润饰咱们的Composable可组合函数,来完结咱们的特别需求。

下面,咱们就经过代码一步一步来实战一个特别功能的 Modifier , 相信假如跟着过一遍的话,基本上也就把握了自定义 Modifier的常识。

1.1 需求,给 Composeable 增加虚线边框

实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

已然是增加边框,想当然直接用 Modifier.border

fun Modifier.border(width: Dp, brush: Brush, shape: Shape): Modifier

可是,自带的border()供给了边框宽度,边框颜色,边框形状,但并没有一个设置 “虚线” 的参数给咱们,没办法要么等候官方猴年马月之后更新支撑,要么自己着手,丰衣足食 DIY 一个来用,岂不美哉!

1.2 制作虚线

边框并不属于Composable内容部分供给的,所以咱们要把它制作出来,然后依附在Composable内容的边上。咱们仿照Modifier.border,运用Modifier.drawXxx来完结一个。

关于Compose draw能够看这儿运用 Jetpack Compose 完结自定义制作

在 View 中,需求制作虚线时,咱们会用到DashPathEffect来完结林林总总的虚线,相同,在ComposePathEffect.dashPathEffect 用法基本保持一致

@Composable
fun ShowCard() {
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier
                .width(160.dp)
                .height(50.dp)
                .padding(2.dp)
                .drawBehind {
                    // 制作圆角矩形,能够满意圆角边框需求
                    drawRoundRect(
                            color = Color.Black,
                            style = Stroke(
                                width = 1f,
                                pathEffect = PathEffect.dashPathEffect(
                                intervals = floatArrayOf(20f, 20f),
                                phase = 0f
                             )
                            )
                        )
                    }
        ) {
            Text(text = "看看四周的框框")
        }
    }

intervalsphase,分别用来操控虚线的距离,以及偏移量。

到这儿,假如只是为了某一个 Composable 增加虚线边框的话,现已初步满意意图。可是,咱们还想要把这个作用独立出来

1.3 抽取为自定义 Modifier

其实Android供给的自带modifer ,比方 size() padding() 等等,都是经过拓展函数的办法来完结链式调用。

创建一个拓展函数,一起咱们提高一下可装备性,将一些特点作为办法参数,并供给圆角巨细,丰富一下功能:

fun Modifier.dashBorder(、
   color: Color = Color.Black,
   width: Dp = 1.dp,
   cornerRadiusDp: Dp = 0.dp,
   dashLength:Dp,
) = drawBehind {
   drawRoundRect(
       color = color,
        style = Stroke(
           width = width.toPx(),
           pathEffect = PathEffect.dashPathEffect(
               // 简单起见,让空白和线段的长度相同
               intervals = floatArrayOf(dashLength.toPx(),dashLength.toPx()),
               phase = 0f
           )
       ),
       cornerRadius = CornerRadius(cornerRadiusDp.toPx())
   )
}
// 运用
   Box(
        contentAlignment = Alignment.Center,
           modifier = Modifier
               .width(160.dp)
               .height(50.dp)
               .padding(2.dp)
               .dashBorder(
                   width = 1.dp,
                   intervals = 5.dp,
                   cornerRadiusDp = 5.dp
               )
       ) {
           Text(text = "看看四周的框框")
       }

Compose中咱们一般运用Dp作为屏幕显示单位,所以咱们暴露办法参数最好运用Dp,在制作时,运用dp.toPx() 即可, 另外,建议供给默许参数值,让代码更简练。 到此,咱们的自定义Modifier现已完结了

  • 可设置宽度的边框
  • 可设置虚线的长度
  • 可增加圆角 完美!

2.1 怎么让边框动起来?

其实,假如是针对需求的话,咱们的Modifier现已完结,可是,为了更好的学习,咱们更进一步——让咱们的虚线边框转动起来!

实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

剖析,假如想要边框动转起来,咱们应该找到能够引发边框沿着咱们的四个边转动的人物,有自定义view经验的朋友估量现已知道了,那便是 dashPathEffectphase

phase,虚线的偏移量,说白了便是虚线初步偏移起点的距离。

动画原理:让虚线偏移一个完好虚线长度(包括线段和空白),然后restart,这样在视觉上看,便是一个无限延伸的线啦!

实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

文字有点笼统,咱们结合示意图来剖析,咱们用实线代表虚线中的线,用虚线代表虚线中的空白,当线段从A偏移到B时,咱们让动画 Restart,这样又会从A'偏移到B',如此不断Restart,完结无限转动作用。

2.2 运用 ComposedModifier 给边框增加动画

Compose中,动画给状况的改变供给丝滑的过渡作用,不可避免的,在 Modifier 中假如需求运用状况 api (remember),要用到 ComposedModifier 来为咱们供给一个带有状况的 Modifier

fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

运用拓展函数composed即可拿到一个ComposedModifier

fun Modifier.dashBorder(
    color: Color = Color.Black,
    width: Dp = 1.dp,
    dashLength:Dp,
    cornerRadiusDp: Dp = 0.dp,
) = composed {
    // 不在drawScope 中,无法直接运用 dp.toPx()
    val density = LocalDensity.current
    val dashLengthPx = density.run { dashLength.toPx() }
    // 声明一个无限循环动画
    val infinite = rememberInfiniteTransition()
    val anim by infinite.animateFloat(initialValue = 0f,
        targetValue = dashLengthPx*2,//偏移一个完好长度
        animationSpec = infiniteRepeatable(
            animation = tween(1000, easing = LinearEasing),
            repeatMode = RepeatMode.Restart // 动画循环模式为 restart
        ) )
    drawBehind {
        drawRoundRect(
            color = color,
            style = Stroke(
                width = width.toPx(),
                pathEffect = PathEffect.dashPathEffect(
                    intervals = floatArrayOf(dashLength.toPx(),dashLength.toPx()),
                    phase = anim // 动画应用
                )
            ),
            cornerRadius = CornerRadius(cornerRadiusDp.toPx())
        )
    }
}

看看作用,至此,咱们成功的让边框动起来啦:

实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

加餐

让动画愈加完美,增加动画的暂停与持续

考虑:有时候,咱们并不期望动画一直在那里播映,那么怎么操控动画的暂停与恢复呢?

其实这儿的现已偏向于动画啦,感兴趣的能够持续阅读

依据我的学习,很不幸,compose 并没有供给直接操控动画暂停与持续的api , 可是咱们能够开动一下思想,变通一下,完结暂停与持续的等价作用。

  • 首要,咱们需求运用更底层的apiAnimatable,来设置每次动画的起始值。
  • 然后咱们增加一个参数animate来操控动画是否播映, 新增一个状况lastAnimValue来记录前次动画的完毕值,而且动画的目标值也需求额定加上lastAnimValue,确保动画每次偏移是一个完好长度。
  • 再次开端动画时,将lastAnimValue 作为本次动画的起始值。
fun Modifier.dashsBorder(
    color: Color = Color.Black,
    width: Dp = 1.dp,
    dashLength:Dp,
    cornerRadiusDp: Dp = 0.dp,
    animate: Boolean = true
) = composed {
    var lastAnimValue by remember { mutableStateOf(0f) }
    val anim = remember(animate) { Animatable(lastAnimValue) }
    val density = LocalDensity.current
    val dashLengthPx = density.run { dashLength.toPx() }
    LaunchedEffect(animate) {
        if (animate) {
            anim.animateTo(
             (dashLengthPx * 2 + lastAnimValue),
                animationSpec =
                infiniteRepeatable(
                    animation = tween(1000, easing = LinearEasing),
                    repeatMode = RepeatMode.Restart,
                )
            ) {
                lastAnimValue = value // store the anim value
            }
        }
    }
    drawBehind {
        drawRoundRect(
            color = color,
            style = Stroke(
                width = width.toPx(),
                pathEffect = PathEffect.dashPathEffect(
                    intervals = floatArrayOf(dashLength.toPx(),dashLength.toPx()),
                    phase = anim.value
                )
            ),
            cornerRadius = CornerRadius(cornerRadiusDp.toPx())
        )
    }
}

看看作用,完美:

实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

总结

自定义Modifier,在Compose中是一个很有用的常识点,不少长辈大佬们利用它来完结了许许多多有用的功能,比方骨架屏,比方自由滚动。期望咱们能把握它,然后发明共享出更多的便利代码库✈️,这样,就能够更早下班啦~

参考资料

android – How to have dashed border in Jetpack Compose? – Stack Overflow

android – How to pause/resume a jetpack compose infinite transition animation – Stack Overflow