andoid Skeleton Screen框架源码分析(一)

骨架屏可以理在为页面完全渲染完成之前,用户会看到一个样式简单,描绘当前页面的大致框架的骨架屏页面,然后骨架屏中各个占位部分被实际资源完全替换。更符合用户体验。 它的优点: 入侵程度较小,不用修改基本控件,对业务逻辑不造成影响。 使用简单,功能强大,支持列表,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又设置了我们自己真实的适配器。

相关推荐
小墙程序员9 分钟前
kotlin元编程(一)一文理解 Kotlin 反射
android·kotlin·android studio
fatiaozhang95271 小时前
创维智能融合终端DT741_移动版_S905L3芯片_安卓9_线刷固件包
android·电视盒子·刷机固件·机顶盒刷机
小林学Android3 小时前
Android四大组件之Activity详解
android
搬砖不得颈椎病3 小时前
Jetpack DataStore vs SharedPreferences:现代Android数据存储方案对比
android
auxor5 小时前
Android 窗口管理 - 窗口添加过程分析Client端
android
雨白6 小时前
HTTP协议详解(一):工作原理、请求方法与状态码
android·http
Yang-Never7 小时前
Kotlin -> object声明和object表达式
android·java·开发语言·kotlin·android studio
小白马丶7 小时前
Jetpack Compose开发框架搭建
android·前端·android jetpack
攻城狮Talk7 小时前
FocusParkingView清除旧Window焦点
android
狂浪天涯7 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 8 | SurfaceFlinger 合成 (一)
android