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 }
View Code

 

如果类型比较少还可以,但是类型多了之后,需要写多个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 }

运行效果截图:

 

 

  有了视图模板分离的基础,以后再多在复杂的列表还有什么搞不定的呢?以上是部分代码,完整工程代码 点击这里

 

posted @ 2017-04-30 22:16  Ajava攻城师  阅读(310)  评论(0编辑  收藏  举报
无觅关联推荐,快速提升流量