您的位置:首页 > 其它

使用注解来折腾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;
}
}




我们下期再来解决点击某个控件执行相应的方法。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: