首頁的加載效率直接影響了用戶的體驗,經過仔細分析,發現我們首頁有2個性能較差的控件:
CfgBanner
和ViewFlipper
。這章將通過對CfgBanner
深入優化,達到優化首頁加載的目的。
現存問題
先上一張圖,看一下目前的CfgBanner
渲染架構
CfgBanner架構
目前存在的問題:
- 一個
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();
}
- 原本簡潔的
List
由于這樣的架構不得不拆分成幾個單獨的List
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:253
List<List<ModuleCfgItem>> pagesDataList = createPagesData(mModuleList);
- 頁面由于特殊item(頭部),不得不進行單獨適配,注意這里的
CfgQualifyingPagesView
和CfgPagesView
// 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);
}
- 控件本身內聚性不夠高,數據、布局全耦合在一起
// 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);
- 同樣耦合的還有業務邏輯
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:285
public boolean haveGuideQualifyingR() {
try {
if (mPosition == 0 && mPagesList != null) {
- 結合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>{}
- 擴展性很低
步數寶需要把特殊的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);
}
- item改變會引起全部view的重繪
// app/src/main/java/com/xianlai/protostar/hall/view/CfgBanner.java -- line:258
mPagesList.clear();
//更新當前頁面數據
mCurPagesDataList.clear();
mCurPagesDataList.addAll(pagesDataList);
解決方案
同樣先看一張圖吧
RecycleView + GridLayout
不過癮,直接看動圖吧
原理展示
效果展示
- 針對控件本身的架構問題,通過一個
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
}
}
- 針對效率問題,直接使用
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)
- 針對多種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)
}
- 針對業務邏輯耦合問題,
adapter
只負責數據,holder
只負責界面(會有多種holder
) - 針對擴展性,由于使用的是標準的
GridLayout
,對布局的數量(每一行的數量)完全可以按照需求來擴展(理論上限為屏幕像素)。
同樣adapter
,Holder
可以完全單獨成為一個文件,代碼的復用將會得到極大的提高。