关于ListView的那些事(笔记)

本文介绍如何在Android应用中使用ListView,并通过缓存布局和ViewHolder模式提高其滚动性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用ListView

首先主布局xml中需要有<ListView>部件(详细见127)

然后需要为<ListView>子项创建一个自定义布局fruit_item.xml例如:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.   
  5.     <ImageView  
  6.         android:id="@+id/fruit_image"  
  7.         android:layout_width="wrap_content"  
  8.         android:layout_height="wrap_content" />  
  9.   
  10.     <TextView  
  11.         android:id="@+id/fruit_name"  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="wrap_content"  
  14.         android:layout_gravity="center"  
  15.         android:layout_marginLeft="10dip" />  
  16.   
  17. </LinearLayout> 

还要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。

  1. public class FruitAdapter extends ArrayAdapter<Fruit> {  
  2.   
  3.     private int resourceId;  
  4.   
  5.     public FruitAdapter(Context context, int textViewResourceId,  
  6.             List<Fruit> objects) {  
  7.         super(context, textViewResourceId, objects);  
  8.         resourceId = textViewResourceId;  
  9.     }  
  10.   
  11.     @Override  
  12.     public View getView(int position, View convertView, ViewGroup parent) {  
  13.         Fruit fruit = getItem(position);  
  14.         View view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  15.         ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  16.         TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  17.         fruitImage.setImageResource(fruit.getImageId());  
  18.         fruitName.setText(fruit.getName());  
  19.         return view;  
  20.     }  
  21. }

FruitAdapter 重写了父类的一组构造函数,用于将上下文、ListView 子项布局的 id 和数据都传递进来。另外又重写了 getView() 方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过 getItem() 方法得到当前项的 Fruit 实例,然后使用 LayoutInflater 来为这个子项加载我们传入的布局,接着调用 View 的 findViewById() 方法分别获取到 ImageView 和 TextView 的实例,并分别调用它们的 setImageResource() 和 setText() 方法来设置显示的图片和文字,最后将布局返回,这样我们自定义的适配器就完成了。

  1. public class MainActivity extends Activity {  
  2.   
  3.     private List<Fruit> fruitList = new ArrayList<Fruit>();  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         initFruits();  // 初始化水果数据  
  10.         FruitAdapter adapter = new FruitAdapter(MainActivity.this,  
  11.                 R.layout.fruit_item, fruitList);  
  12.         ListView listView = (ListView) findViewById(R.id.list_view);  
  13.         listView.setAdapter(adapter);  
  14.     }  
  15.       
  16.     private void initFruits() {  
  17.         Fruit apple = new Fruit("Apple", R.drawable.apple_pic);  
  18.         fruitList.add(apple);  
  19.         Fruit banana = new Fruit("Banana", R.drawable.banana_pic);  
  20.         fruitList.add(banana);  
  21.         Fruit orange = new Fruit("Orange", R.drawable.orange_pic);  
  22.         fruitList.add(orange);  
  23.     }  
  24.   
  25. }  

提升ListView的运行效率

        之所以说 ListView 这个控件很难用,就是因为它由很多的细节可以优化,其中运行效率就是很重要的一点。目前我们 ListView 的运行效率是很低的,因为在 FruitAdapter 的 getView() 方法中每次都将布局重新加载了一遍,当 ListView 快速滚动的时候这就会成为性能的瓶颈。

        仔细观察,getView() 方法中还有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改 FruitAdapter 中的代码,如下所示:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class FruitAdapter extends ArrayAdapter<Fruit> {  
  2.         ......  
  3.   
  4.     @Override  
  5.     public View getView(int position, View convertView, ViewGroup parent) {  
  6.           Fruit fruit = getItem(position);  
  7.         View view;  
  8.         if (convertView == null) {  
  9.             view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  10.         } else {  
  11.             view = convertView;  
  12.         }  
  13.         ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  14.         TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  15.         fruitImage.setImageResource(fruit.getImageId());  
  16.         fruitName.setText(fruit.getName());  
  17.         return view;  
  18.     }  
  19. }  

        可以看到,现在我们在 getView() 方法中进行了判断,如果 convertView 为空,则使用 LayoutInflater 去加载布局,如果不为空则直接对 convertView 进行重用。这样就大大提高了 ListView 的运行效率,在快速滚动的时候也可以表现出更好的性能。

        不过,目前我们的这份代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但是每次在 getView() 方法中还是会调用 View 的 findViewById() 方法来获取一次控件的实例。我们可以借助一个 ViewHolder 来对这部分性能进行优化,修改 FruitAdapter 中的代码,如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class FruitAdapter extends ArrayAdapter<Fruit> {  
  2.         ......  
  3.   
  4.        @Override  
  5.     public View getView(int position, View convertView, ViewGroup parent) {  
  6.         Fruit fruit = getItem(position);  
  7.         View view;  
  8.         ViewHolder viewHolder;  
  9.         if (convertView == null) {  
  10.             view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  11.             viewHolder = new ViewHolder();  
  12.             viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  13.             viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  14.             view.setTag(viewHolder);  
  15.         } else {  
  16.             view = convertView;  
  17.             viewHolder = (ViewHolder) view.getTag();  
  18.         }  
  19.         viewHolder.fruitImage.setImageResource(fruit.getImageId());  
  20.         viewHolder.fruitName.setText(fruit.getName());  
  21.         return view;  
  22.     }  
  23.       
  24.     class ViewHolder {  
  25.           
  26.         ImageView fruitImage;  
  27.           
  28.         TextView fruitName;  
  29.           
  30.     }  
  31. }  

        我们新增了一个内部类 ViewHolder,用于对控件的实例进行缓存。当 convertView 为空的时候,创建一个 ViewHolder 对象,并将控件的实例都存放在 ViewHolder 里,然后调用 View 的 setTag() 方法,将 ViewHolder 对象存储在 View 中。当 convertView 不为空的时候则调用 View 的 getTag() 方法,把 ViewHolder 重新取出。这样所有控件的实例都缓存在了 ViewHolder 里,就没有必要每次都通过 findViewById() 方法来获取控件实例了。

        通过这两步的优化之后,我们 ListView 的运行效率就已经非常不错了。


ListView的点击事件

  1.         listView.setOnItemClickListener(new OnItemClickListener() {  
  2.             @Override  
  3.             public void onItemClick(AdapterView<?> parent, View view,  
  4.                     int position, long id) {  
  5.                 Fruit fruit = fruitList.get(position);  
  6.                 Toast.makeText(MainActivity.this, fruit.getName(),  Toast.LENGTH_SHORT).show();  
  7.             }  
  8.         });  

ListView的一些属性

android:divider="#0000"  它可以指定分割线的颜色
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值