Android UI控件之ListView&RecyclerView
前言
ListView&RecyclerView 这次我们来说说这两个控件的区别,我们知道ListView在有限的屏幕上显示更多的内容,若不使用优化的方案会导致性能很差,现在出现了RecyclerView滚动控件,同样可以实现listView的功能还可以解决listView存在的问题。
两者虽然功能很强大但是创建起来比起普通的控件多少有些麻烦,而创建之中要属适配器的定制是最麻烦的。适配器:是一个连接数据和AdapterView(ListView就是一个典型的AdapterView)的桥梁,通过它能有效实现数据与AdapterView的分离设置,使AdapterView和数据的绑定简便,修改更加方便。搞定适配器基本就了解这两个控件是怎么玩的了。
ListView
我们先来看ListView的创建过程,ListView的子项中显示什么需要定义一个实体类,作为ListView适配器的适配类型,
如:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
// Getter()
……
}
再创建ListView子项的自定义布局文件。在展示ListView的布局中使用的标签
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
//这个构造函数传入的参数分别是:上下文、ListView子项布局的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) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
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;
}
}
下面将数据借助适配器加载到ListView控件中显示
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
可是这样的适配器是比较难用的,每次快速滚动ListView的时候,getView()中每次都要将布局重新加载一遍,影响性能。每次都要重新加载布局,想想都觉得会拖慢速度,怎么办?所以把getView()中的代码进行一次小的修改。如下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
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;
}
没错就是加了一个条件判断的语句,判断convertView是否为null,为null就用LayoutInflater重新加载布局,否则直接重用。这样是解决了布局的重复加载的问题,可是控件还是要重新调用,所以还要进一步优化,这时候就要用到ViewHolder。代码如下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById (R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById (R.id.fruit_name);
view.setTag(viewHolder); // 将ViewHolder存储在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
上一次的优化是加入了convertView且进行判断,是对子项布局缓存并重用,这个是在自定义适配器类中定义了ViewHolder类并声明了子项中得控件。在填充了子项布局后实例化控件,view.setTag(viewHolder)将ViewHolder缓存在View中。 view.getTag()将ViewHolder重新取出,经过这两次优化会很好的提高性能,其中我们也叫能看清ListView实现的过程。
RecyclerView
接下来我们继续来介绍RecyclerView滚动控件,RecyclerView定义在support库中所以需要在build.gradle中添加依赖库
dependencies{
.....
compile 'com.android.support:recyclerview-v7:24.2.1'
}
布局中添加RecyclerView控件
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
RecyclerView控件很强大,不但能实现listView的功能,在滚动方向上可以横向滑动。
下面来介绍RecyclerView控件适配器类的创建,以便和ListView做个对比,这个仍然要定义一个实体类作为适配器的适配类型。
代码如下:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);//recyclerView这里就添加子项布局,而listView在真正适配的时候用到子项布局
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
我们看到这个适配器继承了RecyclerView.Adapter,并指定FruitAdapter.ViewHolder泛型类型,这里也写一个内部类ViewHolder,和ListView适配器中很像,但这个除了声明了控件,还获得了控件的实例,这样控件直接封装到这个ViewHolder里。
这个内部类往下,有一个构造函数用来传入数据。剩下的是重写的三个方法:onCreateViewHolder()、onBindViewHolder()和getItemCount()。
- onCreateViewHolder():中用来创建ViewHolder实例和加载子项布局
- onBindViewHolder():通过获得子项的位置position,对RecyclerView每个子项进行赋值添加数据。
- getItemCount():返回子项的长度
看一下如何使用这个适配器:
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManger(this);
//对线性布局设置排列的方向,默认是纵向,这个是设置成了横向排列
//layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
这里我们可以看到RecyclerView除了绑定控件获得实例,通过适配器添加数据,还加入了对RecyclerView滚动控件布局形式的排列,不得不说这要比ListView在展现形式要灵活很多。
此外在事件触发的设计上两者也存在着差异,结果还是RecyclerView要好一些。