我为 Compose 写了一个波浪效果的进度加载库,API 的设计上符合 Compose 的开发规范,使用非常简便。

1. 使用方式

在 root 的 build.gradle 中引入 jitpack

allprojects {repositories {...maven { url 'https://jitpack.io' }}
}

在 module 的 build.gradle 中引入 ComposeWaveLoading 的最新版本

dependencies {implementation 'com.github.vitaviva:ComposeWaveLoading:$latest_version'
}

2. API 设计思想


Box {WaveLoading (progress = 0.5f // 0f ~ 1f) {Image(painter = painterResource(id = R.drawable.logo_tiktok),contentDescription = "")}
}

传统的 UI 开发方式中,设计这样一个波浪控件,一般会使用自定义 View 并将 Image 等作为属性传入。 而在 Compose 中,我们让 WaveLoadingImage 以组合的方式使用,这样的 API 更加灵活,WaveLoding 的内部可以是 Image,也可以是 Text 亦或是其他 Composable。波浪动画不拘泥于某一特定 Composable, 任何 Composable 都可以以波浪动画的形式展现, 通过 Composable 的组合使用,扩大了 “能力” 的覆盖范围。

3. API 参数介绍

@Composable
fun WaveLoading(modifier: Modifier = Modifier,foreDrawType: DrawType = DrawType.DrawImage,backDrawType: DrawType = rememberDrawColor(color = Color.LightGray),@FloatRange(from = 0.0, to = 1.0) progress: Float = 0f,@FloatRange(from = 0.0, to = 1.0) amplitude: Float = defaultAmlitude,@FloatRange(from = 0.0, to = 1.0) velocity: Float = defaultVelocity,content: @Composable BoxScope.() -> Unit
) { ... }

参数说明如下:

参数 说明
progress 加载进度
foreDrawType 波浪图的绘制类型: DrawColor 或者 DrawImage
backDrawType 波浪图的背景绘制
amplitude 波浪的振幅, 0f ~ 1f 表示振幅在整个绘制区域的占比
velocity 波浪移动的速度
content 子Composalble

接下来重点介绍一下 DrawType

DrawType

波浪的进度体现在前景(foreDrawType)和后景(backDrawType)的视觉差,我们可以为前景后景分别指定不同的 DrawType 改变波浪的样式。

sealed interface DrawType {object None : DrawTypeobject DrawImage : DrawTypedata class DrawColor(val color: Color) : DrawType
}

如上,DrawType 有三种类型:

  • None: 不进行绘制
  • DrawColor:使用单一颜色绘制
  • DrawImage:按照原样绘制

以下面这个 Image 为例, 体会一下不同 DrawType 的组合效果

index backDrawType foreDrawType 说明
1 DrawImage DrawImage 背景灰度,前景原图
2 DrawColor(Color.LightGray) DrawImage 背景单色,前景原图
3 DrawColor(Color.LightGray) DrawColor(Color.Cyan) 背景单色,前景单色
4 None DrawColor(Color.Cyan) 无背景,前景单色

注意 backDrawType 设置为 DrawImage 时,会显示为灰度图。

4. 原理浅析

简单介绍一下实现原理。为了便于理解,代码经过简化处理,完整代码可以在 github 查看

这个库的关键是可以将 WaveLoading {...} 内容取出,加以波浪动画的形式显示。所以需要将子 Composalbe 转成 Bitmap 进行后续处理。

4.1 获取 Bitmap

我在 Compose 中没找到获取位图的办法,所以用了一个 trick 的方式, 通过 Compose 与 Android 原生视图良好的互操作性,先将子 Composalbe 显示在 AndroidView 中,然后通过 native 的方式获取 Bitmap:

@Composable
fun WaveLoading (...)
{Box {var _bitmap by remember {mutableStateOf(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565))}AndroidView(factory = { context ->// Creates custom viewobject : AbstractComposeView(context) {@Composableoverride fun Content() {Box(Modifier.wrapContentSize(){content()}}override fun dispatchDraw(canvas: Canvas?) {val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val canvas2 = Canvas(source)super.dispatchDraw(canvas2)_bitmap = bmp}}})WaveLoadingInternal(bitmap = _bitmap)}
}

AndroidView 是一个可以绘制 Composable 的原生控件,我们将 WaveLoading 的子 Composable 放在其 Content 中,然后在 dispatchDraw 中绘制时,将内容绘制到我们准备好的 Bitmap 中。

4.2 绘制波浪线

我们基于 Compose 的 Canvas 绘制波浪线,波浪线通过 Path 承载 定义 WaveAnim 用来进行波浪线的绘制

internal data class WaveAnim(val duration: Int,val offsetX: Float,val offsetY: Float,val scaleX: Float,val scaleY: Float,
) {private val _path = Path()//绘制波浪线internal fun buildWavePath(dp: Float,width: Float,height: Float,amplitude: Float,progress: Float): Path {var wave = (scaleY * amplitude).roundToInt() //计算拉伸之后的波幅_path.reset()_path.moveTo(0f, height)_path.lineTo(0f, height * (1 - progress))// 通过正弦曲线绘制波浪if (wave > 0) {var x = dpwhile (x < width) {_path.lineTo(x,height * (1 - progress) - wave / 2f * Math.sin(4.0 * Math.PI * x / width).toFloat())x += dp}}_path.lineTo(width, height * (1 - progress))_path.lineTo(width, height)_path.close()return _path}}

如上,波浪线 Path 通过正弦函数绘制。

4.3 波浪填充

有了 Path ,我们还需要填充内容。填充的内容前文已经介绍过,或者是 DrawColor 或者 DrawImage。 绘制 Path 需要定义 Paint

    val forePaint = remember(foreDrawType, bitmap) {Paint().apply {shader = BitmapShader(when (foreDrawType) {is DrawType.DrawColor -> bitmap.toColor(foreDrawType.color)is DrawType.DrawImage -> bitmapelse -> alphaBitmap},Shader.TileMode.CLAMP,Shader.TileMode.CLAMP)}} 

Paint 使用 Shader 着色器绘制 Bitmap, 当 DrawType 只绘制单色时, 对位图做单值处理:

/*** 位图单色化*/
fun Bitmap.toColor(color: androidx.compose.ui.graphics.Color): Bitmap {val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val oldPx = IntArray(width * height) //用来存储原图每个像素点的颜色信息getPixels(oldPx, 0, width, 0, 0, width, height) //获取原图中的像素信息val newPx = oldPx.map {color.copy(Color.alpha(it) / 255f).toArgb()}.toTypedArray().toIntArray()bmp.setPixels(newPx, 0, width, 0, 0, width, height) //将处理后的像素信息赋给新图return bmp
}

4.4 波浪动画

最后通过 Compose 动画让波浪动起来

    val transition = rememberInfiniteTransition()val waves = remember(Unit) {listOf(WaveAnim(waveDuration, 0f, 0f, scaleX, scaleY),WaveAnim((waveDuration * 0.75f).roundToInt(), 0f, 0f, scaleX, scaleY),WaveAnim((waveDuration * 0.5f).roundToInt(), 0f, 0f, scaleX, scaleY))}val animates :  List<State<Float>> = waves.map { transition.animateOf(duration = it.duration) }

为了让波浪更有层次感,我们定义三个 WaveAnim 以 Set 的形式做动画

最后,配合 WaveAnim 将波浪的 Path 绘制到 Canvas 即可

 Canvas{drawIntoCanvas { canvas ->//绘制后景canvas.drawRect(0f, 0f, size.width, size.height, backPaint)//绘制前景waves.forEachIndexed { index, wave ->canvas.withSave {val maxWidth = 2 * scaleX * size.width / velocity.coerceAtLeast(0.1f)val maxHeight = scaleY * size.heightcanvas.drawPath (wave.buildWavePath(width = maxWidth,height = maxHeight,amplitude = size.height * amplitude,progress = progress), forePaint)}}}}

需要源码可以私信我,当天回复

Dialog 按照顺序弹窗相关推荐

  1. Android弹出自定义Dialog,android自定义Dialog实现底部弹窗

    android自定义Dialog实现底部弹窗 拿到这个需求,很多人都是直接想用popWindow 实现,但是这样的效果我们完全可以根据系统的Dialog 自定义一个. AlertDialog.Buil ...

  2. art.dialog重设弹窗大小和刷新位置

    var adialog=parent.art.dialog.get('payapplydialog'); adialog.size(720,430); var obj=adialog.size(720 ...

  3. dialog对话框初始化 mfc_MFC中Dialog初始化顺序

    原文:http://blog.sina.com.cn/s/blog_472a9f0c0101ax3q.html 有需要的可以点击进去查看. 1.模式对话框的创建过程: 1)DoModal()   重载 ...

  4. Android优雅实现弹窗优先级管理

    前言 在日常的android开发中,我们或多或少都有做过应用内的一些弹窗,比如在应用的某些页面弹窗展示广告,弹窗通知消息等.你的app中使用弹窗是否比较频繁?你是否厌烦了每次敲击一大堆代码就为了展示一 ...

  5. 开发kendo-ui弹窗组件

    摘要: kendo-ui中只是提供了windwo插件,并没有提供页内弹窗插件.现在分享项目中自己定制的基于window组件的弹窗插件,如果你的项目也是用的kendo-ui,只需要将组件代码引到项目中即 ...

  6. 小程序dialog ,警告:未找到 van-dialog 节点,请确认 selector 及 context 是否正确

    文档: Dialog.alert({message: '弹窗内容' }).then(() => {// on close }); 修改: 需要:content: this Dialog.aler ...

  7. easyUI datagrid editor扩展dialog

    easyUI datagrid简单使用:着重两点1.editor对象的click事件:2.将dialog窗体内的值填写到当前正编辑的单元格内 1 <!DOCTYPE html> 2 < ...

  8. Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果)、自定义背景图片、UI优化调整

    上一篇:Android 天气APP(十二)空气质量.UI优化调整 天气预报详情,逐小时预报详情 新版------------------- 一.适配器点击监听 二.页面实现 三.天气预报详情弹窗 四. ...

  9. Android底部弹窗的正确打开方式

    阅读完本文后,你可以有以下收获 利用PopupWindow实现底部弹窗 PopupWindow实现底部弹窗时的缺点 解决利用PopupWindow实现底部弹窗,无法覆盖状态栏的问题 利用dialog实 ...

最新文章

  1. 《麻省理工科技评论》发布2019年全球十大突破性技术!
  2. ELF 文件 动态链接 - 地址无关代码(GOT)
  3. Part1_4 python函数、文件操作、异常处理
  4. c语言平滑raw图像(取平均值法)
  5. 使用SQLite3存储和读取数据
  6. SAP 产品一脉相承的 UI 增强思路,在 SAP Commerce Cloud(电商云) UI 增强实现中的体现
  7. sqlite数据库的char,varchar,text,nchar,nvarchar,ntext的区别
  8. Copying to tmp table Problem Solving
  9. java怎么知道上传文件是否成功_文件包含漏洞之——tomcat CVE-2020-1938漏洞复现
  10. ajax传值controller怎么写,关于ajax请求Controller传值问题详细记录
  11. Cannot connect to the database. –Error connecting to database.
  12. 百灵欧拓O2O移动广告平台
  13. 周易全文&白話翻譯(上)
  14. Ubuntu20.04 虚拟机 联网
  15. 2022年黑龙江最新建筑八大员(标准员)模拟考试试题及答案
  16. Windows驱动之IRP PENDING
  17. Python刷点击率,下载量代码
  18. 栈解旋unwinding
  19. C语言练习题:厘米换算英尺英寸
  20. 计算方法(四):插值与拟合

热门文章

  1. Windows学习总结
  2. The 36th ACM/ICPC Asia Regional Dalian Site 1006 Dave
  3. matlab最小二乘法拟合图旋转,【Matlab】—{最小二乘法拟合一阶线性拟合传感器实验}...
  4. 关于Android 版本向下兼容
  5. java 向后兼容性_关于java:JDK“向上”还是“向后”兼容?
  6. 啃完阿里这份高并发编程核心笔记,反手涨了5K
  7. 【LeetCode】332. 重新安排行程
  8. animate调整动画持续时间 修改动画持续时间 修改动画延迟时间
  9. 幽默及顿悟的哲理故事
  10. golang使用excelize导出Excel表格数据