AdapterView的视图模板解耦分离
AdapterView是安卓里面基于Adapter适配器模式控件的抽象,顾名思义View与数据两者之间通过Adapter这个中间桥梁进行连接、交互。日常开发中最常见的控件莫过于Listivew、Gridview了,本文以Listview为例进行讲解如何模板分离
下面我们看一下大家日常开发常见的Listview常用的写法
1 /** 2 * 文章列表适配器 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public class ArticleListAdapter extends BaseAdapter { 8 9 private Context mContext; 10 private List dataSource; 11 private DisplayImageOptions mRoundOption,mFadeOption; 12 13 public ArticleListAdapter(Context mContext) { 14 this.mContext = mContext; 15 mRoundOption = GlobalApplication.getRoundOptions(R.mipmap.common_module_default_user_avtar); 16 mFadeOption = GlobalApplication.getFadeOptions(R.mipmap.common_module_default_picture); 17 } 18 19 /** 20 * 设置数据源 21 * @param dataSource 22 */ 23 public void setDataSource(List dataSource){ 24 this.dataSource = dataSource; 25 } 26 27 @Override 28 public int getCount() { 29 if (null != dataSource) { 30 return dataSource.size(); 31 } 32 return 0; 33 } 34 35 @Override 36 public Object getItem(int position) { 37 if (null != dataSource && position < dataSource.size()) { 38 return dataSource.get(position); 39 } 40 return null; 41 } 42 43 @Override 44 public long getItemId(int position) { 45 return position; 46 } 47 48 @Override 49 public View getView(final int position, View convertView, 50 ViewGroup parent) { 51 ViewHolder holder = null; 52 // 查找控件 53 if (null == convertView) { 54 convertView = LayoutInflater.from(mContext).inflate(R.layout.element_item_article, parent, false); 55 holder = new ViewHolder(); 56 //上下分割线 57 holder.mTopSapce = convertView.findViewById(R.id.top_space); 58 holder.mButtomSpace = convertView.findViewById(R.id.buttom_space); 59 //作者区域 60 holder.mTopAuthorGroup = (ViewGroup) convertView.findViewById(R.id.ll_author_tag); 61 holder.mAuthorImg = (ImageView) convertView.findViewById(R.id.iv_author_avatar); 62 holder.mAuthorName = (TextView) convertView.findViewById(R.id.tv_author_name); 63 holder.mReleaseDT = (TextView) convertView.findViewById(R.id.tv_release_datetime); 64 //文章标题、缩略图 65 holder.mArticleThumb = (ImageView) convertView.findViewById(R.id.iv_list_thumb); 66 holder.mTitle = (TextView) convertView.findViewById(R.id.tv_article_title); 67 holder.mSummary = (TextView) convertView.findViewById(R.id.tv_summary); 68 holder.mTagLabel = (TextView) convertView.findViewById(R.id.tv_label); 69 // 缓存Holder 70 convertView.setTag(holder); 71 } else { 72 holder = (ViewHolder) convertView.getTag(); 73 } 74 75 // 设置数据 76 final ArticleRowData rowData = (ArticleRowData) getItem(position); 77 //第一个不显示顶部的线 78 holder.mTopSapce.setVisibility(0 == position ? View.GONE :View.VISIBLE); 79 if(!TextUtils.isEmpty(rowData.authorImg)){ 80 ImageLoader.getInstance().displayImage(rowData.authorImg,holder.mAuthorImg,mRoundOption); 81 } 82 holder.mAuthorName.setText(rowData.authorName); 83 holder.mReleaseDT.setText(rowData.releaseDT); 84 if(!TextUtils.isEmpty(rowData.thumb)){ 85 ImageLoader.getInstance().displayImage(rowData.thumb,holder.mArticleThumb,mFadeOption); 86 } 87 holder.mTitle.setText(rowData.title); 88 holder.mSummary.setText(rowData.summary); 89 holder.mTagLabel.setText(rowData.tag); 90 holder.mTagLabel.setVisibility(TextUtils.isEmpty(rowData.tag)?View.GONE:View.VISIBLE); 91 92 return convertView; 93 } 94 95 class ViewHolder { 96 /** 97 * 上下占位间隙 98 */ 99 View mTopSapce, mButtomSpace; 100 101 /** 102 * 顶部作者区域 103 */ 104 ViewGroup mTopAuthorGroup; 105 106 /** 107 * 作者头像、文章缩略图 108 */ 109 ImageView mAuthorImg, mArticleThumb; 110 111 /** 112 * 作者名称、发布时间、标题、摘要、类别标签 113 */ 114 TextView mAuthorName, mReleaseDT, mTitle, mSummary, mTagLabel; 115 } 116 }
以上代码大家并不陌生,都是一直写的代码,只要是继承BaseAdapter,其中getCount、getItem、getItemId、getView这几个方法必须实现。对于日常开发使用频率最高的Listview、Gridvewi每次都写重复的这种Adapter是否觉得枯燥无味并且一点技术含量都没有?每天都写这种代码简直是对大神的侮辱!!! Adapter里面最关键的是getView方法,在这个方法中完成对item的布局渲染、复用、数据装填,其他方法都是完全可以交给一个基类来共通处理,另外可以将数据源完全托管给Adapter,由Adapter来管理数据源,并且对外提供对数据源的增删改查方法。那么接下来按照分析进行抽取一个基础的Adapter
基础Adapter抽取封装
1 /** 2 * 基础适配器 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public abstract class BasicAdapter extends BaseAdapter { 8 9 /** 10 * 托管数据源 11 */ 12 private List mDataSource = new ArrayList(); 13 14 /** 15 * 持有Context的软引用,防止内存泄露 16 */ 17 protected WeakReference<Context> mWeakRefContext; 18 19 public BasicAdapter(Context mContext){ 20 this.mWeakRefContext = new WeakReference<Context>(mContext); 21 } 22 23 @Override 24 public int getCount() { 25 return mDataSource.size(); 26 } 27 28 @Override 29 public Object getItem(int position) { 30 if (null != mDataSource && position < mDataSource.size()) { 31 return mDataSource.get(position); 32 } 33 return null; 34 } 35 36 @Override 37 public long getItemId(int position) { 38 return position; 39 } 40 41 /** 42 * 获取当前ListView绑定的数据源 43 * @return 44 */ 45 public List gainDataSource(){ 46 return mDataSource; 47 } 48 49 /** 50 * 添加数据 51 * @param object 数据模型 52 */ 53 public boolean addItem(Object object){ 54 return mDataSource.add(object); 55 } 56 57 /** 58 * 在指定索引位置添加数据 59 * @param location 索引 60 * @param object 数据模型 61 */ 62 public void addItem(int location,Object object){ 63 mDataSource.add(location, object); 64 } 65 66 /** 67 * 集合方式添加数据 68 * @param collection 集合 69 */ 70 public boolean addItem(Collection collection){ 71 return mDataSource.addAll(collection); 72 } 73 74 /** 75 * 在指定索引位置添加数据集合 76 * @param location 索引 77 * @param collection 数据集合 78 */ 79 public boolean addItem(int location,Collection collection){ 80 return mDataSource.addAll(location,collection); 81 } 82 83 /** 84 * 移除指定对象数据 85 * @param object 移除对象 86 * @return 是否移除成功 87 */ 88 public boolean removeItem(Object object){ 89 return mDataSource.remove(object); 90 } 91 92 /** 93 * 移除指定索引位置对象 94 * @param location 删除对象索引位置 95 * @return 被删除的对象 96 */ 97 public Object removeItem(int location){ 98 return mDataSource.remove(location); 99 } 100 101 /** 102 * 移除指定集合对象 103 * @param collection 待移除的集合 104 * @return 是否移除成功 105 */ 106 public boolean removeAll(Collection collection){ 107 return mDataSource.removeAll(collection); 108 } 109 110 /** 111 * 清空数据 112 */ 113 public void clear() { 114 mDataSource.clear(); 115 } 116 117 /** 118 * 获取Context上下文 119 * @return 120 */ 121 public Context getContext(){ 122 return mWeakRefContext.get(); 123 } 124 }
有了上面的基础Adapter以后写适配器方便多了,也避免写重复代码了,子类只关心getView实现item对应的布局渲染、复用、填充数据就可以了,经过改造之后的文章列表代码如下:
1 /** 2 * 【精简优化】文章列表适配器 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public class SmartArticleAdapter extends BasicAdapter { 8 9 private DisplayImageOptions mRoundOption,mFadeOption; 10 11 public SmartArticleAdapter(Context mContext) { 12 super(mContext); 13 mRoundOption = GlobalApplication.getRoundOptions(R.mipmap.common_module_default_user_avtar); 14 mFadeOption = GlobalApplication.getFadeOptions(R.mipmap.common_module_default_picture); 15 } 16 17 @Override 18 public View getView(final int position, View convertView, 19 ViewGroup parent) { 20 ViewHolder holder = null; 21 // 查找控件 22 if (null == convertView) { 23 convertView = LayoutInflater.from(getContext()).inflate(R.layout.element_item_article, parent, false); 24 holder = new ViewHolder(); 25 //上下分割线 26 holder.mTopSapce = convertView.findViewById(R.id.top_space); 27 holder.mButtomSpace = convertView.findViewById(R.id.buttom_space); 28 //作者区域 29 holder.mTopAuthorGroup = (ViewGroup) convertView.findViewById(R.id.ll_author_tag); 30 holder.mAuthorImg = (ImageView) convertView.findViewById(R.id.iv_author_avatar); 31 holder.mAuthorName = (TextView) convertView.findViewById(R.id.tv_author_name); 32 holder.mReleaseDT = (TextView) convertView.findViewById(R.id.tv_release_datetime); 33 //文章标题、缩略图 34 holder.mArticleThumb = (ImageView) convertView.findViewById(R.id.iv_list_thumb); 35 holder.mTitle = (TextView) convertView.findViewById(R.id.tv_article_title); 36 holder.mSummary = (TextView) convertView.findViewById(R.id.tv_summary); 37 holder.mTagLabel = (TextView) convertView.findViewById(R.id.tv_label); 38 // 缓存Holder 39 convertView.setTag(holder); 40 } else { 41 holder = (ViewHolder) convertView.getTag(); 42 } 43 44 // 设置数据 45 final ArticleRowData rowData = (ArticleRowData) getItem(position); 46 //第一个不显示顶部的线 47 holder.mTopSapce.setVisibility(0 == position ? View.GONE :View.VISIBLE); 48 if(!TextUtils.isEmpty(rowData.authorImg)){ 49 ImageLoader.getInstance().displayImage(rowData.authorImg,holder.mAuthorImg,mRoundOption); 50 } 51 holder.mAuthorName.setText(rowData.authorName); 52 holder.mReleaseDT.setText(rowData.releaseDT); 53 if(!TextUtils.isEmpty(rowData.thumb)){ 54 ImageLoader.getInstance().displayImage(rowData.thumb,holder.mArticleThumb,mFadeOption); 55 } 56 holder.mTitle.setText(rowData.title); 57 holder.mSummary.setText(rowData.summary); 58 holder.mTagLabel.setText(rowData.tag); 59 holder.mTagLabel.setVisibility(TextUtils.isEmpty(rowData.tag)?View.GONE:View.VISIBLE); 60 61 return convertView; 62 } 63 64 class ViewHolder { 65 /** 66 * 上下占位间隙 67 */ 68 View mTopSapce, mButtomSpace; 69 70 /** 71 * 顶部作者区域 72 */ 73 ViewGroup mTopAuthorGroup; 74 75 /** 76 * 作者头像、文章缩略图 77 */ 78 ImageView mAuthorImg, mArticleThumb; 79 80 /** 81 * 作者名称、发布时间、标题、摘要、类别标签 82 */ 83 TextView mAuthorName, mReleaseDT, mTitle, mSummary, mTagLabel; 84 } 85 }
上面举例的是最简单、最普通的中规中矩的列表,只涉及到一种item,但是我们实际项目开发中会涉及到多种item并存的情况,这个时候应该注意什么,又应该怎么写呢?
知识点补充:
1、多类型的Adapter写法其中有两个方法很关键,分别是getViewTypeCount和getItemViewType。前者返回一共有多种类,后者返回当前行数据对应哪种类型
2、getItemViewType定义返回的类型索引必须从0开始,并且种类不能断续,否则会发生数组越界异常【java.lang.ArrayIndexOutOfBoundsException: length=2; index=2 at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6643)】
3、viewType不能随便定义并且不能断续,因为在AbsListView的内部回收类RecycleBin中是需要根据viewType来从缓存的数组中当下标使用的,所以定义需要慎重!!!
下面请看多种类型Adapter范例:
1 /** 2 * 多种类型列表适配器 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public class MutilTypeAdapter extends BasicAdapter implements IAdapterViewType { 8 9 /** 10 * 图片显示option 11 */ 12 protected DisplayImageOptions mExactlyOption,mRoundOption; 13 14 public MutilTypeAdapter(Context mContext) { 15 super(mContext); 16 mExactlyOption = GlobalApplication.gainExactlyOption(R.mipmap.common_module_default_picture); 17 mRoundOption = GlobalApplication.getRoundOptions(R.mipmap.common_module_default_user_avtar); 18 } 19 20 @Override 21 public int getViewTypeCount() { 22 //返回一共有多少种类型的item 23 return 2; 24 } 25 26 @Override 27 public int getItemViewType(int position) { 28 return ((MutilTypeRowBean) getItem(position)).itemType; 29 } 30 31 @Override 32 public View getView(int position, View convertView, ViewGroup parent) { 33 ArticleViewHolder mArticleHoder = null; 34 ButtonLinkViewHolder mButtonLinkHoder = null; 35 36 int viewType = getItemViewType(position); 37 //渲染布局UI以及复用 38 if (null == convertView) { 39 switch (viewType){ 40 case TYPE_BUTTON_LINK: 41 convertView = LayoutInflater.from(getContext()).inflate(R.layout.element_item_button_link,parent,false); 42 mButtonLinkHoder = new ButtonLinkViewHolder(); 43 mButtonLinkHoder.mIcon = (ImageView)convertView.findViewById(R.id.iv_button_icon); 44 mButtonLinkHoder.mButtonText = (TextView) convertView.findViewById(R.id.tv_button_text); 45 convertView.setTag(mButtonLinkHoder); 46 break; 47 case TYPE_ARTICLE: 48 convertView = LayoutInflater.from(getContext()).inflate(R.layout.element_item_article,parent,false); 49 mArticleHoder = new ArticleViewHolder(); 50 //上下分割线 51 mArticleHoder.mTopSapce = convertView.findViewById(R.id.top_space); 52 mArticleHoder.mButtomSpace = convertView.findViewById(R.id.buttom_space); 53 //作者区域 54 mArticleHoder.mTopAuthorGroup = (ViewGroup) convertView.findViewById(R.id.ll_author_tag); 55 mArticleHoder.mAuthorImg = (ImageView) convertView.findViewById(R.id.iv_author_avatar); 56 mArticleHoder.mAuthorName = (TextView) convertView.findViewById(R.id.tv_author_name); 57 mArticleHoder.mReleaseDT = (TextView) convertView.findViewById(R.id.tv_release_datetime); 58 //文章标题、缩略图 59 mArticleHoder.mArticleThumb = (ImageView) convertView.findViewById(R.id.iv_list_thumb); 60 mArticleHoder.mTitle = (TextView) convertView.findViewById(R.id.tv_article_title); 61 mArticleHoder.mSummary = (TextView) convertView.findViewById(R.id.tv_summary); 62 mArticleHoder.mTagLabel = (TextView) convertView.findViewById(R.id.tv_label); 63 // 缓存Holder 64 convertView.setTag(mArticleHoder); 65 break; 66 } 67 }else{ 68 switch (viewType) { 69 case TYPE_BUTTON_LINK: 70 mButtonLinkHoder = (ButtonLinkViewHolder) convertView.getTag(); 71 break; 72 case TYPE_ARTICLE: 73 mArticleHoder = (ArticleViewHolder) convertView.getTag(); 74 break; 75 } 76 } 77 78 //填充数据 79 final MutilTypeRowBean rowData = (MutilTypeRowBean) getItem(position); 80 switch (viewType){ 81 case TYPE_BUTTON_LINK: 82 ButtonLinkBean btnLink = rowData.buttonLink; 83 ImageLoader.getInstance().displayImage(btnLink.iconURL,mButtonLinkHoder.mIcon,mExactlyOption); 84 mButtonLinkHoder.mIcon.setVisibility(TextUtils.isEmpty(btnLink.iconURL)?View.GONE:View.VISIBLE); 85 mButtonLinkHoder.mButtonText.setText(btnLink.buttonText); 86 mButtonLinkHoder.mButtonText.setTextColor(Color.parseColor(btnLink.buttonTextColor)); 87 break; 88 case TYPE_ARTICLE: 89 ArticleRowData article = rowData.article; 90 //第一个不显示顶部的线 91 mArticleHoder.mTopSapce.setVisibility(0 == position ? View.GONE :View.VISIBLE); 92 ImageLoader.getInstance().displayImage(article.authorImg,mArticleHoder.mAuthorImg,mRoundOption); 93 mArticleHoder.mAuthorName.setText(article.authorName); 94 mArticleHoder.mReleaseDT.setText(article.releaseDT); 95 if(!TextUtils.isEmpty(article.thumb)){ 96 ImageLoader.getInstance().displayImage(article.thumb,mArticleHoder.mArticleThumb,mExactlyOption); 97 } 98 mArticleHoder.mTitle.setText(article.title); 99 mArticleHoder.mSummary.setText(article.summary); 100 mArticleHoder.mTagLabel.setText(article.tag); 101 mArticleHoder.mTagLabel.setVisibility(TextUtils.isEmpty(article.tag)?View.GONE:View.VISIBLE); 102 break; 103 } 104 105 return convertView; 106 } 107 108 class ArticleViewHolder { 109 /** 110 * 上下占位间隙 111 */ 112 View mTopSapce, mButtomSpace; 113 114 /** 115 * 顶部作者区域 116 */ 117 ViewGroup mTopAuthorGroup; 118 119 /** 120 * 作者头像、文章缩略图 121 */ 122 ImageView mAuthorImg, mArticleThumb; 123 124 /** 125 * 作者名称、发布时间、标题、摘要、类别标签 126 */ 127 TextView mAuthorName, mReleaseDT, mTitle, mSummary, mTagLabel; 128 } 129 130 class ButtonLinkViewHolder{ 131 /** 132 * 图标 133 */ 134 private ImageView mIcon; 135 136 /** 137 * 按钮文案 138 */ 139 private TextView mButtonText; 140 } 141 }
如果类型比较少还可以,但是类型多了之后,需要写多个ViewHolder或者在一个大的ViewHodler里面容纳全部类型的View,getView方法的分支判断也会变得臃肿,不便于代码的阅读以及后期的维护,这是一个很严重的问题。那么是否可以尝试将每一种类型的UI渲染、数据装填进行分开呢?每一个类型独立的负责UI的渲染、数据填充?甚至把点击事件也直接在内部处理,这样就无需设置OnItemClickListener了?答案是可以的,这就是本文的重点,把每一个类型进行模板化、封装分离化,我把他叫视图模板的分离,因为这个不单单适合在AdapterView中,适应用于任何View的封装,可以对接到任何UI上。
我们试想一下视图模板应该怎样封装?我个人的思考方式是这样的,在面向对象编程领域里面,对象进行封装抽象,那么就肯定具备面向对象的三要素了(封装、继承、多态),不要小看了这三个概念,可能一刚开始接触这三个特征的时候没什么理解,当你编程的年限长了,就会慢慢发现、理解这三个特性的微妙之处,每一个年纪的成长都会加深对面向对象编程的理解和诠释,接触的语言多了,只要是面向对象的语言他们都离不开这个思想,学起来、理解起来会容易得多,最终都汇聚在一个万变不离其宗,万事万物皆对象,万法归一。这是我这些年编码的真谛领悟,每每分析阅读优秀开源项目源码时,看到他们的架构分层、每一个类的职责设计都能产生共鸣,视乎有一种恰到好处的异曲同工之妙,只可意会不可言传之感!有点扯远了,下面请看我对视图模板的设计定义:
1 /** 2 * 模板基础抽象接口定义 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public interface IViewTemplet { 8 9 /** 10 * 绑定布局文件 11 * @return 12 */ 13 int bindLayout(); 14 15 /** 16 * 代码创建View与bindLayout二选一 17 * @return 18 */ 19 View bindView(); 20 21 /** 22 * 查找控件 23 */ 24 void initView(); 25 26 /** 27 * 填充每一行数据 28 * @param model 当前行数据模型 29 * @param position 数据索引位置 30 */ 31 void fillData(IAdapterModel model, int position); 32 33 /** 34 * 获取当前item的View视图 35 * @return 36 */ 37 View getItemLayoutView(); 38 }
根据日常研发的习惯和步骤,分别定义bindLayout,返回一个布局文件的资源id,当然了针对一些比较简单的item比如一张图片、一个文本、按钮这样的类型,可以不用单独定义个布局文件了,直接用代码构建,也满足了一些开发者喜代码写布局的喜好(代码控)
然后就是initView方法了,这里面写findViewById查找控件的代码,再接下来就是fillData用来填充数据,携带数据模型以及位置索引形参(为什么需要定义一个IAdapterModel ?方便为所有数据模型增加共通基础属性,比如itemType,至于为什么要用接口?为了方便扩展,如果定义成抽象类就不方便后续其他数据模型的扩展了,如果原来的代码有基类这下不就尴尬了吗?定义成接口只需要让原来的基类实现接口就可以无缝对接了;为什么需要position?方便控制第一行或者最后一行分割线是否需要)
那么接下来接口定义好了,需要定义一个对接Adapter的模板抽象基类了,我个人觉得模板基类含有Context、当前行数据rowData、当前视图View、当前模板类型viewType等这些属性,并且子类可以访问得到,另外需要提供findViewById这种方法方便initView方法中使用、静态创建视图模板的方法、获取当前类型的视图View等方法,示例代码如下:
1 /** 2 * 模板基础抽象接口定义 3 * <p/> 4 * inflate-->bindLayout或者bindView-->initView-->fillData 5 * 6 * @author 曾繁添 7 * @version 1.0 8 */ 9 public abstract class AbsViewTemplet implements IViewTemplet,View.OnClickListener { 10 11 /** 12 * 上下文 13 */ 14 protected Context mContext; 15 16 /** 17 * 当前item对应的view视图 18 */ 19 protected View mLayoutView; 20 21 /** 22 * 当前item对应的type类型 23 */ 24 protected int viewType; 25 26 /** 27 * 当前位置索引 28 */ 29 protected int position; 30 31 /** 32 * 当前item数据 33 */ 34 protected IAdapterModel data; 35 36 /** 37 * 日志输出标识 38 */ 39 protected final String TAG = this.getClass().getSimpleName(); 40 41 public AbsViewTemplet(Context mContext) { 42 this.mContext = mContext; 43 } 44 45 /** 46 * 渲染UI布局 47 * 48 * @param viewType item对应的类型 49 * @param postion 当前数据行位置索引 50 * @param parent 根节点,没有可以传null 51 * @return 当前模板渲染的View 52 */ 53 public View inflate(int viewType, int postion, ViewGroup parent) { 54 this.viewType = viewType; 55 this.position = postion; 56 57 int layout = bindLayout(); 58 if (null != parent) { 59 mLayoutView = LayoutInflater.from(mContext).inflate(layout, parent, false); 60 } else { 61 mLayoutView = LayoutInflater.from(mContext).inflate(layout, parent); 62 } 63 mLayoutView.setOnClickListener(this); 64 65 return mLayoutView; 66 } 67 68 /** 69 * 缓存住当前行对应的参数,供基类adapter的getView方法中调用 70 * 71 * @param viewType item对应的类型 72 * @param postion 当前数据所在位置索引 73 * @param data 当前行数据 74 */ 75 void holdCurrentParams(int viewType, int postion, IAdapterModel data) { 76 this.viewType = viewType; 77 this.position = postion; 78 this.data = data; 79 } 80 81 @Override 82 public void onClick(View v) { 83 try { 84 itemClick(v, position, data); 85 } catch (Exception e) { 86 e.printStackTrace(); 87 Log.d(TAG, "点击跳转发生异常,原因:" + e.getMessage()); 88 } 89 } 90 91 /** 92 * item点击事件 93 * 94 * @param view 当前点击的view 95 * @param postion 当前行位置索引 96 * @param rowData 当前行数据模型 97 */ 98 public void itemClick(View view, int postion, IAdapterModel rowData) { 99 100 } 101 102 /** 103 * 获取当前item渲染的view 104 * 105 * @return 106 */ 107 public View getItemLayoutView() { 108 return mLayoutView; 109 } 110 111 /** 112 * 查找控件 113 * 114 * @param id 115 * @return 116 */ 117 protected View findViewById(int id) { 118 if (null != mLayoutView) { 119 return mLayoutView.findViewById(id); 120 } 121 return null; 122 } 123 124 /** 125 * 构建模板实例 126 * 127 * @param mViewTemplet 128 * @param arguments 构造方法形参 129 * @param <D> 130 * @return 131 * @throws Exception 132 */ 133 public static <D extends AbsViewTemplet> D createViewTemplet(Class<D> mViewTemplet, Object... arguments) { 134 Constructor<?> constructor = findConstructor(mViewTemplet, arguments); 135 D mTemplet = null; 136 try { 137 mTemplet = (D) constructor.newInstance(arguments); 138 } catch (InstantiationException e) { 139 e.printStackTrace(); 140 } catch (IllegalAccessException e) { 141 e.printStackTrace(); 142 } catch (InvocationTargetException e) { 143 e.printStackTrace(); 144 } 145 return mTemplet; 146 } 147 148 /** 149 * 匹配构造器 150 * 151 * @param mClass 152 * @param params 153 * @return 154 */ 155 private static Constructor<?> findConstructor(Class<?> mClass, Object... params) { 156 for (Constructor<?> constructor : mClass.getConstructors()) { 157 Class<?>[] paramsTypes = constructor.getParameterTypes(); 158 if (paramsTypes.length == params.length) { 159 boolean match = true; 160 for (int i = 0; i < paramsTypes.length; i++) { 161 if (!paramsTypes[i].isAssignableFrom(params[i].getClass())) { 162 match = false; 163 break; 164 } 165 } 166 if (match) { 167 return constructor; 168 } 169 } 170 } 171 return null; 172 } 173 }
那么接下来我们将如何对接Adapter呢?难道每次还需要上述的那样不断的在getView中写case? NO!我不想写这种代码了,但是我们可以把步骤化的东西升华抽取基类来完成,我们需要在基于BasicAdapter综合多种类型的Adapter抽取出一个适用于多种类型的Adapter基类,有了他之前的一种类型的Adapter那简直就是易如反掌(难道不是吗?一种类型也属于多种类型啊)。基类需要解决几个问题:
1、如何在构建Adapter的时候就知道当前有多少种类型,也就是封装getViewTypeCount这个方法?
2、如何让使用者无需关心当前行对应是哪种类型,也就是getItemViewType这个方法?
3、如何让每一种模板无需关心getView,只关心自己当前模板的本职工作?
其实这几个问题也简单,我们一个一个问题来解决
第1个问题,我们可以事先在一个地方(常量、业务管理类、当前Fragment或者Activity)维护一个类型与模板的映射关系,定义一个Map<int,class<? extends AbsViewTemplet>>就可以了,K就是int类型的viewType,V就是AbsViewTemplet类型的Class,只需要在构建Adapter之前能注册或者拿到这个Map的size,这不就就解决getViewTypeCount的返回值问题了
第2个问题,我们事先已经定义数据模型的类型了-->IAdapterModel,我们实现这个接口,并且提供一个返回itemType类型的方法,组装数据的时候直接返回对应的itemType就可以了
第3个问题,getView方法体内部的代码流程都是死的,首先判断传入的convertView是否为空来决定是渲染一个新布局、创建一个ViewHolder,还是从tag中取出之前的view来复用,然后填充数据,最后将填充好数据的view返回。在这个代码流程不变的前提下,在创建新布局和ViewHolder的时候,我们是否可以将咱们的AbsViewTemplet来替代ViewHolder?并且指定单独id的tag来绑定到View中呢?答案是肯定的,我们之前定义的AbsViewTemplt就是取代ViewHolder的,在对应的时机调用AbsViewTemplt定义的方法就达到我们对接Adapter的目的了
以上问题都解决了,接下来就是编写我们的多类型Adapter类了,示例代码:
1 /** 2 * 多种类型适配器基类 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public abstract class AbsMutilTypeAdapter extends BasicAdapter { 8 9 /** 10 * item模板映射<K=viewType,V=AbsViewTemplet> 11 */ 12 private Map<Integer,Class<? extends AbsViewTemplet> > mViewTemplet = new TreeMap<>(); 13 14 /** 15 * 日志输出标志 16 */ 17 private final String TAG = this.getClass().getSimpleName(); 18 19 public AbsMutilTypeAdapter(Context mContext) { 20 super(mContext); 21 registeViewTemplet(mViewTemplet); 22 } 23 24 @Override 25 public int getItemViewType(int position) { 26 IAdapterModel model = getItem(position); 27 return adjustItemViewType(model, position); 28 } 29 30 @Override 31 public int getViewTypeCount() { 32 //官方要求viewType必须从0索引开始 33 int maxViewType = 0; 34 for (Iterator<Integer> it = mViewTemplet.keySet().iterator(); it.hasNext();){ 35 maxViewType = it.next(); 36 } 37 //兼容跨越索引导致的ArrayIndexOutOfBoundsException,但是不推荐,因为会增加listview回收缓存view的数组长度开销 38 return (maxViewType >= mViewTemplet.size())?(maxViewType+1):mViewTemplet.size(); 39 } 40 41 @Override 42 public View getView(int position, View convertView, ViewGroup parent) { 43 AbsViewTemplet mTemplet = null; 44 int viewType = getItemViewType(position); 45 if(null == convertView){ 46 mTemplet = AbsViewTemplet.createViewTemplet(mViewTemplet.get(viewType), getContext()); 47 mTemplet.inflate(viewType, position, parent); 48 mTemplet.initView(); 49 convertView = mTemplet.getItemLayoutView(); 50 convertView.setTag(R.id.view_templet, mTemplet); 51 }else{ 52 mTemplet = (AbsViewTemplet)convertView.getTag(R.id.view_templet); 53 } 54 55 //填充数据 56 IAdapterModel rowData = getItem(position); 57 mTemplet.holdCurrentParams(viewType,position,rowData); 58 mTemplet.fillData(rowData, position); 59 return convertView; 60 } 61 62 /** 63 * 注册viewType以及绑定的Templet,一定要在listview.setAdapter方法之前调用 64 * @param mViewTemplet 65 */ 66 protected abstract void registeViewTemplet(Map<Integer,Class<? extends AbsViewTemplet> > mViewTemplet); 67 68 /** 69 * 根据数据模型返回对应的ViewType 70 * 71 * @param model 数据模型 72 * @param position 当前数据位置 73 * @return 74 */ 75 protected abstract int adjustItemViewType(IAdapterModel model, int position); 76 }
视图模板AbsViewTemplet、多类型Adapter基类都定义好了,那么接下来就可以进行视图分离了,以后再多的类型都不怕了,只关心对应的类型本职工作就可以了
多类型Adapter样例:
1 /** 2 * 【多类型】列表适配器 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public class SmartMutilTypeAdapter extends AbsMutilTypeAdapter implements IAdapterViewType { 8 9 public SmartMutilTypeAdapter(Context mContext) { 10 super(mContext); 11 } 12 13 @Override 14 protected void registeViewTemplet(Map<Integer, Class<? extends AbsViewTemplet>> mViewTemplet) { 15 mViewTemplet.put(IAdapterViewType.TYPE_ARTICLE,ArticleViewTemplet.class); 16 mViewTemplet.put(IAdapterViewType.TYPE_BUTTON_LINK,ButtonLinkViewTemplet.class); 17 } 18 19 @Override 20 protected int adjustItemViewType(IAdapterModel model, int position) { 21 if(null != model && model instanceof MutilTypeRowBean){ 22 return ((MutilTypeRowBean)model).itemType; 23 } 24 return 0; 25 } 26 }
两个类型的视图模板样例代码:
1 /** 2 * 文章资讯视图模板 3 * 4 * @author 曾繁添 5 * @version 1.0 6 */ 7 public class ArticleViewTemplet extends AbsViewTemplet { 8 9 /** 10 * 上下占位间隙 11 */ 12 private View mTopSapce, mButtomSpace; 13 14 /** 15 * 顶部作者区域 16 */ 17 private ViewGroup mTopAuthorGroup; 18 19 /** 20 * 作者头像、文章缩略图 21 */ 22 private ImageView mAuthorImg, mArticleThumb; 23 24 /** 25 * 作者名称、发布时间、标题、摘要、类别标签 26 */ 27 private TextView mAuthorName, mReleaseDT, mTitle, mSummary, mTagLabel; 28 29 /** 30 * 图片显示option 31 */ 32 private DisplayImageOptions mRoundOption,mFadeOption; 33 34 public ArticleViewTemplet(Context mContext) { 35 super(mContext); 36 mRoundOption = GlobalApplication.getRoundOptions(R.mipmap.common_module_default_user_avtar); 37 mFadeOption = GlobalApplication.getFadeOptions(R.mipmap.common_module_default_picture); 38 } 39 40 @Override 41 public int bindLayout() { 42 return R.layout.element_item_article; 43 } 44 45 @Override 46 public void initView() { 47 //上下分割线 48 mTopSapce = findViewById(R.id.top_space); 49 mButtomSpace = findViewById(R.id.buttom_space); 50 //作者区域 51 mTopAuthorGroup = (ViewGroup) findViewById(R.id.ll_author_tag); 52 mAuthorImg = (ImageView) findViewById(R.id.iv_author_avatar); 53 mAuthorName = (TextView) findViewById(R.id.tv_author_name); 54 mReleaseDT = (TextView) findViewById(R.id.tv_release_datetime); 55 //文章标题、缩略图 56 mArticleThumb = (ImageView) findViewById(R.id.iv_list_thumb); 57 mTitle = (TextView) findViewById(R.id.tv_article_title); 58 mSummary = (TextView) findViewById(R.id.tv_summary); 59 mTagLabel = (TextView) findViewById(R.id.tv_label); 60 } 61 62 @Override 63 public void fillData(IAdapterModel model, int position){ 64 MutilTypeRowBean rowBean = (MutilTypeRowBean) model; 65 if (null == rowBean || rowBean.article == null) { 66 Log.e(TAG, position + "-->数据为空"); 67 return; 68 } 69 70 //第一个不显示顶部的线 71 mTopSapce.setVisibility(0 == position ? View.GONE :View.VISIBLE); 72 if(!TextUtils.isEmpty(rowBean.article.authorImg)){ 73 ImageLoader.getInstance().displayImage(rowBean.article.authorImg,mAuthorImg,mRoundOption); 74 } 75 mAuthorName.setText(rowBean.article.authorName); 76 mReleaseDT.setText(rowBean.article.releaseDT); 77 //复位默认图片 78 mArticleThumb.setImageResource(R.mipmap.common_module_default_picture); 79 if(!TextUtils.isEmpty(rowBean.article.thumb)){ 80 ImageLoader.getInstance().displayImage(rowBean.article.thumb,mArticleThumb,mFadeOption); 81 } 82 mTitle.setText(rowBean.article.title); 83 mSummary.setText(rowBean.article.summary); 84 mTagLabel.setText(rowBean.article.tag); 85 mTagLabel.setVisibility(TextUtils.isEmpty(rowBean.article.tag)?View.GONE:View.VISIBLE); 86 } 87 }
1 /** 2 * 按钮基础视图模板 3 * 4 * @author 曾繁添 5 * @version 1.0 6 * 7 */ 8 public class ButtonLinkViewTemplet extends AbsViewTemplet { 9 10 /** 11 * 图标 12 */ 13 private ImageView mIcon; 14 15 /** 16 * 按钮文案 17 */ 18 private TextView mButtonText; 19 20 /** 21 * 图片显示option 22 */ 23 protected DisplayImageOptions mExactlyOption; 24 25 public ButtonLinkViewTemplet(Context mContext) { 26 super(mContext); 27 mExactlyOption = GlobalApplication.gainExactlyOption(R.mipmap.common_module_default_picture); 28 } 29 30 @Override 31 public int bindLayout() { 32 return R.layout.element_item_button_link; 33 } 34 35 @Override 36 public void initView() { 37 mIcon = (ImageView)findViewById(R.id.iv_button_icon); 38 mButtonText = (TextView) findViewById(R.id.tv_button_text); 39 } 40 41 @Override 42 public void fillData(IAdapterModel model, int position){ 43 MutilTypeRowBean rowBean = (MutilTypeRowBean) model; 44 if (null == rowBean || rowBean.buttonLink == null) { 45 Log.e(TAG, position + "-->数据为空"); 46 return; 47 } 48 //复位默认图片 49 mIcon.setImageResource(R.mipmap.common_module_default_picture); 50 if(!TextUtils.isEmpty(rowBean.buttonLink.iconURL)){ 51 ImageLoader.getInstance().displayImage(rowBean.buttonLink.iconURL,mIcon,mExactlyOption); 52 } 53 mIcon.setVisibility(TextUtils.isEmpty(rowBean.buttonLink.iconURL)?View.GONE:View.VISIBLE); 54 mButtonText.setText(rowBean.buttonLink.buttonText); 55 mButtonText.setTextColor(Color.parseColor(rowBean.buttonLink.buttonTextColor)); 56 } 57 }
运行效果截图:
有了视图模板分离的基础,以后再多在复杂的列表还有什么搞不定的呢?以上是部分代码,完整工程代码 点击这里
: