使用注解来折腾BaseAdapter(1)

使用注解来折腾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.0 ViewHolder ViewHolder convertView convertView Bean Bean 把控件给我找出来 我的这个属性你给我设置出来显示一下

如果不考虑复用和多种布局的情况下,一个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;
    }
}

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值