Android骨架屏效果的实现与原理解析
大家在使用淘宝的时候,如下图所示有遇到这样的效果,其会只展示一部分骨架大致图,等数据加载完毕之后再展示真正的页面数据。与菊花图相比起来,这样的实现能更好的提升用户的体验,这种效果称做:Skeleton Screen Loading,中文叫做骨架屏,本文主要记录其实现过程。
1、骨架屏的实现方式
在现在主流的骨架屏实现效果中有两种方式:
通过View或者Adapter的替换来实现骨架屏效果。可以参考ShimmerRecyclerView、Skeleton及spruce-android。
自定义一个View来对布局中的View进行一层包裹,当加载数据时则根据View来绘制骨架,否则显示正常UI,参考Skeleton Android。
这些开源库中,自己比较喜欢今天Skeleton这个开源库,总结了有如下一些优缺点:
优点:
- 代码方案实现及使用方式简单,通过替换View和Adapter实现效果,使用Builder设计模式来构造。
- 代码耦合程度不高。没有复杂的设计模式,使得代码结构清晰明了。
- 骨架屏的效果使用相对于较灵活,可以对整个布局实现骨架屏效果,也可以对单一View实现骨架屏效果。
缺点:
- 需要对每个骨架屏效果单独写一套xml布局。
- 使用的removeView和addView对 原有布局的view进行替换,存在一定的风险性
- 必须清晰的知道所bind的View类型,存在一定的类型转化问题。
- 依赖了shimmerlayout第三方库
2、Skeleton解读
一、Skeleton的使用方式
展示骨架屏效果:
1 | View rootView = findViewById(R.id.rootView); |
关闭骨架屏效果并展示原有View:
1 | skeletonScreen.hide() |
流程:
1. 选择需要替换的目标view
2. 将骨架效果xml与目标view进行绑定
3. 添加一些效果属性,比如:动画时间、是否开启展示动画、动画颜色等
4. 在合适的实际关闭骨架屏效果
二、Skeleton源码实现
Skeleton提供两个绑定方法,分别绑定普通View与RecyclerView,分别返回对应的Builder
1 | public class Skeleton { |
我们首先来看看如何实现与普通View绑定,构造方法中传入目标View,并对shimmer动画效果设置默认的颜色,在Builder里面我们可以看到各种相关参数的设定。
1 | public Builder(View view) { |
接下来再到show的步骤,主要实现还是由ViewSkeletonScreen来实现
1 | public ViewSkeletonScreen show() { |
其中ViewSkeletonScreen与绑定的RecyclerViewSkeletonScreen都实现了SkeletonScreen接口,SkeletonScreen有两个接口方法分别是
void show();
void hide();
对于ViewSkeletonScreen.show()进入源码,这里出现一个比较重要的类ViewReplacer
,等下再进行解析,通过show的源码清楚的知道逻辑:
1、生成骨架效果View
2、利用生成的View替换目标View。
其中生成骨架效果View阶段主要还是通过LayoutInflater去加载传入mSkeletonResID
1 |
|
接下来主要讲解ViewReplacer类,其构造方法传入目标View
1 | public ViewReplacer(View sourceView) { |
其比较重要的方法有两个:replace()
和 restore()
这两个方法分别为SkeletonScreen 的show()和hide()的最终实现,首先看replace()
方法,有两个方法重载,分别传入targetViewResID
或者targetView
,最终还是会走到replace(View targetView)
中。
其主要逻辑为:
1. 判断所替换的View和骨架屏效果View是否为同一个View
2. remove掉在父布局中的目标View
3. 将骨架屏效果View添加到目标View的父布局中
1 | public void replace(int targetViewResID) { |
在执行添加到目标View的父布局中,有执行一个init方法,主要做两件事:
1. 获取目标View的父View
2. 找到目标View在父View 中的位置索引,为之后添加骨架屏View到父View中做铺垫
1 | private boolean init() { |
至此对普通View的骨架屏效果实现流程已经完全梳理完成,那对于RecyclerView
呢?其实两者实现逻辑差不多,主要有两个差异:
- 在
RecyclerViewSkeletonScreen
的Builder中,相比ViewSkeletonScreen多了一个adapter()方法,传入目标RecyclerView
的Adapter
- 在show的时候对目标
RecyclerView
的adapter进行替换,使用骨架屏效果的adapter。hide的时候恢复为原先的Adapter
3、总结
- Skeleton的原理主要是通过替换目标View和RecyclerView的Adapter
- 在Skeleton的使用过程中最需要关心的两个问题是:show()和hide()的时机
- 对于整个页面的骨架屏效果实现,个人推荐在布局中添加一个全屏的空View盖在原先内容上
- 注意一些异常情况下的hide(),要不然整个页面就“假死”状态了。