ListView是Android中最常用的控件之一,虽然现在推荐使用RecycleView,但是作为初学者还是有必要了解ListView的基本用法的。
ListView的简单用法
简单地在ListView中单一显示文本还是比较简单的,首先在activity_main.xml中添加
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
在MainActivity.java中准备好String[]数据用于展示,创建String类型的适配器并传入数据data。再调用findViewById(int id)方法获取listView实例,并为这个listView实例设置适配器
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<>(MainActivity.this,android.R.layout.simple_list_item_1,data);
ListView listView=findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
单纯只有文本显示有点乏味,我们可以为ListView自定义View,为每个文本增加图片显示
带图标的ListView
首先创建一个我们自己的类,名为MyItem
package com.example.listviewdemo;
public class MyItem {
private String name;
private int imageId;
public MyItem(String name, int imageId){
this.name=name;
this.imageId=imageId;
}
public String getName(){
return name;
}
public int getImageId(){
return imageId;
}
}
再为每一个item创建独立的layout文件,myitem.xml(不可出现大写),其中ImageView限制了图片的尺寸
<?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="wrap_content">
<ImageView
android:id="@+id/item_image"
android:layout_width="64dp"
android:layout_height="64dp">
</ImageView>
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp">
</TextView>
</LinearLayout>
随后,为我们自定义的类创建一个独特的适配器,重写其中的getView()方法。创建类MyItemAdapter.java
package com.example.listviewdemo;
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 java.util.List;
public class MyItemAdapter extends ArrayAdapter<MyItem> {
private int resourceId;
/**
* 将上下文、ListView子项布局的id和数据传递进来
* @param context 上下文
* @param textViewResourceId 每一个item使用的布局文件id
* @param objects 数据
*/
public MyItemAdapter(Context context, int textViewResourceId, List<MyItem> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
/**
* 每个子项被滚动到屏幕内时会被调用
* @param position item的position
* @param convertView
* @param parent
* @return
*/
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前item实例
MyItem myItem = getItem(position);
//使用LayoutInflater为这个子项加载传入的布局
//第三个参数为false表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局
//因为一旦View有了父布局,它就不能再添加到ListView中了
View view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
//调用View的findViewById方法分别获取到ImageView和TextView的实例
ImageView itemImage = view.findViewById(R.id.item_image);
TextView itemName = view.findViewById(R.id.item_name);
//分别调用方法来设置显示的图片和文字
itemImage.setImageResource(myItem.getImageId());
itemName.setText(myItem.getName());
//最后将布局返回,这样自定义适配器就完成了
return view;
}
}
修改我们的MainActivity.java文件,方法与普通ListView类似
package com.example.listviewdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
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"};
private List<MyItem> myItemList =new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*ArrayAdapter<String> adapter=new ArrayAdapter<>(MainActivity.this,android.R.layout.simple_list_item_1,data);
ListView listView=findViewById(R.id.list_view);
listView.setAdapter(adapter);*/
initItem();
MyItemAdapter myItemAdapter = new MyItemAdapter(MainActivity.this,R.layout.myitem, myItemList);
ListView listView = findViewById(R.id.list_view);
listView.setAdapter(myItemAdapter);
}
private void initItem(){
for (int i = 0; i < 30; i++) {
MyItem item1=new MyItem("item1",R.drawable.icon);
myItemList.add(item1);
MyItem item2=new MyItem("item2",R.drawable.java);
myItemList.add(item2);
}
}
}
效果
由此可见,我们只要修改myitem.xml,就能定制各种复杂的界面
但是呢,这么写还是不够完美,因为getView方法每次都将布局重新加载了一遍
修改我们的getView方法
/**
* 每个子项被滚动到屏幕内时会被调用
* @param position item的position
* @param convertView
* @param parent
* @return
*/
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前item实例
MyItem myItem = getItem(position);
//若convertView不等于null,则直接传入,避免重复获取自定义布局
View view;
if(convertView==null){
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
}else{
view = convertView;
}
//调用View的findViewById方法分别获取到ImageView和TextView的实例
ImageView itemImage = view.findViewById(R.id.item_image);
TextView itemName = view.findViewById(R.id.item_name);
//分别调用方法来设置显示的图片和文字
itemImage.setImageResource(myItem.getImageId());
itemName.setText(myItem.getName());
//最后将布局返回,这样自定义适配器就完成了
return view;
}
如此修改,直接重用了convertView,避免了重复加载布局,大大提高了ListView的运行效率,在快速滚动的时候会体现出更好的性能。但是这段代码仍然可以继续优化,比如在getView方法中仍然会重复findViewById来获取实例,因此我们可以借助一个ViewHolder来进行优化
创建ViewHolder内部类用于存储单个item其自身的ImageView和TextView,代码如下
package com.example.listviewdemo;
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 androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MyItemAdapter extends ArrayAdapter<MyItem> {
private int resourceId;
class ViewHolder{
ImageView itemImage;
TextView itemName;
}
/**
* 将上下文、ListView子项布局的id和数据传递进来
* @param context 上下文
* @param textViewResourceId 每一个item使用的布局文件id
* @param objects 数据
*/
public MyItemAdapter(Context context, int textViewResourceId, List<MyItem> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
/**
* 每个子项被滚动到屏幕内时会被调用
* @param position item的position
* @param convertView
* @param parent
* @return
*/
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前item实例
MyItem myItem = getItem(position);
//若convertView不等于null,则直接传入,避免重复获取自定义布局
View view;
ViewHolder viewHolder;
if(convertView==null){
//第一次加载
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.itemImage=view.findViewById(R.id.item_image);
viewHolder.itemName=view.findViewById(R.id.item_name);
//将viewHolder存储在View中
view.setTag(viewHolder);
}else{
view = convertView;
//重新获取ViewHolder,当中要强转
viewHolder=(ViewHolder) view.getTag();
}
//调用View的findViewById方法分别获取到ImageView和TextView的实例
//分别调用方法来设置显示的图片和文字
viewHolder.itemImage.setImageResource(myItem.getImageId());
viewHolder.itemName.setText(myItem.getName());
//最后将布局返回,这样自定义适配器就完成了
return view;
}
}
经过上述两步的优化之后,ListView滚动 已经变得十分丝滑,运行效率上升不少。
为ListView添加项目点击事件
修改MainActivity.java中的代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initItem();
MyItemAdapter myItemAdapter = new MyItemAdapter(MainActivity.this,R.layout.myitem, myItemList);
final ListView listView = findViewById(R.id.list_view);
listView.setAdapter(myItemAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
MyItem item=myItemList.get(position);
Toast.makeText(MainActivity.this,item.getName(),Toast.LENGTH_SHORT).show();
}
});
}
效果展示
ListView的一些常用属性
分割线
设置颜色必须配合设置高度使用!
部分颜色码参考:https://www.jianshu.com/p/ef2ef0f35cf9
android:dividerHeight="1dp" <!-- 设置分割线高度 或者调用listView.setDividerHeight(0);
android:divider="#00000000" <!--设置分割线颜色 注意,必须要同时设置好分割线高度,否则不显示
android:divider="@drawable/listView_driver" <!--设置分割线的图片资源
android:divider="@drawable/@null" <!--不显示分割线
滚动条
android:scrollbars="none" <!--隐藏listView的滚动条,或listView.setVerticalScrollBarEnabled(true);
android:fadeScrollbars="true" <!--设置为true可以实现滚动条的自动隐藏和显示
快速滚动条
android:fastScrollEnabled="true"
或者调用方法listView.setFastScrollEnabled(true)来控制启用,参数为false则隐藏
背景图设置
android:background="@drawable/background"
缓冲颜色
如果发现ListView在滚动的过程中,item会变黑,则在xml中指定属性
android:cacheColorHint="#00000000" <!-- 指定缓冲颜色为透明色
部分资料参考自《第一行代码》作者:郭霖
由于本人才疏学浅,所以本文仅用于记录及分享相关用法,并将持续更新更多的用法