ViewAndroid知识Android技术知识

Android常用控件-ListView

2017-02-22  本文已影响244人  史慧君

由于手机屏幕有限, 所以ListView的使用非常的普遍. ListView就是用户可以通过手指上下滑动的方式来展现更多的数据.

ListView的简单使用:

新建一个ListViewDemo的项目,修改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"
   android:orientation="vertical">
    <ListView
        android:id="@+id/list_view_demo"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>

为ListView指定一个id, 这样就会在页面出现一个ListView的基本布局.
下面是MainAcitivity中代码:


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

public class MainActivity extends AppCompatActivity {

    private String[] data = {"爸爸","妈妈","姐姐","妹妹","二姑","三姑",
                 "四舅","五伯","哥哥","嫂嫂","爷爷","奶奶","姥姥","姥爷"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //android.R.layout.simple_list_item_1 作为ListView子项布局的id, 
       // 这是一个android内置的布局文件, 里面只有一个textview
        //可用于简单的显示一段文本.
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
        ListView listView = (ListView)findViewById(R.id.list_view_demo);
        listView.setAdapter(adapter);

    }
}

ListView是用来展示大量数据的, 这里用了一个数组. 数组中的数据是无法直接传递给ListView的, 需要借助适配器来传递. 由于这里提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String, 依次传递参数为, 上下文, 子项布局id, 数据.

ListView的setAdapter()方法表示将建好的设配器对象传递给listview, 这样页面和数据就进行了关联.

运行效果:

定制ListView的界面

正式的项目开发中, 不可能使用这么简单的, 很多情况是需要根据客户的需求, 进行定制开发, 下面我们就对上面的ListView进行简单的扩展.

我们把这个列表扩展成一个简单的通讯录功能功能, 会显示头像, 昵称, 和电话.

我们首先定义一个实体类, 作为ListView适配器的适配类型. 新建类Family, 代码如下:

public class Family {
    private String name;
    private int imageID;
    private int phoneNum;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageID() {
        return imageID;
    }

    public void setImageID(int imageID) {
        this.imageID = imageID;
    }

    public int getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(int phoneNum) {
        this.phoneNum = phoneNum;
    }
}

name:表示称呼
imageID: 表示头像id
phoneNum: 表示电话号码

我们需要为ListView的子项指定一个我们自定义的布局, 在layout的目录下新建family_item.xml, 代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/family_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/family_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>
    <TextView
        android:id="@+id/family_phone_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>
</LinearLayout>

在这个布局中, 定义了ImageView用于显示头像, TextView分别用来显示称谓和电话. 并且让TextView在垂直方向居中显示.

接下来我们需要创建自定义的适配器, 这个适配器继承ArrayAdapter, 并将泛型定义为Family类. 新建类FamilyAdapter, 代码如下:

public class FamilyAdapter  extends ArrayAdapter<Family>{

    private  int resourceID;
    public FamilyAdapter(Context context, int resource, List<Family> objects) {
        super(context, resource, objects);
        resourceID = resource;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Family family = getItem(position); // 获取当前项的Family实例
        View view = LayoutInflater.from(getContext()).inflate(resourceID, parent, false);
        ImageView  familyImage = (ImageView) view.findViewById(R.id.family_image);
        TextView familyName = (TextView) view.findViewById(R.id.family_name);
        TextView familyPhone = (TextView) view.findViewById(R.id.family_phone_num);

        familyImage.setImageResource(family.getImageID());
        familyName.setText(family.getName());
        familyPhone.setText(family.getPhoneNum());
        return view;
    }
}

FamilyAdapter重写了父类的一组构造函数, 用于将上下文, ListView子项布局的id和数据都传递进来. 另外又重写了一个getView()方法, 这个方法在每个子项被滚动到屏幕内的时候会被调用.

在getView()中, 首先通过getItem()方法得到当前项的Family实例, 然后使用LayoutInflater来为这个子项加载我们传入的布局.

这里LayoutInflater的inflate()方法接收3个参数, inflate()可以动态加载一个布局文件的id, 布局id, 第二个参数是给加载好的布局添加一个父布局.第三个参数指定成false, 表示只将我们在父布局中声明的layout属性生效, 但不为这个View添加父布局, 因为一旦view有了父布局之后, 它就不能再添加到ListView中了.

之后我们调用View的findViewById()方法分别获取到ImageView和TextView的实例. 并分别调用他们的setImageResource()和setText()方法来设置显示的图片和文字, 最后将布局完成.

下面来修改MainActivity中的代码.

public class MainActivity extends AppCompatActivity {
    private List<Family> familyList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFamilys();// 初始化家庭数据
        FamilyAdapter adapter = new FamilyAdapter(MainActivity.this,R.layout.family_item,familyList);
        ListView listView = (ListView)findViewById(R.id.list_view_demo);
        listView.setAdapter(adapter);

    }

    private void initFamilys() {
        for(int i= 0; i<2; i++){
            Family family1  = new Family("爸爸", R.mipmap.family_1, 
"13813813888");
            familyList.add(family1);
            Family family2  = new Family("妈妈", R.mipmap.family_2, 
"13813813888");
            familyList.add(family2);
            Family family3  = new Family("姐姐", R.mipmap.family_1, 
"13813813888");
            familyList.add(family3);
            Family family4  = new Family("三姑", R.mipmap.family_2, 
"13813813888");
            familyList.add(family4);
            Family family5  = new Family("妹妹", R.mipmap.family_1, 
"13813813888");
            familyList.add(family5);
            Family family6  = new Family("二姑", R.mipmap.family_1, 
"13813813888");
            familyList.add(family6);
            Family family7  = new Family("三姑", R.mipmap.family_2, 
"13813813888");
            familyList.add(family7);
            Family family8  = new Family("四舅", 
R.mipmap.family_1, "13813813888");
            familyList.add(family8);
            Family family9  = new Family("三姑", R.mipmap.family_2, 
"13813813888");
            familyList.add(family9);
            Family family10  = new Family("哥哥", R.mipmap.family_1, 
"13813813888");
            familyList.add(family10);
            Family family11  = new Family("嫂嫂", R.mipmap.family_1, 
"13813813888");
            familyList.add(family11);
            Family family12  = new Family("爷爷", R.mipmap.family_2, 
"13813813888");
            familyList.add(family12);
            Family family13  = new Family("奶奶", R.mipmap.family_1, 
"13813813888");
            familyList.add(family13);
            Family family14  = new Family("姥姥", R.mipmap.family_2,
 "13813813888");
            familyList.add(family14);
            Family family15  = new Family("姥爷", R.mipmap.family_1, 
"13813813888");
            familyList.add(family15);

        }


    }
}

运行效果:

相信你已经领悟到了诀窍, 只要修改family_item.xml中的内容, 就可以定制出各种复杂的界面了.

提升ListView的运行效率

之所以说ListView很难用, 是因为它有很多细节可以优化. 其中效率就是很重要的一点. 目前我们的运行效率极低, 是因为FamilyAdapter的getView()方法中, 每次都将布局重新加载一遍, 当ListView快速滚动的时候, 就会出现性能的瓶颈.

在getView()方法中还有一个converView参数, 这个参数用于将之前加载好的布局进行缓存, 以便之后可以重用. 所以修改代码如下:

Family family = getItem(position); // 获取当前项的Family实例
        View view;

        if(convertView == null){
            view  = LayoutInflater.from(getContext()).inflate(resourceID, 
parent, false);
        } else {
            view = convertView;
        }

convertView 为null的时候, 才去加载布局, 如果不为null, 则直接重用convertView. 这样就大大提高了ListView的运行效率. 加快滚动的时候也可以出现很好的性能.

当然这部分代码还是可以继续优化的, 虽然现在不会再去重复加载布局, 但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例.

我们可以借助一个ViewHolder来对这部分性能进行优化. 修改FamilyAdapter中的优化代码.


public View getView(int position, View convertView, ViewGroup parent) {
        Family family = getItem(position); // 获取当前项的Family实例
        View view;
        ViewHolder viewHolder;

        if(convertView == null){
            view  = LayoutInflater.from(getContext()).inflate(resourceID, 
parent, false);
            viewHolder = new ViewHolder();
            viewHolder.familyImage = (ImageView) view.findViewById(R.id.family_image);
            viewHolder.familyName = (TextView) view.findViewById(R.id.family_name);
            viewHolder.familyPhone = (TextView) view.findViewById(R.id.family_phone_num);
            view.setTag(viewHolder); //将ViewHolder储存在View中
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();// 重新获取ViewHolder
        }

        viewHolder.familyImage.setImageResource(family.getImageID());
        viewHolder.familyName.setText(family.getName());
        viewHolder.familyPhone.setText(family.getPhoneNum());
        return view;
    }

    class ViewHolder {
        ImageView familyImage;
        TextView familyName;
        TextView familyPhone;

    }

解读:
新增的ViewHolder用于对控件的实例进行缓存, 当convertView为null的时候, 创建一个ViewHolder对象, 并将控件实例都存在ViewHolder里, 然后调用view的setTag()方法, 将ViewHolder对象储存在view中.

当convertView不为null的时候, 则调用View的getTag()方法, 把ViewHolder重新取出. 这样所有的控件实例都缓存在了ViewHolder里面. 就没有必要每次通过findViewById()方法来获取控件实例了.

运行效果:

源码如下:
https://github.com/junzaivip/ListViewDemo

上一篇下一篇

猜你喜欢

热点阅读