编写程序时经常会使用到下拉刷新和上拉加载更多。下拉刷新很简单,使用Google的SwipRefreshLayout就能非常方便地实现。而上拉加载更多则稍微复杂一些。
效果
思路:
1.实现上拉加载更多需要使用到RecyclerView.OnScrollerListener这个类。
当执行mRecyclerView.addOnScrollListener(mOnScrollListener)注册监听后,在OnScrollListener的onScrolled()回调中就可以监听到当前mRecyclerView的滑动事件。在监听到滑动事件时判断当前是否需要执行加载更多,如果需要加载更多,就通过抽象方法onLoadMore()将加载更多的事件从mOnScrollListener中传递出去。
2.编写Adapter。
重写onAttachedToRecyclerView()方法,初始化OnScrollListener并调用mRecyclerView.addOnScrollListener(mOnScrollListener)来注册滑动事件监听,同时实现onLoadMore()方法,通过Adapter中的接口进一步将onLoadMore()事件由Adapter传递到Activity中。
3.此时已经能够在Activity中来具体实现onLoadMore()了,这里执行具体的加载更多操作。
4.数据加载完毕时更新数据。
虽然整个思路看起来简单明了,但实际实现过程中还是有些麻烦的,比如以下两个问题。
1.什么时候下拉加载更多
2.如何去正确的更新UI
实现:
1.自定义一个继承自OnScrollListener的RecyclerOnScrollerListener。
public abstract class RecyclerOnScrollerListener extends RecyclerView.OnScrollListener {
private int currentPage = 1;
//是否正在加载
private boolean isLoading = false;
//是否能够加载更多
private boolean isCanLoadMore = true;
private RecyclerView mRecyclerView;
public RecyclerOnScrollerListener(RecyclerView recyclerView) {
super();
this.mRecyclerView = recyclerView;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//能够加载更多
if (isCanLoadMore) {
//已滑动到最底部
if (isSlideToBottom()) {
currentPage++;
onLoadMore(currentPage);
isLoading = true;
}
}
}
/*
* 是否滑动到最底部
*
* !isLoading, 不在加载过程中
* lastVisibleItemPosition == totalItemCount - 1, 当前可见的最后一个item是列表的最后一个
* */
private boolean isSlideToBottom() {
if (mRecyclerView == null) {
return false;
}
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
return !isLoading && visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1;
}
return false;
}
//抽象方法,用来传递加载更多事件
public abstract void onLoadMore(int currentPage);
public void setCanLoadMore(boolean load) {
isCanLoadMore = load;
}
public void setLoading(boolean load) {
isLoading = load;
}
}
本类中加入了两个变量,isLoading和isCanLoadMore,用来控制加载更多事件。
这里通过isSlideToBottom()方法来判断是否滑动到最底部,结合isCanLoadMore和isSlideToBottom共同判断是否加载更多,即当能够加载更多且滑动到最底部时执行加载更多。这就解决了问题1。
2.编写Adapter,这里使用和郭神同样的Fruit为例。
public class FruitAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Fruit> mFruitList;
private Context mContext;
private RecyclerOnScrollerListener mOnScrollListener;
// private RecyclerView.AdapterDataObserver mAdapterDataObserver;
private static final int VIEW_TYPE_CONTENT = 0;
private static final int VIEW_TYPE_FOOTER = 1;
private boolean isCanLoadMore = true;
private Animation rotateAnimation;
private static final int PER_PAGE = 10;
public FruitAdapter(List<Fruit> fruitList) {
this.mFruitList = fruitList;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mContext == null) {
mContext = parent.getContext();
}
if (rotateAnimation == null) {
rotateAnimation = AnimationUtils.loadAnimation(mContext, R.anim.loading);
rotateAnimation.setInterpolator(new LinearInterpolator());
}
if (viewType == VIEW_TYPE_CONTENT) {
return new ContentViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_fruit, parent, false));
} else {
return new FooterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_footer, parent, false));
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == VIEW_TYPE_CONTENT) {
Fruit fruit = mFruitList.get(position);
//这里必须强制转换
//如果外层的判断条件改为if(holder instance ContentViewHolder),这里输入holder后会自动转换
((ContentViewHolder) holder).tvName.setText(fruit.getName());
((ContentViewHolder) holder).ivImage.setImageResource(fruit.getImageId());
} else {
Log.d("mytest", "isCanLoadMore: " + isCanLoadMore);
if (isCanLoadMore) {
((FooterViewHolder) holder).showLoading();
} else {
((FooterViewHolder) holder).showTextOnly("无更多数据");
}
}
}
/*
* 本例中所有数据加载完毕后还是保留footerView并显示已加载完全,所以footerView一直存在。
* */
@Override
public int getItemCount() {
// return isCanLoadMore ? mFruitList.size() + 1 : mFruitList.size();
return mFruitList.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return VIEW_TYPE_FOOTER;
}
return VIEW_TYPE_CONTENT;
}
//ContentView,水果们
class ContentViewHolder extends RecyclerView.ViewHolder {
TextView tvName = itemView.findViewById(R.id.tv_name);
ImageView ivImage = itemView.findViewById(R.id.iv_image);
public ContentViewHolder(View itemView) {
super(itemView);
}
}
//底部的FooterView
class FooterViewHolder extends RecyclerView.ViewHolder {
ImageView ivLoading = itemView.findViewById(R.id.iv_loading);
TextView tvLoading = itemView.findViewById(R.id.tv_loading);
public FooterViewHolder(View itemView) {
super(itemView);
}
void showTextOnly(String s) {
Log.d("mytest", "showTextOnly: " + s);
ivLoading.setVisibility(View.INVISIBLE);
tvLoading.setText(s);
}
void showLoading() {
Log.i("mytest", "show loading");
ivLoading.setImageResource(R.mipmap.ic_launcher);
tvLoading.setText("正在加载...");
if (ivLoading != null) {
ivLoading.startAnimation(rotateAnimation);
}
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mOnScrollListener = new RecyclerOnScrollerListener(recyclerView) {
@Override
public void onLoadMore(int currentPage) {
Log.i("loadingtest", "currentPage: " + currentPage);
mOnLoadMoreListener.onLoadMore(currentPage);
}
};
recyclerView.addOnScrollListener(mOnScrollListener);
// mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
// @Override
// public void onChanged() {
// super.onChanged();
// }
// };
//初始化的时候如果未填满一页,那么肯定就没有更多数据了
if (mFruitList.size() < PER_PAGE) {
setCanLoadMore(false);
} else {
setCanLoadMore(true);
}
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
if (mOnScrollListener != null) {
recyclerView.removeOnScrollListener(mOnScrollListener);
}
// if (mAdapterDataObserver != null) {
// unregisterAdapterDataObserver(mAdapterDataObserver);
// }
mOnScrollListener = null;
}
public void setData(List<Fruit> list) {
mFruitList = list;
notifyDataSetChanged();
}
/*
* 数据加载完毕时执行setCanLoadMore(),此时isLoading都置为false
* */
public void setCanLoadMore(boolean isCanLoadMore) {
this.isCanLoadMore = isCanLoadMore;
mOnScrollListener.setCanLoadMore(isCanLoadMore);
mOnScrollListener.setLoading(false);
}
public interface OnLoadMoreListener {
void onLoadMore(int currentPage);
}
private OnLoadMoreListener mOnLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener listener) {
this.mOnLoadMoreListener = listener;
}
}
Adapter的编写主要有以下几点:
1.因为有footerView提示加载更多,所以这里有两个自定义的ViewHolder,ContentViewHolder和FooterViewHolder,分别用来存放recyclerView的水果们,和底部的FooterView。
2.与一般的RecyclerView相比,这里主要是多了onAttachedToRecyclerView()和onDetachedFromRecyclerView()两个重写方法。
在onAttachedToRecyclerView()进行思路的第2步,在onDetachedFromRecyclerView()注销监听。
3.setCanLoadMore()一般在加载完数据时候调用,重置isCanLoadMore的状态。
3.编写MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView mRvFruits;
private List<Fruit> mFruitList = new ArrayList<>();
private FruitAdapter mAdapter;
private static final int PER_PAGE = 10;
private int mCurrentPage = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
initFruits();
mAdapter = new FruitAdapter(mFruitList);
mRvFruits = findViewById(R.id.rv_fruits);
mRvFruits.setLayoutManager(new LinearLayoutManager(this));
mRvFruits.setAdapter(mAdapter);
mAdapter.setOnLoadMoreListener(new FruitAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore(int currentPage) {
mCurrentPage = currentPage;
loadMoreTest();
}
});
}
private void initFruits() {
Log.i("mytest", "initFruits");
Fruit mango = new Fruit(getRandomLenthName("Mango"), R.drawable.mango);
mFruitList.add(mango);
Fruit orange = new Fruit(getRandomLenthName("Orange"), R.drawable.orange);
mFruitList.add(orange);
Fruit pear = new Fruit(getRandomLenthName("Pear"), R.drawable.pear);
mFruitList.add(pear);
Fruit pineapple = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
mFruitList.add(pineapple);
Fruit strawberry = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
mFruitList.add(strawberry);
Fruit watermelon = new Fruit(getRandomLenthName("Watermelon"), R.drawable.watermelon);
mFruitList.add(watermelon);
Fruit apple = new Fruit(getRandomLenthName("Apple"), R.drawable.apple);
mFruitList.add(apple);
Fruit banana = new Fruit(getRandomLenthName("Banana"), R.drawable.banana);
mFruitList.add(banana);
Fruit cherry = new Fruit(getRandomLenthName("Cherry"), R.drawable.cherry);
mFruitList.add(cherry);
Fruit grape = new Fruit(getRandomLenthName("Grape"), R.drawable.grape);
mFruitList.add(grape);
}
//模拟加载网络数据
private void loadMoreTest() {
Observable.timer(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
//第一个条件是有没有获取到新数据,第二个条件是新数据是否填满了一整页
//这里假如3页之后没有更多数据了,这里的条件根据实际网络请求的返回情况来确定。
Log.d("loadingtest", "main current page: " + mCurrentPage);
if (mCurrentPage < 4) {
List<Fruit> loadMoreList = new ArrayList<>();
Fruit pear = new Fruit(getRandomLenthName("Pear"), R.drawable.pear);
loadMoreList.add(pear);
Fruit pineapple = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
loadMoreList.add(pineapple);
Fruit strawberry = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
loadMoreList.add(strawberry);
Fruit pineapple2 = new Fruit(getRandomLenthName("Pineapple"), R.drawable.pineapple);
loadMoreList.add(pineapple2);
Fruit strawberry2 = new Fruit(getRandomLenthName("Strawberry"), R.drawable.strawberry);
loadMoreList.add(strawberry2);
Fruit watermelon = new Fruit(getRandomLenthName("Watermelon"), R.drawable.watermelon);
loadMoreList.add(watermelon);
Fruit apple = new Fruit(getRandomLenthName("Apple"), R.drawable.apple);
loadMoreList.add(apple);
Fruit banana = new Fruit(getRandomLenthName("Banana"), R.drawable.banana);
loadMoreList.add(banana);
Fruit cherry = new Fruit(getRandomLenthName("Cherry"), R.drawable.cherry);
loadMoreList.add(cherry);
Fruit grape = new Fruit(getRandomLenthName("Grape"), R.drawable.grape);
loadMoreList.add(grape);
mFruitList.addAll(loadMoreList);
//如果未填满一整页,那么肯定没有更多数据了
Log.d("loadingtest", "size1: " + mFruitList.size() + " - size2: " + mCurrentPage * PER_PAGE);
if (mFruitList.size() == mCurrentPage * PER_PAGE) {
mAdapter.setCanLoadMore(true);
} else {
mAdapter.setCanLoadMore(false);
}
mAdapter.setData(mFruitList);
} else {
//网络数据已加载完
mAdapter.setCanLoadMore(false);
mAdapter.notifyDataSetChanged();
}
}
});
}
private String getRandomLenthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(name);
}
return builder.toString();
}
}
在init()中初始化mRvFruits, 并设定加载更多的操作是loadMoreTest()。在loadMoreTest()中先判断了有没有获取到新数据,然后再判断新数据是否填满了一页。
1.如果没有新数据就说明数据已加载完毕。setCanLoadMore(false),并更新适配器。
2.如果有新数据,但是新数据没有填满一页,也说明数据已经加载完毕。设置setCanLoadMore(false),更新数据。
3.如果有新数据并且新数据填满了一页,说明可能还有更多的数据,可以加载更多,setCanLoadMore(true),更新数据。
以上就是Android上拉加载更多的思路和实现方法,希望能够帮助到你。Demo上传到了我的github,需要的朋友们可以去这里下载:
Kanade —— 上拉加载更多Demo