学技术就是要学细节
上一节我们初步了解了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) { // TODO Auto-generated constructor stub this.context = context; items = new int[10]; for(int i=0;i<10;i++) { items[i] = (int)(Math.random()*100); } } public int getCount() { // TODO Auto-generated method stub return items.length; } public Object getItem(int position) { // TODO Auto-generated method stub return null; } public long getItemId(int position) { // TODO Auto-generated method stub return 0; } public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub } });
等等!突然写不下去了,我只知道我点击的item的position,但是拿不到数据啊!其实解决办法很多,你可以在adapter中提供一个获取里面items的方法,然后在这里获取items,拿到数据;或者就把数据放在Activity中,通过一个set方法set到Adapter中去;但是这些都不是android的菜,android早就知道我们有这个需求,为我们提供了解决方法,还记得getItem么?我们将getItem写成这样:
public Object getItem(int position) { // TODO Auto-generated method stub Integer integer = new Integer(items[position]); return integer; }
然后我们就有办法了:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub 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们的一个类。
// my ViewHolder class RandomName { public TextView noteast1 = null; public TextView noteast2 = null; }
然后我们的getView变成:
public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub 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还不止这么简单哦!不过后面的高级内容我们等到打好了基础再来学习吧!