SwipeRefreshLayout作爲官方的下拉刷新控件,簡潔美觀的風格使其廣泛應用在項目中。美中不足的是SwipeRefreshLayout缺少上拉加載的效果,今天結合RecyclerView實現一個支持下拉刷新與上拉加載的SwipeRefreshLayout。
先看一下最後實現的效果圖:
整體效果如上所示,一起看看怎麼實現的:
一.準備工作
1.加載動畫實現:
示例圖中,上拉加載的進度動畫是一個自定義的View,這裏着重分析一下實現方法,源碼末尾會給出:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mWidth / 2, mHeight / 2);
mPaint.setStyle(Paint.Style.FILL);
canvas.rotate(degree);
for (int i = 0; i < 12; i++) {
if (i == 0 || i == 1 || i == 2) {
mPaint.setColor(Color.GRAY);
} else {
mPaint.setColor(Color.LTGRAY);
}
RectF rectF = new RectF(-mRadius / 4, -mRadius * 7 / 2, mRadius / 4, -mRadius * 2);
canvas.drawRoundRect(rectF, mRadius / 8, mRadius / 8, mPaint);
canvas.rotate(30);
canvas.save();
}
canvas.restore();
}
主要是畫布的一些操作,移動原點到中心,繪製圓角矩形,旋轉畫布,動畫效果是通過屬性動畫實現的:
public void startAnimation() {
degree = 0;
degreeAnimation = ValueAnimator.ofFloat(0, 360);
degreeAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
degree = (float) animation.getAnimatedValue();
invalidate();
}
});
degreeAnimation.setRepeatCount(ValueAnimator.INFINITE);
degreeAnimation.setDuration(1000);
degreeAnimation.start();
}
public void endAnimation() {
degreeAnimation.end();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
endAnimation();
}
}
需要注意的是,使用循環動畫時記得調用onWindowFocusChanged()方法來及時結束動畫。這是因爲退出當前Activity或者將當前Activity切入後臺時,如果沒有結束動畫,可能會導致Activity無法釋放從而導致內存泄漏。
關於更多的自定義加載動畫,可以參考我這篇博客:
2.上拉加載View的顯示:
ListView有直接添加頭部View與尾部View的方法,RecyclerView沒有直接提供這兩個方法,那我們上拉加載View放在哪裏呢?處理方法是重寫RecyclerView.Adapter的 getItemViewType(int position)方法,根據getItemViewType傳入的viewType來返回不同類型的ViewHolder。不同的位置返回不同的類型,把最後一個位置預留出來,用來存放加載更多的view。
關於這一塊的內容,可以參考我這篇博客:
二.下拉刷新的實現
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});
refresh();
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refresh();
}
});
private void refresh() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
getData();
recyclerView.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
}, 1500);
}
SwipeRefreshLayout的基本用法,這裏有一點要注意的就是SwipeRefreshLayout進入頁面時自動刷新。直接使用 swipeRefreshLayout.setRefreshing(true)方法沒有效果,得像上面那樣設置纔會有效果。
三.上拉加載的實現
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_refresh_recycler, parent, false);
return new MyViewHolder(rootView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.txt.setText(integerList.get(position));
}
@Override
public int getItemCount() {
return integerList.size();
}
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView txt;
public MyViewHolder(View itemView) {
super(itemView);
txt = (TextView) itemView.findViewById(R.id.item_txt);
}
}
這是支持下拉刷新下RecyclerView的adapter,上面我們分析到,需要重寫 getItemViewType(int position)方法來存放上拉加載View。那我們需要修改getItemViewType(),onCreateViewHolder(),onBindViewHolder(),getItemCount()等方法,並對viewType進行判斷。那麼如何在不破壞原有Adapter實現的情況下完成呢?
這裏引入裝飾器(Decorator)設計模式,該設計模式通過組合的方式,在不 破壞原有類代碼的情況下,對原有類的功能進行擴展。
參考資料:
RecyclerView 必知必會
看看怎麼實現的:
public class MyRefreshAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter adapter;
private View footerView;
public static final int NORMAL_VIEW_TYPE = 1;
public static final int FOOTER_VIEW_TYPE = 2;
public MyRefreshAdapter(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return FOOTER_VIEW_TYPE;
}
return NORMAL_VIEW_TYPE;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == FOOTER_VIEW_TYPE) {
return new RecyclerView.ViewHolder(footerView) {
};
} else {
return adapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == getItemCount() - 1) {
return;
} else {
adapter.onBindViewHolder(holder, position);
}
}
@Override
public int getItemCount() {
return adapter.getItemCount() + 1;
}
public void addFooterView(View footerView) {
this.footerView = footerView;
}
}
getItemViewType()用於決定元素的佈局使用哪種類型,返回的是一個int值作爲傳遞給onCreateViewHolder的第二個參數;onCreateViewHolder根據getItemViewType傳入的viewType來渲染構造不同的ViewHolder;ViewHolder用來存放視圖與數據,通過返回不同類型的ViewHolder達到預期效果。
上拉加載view已經找到存放的地方,什麼時候顯示呢?
我這裏的處理方法是自定義一個RecyclerView,添加滑動監聽,當滑動到底部時進行處理:
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
//停止滾動時
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//獲取最後一個完全顯示Item的Position
int lastVisibleItem = manager.findLastCompletelyVisibleItemPosition();
int totalItemCount = manager.getItemCount();
// 判斷是否滾動到底部,並且不在加載狀態
if (lastVisibleItem == (totalItemCount - 1) && !isLoadMore) {
isLoadMore = true;
loadTxt.setText("正在加載...");
circleProgressView.setVisibility(VISIBLE);
footerView.setVisibility(VISIBLE);
myLoadListener.onLoadMore();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
當RecyclerView停止滾動時,獲取最後一個完全顯示Item的Position,判斷是否滾動到底部,並且不在加載狀態,符合條件的情況就接口回調加載數據。
public interface MyLoadListener {
void onLoadMore();
}
重寫setAdapter()方法,這樣我們的MyRefreshAdapter便能發揮它的功能:
@Override
public void setAdapter(Adapter adapter) {
LinearLayout footerLayout = new LinearLayout(getContext());
footerLayout.setGravity(Gravity.CENTER);
footerLayout.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 160));
circleProgressView = new CircleProgressView(getContext());
circleProgressView.setLayoutParams(new LinearLayout.LayoutParams(80, 80));
circleProgressView.startAnimation();
footerLayout.addView(circleProgressView);
loadTxt = new TextView(getContext());
footerLayout.addView(loadTxt);
footerView = footerLayout;
footerView.setVisibility(GONE);
myRefreshAdapter = new MyRefreshAdapter(adapter);
myRefreshAdapter.addFooterView(footerView);
super.setAdapter(myRefreshAdapter);
}
動態添加上拉加載view,調用myRefreshAdapter.addFooterView()方法添加進去。Activity中使用:
MyAdapter myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
recyclerView.setMyLoadListener(new MyRefreshRecyclerView.MyLoadListener() {
@Override
public void onLoadMore() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (integerList.size() > 14) {
recyclerView.setLoadMore(true);
} else {
int randomInt = new Random().nextInt(100);
integerList.add("上拉加載添加數字:" + randomInt);
myAdapter.notifyDataSetChanged();
recyclerView.setLoadMore(false);
}
}
}, 1000);
}
});
額外在自定義的recyclerView中添加了一個判斷是否加載完成的方法:
public void setLoadMore(boolean complete) {
if (complete) {
loadTxt.setText("已經全部加載完啦!");
circleProgressView.setVisibility(GONE);
} else {
footerView.setVisibility(GONE);
}
isLoadMore = false;
}
這樣,一個完整的下拉刷新與上拉加載就已經完成了,希望看完本篇文章能對你有所幫助。