首页 > 其他分享 >实战!如何在 Jetpack Compose 中拥有一个与众不同的 Modifier

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

时间:2023-06-19 11:32:01浏览次数:43  
标签:动画 Compose toPx Jetpack width 虚线 Modifier dp


步入正题!

相信大家既然已经学习了Compose,那想必也非常熟悉如何使用 Modifer 了,由于Compose 被Android 团推设计的非常容易上手,所以有不了解如何使用的朋友可以去看看 文档,即可轻松掌握基础的使用!

拥有一个与众不同的Modifier,其实就是实现一个特别功能的Modifier,然后使用它去修饰我们的Composable可组合函数,来实现我们的特殊需求。

下面,我们就通过代码一步一步来实战一个特别功能的 Modifier , 相信如果跟着过一遍的话,基本上也就掌握了自定义 Modifier的知识。

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

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

既然是添加边框,想当然直接用 Modifier.border

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

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

1.2 绘制虚线

边框并不属于Composable内容部分提供的,所以我们要把它绘制出来,然后依附在Composable内容的边上。我们仿照Modifier.border,使用Modifier.drawXxx来实现一个。

在 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_Android_02

分析,如果想要边框动转起来,我们应该找到能够引发边框沿着我们的四个边转动的角色,有自定义view经验的朋友估计已经知道了,那就是 dashPathEffectphase

phase,虚线的偏移量,说白了就是虚线开端偏移起点的距离。

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

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

文字有点抽象,我们结合示意图来分析,我们用实线代表虚线中的线,用虚线代表虚线中的空白,当线段从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_android_04

加餐

让动画更加完美,增加动画的暂停与继续

思考:有时候,我们并不希望动画一直在那里播放,那么如何控制动画的暂停与恢复呢?

其实这里的已经偏向于动画啦,感兴趣的可以继续阅读

根据我的学习,很不幸,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_android_05

总结

自定义Modifier,在Compose中是一个很有用的知识点,不少前辈大佬们利用它来实现了许许多多实用的功能,比如骨架屏,比如自由滚动。希望大家能掌握它,然后创造分享出更多的便利代码库,这样,就可以更早下班啦~

标签:动画,Compose,toPx,Jetpack,width,虚线,Modifier,dp
From: https://blog.51cto.com/u_16163442/6512062

相关文章

  • 谷歌内部流出Jetpack Compose最全上手指南,含项目实战演练!
    简介JetpackCompose是在2019Googlei/O大会上发布的新的库。Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速度。Compose并不是像RecyclerView、ConstraintLayout这种做了一个或者几个高级的UI控件,而是直接抛弃了我们写了N年......
  • 从入门到精通,Android Jetpack 架构实战教程合集
    Jetpack是Google推出的一些库的集合,包含组件、工具、架构方案等,其优势众多:可以减少空指针异常崩溃、内存泄漏,为开发出健壮且流畅的程序提供强力保障;可以消除大量重复样板式的代码,加速Android的开发进程;可以统一开发模式,抛弃传统的MVC,MVP…对于谷歌而言,AndroidJetpack是他......
  • Jetpack系列-Lifecycle使用和源码分析
    1简介和简单使用1.1简介Lifecycle是Jetpack中一个生命周期感知型组件,可执行操作来响应另一个组件(如Activity和Fragment)的生命周期状态的变化。该组件通过感知Activity和Fragment的生命周期事件,在内部维护一个状态,该状态又可以转换成生命周期事件。主要作用就是进行系统组件......
  • Compose 状态保存:rememberSaveable 原理分析
    前言我曾经在一篇介绍ComposeNavigation的文章中提到了Navigation的状态保存实际是由rememberSaveable实现的,有同学反馈希望单独介绍一下rememberSaveable的功能及实现原理。我们都知道remember可以保存数据、避免状态因重组而丢失,但它依然无法避免在ConfigurationCha......
  • Jetpack系列-Room+ViewModel+LiveData+ViewBinding实现MVVM
    Room能和LiveData很好的结合实现MVVM,Room可以利用LiveData的观察者模式,感知Lifecyle的状态,实现数据驱动UI,避免MVP模式下更新UI需要大量回调接口的繁琐。下面整合Room、ViewModel、LiveData、ViewBinding,实现一个简单的MVVM示例项目。1引入依赖引入ViewModel依赖:dependencies{......
  • 通过 docker-compose 快速部署 Apache Ambari 保姆级教程
    目录一、概述二、前期准备1)部署docker2)部署docker-compose三、ApacheAmbari编排部署1)获取Ambari安装包2)yum源配置文件3)用户和库初始化sql文件4)启动脚本bootstrap.sh5)修改源码6)构建镜像Dockerfile7)配置hosts8)编排docker-compose.yaml9)开始部署10)检测五、通过ApacheAmb......
  • docker compose启动目录
    目的对于使用dockercompose启动的容器,我们可以使用命令诊断其启动的目录。 方法song@song-VirtualBox:~$dockerpsCONTAINERID  IMAGE                            COMMAND                 CREATED      STA......
  • docker-compose构建kratos微服务项目运行失败,提示:runtime/cgo: pthread_create failed
    这个问题网上解决方案较少,我们这边问题定位是docker-compose.yaml配置问题在配置文件中新增配置如下:privileged:true设置容器的权限为root 最后解决......
  • 浅谈 thinkphp composer 扩展包加载原理
    浅谈thinkphpcomposer扩展包加载原理本文将介绍ThinkPHP中Composer扩展包的加载原理,帮助读者更好地理解和应用该功能。前言如题,今天感觉好久没有更新博客了。最近迷上了物联网开发。一直在研究stm32、51这些东西。想起来前几天群里面有人问到tp扩展包原理。其实这个前......
  • centos7安装docker-compose插件
    引介docker-compose是docker的一款插件,常用来定义和运行多容器的docker应用。本篇文章就来介绍一下,如何在centos7的linux系统中安装docker-compose插件。安装方式docker-compose安装方式的主要可以分为两种,一是使用docker仓库进行安装,而是手动方式安装。使用docker仓库安装使......