SwipeRefreshLayout作为官方的下拉刷新控件,简洁美观的风格使其广泛利用在项目中。美中不足的是SwipeRefreshLayout缺少上拉加载的效果,今天结合RecyclerView实现1个支持下拉刷新与上拉加载的SwipeRefreshLayout。
先看1下最后实现的效果图:
整体效果如上所示,1起看看怎样实现的:
1.准备工作
1.加载动画实现:
示例图中,上拉加载的进度动画是1个自定义的View,这里侧重分析1下实现方法,源码末尾会给出:
@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();
}
主要是画布的1些操作,移动原点到中心,绘制圆角矩形,旋转画布,动画效果是通过属性动画实现的:
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没法释放从而致使内存泄漏。
关于更多的自定义加载动画,可以参考我这篇博客:
Android自定义加载动画(延续更新中…)
2.上拉加载View的显示:
ListView有直接添加头部View与尾部View的方法,RecyclerView没有直接提供这两个方法,那我们上拉加载View放在哪里呢?处理方法是重写RecyclerView.Adapter的 getItemViewType(int position)方法,根据getItemViewType传入的viewType来返回不同类型的ViewHolder。不同的位置返回不同的类型,把最后1个位置预留出来,用来寄存加载更多的view。
关于这1块的内容,可以参考我这篇博客:
RecyclerView学习(1)—-初步认知
2.下拉刷新的实现
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的基本用法,这里有1点要注意的就是SwipeRefreshLayout进入页面时自动刷新。直接使用 swipeRefreshLayout.setRefreshing(true)方法没有效果,得像上面那样设置才会有效果。
3.上拉加载的实现
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()用于决定元素的布局使用哪一种类型,返回的是1个int值作为传递给onCreateViewHolder的第2个参数;onCreateViewHolder根据getItemViewType传入的viewType来渲染构造不同的ViewHolder;ViewHolder用来寄存视图与数据,通过返回不同类型的ViewHolder到达预期效果。
上拉加载view已找到寄存的地方,甚么时候显示呢?
我这里的处理方法是自定义1个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) {
//获得最后1个完全显示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停止转动时,获得最后1个完全显示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中添加了1个判断是不是加载完成的方法:
public void setLoadMore(boolean complete) {
if (complete) {
loadTxt.setText("已全部加载完啦!");
circleProgressView.setVisibility(GONE);
} else {
footerView.setVisibility(GONE);
}
isLoadMore = false;
}
这样,1个完全的下拉刷新与上拉加载就已完成了,希望看完本篇文章能对你有所帮助。
项目完全源码已上传到我的github上,源码地址:
https://github.com/18722527635/MyRecyclerView
欢迎Star,fork,提issues,1起进步!