ListView是Android系统提供的一种列表显示控件。RecyclerView是ListView的升级版,它在展现形式上更加灵活,缓存机制的不同也使得它效率更高。
ListView的使用
以ListView的形式展现内容主要会涉及到四个部分:ListView、Adapter和item布局和数据。ListView只负责显示内容,item布局决定了数据的展现形式,Adapter作为一个中间桥梁将item布局和数据绑定并最终交给ListView展示。
ListView展示简单的水果图片及名称为例。
主界面布局只包含一个ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/fruit_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
数据模型Fruit:里面只包含两个属性,FruitName代表水果名字,ImageId代表水果图片。
public class Fruit {
private String FruitName;
private int ImageId;
public Fruit(String fruitName, int imageId) {
FruitName = fruitName;
ImageId = imageId;
}
public String getFruitName() {
return FruitName;
}
public int getImageId() {
return ImageId;
}
}
item布局layout
<?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">
<ImageView
android:id="@+id/fruit_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/fruit_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Adapter内容,代码里有详细的注释。Adapter继承了系统提供的ArrayAdapter,构造方法要获取item的布局layout。Adapter的核心方法就是getView(),在这个方法中首先将Item的布局layout转换成了View,然后获取了View的控件,最后将数据和控件绑定,再将View返回给ListView。这个方法中可以优化提升效率,我们看到每次ListView要生成一个Item时都得生成一个新的View,但是在这里所有的View样式是一样的,那么当ListView要生成新的Item的View时,能不能利用已经滑出界面的Item的View呢?答案是可以的,而且可以借助ViewHolder将已经获取到的View中的控件保存起来,避免每次都获取控件,具体的实现在后面会写出来。
public class Adapter extends ArrayAdapter<Fruit> {
int ResId;
public Adapter(@NonNull Context context, int resource, @NonNull List objects) {
super(context, resource, objects);
//item的布局layoutId(R.layout.)
ResId = resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//将item的布局layout转化为View
View view = LayoutInflater.from(parent.getContext()).inflate(ResId,parent,false);
//获取item布局中的ImageView和TextView
ImageView FruitImage = view.findViewById(R.id.fruit_item_image);
TextView FruitName = view.findViewById(R.id.fruit_item_name);
//获取对应位置的数据对象
Fruit fruit = (Fruit) getItem(position);
//布局绑定数据
FruitImage.setImageResource(fruit.getImageId());
FruitName.setText(fruit.getFruitName());
return view;
}
}
MainActivity的代码
public class MainActivity extends AppCompatActivity {
private List<Fruit> FruitList = new ArrayList<>();
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化测试数据
initData();
//绑定ListView控件
listView = findViewById(R.id.fruit_listview);
//生成我们前面写的适配器
Adapter adapter = new Adapter(MainActivity.this,R.layout.fruit_list_item,FruitList);
//ListView绑定适配器
listView.setAdapter(adapter);
}
//初始化数据
//这里水果的名称和图片并不是对应的,只是随便找了几张图片来测试
//但是我们可以看到测试数据由三组重复数据构成,一组数据有6个数据模型对象
//只要三组的图片顺序和名称对应上就没问题
private void initData(){
for (int i = 0; i < 3 ; i++){
Fruit a = new Fruit("Apple"+i,R.drawable.a);
FruitList.add(a);
Fruit b = new Fruit("Banana"+i,R.drawable.b);
FruitList.add(b);
Fruit c = new Fruit("Orange"+i,R.drawable.c);
FruitList.add(c);
Fruit d = new Fruit("Demon"+i,R.drawable.d);
FruitList.add(d);
Fruit e = new Fruit("Lemon"+i,R.drawable.e);
FruitList.add(e);
Fruit f = new Fruit("Watermon"+i,R.drawable.f);
FruitList.add(f);
}
}
}
最后效果如下图所示
接下来是对之前提到过的Adapter的优化,具体代码如下。
public class Adapter extends ArrayAdapter<Fruit> {
int ResId;
public Adapter(@NonNull Context context, int resource, @NonNull List objects) {
super(context, resource, objects);
//item的布局layoutId(R.layout.)
ResId = resource;
}
class ViewHolder {
ImageView FruitImage;
TextView FruitName;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder holder;
//先判断convertView是否为空
if (convertView == null){
//将item的布局layout转化为View
view = LayoutInflater.from(parent.getContext()).inflate(ResId,parent,false);
holder = new ViewHolder();
holder.FruitImage = view.findViewById(R.id.fruit_item_image);
holder.FruitName = view.findViewById(R.id.fruit_item_name);
view.setTag(holder);
}else {
view = convertView;
holder = (ViewHolder) view.getTag();
}
holder.FruitImage.setImageResource(fruit.getImageId());
holder.FruitName.setText(fruit.getFruitName());
return view;
}
}
ListView的Item的点击事件有OnItemClickListener,简单贴一下代码
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
//在这里面实现具体操作
}
});
我们可以看到ListView的使用并不复杂,在不优化的情况下就更加简单了。但是它只能以列表的形式展示内容,而且不适合用于多种类型item的展示,效率较低,在最后会统一对比ListView和RecyclerView的缓存机制。
RecyclerView的Demo展示和ListView同样的内容。接下来开始看代码。
同样的配方,同样的主界面布局(只是ListView换成RecyclerView)、item布局和展示内容。
主界面布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fruit_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
item布局:
<?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="wrap_content"
android:orientation="vertical"
android:layout_margin="5dp">
<ImageView
android:id="@+id/fruit_item_image"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_gravity="left"
android:id="@+id/fruit_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Fruit数据模型:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
Adapter中的内容不一样了:
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
private List<Fruit> fruitList;
public Adapter(List<Fruit> dataList) {
fruitList = dataList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_list_item,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.fruitName.setText(fruit.getName());
holder.fruitImage.setImageResource(fruit.getImageId());
}
@Override
public int getItemCount() {
return fruitList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_item_image);
fruitName = itemView.findViewById(R.id.fruit_item_name);
}
}
}
MainActivity代码:
public class MainActivity extends AppCompatActivity {
private List<Fruit> dataList = new ArrayList<>();
RecyclerView recycler;
LinearLayoutManager linearManager;
StaggeredGridLayoutManager staggerManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
recycler = findViewById(R.id.fruit_recycler);
linearManager = new LinearLayoutManager(this);
staggerManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
Adapter adapter = new Adapter(dataList);
recycler.setLayoutManager(linearManager);
recycler.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_add){
recycler.setLayoutManager(staggerManager);
return true;
}
if (item.getItemId() == R.id.action_settings){
recycler.setLayoutManager(linearManager);
return true;
}
return super.onOptionsItemSelected(item);
}
private void initData(){
for (int i = 0; i < 3 ; i++){
//用于瀑布流展示
Fruit a = new Fruit(getRandomLengthName("Apple"),R.drawable.a);
dataList.add(a);
Fruit b = new Fruit(getRandomLengthName("Banana"),R.drawable.b);
dataList.add(b);
Fruit c = new Fruit(getRandomLengthName("Orange"),R.drawable.c);
dataList.add(c);
Fruit d = new Fruit(getRandomLengthName("Demon"),R.drawable.d);
dataList.add(d);
Fruit e = new Fruit(getRandomLengthName("Lemon"),R.drawable.e);
dataList.add(e);
Fruit f = new Fruit(getRandomLengthName("Watermon"),R.drawable.f);
dataList.add(f);
}
}
//将水果名字的长度随机变长,使瀑布流效果更明显一些
private String getRandomLengthName(String name){
Random random = new Random();
int length = random.nextInt(10)+1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++){
builder.append(name);
}
return builder.toString();
}
}
这里我们展示两种效果,分别是列表形式和瀑布流形式,效果如下:
从基础使用上看,我们可以明显看出,RecyclerView相比ListView的区别如下:
- ViewHolder的编写更规范了
- RecyclerView复用Item的工作由它自身实现,不再需要像ListView那样自己setTag
- RecyclerView需要多做一步工作,设置LayoutManager。正是多了一个LayoutManager,RecyclerView才能更加灵活的变换展现形式,可以以列表、网格和瀑布流的形式进行展示。