Android上拉加载更多ListView——PulmListView

思路

今天带大家实现一个上拉加载更多的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
时间: 2025-01-03 12:24:51

Android上拉加载更多ListView——PulmListView的相关文章

Android_ListView上拉加载更多(ListView分页功能)

先上效果图 加载完数据 首先定义一个底部正在加载的布局footer_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:layou

下拉刷新,上拉加载更多ListView

点击这里下载源码     点击这里查看更多 这里是重写ListView实现下拉刷新下拉加载的源码: public class DropDownListView extends ListView implements OnScrollListener { private boolean isDropDownStyle = true; private boolean isOnBottomStyle = true; private boolean isAutoLoadOnBottom = false;

android 安卓 listview 支持下拉刷新 上拉加载更多

[1]重写listView import java.text.SimpleDateFormat; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGrou

android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)

Android实现上拉加载更多功能以及下拉刷新功能, 采用了目前比较火的PullToRefresh,他是目前实现比较好的下拉刷新的类库. 目前他支持的控件有:ListView, ExpandableListView,GridView,WebView等. 下载地址:https://github.com/chrisbanes/Android-PullToRefresh 首先第一步当然是导入libriay到咱们的项目了,具体导入方式,这里不再赘述. 下面是个例子采用的是ListView,当然其余的和这

Android中自定义ListView实现上拉加载更多和下拉刷新

ListView是Android中一个功能强大而且很常用的控件,在很多App中都有ListView的下拉刷新数据和上拉加载更多这个功能.这里我就简单记录一下实现过程. 实现这个功能的方法不止一个,GitHub上有一些开源库可以使用,但是本着学习的精神,我做的是使用自定义ListView实现这个功能. 思路:谷歌提供的ListView是不能提供下拉刷新和下拉加载的,所以我们就需要重写ListView.在ListView的头部和尾部加上我们的布局文件(progressbar). 先说上拉加载更多实现

Android中ListView上拉加载更多及下拉刷新

做几乎每一个Android应用开发,都少不了用到一个控件,那就是ListView,用于加载多条数据,并用一定的样式展示出来.但是为了性能问题(一次性加载太多数据,比如100000条,耗费时间长,消耗资源多等)及用户体验问题(比如用户只想看最新的10条数据,结果一下子把所有的上万条数据都加载了,不方便用户选择)等原因,所以我们要把ListView的数据进行分页加载,常用的就是ListView的上拉加载更多及下拉刷新最新数据. 我们可以自己封装一个带上下拉功能的ListView,通常就是加上头部He

Android项目:使用pulltorefresh开源项目扩展为下拉刷新上拉加载更多的处理方法,监听listview滚动方向

很多android应用的下拉刷新都是使用的pulltorefresh这个开源项目,但是它的扩展性在下拉刷新同时又上拉加载更多时会有一定的局限性.查了很多地方,发现这个开源项目并不能很好的同时支持下拉刷新和上拉加载更多.这个组件有个mode的属性,可以设置为both,即上下同时都可拉动.但是只设置这个属性的话,上拉与下拉产生的效果是完全一致的.所以要使用这个开源项目做到下拉刷新并同时可上拉加载更多,就需要在代码中进行一些处理. ==========================pulltoref

Android实战简易教程-第五十三枪(通过实现OnScrollListener接口实现上拉加载更多功能)

支持上拉加载更多的控件有很多,但是你知道背后的原理吗?有一些面试官可能会问到这方便的知识,他们认为会用不是目的,懂背后的原理才是真人才.下面我们通过实现OnScrollListener接口实现上拉加载更多的效果,这里用到了回调接口,你需要对回调进行比较好的理解,回调机制是Android中很重要的机制,下面我们看一下代码: 1.定义一个footer.xml,用于下拉提示的效果: <?xml version="1.0" encoding="utf-8"?>

让Android Support V4中的SwipeRefreshLayout支持上拉加载更多

前言 原来的Android SDK中并没有下拉刷新组件,但是这个组件确实绝大多数APP必备的一个部件.好在google在v4包中出了一个SwipeRefreshLayout,但是这个组件只支持下拉刷新,不支持上拉加载更多的操作.因此,我们就来简单的扩展一下这个组件以实现上拉下载的目的. 基本原理 上拉加载或者说滚动到底部时自动加载,都是通过判断是否滚动到了ListView或者其他View的底部,然后触发相应的操作,这里我们以ListView来说明.因此我们需要在监听ListView的滚动事件,当