Dialog 按照順序彈窗的優雅寫法
我為 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 ) { ... }
參數說明如下:
參數 | 說明 |
---|---|
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 按照順序彈窗內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Android自定義View實現球形動態加速球
- 面試中canvas繪制圖片模糊圖片問題處理
- 利用Jetpack Compose繪制可愛的天氣動畫
- C#中調整圖像大小的步驟詳解
- Android自定義有限制區域圖例角度自識別塗鴉工具類中篇