GitHub 精选 #2| 如何优雅的实现骨架屏?

大家好,我是路遥,每周五给你推荐一个泛移动端优质 Github 项目。

今天的主角是 AndroidVeil一个简单,灵活,低侵入的骨架屏方案

Author github.com/skydoves
Url github.com/skydoves/An…
Language Kotlin
Star 989
Fork 79
Issue 3 Open/10 Closed
Commits 113
Last Update 16 Nov 2021
License Apache-2.0

以上数据截止至 2022 年 2 月 19 日。

预览

 

使用

首先,引入依赖。

在工程根目录的 build.gradle 文件添加:

allprojects {
    repositories {
        mavenCentral()
    }
}
复制代码

在模块的 build.gradle 文件添加:

implementation "com.github.skydoves:androidveil:1.1.2"
复制代码

AndroidVeil 支持在 RecyclerView 和任意布局中使用骨架屏效果。

在任意布局中使用

<com.skydoves.androidveil.VeilLayout
      android:id="@+id/veilLayout"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:veilLayout_veiled="true" // shows veils initially
      app:veilLayout_shimmerEnable="true" // 初始状态下是否开启骨架屏
      app:veilLayout_baseColor="@android:color/holo_green_dark" // sets shimmer base color
      app:veilLayout_highlightColor="@android:color/holo_green_light" // sets shimmer highlight color
      app:veilLayout_baseAlpha="0.6" // sets shimmer base alpha value
      app:veilLayout_highlightAlpha="1.0" // sets shimmer highlight alpha value
      app:veilLayout_dropOff="0.5"// sets how quickly the shimmer`s gradient drops-off
      app:veilLayout_radius="6dp" // sets a corner radius of the whole veiled items >
​
      <TextView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="And now here is my secret, a very simple secret"
         android:textColor="@android:color/white"
         android:textSize="22sp"/>
​
      <!-- Skip -->    
​
</com.skydoves.androidveil.VeilLayout>
复制代码

VeilLayout 中的所有子 View 都会支持骨架屏的闪烁效果。聪明的你应该大概能想到是怎么实现的了吧!

如果在初始状态下没有开启骨架屏,也可以通过代码控制开关。

veilLayout.veil()
veilLayout.unVeil()
复制代码

另外,也可以自由设置骨架屏的布局。

veilLayout.layout = R.layout.layout_item_test
复制代码

在 RecyclerView 中使用

<com.skydoves.androidveil.VeilRecyclerFrameView
        android:id="@+id/veilRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:veilFrame_layout="@layout/item_profile" // sets to make veiling target layout
        app:veilFrame_veiled="true" // shows veils initially
        app:veilFrame_shimmerEnable="true" // sets shimmer enable
        app:veilFrame_baseColor="@android:color/holo_green_dark" // sets shimmer base color
        app:veilFrame_highlightColor="@android:color/holo_green_light" // sets shimmer highlight color
        app:veilFrame_baseAlpha="0.6" // sets shimmer base alpha value
        app:veilFrame_highlightAlpha="1.0" // sets shimmer highlight alpha value
        app:veilFrame_radius="8dp" // sets a corner radius of the whole veiled items />
复制代码

VeilRecyclerFrameView 可以当作 RecyclerView 来使用。

veilRecyclerView.setAdapter(adapter) // 设置 adapter
veilRecyclerView.setLayoutManager(LinearLayoutManager(this)) // 设置 LayoutManager
veilRecyclerView.addVeiledItems(15) // 添加骨架屏 item 数量
复制代码

同时也提供了代码开关骨架屏效果。

veilRecyclerView.veil() 
veilRecyclerView.unVeil() 
复制代码

但其实 VeilRecyclerFrameView 并不是 RecyclerView,如果想对 RecyclerView 设置更多效果,可以通过 getRecyclerView() 获取。

veilRecyclerView.getRecyclerView() // 真正的 RecyclerView
veilRecyclerView.getVeiledRecyclerView() // 骨架 RecyclerView
复制代码

这两个方法其实也就直接把实现原理告诉你了。

原理

先看 RecyclerView 效果的。

class VeilRecyclerFrameView : RelativeLayout {
	// 用户展示数据用的 RecyclerView
  private val userRecyclerView: RecyclerView = RecyclerView(context)
  // 展示骨架屏的 RecyclerView
  private val veiledRecyclerView: RecyclerView = RecyclerView(context)
  private var veiledAdapter: VeiledAdapter? = null
  private var isVeiled = false

  ...
  
    private fun onCreate() {
    addView(this.userRecyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    addView(this.veiledRecyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    this.veiledRecyclerView.setHasFixedSize(true)
    applyOverScrollMode()
    when (this.isVeiled) {
      true -> visibleVeilRecyclerView()
      false -> visibleUserRecyclerView()
    }

    if (this.layout != -1) {
      setVeilLayout(this.layout)
    }
  }
  
  ...
}
复制代码

其实就是用了两个 Rv,一个提供给用户展示数据,一个用于显示骨架屏效果。之前见过另一种做法是只用一个 Rv,动态切换 adapter 。你觉得哪种实现方法更好呢?欢迎在评论区留言。

VeilLayout 的实现方案稍微复杂一些,但也不难想象,根据原布局中的 View 层级结构,添加对应的骨架屏效果的布局即可。

  override fun onFinishInflate() {
    super.onFinishInflate()
    removeView(shimmerContainer)
    addView(shimmerContainer)
    addMaskElements(this)
  }
复制代码

onFinishInflate() 方法在布局文件 inflate 结束之后调用,此时添加一个 shimmerContainer,它是一个 ShimmerFrameLayout 对象,是 Facebook 的一个开源库提供的。addMaskElements() 方法根据布局的原 View 层级结构,向 shimmerContainer 中添加对应的 View 。

  private fun addMaskElements(parent: ViewGroup) {
    (0 until parent.childCount).map { parent.getChildAt(it) }.forEach { child ->
      child.post {
        if (child is ViewGroup) {
          addMaskElements(child)
        } else {
          var marginX = 0f
          var marginY = 0f
          var grandParent = parent.parent
          ...
            }
          }

          // create a masked view
          View(context).apply {
            layoutParams = LayoutParams(child.width, child.height)
            x = marginX + parent.x + child.x
            y = marginY + parent.y + child.y
            setBackgroundColor(baseColor)

            background = drawable ?: GradientDrawable().apply {
              setColor(Color.DKGRAY)
              cornerRadius = radius
            }
            maskElements.add(this)
            shimmerContainer.addView(this)
          }
        }
      }
    }

    // Invalidate the whole masked view.
    invalidate()
 		...
  }
复制代码

Something else

AndroidVeil 中的骨架屏闪烁效果采用了 Facebook 的开源库 shimmer-android ,毕竟有成熟的轮子,没必要自己再造一个。

另外推荐大家关注这个库的作者 skydoves , 贡献了很多优秀的开源项目。例如 Jetpack MVVM 示例项目 Pokedex,颜色选择框架 ColorPickerView,集成了 Glide, Coil, 和 Fresco 的 Jetpack Compose 图片加载库 landscapist 等等。

看看他的 Contribution 图表,简直堪称活在 Github 上,开源世界的活宝石。

image.png

最后

这一期的介绍就到这里了,我们下周五见。

如果你有好的项目推荐,欢迎给我留言。

猜你喜欢

转载自juejin.im/post/7073275888128753678
今日推荐