学技术就是要学细节

上一节我们初步了解了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 stubthis.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 stubreturn items.length;}public Object getItem(int position) {// TODO Auto-generated method stubreturn null;}public long getItemId(int position) {// TODO Auto-generated method stubreturn 0;}public View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubTextView 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 stubInteger integer = new Integer(items[position]);return integer;}

然后我们就有办法了:

public void onItemClick(AdapterView<?> parent, View view,int position, long id) {// TODO Auto-generated method stubInteger 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 stubSystem.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"         />    <ListViewandroid: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 stubSystem.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 ViewHolderclass RandomName {public TextView noteast1 = null;public TextView noteast2 = null;}

然后我们的getView变成:

public View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubSystem.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还不止这么简单哦!不过后面的高级内容我们等到打好了基础再来学习吧!


更多相关文章

  1. 使用ScrollView实现滚动效果
  2. 2014.04.22 ——— android listview header和左右滑动冲突
  3. 历史最牛 多页切换TabHost,给大家参考。
  4. Android里五种 Toast方式
  5. UltimateRecyclerView发布,Android下新Listview的大杀器
  6. Android电子书翻页效果实现
  7. Android(安卓)仿微信对话列表滑动删除效果
  8. android画出图片阴影!!!!!!!!!!!!!!
  9. Android之利用android:indeterminateDrawable来实现ProgressBar

随机推荐

  1. Android(安卓)Device Monitor工具的使用
  2. 【Android】 Android中对于时间的转换
  3. 输入法问题
  4. Layout 笔记
  5. MQTT的学习研究(十五) MQTT 和android整合
  6. ANDROID 编译源码6.0 问题记录
  7. Android之MediaCodec使用经验分享
  8. android 实现代码关机
  9. 图片缓存优化
  10. Android8.0 页面崩溃问题