下拉刷新,上拉加载更多在许多应用中都频频用到,特别是需要分页加载数据时.之前在项目中经常用到的是网上非常普遍的PullToRefreshLayout.该框架比较好用,不过是比较旧了.就想着重新找一个控件.因为现在android 5.0应用的已经比较多了,所以就想做一个和知乎效果差不多的效果.
谷歌官方为我们提供一个SwipeRefreshLayout的刷新控件,效果不错,不过仅仅有下拉刷新的效果,上拉加载更多的效果没有提供,需要自己实现.开始我封装了有一个RefreshLayout继承SwipeRefreshLayout,其中的ListView可以做刷新和加载更多,但是暂时不支持RecyclerView,而且有一些Bug.而现在谷歌推荐用RecyclerView替代ListView因此仿照他人的思路重新写了一个,其中SwipeRefreshLayout负责刷新动作,自定义RecyclerView负责加载更多,并且封装了几个适配器.比较简单,目前只是用作展示数据,没有加入侧滑等效果.如下图所示
以下是自定义RecyclerView的代码部分
package com.mercury.recyclerload;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
/**
* created by Mercury at 2016/11/20
* descript:
*/
public class HaoRecyclerView extends RecyclerView {
private int downY;
private int moveY;
private int upY;
private Context mContext;
private OnLoadMoreListener mOnLoadMoreListener;
//是否可加载更多
private boolean canloadMore = true;
//真正由界面传入的适配器
private Adapter mAdapter;
//封装的适配器,用于适配自定义Recyclerview,包含了点击事件,显示条目的布局及尾布局
private Adapter mFooterAdapter;
//是否正在加载数据
private boolean isLoadingData = false;
//加载更多的尾布局.继承LinearLayout
private LoadingMoreFooter loadingMoreFooter;
public HaoRecyclerView(Context context) {
this(context, null);
}
public HaoRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HaoRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mContext = context;
LoadingMoreFooter footView = new LoadingMoreFooter(mContext);
addFootView(footView);
footView.setGone();
}
//点击监听
public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) {
if (mFooterAdapter != null && mFooterAdapter instanceof FooterAdapter) {
((FooterAdapter) mFooterAdapter).setOnItemClickListener(onItemClickListener);
}
}
//长按监听
public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) {
if (mFooterAdapter != null && mFooterAdapter instanceof FooterAdapter) {
((FooterAdapter) mFooterAdapter).setOnItemLongClickListener(listener);
}
}
/**
* 底部加载更多视图
*
* @param view
*/
public void addFootView(LoadingMoreFooter view) {
loadingMoreFooter = view;
}
//设置底部加载中效果
public void setFootLoadingView(View view) {
if (loadingMoreFooter != null) {
loadingMoreFooter.addFootLoadingView(view);
}
}
//设置底部到底了布局
public void setFootEndView(View view) {
if (loadingMoreFooter != null) {
loadingMoreFooter.addFootEndView(view);
}
}
//下拉刷新后初始化底部状态
public void refreshComplete() {
if (loadingMoreFooter != null) {
loadingMoreFooter.setGone();
}
isLoadingData = false;
canloadMore = true;
}
public void loadMoreComplete() {
if (loadingMoreFooter != null) {
loadingMoreFooter.setGone();
}
isLoadingData = false;
}
//到底了
public void loadMoreEnd() {
if (loadingMoreFooter != null) {
loadingMoreFooter.setEnd();
}
}
//设置是否可加载更多
public void setCanloadMore(boolean flag) {
canloadMore = flag;
}
//设置加载更多监听
public void setOnLoadMoreListener(OnLoadMoreListener listener) {
mOnLoadMoreListener = listener;
}
@Override
public void setAdapter(Adapter adapter) {
mAdapter = adapter;
mFooterAdapter = new FooterAdapter(this,loadingMoreFooter, adapter);
super.setAdapter(mFooterAdapter);
mAdapter.registerAdapterDataObserver(mDataObserver);
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
//不在加载数据时,即isLoadingData=false时,才去判断是否满足加载更多的条件
if (state == RecyclerView.SCROLL_STATE_IDLE && mOnLoadMoreListener != null &&
!isLoadingData && canloadMore && isPullUp()) {
LayoutManager layoutManager = getLayoutManager();
int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) layoutManager)
.findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
lastVisibleItemPosition = last(into);
} else {
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager)
.findLastVisibleItemPosition();
}
if (layoutManager.getChildCount() > 0
&& lastVisibleItemPosition >= layoutManager.getItemCount() - 1) {
if (loadingMoreFooter != null) {
loadingMoreFooter.setVisible();
}
isLoadingData = true;
mOnLoadMoreListener.onLoadMore();
}
}
}
//取到最后的一个节点
private int last(int[] lastPositions) {
int last = lastPositions[0];
for (int value : lastPositions) {
if (value > last) {
last = value;
}
}
return last;
}
private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mFooterAdapter.notifyDataSetChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mFooterAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mFooterAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mFooterAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mFooterAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mFooterAdapter.notifyItemMoved(fromPosition, toPosition);
}
};
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
upY = (int) ev.getRawY();
default:
break;
}
return super.dispatchTouchEvent(ev);
}
//用于区分是不是向上的手势,否则会触发刷新动作
public boolean isPullUp() {
return (downY - upY) >= 0;
}
}
其中的mAdapter是真正由使用到RecyclerView的组件,比如相应的Activity或Fragment传入的自定义的Adapter.mFooterAdapter是HaoRecyclerView内部独有的适配器,包含了普通条目以及尾布局,并且封装好了点击事件(RecyclerView本身没有点击事件).这样在HaoRecyclerView内部setAdapter其实连接的是FooterAdapter,但是我们真正传入的包含我们数据的适配器是mAdapter,为了解决这个问题.使用RecyclerView包含的AdapterDataObserver来观察FooterAdapter的数据变化,而用mAdapter注册该观察对象.以改变数据.
下面是自定义的FooterAdapter
package com.mercury.recyclerload;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
/**
* created by Mercury at 2016/11/20
* descript: 带脚布局(加载更多)的适配器
*/
public class FooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View
.OnClickListener, View.OnLongClickListener {
private RecyclerView.Adapter adapter;
private HaoRecyclerView haoRecyclerView;
//自定义的尾布局
private LoadingMoreFooter loadingMoreFooter;
private static final int DEFAULT = 0;
private static final int FOOTER = -1;
public FooterAdapter(HaoRecyclerView haoRecyclerView, LoadingMoreFooter loadingMoreFooter,
RecyclerView.Adapter adapter) {
this.haoRecyclerView = haoRecyclerView;
this.loadingMoreFooter = loadingMoreFooter;
this.adapter = adapter;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (getItemViewType(position) == RecyclerView.INVALID_TYPE ||
getItemViewType(position) == RecyclerView.INVALID_TYPE - 1)
? gridManager.getSpanCount() : 1;
}
});
}
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& isFooter(holder.getLayoutPosition())) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams)
lp;
p.setFullSpan(true);
}
}
/**
* 当前布局是否为Footer
*
* @param position
* @return
*/
public boolean isFooter(int position) {
return position == getItemCount() - 1;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == FOOTER) {
return new SimpleViewHolder(loadingMoreFooter);
}
return adapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.itemView.setOnClickListener(this);
holder.itemView.setOnLongClickListener(this);
if (adapter != null) {
int count = adapter.getItemCount();
if (position < count) {
adapter.onBindViewHolder(holder, position);
return;
}
}
}
@Override
public int getItemCount() {
if (adapter != null) {
return 1 + adapter.getItemCount();
} else {
return 1;
}
}
@Override
public int getItemViewType(int position) {
if (isFooter(position)) {
return FOOTER;
}
if (adapter != null) {
int count = adapter.getItemCount();
if (position < count) {
return adapter.getItemViewType(position);
}
}
return DEFAULT;
}
@Override
public long getItemId(int position) {
if (adapter != null && position >= 0) {
int adapterCount = adapter.getItemCount();
if (position < adapterCount) {
return adapter.getItemId(position);
}
}
return -1;
}
@Override
public void onClick(View view) {
int pos = haoRecyclerView.getChildAdapterPosition(view);
if (onItemClickListener != null && !isFooter(pos)) {
onItemClickListener.onItemClick(null, view, pos, 0);
}
}
@Override
public boolean onLongClick(View view) {
int pos = haoRecyclerView.getChildAdapterPosition(view);
if (onItemLongClickListener != null && !isFooter(pos)) {
onItemLongClickListener.onItemLongClick(null, view, pos, 0);
}
return true;
}
//点击
AdapterView.OnItemClickListener onItemClickListener;
public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
//长按
AdapterView.OnItemLongClickListener onItemLongClickListener;
public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener
onItemLongClickListener) {
this.onItemLongClickListener = onItemLongClickListener;
}
private class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
}
这里我使用的尾布局样式非常简单,加载中就是一个居中的ProgressBar,封装了几种判断.包括下拉刷新及上拉加载完成后隐藏该尾布局,加载进行中显示该布局.没有更多数据的时候显示哪个布局,并且如果是尾布局的话,该条目应该是不可点击的,或者点击后的逻辑单独处理.更多逻辑还要进一步完善
package com.mercury.recyclerload;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
/**
* created by Mercury at 2016/11/21
* descript: 加载更多的底部布局
*/
public class LoadingMoreFooter extends LinearLayout {
private Context context;
private LinearLayout loading_view_layout;
private LinearLayout end_layout;
public LoadingMoreFooter(Context context) {
super(context);
this.context = context;
initView(context);
}
/**
* @param context
* @param attrs
*/
public LoadingMoreFooter(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public void initView(Context context) {
setGravity(Gravity.CENTER);
setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.footer_layout, null);
loading_view_layout = (LinearLayout) view.findViewById(R.id.loading_view_layout);
end_layout = (LinearLayout) view.findViewById(R.id.end_layout);
addFootLoadingView(new ProgressBar(context, null, android.R.attr.progressBarStyle));
TextView textView = new TextView(context);
textView.setText("已经到底啦~");
addFootEndView(textView);
addView(view);
}
//设置底部加载中效果
public void addFootLoadingView(View view) {
loading_view_layout.removeAllViews();
loading_view_layout.addView(view);
}
//设置底部到底的布局
public void addFootEndView(View view) {
end_layout.removeAllViews();
end_layout.addView(view);
}
//设置已经没有更多数据
public void setEnd() {
setVisibility(VISIBLE);
loading_view_layout.setVisibility(GONE);
end_layout.setVisibility(VISIBLE);
}
public void setVisible(){
setVisibility(VISIBLE);
loading_view_layout.setVisibility(VISIBLE);
end_layout.setVisibility(GONE);
}
public void setGone(){
setVisibility(GONE);
}
}
我封装了一个基类,真正填充数据的所有使用该控件的适配器均可继承该基类.
package com.mercury.recyclerload;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import java.util.ArrayList;
import java.util.List;
/**
* created by Mercury at 2016/11/21
* descript: 提供给RecyclerView适配器使用的基类
*/
public abstract class BaseRecylerAdapter<T> extends RecyclerView.Adapter {
public Context mContext;
public List<T> mData = new ArrayList<>();
public LayoutInflater mInflater;
@Override
public int getItemCount() {
return mData.size();
}
public List<T> getData() {
return mData;
}
public void setData(List<T> list) {
this.mData.clear();
this.mData.addAll(list);
notifyDataSetChanged();
}
public void addAll(List<T> list) {
int lastIndex = this.mData.size();
if (this.mData.addAll(list)) {
notifyItemRangeInserted(lastIndex, list.size());
}
}
public void removePos(int position) {
if(this.mData.size() > 0) {
mData.remove(position);
notifyItemRemoved(position);
}
}
public void remove(Object obj) {
if(this.mData.size() > 0) {
mData.remove(obj);
}
}
public void clear() {
mData.clear();
notifyDataSetChanged();
}
}
最后附上动图所展示的Demo,为了达成目前的效果,在代码中需要自己调用控件中暴露的一些方法去判断,如Demo的onRefresh和onLoadMore的回调中所示:
package com.mercury.recyclerload;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.view.View;
import android.widget.AdapterView;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout
.OnRefreshListener, OnLoadMoreListener {
@Bind(R.id.rv)
HaoRecyclerView rv;
@Bind(R.id.swr)
SwipeRefreshLayout swr;
List<String> list;
MyAdapter mAdapter;
private Handler mHandler;
private int a ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initData();
initEvent();
}
private void initEvent() {
rv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ToastUtil.showToast(MainActivity.this, position + "");
}
});
}
private void initData() {
mHandler = new Handler();
swr.setColorSchemeResources(R.color.colorPrimary);
swr.setOnRefreshListener(this);
rv.setOnLoadMoreListener(this);
list = new ArrayList<>();
rv.setLayoutManager(new LinearLayoutManager(this));
initList();
mAdapter = new MyAdapter(this);
mAdapter.setData(list);
rv.setAdapter(mAdapter);
}
private void initList() {
list.clear();
for (int i = 0; i < 10; i++) {
list.add(i + "");
}
}
@Override
public void onRefresh() {
a = 0;
rv.setCanloadMore(false);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
initList();
mAdapter.setData(list);
rv.refreshComplete();
swr.setRefreshing(false);
}
}, 1500);
}
@Override
public void onLoadMore() {
addData();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mAdapter.getItemCount() == 30) {
rv.loadMoreEnd();
return;
}
mAdapter.addAll(list);
rv.loadMoreComplete();
}
}, 1500);
}
private void addData() {
a += 10;
list.clear();
for (int i = a; i < a + 10; i++) {
list.add(i + "");
}
}
}
源码下载