<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          簡單易用的Jetpack Compose版骨架屏,不來了解一下~

          共 7604字,需瀏覽 16分鐘

           ·

          2021-10-01 00:00


          作者: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))
                  }
              }
          }

          如上所示:

          • 只需要在ColumnModifier中加上shimmerColumn下的所有組件即可實現(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(0f0f),toOffset,colors,colorStops)//設置漸變色
              }

              override fun ContentDrawScope.draw() {
                  drawContent()
                  val (dx, dy) = getOffset(progress) //根據(jù)progress,設置平移的位置
                  paint.shader?.postTranslate(dx, dy) // 平移操作
                  it.drawRect(Rect(0f0f, size.width, size.height), paint = paint)//繪制骨架屏效果
              }
          }

          如上所示,主要是幾步:

          • 啟動動畫,獲得當前進度progress,并根據(jù)progress獲得當前平移的位置
          • 設置骨架屏的背景漸變顏色與混合模式
          • 繪制骨架屏效果

          自定義骨架屏效果

          上面介紹了我們提供了一些參數(shù),可以自定義骨架屏的效果,其它參數(shù)都比較好理解,主要是以下兩個參數(shù)有點難理解

          • dropOff:漸變部分寬度
          • intensity: 高亮部分寬度

          我們知道,可以通過contentColor自定義普通部分顏色,higLightColor自定義高亮部分顏色 但是這兩種顏色是如何分布的呢?漸變的比例是怎樣的呢?可以看下下面的代碼:

              private val paint = Paint().apply {
                  shader = LinearGradientShader(Offset(0f0f),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(0f1f),
                  ((1f - intensity - 0.001f) / 2f).coerceIn(0f1f),
                  ((1f + intensity + 0.001f) / 2f).coerceIn(0f1f),
                  ((1f + intensity + dropOff) / 2f).coerceIn(0f1f)
              )

          可以看出,我們的顏色漸變有以下特點:

          • 漸變顏色分布為:contentColor->higLightColor->higLightColor->contentColor
          • LinearGradientShader使用colors定義顏色,colorStops定義顏色漸變的分布,colorStops由intensitydropoff計算得來
          • intensity決定了高亮部分的寬度,即intensity越大,高亮部分越大
          • dropOff決定了漸變部分的寬度,即dropOff越大,漸變部分越大

          總結

          在實現(xiàn)Compose版本骨架屏的過程中,主要借鑒了以下開源框架的思想,有興趣的同學也可以了解下

          • Facebook開源的shimmer-android
          • Habib Kazemi開源的compose-shimmer

          項目地址

          簡單易用的Compose版骨架屏:https://github.com/shenzhen2017/ComposeShimmer


          ·················END·················

          推薦閱讀

          ? 耗時2年,Android進階三部曲第三部《Android進階指北》出版!

          ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個視頻帶你玩轉!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進階三部曲第一部《Android進階之光》第2版 出版!

          BATcoder技術群,讓一部分人先進大廠

          大家,我是劉望舒,騰訊TVP,著有三本業(yè)內知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的高級技術專家。

          前華為技術專家,現(xiàn)大廠技術總監(jiān)。


          想要加入 BATcoder技術群,公號回復BAT 即可。

          為了防止失聯(lián),歡迎關注我的小號


            微信改了推送機制,真愛請星標本公號??
          瀏覽 84
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷毛片 | 波霸巨大乳一区二区三区 | 日韩一级AV片 | 噜噜吧噜噜久久综合 | 日韩无码日韩有码 |