這篇文章只是簡(jiǎn)單記錄一下昨天遇到的一個(gè)問(wèn)題藤为。
ListView
和RecyclerView
的復(fù)用,應(yīng)該除了小白大家都知道了夺刑。但是昨天在使用下拉刷新加載數(shù)據(jù)的時(shí)候缅疟,我發(fā)現(xiàn)調(diào)用notifyDataSetChanged()
后loading動(dòng)畫(huà)會(huì)卡頓一下,約100-200毫秒遍愿。強(qiáng)迫癥患者肯定不能忍存淫,結(jié)合Choreographer
進(jìn)行排查,發(fā)現(xiàn)是在getView(...)
的時(shí)候沼填,inflate(...)
非常耗時(shí)桅咆。
大家熟知,如果是當(dāng)前已經(jīng)有了一頁(yè)數(shù)據(jù)坞笙,convertView可以直接復(fù)用岩饼。但如果當(dāng)前沒(méi)有數(shù)據(jù)呢?這時(shí)候convertView為null薛夜,就一定會(huì)走inflate(...)
導(dǎo)致掉幀籍茧。但如果我們能提前完成inflate(...)
,后面來(lái)數(shù)據(jù)了直接復(fù)用就可以避免這個(gè)問(wèn)題梯澜。
昨天經(jīng)過(guò)郭霖大佬點(diǎn)播將ListView
設(shè)置為INVISIBLE
寞冯,默認(rèn)加載一些空數(shù)據(jù)不顯示出來(lái),等正真的數(shù)據(jù)來(lái)了在設(shè)置為VISIBLE
。這算是一個(gè)比較好的思路了吮龄,提前inflate(...)
一頁(yè)View檬某,加載數(shù)據(jù)的時(shí)候直接復(fù)用即可。
但是我們現(xiàn)在來(lái)看一個(gè)使用場(chǎng)景:先隱藏加載了一頁(yè)空數(shù)據(jù)螟蝙,然后用戶條件篩選恢恼,獲取到數(shù)據(jù)集A,這時(shí)候數(shù)據(jù)集A為空胰默,那么這些隱藏的item不會(huì)被復(fù)用场斑,而是直接remove。然后用戶更改條件再次請(qǐng)求數(shù)據(jù)牵署,來(lái)了數(shù)據(jù)集B漏隐,這時(shí)候B不為空,getView(...)
的時(shí)候沒(méi)有可復(fù)用的convertView奴迅,依然會(huì)inflate(...)
導(dǎo)致掉幀青责。這時(shí)候INVISIBLE
大法就不好用了。
經(jīng)過(guò)仔細(xì)斟酌取具,我想如果在Adapter中自己緩存一個(gè)View集合脖隶,在前面這個(gè)使用場(chǎng)景,來(lái)數(shù)據(jù)集B的時(shí)候暇检,如果convertView為null不可復(fù)用产阱,我還可以從自己的緩存集合中獲取。下面放出示例代碼:
public class ExampleAdapter<T> extends BaseAdapter {
private final LayoutInflater mLayoutInflater;
private List<T> mDataList = new ArrayList<>();
private List<View> mConvertViewCaches = new ArrayList<>();
protected ExampleAdapter(Context context) {
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
if (mConvertViewCaches.size() == 0) {
return 999;
}
return mDataList.size();
}
@Override
public T getItem(int position) {
if (position >= 0 && position < mDataList.size()) {
return mDataList.get(position);
}
return null;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
for (View cache : mConvertViewCaches) {
if (cache.getParent() == null) {
convertView = cache;
break;
}
}
}
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.item_example, parent, false);
//省略viewHolder操作
convertView.setTag(viewHolder);
mConvertViewCaches.add(convertView);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
T item = getItem(position);
if (item != null) {
//省略viewHolder操作
}
return convertView;
}
}
代碼很簡(jiǎn)單块仆,就是多了一個(gè)mConvertViewCaches构蹬,用來(lái)緩存在getView(...)
中通過(guò)inflate(...)
解析出來(lái)的convertView,當(dāng)convertView為null無(wú)法復(fù)用時(shí)悔据,從mConvertViewCaches中獲取可以復(fù)用的convertView庄敛,以減少inflate(...)
操作。同時(shí)科汗,在getCount()
方法中通過(guò)判斷mConvertViewCaches的大小藻烤,來(lái)區(qū)分是否ListView是否第一次加載,如果是第一次加載肛捍,返回一個(gè)足夠大的數(shù)字來(lái)保證getView(...)
解析出足夠一頁(yè)的布局候用隐绵。另外注意避免數(shù)據(jù)越界以及做好非空判斷即可之众。
最后總結(jié)一下拙毫,盡量避免頻繁的inflate操作,尤其是有動(dòng)畫(huà)的情況下棺禾,掉幀非常明顯缀蹄。另外,RecyclerView
同ListView
,在Adapter中對(duì)convertView進(jìn)行緩存即可缺前,示例代碼就不放出了蛀醉。