Skip to content

Latest commit

 

History

History
387 lines (318 loc) · 13.9 KB

3.5.md

File metadata and controls

387 lines (318 loc) · 13.9 KB

ListView与BaseAdapter

ListView介绍

listView是安卓开发中非常重要的一个控件,比如说打开微信,对话的列表,联系人的列表,以及打开朋友圈,新鲜事儿的列表,以及新鲜事儿下的评论,都是ListView。可以说无处不在了。

接下来,我们开始做一个简单的应用来学习ListView的使用,目标是用ListView来显示数字1到20。

我们摆脱掉前面的HelloActivity的工程,新建一个工程叫做StudyListView。在activity_main.xml中声明一个listview,像Button和TextView那样设置id,并用findViewById,把listview取出来。

activity_main.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">

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

MainActivity.java:

package com.example.kalen.studylistview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {

    ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
    }
}

我们查看activity_main.xml的预览,可以看到ListView控件就像一个列表一样。


这里有一个个的Item,这些个item都是要我们自己定义的,也是通过xml来配置。比如说我们可以弄成左边一个图片当头像,右边是昵称和最新的聊天信息,这就是微信聊天列表的布局了。不过这里我们的布局弄得比较简单,只用一个TextView就好了。

下面再创建一个item_listview.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="wrap_content"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/tv_item_lv"
        android:textSize="20sp"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>

</LinearLayout>

这里注意把最外层的LinearLayout的高度设置成wrap_content。

接下来,我们再定义一个Bean,用来和Item生成对应,起名叫做ItemBean.java。里面放一个text属性就好了,并生成它的构造方法和setter以及getter。如下:

package com.example.kalen.studylistview;

public class ItemBean {
	private String text;

	public ItemBean(String text) {
		this.text = text;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

}

然后,我们再在MainActivity生成一些使用一个ArrayList,当做数据源,用来放到ListView当中。

package com.example.kalen.studylistview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    ListView listView;
    List<ItemBean> datas;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
        datas = new ArrayList<ItemBean>();
        for (int i = 1; i <= 20; i++) {
            datas.add(new ItemBean(i + ""));
        }
    }
}

到这里,ListView已经创建好了,item的样子还有数据都添加好了,那怎么把我们的数据抽象到ListView当中呢?

我们现在有的是一个ItemBean的List,所以需要一个适配器来转换到屏幕上的每一个Item。如果不理解编程中适配器的概念可以同现实生活中对比一下,比如说现在有的是220V的电,但是手机需要5V的电,这个时候就要用到电源插头,也就是一个适配器。

在使用ListView中,BaseAdapter就是把我们的数据和item的xml配置文件对应到一起的一个适配器。

BaseAdapter使用

下面,我们先创建一个类,我这里起名叫做ItemAdapter,然后继承自BaseAdapter,并实现必须实现的方法后,如下:

package com.example.kalen.studylistview;

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;


public class ItemAdapter extends BaseAdapter{
    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }
}

这里一共有几个方法需要我们实现,看下API文档对这几个API的解释。

public int getCount()

How many items are in the data set represented by this Adapter.

public Object getItem(int position)

Get the data item associated with the specified position in the data set.

public long getItemId(int position)

Get the row id associated with the specified position in the list.

public View getView(int position, View convertView, ViewGroup parent)

Get a View that displays the data at the specified position in the data set.

第一个getCount方法就是有多少行数据,第二个getItem就是该行的数据返回过去。第三个方法没有特殊需求直接返回position就好了。

最主要的就是最后一个方法,需要返回一个view对象。我们需要把item_lv.xml渲染出来,这里需要使用一个叫做layoutInflater对象,而layoutInflater需要Context对象来创建,Context是Activity的基类,我们只要把Activity传过去就好了,当然还要传数据源过去。

接下来,我们来完善一下ItemAdapter这个类。

package com.example.kalen.studylistview;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;

public class ItemAdapter extends BaseAdapter{

    private List<ItemBean> datas;
    private LayoutInflater inflater;

    public ItemAdapter(List<ItemBean> datas, Context context) {
        super();
        this.datas = datas;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return datas.size();
    }

    @Override
    public Object getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //渲染item_lv.xml到view中
        convertView = inflater.inflate(R.layout.item_listview, null);
        //拿到convertView的TextView
        TextView tv = (TextView) convertView.findViewById(R.id.tv_item_lv);
        //取得该位置的bean对象
        ItemBean bean = datas.get(position);
        //为TextView设置Text
        tv.setText(bean.getText());
        return convertView;
    }
}

然后,在MainActivity中声明、创建ItemAdapter对象,通过ListView的setAdapter方法来设置。

package com.example.kalen.studylistview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    ListView listView;
    List<ItemBean> datas;
    ItemAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
        datas = new ArrayList<ItemBean>();
        for (int i = 1; i <= 20; i++) {
            datas.add(new ItemBean(i + ""));
        }
        adapter = new ItemAdapter(datas, MainActivity.this);
        listView.setAdapter(adapter);
    }
}

这个时候,运行一下,就能看到1到20个数了。

提升BaseAdapter的性能1

安卓系统在使用ListView的时候,会为所有在屏幕中显示的Item调用BaseAdapter的getView方法来获取View,当我们滑动ListView时,会把创建过的View(convertView对象)缓存起来,但是还要调用getView方法来获取View。我们在getView方法中添加一个System.out.println,打印一下信息。

public View getView(int position, View convertView, ViewGroup parent) {
    System.out.println("inflate-->" + position);
    //渲染item_lv.xml到view中
    convertView = inflater.inflate(R.layout.item_listview, null);
    //拿到convertView的TextView
    TextView tv = (TextView) convertView.findViewById(R.id.tv_item_lv);
    //取得该位置的bean对象
    ItemBean bean = datas.get(position);
    //为TextView设置Text
    tv.setText(bean.getText());
    return convertView;
}

重新运行程序,我这里屏幕显示了15个元素,可以看到getView方法被调用了15次(0-14)。(注:如果屏幕一下把20个全显示出来的话,可以把item_listview.xml中TextView的高度增大点儿。)


然后把listView往下拖动,再拖回来。


通过日志的输出,我们发现,还会调用getView方法,也就是说,还会重新读取xml,把view重新渲染一遍,我们根本没用到安卓系统给我们的缓存机制。所以既浪费了CPU来渲染界面,又浪费了内存,还平添了性能负担。

改进这一方法通过判断convertView是否为空就可以实现。修改getView方法后,代码如下:

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        System.out.println("inflate-->" + position);
        //渲染item_lv.xml到view中
        convertView = inflater.inflate(R.layout.item_listview, null);
    }
    //拿到convertView的TextView
    TextView tv = (TextView) convertView.findViewById(R.id.tv_item_lv);
    //取得该位置的bean对象
    ItemBean bean = datas.get(position);
    //为TextView设置Text
    tv.setText(bean.getText());

    return convertView;
}

此时再运行程序,来回拖动listView,我们发现,不会再重复出现inflate的现象了。


提升BaseAdapter的性能2

除了inflate是一个耗时的操作之外,findViewById也是一个费时的操作。Google官方推荐创建一个ViewHolder的类来保存已经findViewById过的TextView,然后把ViewHolder设置到convertView的tag属性上。读者看一下完整ItemAdapter的代码,自行理解,如果不理解也没有关系,并不影响我们继续。

package com.example.kalen.studylistview;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;


public class ItemAdapter extends BaseAdapter{

    private List<ItemBean> datas;
    private LayoutInflater inflater;

    public ItemAdapter(List<ItemBean> datas, Context context) {
        super();
        this.datas = datas;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return datas.size();
    }

    @Override
    public Object getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            System.out.println("inflate-->" + position);
            //渲染item_lv.xml到view中
            convertView = inflater.inflate(R.layout.item_listview, null);
            holder = new ViewHolder();
            //拿到convertView的TextView
            holder.tv = (TextView) convertView.findViewById(R.id.tv_item_lv);
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }

        //取得该位置的bean对象
        ItemBean bean = datas.get(position);
        //为TextView设置Text
        holder.tv.setText(bean.getText());
        return convertView;
    }

    public class ViewHolder{
        public TextView tv;
    }
}

添加点击ListView的方法

通过以上知识的学习,我们就可以知道如何创建一个ListView,来显示数据了。接下来来看看如何监听点击事件呢?其实很简单,就像Button有一个setOnClickListener,listView也有一个setOnItemClickListener。这里使用Toast来显示ListView被点击的位置。

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(MainActivity.this, "点击了"+datas.get(position).getText(),
                Toast.LENGTH_LONG).show();
    }
});

运行代码,点击item,可以看到点击的文字内容显示了出来。


扩展学习

安卓5.0之后出了一个新的控件叫做RecyclerView,它可以用来实现垂直布局(ListView的布局)、水平布局、网格布局以及瀑布流等效果。有兴趣的可以自己学习。

链接