android开发——ListView的使用

 ListView是一种如今比较常见的组件,是用来显示多个可滑动项(Item)列表的的ViewGroup。它的优点在于可以使用列表的形式来展示内容,超出屏幕部分的内容只需要通过手指滑动就可以移动到屏幕内了。即使在ListView中加载非常非常多的数据,都不会发生崩溃,而且随着我们手指滑动来浏览更多数据时,程序所占用的内存竟然都不会跟着增长。其他关于ListView的一些基础知识可参考郭神的文章从源码角度分析ListView。在这里我们只通过一些的demo来让大家了解如何使用这么一款强大的原生控件。

 一个ListView的创建需要三个元素:

  • 每一列Item的View
  • 填入View的数据或图片等
  • 连接ListView和数据的适配器Adapter

 我们首先把布局工作完成。ListView的布局其实并不复杂,可以理解为有两层布局。第一是整体布局,第二是每个Item的布局。我们首先创建整体的布局:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.listviewdemo.MainActivity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_view"/>

</LinearLayout>

创建完该xml布局后我们可以发现预览中已经出现了我们想要的ListView的效果:

                                                   

 这也是系统的布局,但目前我们只需显示一行String,所以最终不考虑这样的布局。当然我们自定义这样的布局也不困难。

 那么接下来再为我们的Item创建布局: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"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tx_view"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="1"
        android:textSize="36dp"/>
    
</LinearLayout>
 这里我们先简单的只在Item中添加一个TextView。text设置成什么并没有影响,之后会被绑定的数据所替换。

 这样每一列Item的View就完成了。接着需要把数据写入到Item的TextView中并展现出来。在这之前我们需要一个连接ListView和数据的适配器Adapter,可以先自定义一个适配器MyAdapter:

public class MyAdapter extends ArrayAdapter{

    private int resourceId;
    private ArrayList<String> mData;

    public MyAdapter(Context context, int textViewResourceId, List objects){
        super(context,textViewResourceId,objects);
        resourceId = textViewResourceId;
        mData = (ArrayList<String>)objects;
    }

    public View getView(int position,View convertView,ViewGroup parent){
        View view = LayoutInflater.from(getContext()).inflate(resourceId,null);
        TextView tx = view.findViewById(R.id.tx_view);
        tx.setText(mData.get(position));
        return view;
    }
}

 可能有人不了解View view = LayoutInflater.from(getContext()).inflate(resourceId,null);的作用是啥,那咱们来拆分一下。

        //加载布局管理器
        LayoutInflater inflater = LayoutInflater.from(context);
        //将xml布局转换为view对象
        convertView = inflater.inflate();
        //利用view对象找到布局中的组件
        convertView.findViewById();

 因为在TextView中我只简单的添加字符串,因此我把要接受(适配)的数据规定为String类型。如果你想要实现Item中有很多控件(如按钮、图片、文字等)可以先自定义一个类,然后再规定适配器所要适配的数据类型(下面会有实例)。getView方法我们可以把它认为是把数据绑定View并返回,即之前所说的填入View的数据或图片等。在getView中我们通过每个Item的位置position来确定他所要加载的数据。

 最后我们需要完成MainActivity并写入我们所要呈现的数据,设置ListView与我们的适配器相关联。MainActivity:

public class MainActivity extends Activity {

    private ListView listView;
    private MyAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.list_view);
        mAdapter = new MyAdapter(MainActivity.this,R.layout.item,getData());
        listView.setAdapter(mAdapter);
    }

    private ArrayList<String> getData(){
        ArrayList<String> mData = new ArrayList<>();
        String temp = "item";
        for(int i=1;i<41;i++)
            mData.add(temp + i);
        return mData;
    }
}
 核心代码就是定义适配器并setAdapter。这里MyAdapter()传入的三个数据分别为:相关上下文context、子Item的布局、所要适配的数据。最后效果:(这里由于录屏软件的问题并没有把所有Item都录进去)

                                             

 上面应该能说最简单的ListView实现了吧。接下来稍微增加一点难度,Item里面实现多个组件。那么就需要定义一个类来“容纳它们”,并设置它为适配器类型,同时需要改变Item的布局。

 每个Item中包含学生的照片,学号和姓名,定义类MyClass:

public class MyClass{

    private String name;
    private String id;
    private int imageId;

    public MyClass(String name,String id,int imageId){
        this.id = id;
        this.imageId = imageId;
        this.name = name;
    }

    public String getName(){
        return name;
    }

    public String getId(){
        return id;
    }

    public int getImageId(){
        return imageId;
    }
}
 同时修改我们的布局。此时如果继续采用LinearLayout布局可能会出现嵌套,所以我们采用相对布局RelativeLayout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:adjustViewBounds="true"
        android:padding="2dp" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/image"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:gravity="center_vertical"
        android:layout_marginTop="10dp"
        android:textSize="25dp"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:layout_toRightOf="@id/image"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_below="@id/title"
        android:textSize="40dp"/>

</RelativeLayout>
 这里通过layout_alignParent来设置控件相对于父容器的位置。通过layout_toRightOf和layout_below来确定相互之间的位置关系。这里需要注意如何设置第一个TextView layout_above第二个TextView,最后第一个TextView可能会显示不出来。具体原因目前也不太清楚,按照网上的方法试验下来也不起效果,如果哪位同学知道原因可直接在评论区留言。

 布局完成之后我们就需要自定义一个类来“包含”这些内容。MyClass:

public class MyClass{

    private String name;
    private String id;
    private int imageId;

    public MyClass(String name,String id,int imageId){
        this.id = id;
        this.imageId = imageId;
        this.name = name;
    }

    public String getName(){
        return name;
    }

    public String getStId(){
        return id;
    }

    public int getImageId(){
        return imageId;
    }
}
 比较简单,就定义了三个值。三个返回函数也是为了等会能在getView方法中绑定数据使用。

 接着我们需要改变我们的适配器所适配的数据对象类型,注意接收对象改为MyClass类型。MyAdapter:

public class MyAdapter extends ArrayAdapter<MyClass>{

    private int resourceId;

    public MyAdapter(Context context, int textViewResourceId, List<MyClass> objects){
        super(context,textViewResourceId,objects);
        resourceId = textViewResourceId;
    }

    public View getView(int position,View converView,ViewGroup parent){
        MyClass stu = getItem(position);  //直接通过getItem方法获取当前实例
        View view = LayoutInflater.from(getContext()).inflate(resourceId,null);
        TextView title = view.findViewById(R.id.title);
        TextView text = view.findViewById(R.id.text);
        ImageView image = view.findViewById(R.id.image);
        title.setText(stu.getStId());
        text.setText(stu.getName());
        image.setImageResource(stu.getImageId());
        return view;
    }
}

 最后在MainActivity中同步修改接受数据的类型。MainAcitvity:

public class MainActivity extends Activity {

    private ListView listView;
    private MyAdapter mAdapter;
    private String[] names = {"曹一","孙二","张三","李四","王五","赵六","广七","刘八","夏九","付十"};
    private String[] ids = {"10001","10002","10003","10004","10005","10006","10007","10008","10009","10010"};
    private MyClass[] students = new MyClass[10];
    private List<MyClass> list = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.list_view);
        for (int i=0;i<10;i++){
            students[i] = new MyClass(names[i],ids[i],R.drawable.ic_launcher_background);
            list.add(students[i]);
        }
        mAdapter = new MyAdapter(MainActivity.this,R.layout.item,list);//最后的数据类型发生改变
        listView.setAdapter(mAdapter);
    }
}
 放上最后的效果图(这里偷懒我就直接用了系统自带的背景图啦~):

                                         

 那么稍复杂的ListView我们也就完成啦。同理我们可以继续添加其他如Button等的控件来实现需求。

ListView基本使用相信大家都已掌握,接下来咱们将进一步学习ListView的优化以及它的点击事件处理。

 相信大家都已经注意到了,在适配器的getView中我们每次都需要通过.inflate方法来加载一个布局,即使是已加载过但被划出屏幕的布局也需重新加载,这显然是十分不合理的。细看该方法,发现有个参数我们从来没有使用过,对就是convertView,简单来说他的作用是缓存了ListView中已经加载好的View。这样就可以用它来优化加载布局问题。修改getView方法:

public View getView(int position,View convertView,ViewGroup parent){
        MyClass stu = getItem(position);  
        View view;
        if(convertView==null)   //如果未加载过,则加载。否则直接使用convertView对象
            view = LayoutInflater.from(getContext()).inflate(resourceId,null);
        else
            view = convertView;
        TextView title = view.findViewById(R.id.title);
        TextView text = view.findViewById(R.id.text);
        ImageView image = view.findViewById(R.id.image);
        title.setText(stu.getStId());
        text.setText(stu.getName());
        image.setImageResource(stu.getImageId());
        return view;
    }
 同时我们发现每次我们都需要调用findViewById方法来获取一次控件的代码,这同样也是不合理的。那是否有办法可以对此进行优化呢?答案是有。我们可以新增内部类ViewHolder来缓存控件的实例。convertView为空时,会将控件的实例存放在ViewHolder里,然后用setTag方法将ViewHolder对象存储在view里。convertView不为空时,用getTag方法获取viewHolder对象。我们再次修改getView,并增加内部类ViewHolder:

public View getView(int position,View convertView,ViewGroup parent){
        MyClass stu = getItem(position); 
        View view;
        ViewHolder viewHolder;
        if(convertView==null){
            view = LayoutInflater.from(getContext()).inflate(resourceId,null);
            viewHolder = new ViewHolder();
            viewHolder.image = view.findViewById(R.id.image);
            viewHolder.text = view.findViewById(R.id.text);
            viewHolder.title = view.findViewById(R.id.title);
            view.setTag(viewHolder);
        }
        else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.title.setText(stu.getStId());
        viewHolder.text.setText(stu.getName());
        viewHolder.image.setImageResource(stu.getImageId());
        return view;
    }

    class ViewHolder{
        TextView title;
        TextView text;
        ImageView image;
    }

  • 判断convertView是否为空优化加载布局
  • 设置ViewHolder优化加载控件


 这样ListView的优化工作也就完成了。最后我们来说一下ListView中点击Item的事件处理。和普通的监听事件相类似,我们只需重写onItemClick方法即可,这里我们就直接贴出代码:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                MyClass stud = list.get(position);
                Toast.makeText(MainActivity.this,"你点击了学号为"+stud.getStId()+"的"+stud.getName()+"同学",Toast.LENGTH_SHORT).show();
            }
        });
 最后效果图(最后的鼠标点击gif上看的可能有点问题但是实际操作是OK的):

                                             

 到这里ListView我们算是基本都了解了一遍,之后我们将会开始了解RecyclerView。希望大家都有所收获一起进步。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值