前几天看了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 }
好了,最简单的上拉加载就是这个样子了。我已经把它集成进了自己的项目里面。