android封裝通用Adapter、ViewHolder實(shí)現(xiàn)ListView多條目混排

先看下效果圖


處理多條目android給我們的方法是設(shè)置setViewTypeCount傳入類(lèi)型個(gè)數(shù),RecycleBin會(huì)創(chuàng)建對(duì)應(yīng)數(shù)量的mScrapViews集合數(shù)組,每種類(lèi)型的View在對(duì)應(yīng)的集合中管理。當(dāng)要告訴ListView我要顯示什么樣的UI布局時(shí)就得調(diào)用getItemViewType,給每個(gè)position指定要使用的ViewType類(lèi)型。
但是要注意如果返回錯(cuò)誤就會(huì)有問(wèn)題,例如你不能返回超過(guò)setViewTypeCount的值,否則會(huì)數(shù)組腳本越界。ListView根據(jù)getItemViewType就能找到緩存該ViewTypewhichScrap然后來(lái)渲染UI這就是android給我們提供的ListView多條目混排的實(shí)現(xiàn)方案,如果我們不進(jìn)行封裝,擴(kuò)展的話(huà),會(huì)導(dǎo)致一個(gè)Adapter中有N多個(gè)ViewType,getView的時(shí)候會(huì)根據(jù)positon返回N多種,然后再在getItemViewType方法中根據(jù)position返回不同的ViewType,還有定義不同的ViewHolder來(lái)管理。這樣就會(huì)導(dǎo)致我們的一個(gè)Adapter類(lèi)爆棚,滿(mǎn)屏都是if else想想就可怕。
下面就是用傳統(tǒng)的方法實(shí)現(xiàn)三種Item的adapter,我已經(jīng)精簡(jiǎn)了很多。每次添加新條目,就要改動(dòng)這個(gè)Adapter,擴(kuò)展性很差,當(dāng)代碼多了,很容易出bug,也不好查找。不符合開(kāi)放封閉原則

    public class ChatAdapter extends BaseAdapter {  
  
          //3種不同的布局  
        public static final int VALUE_TIME_TIP = 0;
        public static final int VALUE_LEFT_TEXT = 1;  
        public static final int VALUE_LEFT_IMAGE = 2;   

        public ChatAdapter(Context context, List<Message> myList) {  
            this.myList = myList;  
      
            mInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
        }  
      
        @Override  
        public int getCount() {  
            return myList.size();  
        }  
      
        @Override  
        public Object getItem(int arg0) {  
            return myList.get(arg0);  
        }  
      
        @Override  
        public long getItemId(int arg0) {  
            return arg0;  
        }  
      
        @Override  
        public View getView(int position, View convertView, ViewGroup arg2) {  
      
            if (convertView == null) {  
                switch (type) {  
                case VALUE_TIME_TIP:  
                    ..
                case VALUE_LEFT_TEXT:  
                    ...
                case VALUE_LEFT_IMAGE:  
                    ...             
            } else {  
                Log.d("baseAdapter", "Adapter_:"+(convertView == null) );  
                switch (type) {  
                case VALUE_TIME_TIP:  
                    ... 
                case VALUE_LEFT_TEXT:  
                    ...  
                case VALUE_LEFT_IMAGE:  
                    ...  
                }  
            }  
            return convertView;  
        }  
      
         //根據(jù)數(shù)據(jù)源的position返回需要顯示的的layout的type 
        @Override  
        public int getItemViewType(int position) {  
      
            Message msg = myList.get(position);  
            int type = msg.getType();  
            return type;  
        }  
      
         //返回所有的layout的數(shù)量 
        @Override  
        public int getViewTypeCount() {  
            return 3;  
        }  
      
        class ViewHolderTime {  
           ...
        }  
      
        class ViewHolderRightText {  
           ...
        }  
      
        class ViewHolderRightImg {  
           ...
        }  
    }  

首先分析下ViewHolder的作用,通過(guò)convertView.setTag與convertView進(jìn)行綁定,然后當(dāng)convertView復(fù)用時(shí),直接從與之對(duì)應(yīng)的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的時(shí)間。這里我們先打造一個(gè)通用的Adapter并抽出一個(gè)公用的ViewHolder

    public abstract class CommonListAdapter<T> extends BaseAdapter {
        protected Context mContext;
        protected List<T> mDatas;
        public CommonListAdapter(Context context, List<T> datas) {
            mDatas = datas;
            mContext = context;
        }
    
        /**
         * @return 這里應(yīng)理解為UI的條目數(shù)量
         */
        @Override
        public int getCount() {
            return mDatas.size();
        }
    
        @Override
        public T getItem(int position) {
            return mDatas.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return getItemListView(position, convertView, parent);
        }
    
        private View getItemListView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = ViewHolder.get(mContext, convertView, parent, getItemLayoutId(position));
            onBindHolder(holder, position, parent);
            return holder.getItemView();
        }
        /**
         * 返回創(chuàng)建條目view所需的res
         *
         * @param position
         * @return
         */
        protected abstract int getItemLayoutId(int position);
        /**
         * 這里可以實(shí)現(xiàn)數(shù)據(jù)的加載,事件的添加,界面的顯隱等操作
         * @param holder
         * @param position 對(duì)應(yīng)的數(shù)據(jù)的位置
         * @param parent
         */
        public abstract void onBindHolder(ViewHolder holder, int position, ViewGroup parent);
    }

這樣我們就打造了一個(gè)通用的adapter,并把ViewHolder與Adapter分離,我們?cè)倏纯催@個(gè)通用的ViewHolder。既然是通用的,那我們就不能像以前一樣將成員變量寫(xiě)進(jìn)去了,因?yàn)槊總€(gè)Item的布局是不同的,那我們?cè)趺崔k呢?既然沒(méi)有,那我們就提供一個(gè)方法,幫調(diào)用者查找,同時(shí)維護(hù)一個(gè)集合,鍵為控件的Id,值為控件引用,集合里找不到我們?cè)偃indViewByid,這里我們用SparseArray,他比Map集合更高效,內(nèi)部是二分查找法。但是key只能是Integer,我們給每個(gè)mItemView設(shè)置一個(gè)Tag,再綁定一個(gè)ViewHolder對(duì)象(this)。

    public class ViewHolder {
        private SparseArray<View> mViews;
        private View mItemView;
    
        private ViewHolder(Context context, ViewGroup parent, int layoutId) {
            LayoutInflater inflater = LayoutInflater.from(context);
            mItemView = inflater.inflate(layoutId, parent, false);
            mViews = new SparseArray<View>();
    
            mItemView.setTag(R.id.item_holder, this);
        }
    
        public View getItemView() {
            return mItemView;
        }
    
        public static ViewHolder get(Context context, View itemView, ViewGroup parent, int layoutId) {
            if (itemView == null)
                return new ViewHolder(context, parent, layoutId);
            else
                return (ViewHolder) itemView.getTag(R.id.item_holder);
        }
    
        public <T extends View> T getView(int id) {
            View view = mViews.get(id);
            if (view == null) {
                view = mItemView.findViewById(id);
                mViews.put(id, view);
            }
            return (T) view;
        }
    
        public ViewHolder setText(int id, CharSequence sequence) {
            TextView view = getView(id);
            if (view != null)
                view.setText(sequence);
            return this;
        }
    
        public ViewHolder show(int id) {
            View view = getView(id);
            if (view != null)
                ViewUtils.setGone(view, false);
            return this;
        }
    
        public ViewHolder hide(int id) {
            View view = getView(id);
            if (view != null)
                ViewUtils.setGone(view, true);
            return this;
        }
    
    }

這樣我們就打造了一個(gè)通用的ViewHolder,它負(fù)責(zé)管理每個(gè)ItemView控件,同時(shí)也可以看成一個(gè)工具類(lèi),以后可以隨意添加。

有了通用的Adapter與通用的ViewHolder,那我們以后寫(xiě)代碼就方便多了,接下來(lái)我們就開(kāi)始實(shí)現(xiàn)多條目功能

其實(shí)我們仔細(xì)想想,不同的條目也就是我們的數(shù)據(jù)不同,需要的UI不同而已,每個(gè)數(shù)據(jù)對(duì)應(yīng)一個(gè)UI,但是我們?cè)醪拍茏孡istView知道加載什么UI?它只認(rèn)識(shí)getViewTypeCount與getItemViewType。那我們只有將不同UI的layoutId轉(zhuǎn)換成ListView認(rèn)識(shí)的getItemViewType,這就需要一個(gè)轉(zhuǎn)換ViewTypeGenerator

    class ViewTypeGenerator implements IViewTypeInfo{
        private Map<Integer, Integer> mAdapterTypeMap;
    
        private int mNextType = 1;
    
        private boolean mNeedReGenerate;
    
        public ViewTypeGenerator() {
            mAdapterTypeMap = new HashMap<Integer, Integer>();
            mNeedReGenerate = true;
        }
    
        @Override
        public void setNeedReGenerate(boolean needReGenerate) {
            mNeedReGenerate = needReGenerate;
        }
    
        public int reGenViewType(int key) {
            if (!mNeedReGenerate) {
                return key;
            }
    
            Integer resultType = mAdapterTypeMap.get(key);
            if (resultType == null || resultType <= 0) {
                resultType = mNextType++;
                mAdapterTypeMap.put(key, resultType);
            }
    
            return resultType;
        }
    }

ViewTypeGenerator維護(hù)一個(gè)集合,它可以將我們的layoutId轉(zhuǎn)換成連續(xù)的int值,滿(mǎn)足getItemViewType方法
我們來(lái)看看使用方法:

    public class MixListAdapter extends CommonListAdapter<BaseBean> {
        private ViewTypeGenerator mTypeGenerator;
        private IViewHandler viewHandler;
        private BaseBean itemData;
    
        public MixListAdapter(Context context, List datas) {
            super(context, datas);
            mTypeGenerator = new ViewTypeGenerator();
        }
    
    
        /**
         * 那么此處的adapter只要保證返回一個(gè)獨(dú)有的int值即可,不需要限制在[0, getViewTypeCount)之間
         * @return
         */
        @Override
        public int getItemViewType(int position) {
            IViewHandler viewHandler = ViewHandlerFactory.getViewHandler(getItem(position).getViewHandlerType());
            int viewHandlerItemViewType = viewHandler.getItemViewType();
            int itemViewType = mTypeGenerator.reGenViewType(viewHandlerItemViewType);
            if (itemViewType > getViewTypeCount())
                throw new RuntimeException("條目類(lèi)型數(shù)量超過(guò)了預(yù)設(shè)值:" + getViewTypeCount() + ",請(qǐng)擴(kuò)大預(yù)設(shè)值");
            return itemViewType ;
        }
    
        public int getViewTypeCount() {
            return 20;//根據(jù)需要,預(yù)設(shè)一個(gè)值,不要小于你的Item類(lèi)型總數(shù)
        }
    
        @Override
        protected int getItemLayoutId(int position) {
            //注釋1.獲取數(shù)據(jù)
            itemData = getItem(position);
               //注釋2.獲取ViewHandler
            viewHandler = ViewHandlerFactory.getViewHandler(itemData.getViewHandlerType());
            return viewHandler.getItemViewType();
        }
    
        @Override
        public void onBindHolder(ViewHolder holder, int position, ViewGroup parent) {
            viewHandler.handle(parent, holder, itemData, position);
        }
    }

注釋2處大家可能有點(diǎn)不明白,這也是最重要,最想說(shuō)明的地方,我是不想把View綁定數(shù)據(jù)(handle方法),返回布局layoutId(getItemViewLayoutId方法)、設(shè)置ItemViewType(getItemViewType方法)都放在子類(lèi)Adapter中所以抽出一個(gè)IViewHandler接口,統(tǒng)一處理.item數(shù)據(jù)綁定一個(gè)viewHandlerType屬性,是IViewHandler子類(lèi)的Class名,我們就可以在IViewHandler子類(lèi)中返回LayoutId給View綁定數(shù)據(jù),以后新加item類(lèi)型,也不用改動(dòng)adapter,只要
新建一個(gè)IViewHandler的子類(lèi)再給數(shù)據(jù)綁定該類(lèi)的Class文件名就可以了。

    /**
     * 處理ListView的不同類(lèi)型條目的顯示
     */
    public interface IViewHandler<T> {
        /**
         * 回調(diào)函數(shù),用于處理ListView的條目顯示
         */
        void handle(ViewGroup parent, ViewHolder holder, T data, int position);
    
        /**
         * 返回Item對(duì)應(yīng)的布局資源
         */
        int getItemViewLayoutId();
    
        /**
         * 設(shè)置ItemViewType,用于ListView的標(biāo)記回收
         */
        int getItemViewType();
    }

看看反射類(lèi)

    /**
     * 統(tǒng)一生成ViewHandler的工廠類(lèi)
     */
    public class ViewHandlerFactory {
        private static final String TAG = "[ViewHandlerFactory:wangsai]";
    
        /**
         * 緩存已存在的handler
         */
        private static Map<String, IViewHandler> mHandlerMap = new HashMap<String, IViewHandler>();
    
        /**
         * 通過(guò)類(lèi)名動(dòng)態(tài)加載創(chuàng)建、加載對(duì)應(yīng)的類(lèi)對(duì)象
         */
        public static IViewHandler getViewHandler(String viewHandlerClazz) {
            IViewHandler result = mHandlerMap.get(viewHandlerClazz);
    
            if (result == null) {
                try {
                    Class clazz = Class.forName(viewHandlerClazz);
                    result = (IViewHandler) clazz.newInstance();
                    mHandlerMap.put(viewHandlerClazz, result);
                } catch (Exception e) {
                    DebugLog.e(TAG, e.toString());
                }
            }
    
            if(result == null)
                throw new RuntimeException("IViewHandler創(chuàng)建失敗:" + viewHandlerClazz);
    
            return result;
        }
    }

貼一個(gè)子類(lèi)看看

    public class LeftViewHandler implements IViewHandler<BaseBean> {
        @Override
        public void handle(ViewGroup parent, ViewHolder holder, BaseBean data, int position) {
            holder.setText(R.id.tv,data.getName());
        }
    
        @Override
        public int getItemViewLayoutId() {
            return R.layout.simple_common_left;
        }
    
        @Override
        public int getItemViewType() {
            return R.layout.simple_common_left;
        }
    }

對(duì)數(shù)據(jù)的處理

    public List<BaseBean> getData(){
        data = new ArrayList<BaseBean>();
        for (int i = 0 ; i < 100 ; i++){
            BaseBean b = new BaseBean();
            b.setViewHandlerType(LeftViewHandler.class.getName());
            b.setName("item:"+i);
            data.add(b);
        }
        return data;
    }

我們以后對(duì)于多條目的混排,只需要給每個(gè)Data設(shè)定不同的Class文件名,然后在這個(gè)文件中綁定數(shù)據(jù)就Ok了

最后我們通過(guò)UML來(lái)梳理下


源碼可能跟文章里面的不一樣,注釋換成英文的了
源碼下載

聲明:主要思想是剽竊賽哥的勞動(dòng)成果(!_!

最后編輯于
?著作權(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)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,030評(píng)論 25 709
  • 簡(jiǎn)介 在Android開(kāi)發(fā)中ListView是比較常用的組件。 以列表的形式展示具體內(nèi)容。 并且能夠根據(jù)數(shù)據(jù)的長(zhǎng)度...
    上善若水Ryder閱讀 7,080評(píng)論 2 5
  • Android四大組件: activity: activity的生命周期:activity是context的子類(lèi),...
    梧桐樹(shù)biu閱讀 719評(píng)論 0 2
  • 1.開(kāi)啟圖片上下文 2.獲取當(dāng)前上下文 3.把drawView的layer 渲染到當(dāng)前上下文中,drawView為...
    金歌漫舞閱讀 176評(píng)論 0 0
  • (小白) 花兒開(kāi)得燦爛 經(jīng)過(guò)一次次 看了一眼又一眼 砰動(dòng)的心 如平靜西湖的水煮沸 如老鼠與貓狹路相逢 無(wú)波無(wú)瀾 以...
    cc1cc44bccf8閱讀 283評(píng)論 0 1

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