優(yōu)雅地使用ListView--一句代碼定制Adapter

前言

如何高效并簡(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)自定義ViewHoldersetView()方法數(shù)據(jù)綁定,具體實(shí)現(xiàn)請(qǐng)看下文。

如何定制自己的ListView綁定的Adapter

下面是繼承BaseAdaptergetView()方法中一段喜聞樂(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ì)listviewitem 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容