ListView
简介:ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,例如QQ的聊天记录等。所谓ListView也就是把具有相同布局的View给放在一个列表中展示而已。
使用ListView需要设置数据适配器Adapter,那什么是Adapter?
Adapter
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
一个Adapter是AdapterView视图与数据之间的桥梁,Adapter提供对数据的访问,也负责为每一项数据产生一个对应的View,后将View添加到ListView之中。
可能直接这样说比较抽象,下面我们来举例说明一下:我们的手机通讯录就是一个简单的ListView。通讯录中有一条一条的联系人,每一个联系人都显示了相应的信息。现在通讯录比较简单,一般情况下只显示头像和姓名,以前还会显示手机号。每一条联系人的显示方式又是一个View。Adapter适配器将我们的信息(头像和姓名)根据一定的格式适配到每个View中,然后再将View添加到ListView中。大家根据这个例子可能对ListView控件和Adapter适配器以及他们之间的关系有了更好的理解了。
Adapter的构造函数有三个参数,分别代表当前上下文
、ListView子项布局的id
和要适配的数据
。
Android提供了很多适配器的实现类,我们这里先看看ArrayAdapter,其通过泛型来指定要适配的数据,比如我们要传入文本类型的数据:
private String[] data = {"a", "b", "c", "d", "e", "f", "g"};
@Override
protected void onCreate(Bindle savedInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.id.activity_main);
//生成适配器实例,其中android.R.layout.simple_list_item_1是内置的简单布局,只含有一个TextView
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, data);
//生成ListView实例并载入适配器,
//其中R.id.lsit_view是自定义的ListView控件,该控件的用法和普通控件一样,这里就不写布局的代码了
ListView listView = (ListView) findViewById(R.id.lsit_view);
listView.setAdapter(adapter);
}
Adapter中有一个getView()
方法,每当子项被滚动到屏幕内的时候就会被ListView调用,用于干什么呢?当然是给当前的ListView的子项填进一个View,所以理所当然该方法返回的是View类型,因此如果要自定义ListView的子项布局的话,就可以自己新建一个View然后再用自定义的布局的控件跟View绑定到一起。
那怎么绑定呢?我们看一下下面的例子:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(getContext()).inflate(自定义子项布局的id, parent, false);
//对View中的控件进行设置,如:
TextView text = (TextView) view.findViewById(R.id.text);
text.setText("Hello");
return view;
}
这里有必要再解释一下LayoutInflater.from(getContext()).inflate()
。
LayoutInflater.from(getContext())
可以构造出一个LayoutInflater对象,然后调用inflate()
方法动态加载一个布局。inflate()
方法有三个参数,,第一个参数是要填充的布局的id,第二个是父布局,第三个是是否把布局先添加到父布局再返回View。其中第三个参数省略的话默认为true:
inflate()
方法第三个参数attachToRoot是true的话,那第一个参数的layout文件就会被填充并附加在第二个参数所指定的ViewGroup内。方法返回结合后的View,根元素是第二个参数ViewGroup。如果是false的话,第一个参数所指定的layout文件会被填充并作为View返回。这个View的根元素就是layout文件的根元素。不管是true还是false,都需要ViewGroup的LayoutParams来正确的测量与放置layout文件所产生的View对象。作者:斯科特安链接:http://www.jianshu.com/p/41796f541e67來源:简书 著作权归作者所有。
反正就是这样来给View加载布局的。
然后就是要说说怎么自定义Adapter了,比如我们要在ListView中展示水果图片+名字的一组列表:
/*在定义Adapter之前先定义一个我们要展示的数据的类*/
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imagId) {
this.name = name;
this.imageId = imageId;
}
//此处省略两个成员变量的getter和setter方法
...
}
/*一般自定义Adapter可以选择继承ArrayAdapter,可以省下很多功夫*/
public class FruitAdapter extends ArrayAdapter<Fruit> {
//resourceId是我们自定义的布局的id,在后面的getView()方法中需要用来给View加载布局
private int resourceId;
//第二个参数之所以叫textViewResourceId是因为我们继承的ArrayAdapter就是只用来展示TextView的
//我们自定义的时候就可以传入我们自定义的布局id
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) {
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
//对View中的控件进行设置
TextView fruitName = (TextView) view.findViewById(R.id.name);
ImagView fruitImage = (ImageView) view.findViewById(R.id.image)
//getItem()方法是继承的ArrayAdapter中实现的方法,用于获取当前项的数据的实例
Fruit fruit = getItem(position);
fruitName.setText(fruit.getName());
fruitImage.setImageResource(fruit.getImageId);
return view;
}
}
自定义Adapter就是这样子了,应该没什么问题吧, 使用的话就跟ArrayAdapter的用法类似就可以了。
下面来学习怎么优化ListView的性能。
首先由于每次子项进入怕屏幕的时候都会调用getView()
方法,这样会导致每次都重新加载布局,当ListView快速滚动的时候性能就很差。
仔细看Adapter的getView()
方法,还会发现它的第二个参数convertView我们没有用到,这个参数就是用来缓存之前加载好的布局的。所以我们修改一下上面例子中getView()
的内容:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
...
}
}
其次由于getView()
方法中每次都会调用findViewById()
来获取控件实例,因此我们可以借助一个ViewHolder来进行优化:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.image = (ImageView) view.findViewById(R.id.iamge);
viewHolder.name = (TextView) view.findViewById(R.id.name);
//调用View的setTag()方法,该方法可以存储和View相关联的信息,然后调用View的getTag()可以读取信息
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.name.setText(fruit.getName());
viewHolder.image.setImageResource(fruit.getImageId());
return view;
}
}
class ViewHolder {
ImageView image;
TextView name;
}
至此ListView的性能就很不错了。
ListView的每一个子项是可以点击的,下面学习一下如何注册点击事件。
ListView listView = (ListView) findViewById(R.id.list_View);
listView.setAdapter(adapter);
lsitView.setOnItemClickListener(new AdapterView.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();
}
});
其实跟Button和TextView等的监听器差不多,过一下就可以了。
ListView的学习就到这里了,初次写博客,有啥错误欢迎批评指出。
主要参考文献:郭霖《第一行代码(第2版)》