这篇文章主要介绍了Dialog 按照顺序弹窗的写法及示例,给大家介绍了DrawType的使用方式,需要的朋友可以参考下
目录
1. 使用方式
2. API 设计思想
3. API 参数介绍
4. 原理浅析
4.1 获取 Bitmap
4.2 绘制波浪线
4.3 波浪填充
4.4 波浪动画
我为 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 中,我们让 WaveLoading 和 Image 以组合的方式使用,这样的 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 : DrawType object DrawImage : DrawType data 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 view object : AbstractComposeView(context) { @Composable override 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 = dp while (x
如上,波浪线 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 -> bitmap else -> 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> = 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.height canvas.drawPath ( wave.buildWavePath( width = maxWidth, height = maxHeight, amplitude = size.height * amplitude, progress = progress ), forePaint ) } } } }
需要源码可以私信我,当天回复