Android中下拉刷新的框架可謂是多不可數,今天,我給大家介紹一下我最近新學習到的一個基於RecyclerView的一個下拉刷新框架,支持上拉加載,並且可以自定義上拉加載視圖動畫,下拉刷新是基於SwipeRefreshLayout的。項目地址奉上 https://github.com/niniloveyou/SwipeRecyclerView
1、首先在modle的gradle中對RecyclerView進行配置
compile 'com.android.support:recyclerview-v7:23.3.0'
2、我們這個下拉刷新框架是基於SwipeRefreshLayout+RecyclerView的所以我們將這個進行了封裝
自定義SwipeRecyclerView 類
public class SwipeRecyclerView extends FrameLayout
implements SwipeRefreshLayout.OnRefreshListener{
private View mEmptyView;
private BaseFooterView mFootView;
private RecyclerView recyclerView;
private SwipeRefreshLayout mRefreshLayout;
private LayoutManager mLayoutManager;
private OnLoadListener mListener;
private SpanSizeLookup mSpanSizeLookup;
private DataObserver mDataObserver;
private WrapperAdapter mWrapperAdapter;
private boolean isEmptyViewShowing;
private boolean isLoadingMore;
private boolean isLoadMoreEnable;
private boolean isRefreshEnable;
private int lastVisiablePosition = 0;
public SwipeRecyclerView(Context context) {
this(context, null);
}
public SwipeRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setupSwipeRecyclerView();
}
private void setupSwipeRecyclerView() {
isEmptyViewShowing = false;
isRefreshEnable = true;
isLoadingMore = false;
isLoadMoreEnable = true;
mFootView = new SimpleFooterView(getContext());
View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_swipe_recyclerview, this);
mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.SwipeRefreshLayout);
recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView);
mLayoutManager = recyclerView.getLayoutManager();
mRefreshLayout.setOnRefreshListener(this);
recyclerView.setOnScrollListener(new OnScrollListener() {
@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);
// do nothing if load more is not enable or refreshing or loading more
if(!isLoadMoreEnable || isRefreshing() || isLoadingMore){
return;
}
//get the lastVisiablePosition
mLayoutManager = recyclerView.getLayoutManager();
if(mLayoutManager instanceof LinearLayoutManager){
lastVisiablePosition = ((LinearLayoutManager)mLayoutManager).findLastVisibleItemPosition();
}else if(mLayoutManager instanceof GridLayoutManager){
lastVisiablePosition = ((GridLayoutManager)mLayoutManager).findLastCompletelyVisibleItemPosition();
}else if(mLayoutManager instanceof StaggeredGridLayoutManager){
int[] into = new int[((StaggeredGridLayoutManager) mLayoutManager).getSpanCount()];
((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(into);
lastVisiablePosition = findMax(into);
}
int childCount = mWrapperAdapter == null ? 0 : mWrapperAdapter.getItemCount();
Log.i("info","lastVisiablePosition="+lastVisiablePosition+",childCount-1="+(childCount-1));
if(childCount > 1 && lastVisiablePosition == childCount - 1){
if(mListener != null){
isLoadingMore = true;
mListener.onLoadMore();
}
}
}
});
}
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
/**
* set is enable pull to refresh
* @param refreshEnable
*/
public void setRefreshEnable(boolean refreshEnable){
isRefreshEnable = refreshEnable;
mRefreshLayout.setEnabled(isRefreshEnable);
}
public boolean getRefreshEnable(){
return isRefreshEnable;
}
/**
* set is loading more enable
* @param loadMoreEnable
* if true when recyclerView scroll to bottom load more action will be trigger
*/
public void setLoadMoreEnable(boolean loadMoreEnable) {
if(!loadMoreEnable){
stopLoadingMore();
}
isLoadMoreEnable = loadMoreEnable;
}
public boolean getLoadMoreEnable(){
return isLoadMoreEnable;
}
/**
* get is refreshing
* @return
*/
public boolean isRefreshing(){
return mRefreshLayout.isRefreshing();
}
/**
* get is loading more
* @return
*/
public boolean isLoadingMore(){
return isLoadingMore;
}
/**
* is empty view showing
* @return
*/
public boolean isEmptyViewShowing(){
return isEmptyViewShowing;
}
/**
* you may need set some other attributes of swipeRefreshLayout
* @return
* swipeRefreshLayout
*/
public SwipeRefreshLayout getSwipeRefreshLayout(){
return mRefreshLayout;
}
/**
* you may need set some other attributes of RecyclerView
* @return
* RecyclerView
*/
public RecyclerView getRecyclerView(){
return recyclerView;
}
/**
* set load more listener
* @param listener
*/
public void setOnLoadListener(OnLoadListener listener){
mListener = listener;
}
/**
* support for GridLayoutManager
* @param spanSizeLookup
*/
public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup){
this.mSpanSizeLookup = spanSizeLookup;
}
/**
* set the footer view
* @param footerView
* the view to be showing when pull up
*/
public void setFooterView(BaseFooterView footerView){
if(footerView != null) {
this.mFootView = footerView;
}
}
/**
* set a empty view like listview
* @param emptyView
* the view to be showing when the data set size is zero
*/
public void setEmptyView(View emptyView){
if(mEmptyView != null){
removeView(mEmptyView);
}
this.mEmptyView = emptyView;
if(mDataObserver != null) {
mDataObserver.onChanged();
}
}
/**
* set adapter to recyclerView
* @param adapter
*/
public void setAdapter(RecyclerView.Adapter adapter){
if(adapter != null) {
if(mDataObserver == null){
mDataObserver = new DataObserver();
}
mWrapperAdapter = new WrapperAdapter(adapter);
recyclerView.setAdapter(mWrapperAdapter);
adapter.registerAdapterDataObserver(mDataObserver);
mDataObserver.onChanged();
}
}
/**
* refresh or load more completed
*/
public void complete(){
mRefreshLayout.setRefreshing(false);
stopLoadingMore();
}
/**
* set refreshing
* if you want load data when first in, you can setRefreshing(true)
* after {@link #setOnLoadListener(OnLoadListener)}
* @param refreshing
*/
public void setRefreshing(boolean refreshing){
mRefreshLayout.setRefreshing(refreshing);
if(refreshing && !isLoadingMore && mListener != null){
mListener.onRefresh();
}
}
/**
* stop loading more without animation
*/
public void stopLoadingMore(){
isLoadingMore = false;
if(mWrapperAdapter != null) {
mWrapperAdapter.notifyItemRemoved(mWrapperAdapter.getItemCount());
}
}
/**
* call method {@link OnLoadListener#onRefresh()}
*/
@Override
public void onRefresh() {
if(mListener != null){
//reset footer view status loading
if(mFootView != null){
mFootView.onLoadingMore();
}
mListener.onRefresh();
}
}
/**
*
* call when network is available or not available
*/
public void onNetChange(boolean isAvailable) {
if(mFootView != null){
mFootView.onNetChange(isAvailable);
}
}
/**
* call when you need change footer view to loading status
*/
public void onLoadingMore() {
if(mFootView != null){
mFootView.onLoadingMore();
}
}
/**
* call when no more data add to list
*/
public void onNoMore(CharSequence message) {
if(mFootView != null){
mFootView.onNoMore(message);
}
}
/**
* call when you need show error message
*/
public void onError(CharSequence message) {
if(mFootView != null){
mFootView.onError(message);
}
}
private class WrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
public static final int TYPE_FOOTER = 0x100;
RecyclerView.Adapter<RecyclerView.ViewHolder> mInnerAdapter;
public WrapperAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter){
this.mInnerAdapter = adapter;
}
public boolean isLoadMoreItem(int position){
return isLoadMoreEnable && position == getItemCount() - 1;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(TYPE_FOOTER == viewType){
return new FooterViewHolder(mFootView);
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(isLoadMoreItem(position)){
return;
}
mInnerAdapter.onBindViewHolder(holder, position);
}
@Override
public int getItemViewType(int position) {
if(isLoadMoreItem(position)){
return TYPE_FOOTER;
}else{
return mInnerAdapter.getItemViewType(position);
}
}
@Override
public int getItemCount() {
int count = mInnerAdapter == null ? 0 : mInnerAdapter.getItemCount();
//without loadingMore when adapter size is zero
if(count == 0){
return 0;
}
return isLoadMoreEnable ? count + 1 : count;
}
@Override
public long getItemId(int position) {
return mInnerAdapter.getItemId(position);
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& isLoadMoreItem(holder.getLayoutPosition()))
{
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
mInnerAdapter.onViewAttachedToWindow(holder);
}
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewDetachedFromWindow(holder);
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
boolean isLoadMore = isLoadMoreItem(position);
if(mSpanSizeLookup != null && !isLoadMore){
return mSpanSizeLookup.getSpanSize(position);
}
return isLoadMore ? gridManager.getSpanCount() : 1;
}
});
}
mInnerAdapter.onAttachedToRecyclerView(recyclerView);
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
mInnerAdapter.onDetachedFromRecyclerView(recyclerView);
}
@Override
public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
return mInnerAdapter.onFailedToRecycleView(holder);
}
@Override
public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
mInnerAdapter.registerAdapterDataObserver(observer);
}
@Override
public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
mInnerAdapter.unregisterAdapterDataObserver(observer);
}
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewRecycled(holder);
}
}
/**
* ViewHolder of footerView
*/
private class FooterViewHolder extends RecyclerView.ViewHolder {
public FooterViewHolder(View itemView) {
super(itemView);
}
}
/**
* a inner class used to monitor the dataSet change
* <p>
* because wrapperAdapter do not know when wrapperAdapter.mInnerAdapter
* <p>
* dataSet changed, these method are final
*/
class DataObserver extends RecyclerView.AdapterDataObserver{
@Override
public void onChanged() {
super.onChanged();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if(adapter != null && mEmptyView != null){
int count = 0;
if(isLoadMoreEnable && adapter.getItemCount() != 0){
count ++;
}
if(adapter.getItemCount() == count){
isEmptyViewShowing = true;
if(mEmptyView.getParent() == null){
LayoutParams params = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
addView(mEmptyView, params);
}
recyclerView.setVisibility(GONE);
mEmptyView.setVisibility(VISIBLE);
}else{
isEmptyViewShowing = false;
mEmptyView.setVisibility(GONE);
recyclerView.setVisibility(VISIBLE);
}
}
mWrapperAdapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
mWrapperAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
super.onItemRangeChanged(positionStart, itemCount, payload);
mWrapperAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
mWrapperAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
mWrapperAdapter.notifyItemRangeRemoved(fromPosition, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
mWrapperAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
public interface OnLoadListener {
void onRefresh();
void onLoadMore();
}
}
3、SwipeRecyclerView控件的佈局layout_swipe_recyclerview.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/SwipeRefreshLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@null"
android:scrollbars="none"/>
</android.support.v4.widget.SwipeRefreshLayout>
4、BaseFootView.class
public abstract class BaseFooterView extends FrameLayout implements FooterViewListener{
public BaseFooterView(Context context) {
super(context);
}
public BaseFooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BaseFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
5、FooterViewListener.class
public interface FooterViewListener {
/**
* 網絡不好的時候想要展示的UI
*/
void onNetChange(boolean isAvailable);
/**
* 正常的loading的View
*/
void onLoadingMore();
/**
* 沒有更多數據
*/
void onNoMore(CharSequence message);
/**
* 錯誤時展示的View
*/
void onError(CharSequence message);
}
6、SimpleFooterView.class
private TextView mText;
private ProgressBar progressBar;
public SimpleFooterView(Context context) {
this(context, null);
}
public SimpleFooterView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SimpleFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_footer_view, this);
progressBar = (ProgressBar) view.findViewById(R.id.footer_view_progressbar);
mText = (TextView) view.findViewById(R.id.footer_view_tv);
}
@Override
public void onLoadingMore() {
progressBar.setVisibility(VISIBLE);
mText.setVisibility(GONE);
}
public void showText(){
progressBar.setVisibility(GONE);
mText.setVisibility(VISIBLE);
}
/**************文字自行修改或根據傳入的參數動態修改****************/
@Override
public void onNoMore(CharSequence message) {
showText();
mText.setText("-- the end --");
}
@Override
public void onError(CharSequence message) {
showText();
mText.setText("啊哦,好像哪裏不對勁!");
}
@Override
public void onNetChange(boolean isAvailable) {
showText();
mText.setText("網絡連接不通暢!");
}
7、調用
public class SwipeRecyclerViewActivity extends AppCompatActivity {
private SwipeRecyclerView recyclerView;
private List<String> data;
private SwipeRecycleViewAdapter adapter;
private int pagerSize = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe_recycler_view);
recyclerView = (SwipeRecyclerView) findViewById(R.id.swipeRecyclerView);
//set color
recyclerView.getSwipeRefreshLayout()
.setColorSchemeColors(Color.RED,Color.YELLOW,Color.BLUE);
//set layoutManager
recyclerView.getRecyclerView().setLayoutManager(new LinearLayoutManager(this));
//recyclerView.getRecyclerView().setLayoutManager(new GridLayoutManager(this, 3));
//recyclerView.getRecyclerView().setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
//禁止下拉刷新
// recyclerView.setRefreshEnable(false);
//禁止加載更多
//recyclerView.setLoadMoreEnable(false);
//設置emptyView
/*TextView textView = new TextView(this);
textView.setText("empty view");
recyclerView.setEmptyView(textView);*/
//設置footerView
// recyclerView.setFooterView(new SimpleFooterView(this));
//由於SwipeRecyclerView中對GridLayoutManager的SpanSizeLookup做了處理,因此對於使用了
//GridLayoutManager又要使用SpanSizeLookup的情況,可以這樣使用!
/*recyclerView.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return 3;
}
});*/
//設置去除footerView 的分割線
/* recyclerView.getRecyclerView().addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xFFEECCCC);
Rect rect = new Rect();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount() - 1;
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
//獲得child的佈局信息
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int itemDividerHeight = 1;//px
rect.set(left + 50, top, right - 50, top + itemDividerHeight);
c.drawRect(rect, paint);
}
}
});*/
//設置noMore
// recyclerView.onNoMore("-- end --");
//設置網絡處理
//recyclerView.onNetChange(true);
//設置錯誤信息
//recyclerView.onError("error");
data = new ArrayList<>();
adapter = new SwipeRecycleViewAdapter(SwipeRecyclerViewActivity.this,data);
recyclerView.setAdapter(adapter);
recyclerView.setOnLoadListener(new SwipeRecyclerView.OnLoadListener() {
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
data.clear();
for (int i = 0; i < pagerSize; i++) {
data.add(String.valueOf(i));
}
recyclerView.complete();
adapter.notifyDataSetChanged();
}
}, 1000);
}
@Override
public void onLoadMore() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
for (int i = 0; i < pagerSize; i++) {
data.add(String.valueOf(i));
}
if(data.size() > 20){
recyclerView.onNoMore("-- the end --");
}else {
recyclerView.stopLoadingMore();
adapter.notifyDataSetChanged();
}
}
}, 1000);
}
});
//設置自動下拉刷新,切記要在recyclerView.setOnLoadListener()之後調用
//因爲在沒有設置監聽接口的情況下,setRefreshing(true),調用不到OnLoadListener
recyclerView.setRefreshing(true);
}
}
8、適配器代碼
public class SwipeRecycleViewAdapter extends RecyclerView.Adapter<SwipeRecycleViewAdapter.ItemViewHolder> {
private Context context;
private List<String> data;
private LayoutInflater inflater;
public SwipeRecycleViewAdapter(Context context, List<String> data) {
this.context = context;
this.data = data;
inflater=LayoutInflater.from(context);
}
@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_swiperecyclerview_item, parent, false);
ItemViewHolder viewHolder=new ItemViewHolder(view);
return viewHolder;
}
@Override
public int getItemCount() {
return data==null?0:data.size();
}
@Override
public void onBindViewHolder(ItemViewHolder holder, final int position) {
holder.tv.setText(data.get(position));
holder.tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "您點擊的是"+data.get(position), Toast.LENGTH_SHORT).show();
}
});
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView tv;
public ItemViewHolder(View view) {
super(view);
tv = (TextView) view.findViewById(R.id.tv);
}
}
}