前言
如何高效并簡(jiǎn)潔的使用
listview,首先自然是關(guān)心如何對(duì)BaseAdapter定制。站在代碼抽象的角度,子類對(duì)接口進(jìn)行實(shí)現(xiàn),父類應(yīng)該做好一切需要的工作。點(diǎn)擊此處,源碼已上傳github
通過(guò)繼承我的BaeListAdapter,使用時(shí)不再需要關(guān)心view的復(fù)用和ViewHolder的實(shí)例化和convertView實(shí)例化,也不用關(guān)系多個(gè)ViewHolder的類型檢測(cè),這一切都是自動(dòng)的,而只需要實(shí)現(xiàn)自定義ViewHolder的setView()方法數(shù)據(jù)綁定,具體實(shí)現(xiàn)請(qǐng)看下文。
如何定制自己的ListView綁定的Adapter
下面是繼承BaseAdapter的getView()方法中一段喜聞樂(lè)見(jiàn)的代碼:
public View getView(int position, View convertView, ViewGroup parent) {
ItemHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.home_city_list_item, null);
holder = new ItemHolder();
//bind holder views
convertView.setTag(holder);
} else {
holder = (ItemHolder) convertView.getTag();
}
//set holder views data
return convertView;
}
為了對(duì)listview的item view進(jìn)行復(fù)用我們往往會(huì)在自己的adapter中寫(xiě)這樣類似的重復(fù)代碼,然而這一切都可以在父類中進(jìn)行實(shí)現(xiàn),子類只需要實(shí)現(xiàn)具體的數(shù)據(jù)綁定功能。
下面是一段繼承自我自定義的BaseListAdapter的代碼。
class ListAdapter extends BaseListAdapterExt<PayMentInfoBean> {
public ListAdapter(Context context) {
super(context);
}
@Override
protected void onBindViewHolder(List<ViewBundle> list) {
list.add(new ViewBundle(R.layout.item_card_rentpay, ViewHolder.class));
//此處只需要傳遞ViewHolder的類型,和對(duì)應(yīng)layoutId
//如果有多個(gè)ViewType時(shí)可以add多個(gè)不同的ViewHolder子類和對(duì)應(yīng)layout的綁定關(guān)系
}
}
BaseListAdapter是一個(gè)模板類持有一個(gè)泛型T,它用來(lái)裝載List中的實(shí)例化類型,
private List<T> list = new ArrayList<>();
通過(guò)繼承BaseListAdapter在實(shí)現(xiàn)子類的時(shí)候通過(guò)實(shí)例化泛型傳入具體類型,并重寫(xiě)
onBindViewHolder()方法,可以輕易地完整實(shí)現(xiàn)一個(gè)高效可用的自定義Adapter,然后可以愉快的通過(guò)LIstView的setAdapter()方法進(jìn)行綁定了。然而這些并不夠,通過(guò)該方法還可以傳入多個(gè)不同的viewType實(shí)現(xiàn)一些復(fù)雜的多樣式視圖,通過(guò)onBindViewHolder傳遞給子類的List<ViewBundle> list參數(shù),可以add多個(gè)不同的ViewHolder類型,例如像這樣:
@Override protected void onBindViewHolder(List<ViewBundle> list) {
list.add(new ViewBundle(R.layout.list_item_0, VHtype1.class));
list.add(new ViewBundle(R.layout.list_item_1, VHtype2.class));
list.add(new ViewBundle(R.layout.list_item_2, VHtype3.class));
}
當(dāng)如重寫(xiě)getItemViewType()方法也是必要的,而type的位置就是List<ViewBundle>list中 ViewHolder類型所對(duì)應(yīng)的位置了。其實(shí)這里可以進(jìn)一步的抽象,在ViewHolder.class通過(guò)注解對(duì)layoutId關(guān)聯(lián),這樣只需要add對(duì)應(yīng)的class就OK了。具體可以參考這個(gè)項(xiàng)目的源碼落和APP。
@Overridepublic int getItemViewType(int position) {
return super.getItemViewType(position);
}
其實(shí)看到這里實(shí)現(xiàn)一個(gè)adapter已經(jīng)很簡(jiǎn)單了,如果這個(gè)框架只是實(shí)現(xiàn)了對(duì)子類的一個(gè)抽象顯得沒(méi)什么太大意義。在這里還有一個(gè)對(duì)adpter和ViewHolder類進(jìn)行解耦的過(guò)程,因?yàn)閿?shù)據(jù)綁定的方法應(yīng)該是ViewHolder的對(duì)象的職能而不應(yīng)該在Adapter中進(jìn)行實(shí)現(xiàn),這里面向?qū)ο笏枷牒苤匾?!下面是adpter和ViewHolder進(jìn)一步解耦。
ViewHolder的實(shí)現(xiàn):
class ViewHolder extends BaseListAdapterExt.BaseViewHolder<HouseDetail.PayMentInfoBean> {
@Bind(R.id.tv1)
TextView textView1;
@Bind(R.id.tv2)
TextView textView2;
@Bind(R.id.tv3)
TextView textView3;
@Bind(R.id.tv4)
TextView textView4;
@Override
protected void setView(HouseDetail.PayMentInfoBean bean, Context context) {//這里設(shè)置需要的數(shù)據(jù)
textView1.setText(bean.rentPaywayName);
textView2.setText(bean.serviceFee);
textView3.setText(bean.guarantee);
textView4.setText(bean.rentPrice);
}
}
在ViewHolder.class中加入了一個(gè)setview方法,并從父類中傳過(guò)來(lái)T bean,Context context兩參數(shù),毫無(wú)疑問(wèn)我們?cè)谶@里進(jìn)行數(shù)據(jù)綁定,至此,解耦過(guò)程完成。寫(xiě)法類似于recycleview ViewHolder中寫(xiě)法。setView方法是整個(gè)listview進(jìn)行數(shù)據(jù)綁定真正需要實(shí)現(xiàn)的方法,也是唯一的一個(gè)。剩下的就只有一些class定義的代碼,由于是abstract方法和泛型參數(shù),IDE會(huì)給你提供一切,這一切代碼都是自動(dòng)生成的,除了Adapter和ViewHolder子類的名字。
為什么高效簡(jiǎn)約?可以具體查看源碼的實(shí)現(xiàn)或繼續(xù)閱讀。
BaseListAdapter實(shí)現(xiàn)
public abstract class BaseListAdapter<T> extends BaseAdapter
BaseListAdapter是一個(gè)模板類繼承Android SDK中的BaseAdapter,里面可以看到一個(gè)成員對(duì)象
private List<T> list = new ArrayList<>();
通過(guò)該對(duì)象裝載Adater中所需要的數(shù)據(jù),經(jīng)常看到有朋友在Adapter的外部持有Adapter中l(wèi)ist引用用來(lái)改變Adapter中的數(shù)據(jù),其實(shí)這樣會(huì)導(dǎo)致兩個(gè)對(duì)象同時(shí)對(duì)Adapter中的數(shù)據(jù)進(jìn)行改變,風(fēng)險(xiǎn)是不可控的,而且外部的list生命周期在長(zhǎng)于Adapter的時(shí)候會(huì)導(dǎo)致list來(lái)不及內(nèi)存回收,如果做了一些非法的操作很容易使得list對(duì)象這塊內(nèi)存逃逸出去,比如用一個(gè)static的引用去指向它。在Adapter中增加一些數(shù)據(jù)改變的方法可以避免這一切情況的出現(xiàn),而且很方便。
/**
* 初始化list
*
* @param list
*/
public void initList(List<T> list) {
if (this.list.size() > 0) {
this.list.clear();
}
this.list.addAll(list);
notifyDataSetChanged();
}
/**
* add the list ,but no clear
*
* @param list
*/
public void addList(List<T> list) {
this.list.addAll(list);
notifyDataSetChanged();
}
/**
* clear all list
*/
public void clearList() {
this.list.clear();
notifyDataSetChanged();
}
```
通過(guò)這些方法使得`Adapter`才真正成為`data controller`, 在每次需要數(shù)據(jù)操作的時(shí)候?qū)Adapter`進(jìn)行發(fā)送消息。
下面就是真正源碼抽象的過(guò)程了:
##1. 首先是getView()方法的抽象
```java
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder viewHolder = null;
if (convertView == null) {
viewHolder = onCreateViewHolder(position, getViewBundles());
convertView = createView(position, viewHolder);
} else {
viewHolder = (BaseViewHolder) convertView.getTag();
}
if (convertView == null || convertView.getTag() == null)
throw new NullPointerException(" creatview fails");
onSetViewHolder(position, getItem(position), viewHolder);
return convertView;
}
```
在`getView`中通過(guò)`position`參數(shù)實(shí)例化對(duì)應(yīng)的`viewHolder`對(duì)象`onCreateViewHolder`方法會(huì)call`getItemViewType`方法通過(guò)子類實(shí)現(xiàn)的`List<ViewBundle> ViewBundles`對(duì)象取得需要的ViewHolder.class類型然而通過(guò)反射去實(shí)例化。
```java
protected BaseViewHolder onCreateViewHolder(int pos,List<ViewBundle> ViewBundles){
Class<? extends BaseViewHolder> clazz = ViewBundles
.get(getItemViewType(pos)).vHClazz;
return ViewUtil.getInstance(clazz);
}
拿到ViewHolder對(duì)象后就可以開(kāi)始進(jìn)行相應(yīng)的數(shù)據(jù)set了,
onSetViewHolder(position, getItem(position), viewHolder);這個(gè)是set data的方法:
/**
* set data for viewHolder by viewType(through by getItemViewType)
*
* @param position
* @param bean
* @param baseViewHolder
*/
protected void onSetViewHolder(int position, T bean,
BaseViewHolder baseViewHolder) {
baseViewHolder.setView(bean, getContext());
}
在onSetViewHolder方法中可以看到回去調(diào)用ViewHolder父類的setView()方法,下面是BaseViewHolder的代碼,通過(guò)baseViewHolder.setView(bean, getContext());可以調(diào)用子類的具體實(shí)現(xiàn),這樣就解耦了adapter和viewHolder之間的關(guān)聯(lián),并且不需要關(guān)注viewHolder的對(duì)象具體類型(因?yàn)樵趃etViewTypeCount!=1的時(shí)候會(huì)在onBindViewHolder(List<ViewBundle> list)中綁定多個(gè)BaseViewHolder子類的類型),其實(shí)就是java多態(tài)的使用。
public static abstract class BaseViewHolder<T> {
public BaseViewHolder() {
}
protected abstract void setView(T bean, Context context);
public Class getClassTag() {
return this.getClass();
}
}
2. ViewBundle的職能?
在子類實(shí)現(xiàn)中有這樣的一個(gè)方法onBindViewHolder(List<ViewBundle> list),ViewBundle的作用是什么呢?
public static class ViewBundle {
public ViewBundle(int layoutId, Class<? extends BaseViewHolder> clazz) {
this.layoutId = layoutId;
this.vHClazz = clazz;
}
public Class<? extends BaseViewHolder> vHClazz;
public int layoutId;
@Override
public String toString() {
return "ViewBundle{" + "vHClazz=" + vHClazz + ", layoutId="
+ layoutId + '}';
}
}
可以看到其實(shí)是一個(gè)layoutId和viewHolder類型的對(duì)應(yīng)關(guān)系,通過(guò)list將數(shù)組對(duì)象傳遞給父類,通過(guò)下標(biāo)取得對(duì)應(yīng)的class值。
到這里代碼解析全部完畢,主要工作就是Adapter的簡(jiǎn)單定制,讓子類的具體實(shí)現(xiàn)最小化。這樣的處理在開(kāi)發(fā)中可以減少相應(yīng)的繁瑣重復(fù)工作并降低錯(cuò)誤率,父類中輸出了輸出了必要的log并在子類實(shí)現(xiàn)除錯(cuò)的情況跑出了對(duì)應(yīng)的異常,可以很快的定位原因。
有問(wèn)題請(qǐng)聯(lián)系我:ganquan3640@gmail.com。