Android 上拉加载更多功能

2023-03-14,,

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

首先要重写ListView:

 import android.content.Context;
import android.util.AttributeSet;
import android.widget.AbsListView;
import android.widget.HeaderViewListAdapter;
import android.widget.ListAdapter;
import android.widget.ListView; import java.util.List; import njucm.edu.loadmore.activities.LoadingView;
import njucm.edu.loadmore.adapters.PagingBaseAdapter; /**
* Created by Mesogene on 10/9/15.
*/
public class LoadListView extends ListView { private OnScrollListener onScrollListener = null;
private PageEnableListener pageEnableListener = null;
LoadingView loadListView = null;
private boolean isLoading;
private boolean hasMoreItem;
private int lastVisibleItem; //最后一个可见的项
private int totalItemCount; //总的项数 public LoadListView(Context context) {
super(context);
init();
} public LoadListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} public LoadListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} public void setPageEnableListener(PageEnableListener pageEnableListener) {
this.pageEnableListener = pageEnableListener;
} public boolean isLoading() {
return isLoading;
} public void setIsLoading(boolean isLoading) {
this.isLoading = isLoading;
} public boolean isHasMoreItem() {
return hasMoreItem;
} public void setHasMoreItem(boolean hasMoreItem) {
this.hasMoreItem = hasMoreItem;
if(!this.hasMoreItem){ //如果没有更多项,移除底部
removeFooterView(loadListView);
}
else if(findViewById(R.id.loading_view) == null){
addFooterView(loadListView);
ListAdapter adapter = ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
setAdapter(adapter);
}
} /**
* 在下载任务完成之后去调用这个方法
* @param hasMoreItem 是否还有更多项
* @param newItems 新的项
*/
public void onFinsihLoading(boolean hasMoreItem, List<? extends Object> newItems){
setHasMoreItem(hasMoreItem);
setIsLoading(false); //下载任务完成后,把loading设置成false
if(newItems != null && newItems.size() >0){
ListAdapter adapter = ((HeaderViewListAdapter)getAdapter()).getWrappedAdapter(); //获取这个listview的adapter
if(adapter instanceof PagingBaseAdapter){
((PagingBaseAdapter) adapter).addMoreItems(newItems); //添加项目,包含notify方法
}
}
}
/**
* 初始化listview的操作
*/
private void init(){
isLoading = false;
loadListView = new LoadingView(getContext());
addFooterView(loadListView);
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if(onScrollListener != null){
onScrollListener.onScrollStateChanged(absListView, scrollState);
}
/**
* 当你的listview移动到底部的时候,即你看到的最后一项等于总的项数,已经停止滚动 没有正在加载并且还有更多项
* 的时候会被执行
*/
if(lastVisibleItem == totalItemCount && scrollState == SCROLL_STATE_IDLE && !isLoading && hasMoreItem){
if(pageEnableListener != null){
isLoading = true; //执行之后的状态就是loading
pageEnableListener.onLoadMoreItems(); //调用回调方法
}
}
} @Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totleItem) {
//Dispatch to child OnScrollListener
if (onScrollListener != null) {
onScrollListener.onScroll(absListView, firstVisibleItem, visibleItemCount, totleItem);
}
lastVisibleItem = firstVisibleItem + visibleItemCount; //最后看到的一项
totalItemCount = totleItem; //总的项数
}
}); } @Override
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
} public interface PageEnableListener{
public void onLoadMoreItems();
} }

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

 package njucm.edu.loadmore.activities;

 import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout; import njucm.edu.loadmore.R; /**
* Created by Mesogene on 10/10/15.
*/
public class LoadingView extends LinearLayout { public LoadingView(Context context) {
super(context);
init();
} public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} public void init(){
inflate(getContext(), R.layout.loadinflinear, this);
} }

只是简单重写了一下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的父类的适配器要自己写一下:

 import android.widget.BaseAdapter;
import android.widget.ListAdapter; import java.util.ArrayList;
import java.util.List; import njucm.edu.loadmore.LoadListView; /**
* Created by Mesogene on 10/12/15.
*/
public abstract class PagingBaseAdapter<T> extends BaseAdapter { protected List<T> items = null; public PagingBaseAdapter(List<T> items) {
this.items = items;
} public PagingBaseAdapter(){
this.items = new ArrayList<>();
} public void addMoreItems(List<T> items){
this.items.addAll(items); //把新的项添加到listview里面
notifyDataSetChanged(); //更新布局
} }

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

 package njucm.edu.loadmore.adapters;

 import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; import java.util.List; /**
* Created by Mesogene on 10/12/15.
*/
public class MyListAdapter extends PagingBaseAdapter<String> { @Override
public int getCount() {
return items.size();
} @Override
public String getItem(int position) {
return items.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View view, ViewGroup viewGroup) {
TextView textView;
String text = getItem(position);
if(view != null){
textView = (TextView) view;
}else {
textView = (TextView) LayoutInflater.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1, null);
}
textView.setText(text);
return textView;
}
}

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

 li.setAdapter(adapter);
li.setHasMoreItem(true);
li.setPageEnableListener(new LoadListView.PageEnableListener() {
@Override
public void onLoadMoreItems() {
if(pager < 3){
new CountryAsyncTask().execute();
}else{
li.onFinsihLoading(false, null);
}
}
});
 private class CountryAsyncTask extends SafeAsyncTask<List<String>>{
@Override
public List<String> call() throws Exception { //模拟后台下载数据
List result = null;
switch (pager){
case 0:
result = firstList;
break;
case 1:
result = secondList;
break;
case 2:
result = thirdList;
break;
}
Thread.sleep(3000);
return result;
} @Override
protected void onSuccess(List<String> strings) throws Exception {
super.onSuccess(strings);
pager++;
li.onFinsihLoading(true, strings); //下载成功之后调用的方法,更新UI
}
}

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

 package njucm.edu.loadmore.activities;

 import android.os.Handler;
import android.os.Looper;
import android.util.Log; import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask; /**
* A class similar but unrelated to android's {@link android.os.AsyncTask}.
* <p/>
* Unlike AsyncTask, this class properly propagates exceptions.
* <p/>
* If you're familiar with AsyncTask and are looking for {@link android.os.AsyncTask#doInBackground(Object[])},
* we've named it {@link #call()} here to conform with java 1.5's {@link java.util.concurrent.Callable} interface.
* <p/>
* Current limitations: does not yet handle progress, although it shouldn't be
* hard to add.
* <p/>
* If using your own executor, you must call future() to get a runnable you can execute.
*
* @param <ResultT>
*/
public abstract class SafeAsyncTask<ResultT> implements Callable<ResultT> { //Callable可以返回任意类型。1.5 以后加入
public static final int DEFAULT_POOL_SIZE = 25; //默认的线程池的大小是25
protected static final Executor DEFAULT_EXECUTOR = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE); //利用线程池 protected Handler handler;
protected Executor executor;
protected StackTraceElement[] launchLocation;
protected FutureTask<Void> future; /**
* Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE) and
* Handler to new Handler()
*/
public SafeAsyncTask() {
this.executor = DEFAULT_EXECUTOR;
} /**
* Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE)
*/
public SafeAsyncTask(Handler handler) {
this.handler = handler;
this.executor = DEFAULT_EXECUTOR; //线程池用默认的设置
} /**
* Sets Handler to new Handler()
*/
public SafeAsyncTask(Executor executor) {
this.executor = executor;
} public SafeAsyncTask(Handler handler, Executor executor) {
this.handler = handler;
this.executor = executor;
} public FutureTask<Void> future() {
future = new FutureTask<Void>(newTask());
return future;
} public SafeAsyncTask<ResultT> executor(Executor executor) {
this.executor = executor;
return this;
} public Executor executor() {
return executor;
} public SafeAsyncTask<ResultT> handler(Handler handler) {
this.handler = handler;
return this;
} public Handler handler() {
return handler;
} public void execute() {
execute(Thread.currentThread().getStackTrace());
} protected void execute(StackTraceElement[] launchLocation) {
this.launchLocation = launchLocation;
executor.execute(future());
} public boolean cancel(boolean mayInterruptIfRunning) {
if (future == null) throw new UnsupportedOperationException("You cannot cancel this task before calling future()"); return future.cancel(mayInterruptIfRunning);
} /**
* @throws Exception, captured on passed to onException() if present.
*/
protected void onPreExecute() throws Exception {
} /**
* @param t the result of {@link #call()}
* @throws Exception, captured on passed to onException() if present.
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void onSuccess(ResultT t) throws Exception {
} /**
* Called when the thread has been interrupted, likely because
* the task was canceled.
* <p/>
* By default, calls {@link #onException(Exception)}, but this method
* may be overridden to handle interruptions differently than other
* exceptions.
*
* @param e an InterruptedException or InterruptedIOException
*/
protected void onInterrupted(Exception e) {
onException(e);
} /**
* Logs the exception as an Error by default, but this method may
* be overridden by subclasses.
*
* @param e the exception thrown from {@link #onPreExecute()}, {@link #call()}, or {@link #onSuccess(Object)}
* @throws RuntimeException, ignored
*/
protected void onException(Exception e) throws RuntimeException {
onThrowable(e);
} protected void onThrowable(Throwable t) throws RuntimeException {
Log.e("roboguice", "Throwable caught during background processing", t);
} /**
* @throws RuntimeException, ignored
*/
protected void onFinally() throws RuntimeException {
} protected Task<ResultT> newTask() {
return new Task<ResultT>(this);
} public static class Task<ResultT> implements Callable<Void> {
protected SafeAsyncTask<ResultT> parent;
protected Handler handler; public Task(SafeAsyncTask<ResultT> parent) {
this.parent = parent;
this.handler = parent.handler != null ? parent.handler : new Handler(Looper.getMainLooper());
} public Void call() throws Exception {
try {
doPreExecute();
doSuccess(doCall()); } catch (final Exception e) {
try {
doException(e);
} catch (Exception f) {
// logged but ignored
Log.e("BACKGROUND_TASK", "Exception in", f);
} } catch (final Throwable t) {
try {
doThrowable(t);
} catch (Exception f) {
// logged but ignored
Log.e("BACKGROUND_TASK", "Exception in", f);
}
} finally {
doFinally();
} return null;
} protected void doPreExecute() throws Exception {
postToUiThreadAndWait(new Callable<Object>() {
public Object call() throws Exception {
parent.onPreExecute();
return null;
}
});
} protected ResultT doCall() throws Exception {
return parent.call();
} protected void doSuccess(final ResultT r) throws Exception {
postToUiThreadAndWait(new Callable<Object>() {
public Object call() throws Exception {
parent.onSuccess(r);
return null;
}
});
} protected void doException(final Exception e) throws Exception {
if (parent.launchLocation != null) {
final ArrayList<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
stack.addAll(Arrays.asList(parent.launchLocation));
e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
}
postToUiThreadAndWait(new Callable<Object>() {
public Object call() throws Exception {
if (e instanceof InterruptedException || e instanceof InterruptedIOException) parent.onInterrupted(e);
else parent.onException(e);
return null;
}
});
} protected void doThrowable(final Throwable e) throws Exception {
if (parent.launchLocation != null) {
final ArrayList<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
stack.addAll(Arrays.asList(parent.launchLocation));
e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
}
postToUiThreadAndWait(new Callable<Object>() {
public Object call() throws Exception {
parent.onThrowable(e);
return null;
}
});
} protected void doFinally() throws Exception {
postToUiThreadAndWait(new Callable<Object>() {
public Object call() throws Exception {
parent.onFinally();
return null;
}
});
} /**
* Posts the specified runnable to the UI thread using a handler,
* and waits for operation to finish. If there's an exception,
* it captures it and rethrows it.
*
* @param c the callable to post
* @throws Exception on error
*/
protected void postToUiThreadAndWait(final Callable c) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Exception[] exceptions = new Exception[1]; // Execute onSuccess in the UI thread, but wait
// for it to complete.
// If it throws an exception, capture that exception
// and rethrow it later.
handler.post(new Runnable() {
public void run() {
try {
c.call();
} catch (Exception e) {
exceptions[0] = e;
} finally {
latch.countDown();
}
}
}); // Wait for onSuccess to finish
latch.await(); if (exceptions[0] != null) throw exceptions[0]; } } }

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

Android 上拉加载更多功能的相关教程结束。

《Android 上拉加载更多功能.doc》

下载本文的Word格式文档,以方便收藏与打印。