Android实现上拉加载更多ListView(PulmListView)
作者:zinss26914 发布时间:2023-11-03 19:19:44
思路
今天带大家实现一个上拉加载更多的ListView.GitHub传送门:PulmListView, 欢迎大家fork&&star.
先带大家理一下思路, 如果我们要实现一个上拉加载更多的ListView, 我们需要实现的功能包括:
1.一个自定义的ListView, 并且该ListView能够判断当前是否已经处于最底部.
2.一个自定义的FooterView, 用于在ListView加载更多的过程中进行UI展示.
3.关联FooterView和ListView, 包括加载时机判断、FooterView的显示和隐藏.
4.提供一个加载更多的接口, 便于回调用户真正加载更多的功能实现.
5.提供一个加载更多结束的回调方法, 用于添加用户的最新数据并更新相关状态标记和UI显示.
针对上面的5个功能, 我们挨个分析对应的实现方法.
功能1(自定义ListView)
我们可以通过继承ListView, 实现一个自定义的PulmListView.
public class PulmListView extends ListView {
public PulmListView(Context context) {
this(context, null);
}
public PulmListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PulmListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化
init();
}
}
只是实现ListView的三个构造函数还不够, 我们需要ListView能够判断当前的ListView是否滑动到最后一个元素.
判断是否滑动到最后一个元素, 我们可以通过为ListView设置OnScrollListener来实现.代码如下:
private void init() {
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 调用用户设置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 调用用户设置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
// firstVisibleItem是当前屏幕能显示的第一个元素的位置
// visibleItemCount是当前屏幕能显示的元素的个数
// totalItemCount是ListView包含的元素总数
int lastVisibleItem = firstVisibleItem + visibleItemCount;
if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
if (mOnPullUpLoadMoreListener != null) {
mIsLoading = true;
mOnPullUpLoadMoreListener.onPullUpLoadMore();
}
}
}
});
}
从代码注释可以知道, 通过(firstVisibleItem + visibleItemCount)可以获取当前屏幕已经展示的元素个数, 如果已经展示的元素个数等于ListView的元素总数, 则此时可以认为ListView已经滑动到底部.
功能2(自定义的FooterView)
这里我们可以实现一个比较简单的FooterView, 即加载更多的UI布局.例如我们可以展示一个ProgressBar和一行文字, 具体代码如下:
/**
* 加载更多的View布局,可自定义.
*/
public class LoadMoreView extends LinearLayout {
public LoadMoreView(Context context) {
this(context, null);
}
public LoadMoreView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadMoreView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.lv_load_more, this);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/id_load_more_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_margin="@dimen/loading_view_margin_layout">
<ProgressBar
android:id="@+id/id_loading_progressbar"
android:layout_width="@dimen/loading_view_progress_size"
android:layout_height="@dimen/loading_view_progress_size"
android:indeterminate="true"
style="?android:progressBarStyleSmall"/>
<TextView
android:id="@+id/id_loading_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/page_loading"/>
</LinearLayout>
功能3(关联ListView和FooterView)
第一,我们需要在ListView中通过一个变量保存FooterView, 并且在构造函数中将其实例化.
private View mLoadMoreView;
private void init() {
mLoadMoreView = new LoadMoreView(getContext());
}
第二,我们需要控制FooterView的显示和隐藏.考虑一下FooterView的显示和隐藏的时机:
•显示的时机: ListView处于最底部并且当前还有更多的数据需要加载.
•隐藏的时机: ListView结束完加载更多的操作.
为了判断当前是否还有数据需要加载, 因此我们需要定义一个boolean变量mIsPageFinished, 表示数据加载是否结束.
为了保证同一时间只进行一次数据加载过程, 因此我们还需要定义一个boolean变量mIsLoading, 表示当前是否已经处于数据加载状态.
明确了FooterView的显示和隐藏时机, 也有了控制状态的变量, 代码也就比较容易实现了.
显示时机:
private void init() {
mIsLoading = false; // 初始化时没处于加载状态
mIsPageFinished = false; // 初始化时默认还有更多数据需要加载
mLoadMoreView = new LoadMoreView(getContext()); // 实例化FooterView
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 调用用户设置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 调用用户设置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
int lastVisibleItem = firstVisibleItem + visibleItemCount;
// 当处于ListView尾部且有更多数据需要加载且当前没有加载程序再进行中时, 执行加载更多操作
if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
if (mOnPullUpLoadMoreListener != null) {
mIsLoading = true; // 将加载更多进行时状态设置为true
showLoadMoreView(); // 显示加载更多布局
mOnPullUpLoadMoreListener.onPullUpLoadMore(); // 调用用户设置的加载更多回调接口
}
}
}
});
}
private void showLoadMoreView() {
// 这里将加载更多的根布局id设置为id_load_more_layout, 便于用户自定制加载更多布局.
if (findViewById(R.id.id_load_more_layout) == null) {
addFooterView(mLoadMoreView);
}
}
隐藏时机:
/**
* 加载更多结束后ListView回调方法.
*
* @param isPageFinished 分页是否结束
* @param newItems 分页加载的数据
* @param isFirstLoad 是否第一次加载数据(用于配置下拉刷新框架使用, 避免出现页面闪现)
*/
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
mIsLoading = false; // 标记当前已经没有加载更多的程序在执行
setIsPageFinished(isPageFinished); // 设置分页是否结束标志并移除FooterView
}
private void setIsPageFinished(boolean isPageFinished) {
mIsPageFinished = isPageFinished;
removeFooterView(mLoadMoreView);
}
功能4(上拉加载更多实现的回调接口)
这个比较简单, 我们定义一个interface, 便于回调用户真正的加载更多的实现方法.
/**
* 上拉加载更多的回调接口
*/
public interface OnPullUpLoadMoreListener {
void onPullUpLoadMore();
}
private OnPullUpLoadMoreListener mOnPullUpLoadMoreListener;
/**
* 设置上拉加载更多的回调接口.
* @param l 上拉加载更多的回调接口
*/
public void setOnPullUpLoadMoreListener(OnPullUpLoadMoreListener l) {
this.mOnPullUpLoadMoreListener = l;
}
功能5(加载更多的结束回调)
为了在PulmListView中维护数据集合, 必须自定义一个Adapter, 在Adapter中使用List存储数据集合, 并提交增删的方法.
自定义的Adapter:
/**
* 抽象的Adapter.
*/
public abstract class PulmBaseAdapter<T> extends BaseAdapter {
protected List<T> items;
public PulmBaseAdapter() {
this.items = new ArrayList<>();
}
public PulmBaseAdapter(List<T> items) {
this.items = items;
}
public void addMoreItems(List<T> newItems, boolean isFirstLoad) {
if (isFirstLoad) {
this.items.clear();
}
this.items.addAll(newItems);
notifyDataSetChanged();
}
public void removeAllItems() {
this.items.clear();
notifyDataSetChanged();
}
}
为什么在addMoreItems方法中要增加一个isFirstLoad变量呢?
是因为上拉加载更多通常要配合下拉刷新使用.而下拉刷新的过程中会牵扯到ListView的数据集合clear然后再addAll.如果没有isFirstLoad参数, 那用户下拉刷新去更新ListView的数据集合就必须分为两步:
1.removeAllItems并进行notifyDataSetChanged.
2.addMoreItems并进行notifyDataSetChanged.
同一时间连续两次notifyDataSetChanged会导致屏幕闪屏, 因此这里提交了一个isFirstLoad方法.当是第一次加载数据时, 会先clear掉所有的数据, 然后再addAll, 最后再notify.
有了自定义的adapter, 就可以写加载更多结束的回调函数了:
/**
* 加载更多结束后ListView回调方法.
*
* @param isPageFinished 分页是否结束
* @param newItems 分页加载的数据
* @param isFirstLoad 是否第一次加载数据(用于配置下拉刷新框架使用, 避免出现页面闪现)
*/
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
mIsLoading = false;
setIsPageFinished(isPageFinished);
// 添加更新后的数据
if (newItems != null && newItems.size() > 0) {
PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
adapter.addMoreItems(newItems, isFirstLoad);
}
}
这里需要注意, 当添加了FooterView或者HeaderView之后, 我们无法通过listview.getAdapter拿到我们自定义的adapter, 必须按照如下步骤:
PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
参考
1.PagingListView
猜你喜欢
- Struts2的核心在于它复杂的 * ,几乎70%的工作都是由 * 完成的。比如我们之前用于将上传的文件对应于action实例中的三个属性的
- 最近要给一个 Winform 项目添加功能,需要一个能显示进度条的弹窗,还要求能够中止任务,所以就做了一个,在此做个记录总结。虽然用的是比较
- 快速入门在Spring Boot的工程中的pom.xml中引入spring-boot-starter-mail依赖:<dependen
- 第一个案例为大家分享了Android遍历特定目录下所有文件,包含子目录的,并删除最新创建的。 private boolean deleteL
- 本文实例讲述了Java中的异常和处理机制。分享给大家供大家参考,具体如下:简介程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期
- 前言集合就是一组数的集合,就像是一个容器,但是我们应该清楚的是集合中存放的都是对象的引用,而不是真正的实体。而我们常说的集合中的对象其实指的
- 可以理解当我们要调用一个方法时,我们会把指定的数值,传递给方法中的参数,这样方法中的参数就拥有了这个指定的值,可以使用该值,在方法中运算了。
- SpringMVC配置多个properties文件之通配符在springmvc中配置加载properties文件一般会在xml文件中配置如下
- 本文实例讲述了C#获取指定年份第一个星期一具体日期的方法。分享给大家供大家参考。具体如下:DateTime day = DateTime.P
- 本文实例为大家分享了Android实现界面跳转的具体代码,供大家参考,具体内容如下布局<?xml version="1.0&
- 前言我通常是不太关心代码的具体实现的,因为我的开发语言很杂,倾向于一些最简单通用的方式去解决。今儿不小心在群里看到一位朋友发了下面的java
- c#将字符串转换为大写或小写using System;using System.Collections.Generic;using Syst
- 由于要在Web项目中采用RFID读取功能,所以有必要开发Activex,一般情况下开发Activex都采用VC,VB等,但对这两块不是很熟悉
- 目录之前的处理方式现在的处理方式其他场景处理重复点击间接设置点击富文本列表数据绑定总结项目地址一般手机上的 Android App,主要的交
- 本文介绍Android中的5种数据存储方式。数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是
- 前言项目中有使用到水印效果,如下图所示。在实现过程中,最终选用ItemDecoration来实现,其中有两大步骤:自定义Drawable来完
- 你是否受够了每次修改静态文件都要重启服务器?有时候在一些公司前后端的职责没有那么的明确,往往后台人员也要去写一些页面,像jsp页面,或者其他
- BufferedReaderBufferedReader 是缓冲字符输入流。它继承于Reader。 BufferedReader 的作用是为
- 需求:有一个列表,列表中有一个edittext(只能输整形),外部有一个整形变量Int,每次改变列表中其中一项的edittext的值时,外部
- 生命周期速览优先级servlet 的声明周期由 tomcat 服务器自行管辖,程序员无法插手;只要没有通过 url 访问 servlet,那