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<ChatSimple> datas;     //数据的集合


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

    public MyLVAdapter(List<ChatSimple> 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<? extends Map<String, ?>> 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 ? "<unknown type>" : 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的实现过程。
欢迎分享交流

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容