我為 Compose 寫(xiě)了一個(gè)波浪效果的進(jìn)度加載庫(kù),API 的設(shè)計(jì)上符合 Compose 的開(kāi)發(fā)規(guī)范,使用非常簡(jiǎn)便。
1. 使用方式
在 root 的 build.gradle
中引入 jitpack
,
1
2
3
4
5
6
|
allprojects { repositories { ... maven { url 'https://jitpack.io' } } } |
在 module 的 build.gradle
中引入 ComposeWaveLoading
的最新版本
1
2
3
|
dependencies { implementation 'com.github.vitaviva:ComposeWaveLoading:$latest_version' } |
2. API 設(shè)計(jì)思想
1
2
3
4
5
6
7
8
9
10
|
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ù)介紹
1
2
3
4
5
6
7
8
9
10
|
@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 改變波浪的樣式。
1
2
3
4
5
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@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)的繪制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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ì)位圖做單值處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/ * * * 位圖單色化 * / 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)
1
2
3
4
5
6
7
8
9
10
11
|
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 即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/weixin_55362248/article/details/120374053