首页优化

首页的加载效率直接影响了用户的体验,经过仔细分析,发现我们首页有2个性能较差的控件:CfgBannerViewFlipper。这章将通过对CfgBanner深入优化,达到优化首页加载的目的。

现存问题

先上一张图,看一下目前的CfgBanner渲染架构

CfgBanner架构

目前存在的问题:

  1. 一个PageView嵌套了多了RecycleView,并且RecycleView的回收复用机制完全没有发挥作用,每一次item的变动都会引起整个控件的重新渲染:
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:358
public void setData(List moduleList, boolean loading) {
    this.mLoading = loading;
    if (moduleList == null) {
        return;
    }
    mModuleList.clear();
    mModuleList.addAll(moduleList);
    showList();
}
  1. 原本简洁的List由于这样的架构不得不拆分成几个单独的List
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:253
List> pagesDataList = createPagesData(mModuleList);
  1. 页面由于特殊item(头部),不得不进行单独适配,注意这里的CfgQualifyingPagesViewCfgPagesView
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:277
CfgPagesView pageView;
if (CenterFg.Qualifying) {
    pageView = new CfgQualifyingPagesView(mPagesList.size(),getContext(), mPool);
} else {
    pageView = new CfgPagesView( mPagesList.size(),getContext(), mPool);
}
  1. 控件本身内聚性不够高,数据、布局全耦合在一起
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:238
private void showList() {
    //必须有数据并且获取到了可配置区域的高度
    if (mModuleList.size() != 0 && mContentHeight != -1) {
        if (mFristPageItemsCount == -1) {
            //计算页面数据
            calcPageData();
        }
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:383
private void calcPageData() {
    if (mFristPageItemsCount == -1) {
        synchronized (this) {
            if (mFristPageItemsCount == -1) {
                int itemHeight = (int) getContext().getResources().getDimension(R.dimen.cfgModuleHeight);
                //图片文字高度,粗略计算
                int topMargin = (int) getContext().getResources().getDimension(R.dimen.cfgMainModuleAndTextHeight);
                int btmMargin = (int) getContext().getResources().getDimension(R.dimen.cfgPaddingBtn);
  1. 同样耦合的还有业务逻辑
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:285
public boolean haveGuideQualifyingR() {
    try {
        if (mPosition == 0 && mPagesList != null) {
  1. 结合item有多种情况,逻辑变得异常复杂
// app/src/main/java/com/xianlai/protostar/hall/adapter/BaseHallItemAdapter.java
public abstract class BaseHallItemAdapter extends RecyclerView.Adapter{}

// app/src/main/java/com/xianlai/protostar/hall/adapter/HallAnimItemAdapter.java
public class HallAnimItemAdapter extends BaseHallItemAdapter{}

// app/src/main/java/com/xianlai/protostar/hall/adapter/HallFolderItemAdapter.java
public class HallFolderItemAdapter extends BaseHallItemAdapter {}

// app/src/main/java/com/xianlai/protostar/hall/adapter/HallItemAdapter.java
public class HallItemAdapter extends BaseHallItemAdapter{}
  1. 扩展性很低
    步数宝需要把特殊的item去掉,这边都只能使用取巧的方式,把第一页的数据直接暴力移除;
    由于业务逻辑也被耦合在里面了,新建步数宝的时候只能是选择复制粘贴
// app/src/main/java/com/xianlai/protostar/hall/view/CfgPagesView.java
private void processPages(List list, int position) {
    if (position == 0 && withMain) {
        //首页处理
        //从list中移除主配置的数据
        initMainPage(list);
    } 
  1. item改变会引起全部view的重绘
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java  -- line:258
mPagesList.clear();
//更新当前页面数据
mCurPagesDataList.clear();
mCurPagesDataList.addAll(pagesDataList);

解决方案

同样先看一张图吧


RecycleView + GridLayout

不过瘾,直接看动图吧


原理展示

效果展示
  1. 针对控件本身的架构问题,通过一个RecycleView加以解决,至于“特殊”的顶部,通过GridLayout把它变成普通的item
// app/src/main/java/com/abelhu/MainActivity.kt
recyclerView.layoutManager = PagerLayoutManager(12) {
    when (it) {
        37 -> SlideAdapter.TYPE_1
        in 0..1 -> SlideAdapter.TYPE_2
        in 18..20 -> SlideAdapter.TYPE_3
        in 46..51 -> SlideAdapter.TYPE_6
        in 56..58 -> SlideAdapter.TYPE_3
        else -> SlideAdapter.TYPE_4
    }
}
  1. 针对效率问题,直接使用RecycleView的缓存池就可以了
// app/src/main/java/com/abelhu/MainActivity.kt
// 离屏缓存,并不会放入回收池,在反向滑动的时候保证item**不会**经过onBindViewHolder过程直接显示出来
recyclerView.setItemViewCacheSize(0)
// 根据每屏最多显示的item数量,设置其缓存阈值
recyclerView.recycledViewPool.setMaxRecycledViews(SlideAdapter.TYPE_6, 20)
recyclerView.recycledViewPool.setMaxRecycledViews(SlideAdapter.TYPE_4, 20)
recyclerView.recycledViewPool.setMaxRecycledViews(SlideAdapter.TYPE_3, 4)
recyclerView.recycledViewPool.setMaxRecycledViews(SlideAdapter.TYPE_2, 4)
recyclerView.recycledViewPool.setMaxRecycledViews(SlideAdapter.TYPE_1, 4)
  1. 针对多种item,「锁+红点+进度」合并为单独控件,同时给「静态+动图」icon使用,「九宫格」控件单独优化
// src/main/java/com/abelhu/lockitem/LockItem.kt
override fun onDraw(canvas: Canvas) {
    // 完成需要裁减的绘制
    val normalLayer = canvas.saveLayer(0f, 0f, measuredWidth.toFloat(), uredHeight.toFloat(), paint, Canvas.ALL_SAVE_FLAG)
    super.onDraw(canvas)
    if (showLock) drawLock(canvas)
    drawCorners(canvas)
    // 恢复图层
    canvas.restoreToCount(normalLayer)
    // 绘制不受圆角影响的圆点
    if (dotNumber > 0) drawDot(canvas)
}
  1. 针对业务逻辑耦合问题,adapter只负责数据,holder只负责界面(会有多种holder
  2. 针对扩展性,由于使用的是标准的GridLayout,对布局的数量(每一行的数量)完全可以按照需求来扩展(理论上限为屏幕像素)。
    同样adapter, Holder可以完全单独成为一个文件,代码的复用将会得到极大的提高。

前后对比

你可能感兴趣的:(首页优化)