第一行代码Android
第三章
最常用和最难用的控件--ListView
文章目录
目录
第一行代码Android
文章目录
简介
一、ListView
1.ListView的简单用法
2.定制ListView的界面
3.提升ListView的运行效率
4.ListView的点击事件
总结
简介
由于手机屏幕的空间比较有限,能够一次在屏幕上显示的内容不多,我们可以借助滚动来显示更多的数据。
一、ListView
1.ListView的简单用法
先创建一个新的活动,然后修改布局里面的代码,指定一个id,宽度和高度都占满布局
<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"
/>
</LinearLayout>
然后修改MianActivity里面的代码
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
public void onCreate(@Nullable 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的,我们还要借助适配器ArrayAdapter,将泛型指定为String,然后在ArrayAdapter的构造函数中依次传递当前的上下文,ListView子项布局的id,和要适配的数据。
注意,我们使用了安卓自己定义的android.R.layout_simple_list_item_1,这是一个安卓内置的布局文件,里面只有一个TextView,可以用于简单的显示一段文本。
2.定制ListView的界面
只有一段文字的界面实在是太单调了,所以我们要自己定制一个界面,可以让他显示自己的图片
我们定义一个实体的类Fruit,作为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;
}
}
name表示水果的名字,imageId表示水果对应图片的资源id。
然后我们给ListView的子项指定一个我们自定义的布局,在layout目录下,新建一个fruit_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="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用于显示水果的名称
接下来我们就需要创建一个自定义的适配器,这个适配器继承ArrayAdapter,并将泛型指定为Fruit类,新建一个FruitAdapter
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int textViewResourceId,
@NonNull List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView,
@NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
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;
}
}
FruitAdapter重写了父类的一组构造函数,用于将上下文中,ListView子项布局的id和数据都传递进来。另外重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。首先通工getItem方法得到当前Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局
LayoutInflater的inflate()方法接受三个参数,前两个参数我们已经知道是什么了,第三个参数指定完成false,表示只让我们在父布局中声明的layout属性失效,但是不会为了这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中。这个就是ListView中的标准写法。
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 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 < 3; i++){
Fruit chutian = new Fruit("雏田",R.drawable.chutian);
fruitList.add(chutian);
Fruit guhe = new Fruit("古河",R.drawable.guhe);
fruitList.add(guhe);
Fruit huangmao = new Fruit("黄毛",R.drawable.huangmao);
fruitList.add(huangmao);
Fruit leimu = new Fruit("蕾姆",R.drawable.leimu);
fruitList.add(leimu);
Fruit mayi = new Fruit("麻衣",R.drawable.mayi);
fruitList.add(mayi);
Fruit nvdi = new Fruit("女帝",R.drawable.nvdi);
fruitList.add(nvdi);
Fruit paojie = new Fruit("炮姐",R.drawable.paojie);
fruitList.add(paojie);
Fruit wang = new Fruit("吾王",R.drawable.wang);
fruitList.add(wang);
Fruit xiaolan = new Fruit("小兰",R.drawable.xiaolan);
fruitList.add(xiaolan);
Fruit xuexiaoban = new Fruit("血小板",R.drawable.xuexiaoban);
fruitList.add(xuexiaoban);
Fruit yasina = new Fruit("亚丝娜",R.drawable.yasina);
fruitList.add(yasina);
}
}
}
新建一个drawable-xhdpi,想要的照片传入其中
3.提升ListView的运行效率
目前我们的ListView的运行效率是很低的,因为在FruitAdapter的grtView()方法,每一次都将布局重新加载了一遍,当ListView快速滚动的时候,就会成为性能的瓶颈。
仔细发现,getView()的方法还有一个convertView参数,这个参数可以将之前的布局进行缓存,以便之后可以重用。
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view ;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}else{
view = convertView;
}
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;
}
可以看到我们在getView()方法中进行了判断,如果converView为null,则使用LayoutInflater去加载布局,如果不为null则converView对进行重用
但是我们的getView()方法还是会调用View的findViewById()方法来获取一次控件的实例,我们可以借助ViewHolder来对这部分性能进行优化
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view ;
ViewHolder viewHolder;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
viewHolder = new ViewHolder();
viewHolder.fruitImage =(ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);
}else{
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder{
ImageView fruitImage;
TextView fruitName;
}
}
这里我们新增加了一个ViewHolder内部类,用于对控件的实例进行缓存。当convertView为null时候,创建一个ViewHolder对象,并将控件的实例都存到ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null时,则调用View的getTag()方法,把ViewHolder重新取出。
4.ListView的点击事件
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 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<?> adapterView, View view, int i, long l) {
Fruit fruit = fruitList.get(i);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_LONG).show();
}
});
}
private void initFruits(){
for(int i = 0; i < 3; i++){
Fruit chutian = new Fruit("雏田",R.drawable.chutian);
fruitList.add(chutian);
Fruit guhe = new Fruit("古河",R.drawable.guhe);
fruitList.add(guhe);
Fruit huangmao = new Fruit("黄毛",R.drawable.huangmao);
fruitList.add(huangmao);
Fruit leimu = new Fruit("蕾姆",R.drawable.leimu);
fruitList.add(leimu);
Fruit mayi = new Fruit("麻衣",R.drawable.mayi);
fruitList.add(mayi);
Fruit nvdi = new Fruit("女帝",R.drawable.nvdi);
fruitList.add(nvdi);
Fruit paojie = new Fruit("炮姐",R.drawable.paojie);
fruitList.add(paojie);
Fruit wang = new Fruit("吾王",R.drawable.wang);
fruitList.add(wang);
Fruit xiaolan = new Fruit("小兰",R.drawable.xiaolan);
fruitList.add(xiaolan);
Fruit xuexiaoban = new Fruit("血小板",R.drawable.xuexiaoban);
fruitList.add(xuexiaoban);
Fruit yasina = new Fruit("亚丝娜",R.drawable.yasina);
fruitList.add(yasina);
}
}
}
我们使用了setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击率ListView中的任何一个子项中的时候,就会回调onItemClick()方法,在这个方法中可以通过position参数判断用户点击的是哪一个子项,然后得到响应的水果,并通过Toast将水果的名字显示出来。
总结
恭喜你,ListView的大部分知识点你已经学完!
参考Android第一行代码(第2版)