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

学技术就是要学细节

         上一节我们初步了解了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还不止这么简单哦!不过后面的高级内容我们等到打好了基础再来学习吧!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值