继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android零基础入门第43节:ListView优化和列表首尾使用

喵喔喔
关注TA
已关注
手记 514
粉丝 103
获赞 606

前面连续几期都在学习ListView的各种使用方法,如果细心的同学可能会发现其运行效率是有待提高的,那么本期就来一起学习有哪些方法技巧来优化ListView的效率。

一、使用convertView

    前面讲的自定义ArrayAdapter和自定义BaseAdapter,都会重写getView()方法,虽然可以正常使用,但其实效率非常低。当列表项很多时,用户每次滚动屏幕,都会创建一批新的View对象,以填充新出现的列表项,这样势必会影响用户体验。

    我们可以看到getView()方法中传入了一个参数convertView,可以验证该convertView的值有时候是null,有时候又不是null,特别是当用户滚动ListView的时候。其实这是适配器使用相同组件动态绑定数据的方式进行了优化,这是为何呢?

    大家可以想想,如果列表项有成百上千个,Android系统会为每个列表项新建一个列表项组件吗?当然这是不可能的,毕竟Android系统的内存有限,不可能无限新建列表项组件。实际上Android缓存了视图组件,由于Android系统中有一个Recycler构件,其工作原理如下图所示。

     如果有很多个列表项,其中只有可见的列表项组件保存在内存中,其他的都在Recycler中。其实Recyler可以理解为就是一个队列,用来存储不在屏幕范围内的item,如果item完全滚粗屏幕范围,那么该item就保存在队列中;如果新的item要滚动出来,那么就会首先查看Recyler是否含有可以重复使用的View,如果有就直接重新设置该View 的数据源,然后显示出来。

    其实Recycler缓存的item就是getView()方法中的参数convertView。所以会发现convertView有时候为null,有时候不为null。那么我们是否可以利用这一点来优化我们的ListView运行效率呢?答案是肯定的。

    接下来就在“自定义BaseAdapter”的基础上来开始优化,除了MyBaseAdapter类的getView()方法代码会发生改变,其他不变。修改后的MyBaseAdapter类代码如下:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

package com.jinyu.cqkxzsxy.android.listviewsample.adapter;

 

import android.content.Context;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.ImageView;

import android.widget.TextView;

 

import com.jinyu.cqkxzsxy.android.listviewsample.R;

import com.jinyu.cqkxzsxy.android.listviewsample.entity.Data;

 

import java.util.List;

 

/**

 * @创建者 鑫鱻

 * @描述 Android零基础入门到精通系列教程,欢迎关注微信公众号ShareExpert

 */

public class MyBaseAdapter   extends BaseAdapter   {

    private Context mContext; // 上下文环境

    private List<data> mDatas; // 列表数据集合

    private int mResId; // 列表项布局文件ID

 

    // 构造方法

    public MyBaseAdapter(Context context,   List<data> datas, int resId) {

        this.mContext   = context;

        this.mDatas   = datas;

        this.mResId   = resId;

    }

 

    // 获得列表项的数量

    @Override

    public int getCount() {

        return mDatas.size();

    }

 

    // 获得当前列表项

    @Override

    public Data getItem(int position) {

        return mDatas.get(position);

    }

 

    // 获得当前列表项的ID

    @Override

    public long getItemId(int position) {

        return position;

    }

 

    // 获得第position处的列表项组件

    @Override

    public View getView(int position, View convertView, ViewGroup   viewGroup) {

        if(null == convertView) {

            //   获取LayoutInflater对象

            LayoutInflater   inflater = LayoutInflater.from(mContext);

            //   装载列表项视图

            convertView   = inflater.inflate(mResId, null);

        }

 

        //   获取列表项组件

        ImageView   iconImg = (ImageView) convertView.findViewById(R.id.icon_img);

        TextView   titleTv = (TextView) convertView.findViewById(R.id.title_tv);

        TextView   infoTv = (TextView) convertView.findViewById(R.id.info_tv);

 

        //   给列表项赋值

        Data   data = getItem(position);

        if(null != data) {

            iconImg.setImageResource(data.getIcon());

            titleTv.setText(data.getTitle());

            infoTv.setText(data.getInfo());

        }

 

        return convertView;

    }

}

</data></data>

    经过这样的改造后,getView()方法首先检查convertView是否为空,如果是则新装填一个列表项组件,否则就重用它,就可以避免多余的装载导致的内存开销。

二、使用持有者模式

    与创建列表项组件的另一个代价较大的操作,就是调用findViewById()方法。这个方法会深入到已装填的行,根据指定的标识符取出对应的组件,便于修改列表项组件的内容,如修改TextView的文本。由于findViewById()方法可以从行所在根视图的所有子组件中找到组件,因此可能需要执行相当多的指令,而在重复取的相同组件的情况下则更是如此。

    在某些GUI工具包中,可以通过在程序代码中整体性地声明复合的View对象来避免这个问题。因为在访问这个组件时,无非就是调用getter方法或访问字段。当然,在Android中也可以做到这一点,只不过代码会复杂繁琐一些。一个比较理想的方案就是,仍然使用XML布局,但是又可以缓存行中的关键子组件,也就是只需要查找一次即可,就意味着要使用持有者模式了。

    在前面学习View的时候,知道每个View对象都有一个getTag()和setTag()方法,通过这两个方法可以在任何对象与组件之间建立联系。在持有者模式中,Tag标签用来保存对象,而对象又用来保存要使用的子组件。在将持有者添加到视图后,只要用到了行,就可以轻而易举的访问其子组件,而不必再调用findViewById()方法了。

    接下来继续在“自定义BaseAdapter”的基础上来开始优化,除了MyBaseAdapter类中增加一个持有者类和修改getView()方法代码,其他不变。修改后的MyBaseAdapter类代码如下:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

package com.jinyu.cqkxzsxy.android.listviewsample.adapter;

 

import android.content.Context;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.ImageView;

import android.widget.TextView;

 

import com.jinyu.cqkxzsxy.android.listviewsample.R;

import com.jinyu.cqkxzsxy.android.listviewsample.entity.Data;

 

import java.util.List;

 

/**

 * @创建者 鑫鱻

 * @描述 Android零基础入门到精通系列教程,欢迎关注微信公众号ShareExpert

 */

public class MyBaseAdapter   extends BaseAdapter   {

    private Context mContext; // 上下文环境

    private List<data> mDatas; // 列表数据集合

    private int mResId; // 列表项布局文件ID

 

    // 构造方法

    public MyBaseAdapter(Context context,   List<data> datas, int resId) {

        this.mContext   = context;

        this.mDatas   = datas;

        this.mResId   = resId;

    }

 

    // 获得列表项的数量

    @Override

    public int getCount() {

        return mDatas.size();

    }

 

    // 获得当前列表项

    @Override

    public Data getItem(int position) {

        return mDatas.get(position);

    }

 

    // 获得当前列表项的ID

    @Override

    public long getItemId(int position) {

        return position;

    }

 

    // 获得第position处的列表项组件

    @Override

    public View getView(int position, View convertView, ViewGroup   viewGroup) {

        ViewHolder   holder = null;

        if(null == convertView) {

            //   获取LayoutInflater对象

            LayoutInflater   inflater = LayoutInflater.from(mContext);

            //   装载列表项视图

            convertView   = inflater.inflate(mResId, null);

 

            //   新建持有者ViewHolder

            holder   = new ViewHolder();

            //   获取列表项组件

            holder.iconImg   = (ImageView) convertView.findViewById(R.id.icon_img);

            holder.titleTv   = (TextView) convertView.findViewById(R.id.title_tv);

            holder.infoTv   = (TextView) convertView.findViewById(R.id.info_tv);

 

            //   将ViewHolder对象存储到convertView中

            convertView.setTag(holder);

        }   else {

            //   从convertView取出ViewHolder对象

            holder   = (ViewHolder) convertView.getTag();

        }

 

        //   给列表项组件设置内容

        Data   data = getItem(position);

        if(null != data) {

            holder.iconImg.setImageResource(data.getIcon());

            holder.titleTv.setText(data.getTitle());

            holder.infoTv.setText(data.getInfo());

        }

 

        return convertView;

    }

 

    // 持有者类

    private class ViewHolder {

        ImageView   iconImg; // 图标

        TextView   titleTv; // 标题

        TextView   infoTv; // 内容

    }

}

</data></data>

    这里ViewHolder作为持有者类,此处比较简单直接使用没有给出getter和setter方法。当convertView 为空的时候,装填一个列表项组件,并同时创建相应的ViewHolder对象;当convertView 不为空,只需要从其中取出ViewHolder对象,即可轻松给子组件填充内容。

三、列表头和列表尾的使用

    在实际使用ListView时,经常会有这样的需求:当位于ListView最顶部的时候,显示一个搜索框可以搜索列表内容,或者显示下拉刷新;当位于ListView最底部的时候,显示一个上拉加载更多的功能。由于这显示的内容同ListView列表项内容不同,可以通过控制position来实现效果,但是非常繁琐,当然Android中提供了ListView的列表头和列表尾功能。

    给ListView添加HeadView和FootView,当ListView滑动至列表第一项时使HeadView滑动出现,当ListView滑动至列表最后一项时使FootView滑动出现。

    接下来就通过一个示例来学习如何使用ListView列表头和列表尾。仍然在“自定义BaseAdapter”的基础上来完成。

    首先设计一个ListView列表头布局list_headview_layout.xml,主要是一个搜索框,代码如下:

[代码]xml代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

<?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">

 

    <EditText

        android:id="@+id/search_et"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:hint="搜索"

        android:padding="10dp"/>

</LinearLayout>

    接着设计一个ListView列表尾布局list_footview_layout.xml,主要是提示用户上拉加载更多,代码如下:

[代码]xml代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

<?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:gravity="center_horizontal">

 

    <TextView

        android:id="@+id/prompt_tv"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:padding="10dp"

        android:text="上拉加载更多"/>

</LinearLayout>

    最后是将上面定义的列表头布局额列表尾布局添加到ListView列表,主要修改Activity类的onCreate方法,其他不变,代码如下:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

package com.jinyu.cqkxzsxy.android.listviewsample;

 

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.view.View;

import android.widget.ListView;

 

import com.jinyu.cqkxzsxy.android.listviewsample.adapter.MyBaseAdapter;

import com.jinyu.cqkxzsxy.android.listviewsample.entity.Data;

 

import java.util.ArrayList;

import java.util.List;

 

public class CustomBaseAdapterActivity   extends AppCompatActivity   {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.custom_baseadapter_layout);

 

        //   获取界面组件

        ListView   listView = (ListView) findViewById(R.id.listview);

 

        //   获取列表和列表尾

        View   hearderView = getLayoutInflater().inflate(R.layout.list_headview_layout, null);

        View   footView = getLayoutInflater().inflate(R.layout.list_footview_layout, null);

 

        //   给ListView添加列表和列表尾

        listView.addHeaderView(hearderView);

        listView.addFooterView(footView);

 

        //   将数组包装为自定义MyBaseAdapter

        MyBaseAdapter   adapter = new MyBaseAdapter(this,   getData(), R.layout.custom_baseadapter_item);

 

        //   为ListView设置Adapter

        listView.setAdapter(adapter);

    }

 

    /**

     * 获取列表数据

     * @return

     */

    private List<data> getData() {

        List<data>   datas = new ArrayList<>();

        datas.add(new Data(R.drawable.item_01, "小宗", "电台DJ"));

        datas.add(new Data(R.drawable.item_02, "貂蝉", "四大美女"));

        datas.add(new Data(R.drawable.item_03, "奶茶", "清纯妹妹"));

        datas.add(new Data(R.drawable.item_04, "大黄", "是小狗"));

        datas.add(new Data(R.drawable.item_05, "hello",   "every thing"));

        datas.add(new Data(R.drawable.item_06, "world",   "hello world"));

        return datas;

    }

}

</data></data>

    这里需要注意的是,给ListView添加列表和列表尾的代码必须放在设置Adapter代码之前,否则会报错。

    运行程序,可以看到如下图所示效果。

    关于列表搜索和加载的功能此处不做过多学习,后期根据需要再进行学习。

    至此,关于ListView简单优化和列表头、尾的简单使用学习完毕,如果还不是很熟悉,建议多加练习,下期一起来学习ListView的数据动态更新。

    今天就先到这里,如果有问题欢迎留言一起探讨,也欢迎加入Android零基础入门技术讨论微信群,共同成长!

原文链接:http://www.apkbus.com/blog-205190-68734.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP