Android如何优雅地实现ListView加载更多功能(2)
来源:互联网 发布:手机直播字幕软件 编辑:程序博客网 时间:2024/06/05 06:25
利用ListView的分类型Item功能实现加载更多功能
上一篇博客Android如何优雅地实现ListView加载更多功能(1),我们介绍完了如何对ListView的Adapter进行抽取的过程,根据MVC编程规范,对getView方法的抽取,其中BaseHolder作为一个抽象类,主要提供Item的界面构造与数据填充功能,有效地将UI与数据解耦。这次我们将基于上一次的内容,利用ListView分类型Item来实现加载更多的功能。
3、加载更多Item的添加
分类型Item即一个ListView列表可以显示多种不同类型的Item布局结构,要显示多种不同类型的Item,主要是要重写BaseAdapter的getItemViewType和getViewTypeCount方法,其中getItemViewType方法是根据position索引位置返回当前Item的类型,getViewTypeCount方法是返回Item的类型的总数。
@Override public int getItemViewType(int position) { return super.getItemViewType(position); } @Override public int getViewTypeCount() { return super.getViewTypeCount(); }
我们重写这两个方法之前,首先定义好Item的类型:
private static final int TYPE_MOREITEM = 0;
private static final int TYPE_CONTENTITEM = TYPE_MOREITEM + 1;
如上,我定义了两种不同的Item类型,TYPE_MOREITEM 既是加载更多对应的Item,TYPE_CONTENTITEM 是普通的item类型。
接下来,因为我们添加了一种Item,我们就需要修改getCount方法,将返回的总数+1;
@Override public int getCount() { return mList.size() + 1; }
加的这个1,也就是加载更多这个Item条目。
紧接着,我们需要将刚刚我们重写的getItemViewType和getViewTypeCount方法返回正确的数据:
@Override public int getItemViewType(int position) { if (getCount() - 1 == position) { return TYPE_MOREITEM; } else { return TYPE_CONTENTITEM ; } }@Override public int getViewTypeCount() { return TYPE_CONTENTITEM + 1; }
因为,加载更多类型的Item条目,总是在ListView的最后一个位置,所以在getItemViewType中,当我们判断到position与数据集合元素的size一致时,返回加载更多类型Item对应的条目,其它情况返回普通的Item类型。
getViewTypeCount方法名的字面意思就是返回Item类型的总数,这个很简单,直接return目前最大的Item类型的下标加1,这是因为下标都是从0开始计数的。
好了,刚才我们定义了一种Item的类型是加载更多,接下来,我们需要在合适的时候,返回加载更多的View对象用于显示,当然就需要继续修改getView方法啦。
接着看,getView方法里面,上次我们当convertView为空时,我们返回的是BaseHolder的对象,这个对象,因为在getView方法里面无法确定下来,所以构建了一个抽象方法,子类需要实现。那么我们想想,加载更多的Holder在getView里面能确定下来么?答案是肯定的,因为,加载更多类型的item的界面布局是通用的,UI设计师,肯定是做成一样的,所以我们这时候,需要创建一个加载更多的Holder类,我把这个类叫做MoreHolder,继承自BaseHolder。
那么,我们就需要考虑下,加载更多这个Item都会显示哪些内容呢?根据市面上的一些应用,我们发现,一般这个Item在不同的加载阶段会显示不一样的状态,我总结了一下,一般可以概括为一下几种:
- 加载更多(上拉过程中,listView显示完成最后一条有效数据时,紧接着显示加载更多的View)
- 加载中(数据请求的过程显示此View)
- 加载失败(数据请求失败,包括服务端返回404或者网络出现错误导致请求失败的情况)
- 加载成功(数据请求成功)
- 没有更多内容(服务器没有更多内容时显示)
那么首先,我们创建出来这个MoreHolder类,接着定义出这5中状态:
public static final int STATE_LOADMORE = 0x00; //加载更多 public static final int STATE_LOADING = 0x01; //加载中 public static final int STATE_LOADED = 0x02; //加载完成 public static final int STATE_NOMORE = 0x03; //没有更多数据 public static final int STATE_ERROR = 0x04; //加载失败
并创建出相对用的View,我在initView方法中,实现了界面的布局:
@Override public View initView() { layout = new LinearLayout(mContext); layout.setOrientation(LinearLayout.VERTICAL); layout.setGravity(Gravity.CENTER); layout.setClickable(false); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); params.gravity = Gravity.CENTER; loadMoreView = new TextView(mContext); loadMoreView.setText("加载更多"); loadMoreView.setPadding(40,40,40,40); loadMoreView.setGravity(Gravity.CENTER); loadMoreView.setVisibility(View.GONE); loadingView = new TextView(mContext); loadingView.setText("正在加载中"); loadingView.setGravity(Gravity.CENTER); loadingView.setVisibility(View.GONE); loadingView.setPadding(40,40,40,40); loadedView = new TextView(mContext); loadedView.setText("加载成功"); loadedView.setPadding(40,40,40,40); loadedView.setGravity(Gravity.CENTER); loadedView.setVisibility(View.GONE); noMoreView = new TextView(mContext); noMoreView.setText("------我是有底线的------"); noMoreView.setGravity(Gravity.CENTER); noMoreView.setPadding(40,40,40,40); noMoreView.setVisibility(View.GONE); loadingErrorView = new TextView(mContext); loadingErrorView.setText("加载失败,点击重试"); loadingErrorView.setPadding(40,40,40,40); loadingErrorView.setGravity(Gravity.CENTER); loadingErrorView.setVisibility(View.GONE); loadingErrorView.setOnClickListener(this); layout.addView(loadMoreView, params); layout.addView(loadingView, params); layout.addView(loadedView, params); layout.addView(noMoreView, params); layout.addView(loadingErrorView, params); return layout; }
因为,我没有创建layout布局文件,在这里,我用纯Java代码写的布局,这样反而方便,因为大部分情况,这个布局是非常简单的,特意写一个布局,我认为是没有必要的。上面的代码,我用最简单的TextView创建出了这5中不同状态下的View界面。并把它们统一加在一个线性布局中,然后返回这个LinearLayout。
现在回头想想,前面说了,这个Item可能存在5种不同的状态,所以我们还需要定义一个成员变量,记录当前加载更多这个Item的状态,是否需要提供set方法呢?不需要,为什么呢?不要忘了,我们的BaseHolder中有一个setData的方法,可以借这个方法来为当前状态赋值。那么我们先定义出来,这时候,我们MoreHolder的泛型是不是可以确定下来了呢,对,我们要给Integer类型做为限制:
private int currentState = STATE_LOADMORE;
下面,我们实现initData方法:
@Override public void initData() { currentState = getData(); }
这时候,我们就已经给currentState赋值了,我们目前其实还差一个根据状态来控制5种状态的View对象显示隐藏的方法,我们也定义出来:
private void refleshView() { loadMoreView.setVisibility(currentState == STATE_LOADMORE ? View.VISIBLE : View.GONE); loadingView.setVisibility(currentState == STATE_LOADING ? View.VISIBLE : View.GONE); loadedView.setVisibility(currentState == STATE_LOADED ? View.VISIBLE : View.GONE); noMoreView.setVisibility(currentState == STATE_NOMORE ? View.VISIBLE : View.GONE); loadingErrorView.setVisibility(currentState == STATE_ERROR ? View.VISIBLE : View.GONE); }
这个方法不对外暴露,给private就可以了。这个方法在initView就需要调用一次,初始化加载更多的Item为STATE_LOADMORE 状态对应的View对象。initData时,还需要调用,状态改变了,用于刷新当前的Item的View。
创建完了MoreHolder类之后,我们再回来AnzhiBaseAdapter中的getView方法,当convertView为空时,我们要根据position的位置,调用getItemViewType方法来判断Item的类型,当确定是加载更多这一类型的Item时,我们就创建一个MoreHolder对象,但是,这个对象,我们只需要创建一次,所以,我们可以创建一个方法getMoreHolder,用于获取MoreHolder的对象,代码如下:
private MoreHolder getMoreHolder() { if (moreHolder == null) { moreHolder = new MoreHolder(mContext, isHasMore(), this); } return moreHolder; }
Holder创建好了 ,接下来就应该设置数据了,但是,我们不应该给MoreHolder设置数据,这是为什么呢?我们想想,上拉加载更多的时候,是不是有一个请求网络的过程,请求网络又是一个耗时并且不确定结果的操作。那么,我们不可能在这是为MoreHolder设置数据,所以,需要做一个判断,当getItemViewType的类型是一般类型条目,即TYPE_CONTENTITEM类型时,我们才设置数据。所以getView里面的方法体我们也分析完了。下面给出代码:
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { if (getItemViewType(position) == TYPE_MOREITEM) { itemHolder = getMoreHolder(); } else { itemHolder = getHolder(); } } else { itemHolder = (BaseHolder) convertView.getTag(); } if (getItemViewType(position) == TYPE_CONTENTITEM) { itemHolder.setData(getItem(position)); } return itemHolder.getItemView(); }
现在我们其实运行程序的话,到了最后一个item时,显示的就是加载更多的item,但是,我们还没有写完,显示出加载更多时,我们需要立即做的事是请求网络,然后获得数据,添加到集合的list中,然后再notify通知ListView刷新列表。我们下面看下,这个过程,该如何实现?
4、加载更多功能实现
首先考虑一个问题,当ListView滑动到最后一个Item的时候,在MoreHolder里面我们知不知道?答案是肯定的,因为滑动到最后一个Item的时候,需要调用BaseHolder里面的getItemView这个方法返回一个View给ListView显示。那么,我们就可以考虑在这个方法里面来加载数据。但是还有一个问题,我们的这个MoreHolder针对的是整个APP的ListView的一个统一的通用的Holder,考虑到,应用中,需要用列表分页展示的ListView有很多,而且,他们请求的URL也不一致,那么,我们就没法在MoreHolder里面确定下来请求的URL,如何解决这个问题?
我们可以这样,在MoreHolder的构造方法里面传递一个AnzhiBaseAdapter的对象,如何把加载更多的数据的方法,定义在AnzhiBaseAdapter中,姑且叫它为loadMore吧。在这个方法里面,我们可以去请求网络加载更多数据,然后根据请求结果,给MoreHolder设置currentState的状态,在请求的过程中,动态的去刷新这个item显示的内容。但是依然存在一个问题,AnzhiBaseAdapter还是一个抽象的父类,无法确定具体的请求URL,所以,我们可以这样做:
public void loadMore() { new Thread(new Runnable() { @Override public void run() { ((Activity) mContext).runOnUiThread(new Runnable() { @Override public void run() { getMoreHolder().setData(MoreHolder.STATE_LOADING); } }); SystemClock.sleep(3000); final List<T> list = onLoadMore(); ((Activity) mContext).runOnUiThread(new Runnable() { @Override public void run() { if (list != null) { if (list.size() == LISTSIZE) { getMoreHolder().setData(MoreHolder.STATE_LOADED); SystemClock.sleep(300); getMoreHolder().setData(MoreHolder.STATE_LOADMORE); } else if (list.size() < LISTSIZE) { getMoreHolder().setData(MoreHolder.STATE_NOMORE); } } else { getMoreHolder().setData(MoreHolder.STATE_ERROR); } if (list != null) { mList.addAll(list); notifyDataSetChanged(); } } }); } }).start(); }
这里,我们定义了一个常量LISTSIZE用来维护ListView分页的条数,这个值可以依照不同公司的具体需求更改。
因为请求网络是一个耗时操作,不能放在主线程中执行,我们开了一个线程,线程里面,我们又调用了一个方法onLoadMore,这是一个抽象方法,由子类去实现具体的加载更多的请求操作,我们在这里,只需要关注这个方法返回的list集合的结果,这个请求结果跟我们在MoreHolder中定义的状态值应该是基本吻合的。我们知道,MoreHolder的状态,我已经设置为STATE_LOADMORE了,在开始加载之前,我们为MoreHolder赋了一个STATE_LOADING状态,变为加载中,然后我们调用onLoadMore方法,接着我们判断了返回的list是否为空,当为空时,这时候可能是网络出现错误导致的异常,我们为MoreHolder赋了一个STATE_ERROR状态,当list的size等于LISTSIZE时,默认服务端依然有更多数据,所以,先将MoreHolder的状态赋值为STATE_LOADED,然后给了一个300毫秒的停顿,我们再次将MoreHolder赋了一个STATE_LOADMORE状态,表示下一次还能去服务器请求更多数据。当list的size小于20时,我们判定服务端没有更多数据了,不需要继续去请求更多,这时候,我们将MoreHolder赋了一个STATE_NOMORE状态。
最后,当list不为空的时候,我们还需要将list添加到listView的数据集合中,然后再通知ListView数据集有变化刷新界面。
其实我们返回list的话,不是一个好的选择,最好是根据请求返回的code码来判断,这里我们没有服务器,无法模拟,朋友们自己去实现。
以上就讲解完了,接下来,我们需要把MoreHolder与AnzhiBaseHolder串起来,我们刚刚谈到,在getView的时候,生成MoreHolder对象,会调用getItemView方法,我们传递了一个AnzhiBaseAdapter的对象给MoreHolder,所以,我们在MoreHolder里面需要重写getItemView方法。下面也直接给出代码:
@Override public View getItemView() { if (getData() == STATE_LOADMORE && baseAdapter != null) { baseAdapter.loadMore(); } return super.getItemView(); }
接下来就是一些小细节的调整,我们这个是一个成熟的框架,必须要考虑到一些细节方面的东西,比如最后一个加载更多的item希望是不可点击的,但是当加载失败的时候,又需要点击重新加载,还有,当新建的ListView就不需要加载更多功能的时候,如何关闭加载更多Item的显示之类的,我们在这里就不做进一步的讲解,直接给出代码,以供参考。
public class MoreHolder extends BaseHolder<Integer> implements View.OnClickListener { public static final int STATE_LOADMORE = 0x00; public static final int STATE_LOADING = 0x01; public static final int STATE_LOADED = 0x02; public static final int STATE_NOMORE = 0x03; public static final int STATE_ERROR = 0x04; private int currentState = STATE_LOADMORE; private TextView loadMoreView; private TextView loadingView; private TextView loadedView; private TextView noMoreView; private TextView loadingErrorView; private AnzhiBaseAdapter baseAdapter; private LinearLayout layout; public MoreHolder(Context mContext, boolean isHasMore, AnzhiBaseAdapter baseAdapter) { super(mContext); setData(isHasMore ? STATE_LOADMORE : STATE_NOMORE); this.baseAdapter = baseAdapter; } @Override public View initView() { layout = new LinearLayout(mContext); layout.setOrientation(LinearLayout.VERTICAL); layout.setGravity(Gravity.CENTER); layout.setClickable(false); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); params.gravity = Gravity.CENTER; loadMoreView = new TextView(mContext); loadMoreView.setText("加载更多"); loadMoreView.setPadding(40,40,40,40); loadMoreView.setGravity(Gravity.CENTER); loadMoreView.setVisibility(View.GONE); loadingView = new TextView(mContext); loadingView.setText("正在加载中"); loadingView.setGravity(Gravity.CENTER); loadingView.setVisibility(View.GONE); loadingView.setPadding(40,40,40,40); loadedView = new TextView(mContext); loadedView.setText("加载成功"); loadedView.setPadding(40,40,40,40); loadedView.setGravity(Gravity.CENTER); loadedView.setVisibility(View.GONE); noMoreView = new TextView(mContext); noMoreView.setText("------我是有底线的------"); noMoreView.setGravity(Gravity.CENTER); noMoreView.setPadding(40,40,40,40); noMoreView.setVisibility(View.GONE); loadingErrorView = new TextView(mContext); loadingErrorView.setText("加载失败,点击重试"); loadingErrorView.setPadding(40,40,40,40); loadingErrorView.setGravity(Gravity.CENTER); loadingErrorView.setVisibility(View.GONE); loadingErrorView.setOnClickListener(this); layout.addView(loadMoreView, params); layout.addView(loadingView, params); layout.addView(loadedView, params); layout.addView(noMoreView, params); layout.addView(loadingErrorView, params); refleshView(); return layout; } private void refleshView() { loadMoreView.setVisibility(currentState == STATE_LOADMORE ? View.VISIBLE : View.GONE); loadingView.setVisibility(currentState == STATE_LOADING ? View.VISIBLE : View.GONE); loadedView.setVisibility(currentState == STATE_LOADED ? View.VISIBLE : View.GONE); noMoreView.setVisibility(currentState == STATE_NOMORE ? View.VISIBLE : View.GONE); loadingErrorView.setVisibility(currentState == STATE_ERROR ? View.VISIBLE : View.GONE); } public int getCurrentState() { return currentState; } @Override public void initData() { currentState = getData(); refleshView(); } @Override public View getItemView() { if (getData() == STATE_LOADMORE && baseAdapter != null) { baseAdapter.loadMore(); } return super.getItemView(); } @Override public void onClick(View view) { if (view == loadingErrorView && getData() == STATE_ERROR && baseAdapter != null) { baseAdapter.loadMore(); } }}
public abstract class AnzhiBaseAdapter<T> extends BaseAdapter { private static final int TYPE_MOREITEM = 0; private static final int TYPE_CONTENTITEM = TYPE_MOREITEM + 1; private boolean hasMore = false; protected static final int LISTSIZE = 20; private List<T> mList; public Context mContext; private BaseHolder itemHolder; private MoreHolder moreHolder; public AnzhiBaseAdapter(Context context, List<T> list) { this.mList = list; this.mContext = context; hasMore = list.size() < LISTSIZE ? false : true; } public boolean isHasMore() { return hasMore; } @Override public int getItemViewType(int position) { if (hasMore && getCount() - 1 == position) { return TYPE_MOREITEM; } else { return getOtherItemViewType(); } } /** * 需要添加Item类型时 重写 * * @return */ public int getOtherItemViewType() { return TYPE_CONTENTITEM; } @Override public boolean isEnabled(int position) { if (getItemViewType(position) == TYPE_MOREITEM) { if (getMoreHolder().getCurrentState() == MoreHolder.STATE_ERROR) { return true; } return false; } return super.isEnabled(position); } @Override public int getViewTypeCount() { return TYPE_CONTENTITEM + 1; } @Override public int getCount() { return hasMore ? mList.size() + 1 : mList.size(); } @Override public T getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { if (getItemViewType(position) == TYPE_MOREITEM) { itemHolder = getMoreHolder(); } else { itemHolder = getHolder(); } } else { itemHolder = (BaseHolder) convertView.getTag(); } if (getItemViewType(position) == TYPE_CONTENTITEM) { itemHolder.setData(getItem(position)); } return itemHolder.getItemView(); } private MoreHolder getMoreHolder() { if (moreHolder == null) { moreHolder = new MoreHolder(mContext, isHasMore(), this); } return moreHolder; } public abstract BaseHolder<T> getHolder(); public void loadMore() { new Thread(new Runnable() { @Override public void run() { ((Activity) mContext).runOnUiThread(new Runnable() { @Override public void run() { getMoreHolder().setData(MoreHolder.STATE_LOADING); } }); SystemClock.sleep(3000); final List<T> list = onLoadMore(); ((Activity) mContext).runOnUiThread(new Runnable() { @Override public void run() { if (list != null) { if (list.size() == LISTSIZE) { getMoreHolder().setData(MoreHolder.STATE_LOADED); SystemClock.sleep(300); getMoreHolder().setData(MoreHolder.STATE_LOADMORE); } else if (list.size() < LISTSIZE) { getMoreHolder().setData(MoreHolder.STATE_NOMORE); mList.addAll(list); notifyDataSetChanged(); } } else { getMoreHolder().setData(MoreHolder.STATE_ERROR); } } }); } }).start(); } protected abstract List<T> onLoadMore();}
到现在,我们就已经完成了ListView的加载更多的功能了,写一个demo测试一下,demo代码不贴了吧 篇幅太长,我们看下最终效果
- Android如何优雅地实现ListView加载更多功能(2)
- Android如何优雅地实现ListView加载更多功能(1)
- android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)
- android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)
- android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)
- 使用AndroidAnnotations框架优雅地实现ListView功能例子
- apicloud如何实现优雅的下拉刷新与加载更多(Appcan也可类似实现)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- Android UI--自定义ListView(实现下拉刷新+加载更多)
- 实现Android ListView 自动加载更多内容
- Android ListView实现下拉刷新、加载更多
- android-----ListView上拉加载更多实现
- 网页嵌入视频背景(mp4当作banner)
- iOS UICollectionView 按钮点击变色(收藏点赞功能)实现
- java——深入java.util包(collection接口之AbstractCollection)
- web.xml配置含义
- 继承CDialog 对话框需要改动的地方
- Android如何优雅地实现ListView加载更多功能(2)
- Day 2 CNN进阶之旅
- 深入理解awgn函数如何向信号中添加高斯白噪声
- Win7怎么运用组策略编辑器禁用命令提示符
- php 坐标转换
- Selenium2 IDE安装问题
- Linux系统引导流程
- node.js 使用 body-parser模块时,传输过来的数据出现undefind的情况
- 单机版 solr服务器安装到linux环境