首頁優化

首頁的加載效率直接影響了用戶的體驗,經過仔細分析,發現我們首頁有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<ModuleCfgItem> 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<List<ModuleCfgItem>> 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<VH extends BaseHallItemViewHolder> extends RecyclerView.Adapter<VH>{}

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

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

// app/src/main/java/com/xianlai/protostar/hall/adapter/HallItemAdapter.java
public class HallItemAdapter extends BaseHallItemAdapter<MyViewHolder>{}
  1. 擴展性很低
    步數寶需要把特殊的item去掉,這邊都只能使用取巧的方式,把第一頁的數據直接暴力移除;
    由于業務邏輯也被耦合在里面了,新建步數寶的時候只能是選擇復制粘貼
// app/src/main/java/com/xianlai/protostar/hall/view/CfgPagesView.java
private void processPages(List<ModuleCfgItem> 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可以完全單獨成為一個文件,代碼的復用將會得到極大的提高。

前后對比

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容