Dialog 按照顺序弹窗的优雅写法
目录
- 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 ) { ... }
参数说明如下:
DrawColor
或者 DrawImage
接下来重点介绍一下 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 的组合效果
注意 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 < 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 -> 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<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.height canvas.drawPath ( wave.buildWavePath( width = maxWidth, height = maxHeight, amplitude = size.height * amplitude, progress = progress ), forePaint ) } } } }
需要源码可以私信我,当天回复
到此这篇关于Dialog 按照顺序弹窗的文章就介绍到这了,更多相关Dialog 按照顺序弹窗内容请搜索hwidc以前的文章或继续浏览下面的相关文章希望大家以后多多支持hwidc!
【来源:http://www.yidunidc.com/hkgf.html网络转载请说明出处】