Dialog 按照顺序弹窗的优雅写法

 更新时间:2021年9月19日 15:27  点击:1908

我为 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 : 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 < 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 按照顺序弹窗内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • JS中artdialog弹出框控件之提交表单思路详解

    artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口。本文给大家介绍JS中artdialog弹出框控件之提交表单思路详解,对本文感兴趣的朋友一起学习吧...2016-04-19
  • vue中实现点击空白区域关闭弹窗的两种方法

    这篇文章主要介绍了vue中实现点击空白区域关闭弹窗的两种方法,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2020-12-30
  • vue实现内容可滚动的弹窗效果

    这篇文章主要为大家详细介绍了vue实现内容可滚动的弹窗效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-05
  • vue实现底部弹窗多选

    这篇文章主要为大家详细介绍了vue实现底部弹窗多选,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-05
  • Flutter 底部弹窗ModelBottomSheet的使用示例

    在实际开发过程中,经常会用到底部弹窗来进行快捷操作,例如选择一个选项,选择下一步操作等等。在 Flutter 中提供了一个 showModelBottomSheet 方法用于弹出底部弹窗,本篇介绍如何使用底部弹窗。...2021-06-07
  • jquery ui dialog替代confirm实例分析

    这篇文章主要介绍了jquery ui dialog替代confirm的实现方法,结合实例形式分析了jQuery ui插件的dialog模拟confirm功能的具体步骤与实现技巧,需要的朋友可以参考下...2016-01-26
  • js实现弹窗效果

    这篇文章主要为大家详细介绍了js实现弹窗效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-09
  • 浅谈C# 非模式窗体show()和模式窗体showdialog()的区别

    下面小编就为大家带来一篇浅谈C# 非模式窗体show()和模式窗体showdialog()的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 很棒的vue弹窗组件

    这篇文章主要为大家详细介绍了vue弹窗组件的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-05-27
  • CFileDialog设置多选的问题解决

    前几天同事问我在CFileDialog中多选时按确定按钮后DoModal函数的返回值是IDCANCEL的问题解决...2020-04-25
  • Android自定义Dialog原理实例解析

    这篇文章主要介绍了Android自定义Dialog原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-29
  • vue 实现一个简单的全局调用弹窗案例

    这篇文章主要介绍了vue 实现一个简单的全局调用弹窗案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-10
  • vue集成openlayers加载geojson并实现点击弹窗教程

    这篇文章主要为大家详细介绍了vue集成openlayers加载geojson并实现点击弹窗教程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-09-25
  • Asp.net 中mvc 实现超时弹窗后跳转功能

    这篇文章主要介绍了Asp.net 中mvc 实现超时弹窗后跳转功能,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • python tkinter的消息框模块(messagebox,simpledialog)

    这篇文章主要介绍了python tkinter的消息框模块,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下...2020-11-07
  • Winform OpenFileDialog打开文件对话框

    这篇文章主要为大家详细介绍了Winform OpenFileDialog打开文件对话框 的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • CFileDialog的钩子函数解决对话框的多选之DoModal问题

    前几天领导问我一个问题:就是使用CFileDialog类在设置多选时选中的文件所放的文件缓冲区不知设置多大合适,本文将详细介绍,需要的朋友可以参考下...2020-04-25
  • Vue+Element UI实现概要小弹窗的全过程

    弹窗效果是我们日常开发中经常遇到的一个功能,下面这篇文章主要给大家介绍了关于Vue+Element UI实现概要小弹窗的相关资料,需要的朋友可以参考下...2021-05-30
  • 谷歌showModalDialog()方法不兼容出现对话窗口的解决办法

    这篇文章给大家介绍了谷歌showModalDialog()方法不兼容出现对话窗口的解决办法,解决办法非常好,感兴趣的朋友可以参考下...2016-02-18
  • WPF气泡样式弹窗效果代码分享

    这篇文章主要为大家详细介绍了WPF气泡样式弹窗效果的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22