Dialog 按照順序彈窗的優(yōu)雅寫(xiě)法
我為 Compose 寫(xiě)了一個(gè)波浪效果的進(jìn)度加載庫(kù),API 的設(shè)計(jì)上符合 Compose 的開(kāi)發(fā)規(guī)范,使用非常簡(jiǎn)便。
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 設(shè)計(jì)思想
Box {
WaveLoading (
progress = 0.5f // 0f ~ 1f
) {
Image(
painter = painterResource(id = R.drawable.logo_tiktok),
contentDescription = ""
)
}
}
傳統(tǒng)的 UI 開(kāi)發(fā)方式中,設(shè)計(jì)這樣一個(gè)波浪控件,一般會(huì)使用自定義 View 并將 Image 等作為屬性傳入。 而在 Compose 中,我們讓 WaveLoading 和 Image 以組合的方式使用,這樣的 API 更加靈活,WaveLoding 的內(nèi)部可以是 Image,也可以是 Text 亦或是其他 Composable。波浪動(dòng)畫(huà)不拘泥于某一特定 Composable, 任何 Composable 都可以以波浪動(dòng)畫(huà)的形式展現(xiàn), 通過(guò) Composable 的組合使用,擴(kuò)大了 “能力” 的覆蓋范圍。
3. API 參數(shù)介紹
@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
) { ... }
參數(shù)說(shuō)明如下:
| 參數(shù) | 說(shuō)明 |
|---|---|
| progress | 加載進(jìn)度 |
| foreDrawType | 波浪圖的繪制類(lèi)型: DrawColor 或者 DrawImage |
| backDrawType | 波浪圖的背景繪制 |
| amplitude | 波浪的振幅, 0f ~ 1f 表示振幅在整個(gè)繪制區(qū)域的占比 |
| velocity | 波浪移動(dòng)的速度 |
| content | 子Composalble |
接下來(lái)重點(diǎn)介紹一下 DrawType。
DrawType
波浪的進(jìn)度體現(xiàn)在前景(foreDrawType)和后景(backDrawType)的視覺(jué)差,我們可以為前景后景分別指定不同的 DrawType 改變波浪的樣式。
sealed interface DrawType {
object None : DrawType
object DrawImage : DrawType
data class DrawColor(val color: Color) : DrawType
}
如上,DrawType 有三種類(lèi)型:
- None: 不進(jìn)行繪制
- DrawColor:使用單一顏色繪制
- DrawImage:按照原樣繪制
以下面這個(gè) Image 為例, 體會(huì)一下不同 DrawType 的組合效果
| index | backDrawType | foreDrawType | 說(shuō)明 |
|---|---|---|---|
| 1 | DrawImage | DrawImage | 背景灰度,前景原圖 |
| 2 | DrawColor(Color.LightGray) | DrawImage | 背景單色,前景原圖 |
| 3 | DrawColor(Color.LightGray) | DrawColor(Color.Cyan) | 背景單色,前景單色 |
| 4 | None | DrawColor(Color.Cyan) | 無(wú)背景,前景單色 |
注意 backDrawType 設(shè)置為 DrawImage 時(shí),會(huì)顯示為灰度圖。
4. 原理淺析
簡(jiǎn)單介紹一下實(shí)現(xiàn)原理。為了便于理解,代碼經(jīng)過(guò)簡(jiǎn)化處理,完整代碼可以在 github 查看
這個(gè)庫(kù)的關(guān)鍵是可以將 WaveLoading {...} 內(nèi)容取出,加以波浪動(dòng)畫(huà)的形式顯示。所以需要將子 Composalbe 轉(zhuǎn)成 Bitmap 進(jìn)行后續(xù)處理。
4.1 獲取 Bitmap
我在 Compose 中沒(méi)找到獲取位圖的辦法,所以用了一個(gè) trick 的方式, 通過(guò) Compose 與 Android 原生視圖良好的互操作性,先將子 Composalbe 顯示在 AndroidView 中,然后通過(guò) 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 是一個(gè)可以繪制 Composable 的原生控件,我們將 WaveLoading 的子 Composable 放在其 Content 中,然后在 dispatchDraw 中繪制時(shí),將內(nèi)容繪制到我們準(zhǔn)備好的 Bitmap 中。
4.2 繪制波浪線(xiàn)
我們基于 Compose 的 Canvas 繪制波浪線(xiàn),波浪線(xiàn)通過(guò) Path 承載 定義 WaveAnim 用來(lái)進(jìn)行波浪線(xiàn)的繪制
internal data class WaveAnim(
val duration: Int,
val offsetX: Float,
val offsetY: Float,
val scaleX: Float,
val scaleY: Float,
) {
private val _path = Path()
//繪制波浪線(xiàn)
internal fun buildWavePath(
dp: Float,
width: Float,
height: Float,
amplitude: Float,
progress: Float
): Path {
var wave = (scaleY * amplitude).roundToInt() //計(jì)算拉伸之后的波幅
_path.reset()
_path.moveTo(0f, height)
_path.lineTo(0f, height * (1 - progress))
// 通過(guò)正弦曲線(xiàn)繪制波浪
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
}
}
如上,波浪線(xiàn) Path 通過(guò)正弦函數(shù)繪制。
4.3 波浪填充
有了 Path ,我們還需要填充內(nèi)容。填充的內(nèi)容前文已經(jīng)介紹過(guò),或者是 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, 當(dāng) DrawType 只繪制單色時(shí), 對(duì)位圖做單值處理:
/**
* 位圖單色化
*/
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) //用來(lái)存儲(chǔ)原圖每個(gè)像素點(diǎn)的顏色信息
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 波浪動(dòng)畫(huà)
最后通過(guò) Compose 動(dòng)畫(huà)讓波浪動(dòng)起來(lái)
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) }
為了讓波浪更有層次感,我們定義三個(gè) WaveAnim 以 Set 的形式做動(dòng)畫(huà)
最后,配合 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
)
}
}
}
}
需要源碼可以私信我,當(dāng)天回復(fù)
到此這篇關(guān)于Dialog 按照順序彈窗的文章就介紹到這了,更多相關(guān)Dialog 按照順序彈窗內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非maisonbaluchon.cn所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。
關(guān)注官方微信