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又设置了我们自己真实的适配器。

相关推荐
雨白5 小时前
Jetpack系列(三):Room数据库——从增删改查到数据库平滑升级
android·android jetpack
花王江不语8 小时前
android studio 配置硬件加速 haxm
android·ide·android studio
江太翁10 小时前
mediapipe流水线分析 三
android·mediapipe
与火星的孩子对话11 小时前
Unity进阶课程【六】Android、ios、Pad 终端设备打包局域网IP调试、USB调试、性能检测、控制台打印日志等、C#
android·unity·ios·c#·ip
tmacfrank12 小时前
Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
android·网络·https
fundroid13 小时前
Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
android·kotlin·协程
草字13 小时前
cocos 打包安卓
android
DeBuggggggg14 小时前
centos 7.6安装mysql8
android
浩浩测试一下15 小时前
渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集
android·前端·安全·web安全·网络安全·安全架构
移动开发者1号16 小时前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin