編寫程序時經常會使用到下拉刷新和上拉加載更多。下拉刷新很簡單,使用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