大家好,我是路遥,每周五给你推荐一个泛移动端优质 Github 项目。
Github 精选第二期的主角是 AndroidVeil,一个简单,灵活,低侵入的骨架屏方案。
Author | https://github.com/skydoves |
Url | https://github.com/skydoves/AndroidVeil |
Language | Kotlin |
Star | 999 |
Fork | 79 |
Issue | 3 Open/10 Closed |
Commits | 113 |
Last Update | 16 Nov 2021 |
License | Apache-2.0 |
以上数据截止至 2022 年 2 月 24 日。
首先,引入依赖。
在工程根目录的 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
<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()
...
}
AndroidVeil 中的骨架屏闪烁效果采用了 Facebook 的开源库 shimmer-android ,毕竟有成熟的轮子,没必要自己再造一个。
另外推荐大家关注这个库的作者 skydoves , 贡献了很多优秀的开源项目。例如 Jetpack MVVM 示例项目 Pokedex,颜色选择框架 ColorPickerView,集成了 Glide, Coil, 和 Fresco 的 Jetpack Compose 图片加载库 landscapist 等等。
看看他的 Contribution 图表,简直堪称活在 Github 上,开源世界的活化石。
这一期的介绍就到这里了,我们下周五见。