Dialog按照顺序弹窗的优雅写法是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
我为 Compose 写了一个波浪效果的进度加载库,API 的设计上符合 Compose 的开发规范,使用非常简便。
在 root 的 build.gradle
中引入 jitpack
,
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
在 module 的 build.gradle
中引入 ComposeWaveLoading
的最新版本
dependencies { implementation 'com.github.vitaviva:ComposeWaveLoading:$latest_version' }
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 的组合使用,扩大了 “能力” 的覆盖范围。
@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 时,会显示为灰度图。
简单介绍一下实现原理。为了便于理解,代码经过简化处理,完整代码可以在 github 查看
这个库的关键是可以将 WaveLoading {...}
内容取出,加以波浪动画的形式显示。所以需要将子 Composalbe 转成 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 中。
我们基于 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 通过正弦函数绘制。
有了 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 }
最后通过 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 ) } } } }
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。