ListView使用的步骤
在什么情况下会需要使用 ListView 组件呢? 当有很多项元素,而且每一项元素的布局都是相同的情况下就可以使用 ListView 组件了, ListView = List + View
ListView使用示例
接下来先看看一个 ListView 的使用示例,然后再看看其中关键的元素,最终的显示效果如下:
先看看这个示例涉及到类和布局文件有那些,项目的整体结构如下所示:
makefile
E:.
│ AndroidManifest.xml
├─java
│ └─com
│ └─example
│ └─ademo
│ │ MainActivity.java
│ │
│ ├─adapter
│ │ FruitAdapter.java
│ │
│ └─vo
│ EachItem.java
│
└─res
├─drawable
│ ic_launcher_background.xml
│ ic_launcher_foreground.xml
├─layout
│ activity_main.xml
│ fruit_item.xml
- MainActivity 是项目的主 Activity
- EachItem 是每一行需要展示的数据内容
- FruitAdapter 是继承了 ArrayAdapter 的类,使用 ListView 的时候需要使用 Adapter
- activity_main.xml 是主布局文件
- fruit_item.xml 是每一行的布局形式
- AndroidManifest.xml 是 android 项目的主配置文件
AndroidManifest.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ADemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
EachItem
EachItem 就是存储的每一项的数据
java
package com.example.ademo.vo;
public class EachItem {
private String name;
// imageId 就是存储的图片的id
private int imageId;
public EachItem(String name, int imageId)
{
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
fruit_item.xml
- fruit_item.xml 就是每一项数据最终需要展示的样子
- 也就是最终是要将每一个 EachItem 类型的数据映射到 fruit_item 这个布局上
- fruit_item 布局是 LinearLayout 类型,左侧是一个 ImageView, 右侧是一个 TextView
- 最终呈现的每一行的效果就是左侧是图片,右侧是文字
xml
<?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/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_gravity="center_vertical"/>
</LinearLayout>
FruitAdapter
这个 Adapter 的作用是什么呢? 可以先看看 FruitAdapter 的继承体系
下面是 FruitAdapter 类的内容,后面会解释这个类的作用
java
package com.example.ademo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.ademo.R;
import com.example.ademo.vo.EachItem;
import java.util.List;
public class FruitAdapter extends ArrayAdapter<EachItem> {
private int resourceId;
// 这里的 textViewResourceId 在该例子中就是 fruit_item.xml
public FruitAdapter(@NonNull Context context, int textViewResourceId, List<EachItem> eachItems) {
super(context, textViewResourceId, eachItems);
resourceId = textViewResourceId;
}
@NonNull
@Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
EachItem eachItem = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
ImageView imageView = view.findViewById(R.id.fruit_image);
TextView textView = view.findViewById(R.id.fruit_name);
imageView.setImageResource(eachItem.getImageId());
textView.setText(eachItem.getName());
return view;
}
}
FruitAdapter 顶层父类是 BaseAdapter, 其实前面也说过,ListView = List + View, ListView 只负责将数据一行行的按照要求进行展示,但是具体到每一行的数据应该是要展示成什么样子呢?这个就不是 ListView 来负责的,上面已经说过每一行数据最终是通过 fruit_item.xml 这个布局文件来呈现的,那么现在的问题就是怎么将 ListView 和 fruit_item 进行关联的? 其实就是通过 Adapter 来进行关联,所以 Adapter 做的事情就是将我们一行行的数据(比如这个例子中就是 EachItem 结构的数据) 和每一行展示的视图关联起来(这个视图就是 fruit_item.xml 文件)
activity_listview.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/list_view"/>
</LinearLayout>
MainActivity
java
package com.example.ademo;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.ademo.adapter.FruitAdapter;
import com.example.ademo.vo.EachItem;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<EachItem> eachItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity", "开始创建主Activity" + this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview);
initItems();
// adapter 做的第一件事情就是关联 fruit_item
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, eachItems);
ListView listView = findViewById(R.id.list_view);
// adapter 做的第二件事情就是和 ListView 进行关联
listView.setAdapter(adapter);
}
private void initItems() {
for (int i = 0; i < 20; i++) {
EachItem eachItem = new EachItem("苹果" + i, R.drawable.ic_launcher_background);
eachItems.add(eachItem);
}
}
}
在 MainActivity 中也看到了 Adapter 做的事情其实就是桥梁,前面说过 ListView = List + View, Adapter 就是承担这个 "+" 的作用,将每一项的布局(View) 设置给 ListView 组件
ListView性能优化
性能瓶颈在 Adapter 的 getView 方法中,接下来看看这个方法
java
public class FruitAdapter extends ArrayAdapter<EachItem> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int textViewResourceId, List<EachItem> eachItems) {
super(context, textViewResourceId, eachItems);
resourceId = textViewResourceId;
}
@NonNull
@Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
EachItem eachItem = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
ImageView imageView = view.findViewById(R.id.fruit_image);
TextView textView = view.findViewById(R.id.fruit_name);
imageView.setImageResource(eachItem.getImageId());
textView.setText(eachItem.getName());
return view;
}
}
getView 方法是在滑动页面的时候来加载数据的,即使前面的数据已经加载了,当将屏幕往上滑动的时候还是会调用这个方法,这个方法会通过 View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
这行代码来加载单个布局文件,当数据比较多的时候,对于已经加载的数据重复加载可能会存在瓶颈,所以可以通过 convertView 这个参数来优化,这个参数是将之前已经加载好的布局进行缓存,因此可以进行如下方式修改:
java
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
EachItem eachItem = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
ImageView imageView = view.findViewById(R.id.fruit_image);
TextView textView = view.findViewById(R.id.fruit_name);
imageView.setImageResource(eachItem.getImageId());
textView.setText(eachItem.getName());
return view;
}
ListView数据变化后更新视图
在 BaseAdapter 中有如下方法
java
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
}
所以当需要展示的数据变化后,只需要调用 Adapter 的 notifyDataSetChanged 方法即可