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

相关推荐
野生的码农1 小时前
放过自己,降低预期,及时行乐
android·ai编程
huwuhang2 小时前
索尼PS3游戏合集【中文游戏】8.12T 1430个游戏+PS3模拟器
android·游戏·智能手机·游戏机·电视
Grackers4 小时前
Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
android
踩着两条虫4 小时前
AI驱动的Vue3应用开发平台深入探究(十):物料系统之内置组件库
android·前端·vue.js·人工智能·低代码·系统架构·rxjava
sam.li4 小时前
JADX MCP 原理与使用部署
android·逆向·jadx
冬奇Lab5 小时前
Android 15音频子系统(五):AudioPolicyService策略管理深度解析
android·音视频开发·源码阅读
亚历克斯神5 小时前
Flutter for OpenHarmony: Flutter 三方库 mutex 为鸿蒙异步任务提供可靠的临界资源互斥锁(并发安全基石)
android·数据库·安全·flutter·华为·harmonyos
dalancon7 小时前
SurfaceControl 的事务提交给 SurfaceFlinger,以及 SurfaceFlinger 如何将这些数据设置到对应 Layer 的完整流程
android
dalancon7 小时前
SurfaceFlinger Layer 到 HWC 通信流程详解
android
cccccc语言我来了7 小时前
Linux(9)操作系统
android·java·linux