ListView之Adpter学习

原文发表于vcmo博客

在Android项目开发中,经常会使用到的一个控件就是ListView,对与初学者来说,Adapter或许是一个新鲜的玩意儿。下面我们来一起学习一下ListView如何使用adapter。

为什么要使用Adapter?

adapter顾名思义是“适配器”的意思,它的作用就是将源数据与AdapterView绑定到一起,并且为每一个项创建视图。或许会问,为什么TextView、ImageView这类控件不需要Adapter呢。我的理解是,ListView是用于分批显示大量的数据,所以你总不会手动给它set进入对吧,计算机最擅长的就是重复。所以这种大量重复的的事交给计算机做就好了。

Adapter如何为ListView工作

使用过ListView我们知道,Adapter通过ListView的setAdapter()方法与之建立关系。在setAdapter()方法中将传入的adapter赋值给了ListView的全局变量mAdapter。mAdapter是定义在ListView的父类AbsListView中的一个ListAdapter对象,ListAdapter是一个接口继承自Adapter,BaseAdapter实现了ListAdapter接口。(不知道这样说的大家会不会很绕)

实现一个适配器

现在我们就开始一起写一个Adapter,继承自BaseAdapter,需要实现的方法如下

public class MyLVAdapter extends BaseAdapter {

    private List datas;     //数据的集合


    public void setDatas(List datas) {
        this.datas = datas;
        notifyDataSetChanged();
    }

    public MyLVAdapter(List datas) {        //在构造方法中传入数据并初始化。
        this.datas = datas;
    }

    @Override
    public int getCount() {     //实现adapter中的抽象方法,获取数据的总数。
        return datas.size();
    }

    @Override
    public Object getItem(int position) {   //实现adapter中的抽象方法,获取指定位置的数据对象。
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {       //获取指定item的id,通常使用position作为该item的id。
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {     //  最重要的方法,创建每一个item的视图,并绑定数据。

        Holder mHolder;

        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_simple,null,false);
            mHolder = new Holder(convertView);
            convertView.setTag(mHolder);
        }else {
            mHolder = (Holder) convertView.getTag();
        }

        mHolder.setDatas(datas.get(position));

        return convertView;
    }

    class Holder {
        TextView name;
        TextView message;

        public Holder(View parentView) {
            name = (TextView) parentView.findViewById(R.id.item_name);
            message = (TextView) parentView.findViewById(R.id.item_message);
        }

        public void setDatas(ChatSimple chat){
            name.setText(chat.name);
            message.setText(chat.message);
        }


    }
}

ChatSimple是自定义的数据模型:

public class ChatSimple {
    public String name;
    public String message;
}

在上面的代码中,我们提到getView()方法,创建每一个item的视图并绑定数据。去ListView中看看它是如何被调用的。在ListView的父类AbsListView中可以找到adapter的getView()方法被调用的地方:

View obtainView(int position, boolean[] isScrap) {
        ···
       final View scrapView = mRecycler.getScrapView(position);
        final View child = mAdapter.getView(position, scrapView, this);
        ···
}

首先会通过mRecycler获得指定位置的废弃View(ListView中itemView的循环利用)。调用adapter的getView得到绑定数据后的itemView。
再回到adapter中的

public View getView(int position, View convertView, ViewGroup parent) {
}

此时这里的传入的参数就一目了然

  • position——指定item的位置
  • convertView——循环利用的itemView对象,需要注意,在ListView刚开始构建的时候,这里传入的为null,因为还没有itemView可以被循环利用。所以需要根据convertView== null,判断是否需要填充新的itemView视图。
  • parent——在AbsListView中调用时传入的是this,也就是这个Adapter关联的ListView对象。

上面我们的代码中还定义了一个Holder类型,并且将其绑定到convertView的Tag中。是为了提高效率,因为listView每个item的布局都是相同的,如果每次我们拿到循环的itemView,想要将数据对应的绑定上去,必须拿到相应的控件才能操作,又重新findViewById吗?不,不需要,只需要定义一个Holder,去做这件事,并且将控件保存,然后为每个ItemView绑定一个Holder。下次循环使用的时候,我们拿到了holder也就拿到了itemView所有的控件。

回看SimpleAdapter

SimpleAdapter是android系统提供的一个BaseAdapter的实现类,先看一些它的构造方法:

public SimpleAdapter(Context context, List> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

了解一下这些参数:

  • context:这个不必说,上下文。
  • data:传入的数据集合,一个List,存储的类型是一个Map。
  • resource:item的布局文件
  • from:一个String类型的数组,这个数组就是上面data中每一项Map的key,会根据这个key拿到存在Map中真正的data。
  • to:一个int类型的数组,也就是上面resource布局里面每一个控件的id值。据此拿到每一个控件的对象。

OK,上面说到Adapter中最重要的方法是getView,直奔getView方法:

    public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(mInflater, position, convertView, parent, mResource);
    }

调用了createViewFromResource方法:

private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
            ViewGroup parent, int resource) {
        View v;
        if (convertView == null) {
            v = inflater.inflate(resource, parent, false);
        } else {
            v = convertView;
        }

        bindView(position, v);

        return v;
    }

这里面像我们前面分析的那样,根据convertView==null判断是否填充新的itemView,获得到itemView后调用了bindView()方法。继续往下看:

    private void bindView(int position, View view) {
        final Map dataSet = mData.get(position);
        if (dataSet == null) {
            return;
        }

        final ViewBinder binder = mViewBinder;
        final String[] from = mFrom;
        final int[] to = mTo;
        final int count = to.length;

        for (int i = 0; i < count; i++) {
            final View v = view.findViewById(to[i]);
            if (v != null) {
                final Object data = dataSet.get(from[i]);
                String text = data == null ? "" : data.toString();
                if (text == null) {
                    text = "";
                }

                boolean bound = false;
                if (binder != null) {
                    bound = binder.setViewValue(v, data, text);
                }

                if (!bound) {
                    if (v instanceof Checkable) {
                        if (data instanceof Boolean) {
                            ((Checkable) v).setChecked((Boolean) data);
                        } else if (v instanceof TextView) {
                            // Note: keep the instanceof TextView check at the bottom of these
                            // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                            setViewText((TextView) v, text);
                        } else {
                            throw new IllegalStateException(v.getClass().getName() +
                                    " should be bound to a Boolean, not a " +
                                    (data == null ? "" : data.getClass()));
                        }
                    } else if (v instanceof TextView) {
                        // Note: keep the instanceof TextView check at the bottom of these
                        // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                        setViewText((TextView) v, text);
                    } else if (v instanceof ImageView) {
                        if (data instanceof Integer) {
                            setViewImage((ImageView) v, (Integer) data);                            
                        } else {
                            setViewImage((ImageView) v, text);
                        }
                    } else {
                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
                                " view that can be bounds by this SimpleAdapter");
                    }
                }
            }
        }
    }

代码不长,我们一步步来分析:

  1. 首先根据position去datas里拿到当前item的数据(Map)。
  2. 根据to[]获取到每一个控件,并从Map中拿到当前控件的数据。(可以看到,上面在to[]获得控件,并从from[]中得到该控件的数据,使用的是同一下标i,所以to[]与from[]数据必须是对应的
  3. 判断view(根据to[]获取的控件视图)的类型,并调用相应的方法设置数据。

OK,到此我们已经学会了自己编写ListView的Adapter,并且分析了系统的实现类SimpleAdapter的实现过程。
欢迎分享交流

原创博文,转载请注明出处

你可能感兴趣的:(ListView之Adpter学习)