使用注解来折腾BaseAdapter(1)
2016-03-19 20:45
429 查看
使用注解来折腾BaseAdapter
在正式开始阅读博客前,我们先来大致了解一下JAVA中的注解:定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。—来自某百科
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
因此,我想说的是,注解是注解,反射是反射,二者不要混为一谈,只是说在对某一域进行了注解之后,若要取得注解的值,一般都是使用反射。
首先,我们来看看写Adapter的一般现在时
/** * Created by 阿木木 * com.example.administrator.myapplication.adapter * 2016/3/19 14:06 */ public class YourAdapter extends BaseAdapter { private Context context; private List<GoodsInfo> goodsInfoList; public YourAdapter(Context context, List<GoodsInfo> goodsInfoList) { this.context = context; this.goodsInfoList = goodsInfoList; } public void setData(List<GoodsInfo> goodsInfoList) { this.goodsInfoList.addAll(goodsInfoList); notifyDataSetChanged(); } public List<GoodsInfo> getData() { return this.goodsInfoList; } @Override public int getCount() { return goodsInfoList.size(); } @Override public GoodsInfo getItem(int position) { return goodsInfoList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate(R.layout.list_view_item_goods, parent, false); holder.image = (ImageView) convertView.findViewById(R.id.goods_pic); holder.name = (TextView) convertView.findViewById(R.id.goods_name); holder.price = (TextView) convertView.findViewById(R.id.goods_price); holder.buy = (Button) convertView.findViewById(R.id.goods_buy); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } GoodsInfo current = getItem(position); holder.image.setImageResource(current.getGoodsPic()); holder.name.setText(current.getGoodsName()); holder.price.setText(context.getString(R.string.price_yuan, current.getGoodsPrice())); return convertView; } public static class ViewHolder { ImageView image; TextView name; TextView price; Button buy; }
显示效果就这样
在这可以看到,findViewById和holder.xxx.setText占用篇幅较长,而且每一个listView(gridView)的Adapter都这样写,手(逼)都(格)写(不)麻(高)了。
那么我们就来提升一下逼格
首先进行分析,在重写getView中,先加载此Item的布局(inflate),然后再对ViewHolder中的变量进行赋值(findViewById);将此ViewHolder存入布局中,在下次使用的时候可以再次取出。取出时然后通过Bean.get各种值,将此值设置到ViewHolder中定义的各种控件。
它们之间乱搞的关系大概是这样:
Created with Raphaël 2.1.0ViewHolderViewHolderconvertViewconvertViewBeanBean把控件给我找出来我的这个属性你给我设置出来显示一下
如果不考虑复用和多种布局的情况下,一个ViewHolder也就对应一个Adapter,而此Adapter也就加载一个布局,那么ViewHolder完全可以和布局进行绑定,最终要显示实体类值的时候,实体类的某个属性和ViewHolder(和显示的控件)也是一个对应的关系。
这样分析下来,如果要添加注解
- 那么给ViewHolder类上添加一个,告诉Adapter“我要加载这个布局”,
- 再在定义的变量上分别给它们添加一个注解,告诉Adapter“我的这个控件是刚才给你的布局里面的这个控件”。
那么就依据这两点,来新建两个注解类。
/** * Created by 阿木木 * com.example.administrator.myapplication.annoation * 2016/3/19 17:03 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ListAdapterLayoutId { int value(); }
/** * Created by 阿木木 * com.example.administrator.myapplication.annoation * 2016/3/19 17:05 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ListAdapterViewId { int value(); }
这俩货仅仅Target处不同,一个表示此注解只能放在类上,另一个则表示此注解只能放在成员属性(变量)上。
再到ViewHolder类中来使用,然后大概长这样:
@ListAdapterLayoutId(R.layout.list_view_item_goods) public static class ViewHolder { @ListAdapterViewId(R.id.goods_pic) ImageView image; @ListAdapterViewId(R.id.goods_name) TextView name; @ListAdapterViewId(R.id.goods_price) TextView price; @ListAdapterViewId(R.id.goods_buy) Button buy; }
注:使用反射获取注解中的值时,是一个比较繁琐的过程,也许取着取着,把对象搞错了,可能本应该此class的某个变量,结果取成另外一个东西的了。
那么这个Adapter类就不能只针对这一个地方使用,而应作为基类,在其它地方需要使用的时候,再对它进行定制。
整理好的Adapter基类
/** * Created by 阿木木 * com.example.administrator.myapplication.adapter * 2016/3/19 17:11 */ public class MyBaseAdapter<T> extends BaseAdapter { private Context context; private List<T> list; public MyBaseAdapter(Context context) { this.context = context; this.list = new ArrayList<>(); } @Override public int getCount() { return list.size(); } @Override public T getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { return convertView; } public static class BaseViewHolder { } }
重写getView,在这里面我们做一些有趣的事;
照搬普通的Adapter
ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate(R.layout.list_view_item_goods, parent, false); holder.image = (ImageView) convertView.findViewById(R.id.goods_pic); holder.name = (TextView) convertView.findViewById(R.id.goods_name); holder.price = (TextView) convertView.findViewById(R.id.goods_price); holder.buy = (Button) convertView.findViewById(R.id.goods_buy); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); }
那么问题来了,我作为父类,那么这个ViewHolder肯定不知道到底是什么类型的,自然就不能放在这里来进行实例化,那解决方式就是,由调用者告诉我,你用的ViewHolder是哪个类,我自己来造一个对象满足自己(的私欲)。
/** * Created by 阿木木 * com.example.administrator.myapplication.adapter * 2016/3/19 17:11 */ public class MyBaseAdapter<T> extends BaseAdapter { private Context context; private List<T> list; private Class<? extends BaseViewHolder> holderClass; public MyBaseAdapter(Context context) { this.context = context; this.list = new ArrayList<>(); this.holderClass = holderClass; } @Override public int getCount() { return list.size(); } @Override public T getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { return inject(holderClass, position, convertView, parent); } private void inject(Class<? extends BaseViewHolder> clazz, int position, View convertView, ViewGroup parent){ try { //和基本写法相同 if (convertView == null) { //获取到ViewHolder上的注解 ListAdapterLayoutId viewLayoutId = clazz.getAnnotation(ListAdapterLayoutId.class); //如果获取到的注解不为空,即为“客户端已按要求给ViewHolder指定了它要加载的ItemLayout” if (viewLayoutId != null) { //获取需要加载的布局的ID int layoutId = viewLayoutId.value(); convertView = LayoutInflater.from(context).inflate(layoutId, parent, false); //实例化出ViewHolder的一个对象 holder = clazz.newInstance(); //获取ViewHolder对象中的所有成员属性,包括私有(private)但排除父类 Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { //设置成员属性可修改 field.setAccessible(true); //找到这个成员属性给它的注解,对应为控件ID ListAdapterViewId viewIdAnnotation = field.getAnnotation(ListAdapterViewId.class); //如果给定的注解不为空,则说明在ViewHolder类中指定了它是哪个控件 if (viewIdAnnotation != null) { //获取得到注解中指定的控件ID int viewId = viewIdAnnotation.value(); //在布局中找到此控件 View view = convertView.findViewById(viewId); if (view != null) { //找到此控件后,对ViewHolder的对象设置对应成员属性为找到的这个控件 field.set(holder, view); //将此控件放入集合中 holderFields.put(viewId, view); } else { throw new RuntimeException("ViewHolder类中的"+field.getName()+"注解的View Id无法找到"); } } } //和普通写法相同 convertView.setTag(holder); } else { throw new RuntimeException("传入的ViewHolder类没有注解指定布局ID"); } } else { holder = (BaseViewHolder) convertView.getTag(); } } ***省略了好多加代码 public static class BaseViewHolder { } }
现在就算把ViewHolder中定义的属性找到了,先测试一下。
OK,测试通过,说明前面的逼没白装。
继续
接下来得显示我们实体类中的信息了。
接上的else之后
//再次获取此ViewHolder的所有成员属性,这一步是用来设置具体的值 for (Field field : clazz.getDeclaredFields()) { //获取对应成员属性的指定注解 ListAdapterViewId holderViewAnnotation = field.getAnnotation(ListAdapterViewId.class); //判断ViewHolder有没有给这个成员属性添加注解,添加了注解才能继续下一步 if (holderViewAnnotation != null) { //获取ListView中对应下标的此处的实体,再获取此实体类的成员变量,对Bean类的属性进行遍历 for (Field beanField : getItem(position).getClass().getDeclaredFields()) { //获取实体类中的成员属性的注解,需要这个注解和View的ID进行对比 ListAdapterViewId annotation = beanField.getAnnotation(ListAdapterViewId.class); //设置可修改Bean类对应的成员属性可修改 beanField.setAccessible(true); //随即判断是否添加了注解,若不注解,则表明“这个变量我不设置到ListView中” if (annotation != null) { //对Bean类的变量与ViewHolder的变量进行对比,如果它们都指向同一个View的ID,则表明它们是一一对应 if (annotation.value() == holderViewAnnotation.value()) { field.setAccessible(true); //field.getType(),获取到这个ViewHolder的成员属性,是什么类型, Class<?> type = field.getType(); if (type.getName().equals(TextView.class.getName())) { //如果是TextView再通过类型来找setText(CharSequence)这个方法 Method setText = type.getMethod("setText", CharSequence.class); //如果能找到setText(CharSequence)方法,表明确实是TextView,并判断对应的View有没有添加到布局中 if (setText != null && holderFields.get(annotation.value()) != null) { //直接执行setText setText.invoke(holderFields.get(annotation.value()), beanField.get(getItem(position))+""); } } //注,动态设置显示的只支持TextView及其子类。对于ImageView,在此不能有ImageLoader来对ImageView进行显示的操作 //这里只管做它该做的事,怎么显示由客户端使用相应的操作 } } } } }
以上添加完毕后,一定不要忘记,还要给实体类注解,让它知道该显示哪里。
/** * Created by 阿木木 * com.example.administrator.myapplication.bean * 2016/3/19 13:58 */ public class GoodsInfo { @ListAdapterViewId(R.id.goods_name) private String goodsName; @ListAdapterViewId(R.id.goods_price) private int goodsPrice; @ListAdapterViewId(R.id.goods_pic) private int goodsPic; public GoodsInfo() { } public GoodsInfo(String goodsName, int goodsPrice, int goodsPic) { this.goodsName = goodsName; this.goodsPrice = goodsPrice; this.goodsPic = goodsPic; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } public int getGoodsPrice() { return goodsPrice; } public void setGoodsPrice(int goodsPrice) { this.goodsPrice = goodsPrice; } public int getGoodsPic() { return goodsPic; } public void setGoodsPic(int goodsPic) { this.goodsPic = goodsPic; } }
最终显示效果如下
测试通过
然后代码如下
Activity
public class MainActivity extends Activity { private ListView listView; private MyBaseAdapter<GoodsInfo> adapter; private List<GoodsInfo> goodsInfoList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); goGoGo(); listView.postDelayed(new Runnable() { @Override public void run() { getTheList(); } }, 500); } private void initView() { listView = (ListView) findViewById(R.id.test_list_view); goodsInfoList = new ArrayList<>(); adapter = new MyBaseAdapter(this, ViewHolder.class); } private void goGoGo() { listView.setAdapter(adapter); } private void getTheList() { for (int i = 0; i < 10; i++) { GoodsInfo goodsInfo = new GoodsInfo("商品9527:" + i, (int) (Math.random() * 10), R.mipmap.ic_launcher); goodsInfoList.add(goodsInfo); } adapter.setData(goodsInfoList); } @ListAdapterLayoutId(R.layout.list_view_item_goods) public static class ViewHolder extends MyBaseAdapter.BaseViewHolder{ @ListAdapterViewId(R.id.goods_pic) ImageView image; @ListAdapterViewId(R.id.goods_name) TextView name; @ListAdapterViewId(R.id.goods_price) TextView price; @ListAdapterViewId(R.id.goods_buy) Button buy; } }
MyBaseAdapter
/** * Created by 阿木木 * com.example.administrator.myapplication.adapter * 2016/3/19 17:11 */ public class MyBaseAdapter<T> extends BaseAdapter { private Context context; private List<T> list; private Class<? extends BaseViewHolder> holderClass; public MyBaseAdapter(Context context, Class<? extends BaseViewHolder> holderClass) { this.context = context; this.list = new ArrayList<>(); this.holderClass = holderClass; } public void setData(List<T> list) { this.list.clear(); this.list.addAll(list); this.notifyDataSetChanged(); } public List<T> getData() { return this.list; } @Override public int getCount() { return list.size(); } @Override public T getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { return inject(holderClass, position, convertView, parent); } private View inject(Class<? extends BaseViewHolder> clazz, int position, View convertView, ViewGroup parent) { BaseViewHolder holder; //用于存放Item布局中的View,key为控件ID,value为控件 Map<Integer, Object> holderFields = new HashMap<>(); try { //和基本写法相同 if (convertView == null) { //获取到ViewHolder上的注解 ListAdapterLayoutId viewLayoutId = clazz.getAnnotation(ListAdapterLayoutId.class); //如果获取到的注解不为空,即为“客户端已按要求给ViewHolder指定了它要加载的ItemLayout” if (viewLayoutId != null) { //获取需要加载的布局的ID int layoutId = viewLayoutId.value(); convertView = LayoutInflater.from(context).inflate(layoutId, parent, false); //实例化出ViewHolder的一个对象 holder = clazz.newInstance(); //获取ViewHolder对象中的所有成员属性 Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { //设置成员属性可修改 field.setAccessible(true); //找到这个成员属性给它的注解,对应为控件ID ListAdapterViewId viewIdAnnotation = field.getAnnotation(ListAdapterViewId.class); //如果给定的注解不为空 if (viewIdAnnotation != null) { //获取得到注解中指定的控件ID int viewId = viewIdAnnotation.value(); //在布局中找到此控件 View view = convertView.findViewById(viewId); if (view != null) { //找到此控件后,对ViewHolder的对象设置对应成员属性为找到的这个控件 field.set(holder, view); //将此控件放入集合中 holderFields.put(viewId, view); } else { throw new RuntimeException("ViewHolder类中的" + field.getName() + "注解的View Id无法找到"); } } } //和普通写法相同 convertView.setTag(holder); } else { throw new RuntimeException("传入的ViewHolder类没有注解指定布局ID"); } } else { holder = (BaseViewHolder) convertView.getTag(); } //再次获取此ViewHolder的所有成员属性,这一步是用来设置具体的值 for (Field field : clazz.getDeclaredFields()) { //获取对应成员属性的指定注解 ListAdapterViewId holderViewAnnotation = field.getAnnotation(ListAdapterViewId.class); //判断ViewHolder有没有给这个成员属性添加注解,添加了注解才能继续下一步 if (holderViewAnnotation != null) { //获取ListView中对应下标的此处的实体,再获取此实体类的成员变量,对Bean类的属性进行遍历 for (Field beanField : getItem(position).getClass().getDeclaredFields()) { //获取实体类中的成员属性的注解,需要这个注解和View的ID进行对比 ListAdapterViewId annotation = beanField.getAnnotation(ListAdapterViewId.class); //设置可修改Bean类对应的成员属性可修改 beanField.setAccessible(true); //随即判断是否添加了注解,若不注解,则表明“这个变量我不设置到ListView中” if (annotation != null) { //对Bean类的变量与ViewHolder的变量进行对比,如果它们都指向同一个View的ID,则表明它们是一一对应 if (annotation.value() == holderViewAnnotation.value()) { field.setAccessible(true); //field.getType(),获取到这个ViewHolder的成员属性,是什么类型, Class<?> type = field.getType(); if (type.getName().equals(TextView.class.getName())) { //如果是TextView再通过类型来找setText(CharSequence)这个方法 Method setText = type.getMethod("setText", CharSequence.class); //如果能找到setText(CharSequence)方法,表明确实是TextView,并判断对应的View有没有添加到布局中 if (setText != null && holderFields.get(annotation.value()) != null) { //直接执行setText setText.invoke(holderFields.get(annotation.value()), beanField.get(getItem(position))+""); } } //注,动态设置显示的只支持TextView及其子类。对于ImageView,在此不能有ImageLoader来对ImageView进行显示的操作 //这里只管做它该做的事,怎么显示由客户端使用相应的操作,要对其解藕 } } } } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); throw new RuntimeException("传入的ViewHolder类必须有无参数构造方法"); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return convertView; } public static class BaseViewHolder { int index; } }
现在还有价格,我们并不能直接就显示1234,前面还有几个文字显示。
那么就不能直接使用这个BaseAdapter,还得定制一下。
public class MainActivity extends Activity { private ListView listView; private Adapter adapter; private List<GoodsInfo> goodsInfoList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); goGoGo(); listView.postDelayed(new Runnable() { @Override public void run() { getTheList(); } }, 500); } private void initView() { listView = (ListView) findViewById(R.id.test_list_view); goodsInfoList = new ArrayList<>(); adapter = new Adapter(this, ViewHolder.class); } private void goGoGo() { listView.setAdapter(adapter); } private void getTheList() { for (int i = 0; i < 10; i++) { GoodsInfo goodsInfo = new GoodsInfo("商品9527:" + i, (int) (Math.random() * 10), R.mipmap.ic_launcher); goodsInfoList.add(goodsInfo); } adapter.setData(goodsInfoList); } private class Adapter extends MyBaseAdapter<GoodsInfo> { public Adapter(Context context, Class<? extends BaseViewHolder> holderClass) { super(context, holderClass); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); ViewHolder holder = (ViewHolder) getHolderInstance(); holder.price.setText(getString(R.string.price_yuan, getItem(position).getGoodsPrice())); return view; } } @ListAdapterLayoutId(R.layout.list_view_item_goods) public static class ViewHolder extends MyBaseAdapter.BaseViewHolder { @ListAdapterViewId(R.id.goods_pic) ImageView image; @ListAdapterViewId(R.id.goods_name) TextView name; @ListAdapterViewId(R.id.goods_price) TextView price; @ListAdapterViewId(R.id.goods_buy) Button buy; } }
我们下期再来解决点击某个控件执行相应的方法。
相关文章推荐
- GDB之coredump的学习
- 【一周读书】年轻人你可别哭啊
- 优化MySchool总结习题
- eclipse 添加resources 目录
- Linux ./configure --prefix 命令是什么意思?
- HDOJ-2069 Coin Change(母函数)
- LeetCode108—Convert Sorted Array to Binary Search Tree
- Struts2中jsp前台传值到action后台的三种方式 <转载>属性加载,模型加载
- ffmpeg参数中文详细解释
- java中的常用类、Date和SimpleDateFormat类表示时间、java中基本类型和包装之间的转换
- 第四周项目5递归求阶乘
- 记codeforces两题
- Hibernate逍遥游记-第2章-使用hibernate.properties
- debian ubuntu linux系好用的包管理工具 aptitude
- 输入一个多项式F(X) 计算出(F(X))^P
- 简单谈谈C++中的引用与指针
- 关于surf显示立体图,可视化分析数据
- 初识NuGet - 概念, 安装和使用
- jdk环境变量配置
- Ajax and php 2.5