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

(0)

相关推荐

  • jquery ui dialog实现弹窗特效的思路及代码

    今天我们用jquery ui dialog来做一个弹窗特效.我们先看下效果截图: 我们可以看到,点击的时候,弹窗出现,而且这个弹窗是居中的,还是可以拖动的...实现这一切,只要以下代码: 我们可以看到,我对pop这个div,实现的方式是让它不要autoopen,点击的时候,我只要一句dialog,open就搞定了,借助于jquery ui,我们做弹窗就是这么简单...当然了,大家可以看到,我还有一个插入数据的功能,这个功能,我采用了jquery 的appendto: 我先通过变量获取值,接着建了

  • 支付宝小程序自定义弹窗dialog插件的实现代码

    支付宝小程序官方提供的alert提示框.dialog对话框.model弹窗功能比较有限,有些都不能随意自定义修改的.如是自己就捯饬着封装了个支付宝小程序自定义弹窗插件wcPop,多种展示场景,随意修改调用. 自定义的小程序弹窗采用了全新的模板布局,极简的api调用方式,同时解决了自定义弹窗出现时,蒙层下的页面仍可以滚动的问题. 在原始功能的基础上,新增了跟随定位弹窗.上下左右弹窗.弹窗swipe滑动功能 先来展示部分弹窗demo图: 在需要调用弹窗插件的页面引入tpl.js //信息框 btnT

  • Android仿支付宝微信支付密码界面弹窗封装dialog

    一,功能效果 二,实现过程 1,先写xml文件:dialog_keyboard.xml 注意事项 (1),密码部分用的是一个线性布局中6个TextView,并设置android:inputType="numberPassword",外框是用的一个有stroke属性的shape, (2),1-9数字是用的recycleview ,每个item的底部和右边有1dp的黑线,填充后形成分割线. (3),recycleview 要设置属性  android:overScrollMode=&quo

  • element-ui中dialog弹窗关闭按钮失效的解决

    如下所示: <el-dialog title="修改库存" :visible.sync="kcDialog" @close="kcDialog = false"> ... </el-dialog> 加一个@close可以是一个方法或者直接操作kcDialog为false 补充知识:webpack外部扩展,依赖前置 引入了外部js index.html <script src="https://code.jq

  • vant-ui组件调用Dialog弹窗异步关闭操作

    需求描述: 需求描述:官方文档又是组件调用方式,又是函数调用方式. 我就需要一个很简单的:点击操作弹窗显示后,我填写一个表单,表单校验通过后,再调用API接口,返回成功后,关闭弹窗. 一个很简单的东西,element-ui用的很方便,在这里就懵比了,刚开始做的,弹窗关闭了,才返回异步接口调用的结果.网速慢点,用起来真的很不好. 正确的解决方式一: <van-dialog v-model="showDialog" title="提示" show-cancel-b

  • Android UI设计之AlertDialog弹窗控件

    有关android的弹窗界面相信大家见过不少了,手机上很多应用软件都涉及到弹窗控件,比如典型的每次删除一个图片或者卸载一个等都会弹出一个窗口询问是否删除/卸载等,还有我们系统的设置时间/日期等,都用到了这样的控件,下面我将通过代码来总结下常用的几个弹窗控件 activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http:/

  • Flutter之自定义Dialog实现版本更新弹窗功能的实现

    功能点: 1.更新弹窗UI 2.强更与非强更且别控制 3.屏蔽物理返回键(因为强更的时候点击返回键,弹窗会消失) 4.点击弹窗外透明区域时,弹窗不消失 先看下效果图: Dialog实现代码: import 'package:flutter/material.dart'; import 'package:xiaopijiang/utils/assets_util.dart'; import 'package:xiaopijiang/utils/toast_util.dart'; ///create

  • 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.i

  • JavaScript中判断的优雅写法示例

    目录 前言 一.一元判断 1.1 举个例子

  • JS复杂判断的更优雅写法代码详解

    我们编写js代码时经常遇到复杂逻辑判的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑,本文带你试一下. 举个例子 先看一段代码 /** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ const onButtonClick1 = (s

  • Android底部弹窗的实现示例代码

    本文主要是介绍Android中实现底部弹窗的的正确姿势,如果你在实现底部弹窗时遇到了一些问题,那么请仔细阅读本文,相信文章会对你有所帮助. 收获早知道 阅读完本文后,你可以有以下收获 利用PopupWindow实现底部弹窗 PopupWindow实现底部弹窗时的缺点 解决利用PopupWindow实现底部弹窗,无法覆盖状态栏的问题 利用dialog实现底部弹窗 利用dialogFragment实现底部弹窗 实现底部弹窗的方式 由于本人水平有限,只知道一下几种实现底部弹窗的方式 利用PopupWi

  • 微信小程序vant弹窗组件的实现方式

    作为从事前端开发的你肯定见过不少的弹框组件,你可曾有想过要自己实现一个弹框组件库,又或者想完全定制化的使用各种标准UI框架中的弹框组件呢? 今天这篇文章将会带着你解析这一系列疑问,以vant-weapp组件库为例,从开发标准的弹窗组件使用到高度定制复合自我审美的弹窗,再到完全研究清楚vant-weapp框架弹窗组件部分源码. 一.vant-weapp弹窗组件介绍 vant-weapp组件库是有赞团队开发的 一款灵活简洁且美观的小程序UI组件库 ,此文将以这个组件库的用法为标准,下文提及的弹框组件

  • elementUI同一页面展示多个Dialog的实现

    要实现的效果如下: 首先官方文档是这样描述的: 但是我写了个小demo发现并不能直接平级放置即可,一样会存在先后顺序不同造成的覆盖以及遮罩层导致不能点击被遮盖的dialog. 原因如下:因为dialog先后顺序不同z-index设置的层级不同,所以必定会覆盖遮挡 那么我们要实现一个这样的效果不仅仅平级放置即可,就要用到里面的一个属性:modal 下面贴上代码: 总的思路就是:dialog先后顺序重叠问题,使用便宜去让它们错开:然后就是遮罩层导致不能点击z-index层级低的弹框,就要用到moda

  • Python list去重且保持原顺序不变的方法

    背景 python 去重一顿操作猛如虎,set list 扒拉下去,就去重了,但是顺序就打乱了.如果对顺序没有需要的话,这样确实没有什么所谓. 但是如果需要保留顺序的话,就需要一点小小的改变. code && demo list 去重,顺序乱掉 # normal 写法 l1 = ['b','c','d','b','c','a','a'] l2 = list(set(l1)) print(l2) # plus 写法 l1 = ['b','c','d','b','c','a','a'] l2

  • Java字符串拼接的优雅方式实例详解

    目录 背景 String底层原理 拼接的方法 经典但有时不优雅的 + 优点 缺点 业务一 万能的StringBuilder 线程安全的StringBuffer 灵活的String.format() 有点绿色的concat JDK1.8优雅写法 经典的Guava 总结 背景 字符串拼接不管是在业务上,还是写算法时都会频繁使用到.对于Java来说,字符串拼接有着很多种方式,他们之间的区别是什么,对应不同的业务哪种更好用呢. String底层原理 在讨论字符串拼接时,首先需要知道String的底层原理

  • vue实现弹窗引用另一个页面窗口

    目录 弹窗引用另一个页面窗口 弹窗如何嵌入其它页面 A页面(父页面) B页面(子页面) 弹窗引用另一个页面窗口 需求:在一个主页面A.vue上点击按钮时弹出一个窗口,该窗口的定义在B.vue,比如修改,需要从A.vue传参到B.vue,修改完成后,刷新A.vue. 实现 页面定义,有2个文件,在index.vue上有个[修改]按钮,点击弹出testDialog.vue定义的窗口,如下 testDialog.vue <template>   <!-- 添加或修改业务对话框 -->  

随机推荐