动手学Android之九——列表没那么简单

转载::::http://blog.csdn.net/yeluoxiang/article/details/25740195




学技术就是要学细节

         上一节我们初步了解了BaseAdapter,实现了自己的Adapter,但是BaseAdapter远没有我们想象的那么简单哦,下面我们来详细分析下,先建立一个工程,ListIsNotEasy,实现自己的Adapter。

[java]  view plain copy
  1. package com.example.adapter;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Color;  
  5. import android.view.View;  
  6. import android.view.ViewGroup;  
  7. import android.widget.BaseAdapter;  
  8. import android.widget.TextView;  
  9.   
  10. public class MyAdapter extends BaseAdapter {  
  11.   
  12.     private Context context = null;  
  13.     private int[] items = null;  
  14.       
  15.     public MyAdapter(Context context) {  
  16.         // TODO Auto-generated constructor stub  
  17.         this.context = context;  
  18.         items = new int[10];  
  19.         for(int i=0;i<10;i++) {  
  20.             items[i] = (int)(Math.random()*100);  
  21.         }  
  22.     }  
  23.       
  24.     public int getCount() {  
  25.         // TODO Auto-generated method stub  
  26.         return items.length;  
  27.     }  
  28.   
  29.     public Object getItem(int position) {  
  30.         // TODO Auto-generated method stub  
  31.         return null;  
  32.     }  
  33.   
  34.     public long getItemId(int position) {  
  35.         // TODO Auto-generated method stub  
  36.         return 0;  
  37.     }  
  38.   
  39.     public View getView(int position, View convertView, ViewGroup parent) {  
  40.         // TODO Auto-generated method stub  
  41.         TextView textView = new TextView(context);  
  42.         textView.setText("" + items[position]);  
  43.         textView.setTextSize(30);  
  44.         textView.setTextColor(Color.RED);  
  45.         return textView;  
  46.     }  
  47.   
  48. }  

         这个代码就是我们上一节的水平,我们来看下效果:

动手学Android之九——列表没那么简单_第1张图片

         这里展示的是一些随机数,下面我有一个要求,我点击了一个大于50的item,就弹出“大于50”,否则弹出“小于或等于50”,那么这该怎么实现呢?看过了上一节的你肯定觉得很简单,不就是加个Listener吗?

[java]  view plain copy
  1. list.setOnItemClickListener(new OnItemClickListener() {  
  2.   
  3.     public void onItemClick(AdapterView<?> parent, View view,  
  4.             int position, long id) {  
  5.         // TODO Auto-generated method stub  
  6.           
  7.     }  
  8. });  

         等等!突然写不下去了,我只知道我点击的item的position,但是拿不到数据啊!其实解决办法很多,你可以在adapter中提供一个获取里面items的方法,然后在这里获取items,拿到数据;或者就把数据放在Activity中,通过一个set方法set到Adapter中去;但是这些都不是android的菜,android早就知道我们有这个需求,为我们提供了解决方法,还记得getItem么?我们将getItem写成这样:

[java]  view plain copy
  1. public Object getItem(int position) {  
  2.     // TODO Auto-generated method stub  
  3.     Integer integer = new Integer(items[position]);  
  4.     return integer;  
  5. }  

         然后我们就有办法了:

[java]  view plain copy
  1. public void onItemClick(AdapterView<?> parent, View view,  
  2.         int position, long id) {  
  3.     // TODO Auto-generated method stub  
  4.     Integer item = (Integer) parent.getItemAtPosition(position);  
  5.     if(item.intValue() > 50) {  
  6.         Toast.makeText(MainActivity.this"大于50", Toast.LENGTH_SHORT).show();  
  7.     } else {  
  8.         Toast.makeText(MainActivity.this"小于或等于50", Toast.LENGTH_SHORT).show();  
  9.     }  
  10. }  

         我们先来看看效果:

动手学Android之九——列表没那么简单_第2张图片

         效果已达到,到这里我们就知道getItem是干嘛用的了,就是为了在点击时获取数据用的,那么getItemId呢?一样的道理,看到onItemClick有个参数id了吧,这就是getItemId返回的id,到这里你应该懂了吧。但是还没完哦,我们发现getView方法的第二个参数叫convertView,很难理解啊,什么意思呢?我们将它打印出来看看!

动手学Android之九——列表没那么简单_第3张图片

         我们发现convert view都是null啊,有啥用呢?别着急,convert view只有在列表项较多的时候才能发挥作用!我们增加一些列表项,将10改成20。再看

动手学Android之九——列表没那么简单_第4张图片 动手学Android之九——列表没那么简单_第5张图片

         晕,还是null啊,但是注意,这里只加载了0到11项,因为手机上就显示到11项,我们慢慢往下滑动一下

动手学Android之九——列表没那么简单_第6张图片

         看到没,第13项的convert view不是null了,那么它是什么呢?你上去看看我们item 0的textView是什么,发现就是它,咦?它把item0的textview放到这里来了,这是干嘛呢?首先我们要明确一点,当item 13显示出来的时候,item 0已经消失了,那么item 0的textview已经不需要了,但是android非常巧妙地把它传到第13项来了,这意味着,如果我们这样写代码:

[java]  view plain copy
  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.     // TODO Auto-generated method stub  
  3.     System.out.println("position --> " + position);  
  4.     System.out.println("convert view --> " + convertView);  
  5.     TextView textView = null;  
  6.     if(convertView == null) {  
  7.         textView = new TextView(context);  
  8.         System.out.println("a new textView view --> " + textView);  
  9.     } else {  
  10.         textView = (TextView) convertView;  
  11.         System.out.println("reuse convert view --> " + convertView);  
  12.     }  
  13.     textView.setText("" + items[position]);  
  14.     textView.setTextSize(30);  
  15.     textView.setTextColor(Color.RED);  
  16.     return textView;  
  17. }  

         是不是就可以实现view的复用呢?如果我们的convert View不为空,我们就不去创建新的View了,而是用convert view。运行下看看:

动手学Android之九——列表没那么简单_第7张图片

         看到了吧?我们之后的滑动都是在复用我们的view,这将大大节省内存,不然我们每次滑动都回产生一堆垃圾,不一会儿就运行垃圾回收机制;而这样,我们将垃圾复用,基本上就会产生垃圾了,这就是android,将代码写到极致!

         到这里我要公布一个深坑,我们到目前之所以这么顺利,是因为我们的listview用的是android:layout_height="match_parent",但是,如果我们在listview下面写点东西,比如一个textview,是看不到的呀,我们很自然会想到用wrap_parent,就这样,掉进了坑里,我们用wrap_parent试一下,你看看log

动手学Android之九——列表没那么简单_第8张图片

         一启动就打了144个log,调用了48次getView,而我们的手机只显示12个item,为什么会这样呢?这是因为用wrap_parent,系统一开始并不知道height到底是多少,它只能试着去创建一些View,这样就导致了额外的开销,至于为什么一个view会调用4次,我还真是不清楚,希望有清楚的人能够告诉我啊!

         但是我们总不能一直用match_parent吧,其实没必要,我们还有杀手锏,weight属性,我们可以这样布局:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.       
  7.     <TextView  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="@string/up_text"   
  11.         />  
  12.       
  13.     <ListView  
  14.         android:id="@+id/list"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="0dp"  
  17.         android:layout_weight="1"   
  18.         />  
  19.       
  20.     <TextView  
  21.         android:layout_width="match_parent"  
  22.         android:layout_height="wrap_content"  
  23.         android:text="@string/bottom_text"   
  24.         />  
  25.   
  26. </LinearLayout>  

         看看效果:

动手学Android之九——列表没那么简单_第9张图片


动手学Android之九——列表没那么简单_第10张图片


         效果不错吧?因为我们用weight属性的时候,系统可以计算出listview的height,就没必要加载多余的view去试啦,不知道weight怎么算的去看我的第四节!

         还有一个问题,我们这里的view很简单,只有一个textView,但是有时候我们的view很复杂耶,这时候我们需要通过一个布局文件去加载它,比如:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="horizontal" >  
  6.       
  7.     <TextView  
  8.         android:id="@+id/noteasy1"   
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_weight="1"  
  12.         android:textSize="20sp"  
  13.         />  
  14.       
  15.     <TextView  
  16.         android:id="@+id/noteasy2"   
  17.         android:layout_width="wrap_content"  
  18.         android:layout_height="wrap_content"  
  19.         android:layout_weight="1"  
  20.         android:textSize="20sp"  
  21.         />  
  22.   
  23. </LinearLayout>  

         当然,我这里为了突出重点,布局从简了,只有两个TextView,但是也能说明问题了。我们希望把这个布局文件返回给getView函数,但是getView函数要的是View对象,怎么办呢?这里我们就要用LayoutInflater,用layoutInflater = LayoutInflater.from(context);去回去layoutInflater,然后

[java]  view plain copy
  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.     // TODO Auto-generated method stub  
  3.     System.out.println("position --> " + position);  
  4.     System.out.println("convert view --> " + convertView);  
  5.     LinearLayout linearLayout = null;  
  6.     if(convertView == null) {  
  7.         linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.item, null);  
  8.         System.out.println("a new linearLayout view --> " + linearLayout);  
  9.     } else {  
  10.         linearLayout = (LinearLayout) convertView;  
  11.         System.out.println("reuse convert view --> " + convertView);  
  12.     }  
  13.     TextView noteasy1 = (TextView) linearLayout.findViewById(R.id.noteasy1);  
  14.     TextView noteasy2 = (TextView) linearLayout.findViewById(R.id.noteasy2);  
  15.     noteasy1.setText("position --> " + position);  
  16.     noteasy2.setText("" + items[position]);  
  17.     return linearLayout;  
  18. }  

         还是这样,我们重用convertView,但是里面的子控件真的被重用了吗?TextView noteasy1 = (TextView)linearLayout.findViewById(R.id.noteasy1);是产生一个新的TextView还是原来的呢?这点至关重要,我们打印出来看看:

动手学Android之九——列表没那么简单_第11张图片

         向下滑动:

动手学Android之九——列表没那么简单_第12张图片

         我们发现noteast1和noteasy2并没有变化,同样做到了复用,真是好极了!但是还有一点问题,我们每次调用getView的时候都去findViewById了,要知道findViewById需要解析XML文件,再去找到View,也是比较耗时的,我们为什么不把View存下来呢?这样不就不需要findViewById了吗?这就是网上盛传的ViewHolder方法,其实ViewHolder并不是一个系统类,而是我们自己创建的一个用来存储View们的一个类。

[java]  view plain copy
  1. // my ViewHolder  
  2. class RandomName {  
  3.     public TextView noteast1 = null;  
  4.     public TextView noteast2 = null;  
  5. }  

         然后我们的getView变成:

[java]  view plain copy
  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.     // TODO Auto-generated method stub  
  3.     System.out.println("position --> " + position);  
  4.     System.out.println("convert view --> " + convertView);  
  5.     LinearLayout linearLayout = null;  
  6.     TextView noteasy1 = null;  
  7.     TextView noteasy2 = null;  
  8.     if(convertView == null) {  
  9.         linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.item, null);  
  10.         noteasy1 = (TextView) linearLayout.findViewById(R.id.noteasy1);  
  11.         noteasy2 = (TextView) linearLayout.findViewById(R.id.noteasy2);  
  12.         RandomName viewHolder = new RandomName();  
  13.         viewHolder.noteast1 = noteasy1;  
  14.         viewHolder.noteast2 = noteasy2;  
  15.         linearLayout.setTag(viewHolder);  
  16.         System.out.println("a new linearLayout view --> " + linearLayout);  
  17.     } else {  
  18.         linearLayout = (LinearLayout) convertView;  
  19.         RandomName viewHolder = (RandomName) linearLayout.getTag();  
  20.         noteasy1 = viewHolder.noteast1;  
  21.         noteasy2 = viewHolder.noteast2;  
  22.         System.out.println("reuse convert view --> " + convertView);  
  23.     }  
  24.     noteasy1.setText("position --> " + position);  
  25.     noteasy2.setText("" + items[position]);  
  26.     System.out.println("noteast1 --> " + noteasy1);  
  27.     System.out.println("noteast2 --> " + noteasy2);  
  28.     return linearLayout;  
  29. }  

         网上一些资料把ViewHolder弄成内部静态类,我不太清楚为什么弄成静态类,有高手请告诉我一下,这里我还是按大众思维弄成普通内部类吧。

         好了,我们现在来兑现我们的承诺,写一个列表,有头像,有名字,有简介,有人品值,还有小红点,实现下拉刷新人品值,不过这次有点多了,留到下一节好了,哈哈!这么黑我的人品值会不会下跌啊?下节就知道啦!

         好了我们来总结下今天这节:

1、  getItem和getItemId知道是干嘛的了

2、  AdapterView的getItemAtPosition

3、  convertView详解,ViewHolder

4、  发现深坑:listView不要用wrap_parent,要让系统能够直接计算出它的高度,而不是去试

这节的例子在:http://download.csdn.net/detail/yeluoxiang/7342297,其实ListView还不止这么简单哦!不过后面的高级内容我们等到打好了基础再来学习吧!

你可能感兴趣的:(动手学Android之九——列表没那么简单)