四、ListView
1、ListView的基本用法
a、在activity_main.xml
布局中加入ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--<!– <include layout="@layout/fist1"/>–>-->
<com.example.myapplication4.Fist1Layout
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
b、修改MainActivity中的代码:
数据是无法直接传递给ListView的,我们还需要借助适配器来完成。Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定。
public class MainActivity extends AppCompatActivity {
//图片名称
private String data[] = {"ariplane","TheEarth","bourn","emitter","cati","SatelliteVehicle",
"warplane","Saturn","Jupiter","Apple","Tomato","Onion","watermelon","sun","Banana"};
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(MainActivity.this,
android.R.layout.simple_list_item_1,data);
//获取一个ListView
ListView listView = findViewById(R.id.listView1);
//显示这个listView
listView.setAdapter(arrayAdapter);
}
}
MainActivity.this
:这里的MainActivity.this
表示当前的MainActivity
实例。在这里,我们将当前的Activity作为上下文传递给适配器,以便适配器在创建列表项视图时可以使用正确的上下文信息。android.R.layout.simple_list_item_1
:这是一个预定义的Android系统布局资源ID,用于显示单行文本的列表项。它是一个非常简单的布局,通常用于显示文本内容的列表项。在这里,我们将使用这个布局来显示data
数组中的每个图片名称。data
:这是我们之前定义的图片名称的字符串数组。它是我们要在列表中显示的数据。
显示结果如下:
2、定制ListView界面
2.1 定义一个实体类Fruit
public class Fruit {
private String name; //图片名
private int imageId; //图片在drawble下的ID
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
2.2 为ListView的子项指定一个我们自定义的布局
<?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_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_weight="1" />
</LinearLayout>
在这个布局中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView
用于显示水果的名称,并让TextView在垂直方向上居中显示。
2.3 创建一个自定义的适配器
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为
Fruit类。新建类FruitAdapter,代码如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private List fruit;
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
fruit = (List) objects;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position); //获取当前项的Fruit实例
FruitItemBinding fruitItemBinding = FruitItemBinding.inflate(LayoutInflater.from(getContext()),parent,false);
fruitItemBinding.fruitImg.setImageResource(fruit.getImageId());
fruitItemBinding.fruitName.setText(fruit.getName());
Log.d("TAG", fruit.getName());
return fruitItemBinding.getRoot();
}
}
FruitAdapter
重写了父类的一组构造函数,用于将上下文、ListView
子项布局的id
和数据都传递进来。另外又重写了getV1ew()
方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()
方法中,首先通过getItem()
方法得到当前项的Fruit
实例,然后使用LayoutInflater
来为这个子项加载我们传入的布局。
这里LayoutInflater的inflate()万法接收3个参数,前两个参数找们已经知道是什么意思了,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中了。
2.3.1 使用Binding的inflate与使用LayoutInflater的inflate有什么区别和关系
FruitItemBinding.inflate(LayoutInflater.from(getContext()), parent, false)
LayoutInflater.from(getContext()).inflate(resourceId, parent,false);
如果有一个名为activity_main.xml
的布局文件,并且它的资源ID是R.layout.activity_main,那么您可以 使用R.layout.activity_main作为resourceId来加载该布局文件。在使用LayoutInflater
加载布局时,您需要将resourceId作为第一个参数传递给inflate()
方法,指示要加载的布局文件。
例如:LayoutInflater.from(getContext()).inflate(R.layout.activity_main, parent, false);
而在使用ViewBinding
加载布局时,您可以直接使用ViewBinding
生成的绑定类的inflate()
方法,而无需显式供资源ID,ViewBinding
会根据布局文件的名称自动关联到相应的资源ID。
例如:FruitItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
resourceId的作用是为了标识和定位应用中的不同资源,以便在代码中可以方便地使用它们。在不同的上下文中,ourceId可能表示布局文件、图片、字符串、颜色等各种资源的唯一标识符。
2.4 在MainActivity中调用
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化数据
initFruits();
FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
//获取一个ListView
ListView listView = findViewById(R.id.listView1);
//显示这个listView
listView.setAdapter(fruitAdapter);
}
//初始化水果数据
private void initFruits(){
for (int i = 0; i < 2; i++) {
Fruit ariplane = new Fruit("0",R.drawable.img);
fruitList.add(ariplane);
Fruit Banana= new Fruit("1",R.drawable.img1);
fruitList.add(Banana);
Fruit TheEarth = new Fruit("2",R.drawable.img2);
fruitList.add(TheEarth);
Fruit bourn= new Fruit("3",R.drawable.img3);
fruitList.add(bourn);
Fruit emitter= new Fruit("4",R.drawable.img4);
fruitList.add(emitter);
Fruit cati= new Fruit("5",R.drawable.img5);
fruitList.add(cati);
Fruit SatelliteVehicle= new Fruit("6",R.drawable.img6);
fruitList.add(SatelliteVehicle);
Fruit warplane= new Fruit("7",R.drawable.img7);
fruitList.add(warplane);
Fruit Saturn= new Fruit("8",R.drawable.img8);
fruitList.add(SatelliteVehicle);
Fruit Jupiter= new Fruit("9",R.drawable.img9);
fruitList.add(Jupiter);
Fruit Apple= new Fruit("10",R.drawable.img10);
fruitList.add(Apple);
Fruit Tomato= new Fruit("11",R.drawable.img11);
fruitList.add(Tomato);
Fruit Onion= new Fruit("12",R.drawable.img12);
fruitList.add(Onion);
Fruit watermelon= new Fruit("13",R.drawable.img13);
fruitList.add(watermelon);
Fruit sun= new Fruit("14",R.drawable.img14);
fruitList.add(sun);
}
}
}
可以看到,这里添加了一个initFruits()方法,用于初始化所有的水果数据。在Fruit类的构造函数中将水果的名字和对应的图片id传人,然后把创建好的对象添加到水果列表中。另外我们使用了一个fo「循环将所有的水果数据添加了两遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕。接着在onCreate()方法中创建了FruitAdapter对象,并将FruitAdapter作为适配器传递给ListView,这样定制ListView界面的任务就完成了。
运行程序结果如下:
3、ListView性能优化
目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
仔细观察会发现,getview()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下所示:
我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null的时候,创建一个ViewHolder对象,并将Binding都存放在ViewHolder里,然后调用convertView的setTag()方法,将ViewHolder对象存储在convertView中。当convertView不为null的时候,则调用convertView的getTag()方法,把ViewHolder重新取出。这样所有Binding都缓存在了ViewHolder里。
public class FruitAdapter extends ArrayAdapter<Fruit> {
private List fruit;
//储存加载过的Binding
private static class ViewHolder{
FruitItemBinding fruitItemBinding;
}
public FruitAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
fruit = (List) objects;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position); //获取当前项的Fruit实例
ViewHolder viewHolder;
如果 convertView 为空,说明是第一次创建该视图
if(convertView == null){
FruitItemBinding binding = FruitItemBinding.inflate(
LayoutInflater.from(getContext()),parent,false);
convertView = binding.getRoot();
viewHolder = new ViewHolder();
viewHolder.fruitItemBinding = binding;
convertView.setTag(viewHolder);
}else {
// 如果 convertView 不为空,说明该视图已存在,直接从 tag 中获取 ViewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.fruitItemBinding.fruitImg.setImageResource(fruit.getImageId());
viewHolder.fruitItemBinding.fruitName.setText(fruit.getName());
Log.d("TAG",fruit.getName());
return convertView;
}
}
可以看到,现在我们在getView()方法中进行了判断,如果convertView为null,则使用LayoutInflater去加载布局,如果不为null则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。
4、ListView的点击事件
4.1 setOnItemClickListener 设置项单击监听
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化数据
initFruits();
FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this
, R.layout.fruit_item, fruitList);
//获取一个ListView
ActivityMainBinding activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
//将 activityMainBinding 表示的根视图设置为 Activity 的内容视图
setContentView(activityMainBinding.getRoot());
//显示这个listView
activityMainBinding.listView1.setAdapter(fruitAdapter);
//设计监听器
activityMainBinding.listView1.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Fruit fruit = fruitList.get(i);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
}
可以看到,我们使用setOnItemclickListener()
方法为activityMainBinding.listView1注册了一个监听器,当用户点击了activityMainBinding.listView1中的任何一个子项时,就会回调onItemclick()
方法。在这个方法中可以通过i
参数判断出用户点击的是哪一个子项,然后获取到相应的水果,并通过Toast
将图片内容的名字显示出来。
好了学到这,这个控件已经入门了,接下来很不幸告诉你,它早在16年就已经被淘汰了。 白学