ListView 是一个显示一列可滚动项目的视图控件,其组成部分可以大致分为:数据源,Adapter 和 ListView 显示部分。
其中:
事实上,我觉得 ListView 的重点并不是 ListView 本身,而是 Adapter,下面讲解一下 Adapter。
Adapter 本身只是一个接口,它常用的实现类主要有以下几个:
使用 ArrayAdapter
ArrayAdapter 比较简单,它只能用于显示文本,它接收三个参数:
public class MainActivity extends Activity {
private ListView listView;
private String[] arr = new String[]{"天津", "上海", "北京", "深圳"};
private ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, arr);
listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
}
}
显示如下:
SimpleAdapter
名字叫 Simple 但它却并不 Simple,它可以帮我们实现更复杂的界面。SimpleAdapter 接收五个参数:
List<? extends Map<String,?>>
集合,是数据源示例:
MainActivity
public class MainActivity extends Activity {
private ListView listView;
private SimpleAdapter adapter;
private String[] data = {"橘子", "苹果", "香蕉", "西瓜", "樱桃", "猕猴桃", "木瓜", "山竹",
"桃", "葡萄", "山楂", "杏", "核桃", "哈密瓜", "菠萝", "柚子", "无花果", "柠檬", "李子"};
private List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();
;
private String[] from = {"FruitImage", "FruitName", "FruitDesc"};
private int[] to = {R.id.iv_fruit_img, R.id.tv_fruit_name, R.id.tv_fruit_desc};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listview);
adapter = new SimpleAdapter(MainActivity.this, getData(), R.layout.listview_item, from, to);
listView.setAdapter(adapter);
}
private List<Map<String, Object>> getData() {
for (int i = 0; i < data.length; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("FruitImage", R.mipmap.ic_launcher);
map.put("FruitName", data[i]);
map.put("FruitDesc", "甜甜的,酸酸的,有营养,味道好");
dataList.add(map);
}
return dataList;
}
}
listview_item
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_fruit_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp" />
<TextView
android:id="@+id/tv_fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/iv_fruit_img"
android:paddingLeft="20dp"
android:paddingTop="5dp" />
<TextView
android:id="@+id/tv_fruit_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_fruit_name"
android:layout_toRightOf="@id/iv_fruit_img"
android:paddingLeft="20dp"
android:textColor="#f20404" />
</RelativeLayout>
显示如下:
SimpleCusorAdapter
是不过是数据源从 List 换成了 Cursor,用法基本类似,这里就省略了。
BaseAdapter
BaseAdapter 通常用于扩展,扩展 BaseAdapter 可以对各项列表进行最大限度的定制。
我们需要继承 BaseAdapter,然后实现其一系列方法:
BaseAdapter 最重要的就是这个 getView 方法,该方法接收三个参数:
MainActivity:
public class MainActivity extends Activity {
private ListView listView;
private MyAdapter adapter;
private String[] data = {"橘子", "苹果", "香蕉", "西瓜", "樱桃", "猕猴桃", "木瓜", "山竹",
"桃", "葡萄", "山楂", "杏", "核桃", "哈密瓜", "菠萝", "柚子", "无花果", "柠檬", "李子"};
private List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();
private String[] from = {"FruitImage", "FruitName", "FruitDesc"};
private int[] to = {R.id.iv_fruit_image, R.id.tv_fruit_name, R.id.tv_fruit_desc};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listview);
adapter = new MyAdapter(this, getData());
listView.setAdapter(adapter);
}
private List<Map<String, Object>> getData() {
for (int i = 0; i < data.length; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("FruitImage",R.mipmap.ic_launcher);
map.put("FruitName", data[i]);
map.put("FruitDesc","酸酸的,甜甜的,有营养,味道好");
dataList.add(map);
}
return dataList;
}
}
MyAdapter
public class MyAdapter extends BaseAdapter {
private List<Map<String, Object>> list;
private LayoutInflater inflater;
public MyAdapter(Context context, List<Map<String, Object>> list) {
this.list = list;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = inflater.inflate(R.layout.listview_item, null);
ImageView fruitImage = (ImageView) view.findViewById(R.id.iv_fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.tv_fruit_name);
TextView fruitDesc = (TextView) view.findViewById(R.id.tv_fruit_desc);
fruitImage.setImageResource((int)list.get(position).get("FruitImage"));
fruitName.setText((String) list.get(position).get("FruitName"));
fruitDesc.setText((String) list.get(position).get("FruitDesc"));
return view;
}
}
listview_item
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="30dp"
android:layout_marginTop="5dp" />
<TextView
android:id="@+id/tv_fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_toRightOf="@id/iv_fruit_image"/>
<TextView
android:id="@+id/tv_fruit_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_fruit_name"
android:layout_toRightOf="@id/iv_fruit_image" />
</RelativeLayout>
显示就不上了,这里的显示和 SimpleAdapter 差不多,只是没有设置文字颜色。
Adapter 中的缓存
上一个例子中,我们都是直接 inflate 布局,然后再填充数据的,这种情况下,如果我们的 ListView 很多个 Item 的话,就需要执行很多次的 inflate,浪费时间耗费资源,上面讲过了 getView 方法中的 convertView 参数是用来重用的,所以我们可以在 getView 方法中判断一下,如果我们要显示的 View 是否存在,如果不存在,才创建,如果存在,就直接返回,这样就避免了重复创建的麻烦,所以上面的 getView 方法可以这样修改一下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(R.layout.listview_item,null);
}
ImageView fruitImage = (ImageView) convertView.findViewById(R.id.iv_fruit_image);
TextView fruitName = (TextView) convertView.findViewById(R.id.tv_fruit_name);
TextView fruitDesc = (TextView) convertView.findViewById(R.id.tv_fruit_desc);
fruitImage.setImageResource((int) list.get(position).get("FruitImage"));
fruitName.setText((String) list.get(position).get("FruitName"));
fruitDesc.setText((String) list.get(position).get("FruitDesc"));
return convertView;
}
这样就避免了重复创建 view,但是还有一个问题,每个 Item 的显示,还需要每次执行 findViewById,这个也是十分耗费资源的,这里就可以使用 View 对象的 setTag 和 getTag 方法了,我们创建一个新对象,其属性就是我们 View 要展示的内容,然后使用 setTag 将 View 和 这个新对象关联起来,假如 convertView 为空,则创建 View,并设置 tag,如果不为空,则直接通过 getTag 来取出这个对象,然后通过这个对象去设置展示的内容值。
所以 getView 方法还可以这样修改一下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
FruitItem item = null;
if(convertView == null){
item = new FruitItem();
convertView = inflater.inflate(R.layout.listview_item,null);
item.fruitImage = (ImageView) convertView.findViewById(R.id.iv_fruit_image);
item.fruitName = (TextView) convertView.findViewById(R.id.tv_fruit_name);
item.fruitDesc = (TextView) convertView.findViewById(R.id.tv_fruit_desc);
convertView.setTag(item);
}else{
item = (FruitItem) convertView.getTag();
}
item.fruitImage.setImageResource((int)list.get(position).get("FruitImage"));
item.fruitName.setText((String)list.get(position).get("FruitName"));
item.fruitDesc.setText((String)list.get(position).get("FruitDesc"));
return convertView;
}
我们创建的缓存对象:
class FruitItem{
ImageView fruitImage;
TextView fruitName;
TextView fruitDesc;
}
点击监听
监听 ListView 的 Item 的点击,只需要调用 ListView 的 setOnItemClickListener 和 setOnItemLongClickListener,前者监听普通的 Item 点击事件,后者处理 Item 的长按事件。
滑动监听
设置 Item 分割线
为 ListView 设置 Item 分割线和给布局设置分割线一样,都是使用 android:divider
和 android:dividerHeight
两个属性,当 divider 属性为 @null
的时候,分割线会变成透明的
隐藏 ListView 的滚动条
默认的 ListView 滚动的时候,屏幕右侧会显示滚动条,将 android:scrollbars
属性设置为 node
,会隐藏右侧的滚动条。
设置 ListView 显示第几项
ListView 默认从第一个 Item 开始显示,通过调用 ListView 对象的 setSelection 方法,可以设置从第几项显示
动态更新 ListView
当 ListView 中的数据发生变化的时候,我们不用重新为 ListView 设置 Adapter,那样的话太没有效率,我们仅仅需要调用 adapter 的 notifyDataSetChanged()
方法即可。
调用 notifyDataSetChanged 方法需要注意的是,传入 Adapter 的数据 List 是同一个 List 对象,否则将无法实现更新。
public class MainActivity extends Activity {
private ListView listView;
private List<String> data = new ArrayList<String>();
private Button bt;
private ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data.add("橘子");
data.add("苹果");
data.add("香蕉");
data.add("西瓜");
data.add("樱桃");
data.add("猕猴桃");
listView = (ListView) findViewById(R.id.listview);
bt = (Button) findViewById(R.id.bt);
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, data);
listView.setAdapter(adapter);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
data.add("柚子");
adapter.notifyDataSetChanged();
}
});
}
}
获取 ListView 的子 Item
ListView 包含若干个 Item,自然也就有相应的有关子 Item 的方法了两个获取相关子 Item 的方法:
需要注意的是,getChildCount 方法获取到的只是显示在屏幕上的 Item 的数量,如果要获取全部的 Item,需要调用 ListView 的 getCount 方法。