最常用和最难用的控件----ListView
ListView绝对是称得上Android中最常用的控件之一。几乎所有应用程序都会用到它。
ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
不过比起先前学的几种控件,ListView的用法也相对复杂了很多。
新建项目ListViewTest
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
一个ListView通常有两个职责。
(1)将数据填充到布局。
(2)处理用户的选择点击等操作。
一个ListView的创建需要三个元素
(1)ListView中的每一列的View
(2)填入View的数据或图片等。
(3)连接数据与ListView的适配器。
也就是说,要使用ListView,首先要了解什么是适配器。
适配器是一个连接数据和AdapterView(ListView就是一个典型的AdapterView)
的桥梁,通过它能有效地实现数据与AdaterView的分离设置,使AndroidView与数据的绑定更加简便,修改更加方便。
Android中提供了很多的Adapter
Adapter 含义
ArrayAdapter 用来绑定一个数组,支持泛型操作
SimpleAdapter 用来绑定在xml中定义的控件对应的数据
SimpleCursorAdapter 用来绑定游标得到的数据
BaseAdapter 通用的基础适配器
其实适配器还有很多,要注意的是,各种Adapter只不过是转换的方式和能力不一样而已。
package net.nyist.lenovo.listviewtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends AppCompatActivity {
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>(
MainActivity.this,android.R.layout.simple_list_item_1,data);
ListView listView= (ListView)findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
既然ListView是用于展示大量数据的,那我们就应该先将数据提供好。这些数据可以是从网上下载的,也可以是从数据库中读取的,应该视具体的应用程序场景而定。这里我就使用了一个简单的data数组来测试,里面包含了许多水果的名称。
不过,数组中的数据时无法直接传递给ListView的,我们还需要借助适配器来完成。
ArrayAdpter有多个构造函数的重载,应该根据实际情况选择合适的一种。
由于我们提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造函数中一次传入当前上下文、LitView子项布局的id,以及要适配的数据。注意,我们使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。
最后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0I9U22qy-1587024879884)(http://i.imgur.com/xBKRLfV.png)]
#定制ListView的界面
只能显示一段文本的ListView实在是太单调了,我们现在就来对ListView的界面进行定制,让它可以显示更加丰富的内容。
首先需要准备好一组图片。
接着定义一个实体类,作为ListView适配器的适配类型。新建Fruit,代码如下:
package net.nyist.lenovo.listviewtest;
public class Fruit {
private String name;
private int imageId;
public Fruit(String name ,int imageId){
this.name = name;
this.imageId = imageId;
}
public int getImageId() {
return imageId;
}
public String getName() {
return name;
}
}
Fruit类中只有两个字段,name表示水果的名字,imageId表示水果对应图片的资源id。
然后需要为ListView的子项指定一个我们自定义的布局,在layout目录下新建fruit_item.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">
<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_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
在这个布局中,我们定义了一个ImageView用于显示水果的图片,有定义了一个TextView用于显示水果的名称,并让TextView在垂直方向上居中显示。
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,代码如下所示:
package net.nyist.lenovo.listviewtest;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
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 java.util.List;
public class FruitAdapter extends ArrayAdapter<Fruit>{
private int resourceId;
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;
}
}
// 加载布局管理器,将xml布局转换为view对象
View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
//利用view对象,找到布局中的组件
ImageView fruitImage = (ImageView)view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView)view.findViewById(R.id.fruit_name);
FruitAdapter
重写了父类的一组构造函数,用于将上下文,ListView子项布局的id和数据都传递进来。
另外又重写了getView()方法这个方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。
这里LayoutInflater的inflate()方法接收三个参数,前两个参数我们已经知道是什么意思了,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布局之后,他就不能再添加到ListView中了。
接下来调用View的findViewById()方法分别获取到ImageView和TextView的实例。
并分别调用它们的setImageResource()和setText()方法来设置显示的图片和文字,,最后将布局返回,这样定义的适配器就完成了。
下面修改MainActivity中的代码,如下所示:
package net.nyist.lenovo.listviewtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Fruit>fruitList = new ArrayList<>();
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);
initFruits();//初始化水果数据
FruitAdapter adapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView 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_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana",R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange",R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon",R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear",R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape",R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple",R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry",R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry",R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango",R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
这里添加一个initFruits()方法,用于初始化所有的水果数据。在Fruit类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中,另外我们使用了一个for循环将所有的水果数据添加了两遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕,接着在onCreate()方法中创建了FruitAdpter对象,并将FruitAdapter作为适配器传递给ListView,这样定制ListView界面的任务就做好了。
#ListView的点击事件
话说回来,ListView的滚动毕竟只是满足了我们视觉上的效果,可是如果ListView中的子项不能点击的话,这个控件就没有什么实际的用途了。因此,本小节我们就来学习一下ListView如何才能响应用户的点击事件。
修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private List<Fruit>fruitList = new ArrayList<>();
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);
initFruits();//初始化水果数据
FruitAdapter adapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView listView= (ListView)findViewById(R.id.list_view);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
}
可以看到,我们使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法。在这个方法中可以通过position参数判断出用户点击的是哪一个子项,然后获取到响应的水果,并通过Toast将水果的名字显示出来。