首页的加载效率直接影响了用户的体验,经过仔细分析,发现我们首页有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 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> 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 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{}
- 扩展性很低
步数宝需要把特殊的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);
}
- 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
可以完全单独成为一个文件,代码的复用将会得到极大的提高。