Android自定义BaseAdapter最佳实践
虽然现在很多新的项目都在使用RecyclerView,但是很多开发者在一些场景中还是倾向使用ListView或者GridView,然后就是需要写许多的Adapter。一次项目组在新启动一个新项目的时候,有个同事拿来了一个网上说的万能Adapter,在使用的时候发现即使在单个视图类型一旦逻辑判断比较复杂情况下非常不方便,更不用说在适配器Adapter中使用多视图类型了,这里仅是个人观点,也许没有掌握到精华,这是有关万能适配器Adapter的一片博文 Android 快速开发系列 打造万能的ListView GridView 适配器 。 当然了随着RecyclerView的使用,网上也有很多有关对RecyclerView多视图类型Adapter封装的博客,MultiType 3.0是一个大神写的比较全面的Adapter,这篇博客Android 复杂的多类型列表视图新写法:MultiType 3.0有详细的用法。万能适配器Adapter自己使用不是很方便,于是就参看RecyclerView中Adapter的实现方式进行对BaseAdapter进行了简单的封装,封装的目一是为了少写代码,另外一个就是让逻辑看上去更清晰一些。我们知道在RecyclerView的Adapter实现中它将视图创建与数据绑定进行了分离,同时将对View的查找创建也剥离开来了,本文就主要介绍如何将BaseAdapter的使用封装为跟RecyclerView的Adapter使用方式一致。由于很多时候在Adapter中我们都是使用的简单的视图类型,即单类型视图,因此本文将单视图类型的Adapter单独封装了一下,比使用多视图类型的Adapter使用了更严格的数据类型检查,同时在使用上也方便了许多。 RecyclerView中Adapter的使用 在使用RecyclerView的Adapter的时候我们首先需要继承RecyclerView的一个静态内部类Adapter,然后重写三个方法,实际上下面三个方法是必须要重写的,因为都是抽象方法。 getItemCount() onBindViewHolder(VH holder, int position) onCreateViewHolder(ViewGroup parent, int viewType) 一般情况下重写上面三个方法就可以,但是如果存在多视图类型,在第三个方法 onCreateViewHolder()方法中我们也可以看到有一个参数是viewType,该参数作用就是针对不同的viewType需要创建不同的ViewHolder,因此还需要重写一个方法getItemViewType(int position),针对多视图类型同BaseAdapter实现方式倒是很像,在BaseAdapter中这是需要除此之外还要重写一个方法getViewTypeCount(),但是在RecyclerView的Adapter中不需要该方法。 简单类型Adapter privateclassMyAdapterextendsRecyclerView.Adapter<MyViewHolder>{ @Override publicintgetItemCount(){ returnCOUNT; } @Override publicvoidonBindViewHolder(MyViewHolderholder,intposition){ holder.textView.setText("TEXT_"+position); } @Override publicMyViewHolderonCreateViewHolder(ViewGroupparent,intviewType){ Viewview=LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text,parent,false); MyViewHolderholder=newMyViewHolder(view); returnholder; } } privatestaticclassMyViewHolderextendsRecyclerView.ViewHolder{ privateTextViewtextView; publicMyViewHolder(ViewitemView){ super(itemView); textView=(TextView)itemView.findViewById(R.id.textView); } } 复杂类型Adapter privateclassMyAdapterextendsRecyclerView.Adapter<ViewHolder>{ @Override publicintgetItemCount(){ returnCOUNT; } @Override publicintgetItemViewType(intposition){ returnposition%2==0?TYPE_IMAGE:TYPE_TEXT; } @Override publicvoidonBindViewHolder(ViewHolderholder,intposition){ inttype=getItemViewType(position); switch(type){ caseTYPE_TEXT: ((MyTextHolder)holder).textView.setText("TEXT_"+position); break; caseTYPE_IMAGE: ((MyImageHolder)holder).imageView.setImageResource(R.drawable.image); break; } } @Override publicViewHolderonCreateViewHolder(ViewGroupparent,intviewType){ Viewview; ViewHolderholder=null; switch(viewType){ caseTYPE_TEXT: view=LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text,parent,false); holder=newMyTextHolder(view); break; caseTYPE_IMAGE: view=LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_image,parent,false); holder=newMyImageHolder(view); break; } returnholder; } } privateclassMyTextHolderextendsRecyclerView.ViewHolder{ privateTextViewtextView; publicMyTextHolder(ViewitemView){ super(itemView); textView=(TextView)itemView.findViewById(R.id.textView); } } privateclassMyImageHolderextendsViewHolder{ privateImageViewimageView; publicMyImageHolder(ViewitemView){ super(itemView); imageView=(ImageView)itemView.findViewById(R.id.imageView); } } 自定义BaseAdapter 在自定义基类之前,先简单分析一下,我们需要自定义一个支持单种视图的Adapter,还要自定义一个支持多种视图类型的Adapter,两个类都要继承BaseAdapter,先将两个类都公用的部分抽取出来定义为MyAdapter。 publicabstractclassMyAdapter<T>extendsBaseAdapter{ protectedList<T>dataList=newArrayList<>(); protectedContextcontext; protectedLayoutInflaterinflater; publicMyAdapter(Contextcontext){ this.context=context; inflater=LayoutInflater.from(context); } publicvoidsetDataList(List<T>dataList){ this.dataList=dataList; notifyDataSetChanged(); } @Override publicintgetCount(){ if(null==dataList){ return0; } returndataList.size(); } @Override publicTgetItem(intposition){ returndataList.get(position); } @Override publiclonggetItemId(intposition){ returnposition; } } 在RecyclerView的Adapter实现中是没有getView()方法的,下面我们就分析一下getView()方法如何拆分,一般情况下我们在实现getView()方法都是如下流程。 publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ ViewHolderholder=null; if(null==convertView){ //填充布局 convertView=inflater.inflate(R.layout.item_layout,parent,false); holder=newViewHolder(); //通过ID查询控件 holder.textView=(TextView)convertView.findViewById(R.id.textView); holder.imageView=(ImageView)convertView.findViewById(R.id.imageView); convertView.setTag(holder); }else{ holder=(ViewHolder)convertView.getTag(); } //赋值逻辑 returnconvertView; } //一个空的ViewHolder publicstaticclassViewHolder{ TextViewtextView; ImageViewimageView; } Java编程比较流行的一种编程方式不是说面向接口编程吗,在Android开发中也有一个开发方式叫做面向Holder的编程,上面代码是传统的实现ViewHolder的方式,说句实现话就没做什么事,就是作为一个载体承载着我们需要的控件。我们让ViewHolder多做一些事情,让它在convertView==null情况下需要做的多数逻辑都放到ViewHolder中去。 publicclassViewHolder{ privatefinalViewitemView; publicViewHolder(ViewitemView){ if(null==itemView){ thrownewIllegalArgumentException("itemViewmustnotbenull"); }else{ this.itemView=itemView; itemView.setTag(this); } } publicViewgetItemView(){ returnitemView; } } 在ViewHolder中的itemView就是getView()方法中的convertView,这里刚好是条目的根View,类似RecyclerView中ViewHolder构造方法中itemView。由于不同的视图需要创建不同的ViewHolder,因此我们可以将创建ViewHolder的方法设置为抽象的方法暴露出去,另外赋值的时候我们也需要根据具体的业务进行赋值,同样设置一个抽象方法。 publicabstractclassSimpleAdapter<T,VHextendsViewHolder>extendsMyAdapter<T>{ publicSimpleAdapter(Contextcontext){ super(context); } publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ VHholder=null; if(null==convertView){ holder=onCreateViewHolder(parent); convertView=holder.getItemView(); }else{ holder=(VH)convertView.getTag(); } onBindViewHolder(holder,position); returnconvertView; } publicabstractvoidonBindViewHolder(VHholder,intposition); publicabstractVHonCreateViewHolder(ViewGroupparent); } 在设置多视图类型的Adapter的时候只需要在创建ViewHolder的时候多传入一个viewType的参数即可。 publicabstractclassMultiAdapter<T>extendsMyAdapter<T>{ publicMultiAdapter(Contextcontext){ super(context); } @Override publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ ViewHolderholder=null; if(null==convertView){ holder=onCreateViewHolder(parent,getItemViewType(position)); convertView=holder.getItemView(); }else{ holder=(ViewHolder)convertView.getTag(); } onBindViewHolder(holder,position); returnconvertView; } publicabstractvoidonBindViewHolder(ViewHolderholder,intposition); publicabstractViewHolderonCreateViewHolder(ViewGroupparent,intviewType); } 自定义BaseAdapter的使用 单视图类型SimpleAdapter使用 publicclassTextAdapterextendsSimpleAdapter<String,TextAdapter.TextHolder>{ publicTextAdapter(Contextcontext){ super(context); } @Override publicvoidonBindViewHolder(TextHolderholder,intposition){ holder.textView.setText(getItem(position)); } @Override publicTextHolderonCreateViewHolder(ViewGroupparent){ ViewconvertView=inflater.inflate(R.layout.item_text,parent,false); returnnewTextHolder(convertView); } staticclassTextHolderextendsViewHolder{ publicTextViewtextView; publicTextHolder(ViewitemView){ super(itemView); textView=(TextView)itemView.findViewById(R.id.textView); } } } 这里我们使用了两个泛型,一个是ViewHolder中支持的数据类型String,另外一个就是我们需要创建的ViewHolder,这样在onCreateViewHolder方法的返回值就会自动返回我们自定义的ViewHolder,有关泛型更多的知识可以参看Java泛型使用解析,单视图类型Adapter的使用比RecyclerView的Adapter还要方便许多。 多视图类型的使用 publicclassRichAdapterextendsMultiAdapter<String>{ privatestaticfinalintTEXT=0; privatestaticfinalintPIC=1; publicRichAdapter(Contextcontext){ super(context); } @Override publicintgetViewTypeCount(){ return2; } @Override publicintgetItemViewType(intposition){ if(position%3==0){ returnPIC; }else{ returnTEXT; } } @Override publicvoidonBindViewHolder(ViewHolderholder,intposition){ switch(getItemViewType(position)){ caseTEXT: TextHoldertextHolder=(TextHolder)holder; textHolder.textView.setText(getItem(position)); break; casePIC: ImageHolderimageHolder=(ImageHolder)holder; imageHolder.imageView.setImageResource(R.drawable.image); break; } } @Override publicViewHolderonCreateViewHolder(ViewGroupparent,intviewType){ ViewitemView=null; ViewHolderholder=null; switch(viewType){ caseTEXT: itemView=inflater.inflate(R.layout.item_text,parent,false); holder=newTextHolder(itemView); break; casePIC: itemView=inflater.inflate(R.layout.item_image,parent,false); holder=newImageHolder(itemView); break; } returnholder; } privatestaticclassTextHolderextendsViewHolder{ TextViewtextView; publicTextHolder(ViewitemView){ super(itemView); textView=(TextView)itemView.findViewById(R.id.textView); } } privatestaticclassImageHolderextendsViewHolder{ ImageViewimageView; publicImageHolder(ViewitemView){ super(itemView); imageView=(ImageView)itemView.findViewById(R.id.imageView); } } } 这里的使用情况跟RecyclerView的使用几乎是一模一样,唯一不一样的地方就是多写了一个getViewTypeCount()方法,在ListView或者GridView使用BaseAdapter实现多种类型视图的时候该方法必须要重写。 本文作者:佚名 来源:51CTO