转载::::http://blog.csdn.net/yeluoxiang/article/details/25740195
学技术就是要学细节
上一节我们初步了解了BaseAdapter,实现了自己的Adapter,但是BaseAdapter远没有我们想象的那么简单哦,下面我们来详细分析下,先建立一个工程,ListIsNotEasy,实现自己的Adapter。
- package com.example.adapter;
-
- import android.content.Context;
- import android.graphics.Color;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.TextView;
-
- public class MyAdapter extends BaseAdapter {
-
- private Context context = null;
- private int[] items = null;
-
- public MyAdapter(Context context) {
-
- this.context = context;
- items = new int[10];
- for(int i=0;i<10;i++) {
- items[i] = (int)(Math.random()*100);
- }
- }
-
- public int getCount() {
-
- return items.length;
- }
-
- public Object getItem(int position) {
-
- return null;
- }
-
- public long getItemId(int position) {
-
- return 0;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
-
- TextView textView = new TextView(context);
- textView.setText("" + items[position]);
- textView.setTextSize(30);
- textView.setTextColor(Color.RED);
- return textView;
- }
-
- }
这个代码就是我们上一节的水平,我们来看下效果:
这里展示的是一些随机数,下面我有一个要求,我点击了一个大于50的item,就弹出“大于50”,否则弹出“小于或等于50”,那么这该怎么实现呢?看过了上一节的你肯定觉得很简单,不就是加个Listener吗?
- list.setOnItemClickListener(new OnItemClickListener() {
-
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
-
-
- }
- });
等等!突然写不下去了,我只知道我点击的item的position,但是拿不到数据啊!其实解决办法很多,你可以在adapter中提供一个获取里面items的方法,然后在这里获取items,拿到数据;或者就把数据放在Activity中,通过一个set方法set到Adapter中去;但是这些都不是android的菜,android早就知道我们有这个需求,为我们提供了解决方法,还记得getItem么?我们将getItem写成这样:
- public Object getItem(int position) {
-
- Integer integer = new Integer(items[position]);
- return integer;
- }
然后我们就有办法了:
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
-
- Integer item = (Integer) parent.getItemAtPosition(position);
- if(item.intValue() > 50) {
- Toast.makeText(MainActivity.this, "大于50", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(MainActivity.this, "小于或等于50", Toast.LENGTH_SHORT).show();
- }
- }
我们先来看看效果:
效果已达到,到这里我们就知道getItem是干嘛用的了,就是为了在点击时获取数据用的,那么getItemId呢?一样的道理,看到onItemClick有个参数id了吧,这就是getItemId返回的id,到这里你应该懂了吧。但是还没完哦,我们发现getView方法的第二个参数叫convertView,很难理解啊,什么意思呢?我们将它打印出来看看!
我们发现convert view都是null啊,有啥用呢?别着急,convert view只有在列表项较多的时候才能发挥作用!我们增加一些列表项,将10改成20。再看
晕,还是null啊,但是注意,这里只加载了0到11项,因为手机上就显示到11项,我们慢慢往下滑动一下
看到没,第13项的convert view不是null了,那么它是什么呢?你上去看看我们item 0的textView是什么,发现就是它,咦?它把item0的textview放到这里来了,这是干嘛呢?首先我们要明确一点,当item 13显示出来的时候,item 0已经消失了,那么item 0的textview已经不需要了,但是android非常巧妙地把它传到第13项来了,这意味着,如果我们这样写代码:
- public View getView(int position, View convertView, ViewGroup parent) {
-
- System.out.println("position --> " + position);
- System.out.println("convert view --> " + convertView);
- TextView textView = null;
- if(convertView == null) {
- textView = new TextView(context);
- System.out.println("a new textView view --> " + textView);
- } else {
- textView = (TextView) convertView;
- System.out.println("reuse convert view --> " + convertView);
- }
- textView.setText("" + items[position]);
- textView.setTextSize(30);
- textView.setTextColor(Color.RED);
- return textView;
- }
是不是就可以实现view的复用呢?如果我们的convert View不为空,我们就不去创建新的View了,而是用convert view。运行下看看:
看到了吧?我们之后的滑动都是在复用我们的view,这将大大节省内存,不然我们每次滑动都回产生一堆垃圾,不一会儿就运行垃圾回收机制;而这样,我们将垃圾复用,基本上就会产生垃圾了,这就是android,将代码写到极致!
到这里我要公布一个深坑,我们到目前之所以这么顺利,是因为我们的listview用的是android:layout_height="match_parent",但是,如果我们在listview下面写点东西,比如一个textview,是看不到的呀,我们很自然会想到用wrap_parent,就这样,掉进了坑里,我们用wrap_parent试一下,你看看log
一启动就打了144个log,调用了48次getView,而我们的手机只显示12个item,为什么会这样呢?这是因为用wrap_parent,系统一开始并不知道height到底是多少,它只能试着去创建一些View,这样就导致了额外的开销,至于为什么一个view会调用4次,我还真是不清楚,希望有清楚的人能够告诉我啊!
但是我们总不能一直用match_parent吧,其实没必要,我们还有杀手锏,weight属性,我们可以这样布局:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/up_text"
- />
-
- <ListView
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- />
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/bottom_text"
- />
-
- </LinearLayout>
看看效果:

效果不错吧?因为我们用weight属性的时候,系统可以计算出listview的height,就没必要加载多余的view去试啦,不知道weight怎么算的去看我的第四节!
还有一个问题,我们这里的view很简单,只有一个textView,但是有时候我们的view很复杂耶,这时候我们需要通过一个布局文件去加载它,比如:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
-
- <TextView
- android:id="@+id/noteasy1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textSize="20sp"
- />
-
- <TextView
- android:id="@+id/noteasy2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textSize="20sp"
- />
-
- </LinearLayout>
当然,我这里为了突出重点,布局从简了,只有两个TextView,但是也能说明问题了。我们希望把这个布局文件返回给getView函数,但是getView函数要的是View对象,怎么办呢?这里我们就要用LayoutInflater,用layoutInflater = LayoutInflater.from(context);去回去layoutInflater,然后
- public View getView(int position, View convertView, ViewGroup parent) {
-
- System.out.println("position --> " + position);
- System.out.println("convert view --> " + convertView);
- LinearLayout linearLayout = null;
- if(convertView == null) {
- linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.item, null);
- System.out.println("a new linearLayout view --> " + linearLayout);
- } else {
- linearLayout = (LinearLayout) convertView;
- System.out.println("reuse convert view --> " + convertView);
- }
- TextView noteasy1 = (TextView) linearLayout.findViewById(R.id.noteasy1);
- TextView noteasy2 = (TextView) linearLayout.findViewById(R.id.noteasy2);
- noteasy1.setText("position --> " + position);
- noteasy2.setText("" + items[position]);
- return linearLayout;
- }
还是这样,我们重用convertView,但是里面的子控件真的被重用了吗?TextView noteasy1 = (TextView)linearLayout.findViewById(R.id.noteasy1);是产生一个新的TextView还是原来的呢?这点至关重要,我们打印出来看看:
向下滑动:
我们发现noteast1和noteasy2并没有变化,同样做到了复用,真是好极了!但是还有一点问题,我们每次调用getView的时候都去findViewById了,要知道findViewById需要解析XML文件,再去找到View,也是比较耗时的,我们为什么不把View存下来呢?这样不就不需要findViewById了吗?这就是网上盛传的ViewHolder方法,其实ViewHolder并不是一个系统类,而是我们自己创建的一个用来存储View们的一个类。
-
- class RandomName {
- public TextView noteast1 = null;
- public TextView noteast2 = null;
- }
然后我们的getView变成:
- public View getView(int position, View convertView, ViewGroup parent) {
-
- System.out.println("position --> " + position);
- System.out.println("convert view --> " + convertView);
- LinearLayout linearLayout = null;
- TextView noteasy1 = null;
- TextView noteasy2 = null;
- if(convertView == null) {
- linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.item, null);
- noteasy1 = (TextView) linearLayout.findViewById(R.id.noteasy1);
- noteasy2 = (TextView) linearLayout.findViewById(R.id.noteasy2);
- RandomName viewHolder = new RandomName();
- viewHolder.noteast1 = noteasy1;
- viewHolder.noteast2 = noteasy2;
- linearLayout.setTag(viewHolder);
- System.out.println("a new linearLayout view --> " + linearLayout);
- } else {
- linearLayout = (LinearLayout) convertView;
- RandomName viewHolder = (RandomName) linearLayout.getTag();
- noteasy1 = viewHolder.noteast1;
- noteasy2 = viewHolder.noteast2;
- System.out.println("reuse convert view --> " + convertView);
- }
- noteasy1.setText("position --> " + position);
- noteasy2.setText("" + items[position]);
- System.out.println("noteast1 --> " + noteasy1);
- System.out.println("noteast2 --> " + noteasy2);
- return linearLayout;
- }
网上一些资料把ViewHolder弄成内部静态类,我不太清楚为什么弄成静态类,有高手请告诉我一下,这里我还是按大众思维弄成普通内部类吧。
好了,我们现在来兑现我们的承诺,写一个列表,有头像,有名字,有简介,有人品值,还有小红点,实现下拉刷新人品值,不过这次有点多了,留到下一节好了,哈哈!这么黑我的人品值会不会下跌啊?下节就知道啦!
好了我们来总结下今天这节:
1、 getItem和getItemId知道是干嘛的了
2、 AdapterView的getItemAtPosition
3、 convertView详解,ViewHolder
4、 发现深坑:listView不要用wrap_parent,要让系统能够直接计算出它的高度,而不是去试
这节的例子在:http://download.csdn.net/detail/yeluoxiang/7342297,其实ListView还不止这么简单哦!不过后面的高级内容我们等到打好了基础再来学习吧!