骨架屏可以理在为页面完全渲染完成之前,用户会看到一个样式简单,描绘当前页面的大致框架的骨架屏页面,然后骨架屏中各个占位部分被实际资源完全替换。更符合用户体验。 它的优点: 入侵程度较小,不用修改基本控件,对业务逻辑不造成影响。 使用简单,功能强大,支持列表,View。
1 在列表页中使用:
scss
final SkeletonScreen skeletonScreen = SkeletonStrategy.bind(recyclerView)
//设置加载列表适配器,
.adapter(adapter)
//默认是开启光晕动画
.shimmer(true)
.angle(20)
.frozen(false)
.duration(1200)
.count(10)
//骨架屏样式item
.load(R.layout.item_skeleton_news)
.show(); //default count is 10
当加载完数据需要隐藏
ini
skeletonScreen.hide();
源码分析: 先一个策略类(我修改了类名):
typescript
public class SkeletonStrategy {
//这里是支持RecyclerView列表模式的
public static RecyclerViewSkeletonScreen.Builder bind(RecyclerView recyclerView) {
return new RecyclerViewSkeletonScreen.Builder(recyclerView);
}
//这里是支持替换目标View
public static ViewSkeletonScreen.Builder bind(View view) {
return new ViewSkeletonScreen.Builder(view);
}
}
从我们show开始分析:
scss
final SkeletonScreen skeletonScreen = SkeletonStrategy.bind(recyclerView)
.adapter(adapter)
.shimmer(true)
.angle(20)
.frozen(false)
.duration(1200)
.count(10)
.load(R.layout.item_skeleton_news)
.show(); //default count is 10
当我们调用SkeletonStrategy的bind方法时,其实调用的是RecyclerViewSkeletonScreen.Builder的构造方法:
kotlin
public static class Builder {
private RecyclerView.Adapter mActualAdapter;
private final RecyclerView mRecyclerView;
private boolean mShimmer = true;
private int mItemCount = 10;
private int mItemResID = R.layout.layout_default_item_skeleton;
private int[] mItemsResIDArray;
private int mShimmerColor;
private int mShimmerDuration = 1000;
private int mShimmerAngle = 20;
private boolean mFrozen = true;
public Builder(RecyclerView recyclerView) {
this.mRecyclerView = recyclerView;
this.mShimmerColor = ContextCompat.getColor(recyclerView.getContext(), R.color.shimmer_color);
}
/**
* @param adapter the target recyclerView actual adapter
*/
public Builder adapter(RecyclerView.Adapter adapter) {
this.mActualAdapter = adapter;
return this;
}
/**
* @param itemCount the child item count in recyclerView
*/
public Builder count(int itemCount) {
this.mItemCount = itemCount;
return this;
}
/**
* @param shimmer whether show shimmer animation
*/
public Builder shimmer(boolean shimmer) {
this.mShimmer = shimmer;
return this;
}
/**
* the duration of the animation , the time it will take for the highlight to move from one end of the layout
* to the other.
*
* @param shimmerDuration Duration of the shimmer animation, in milliseconds
*/
public Builder duration(int shimmerDuration) {
this.mShimmerDuration = shimmerDuration;
return this;
}
/**
* @param shimmerColor the shimmer color
*/
public Builder color(@ColorRes int shimmerColor) {
this.mShimmerColor = ContextCompat.getColor(mRecyclerView.getContext(), shimmerColor);
return this;
}
/**
* @param shimmerAngle the angle of the shimmer effect in clockwise direction in degrees.
*/
public Builder angle(@IntRange(from = 0, to = 30) int shimmerAngle) {
this.mShimmerAngle = shimmerAngle;
return this;
}
/**
* @param skeletonLayoutResID the loading skeleton layoutResID
*/
public Builder load(@LayoutRes int skeletonLayoutResID) {
this.mItemResID = skeletonLayoutResID;
return this;
}
/**
* @param skeletonLayoutResIDs the loading array of skeleton layoutResID
*/
public Builder loadArrayOfLayouts(@ArrayRes int[] skeletonLayoutResIDs) {
this.mItemsResIDArray = skeletonLayoutResIDs;
return this;
}
/**
* @param frozen whether frozen recyclerView during skeleton showing
* @return
*/
public Builder frozen(boolean frozen) {
this.mFrozen = frozen;
return this;
}
public RecyclerViewSkeletonScreen show() {
RecyclerViewSkeletonScreen recyclerViewSkeleton = new RecyclerViewSkeletonScreen(this);
recyclerViewSkeleton.show();
return recyclerViewSkeleton;
}
}
可以Builder代码可以看到,我们通过链式代码调用到show之前,其实是完成对j静态类Builder的属性赋值,最后我们来看调用Vuilder#show时候:
csharp
public RecyclerViewSkeletonScreen show() {
RecyclerViewSkeletonScreen recyclerViewSkeleton = new RecyclerViewSkeletonScreen(this);//这里this是Builder对象
recyclerViewSkeleton.show();
return recyclerViewSkeleton;
}
可以看到这里通过Builder对象创建了RecyclerViewSkeletonScreen对象,然后show出来。
ini
public class RecyclerViewSkeletonScreen implements SkeletonScreen {
private final RecyclerView mRecyclerView;
private final RecyclerView.Adapter mActualAdapter;
private final SkeletonAdapter mSkeletonAdapter;
private final boolean mRecyclerViewFrozen;
//省略部分
private RecyclerViewSkeletonScreen(Builder builder) {
mRecyclerView = builder.mRecyclerView;
mActualAdapter = builder.mActualAdapter;
mSkeletonAdapter = new SkeletonAdapter();
mSkeletonAdapter.setItemCount(builder.mItemCount);
mSkeletonAdapter.setLayoutReference(builder.mItemResID);
mSkeletonAdapter.setArrayOfLayoutReferences(builder.mItemsResIDArray);
mSkeletonAdapter.shimmer(builder.mShimmer);
mSkeletonAdapter.setShimmerColor(builder.mShimmerColor);
mSkeletonAdapter.setShimmerAngle(builder.mShimmerAngle);
mSkeletonAdapter.setShimmerDuration(builder.mShimmerDuration);
mRecyclerViewFrozen = builder.mFrozen;
}
}
这里其实就是将Builder的变量值赋值给RecyClerViewSkeletonScreen的全局变量。然后调用show方法。
typescript
@Override
public void show() {
mRecyclerView.setAdapter(mSkeletonAdapter);
if (!mRecyclerView.isComputingLayout() && mRecyclerViewFrozen) {
mRecyclerView.setLayoutFrozen(true);
}
}
调用show方法,将SkeletonAdapter设置给了mRecyclerView,到此为止,一个骨架屏就显示出来了。 下面来看下骨架屏适配器:
typescript
public class SkeletonAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private int mItemCount;
private int mLayoutReference;
private int[] mLayoutArrayReferences;
private int mColor;
private boolean mShimmer;
private int mShimmerDuration;
private int mShimmerAngle;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (doesArrayOfLayoutsExist()) { //是否有多种布局item
mLayoutReference = viewType;
}
if (mShimmer) { //是否开启光晕动画
return new ShimmerViewHolder(inflater, parent, mLayoutReference);
}
//如果是单一布局Item和无光晕动画,就加载Builder里面的item布局实现骨架屏
return new RecyclerView.ViewHolder(inflater.inflate(mLayoutReference, parent, false)) {
};
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (mShimmer) { //不需要什么数据绑定
ShimmerLayout layout = (ShimmerLayout) holder.itemView;
layout.setShimmerAnimationDuration(mShimmerDuration);
layout.setShimmerAngle(mShimmerAngle);
layout.setShimmerColor(mColor);
layout.startShimmerAnimation();
}
}
@Override
public int getItemViewType(int position) {
if(doesArrayOfLayoutsExist()) {
return getCorrectLayoutItem(position);
}
return super.getItemViewType(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemCount() {
return mItemCount;
}
public void setLayoutReference(int layoutReference) {
this.mLayoutReference = layoutReference;
}
public void setArrayOfLayoutReferences(int[] layoutReferences) {
this.mLayoutArrayReferences = layoutReferences;
}
public void setItemCount(int itemCount) {
this.mItemCount = itemCount;
}
public void setShimmerColor(int color) {
this.mColor = color;
}
public void shimmer(boolean shimmer) {
this.mShimmer = shimmer;
}
public void setShimmerDuration(int shimmerDuration) {
this.mShimmerDuration = shimmerDuration;
}
public void setShimmerAngle(@IntRange(from = 0, to = 30) int shimmerAngle) {
this.mShimmerAngle = shimmerAngle;
}
public int getCorrectLayoutItem(int position) {
if(doesArrayOfLayoutsExist()) {
return mLayoutArrayReferences[position % mLayoutArrayReferences.length];
}
return mLayoutReference;
}
private boolean doesArrayOfLayoutsExist() {
return mLayoutArrayReferences != null && mLayoutArrayReferences.length != 0;
}
}
当我们数据加载完调用hide方法,
typescript
@Override
public void hide() {
mRecyclerView.setAdapter(mActualAdapter);
}
其实就是mRecyClerView又设置了我们自己真实的适配器。