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是一个适配器,适配器有很多,这里我们选用这个可以通过泛型指定数据的适配器,这是一个比较好用的适配器。然后看下效果:
看很神奇吧,但这不是我们要的,接下来我们来看看更为复杂的。
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();
}
});
}
}
效果图
是不是好看多了。
现在我们来分析下代码,首先我们看下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显示出来。看下效果: