使用ListView
首先主布局xml中需要有<ListView>部件(详细见127)
然后需要为<ListView>子项创建一个自定义布局fruit_item.xml例如:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <ImageView
- android:id="@+id/fruit_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/fruit_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginLeft="10dip" />
- </LinearLayout>
还要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。
- public class FruitAdapter extends ArrayAdapter<Fruit> {
- private int resourceId;
- public FruitAdapter(Context context, int textViewResourceId,
- List<Fruit> objects) {
- super(context, textViewResourceId, objects);
- resourceId = textViewResourceId;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- Fruit fruit = getItem(position);
- View view = LayoutInflater.from(getContext()).inflate(resourceId, null);
- ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
- TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
- fruitImage.setImageResource(fruit.getImageId());
- fruitName.setText(fruit.getName());
- return view;
- }
- }
FruitAdapter 重写了父类的一组构造函数,用于将上下文、ListView 子项布局的 id 和数据都传递进来。另外又重写了 getView() 方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过 getItem() 方法得到当前项的 Fruit 实例,然后使用 LayoutInflater 来为这个子项加载我们传入的布局,接着调用 View 的 findViewById() 方法分别获取到 ImageView 和 TextView 的实例,并分别调用它们的 setImageResource() 和 setText() 方法来设置显示的图片和文字,最后将布局返回,这样我们自定义的适配器就完成了。
- public class MainActivity extends Activity {
- private List<Fruit> fruitList = new ArrayList<Fruit>();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initFruits(); // 初始化水果数据
- FruitAdapter adapter = new FruitAdapter(MainActivity.this,
- R.layout.fruit_item, fruitList);
- ListView listView = (ListView) findViewById(R.id.list_view);
- listView.setAdapter(adapter);
- }
- private void initFruits() {
- Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
- fruitList.add(apple);
- Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
- fruitList.add(banana);
- Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
- fruitList.add(orange);
- }
- }
提升ListView的运行效率
之所以说 ListView 这个控件很难用,就是因为它由很多的细节可以优化,其中运行效率就是很重要的一点。目前我们 ListView 的运行效率是很低的,因为在 FruitAdapter 的 getView() 方法中每次都将布局重新加载了一遍,当 ListView 快速滚动的时候这就会成为性能的瓶颈。
仔细观察,getView() 方法中还有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改 FruitAdapter 中的代码,如下所示:
可以看到,现在我们在 getView() 方法中进行了判断,如果 convertView 为空,则使用 LayoutInflater 去加载布局,如果不为空则直接对 convertView 进行重用。这样就大大提高了 ListView 的运行效率,在快速滚动的时候也可以表现出更好的性能。
不过,目前我们的这份代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但是每次在 getView() 方法中还是会调用 View 的 findViewById() 方法来获取一次控件的实例。我们可以借助一个 ViewHolder 来对这部分性能进行优化,修改 FruitAdapter 中的代码,如下所示:
我们新增了一个内部类 ViewHolder,用于对控件的实例进行缓存。当 convertView 为空的时候,创建一个 ViewHolder 对象,并将控件的实例都存放在 ViewHolder 里,然后调用 View 的 setTag() 方法,将 ViewHolder 对象存储在 View 中。当 convertView 不为空的时候则调用 View 的 getTag() 方法,把 ViewHolder 重新取出。这样所有控件的实例都缓存在了 ViewHolder 里,就没有必要每次都通过 findViewById() 方法来获取控件实例了。
通过这两步的优化之后,我们 ListView 的运行效率就已经非常不错了。
ListView的点击事件
- listView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- Fruit fruit = fruitList.get(position);
- Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
- }
- });