ListView
- 在讲ListView的使用之前,需要了解Adapter适配器,也顺便讲一下MVC的原理以及Adapter在其中的作用
MVC
- 图解
View:用户操作接口,GUI,可以想象成用户看到的界面
Model:事务逻辑层,负责执行程序的核心运算和逻辑判断,通过view来获取用户输入的数据,然后根据数据库查询相关操作,将结果返回给view展示
Controller:控制器,view与model之间的枢纽,通过控制程序的执行流程来进行view和model的交互
Adapter
- 在android中就是充当controller的作用
- 连接view和model的桥梁
- 图解
- 表格说明
BaseAdapter:抽象类,实际开发中我们一般继承该类并重写相关的方法
ArrayAdapter:支持泛型操作,只能展示一行文字
SimpleAdapter:具有良好扩展性的简单Adapter,可以自定义多种效果
SimpleCursorAdapter:一般在数据库会用到,用来显示简单文本类型的listView(textview或imageView等)
简单的ListView实现
<ListView
android:id="@+id/list_view"
android:layout_width="409dp"
android:layout_height="729dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
private String[] data = {"Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango","Apple","Banana",
"Orange","Watermelon","Pear", "Grape","Pineapple","Strawberry","Cherry","Mango"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
ListView listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
ArrayAdapter有多个构造函数的重载,需要三个参数(当前上下文/ListView子项布局的id/要适配的数据)
android.R.layout.simple_list_item_1是一个Android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本
就相当于ListView每一行就是一个TextView
实例(定制ListView界面)
- 定义一个实体类,作为ListView适配器的适配类型
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;
}
}
- 为ListView的子项定义一个布局fruit_item.xml
<ImageView
android:id="@+id/image_fruit"
android:layout_width="64dp"
android:layout_height="45dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="@string/image_fruit_description" />
<TextView
android:id="@+id/text_fruit_name"
android:layout_width="348dp"
android:layout_height="45dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/image_fruit"
app:layout_constraintTop_toTopOf="parent" />
- 创建一个适配器FruitAdapter,继承自ArrayAdapter
重写了父类的一组构造函数,用于将上下文、ListView的子项布局id和数据都传递进来
重写了getView()方法,每个子项被滚动到屏幕内的时候会被调用
在此方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为子项加载我们传入的布局
LayoutInflater的inflate()有三个参数,分别是子项布局id,父布局,false(表示只让我们在父布局中声明的layout属性生效,但不会为这个view添加父布局。因为一旦view有了父布局,就不能被添加进ListView中了)
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int resourceId, @NonNull List<Fruit> objects) {
super(context, resourceId, objects);
this.resourceId = resourceId;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前项的fruit实例
Fruit fruit = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
ImageView imageView = view.findViewById(R.id.image_fruit);
TextView textView = view.findViewById(R.id.text_fruit_name);
imageView.setImageResource(fruit.getImageId());
textView.setText(fruit.getName());
return view;
}
}
- 修改MainActivity
initFruits()用于初始化所有的水果数据
在onCreate()里创建FruitAdapter对象,并将FruitAdapter作为适配器传递给listView
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
FruitAdapter adapter = new FruitAdapter(this,R.layout.fruit_item,fruitList);
ListView listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruits(){
for (int i = 0; i < 2; i++){
Fruit apple = new Fruit("Apple",R.drawable.apple);
fruitList.add(apple);
Fruit banana = new Fruit("Banana",R.drawable.banana);
fruitList.add(banana);
Fruit orange = new Fruit("Orange",R.drawable.orange);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon",R.drawable.watermelon);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear",R.drawable.pear);
fruitList.add(pear);
Fruit grape = new Fruit("Grape",R.drawable.grape);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple",R.drawable.pineapple);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry",R.drawable.strawberry);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry",R.drawable.cherry);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango",R.drawable.mango);
fruitList.add(mango);
}
}
- 提升ListView运行效率
在FruitAdapter里,getView()方法每次都将布局重新加载,当List View快速滚动时,会成为性能的瓶颈
convertView参数用于将之前加载好的布局进行缓存,以便之后可以进行重用
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前项的fruit实例
Fruit fruit = getItem(position);
View view;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}else {
view = convertView;
}
ImageView imageView = view.findViewById(R.id.image_fruit);
TextView textView = view.findViewById(R.id.text_fruit_name);
imageView.setImageResource(fruit.getImageId());
textView.setText(fruit.getName());
return view;
}
- 继续更好的优化
前一个虽然不会重复去加载布局,但是每次还是会重复调用View的findViewById()方法来获取一次控件的实例
我们可以借助ViewHolder(内部类)来对此进行优化
ViewHolder用于对控件的实例进行缓存
view的setTag方法,将viewHolder对象存储在view中
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前项的fruit实例
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
viewHolder = new ViewHolder();
viewHolder.imageView = view.findViewById(R.id.image_fruit);
viewHolder.textView = view.findViewById(R.id.text_fruit_name);
//将viewHolder储存在View中
view.setTag(viewHolder);
}else {
view = convertView;
//重新获取viewHolder
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.imageView.setImageResource(fruit.getImageId());
viewHolder.textView.setText(fruit.getName());
return view;
}
class ViewHolder{
ImageView imageView;
TextView textView;
}
- 点击事件
使用setOnItemClickListener给listView注册了一个监听器,当用户点击任何一个子项时,都会回调onItemClick()方法,可以通过position参数判断用户点击的哪一个子项
/**
* listView点击事件
* */
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
FruitAdapter adapter = new FruitAdapter(this,R.layout.fruit_item,fruitList);
ListView listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
}
- 源码地址
DoSomeAndroidTest/ListViewTest at main · qricis/DoSomeAndroidTest · GitHub