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

前面连续几期都在学习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类代码如下:

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;
}
}

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

二、使用持有者模式

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

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

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

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

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; // 内容
}
}

这里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 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 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方法,其他不变,代码如下:

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;
}
}

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

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

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

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

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

此文章版权为微信公众号分享达人秀(ShareExpert)——鑫鱻所有,若需转载请联系作者授权,特此声明!

往期总结分享:

Android零基础入门第1节:Android的前世今生

Android零基础入门第2节:Android 系统架构和应用组件那些事

Android零基础入门第3节:带你一起来聊一聊Android开发环境

Android零基础入门第4节:正确安装和配置JDK, 高富帅养成第一招

Android零基础入门第5节:善用ADT Bundle, 轻松邂逅女神

Android零基础入门第6节:配置优化SDK Manager, 正式约会女神

Android零基础入门第7节:搞定Android模拟器,开启甜蜜之旅

Android零基础入门第8节:HelloWorld,我的第一趟旅程出发点

Android零基础入门第9节:Android应用实战,不懂代码也可以开发

Android零基础入门第10节:开发IDE大升级,终于迎来了Android Studio

Android零基础入门第11节:简单几步带你飞,运行Android Studio工程

Android零基础入门第12节:熟悉Android Studio界面,开始装逼卖萌

Android零基础入门第13节:Android Studio配置优化,打造开发利器

Android零基础入门第14节:使用高速Genymotion,跨入火箭时代

Android零基础入门第15节:掌握Android Studio项目结构,扬帆起航

Android零基础入门第16节:Android用户界面开发概述

Android零基础入门第17节:TextView属性和方法大全

Android零基础入门第18节:EditText的属性和使用方法

Android零基础入门第19节:Button使用详解

Android零基础入门第20节:CheckBox和RadioButton使用大全

Android零基础入门第21节:ToggleButton和Switch使用大全

Android零基础入门第22节:ImageView的属性和方法大全

Android零基础入门第23节:ImageButton和ZoomButton使用大全

Android零基础入门第24节:自定义View简单使用,打造属于你的控件

Android零基础入门第25节:简单且最常用的LinearLayout线性布局

Android零基础入门第26节:两种对齐方式,layout_gravity和gravity大不同

Android零基础入门第27节:正确使用padding和margin

Android零基础入门第28节:轻松掌握RelativeLayout相对布局

Android零基础入门第29节:善用TableLayout表格布局

Android零基础入门第30节:两分钟掌握FrameLayout帧布局

Android零基础入门第31节:少用的AbsoluteLayout绝对布局

Android零基础入门第32节:新推出的GridLayout网格布局

Android零基础入门第33节:Android事件处理概述

Android零基础入门第34节:Android中基于监听的事件处理

Android零基础入门第35节:Android中基于回调的事件处理

Android零基础入门第36节:Android系统事件的处理

Android零基础入门第37节:初识ListView

Android零基础入门第38节:初识Adapter

Android零基础入门第39节:ListActivity和自定义列表项

Android零基础入门第40节:自定义ArrayAdapter

Android零基础入门第41节:使用SimpleAdapter

Android零基础入门第42节:自定义BaseAdapter

Android零基础入门第43节:ListView优化和列表首尾使用的更多相关文章

  1. Android零基础入门第44节:ListView数据动态更新

    原文:Android零基础入门第44节:ListView数据动态更新 经过前面几期的学习,关于ListView的一些基本用法大概学的差不多了,但是你可能发现了,所有ListView里面要填充的数据都是 ...

  2. Android零基础入门第58节:数值选择器NumberPicker

    原文:Android零基础入门第58节:数值选择器NumberPicker 上一期学习了日期选择器DatePicker和时间选择器TimePicker,是不是感觉非常简单,本期继续来学习数值选择器Nu ...

  3. Android零基础入门第59节:AnalogClock、DigitalClock和TextClock时钟组件

    原文:Android零基础入门第59节:AnalogClock.DigitalClock和TextClock时钟组件 在前面一期,我们学习了DatePicker和TimePicker,在实际开发中其不 ...

  4. Android零基础入门第57节:日期选择器DatePicker和时间选择器TimePicker

    原文:Android零基础入门第57节:日期选择器DatePicker和时间选择器TimePicker 在实际开发中,经常会遇见一些时间选择器.日期选择器.数字选择器等需求,那么从本期开始来学习And ...

  5. Android零基础入门第56节:翻转视图ViewFlipper打造引导页和轮播图

    原文:Android零基础入门第56节:翻转视图ViewFlipper打造引导页和轮播图 前面两期学习了 ViewAnimator及其子类ViewSwitcher的使用,以及ViewSwitcher的 ...

  6. Android零基础入门第55节:ImageSwitcher和TextSwitcher使用

    原文:Android零基础入门第55节:ImageSwitcher和TextSwitcher使用 上一期我们了解了ViewAnimator组件和ViewSwitcher组件的使用,你都掌握了吗?本期一 ...

  7. Android零基础入门第54节:视图切换组件ViewSwitcher

    原文:Android零基础入门第54节:视图切换组件ViewSwitcher 前面三期学习了ProgressBar系列组件,那本期开始一起来学习ViewAnimator组件. 一.ViewAnimat ...

  8. Android零基础入门第53节:拖动条SeekBar和星级评分条RatingBar

    原文:Android零基础入门第53节:拖动条SeekBar和星级评分条RatingBar 前面两期都在学习ProgressBar的使用,关于自定义ProgressBar的内容后期会继续学习的,本期先 ...

  9. Android零基础入门第52节:自定义酷炫进度条

    原文:Android零基础入门第52节:自定义酷炫进度条 Android系统默认的ProgressBar往往都不能满足实际开发需要,一般都会开发者自定义ProgressBar. 在Android开发中 ...

随机推荐

  1. Erlang 位串和二进制数据

    http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25876834&id=3300393 因为在本人工作中,服务端Erla ...

  2. Oracle数据库零散知识03

    21,存储过程,简化复杂操作,增加数据独立性,提高安全性,提高性能 与函数创建对比: create or replace function fun_01(v_01 in number) return ...

  3. Graphics processing architecture employing a unified shader

    FIELD OF THE INVENTION The present invention generally relates to graphics processors and, more part ...

  4. ANR触发原理

    ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造 ...

  5. cordova通过指纹插件进行指纹验证

    原文:cordova通过指纹插件进行指纹验证 版权声明:本文为博主原创文章,转载须注明出处,博客地址:https://blog.csdn.net/wx13227855087 https://blog. ...

  6. 【PHP Manager for IIS】让IIS支持PHP

    本文安装环境: 操作系统:Win7 64位 PHP版本:PHP 5.5.15(VC11 x64 Thread Safe)    下载地址:http://windows.php.net/download ...

  7. In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.

    In partitioned databases, trading some consistency for availability can lead to dramatic improvement ...

  8. Ibatis之RowHandler

    如果一个场景:账户表中有1千万账户,现在,我们需要这个1千万账户利息结算业务.需求是基于Ibatis框架来实现这个功能. 如果按照一般的编程模式,我们将编写一个sql,然后调用QueryForList ...

  9. 详尽分析世纪之战:360VS腾讯是两个阶层的抗争

    很不错的一篇文字  分析的也很透彻 [转自中国移动http://labs.chinamobile.com/] 来源:搜狐IT 作者:吃熊掌的鱼 2010-11-01 10:11:51 [ 13967阅 ...

  10. UVA 1347(POJ 2677) Tour(双色欧几里德旅行商问题)

    Description John Doe, a skilled pilot, enjoys traveling. While on vacation, he rents a small plane a ...