Android骨架屏的实现

3,273 阅读2分钟

来源:APP研发 - 毕霞

实现思路

目前我们项目用到的骨架屏在列表页及商品详情页,所以我的项目里将骨架屏分为两个模块,一个是针对list这种多个view要预加载的,另一种针对一个页面某个view需要预加载的情况。

图片描述

List部分

在Android端,通常用RecyclerView+Adapter+ViewHolder来实现列表加载视图,Adapter作为适配器用来将各种数据以合适的形式显示到RecyclerView上,

图片
初步想法有以下几种方案:

  • 直接在原来的布局上进行修改,将骨架屏的布局放置到当前布局上层,通过控制显示隐藏来实现
    • 这样实现和业务耦合性太强,已有的代码和逻辑改动较大,风险也太大

图片描述

  • 新建骨架屏的ViewHolder,重新写一个骨架屏item的布局,通过判断是否加载完成控制绑定ViewHolder,没加载完成绑定骨架屏ViewHolder,加载完成后绑定正常数据的ViewHolder
    • 缺点:也是和直接改布局一样,会动到很多以前的逻辑和代码

图片描述

  • 新建骨架屏通用Adapter,加载数据时使用骨架屏Adapter,加载完成后替换为正常的Adapter
    • 我们需要用最小的代价,在原有的基础上添加骨架屏,所以我选择在adapter上做文章,这样既可以将业务和骨架屏代码分离,又不用写大量判断的代码。

图片描述

单个View部分

实现的思路是拿到要替换部分的ViewA,获取他的父类ViewGroup,从父类里移除ViewA,再将骨架屏ViewB添加进ViewA所处的位置,加载结束之后再移除ViewB,添加ViewA。

图片描述

##具体实现 画了个粗略的类图:

图片描述

核心代码

RecyclerViewSkeleton.kt

override fun show() {
        skeletonAdapter.isGrid = displayMode == GRID_MODE
        recyclerView?.let {
            val gridLayoutManager = GridLayoutManagerWrapper(it.context, 2)
            gridLayoutManager.orientation = LinearLayoutManager.VERTICAL
            recyclerView?.layoutManager = if (displayMode == GRID_MODE) gridLayoutManager else LinearLayoutManager(it.context)
            recyclerView?.adapter = skeletonAdapter
        }
    }

    override fun hide() {
        if (recyclerView?.adapter != mActualAdapter) {
            recyclerView?.adapter = mActualAdapter
        }
    }

ViewSkeleton.kt

	override fun show() {
        rootView?.let {
            if (it.parent != null) {
                parentView = (it.parent as ViewGroup)
                if (shimmerViewIds.isNotEmpty()) {
                    for (shimmerViewId in shimmerViewIds) {
                        initShimmerLayout(targetView?.findViewById(shimmerViewId))
                    }
                }
                val index = parentView?.indexOfChild(it) ?: -1
                if (index != -1) {
                    parentView?.removeView(it)
                    parentView?.addView(targetView, index)
                }
            }
        }
    }
    
    override fun hide() {
        val index = parentView?.indexOfChild(targetView) ?: -1
        if (index != -1) {
            parentView?.removeView(targetView)
            parentView?.addView(rootView, index)
        }
    }

在项目中的运用

主要是在v2/ProductActivity.kt 以及SearchResultActivity.kt中使用

闪光动画部分

动画部分用的第三方的库ShimmerLayout,可以设置闪光颜色,角度,动画持续时间等功能,通过startShimmerAnimation()和stopShimmerAnimation()控制动画开始和结束,基本能满足当前产品设计的需求,就不展开赘述了:

shimmerLayout.setShimmerColor(Color.argb(178, 255, 255, 255))
shimmerLayout.setShimmerAngle(30)
shimmerLayout.setShimmerAnimationDuration(1500)
shimmerLayout.startShimmerAnimation()