簡單易用的Jetpack Compose版骨架屏,不來了解一下~
作者:
RicardoMJiang, 鏈接:https://juejin.cn/post/7004447246854914085
前言
骨架屏是頁面的一個空白版本,通常會在頁面完全渲染之前,通過一些灰色的區(qū)塊大致勾勒出輪廓,待數(shù)據(jù)加載完成后,再替換成真實的內容。骨架屏加載中效果,比起傳統(tǒng)的加載中效果可以提供更多信息,用戶體驗更好,因此也變得越來越流行
本文主要介紹如何使用Compose實現(xiàn)一個簡單易用的骨架屏效果,有興趣的同學可以點個Star:Compose版骨架屏
效果圖
首先看下最終的效果圖


特性
簡單易用,可復用頁面UI,不需要針對骨架屏定制UI 支持設置骨架屏是否顯示,一般結合加載狀態(tài)使用 支持設置骨架屏背景與高亮顏色 支持設置骨架屏高度部分寬度,漸變部分寬度 支持設置骨架屏動畫的角度與方向 支持設置骨架屏動畫的時間與兩次動畫間隔
使用
接入
第 1 步:在工程的build.gradle中添加:
allprojects {
repositories {
...
mavenCentral()
}
}
第2步:在應用的build.gradle中添加:
dependencies {
implementation 'io.github.shenzhen2017:shimmer:1.0.0'
}
簡單使用
@Composable
fun ShimmerSample() {
var loading: Boolean by remember {
mutableStateOf(true)
}
Column(
modifier = Modifier
.fillMaxWidth()
.shimmer(loading,config = ShimmerConfig())
) {
repeat(3) {
PlaceHolderItem()
Spacer(modifier = Modifier.height(10.dp))
}
}
}
如上所示:
只需要在 Column的Modifier中加上shimmer,Column下的所有組件即可實現(xiàn)骨架屏效果可通過loading參數(shù),控制骨架屏效果是否顯示 如果需要定制骨架屏動畫效果,也可通過一些參數(shù)配置
具體主要有以下這些參數(shù)
data class ShimmerConfig(
// 未高亮部分顏色
val contentColor: Color = Color.LightGray.copy(alpha = 0.3f),
// 高亮部分顏色
val higLightColor: Color = Color.LightGray.copy(alpha = 0.9f),
// 漸變部分寬度
@FloatRange(from = 0.0, to = 1.0)
val dropOff: Float = 0.5f,
// 高亮部分寬度
@FloatRange(from = 0.0, to = 1.0)
val intensity: Float = 0.2f,
//骨架屏動畫方向
val direction: ShimmerDirection = ShimmerDirection.LeftToRight,
//動畫旋轉角度
val angle: Float = 20f,
//動畫時長
val duration: Float = 1000f,
//兩次動畫間隔
val delay: Float = 200f
)
主要原理
通過圖像混合模式復用頁面UI
如果我們要實現(xiàn)骨架屏效果,首先想到的是需要按照頁面的結構再寫一套UI,然后在加載中的時候,顯示這套UI,否則隱藏
一般的加載中效果都是這樣實現(xiàn)的,但這樣會帶來一個問題,不同的頁面結構不同,那我們豈不是要一個頁面就重寫一套UI?這顯然是不可接受的
我們可以想到,頁面的結構其實我們已經寫過一遍了,如果我們能復用我們寫的頁面結構不就好了嗎?我們可以通過圖像混合模式來實現(xiàn)這一點
圖像混合模式定義的是,當兩個圖像合成時,圖像最終的展示方式。在Androd中,有相應的API接口來支持圖像混合模式,即Xfermode。
圖像混合模式主要有以下16種,以下這張圖片從一定程度上形象地說明了圖像混合的作用,兩個圖形一圓一方通過一定的計算產生不同的組合效果,具體如下

我們介紹幾個常用的,其它的感興趣的同學可自行查閱
SRC_IN:只在源圖像和目標圖像相交的地方繪制【源圖像】DST_IN:只在源圖像和目標圖像相交的地方繪制【目標圖像】,繪制效果受到源圖像對應地方透明度影響SRC_OUT:只在源圖像和目標圖像不相交的地方繪制【源圖像】,相交的地方根據(jù)目標圖像的對應地方的alpha進行過濾,目標圖像完全不透明則完全過濾,完全透明則不過濾DST_OUT:只在源圖像和目標圖像不相交的地方繪制【目標圖像】,在相交的地方根據(jù)源圖像的alpha進行過濾,源圖像完全不透明則完全過濾,完全透明則不過濾
如果我們把頁面的UI結構作為目標圖像,骨架屏效果作為源圖像,然后使用SRC_IN混合模式
就可以實現(xiàn)只在頁面的結構上顯示骨架屏,在空白部分不顯示,這樣就可以避免重復寫UI了
通過平移實現(xiàn)動畫效果
上面我們已經實現(xiàn)了在頁面結構上顯示骨架屏,但是骨架屏效果還有一個動畫效果
其實也很簡單,給骨架屏設置一個漸變效果,然后做一個平移動畫,然后看起來就是現(xiàn)在的骨架屏閃光動畫了
fun Modifier.shimmer(): Modifier = composed {
var progress: Float by remember { mutableStateOf(0f) }
val infiniteTransition = rememberInfiniteTransition()
progress = infiniteTransition.animateFloat().value // 動畫效果,計算百分比
ShimmerModifier(visible = visible, progress = progress, config = config)
}
internal class ShimmerModifier(progress:Float) : DrawModifier, LayoutModifier {
private val paint = Paint().apply {
blendMode = BlendMode.SrcIn //設置混合模式
shader = LinearGradientShader(Offset(0f, 0f),toOffset,colors,colorStops)//設置漸變色
}
override fun ContentDrawScope.draw() {
drawContent()
val (dx, dy) = getOffset(progress) //根據(jù)progress,設置平移的位置
paint.shader?.postTranslate(dx, dy) // 平移操作
it.drawRect(Rect(0f, 0f, size.width, size.height), paint = paint)//繪制骨架屏效果
}
}
如上所示,主要是幾步:
啟動動畫,獲得當前進度progress,并根據(jù)progress獲得當前平移的位置 設置骨架屏的背景漸變顏色與混合模式 繪制骨架屏效果
自定義骨架屏效果
上面介紹了我們提供了一些參數(shù),可以自定義骨架屏的效果,其它參數(shù)都比較好理解,主要是以下兩個參數(shù)有點難理解
dropOff:漸變部分寬度intensity: 高亮部分寬度
我們知道,可以通過contentColor自定義普通部分顏色,higLightColor自定義高亮部分顏色
但是這兩種顏色是如何分布的呢?漸變的比例是怎樣的呢?可以看下下面的代碼:
private val paint = Paint().apply {
shader = LinearGradientShader(Offset(0f, 0f),toOffset,colors,colorStops)//設置漸變色
}
private val colors = listOf(
config.contentColor,
config.higLightColor,
config.higLightColor,
config.contentColor
)
private val colorStops: List<Float> = listOf(
((1f - intensity - dropOff) / 2f).coerceIn(0f, 1f),
((1f - intensity - 0.001f) / 2f).coerceIn(0f, 1f),
((1f + intensity + 0.001f) / 2f).coerceIn(0f, 1f),
((1f + intensity + dropOff) / 2f).coerceIn(0f, 1f)
)
可以看出,我們的顏色漸變有以下特點:
漸變顏色分布為: contentColor->higLightColor->higLightColor->contentColorLinearGradientShader使用colors定義顏色,colorStops定義顏色漸變的分布,colorStops由intensity與dropoff計算得來intensity決定了高亮部分的寬度,即intensity越大,高亮部分越大dropOff決定了漸變部分的寬度,即dropOff越大,漸變部分越大
總結
在實現(xiàn)Compose版本骨架屏的過程中,主要借鑒了以下開源框架的思想,有興趣的同學也可以了解下
Facebook開源的shimmer-android Habib Kazemi開源的compose-shimmer
項目地址
簡單易用的Compose版骨架屏:https://github.com/shenzhen2017/ComposeShimmer
推薦閱讀
? 耗時2年,Android進階三部曲第三部《Android進階指北》出版!
BATcoder技術群,讓一部分人先進大廠
大家好,我是劉望舒,騰訊TVP,著有三本業(yè)內知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的高級技術專家。
前華為技術專家,現(xiàn)大廠技術總監(jiān)。
想要加入 BATcoder技術群,公號回復BAT 即可。
為了防止失聯(lián),歡迎關注我的小號
微信改了推送機制,真愛請星標本公號??
