原文: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. #781 – 多个变换执行的顺序问题(Transform Order Matters)

    原文:#781 – 多个变换执行的顺序问题(Transform Order Matters) 原文地址:  https://wpf.2000things.com/2013/03/21/781-tr ...

  2. 学习web开发遇到几个细节问题

    1.在jsp中使用jsp表达式在input标签中时,避免直接和结束"/"相连 2.提取input select 标签内的内容,使用...value提取其值 3.form中含有一个o ...

  3. 如何通过submit提交form表单获取后台传来的返回值

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/qq_34651764/article/details/76373846 小伙伴是不是遇到过这样的问题 ...

  4. cellForRowAtIndexPath 设置图片

    #import "UIImageView+MJWebCache.h" #import "MJPhotoBrowser.h" #import "MJPh ...

  5. NSUserDefaults:通常用来保存一些小巧的数据,如用户的用户名密码,或者是用户的使用习惯,如是否自动打开背景音乐等。

    1,NSUserDefaults通常用来保存一些小巧的数据,如用户的用户名密码,或者是用户的使用习惯,如是否自动打开背景音乐等. 2,使用它的原因就是:代码简短,使用方便 3,使用方法如下: -(vo ...

  6. SQL基础问题整理

    在程序中,数据库操作是必不可少的部分,所以我们要备足数据库相关知识才能去应付程序中出现的种种问题.基于此,我特地在国外网站.博客上整理了一些问题,并附带了答案和解释.参考.为了保证“原汁原味”,我就保 ...

  7. Java获取URL对应的资源

    Java获取URL对应的资源   认识IP.认识URL是进行网络编程的第一步.java.net.URL提供了丰富的URL构建方式,并可以通过java.net.URL来获取资源.   一.认识URL   ...

  8. winform - FixedDialog

    可以设置form的属性: 窗口样式: MaximizeBox  :  false       去掉 方法 按钮 MinimizeBox  :  false       去掉 缩小到任务栏 的按钮 禁止 ...

  9. spring boot——结合docker

    spring boot——结合docker 前言 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 liunx机器上,也可以实现虚 ...

  10. matplotlib tricks(关闭坐标刻度、坐标轴不可见)

    plt.gray():只有黑白两色,没有中间的渐进色 1. 关闭坐标刻度(plt 与 AxesSubplot) plt plt.xticks([]) plt.yticks([]) 关闭坐标轴: plt ...