listview是android上非常常用的一个控件,也是一个很复杂的控件。当初看这个,把我搞得各种头疼。现在我们来具体看看这个玩意到底有多么难搞。

1.5.1 ListView

首先我们知道Android的手机屏幕大小很有限,但是我们开发程序的时候,肯定都会有大量数据,通常一个页面根本无法显示完全,难道我们要满了数据就换个Activity?当然这是不现实的,所以我们这里就要借助ListView了。ListView允许用户滑动数据,用户可以上下滑动将屏幕外的数据加载到屏幕内,同时屏幕内的数据会滑出去。具体如何,你可以打开手机打开联系人,上下滑动下你就知道了。


现在我们来做一个简单的ListView
首先创建布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <ListView 
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</LinearLayout>
package com.marisa.androidlistview;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends Activity {

    private String[] company = {"Google", "Microsoft", "IBM", "Oracle",
            "Apple", "Dell", "Google", "Microsoft", "IBM", "Oracle",
            "Apple", "Dell", "Google", "Microsoft", "IBM", "Oracle",
            "Apple", "Dell"
    };
    private ListView mListView = null;
    private ArrayAdapter<String> adapter =null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.list_view);
        adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1,
                company);
        mListView.setAdapter(adapter);
    }
}

在上面我们发现一个奇怪的东西ArrayAdapter,这是因为ListView它无法读取数据,不管是服务器上的还是数据库里的,还是我们这个数组里的,ListView都无法直接读取,这时它就要借助这个ArrayAdapter,ArrayAdapter是一个适配器,适配器有很多,这里我们选用这个可以通过泛型指定数据的适配器,这是一个比较好用的适配器。然后看下效果:

Android 的listview属性详解 android开发listview_android


看很神奇吧,但这不是我们要的,接下来我们来看看更为复杂的。


1.5.2 自定义ListView

单纯只有文字是不是觉得很单调?所以我们给这个ListView加点图片怎么样。说干我们就干!
首先布局不变,我们先定义一个实体类。

package com.marisa.androidlistview;

public class Company {
    private String name = null;
    private int imgId = 0;

    public Company(String n, int id) {
        name = n;
        imgId = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImgId() {
        return imgId;
    }

    public void setImgId(int imgId) {
        this.imgId = imgId;
    }

}

现在定义自定义ListView布局

<?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" >

    <ImageView 
        android:id="@+id/company_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/company_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="10dip" />

</LinearLayout>

接下来自定义adapter,新建一个类继承BaseAdapter

package com.marisa.androidlistview;

import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter {

    private LayoutInflater mInflater;  
    private List<Company> mDatas; 

    public MyAdapter(Context context, List<Company> objects) {
        mInflater = LayoutInflater.from(context);
        mDatas = objects;
    }
    @Override
    public int getCount() {
        return mDatas.size();
    }
    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Company cm = mDatas.get(position);
        View v = null;
        ViewHolder mView = null;
        if (convertView == null) {
            mView = new ViewHolder();
            v = mInflater.inflate(R.layout.company_item, parent,false);
            mView.mTextView = (TextView) v.findViewById(R.id.company_name);
            mView.mImageView = (ImageView) v.findViewById(R.id.company_id);
            v.setTag(mView);
        } else {
            v = convertView;
            mView = (ViewHolder) v.getTag();
        }
        mView.mTextView.setText(cm.getName());
        mView.mImageView.setImageResource(cm.getImgId());
        return v;
    }

    private class ViewHolder {
        TextView mTextView = null;
        ImageView mImageView = null;
    }

}

主类

package com.marisa.androidlistview;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private List<Company> mDatas = new ArrayList<Company>();
    private ListView mListView = null;
    private MyAdapter adapter = null;

    private void initDatas() {
        mDatas.add(new Company("Google", R.drawable.google));
        mDatas.add(new Company("Apple", R.drawable.apple));
        mDatas.add(new Company("IBM", R.drawable.ibm));
        mDatas.add(new Company("Lenovo", R.drawable.lenovo));
        mDatas.add(new Company("Microsoft", R.drawable.microsoft));
        mDatas.add(new Company("Oracle", R.drawable.oracle));
        mDatas.add(new Company("Sony", R.drawable.sony));
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initDatas();
        mListView = (ListView) findViewById(R.id.list_view);
        adapter = new MyAdapter(MainActivity.this, mDatas);
        mListView.setAdapter(adapter);
        mListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                Toast.makeText(MainActivity.this, 
                        mDatas.get(position).getName(), 
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

效果图

Android 的listview属性详解 android开发listview_android_02


是不是好看多了。


现在我们来分析下代码,首先我们看下BaseAdapter,BaseAdapter是直接继承自java.lang.Object的。

直接子类
ArrayAdapter, CursorAdapter, SimpleAdapter
间接子类
ResourceCursorAdapter, SimpleCursorAdapter

根据API是这么介绍BaseAdapter的:

用于ListView(实现指定的ListAdapter接口)和Spinner(实现指定的SpinnerAdapter接口)的共同实现一个公共基类适配器。

首先我们看见继承这个BaseAdapter,我们需要实现四个方法

public int getCount();
public Object getItem(int position);
public long getItemId(int position);
public View getView(int position, View convertView, ViewGroup parent);

1)public int getCount();这个就是返回我们数据集的长度,也就是数据集的个数,好让我们在View调用。
2)public Object getItem(int position);获取数据集的数据项。
3) public long getItemId(int position);获取数据集的ID,以便我们调用数据
4)public View getView(int position, View convertView, ViewGroup parent);绘制我们的ListView,这个方法在每个子项被滚动到屏幕内的时候会被调用。


你们在观察以上代码时,肯定会发现一个private class ViewHolder的内部类,为啥要写这个内部类?因为ListView是个很常用的控件,而且ListView里的内容不可能就那么点,所以这时我们就需要考虑运行的效率了。通常如果我们直接写进getView里面,我们滑动一次ListView就要加载一次getView,但数据多而且我们又快速滑动时,就会出现问题了。所以我们需要优化它,怎么样优化呢?那就是给他设置缓存,让它不会一直加载,这时我们就需要使用convertView进行布局缓存。这里我们还使用一个setTag()ViewHolder保存进View,这样我们就不需要每次加载时都使用一次findViewById()方法。所以我们这里用一个这里我们加个判断,如果convertView如果为空,就加载布局,否则就直接调用缓存中的View和使用一个getTag()方法提出ViewHolder


1.5.3 ListView点击事件

单纯能看能拖,还是不够的,我们还必须让我们点击它会有动作。
所以我们给它一个监听器

mListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                Toast.makeText(MainActivity.this, 
                        mDatas.get(position).getName(), 
                        Toast.LENGTH_SHORT).show();
            }
        });

我们依靠 int position找到需要项的位置,然后添加点击效果,当用户点击Item是就会回滚这个onItemClick方法。然后通过Toast显示出来。看下效果:

Android 的listview属性详解 android开发listview_数据_03