前几天看了github上面的例子,参照它的实现,自己又稍微改了一点,往项目里面增加了一个上拉加载更多功能。具体的实现如下:

首先要重写ListView:

2 
  3 import android.content.Context;
  4 import android.util.AttributeSet;
  5 import android.widget.AbsListView;
  6 import android.widget.HeaderViewListAdapter;
  7 import android.widget.ListAdapter;
  8 import android.widget.ListView;
  9 
 10 import java.util.List;
 11 
 12 import njucm.edu.loadmore.activities.LoadingView;
 13 import njucm.edu.loadmore.adapters.PagingBaseAdapter;
 14 
 15 /**
 16  * Created by Mesogene on 10/9/15.
 17  */
 18 public class LoadListView extends ListView {
 19 
 20     private OnScrollListener onScrollListener          = null;
 21     private PageEnableListener pageEnableListener      = null;
 22     LoadingView loadListView                          = null;
 23     private boolean isLoading;
 24     private boolean hasMoreItem;
 25     private int lastVisibleItem;                                //最后一个可见的项
 26     private int totalItemCount;                                 //总的项数
 27 
 28 
 29     public LoadListView(Context context) {
 30         super(context);
 31         init();
 32     }
 33 
 34     public LoadListView(Context context, AttributeSet attrs) {
 35         super(context, attrs);
 36         init();
 37     }
 38 
 39     public LoadListView(Context context, AttributeSet attrs, int defStyleAttr) {
 40         super(context, attrs, defStyleAttr);
 41         init();
 42     }
 43 
 44     public void setPageEnableListener(PageEnableListener pageEnableListener) {
 45         this.pageEnableListener = pageEnableListener;
 46     }
 47 
 48     public boolean isLoading() {
 49         return isLoading;
 50     }
 51 
 52     public void setIsLoading(boolean isLoading) {
 53         this.isLoading = isLoading;
 54     }
 55 
 56     public boolean isHasMoreItem() {
 57         return hasMoreItem;
 58     }
 59 
 60     public void setHasMoreItem(boolean hasMoreItem) {
 61         this.hasMoreItem = hasMoreItem;
 62         if(!this.hasMoreItem){  //如果没有更多项,移除底部
 63             removeFooterView(loadListView);
 64         }
 65         else if(findViewById(R.id.loading_view) == null){
 66             addFooterView(loadListView);
 67             ListAdapter adapter = ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
 68             setAdapter(adapter);
 69         }
 70     }
 71 
 72     /**
 73      * 在下载任务完成之后去调用这个方法
 74      * @param hasMoreItem  是否还有更多项
 75      * @param newItems  新的项
 76      */
 77     public void onFinsihLoading(boolean hasMoreItem, List<? extends Object> newItems){
 78         setHasMoreItem(hasMoreItem);
 79         setIsLoading(false);  //下载任务完成后,把loading设置成false
 80         if(newItems != null && newItems.size() >0){
 81             ListAdapter adapter = ((HeaderViewListAdapter)getAdapter()).getWrappedAdapter();  //获取这个listview的adapter
 82             if(adapter instanceof PagingBaseAdapter){
 83                 ((PagingBaseAdapter) adapter).addMoreItems(newItems);  //添加项目,包含notify方法
 84             }
 85         }
 86     }
 87     /**
 88      * 初始化listview的操作
 89      */
 90     private void init(){
 91         isLoading = false;
 92         loadListView = new LoadingView(getContext());
 93         addFooterView(loadListView);
 94         super.setOnScrollListener(new OnScrollListener() {
 95             @Override
 96             public void onScrollStateChanged(AbsListView absListView, int scrollState) {
 97                 if(onScrollListener != null){
 98                     onScrollListener.onScrollStateChanged(absListView, scrollState);
 99                 }
100                 /**
101                  * 当你的listview移动到底部的时候,即你看到的最后一项等于总的项数,已经停止滚动 没有正在加载并且还有更多项
102                  * 的时候会被执行
103                  */
104                 if(lastVisibleItem == totalItemCount && scrollState == SCROLL_STATE_IDLE && !isLoading && hasMoreItem){
105                     if(pageEnableListener != null){
106                         isLoading = true;  //执行之后的状态就是loading
107                         pageEnableListener.onLoadMoreItems();  //调用回调方法
108                     }
109                 }
110             }
111 
112             @Override
113             public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totleItem) {
114                 //Dispatch to child OnScrollListener
115                 if (onScrollListener != null) {
116                     onScrollListener.onScroll(absListView, firstVisibleItem, visibleItemCount, totleItem);
117                 }
118                 lastVisibleItem = firstVisibleItem + visibleItemCount;  //最后看到的一项
119                 totalItemCount  = totleItem;  //总的项数
120             }
121         });
122 
123     }
124 
125     @Override
126     public void setOnScrollListener(OnScrollListener onScrollListener) {
127         this.onScrollListener = onScrollListener;
128     }
129 
130     public interface PageEnableListener{
131         public void onLoadMoreItems();
132     }
133 
134 }

我们可以看到还要加一个loadingview的,就是你的正在加载界面,这个界面会被动态添加到你的footview里面的:

1 package njucm.edu.loadmore.activities;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.widget.LinearLayout;
 6 
 7 import njucm.edu.loadmore.R;
 8 
 9 /**
10  * Created by Mesogene on 10/10/15.
11  */
12 public class LoadingView extends LinearLayout {
13 
14     public LoadingView(Context context) {
15         super(context);
16         init();
17     }
18 
19     public LoadingView(Context context, AttributeSet attrs) {
20         super(context, attrs);
21         init();
22     }
23 
24     public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
25         super(context, attrs, defStyleAttr);
26         init();
27     }
28 
29     public void init(){
30         inflate(getContext(), R.layout.loadinflinear, this);
31     }
32 
33 }

只是简单重写了一下LinearLayout, 下面只要写一个loading的不局文件就行了

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:layout_margin="@dimen/dp_10"
    android:id="@+id/loading_view">

    <ProgressBar
        android:id="@+id/video_item_image"
        style="?android:progressBarStyle"
        android:layout_width="@dimen/loading_view_progress_size"
        android:layout_height="@dimen/loading_view_progress_size"
        android:layout_marginRight="@dimen/loading_view_margin_right"
        android:indeterminate="true"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/loading"
        android:layout_gravity="center"
        android:textColor="@color/material_blue_grey_800"
        android:textSize="@dimen/sp_18"/>

</LinearLayout>

这样基本就已经好了,那么还有一个listview的父类的适配器要自己写一下:

1 import android.widget.BaseAdapter;
 2 import android.widget.ListAdapter;
 3 
 4 import java.util.ArrayList;
 5 import java.util.List;
 6 
 7 import njucm.edu.loadmore.LoadListView;
 8 
 9 /**
10  * Created by Mesogene on 10/12/15.
11  */
12 public abstract class PagingBaseAdapter<T> extends BaseAdapter {
13 
14     protected List<T> items  = null;
15 
16     public PagingBaseAdapter(List<T> items) {
17         this.items = items;
18     }
19 
20     public PagingBaseAdapter(){
21         this.items = new ArrayList<>();
22     }
23 
24     public void addMoreItems(List<T> items){
25         this.items.addAll(items);  //把新的项添加到listview里面
26         notifyDataSetChanged();  //更新布局
27     }
28 
29 }

这样之后,你自己的listviewAdapter就可以继承这个类,你的adapter拥有绘制每一个listitem的功能和添加下一页数据项的功能。

1 package njucm.edu.loadmore.adapters;
 2 
 3 import android.view.LayoutInflater;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.TextView;
 7 
 8 import java.util.List;
 9 
10 /**
11  * Created by Mesogene on 10/12/15.
12  */
13 public class MyListAdapter extends PagingBaseAdapter<String> {
14 
15     @Override
16     public int getCount() {
17         return items.size();
18     }
19 
20     @Override
21     public String getItem(int position) {
22         return items.get(position);
23     }
24 
25     @Override
26     public long getItemId(int position) {
27         return position;
28     }
29 
30     @Override
31     public View getView(int position, View view, ViewGroup viewGroup) {
32         TextView textView;
33         String text = getItem(position);
34         if(view != null){
35             textView = (TextView) view;
36         }else {
37             textView = (TextView) LayoutInflater.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1, null);
38         }
39         textView.setText(text);
40         return textView;
41     }
42 }

最后就是如何使用了,再onCreate() 或者 onCreateView() 添加如下代码,我们发现它有一个异步加载的过程,使用到了线程

1 li.setAdapter(adapter);
 2 li.setHasMoreItem(true);
 3 li.setPageEnableListener(new LoadListView.PageEnableListener() {
 4             @Override
 5             public void onLoadMoreItems() {
 6                 if(pager < 3){
 7                     new CountryAsyncTask().execute();
 8                 }else{
 9                     li.onFinsihLoading(false, null);
10                 }
11             }
12         });
1 private class CountryAsyncTask extends SafeAsyncTask<List<String>>{
 2         @Override
 3         public List<String> call() throws Exception {  //模拟后台下载数据
 4             List result = null;
 5             switch (pager){
 6                 case 0:
 7                     result = firstList;
 8                     break;
 9                 case 1:
10                     result = secondList;
11                     break;
12                 case 2:
13                     result = thirdList;
14                     break;
15             }
16             Thread.sleep(3000);
17             return result;
18         }
19 
20         @Override
21         protected void onSuccess(List<String> strings) throws Exception {
22             super.onSuccess(strings);
23             pager++;
24             li.onFinsihLoading(true, strings);  //下载成功之后调用的方法,更新UI
25         }
26     }

这里面可能要自己添加一些数据在firstlist等里面。  还有下面是这个类似于AsyncTask但又不是的,这个类的代码如下

1 package njucm.edu.loadmore.activities;
  2 
  3 import android.os.Handler;
  4 import android.os.Looper;
  5 import android.util.Log;
  6 
  7 import java.io.InterruptedIOException;
  8 import java.util.ArrayList;
  9 import java.util.Arrays;
 10 import java.util.concurrent.Callable;
 11 import java.util.concurrent.CountDownLatch;
 12 import java.util.concurrent.Executor;
 13 import java.util.concurrent.Executors;
 14 import java.util.concurrent.FutureTask;
 15 
 16 
 17 /**
 18  * A class similar but unrelated to android's {@link android.os.AsyncTask}.
 19  * <p/>
 20  * Unlike AsyncTask, this class properly propagates exceptions.
 21  * <p/>
 22  * If you're familiar with AsyncTask and are looking for {@link android.os.AsyncTask#doInBackground(Object[])},
 23  * we've named it {@link #call()} here to conform with java 1.5's {@link java.util.concurrent.Callable} interface.
 24  * <p/>
 25  * Current limitations: does not yet handle progress, although it shouldn't be
 26  * hard to add.
 27  * <p/>
 28  * If using your own executor, you must call future() to get a runnable you can execute.
 29  *
 30  * @param <ResultT>
 31  */
 32 public abstract class SafeAsyncTask<ResultT> implements Callable<ResultT> {  //Callable可以返回任意类型。1.5 以后加入
 33     public static final int DEFAULT_POOL_SIZE = 25;  //默认的线程池的大小是25
 34     protected static final Executor DEFAULT_EXECUTOR = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);  //利用线程池
 35 
 36     protected Handler handler;
 37     protected Executor executor;
 38     protected StackTraceElement[] launchLocation;
 39     protected FutureTask<Void> future;
 40 
 41 
 42     /**
 43      * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE) and
 44      * Handler to new Handler()
 45      */
 46     public SafeAsyncTask() {
 47         this.executor = DEFAULT_EXECUTOR;
 48     }
 49 
 50     /**
 51      * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE)
 52      */
 53     public SafeAsyncTask(Handler handler) {
 54         this.handler = handler;
 55         this.executor = DEFAULT_EXECUTOR;  //线程池用默认的设置
 56     }
 57 
 58     /**
 59      * Sets Handler to new Handler()
 60      */
 61     public SafeAsyncTask(Executor executor) {
 62         this.executor = executor;
 63     }
 64 
 65     public SafeAsyncTask(Handler handler, Executor executor) {
 66         this.handler = handler;
 67         this.executor = executor;
 68     }
 69 
 70 
 71     public FutureTask<Void> future() {
 72         future = new FutureTask<Void>(newTask());
 73         return future;
 74     }
 75 
 76     public SafeAsyncTask<ResultT> executor(Executor executor) {
 77         this.executor = executor;
 78         return this;
 79     }
 80 
 81     public Executor executor() {
 82         return executor;
 83     }
 84 
 85     public SafeAsyncTask<ResultT> handler(Handler handler) {
 86         this.handler = handler;
 87         return this;
 88     }
 89 
 90     public Handler handler() {
 91         return handler;
 92     }
 93 
 94     public void execute() {
 95         execute(Thread.currentThread().getStackTrace());
 96     }
 97 
 98     protected void execute(StackTraceElement[] launchLocation) {
 99         this.launchLocation = launchLocation;
100         executor.execute(future());
101     }
102 
103     public boolean cancel(boolean mayInterruptIfRunning) {
104         if (future == null) throw new UnsupportedOperationException("You cannot cancel this task before calling future()");
105 
106         return future.cancel(mayInterruptIfRunning);
107     }
108 
109 
110     /**
111      * @throws Exception, captured on passed to onException() if present.
112      */
113     protected void onPreExecute() throws Exception {
114     }
115 
116     /**
117      * @param t the result of {@link #call()}
118      * @throws Exception, captured on passed to onException() if present.
119      */
120     @SuppressWarnings({"UnusedDeclaration"})
121     protected void onSuccess(ResultT t) throws Exception {
122     }
123 
124     /**
125      * Called when the thread has been interrupted, likely because
126      * the task was canceled.
127      * <p/>
128      * By default, calls {@link #onException(Exception)}, but this method
129      * may be overridden to handle interruptions differently than other
130      * exceptions.
131      *
132      * @param e an InterruptedException or InterruptedIOException
133      */
134     protected void onInterrupted(Exception e) {
135         onException(e);
136     }
137 
138     /**
139      * Logs the exception as an Error by default, but this method may
140      * be overridden by subclasses.
141      *
142      * @param e the exception thrown from {@link #onPreExecute()}, {@link #call()}, or {@link #onSuccess(Object)}
143      * @throws RuntimeException, ignored
144      */
145     protected void onException(Exception e) throws RuntimeException {
146         onThrowable(e);
147     }
148 
149     protected void onThrowable(Throwable t) throws RuntimeException {
150         Log.e("roboguice", "Throwable caught during background processing", t);
151     }
152 
153     /**
154      * @throws RuntimeException, ignored
155      */
156     protected void onFinally() throws RuntimeException {
157     }
158 
159 
160     protected Task<ResultT> newTask() {
161         return new Task<ResultT>(this);
162     }
163 
164 
165     public static class Task<ResultT> implements Callable<Void> {
166         protected SafeAsyncTask<ResultT> parent;
167         protected Handler handler;
168 
169         public Task(SafeAsyncTask<ResultT> parent) {
170             this.parent = parent;
171             this.handler = parent.handler != null ? parent.handler : new Handler(Looper.getMainLooper());
172         }
173 
174         public Void call() throws Exception {
175             try {
176                 doPreExecute();
177                 doSuccess(doCall());
178 
179             } catch (final Exception e) {
180                 try {
181                     doException(e);
182                 } catch (Exception f) {
183                     // logged but ignored
184                     Log.e("BACKGROUND_TASK", "Exception in", f);
185                 }
186 
187             } catch (final Throwable t) {
188                 try {
189                     doThrowable(t);
190                 } catch (Exception f) {
191                     // logged but ignored
192                     Log.e("BACKGROUND_TASK", "Exception in", f);
193                 }
194             } finally {
195                 doFinally();
196             }
197 
198             return null;
199         }
200 
201         protected void doPreExecute() throws Exception {
202             postToUiThreadAndWait(new Callable<Object>() {
203                 public Object call() throws Exception {
204                     parent.onPreExecute();
205                     return null;
206                 }
207             });
208         }
209 
210         protected ResultT doCall() throws Exception {
211             return parent.call();
212         }
213 
214         protected void doSuccess(final ResultT r) throws Exception {
215             postToUiThreadAndWait(new Callable<Object>() {
216                 public Object call() throws Exception {
217                     parent.onSuccess(r);
218                     return null;
219                 }
220             });
221         }
222 
223         protected void doException(final Exception e) throws Exception {
224             if (parent.launchLocation != null) {
225                 final ArrayList<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
226                 stack.addAll(Arrays.asList(parent.launchLocation));
227                 e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
228             }
229             postToUiThreadAndWait(new Callable<Object>() {
230                 public Object call() throws Exception {
231                     if (e instanceof InterruptedException || e instanceof InterruptedIOException) parent.onInterrupted(e);
232                     else parent.onException(e);
233                     return null;
234                 }
235             });
236         }
237 
238         protected void doThrowable(final Throwable e) throws Exception {
239             if (parent.launchLocation != null) {
240                 final ArrayList<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
241                 stack.addAll(Arrays.asList(parent.launchLocation));
242                 e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
243             }
244             postToUiThreadAndWait(new Callable<Object>() {
245                 public Object call() throws Exception {
246                     parent.onThrowable(e);
247                     return null;
248                 }
249             });
250         }
251 
252         protected void doFinally() throws Exception {
253             postToUiThreadAndWait(new Callable<Object>() {
254                 public Object call() throws Exception {
255                     parent.onFinally();
256                     return null;
257                 }
258             });
259         }
260 
261 
262         /**
263          * Posts the specified runnable to the UI thread using a handler,
264          * and waits for operation to finish.  If there's an exception,
265          * it captures it and rethrows it.
266          *
267          * @param c the callable to post
268          * @throws Exception on error
269          */
270         protected void postToUiThreadAndWait(final Callable c) throws Exception {
271             final CountDownLatch latch = new CountDownLatch(1);
272             final Exception[] exceptions = new Exception[1];
273 
274             // Execute onSuccess in the UI thread, but wait
275             // for it to complete.
276             // If it throws an exception, capture that exception
277             // and rethrow it later.
278             handler.post(new Runnable() {
279                 public void run() {
280                     try {
281                         c.call();
282                     } catch (Exception e) {
283                         exceptions[0] = e;
284                     } finally {
285                         latch.countDown();
286                     }
287                 }
288             });
289 
290             // Wait for onSuccess to finish
291             latch.await();
292 
293             if (exceptions[0] != null) throw exceptions[0];
294 
295         }
296 
297     }
298 
299 }

好了,最简单的上拉加载就是这个样子了。我已经把它集成进了自己的项目里面。