「Efficient Android Threading 笔记」- C1 Android Components and the Need for Multiprocessing Android RecyclerView Android Socket Programming Supporting Multiple Screens Make a Reusable UI in Android App Development 如何在 Android Studio 中包含 *.so library,并使用库中定义的方法? 使用 SpannableString 格式化字符串,实现前景色、下划线、超链接、图文混排等 如何使用 bound service 完成进程间通信? 创建自定义视图 Creating custom views 通过 Android Theme & Style 定制应用的样式 「译」Android ViewPropertyAnimator 介绍 Android Animation Interpolator - Android 动画插值器源码笔记 「译」Android Animation in Honeycomb by Chet Haase(Android 3.0系统中的动画机制) 从 Android Sample ApiDemos 中学习 android.animation API 的用法 如何学习 Android Animation? 如何实现 Android ListView「上拉加载更多」? 「译」向Big Nerd Ranch提问:为什么Fragment在Android App开发中非常重要? 分类整理我在 SegmentFault 上针对某些问题作的回答 Android Servcie 后台服务总结笔记 如何在Android设备旋转时暂存数据以保护当前的交互状态? Android Message Handler 消息处理机制总结笔记 如何获取FragmentTabHost中指定标签页的Fragment? Fragment子类必须包含一个public无参构造器 如何更新及替换ViewPager中的Fragment? 如何使用Android UI Fragment开发“列表-详情”界面? 一个Android音频文本同步的英文有声读物App的开发过程 「Android编程权威指南笔记」Android应用本地化 通过jfeinstein10/SlidingMenu实现Android侧滑菜单 为Ubuntu14.04部署Android App的Eclipse开发环境 「Android编程权威指南笔记」使用ListFragment显示列表 「Android编程权威指南笔记」SDK版本与兼容 「Android编程权威指南笔记」Android布局和组件 「Android编程权威指南笔记」UI Fragment 「Android编程权威指南笔记」Activity 第一次开发iOS App和Android的对比总结笔记 「App Training笔记」创建第一个应用 「App Training笔记」开发入门训练大纲 Android APP - 从远程FTP服务器下载文件到本地

如何实现 Android ListView「上拉加载更多」?

2016年01月12日

ListView上拉加载更多的UI需求

  1. 向上滑动 ListView,当最后一个条目滚入屏幕时开始加载更多条目,在列表底部增加一个 footerView:一个 infinite progressBar,一个 textView 显示 “Loading…”;
  2. 根据数据加载的结果更新 view
    • 如果已经没有更多条目,则更新 footerView:仅包含一个 textView 显示“No More”;
    • 如果成功获取更多条目,则更新 ListView,同时移除(隐藏) footerView;
    • 如果加载失败(网络异常等原因),移除(隐藏) footerView。

综上述,需要有一个 footerView,它包含两种状态:

-------------------------------
|        @  Loading...        | (借用 @ 当做infinite progressBar)
-------------------------------

-------------------------------
|        No More              |
-------------------------------

这是一个挺简单 UI 需求,比常见的实现方式少了一种状态:

-------------------------------
|        查看更多             |
-------------------------------

在这种状态下,点击 footerView 也可以和『上拉』一样加载更多条目。我对比了手Q和微信,手Q就多了这个『查看更多』的状态(当然,必须在上拉时恰好让它停在最后一个条目,不然上拉过头后,就立刻变成『Loading…』)。
本需求并不需要这个状态,所以下面的实现分析不会考虑它,所以整体实现相对简单。

为了实现上述需求,需要考虑三个问题:

  • 如何定义 footerView?
  • 何时加载更多?
  • 数据加载完毕后,如何更新视图?

如何定义 footerView?

如上述,footerView 包含两种状态:加载中、没有加载

『加载中』包含两个控件,infinite progressBar 和 textView,放进一个 LinearLayout;『没有加载』只包含一个控件,textView,也把它放进一个 LinearLayout;然后把这两个 LinearLayout 放到一个 FrameLayout 内。根据状态决定显示哪个 LinearLayout。因此只需要一个 public method:

public class PullUpLoadListViewFooter extends LinearLayout {
    public enum State {
        LOADING,
        NOT_LOADING,
    }

    public void updateView(State state, String content) {}
}

何时加载更多?

向上滑动 ListView,当最后一个条目滚入屏幕时开始加载更多条目。ListView 可以监听滚动事件,因此知道何时加载更多。但数据加载的工作显然应该交给控制器,也就是 ListView 的托管者比如 Activity 来完成。
所以,在 ListView 中定义一个接口,并在 ListView 滚动事件中回调这个接口方法:

public class PullUpLoadListView extends ListView {
    public interface OnPullUpLoadListener {
        void onPullUpLoading();
    }

    public void setOnPullUpLoadListener(OnPullUpLoadListener listener) {
        mOnPullUpLoadListener = listener;
    }

    private OnPullUpLoadListener mOnPullUpLoadListener;

    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // Start a new loading when the last item scrolls into screen, instead of overriding method onTouchEvent.
            // 检查是否到listView底部,检查callbacks是否注册:
            if (needLoad(...)) {
                startPullUpLoad();
            }
        }
    };
}

startPullUpLoad() 是listView 处理上拉加载的核心代码:秀出显示『Loading…』的footerView;设置标志位表示『已经处于上拉加载状态中』防止重复加载;回调。

private void startPullUpLoad() {
    if (mOnPullUpLoadListener != null) {
        // Show the foot view and update its state to LOADING.
        showFooterView();
        // Set flag
        mIsPullUpLoading = true;
        // Call the callback to notify the listView's hosted controller to load data.
        mOnPullUpLoadListener.onPullUpLoading();
    }
}

再由 Activity 实现该接口完成加载工作:

listView.setOnPullUpLoadListener(new PullUpLoadListView.OnPullUpLoadListener() {
    @Override
    public void onPullUpLoading() {
        if (shouldLoadMore) {
            // Loading more data
            new LoadDataAsyncTask().execute();
        } else {
            // Already has no more data
            // 下面会讲到这个方法
            listView.onPullUpLoadFinished(true);
        }
    }
});

数据加载完毕后,如何更新视图?

ListView 提供一个public方法,根据数据加载的结果更新视图:

public class PullUpLoadListView extends ListView {
    // When loading finished, the controller should call this public method to update footer view.
    public void onPullUpLoadFinished(boolean hasNoMoreItems) {
        // Clear flag
        mIsPullUpLoading = false;

        if (hasNoMoreItems) {
            // when have no more items, update footer view to: NO MORE
            mFooterView.updateView(PullUpLoadListViewFooter.State.NOT_LOADING, FOOTER_VIEW_CONTENT_NO_MORE);
        } else {
            // The other cases: (1)Loading succeed and still has more items, (2)Loading failed,
            // should hide footer view.
            hideFooterView();
        }
    }
}

Activity 完成加载数据后,调用 ListView 提供的方法,并更新 adapter 数据集:

listView.onPullUpLoadFinished(false);
// Add more data to adapter and notify data set changed to update listView.
adapter.addMoreItems(newItems);

源码实现总结

上述实现基于 nicolasjafelle/PagingListView,对 PagingListView.java 做了两处较大改动:

  1. 数据加载完成后,由PagingListView负责更新adapter,考虑到ListView可能并不清楚adapter的接口,所以还是交给activity比较好

     // PagingListView的实现
     public void onFinishLoading(boolean hasMoreItems, List<? extends Object> {
         ...
         ((PagingBaseAdapter) adapter).addMoreItems(newItems);
     }
    
  2. PagingListView维护了一个私有成员boolean hasMoreItems,然后在滚动事件回调onScroll(...)中,如果该值为false,就不会加载更多数据。

我觉得不应该由ListView来维护『是否具有更多的item』,这样会带来一些困惑和额外的工作。比如该值为false的情况下,当外部清空list item后,必须重置 hasMoreItems,否则无法继续加载。

这样逻辑显得比较乱,而『是否可以加载更多』,应该分成两部分:

  • 由 ListView 判断『没有处于加载状态』并且『已经滚到了最后一个条目』则允许加载;
  • 由 Activity 判断『还有更多的数据』则允许加载。

这样就显得清晰很多了。

你可以从这里获取源码:Learning_Android_Open_Source/Pull_Up_Load_ListView_Sample

如下的GIF演示了上拉加载的过程:

demo


知识共享许可协议
li2的博客WeiYi.Li 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议进行许可。
© 2011-2017. All rights reserved by WeiYi.Li. Powerd by Jekyll & LinAnYa's Theme