使用注解来折腾BaseAdapter(1)

来源:互联网 发布:什么叫sql语句 编辑:程序博客网 时间:2024/06/06 22:39

使用注解来折腾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中定义的属性找到了,先测试一下。
Dubug下能看出来position变了,控件都能找出来
Dubug下能看出来position变了,控件都能找出来

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;    }}

这里写图片描述
我们下期再来解决点击某个控件执行相应的方法。
这里写图片描述
这里写图片描述

2 0
原创粉丝点击