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的使用方式
展示骨架屏效果:View rootView = findViewById(R.id.rootView);
skeletonScreen = Skeleton.bind(rootView)
.load(R.layout.activity_view_skeleton)//骨架屏UI
.duration(1000)//动画时间,以毫秒为单位
.shimmer(true)//是否开启动画
.color(R.color.shimmer_color)//shimmer的颜色
.angle(30)//shimmer的倾斜角度
.show();
关闭骨架屏效果并展示原有View:skeletonScreen.hide()
流程:
**1. 选择需要替换的目标view
- 将骨架效果xml与目标view进行绑定
- 添加一些效果属性,比如:动画时间、是否开启展示动画、动画颜色等
- 在合适的实际关闭骨架屏效果**
二、Skeleton源码实现
Skeleton提供两个绑定方法,分别绑定普通View与RecyclerView,分别返回对应的Builderpublic class Skeleton {
public static RecyclerViewSkeletonScreen.Builder bind(RecyclerView recyclerView) {
return new RecyclerViewSkeletonScreen.Builder(recyclerView);
}
public static ViewSkeletonScreen.Builder bind(View view) {
return new ViewSkeletonScreen.Builder(view);
}
}
我们首先来看看如何实现与普通View绑定,构造方法中传入目标View,并对shimmer动画效果设置默认的颜色,在Builder里面我们可以看到各种相关参数的设定。public Builder(View view) {
this.mView = view;
this.mShimmerColor = ContextCompat.getColor(mView.getContext(), R.color.shimmer_color);
}
接下来再到show的步骤,主要实现还是由ViewSkeletonScreen来实现public ViewSkeletonScreen show() {
ViewSkeletonScreen skeletonScreen = new ViewSkeletonScreen(this);
skeletonScreen.show();
return skeletonScreen;
}
其中ViewSkeletonScreen与绑定的RecyclerViewSkeletonScreen都实现了SkeletonScreen接口,SkeletonScreen有两个接口方法分别是
void show();
void hide();
对于ViewSkeletonScreen.show()进入源码,这里出现一个比较重要的类ViewReplacer,等下再进行解析,通过show的源码清楚的知道逻辑:
1、生成骨架效果View
2、利用生成的View替换目标View。
其中生成骨架效果View阶段主要还是通过LayoutInflater去加载传入mSkeletonResID
|
接下来主要讲解ViewReplacer类,其构造方法传入目标View
public ViewReplacer(View sourceView) { |
其比较重要的方法有两个:replace() 和 restore() 这两个方法分别为SkeletonScreen 的show()和hide()的最终实现,首先看replace()方法,有两个方法重载,分别传入targetViewResID或者targetView,最终还是会走到replace(View targetView)中。
其主要逻辑为:
**1. 判断所替换的View和骨架屏效果View是否为同一个View
- remove掉在父布局中的目标View
- 将骨架屏效果View添加到目标View的父布局中**
public void replace(int targetViewResID) { |
在执行添加到目标View的父布局中,有执行一个init方法,主要做两件事:
**1. 获取目标View的父View
- 找到目标View在父View 中的位置索引,为之后添加骨架屏View到父View中做铺垫**
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(),要不然整个页面就“假死”状态了。