来源:APP研发 - 毕霞
实现思路
目前我们项目用到的骨架屏在列表页及商品详情页,所以我的项目里将骨架屏分为两个模块,一个是针对list这种多个view要预加载的,另一种针对一个页面某个view需要预加载的情况。

List部分
在Android端,通常用RecyclerView+Adapter+ViewHolder来实现列表加载视图,Adapter作为适配器用来将各种数据以合适的形式显示到RecyclerView上,

- 直接在原来的布局上进行修改,将骨架屏的布局放置到当前布局上层,通过控制显示隐藏来实现
- 这样实现和业务耦合性太强,已有的代码和逻辑改动较大,风险也太大

- 新建骨架屏的ViewHolder,重新写一个骨架屏item的布局,通过判断是否加载完成控制绑定ViewHolder,没加载完成绑定骨架屏ViewHolder,加载完成后绑定正常数据的ViewHolder
- 缺点:也是和直接改布局一样,会动到很多以前的逻辑和代码

- 新建骨架屏通用Adapter,加载数据时使用骨架屏Adapter,加载完成后替换为正常的Adapter
- 我们需要用最小的代价,在原有的基础上添加骨架屏,所以我选择在adapter上做文章,这样既可以将业务和骨架屏代码分离,又不用写大量判断的代码。

单个View部分
实现的思路是拿到要替换部分的ViewA,获取他的父类ViewGroup,从父类里移除ViewA,再将骨架屏ViewB添加进ViewA所处的位置,加载结束之后再移除ViewB,添加ViewA。

##具体实现 画了个粗略的类图:

核心代码
RecyclerViewSkeleton.kt
override fun show() {
skeletonAdapter.isGrid = displayMode == GRID_MODE
recyclerView?.let {
val gridLayoutManager = GridLayoutManagerWrapper(it.context, 2)
gridLayoutManager.orientation = LinearLayoutManager.VERTICAL
recyclerView?.layoutManager = if (displayMode == GRID_MODE) gridLayoutManager else LinearLayoutManager(it.context)
recyclerView?.adapter = skeletonAdapter
}
}
override fun hide() {
if (recyclerView?.adapter != mActualAdapter) {
recyclerView?.adapter = mActualAdapter
}
}
ViewSkeleton.kt
override fun show() {
rootView?.let {
if (it.parent != null) {
parentView = (it.parent as ViewGroup)
if (shimmerViewIds.isNotEmpty()) {
for (shimmerViewId in shimmerViewIds) {
initShimmerLayout(targetView?.findViewById(shimmerViewId))
}
}
val index = parentView?.indexOfChild(it) ?: -1
if (index != -1) {
parentView?.removeView(it)
parentView?.addView(targetView, index)
}
}
}
}
override fun hide() {
val index = parentView?.indexOfChild(targetView) ?: -1
if (index != -1) {
parentView?.removeView(targetView)
parentView?.addView(rootView, index)
}
}
在项目中的运用
主要是在v2/ProductActivity.kt 以及SearchResultActivity.kt中使用
闪光动画部分
动画部分用的第三方的库ShimmerLayout,可以设置闪光颜色,角度,动画持续时间等功能,通过startShimmerAnimation()和stopShimmerAnimation()控制动画开始和结束,基本能满足当前产品设计的需求,就不展开赘述了:
shimmerLayout.setShimmerColor(Color.argb(178, 255, 255, 255))
shimmerLayout.setShimmerAngle(30)
shimmerLayout.setShimmerAnimationDuration(1500)
shimmerLayout.startShimmerAnimation()