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.
publicclassPulmListViewextendsListView{
publicPulmListView(Contextcontext){
this(context,null);
}
publicPulmListView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicPulmListView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
//初始化
init();
}
}
只是实现ListView的三个构造函数还不够,我们需要ListView能够判断当前的ListView是否滑动到最后一个元素.
判断是否滑动到最后一个元素,我们可以通过为ListView设置OnScrollListener来实现.代码如下:
privatevoidinit(){
super.setOnScrollListener(newOnScrollListener(){
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
//调用用户设置的OnScrollListener
if(mUserOnScrollListener!=null){
mUserOnScrollListener.onScrollStateChanged(view,scrollState);
}
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
//调用用户设置的OnScrollListener
if(mUserOnScrollListener!=null){
mUserOnScrollListener.onScroll(view,firstVisibleItem,visibleItemCount,totalItemCount);
}
//firstVisibleItem是当前屏幕能显示的第一个元素的位置
//visibleItemCount是当前屏幕能显示的元素的个数
//totalItemCount是ListView包含的元素总数
intlastVisibleItem=firstVisibleItem+visibleItemCount;
if(!mIsLoading&&!mIsPageFinished&&lastVisibleItem==totalItemCount){
if(mOnPullUpLoadMoreListener!=null){
mIsLoading=true;
mOnPullUpLoadMoreListener.onPullUpLoadMore();
}
}
}
});
}
从代码注释可以知道,通过(firstVisibleItem+visibleItemCount)可以获取当前屏幕已经展示的元素个数,如果已经展示的元素个数等于ListView的元素总数,则此时可以认为ListView已经滑动到底部.
功能2(自定义的FooterView)
这里我们可以实现一个比较简单的FooterView,即加载更多的UI布局.例如我们可以展示一个ProgressBar和一行文字,具体代码如下:
/**
*加载更多的View布局,可自定义.
*/
publicclassLoadMoreViewextendsLinearLayout{
publicLoadMoreView(Contextcontext){
this(context,null);
}
publicLoadMoreView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicLoadMoreView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
init();
}
privatevoidinit(){
LayoutInflater.from(getContext()).inflate(R.layout.lv_load_more,this);
}
}
布局文件:
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns: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,并且在构造函数中将其实例化.
privateViewmLoadMoreView;
privatevoidinit(){
mLoadMoreView=newLoadMoreView(getContext());
}
第二,我们需要控制FooterView的显示和隐藏.考虑一下FooterView的显示和隐藏的时机:
•显示的时机:ListView处于最底部并且当前还有更多的数据需要加载.
•隐藏的时机:ListView结束完加载更多的操作.
为了判断当前是否还有数据需要加载,因此我们需要定义一个boolean变量mIsPageFinished,表示数据加载是否结束.
为了保证同一时间只进行一次数据加载过程,因此我们还需要定义一个boolean变量mIsLoading,表示当前是否已经处于数据加载状态.
明确了FooterView的显示和隐藏时机,也有了控制状态的变量,代码也就比较容易实现了.
显示时机:
privatevoidinit(){
mIsLoading=false;//初始化时没处于加载状态
mIsPageFinished=false;//初始化时默认还有更多数据需要加载
mLoadMoreView=newLoadMoreView(getContext());//实例化FooterView
super.setOnScrollListener(newOnScrollListener(){
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
//调用用户设置的OnScrollListener
if(mUserOnScrollListener!=null){
mUserOnScrollListener.onScrollStateChanged(view,scrollState);
}
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
//调用用户设置的OnScrollListener
if(mUserOnScrollListener!=null){
mUserOnScrollListener.onScroll(view,firstVisibleItem,visibleItemCount,totalItemCount);
}
intlastVisibleItem=firstVisibleItem+visibleItemCount;
//当处于ListView尾部且有更多数据需要加载且当前没有加载程序再进行中时,执行加载更多操作
if(!mIsLoading&&!mIsPageFinished&&lastVisibleItem==totalItemCount){
if(mOnPullUpLoadMoreListener!=null){
mIsLoading=true;//将加载更多进行时状态设置为true
showLoadMoreView();//显示加载更多布局
mOnPullUpLoadMoreListener.onPullUpLoadMore();//调用用户设置的加载更多回调接口
}
}
}
});
}
privatevoidshowLoadMoreView(){
//这里将加载更多的根布局id设置为id_load_more_layout,便于用户自定制加载更多布局.
if(findViewById(R.id.id_load_more_layout)==null){
addFooterView(mLoadMoreView);
}
}
隐藏时机:
/**
*加载更多结束后ListView回调方法.
*
*@paramisPageFinished分页是否结束
*@paramnewItems分页加载的数据
*@paramisFirstLoad是否第一次加载数据(用于配置下拉刷新框架使用,避免出现页面闪现)
*/
publicvoidonFinishLoading(booleanisPageFinished,List<?>newItems,booleanisFirstLoad){
mIsLoading=false;//标记当前已经没有加载更多的程序在执行
setIsPageFinished(isPageFinished);//设置分页是否结束标志并移除FooterView
}
privatevoidsetIsPageFinished(booleanisPageFinished){
mIsPageFinished=isPageFinished;
removeFooterView(mLoadMoreView);
}
功能4(上拉加载更多实现的回调接口)
这个比较简单,我们定义一个interface,便于回调用户真正的加载更多的实现方法.
/**
*上拉加载更多的回调接口
*/
publicinterfaceOnPullUpLoadMoreListener{
voidonPullUpLoadMore();
}
privateOnPullUpLoadMoreListenermOnPullUpLoadMoreListener;
/**
*设置上拉加载更多的回调接口.
*@paraml上拉加载更多的回调接口
*/
publicvoidsetOnPullUpLoadMoreListener(OnPullUpLoadMoreListenerl){
this.mOnPullUpLoadMoreListener=l;
}
功能5(加载更多的结束回调)
为了在PulmListView中维护数据集合,必须自定义一个Adapter,在Adapter中使用List存储数据集合,并提交增删的方法.
自定义的Adapter:
/**
*抽象的Adapter.
*/
publicabstractclassPulmBaseAdapter<T>extendsBaseAdapter{
protectedList<T>items;
publicPulmBaseAdapter(){
this.items=newArrayList<>();
}
publicPulmBaseAdapter(List<T>items){
this.items=items;
}
publicvoidaddMoreItems(List<T>newItems,booleanisFirstLoad){
if(isFirstLoad){
this.items.clear();
}
this.items.addAll(newItems);
notifyDataSetChanged();
}
publicvoidremoveAllItems(){
this.items.clear();
notifyDataSetChanged();
}
}
为什么在addMoreItems方法中要增加一个isFirstLoad变量呢?
是因为上拉加载更多通常要配合下拉刷新使用.而下拉刷新的过程中会牵扯到ListView的数据集合clear然后再addAll.如果没有isFirstLoad参数,那用户下拉刷新去更新ListView的数据集合就必须分为两步:
1.removeAllItems并进行notifyDataSetChanged.
2.addMoreItems并进行notifyDataSetChanged.
同一时间连续两次notifyDataSetChanged会导致屏幕闪屏,因此这里提交了一个isFirstLoad方法.当是第一次加载数据时,会先clear掉所有的数据,然后再addAll,最后再notify.
有了自定义的adapter,就可以写加载更多结束的回调函数了:
/**
*加载更多结束后ListView回调方法.
*
*@paramisPageFinished分页是否结束
*@paramnewItems分页加载的数据
*@paramisFirstLoad是否第一次加载数据(用于配置下拉刷新框架使用,避免出现页面闪现)
*/
publicvoidonFinishLoading(booleanisPageFinished,List<?>newItems,booleanisFirstLoad){
mIsLoading=false;
setIsPageFinished(isPageFinished);
//添加更新后的数据
if(newItems!=null&&newItems.size()>0){
PulmBaseAdapteradapter=(PulmBaseAdapter)((HeaderViewListAdapter)getAdapter()).getWrappedAdapter();
adapter.addMoreItems(newItems,isFirstLoad);
}
}
这里需要注意,当添加了FooterView或者HeaderView之后,我们无法通过listview.getAdapter拿到我们自定义的adapter,必须按照如下步骤:
PulmBaseAdapteradapter=(PulmBaseAdapter)((HeaderViewListAdapter)getAdapter()).getWrappedAdapter();
参考
1.PagingListView
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。