版權聲明:本文爲HaiyuKing原創文章,轉載請註明出處!java
封裝含有上拉加載功能的RecyclerView,而後搭配SwipeRefreshLayout實現下拉刷新、上拉加載功能。android
在項目中將原有的RecyclerView替換成WRecyclerView便可,不改動原有的adapter!git
本Demo中演示了下拉刷新和分頁功能,因此在將RecyclerView替換成WRecyclerView以後還須要添加其餘代碼(好比下拉刷新控件、無數據佈局區域、分頁相關代碼),具體見Demo。github
WRecyclerView:自定義RecyclerView子類,在不改動 RecyclerView 原有 adapter 的狀況下,使其擁有加載更多功能和自定義底部視圖;json
WRecyclerViewAdapter:自定義RecyclerView適配器;數組
WRecyclerViewFooter:自定義RecyclerView的底部上拉加載區域。服務器
注意事項:網絡
一、 導入類文件後須要change包名以及從新import R文件路徑app
二、 Values目錄下的文件(strings.xml、dimens.xml、colors.xml等),若是項目中存在,則複製裏面的內容,不要整個覆蓋ide
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.why.project.recyclerviewloadmoredemo"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//RecyclerView compile "com.android.support:recyclerview-v7:27.1.1"
}
一、建立Bean類
package com.why.project.recyclerviewloadmoredemo.bean; /** * Created by HaiyuKing * Used 列表項的bean類 */ public class NewsBean { private String newsId;//id值 private String newsTitle;//標題 public String getNewsId() { return newsId; } public void setNewsId(String newsId) { this.newsId = newsId; } public String getNewsTitle() { return newsTitle; } public void setNewsTitle(String newsTitle) { this.newsTitle = newsTitle; } }
二、建立Adapter以及item的佈局文件【後續不須要修改】
package com.why.project.recyclerviewloadmoredemo.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.why.project.recyclerviewloadmoredemo.R; import com.why.project.recyclerviewloadmoredemo.bean.NewsBean; import java.util.ArrayList; /** * Created by HaiyuKing * Used 列表適配器 */ public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{ /**上下文*/ private Context myContext; /**集合*/ private ArrayList<NewsBean> listitemList; /** * 構造函數 */ public NewsAdapter(Context context, ArrayList<NewsBean> itemlist) { myContext = context; listitemList = itemlist; } /** * 獲取總的條目數 */ @Override public int getItemCount() { return listitemList.size(); } /** * 建立ViewHolder */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(myContext).inflate(R.layout.news_list_item, parent, false); ItemViewHolder itemViewHolder = new ItemViewHolder(view); return itemViewHolder; } /** * 聲明grid列表項ViewHolder*/ static class ItemViewHolder extends RecyclerView.ViewHolder { public ItemViewHolder(View view) { super(view); listItemLayout = (LinearLayout) view.findViewById(R.id.listitem_layout); mChannelName = (TextView) view.findViewById(R.id.tv_channelName); } LinearLayout listItemLayout; TextView mChannelName; } /** * 將數據綁定至ViewHolder */ @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int index) { //判斷屬於列表項 if(viewHolder instanceof ItemViewHolder){ NewsBean newsBean = listitemList.get(index); final ItemViewHolder itemViewHold = ((ItemViewHolder)viewHolder); itemViewHold.mChannelName.setText(newsBean.getNewsTitle()); //若是設置了回調,則設置點擊事件 if (mOnItemClickLitener != null) { itemViewHold.listItemLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = itemViewHold.getLayoutPosition();//在增長數據或者減小數據時候,position和index就不同了 mOnItemClickLitener.onItemClick(itemViewHold.listItemLayout, position); } }); //長按事件 itemViewHold.listItemLayout.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { int position = itemViewHold.getLayoutPosition();//在增長數據或者減小數據時候,position和index就不同了 mOnItemClickLitener.onItemLongClick(itemViewHold.listItemLayout, position); return false; } }); } } } /** * 添加Item--用於動畫的展示*/ public void addItem(int position,NewsBean listitemBean) { listitemList.add(position,listitemBean); notifyItemInserted(position); } /** * 刪除Item--用於動畫的展示*/ public void removeItem(int position) { listitemList.remove(position); notifyItemRemoved(position); } /*=====================添加OnItemClickListener回調================================*/ public interface OnItemClickLitener { void onItemClick(View view, int position); void onItemLongClick(View view, int position); } private OnItemClickLitener mOnItemClickLitener; public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) { this.mOnItemClickLitener = mOnItemClickLitener; } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listitem_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_margin="1dp" android:background="#ffffff"> <TextView android:id="@+id/tv_channelName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="標題" android:textSize="18sp" android:padding="20dp"/> </LinearLayout>
三、在Activity佈局文件中引用Recyclerview控件【引入WRecyclerView以後,須要修改】
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#F4F4F4"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="#00000000" android:divider="@null" android:listSelector="#00000000" android:scrollbars="none" /> </RelativeLayout>
四、在Activity類中初始化recyclerview數據【此時只是最基本的寫法,後續還須要修改】
package com.why.project.recyclerviewloadmoredemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import com.why.project.recyclerviewloadmoredemo.adapter.NewsAdapter; import com.why.project.recyclerviewloadmoredemo.bean.NewsBean; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private ArrayList<NewsBean> mNewsBeanArrayList; private NewsAdapter mNewsAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initDatas(); initEvents(); } private void initViews() { mRecyclerView = findViewById(R.id.recycler_view); } private void initDatas() { //初始化集合 mNewsBeanArrayList = new ArrayList<NewsBean>(); for(int i=0; i<6;i++){ NewsBean newsBean = new NewsBean(); newsBean.setNewsId("123"+i); newsBean.setNewsTitle("標題"+i); mNewsBeanArrayList.add(newsBean); } //設置佈局管理器 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(linearLayoutManager); //設置適配器 if(mNewsAdapter == null){ //設置適配器 mNewsAdapter = new NewsAdapter(this, mNewsBeanArrayList); mRecyclerView.setAdapter(mNewsAdapter); //添加分割線 //設置添加刪除動畫 //調用ListView的setSelected(!ListView.isSelected())方法,這樣就能及時刷新佈局 mRecyclerView.setSelected(true); }else{ mNewsAdapter.notifyDataSetChanged(); } } private void initEvents() { //列表適配器的點擊監聽事件 mNewsAdapter.setOnItemClickLitener(new NewsAdapter.OnItemClickLitener() { @Override public void onItemClick(View view, int position) { } @Override public void onItemLongClick(View view, int position) { } }); } }
一、將recyclerview包複製到項目中
package com.why.project.recyclerviewloadmoredemo.recyclerview; 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.util.Log; import android.view.MotionEvent; import android.view.View; /** * Used 自定義RecyclerView,在不改動 RecyclerView 原有 adapter 的狀況下,使其擁有加載更多功能和自定義底部視圖。 * 參考資料:https://github.com/nukc/LoadMoreWrapper * @touch執行順序:dispatchTouchEvent(Action_down)——onScrollStateChanged==RecyclerView.SCROLL_STATE_DRAGGING[1]——【onTouchEvent(Action_move)——onScrolled】循環——onTouchEvent(Action_up)——onScrollStateChanged==RecyclerView.SCROLL_STATE_IDLE[0] */ public class WRecyclerView extends RecyclerView { private static final String TAG = WRecyclerView.class.getSimpleName(); /**自定義適配器,用於在基礎列表數據的基礎上添加底部區域*/ private WRecyclerViewAdapter wRecyclerViewAdapter; /**自定義上拉加載的監聽器*/ private WRecyclerViewListener mRecyclerListener; /**===================底部--上拉加載區域========================*/ private WRecyclerViewFooter mFooterView; /**是否啓用上拉加載功能的標記:true-啓用;false-禁用*/ private boolean mEnablePullLoad = true; /**上拉加載區域是否正在顯示的標記*/ private boolean isShowFooter = false; /**是否處於加載狀態的標記:true-加載;false-正常*/ private boolean mPullLoading = false; /**當前是否處於快速滑動的狀態 :默認爲false*/ private boolean isQuickSlide = false; /**當前是否處於下拉刷新的狀態 :默認爲false*/ private boolean isPullRefresh = false; /**快速移動產生慣性的移動距離值(自定義的臨界值)*/ private final static int FAST_MOVE_DY = 150;//原先是100 /**recyclerView最後一個可見的item的下標值*/ private int lastVisibleItem = 0; /**recyclerView總item數目*/ private int mTotalItemCount = 0; public WRecyclerView(Context context) { super(context); initWithContext(context); } public WRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); initWithContext(context); } public WRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initWithContext(context); } private void initWithContext(Context context) { mFooterView = new WRecyclerViewFooter(context); /*解決bug: * 使用 RecyclerView 加官方下拉刷新的時候,若是綁定的 List 對象在更新數據以前進行了 clear,而這時用戶緊接着迅速上滑 RV,就會形成崩潰,並且異常不會報到你的代碼上,屬於RV內部錯誤。 * 初次猜想是,當你 clear 了 list 以後,這時迅速上滑,而新數據還沒到來,致使 RV 要更新加載下面的 Item 時候,找不到數據源了,形成 crash. * https://blog.csdn.net/lvwenbo0107/article/details/52290536*/ this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (isPullRefresh) {//若是正在刷新,則不能滑動 return true; } else { return false; } } }); /**RecyclerView的滾動監聽器*/ this.addOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); Log.w(TAG,"{onScrollStateChanged}newState="+newState); switch (newState){ case RecyclerView.SCROLL_STATE_DRAGGING:/*當前的recycleView被拖動滑動,相似action_down的效果1*/ isQuickSlide = false;//每一次開始滑動以前,設置快速滑動狀態值爲false if(mEnablePullLoad){ mFooterView.show();//由於快速滑動產生慣性的時候會進行隱藏底部上拉加載區域,因此須要在下次正常滑動以前在這裏從新顯示FootView } break; case RecyclerView.SCROLL_STATE_SETTLING:/*當前的recycleView在滾動到某個位置的動畫過程,但沒有被觸摸滾動.調用 scrollToPosition(int) 應該會觸發這個狀態2*/ break; case RecyclerView.SCROLL_STATE_IDLE:/*中止滑動狀態0*/ //isQuickSlide = false;//解開這個註釋,就是無論快速滑動仍是正常滑動,只要顯示最後一條列表,就會自動加載數據 if(isShowFooter && !mPullLoading && !isQuickSlide){//也就是手指慢慢滑動出來foot區域的狀況 startLoadMore(); //中止滑動後,若是上拉加載區域正在顯示而且沒有處於正在加載狀態而且不是快速滑動狀態,那麼就開始加載 }else{ mFooterView.setState(WRecyclerViewFooter.STATE_NORMAL);//底部區域顯示【查看更多】 } break; } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); //獲取當前顯示的最後一個子項下標值 LayoutManager layoutManager = recyclerView.getLayoutManager(); mTotalItemCount = layoutManager.getItemCount(); if (layoutManager instanceof GridLayoutManager) { lastVisibleItem = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] lastPositions = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(lastPositions); int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } lastVisibleItem = max; } else { lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } /* * 對於正常移動的狀況,若是到了最後一個子項(底部上拉加載區域),而且啓用了上拉加載功能 * */ if(mEnablePullLoad){ if(lastVisibleItem >= mTotalItemCount - 1) {//【實現鬆開手指後纔會加載,暫時有問題--貌似沒有問題了】 //if(lastVisibleItem >= mTotalItemCount - 1 - 1 ) {//減去的1,表明的footView,再減去的1表明下標值(0開始)【實現滑動到底部自動加載】 if(dy > 0){//向下滑動 isShowFooter = true; /* * 若是移動的距離大於FAST_MOVE_DY,則代表當前處於快速滑動產生慣性的狀況,則隱藏底部上拉加載區域,這樣就控制了底部上拉加載區域的不正常顯示*/ if(dy > FAST_MOVE_DY){//當快速移動,產生慣性的時候,隱藏底部上拉加載區域 mFooterView.hide(); isQuickSlide = true;//只要有一次滑動的距離超過快速滑動臨界值,則表明當前處於快速滑動狀態 }else{ Log.w(TAG, "{onScrolled}mFooterView.getBottomMargin()="+mFooterView.getBottomMargin()); mFooterView.setState(WRecyclerViewFooter.STATE_READY);//底部區域顯示【上拉加載更多】 } }else{ //向上滑動,不作任何處理 } }else { /*若是尚未到最後一個子項,前提條件是啓用了上拉加載功能 * 則:(1)設置上拉加載區域顯示狀態值爲false * (2)隱藏上拉加載區域*/ isShowFooter = false; mFooterView.hide(); } } } }); } /** * 設置適配器 * @param adapter */ public void setAdapter(Adapter adapter) { wRecyclerViewAdapter = new WRecyclerViewAdapter(adapter, mFooterView, mEnablePullLoad); super.setAdapter(wRecyclerViewAdapter); } /**是否正在上拉加載*/ public boolean ismPullLoading() { return mPullLoading; } /*==================================上拉加載功能==========================================*/ /** * 設置啓用或者禁用上拉加載功能 * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { //禁用上拉加載功能 mFooterView.hide(); mFooterView.setOnClickListener(null); } else { //啓用上拉加載功能 mPullLoading = false; mFooterView.show(); mFooterView.setState(WRecyclerViewFooter.STATE_NORMAL); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } /**解決當第一頁就顯示出來footview的時候,下拉刷新崩潰的問題 * java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true * 是指view沒有被recycled(回收),也就是foot區域沒有被回收 * https://blog.csdn.net/u013106366/article/details/54024113*/ if(wRecyclerViewAdapter != null){ wRecyclerViewAdapter.setmEnablePullLoad(mEnablePullLoad); Log.w(TAG,"wRecyclerViewAdapter.notifyDataSetChanged()"); wRecyclerViewAdapter.notifyDataSetChanged(); } } /**開始加載,顯示加載狀態*/ private void startLoadMore() { if(mEnablePullLoad){ if(mPullLoading)return; mPullLoading = true; mFooterView.setState(WRecyclerViewFooter.STATE_LOADING); if (mRecyclerListener != null) { mRecyclerListener.onLoadMore(); } } } /** * 中止加載,還原到正常狀態 */ public void stopLoadMore() { if(mEnablePullLoad){//啓用上拉加載功能 if (mPullLoading == true) {//若是處於加載狀態 mPullLoading = false; mFooterView.setState(WRecyclerViewFooter.STATE_NORMAL); mFooterView.hide(); } } } /**是不是否處於下拉刷新的狀態*/ public void setPullRefresh(boolean pullRefresh) { isPullRefresh = pullRefresh; } /*==================================自定義下拉刷新和上拉加載的監聽器==========================================*/ /** * 自定義下拉刷新和上拉加載的監聽器 */ public interface WRecyclerViewListener { /**上拉加載*/ public void onLoadMore(); } public void setWRecyclerListener(WRecyclerViewListener l) { mRecyclerListener = l; } }
package com.why.project.recyclerviewloadmoredemo.recyclerview; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.AdapterDataObserver; import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.View; import android.view.ViewGroup; /** * Used 自定義RecyclerView適配器 */ public class WRecyclerViewAdapter extends RecyclerView.Adapter<ViewHolder>{ private static final String TAG = WRecyclerViewAdapter.class.getSimpleName(); private RecyclerView.Adapter adapter; /**是否啓用上拉加載功能的標記:true-使用;false-禁用*/ private boolean mEnablePullLoad; /**上拉加載區域(foot區域)*/ private WRecyclerViewFooter mFooterView; //下面的ItemViewType是保留值(ReservedItemViewType),若是用戶的adapter與它們重複將會強制拋出異常。不過爲了簡化,咱們檢測到重複時對用戶的提示是ItemViewType必須小於10000 private static final int TYPE_FOOTER = 100001;//設置一個很大的數字,儘量避免和用戶的adapter衝突 private static final int TYPE_ITEM = 100002; public WRecyclerViewAdapter(RecyclerView.Adapter adapter, WRecyclerViewFooter mFooterView, boolean mEnablePullLoad) { this.adapter = adapter; this.mFooterView = mFooterView; this.mEnablePullLoad = mEnablePullLoad; } /** * 獲取總的條目數 */ @Override public int getItemCount() { int itemCount = adapter.getItemCount(); if(mEnablePullLoad) {//若是啓用上拉加載區域,那麼就須要在原來的列表總數基礎上加1 itemCount = itemCount + 1; } return itemCount; } @Override public int getItemViewType(int position) { if (isFooter(position)) { return TYPE_FOOTER; }else{ // return TYPE_ITEM;//不能return TYPE_ITEM,由於adapter中可能會設置不一樣的類型 return adapter.getItemViewType(position); } } /** * 判斷是否屬於上拉加載區域-即最後一行 */ public boolean isFooter(int position) { if(mEnablePullLoad) {//若是啓用上拉加載區域,那麼最後一行,就是總數目- 1 return position == getItemCount() - 1; }else { return false; } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_FOOTER) { return new SimpleViewHolder(mFooterView); } return adapter.onCreateViewHolder(parent, viewType); } /**簡單的ViewHolder*/ private class SimpleViewHolder extends ViewHolder { public SimpleViewHolder(View itemView) { super(itemView); } } /** * 將數據綁定至ViewHolder */ @Override public void onBindViewHolder(final ViewHolder holder, int position) { int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (position < adapterCount) { adapter.onBindViewHolder(holder, position); return; } } } @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 isFooter(position) ? gridManager.getSpanCount() : 1; } }); } adapter.onAttachedToRecyclerView(recyclerView); } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { adapter.onDetachedFromRecyclerView(recyclerView); } @Override public void onViewAttachedToWindow(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); } adapter.onViewAttachedToWindow(holder); } @Override public void onViewDetachedFromWindow(ViewHolder holder) { adapter.onViewDetachedFromWindow(holder); } @Override public void onViewRecycled(ViewHolder holder) { adapter.onViewRecycled(holder); } @Override public boolean onFailedToRecycleView(ViewHolder holder) { return adapter.onFailedToRecycleView(holder); } @Override public void unregisterAdapterDataObserver(AdapterDataObserver observer) { adapter.unregisterAdapterDataObserver(observer); } @Override public void registerAdapterDataObserver(AdapterDataObserver observer) { adapter.registerAdapterDataObserver(observer); } public boolean ismEnablePullLoad() { return mEnablePullLoad; } /**解決當第一頁顯示出來footview的時候刷新崩潰的問題*/ public void setmEnablePullLoad(boolean mEnablePullLoad) { this.mEnablePullLoad = mEnablePullLoad; } }
package com.why.project.recyclerviewloadmoredemo.recyclerview; import android.content.Context; import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.why.project.recyclerviewloadmoredemo.R; /** * Used 自定義RecyclerView的底部上拉加載區域 */ public class WRecyclerViewFooter extends LinearLayout { private Context mContext; /**正常狀態*/ public final static int STATE_NORMAL = 0; /**準備狀態*/ public final static int STATE_READY = 1; /**加載狀態*/ public final static int STATE_LOADING = 2; /**根節點*/ private View mContentView; /**含有進度條的佈局區域*/ private View mProgressBarLayout; /**提示文字View*/ private TextView mHintView; public WRecyclerViewFooter(Context context) { super(context); initView(context); } public WRecyclerViewFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } /**初始化*/ private void initView(Context context) { mContext = context; //添加底部上拉加載區域佈局文件 LinearLayout moreView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.wrecyclerview_footer, null); addView(moreView); moreView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); //實例化組件 mContentView = moreView.findViewById(R.id.wrecyclerview_footer_content); mProgressBarLayout = moreView.findViewById(R.id.wrecyclerview_footer_progressbar_layout); mHintView = (TextView)moreView.findViewById(R.id.wrecyclerview_footer_hint_textview); } /**更改加載狀態: * @param state - STATE_NORMAL(0),STATE_READY(1),STATE_LOADING(2)*/ public void setState(int state) { //首先,將提示文字和進度條區域初始化隱藏 mHintView.setVisibility(View.INVISIBLE); mProgressBarLayout.setVisibility(View.INVISIBLE); //而後,根據狀態值進行顯示,隱藏這兩個區域 if (state == STATE_READY) { //準備狀態 mHintView.setVisibility(View.VISIBLE); Drawable drawable = ContextCompat.getDrawable(mContext,R.drawable.wrecyclerview_icon_pull); //setCompoundDrawables 畫的drawable的寬高是按drawable.setBound()設置的寬高 //而setCompoundDrawablesWithIntrinsicBounds是畫的drawable的寬高是按drawable固定的寬高,即經過getIntrinsicWidth()與getIntrinsicHeight()自動得到 drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); mHintView.setCompoundDrawables(null, drawable, null, null); mHintView.setText(R.string.wrecyclerview_footer_hint_ready); } else if (state == STATE_LOADING) { //加載狀態 mProgressBarLayout.setVisibility(View.VISIBLE); } else { //正常狀態 mHintView.setVisibility(View.VISIBLE); mHintView.setCompoundDrawables(null, null, null, null); mHintView.setText(R.string.wrecyclerview_footer_hint_normal); } } /** * 當禁用上拉加載功能的時候隱藏底部區域 */ public void hide() { LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); lp.height = 0;//這裏設爲0,那麼雖然是顯示,可是看不到 mContentView.setLayoutParams(lp); } /** * 顯示底部上拉加載區域 */ public void show() { LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); lp.height = LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } /**設置佈局的底外邊距【暫時沒有用到】*/ public void setBottomMargin(int height) { if (height < 0) return ; LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } /**獲取佈局的底外邊距【暫時沒有用到】*/ public int getBottomMargin() { LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); return lp.bottomMargin; } }
二、將wrecyclerview_footer_progressbar.xml複製到項目中
<?xml version="1.0" encoding="utf-8"?> <!-- WRecyclerView上拉加載的進度條動畫 --> <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/wrecyclerview_footer_loading_rotate" android:pivotX="50%" android:pivotY="50%" />
三、將圖片資源複製到項目中
四、將wrecyclerview_footer.xml複製到項目中
<?xml version="1.0" encoding="utf-8"?> <!-- WRecyclerView底部上拉加載的佈局文件 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/wrecyclerview_footer_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@color/wrecyclerview_footer_bg_color" android:gravity="center" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/wrecyclerview_footer_margin" android:paddingBottom="@dimen/wrecyclerview_footer_margin"> <!-- 正在加載的佈局,默認隱藏 --> <LinearLayout android:id="@+id/wrecyclerview_footer_progressbar_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="invisible" > <!-- 自定義圓形進度條 --> <!-- android:indeterminateDrawable自定義動畫圖標 --> <ProgressBar android:layout_width="@dimen/wrecyclerview_footer_progressbar_WH" android:layout_height="@dimen/wrecyclerview_footer_progressbar_WH" android:indeterminateDrawable="@drawable/wrecyclerview_footer_progressbar" /> <!-- 正在加載 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/wrecyclerview_footer_margin" android:text="@string/wrecyclerview_footer_hint_loading" android:textColor="@color/wrecyclerview_footer_loading_text_color" android:textSize="@dimen/wrecyclerview_footer_text_size" /> </LinearLayout> <!-- 上拉加載更多的佈局 (用來進行提示文字展示)--> <TextView android:id="@+id/wrecyclerview_footer_hint_textview" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="@string/wrecyclerview_footer_hint_normal" android:textColor="@color/wrecyclerview_footer_hint_text_color" android:textSize="@dimen/wrecyclerview_footer_text_size" android:drawableTop="@drawable/wrecyclerview_icon_pull" /> </RelativeLayout> </LinearLayout>
五、在colors.xml文件中添加如下代碼
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <!-- *********************SwipeRefreshLayout進度條顏色********************* --> <color name="swiperefresh_color_1">#5DB5F4</color> <color name="swiperefresh_color_2">#4E93D7</color> <color name="swiperefresh_color_3">#3689EE</color> <color name="swiperefresh_color_4">#5588FF</color> <!-- ************自定義WRecyclerview************ --> <color name="wrecyclerview_footer_bg_color">#EDEFF1</color> <color name="wrecyclerview_footer_hint_text_color">#70747E</color> <color name="wrecyclerview_footer_loading_text_color">#ABB1BD</color> <color name="wrecyclerview_divider_color">#eeeeee</color> </resources>
六、在dimens.xml文件中添加如下代碼
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- ************自定義WRecyclerview************ --> <dimen name="wrecyclerview_footer_margin">10dp</dimen> <dimen name="wrecyclerview_footer_progressbar_WH">24dp</dimen> <dimen name="wrecyclerview_footer_text_size">17sp</dimen> </resources>
七、在strings.xml文件中添加如下代碼
<resources> <string name="app_name">RecyclerViewLoadMoreDemo</string> <!-- ************自定義WRecyclerview************ --> <string name="wrecyclerview_footer_hint_normal">查看更多</string> <string name="wrecyclerview_footer_hint_ready">上拉加載更多</string> <string name="wrecyclerview_footer_hint_loading">正在拼命加載中…</string> </resources>
{
"data": [
{
"newsId": "0001",
"newsTitle": "標題1"
},
{
"newsId": "0002",
"newsTitle": "標題2"
},
{
"newsId": "0003",
"newsTitle": "標題3"
},
{
"newsId": "0004",
"newsTitle": "標題4"
},
{
"newsId": "0005",
"newsTitle": "標題5"
},
{
"newsId": "0006",
"newsTitle": "標題6"
},
{
"newsId": "0007",
"newsTitle": "標題7"
},
{
"newsId": "0008",
"newsTitle": "標題8"
},
{
"newsId": "0009",
"newsTitle": "標題9"
},
{
"newsId": "0010",
"newsTitle": "標題10"
}
],
"flag": "success",
"msg": "操做成功",
"total": 18
}
<?xml version="1.0" encoding="utf-8"?> <!-- 無數據的佔位圖佈局文件 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/nodata_layout" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center"> <!-- 圖片顯示 --> <ImageView android:id="@+id/img_nodata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" android:adjustViewBounds="true" android:contentDescription="@string/app_name" android:scaleType="centerCrop" android:layout_gravity="center" /> <!-- 提示語 --> <TextView android:id="@+id/tv_nodata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="暫無數據" android:textSize="16sp" android:textColor="#BADCFB" android:layout_gravity="center" android:layout_marginTop="8dp"/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#F4F4F4"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/list_swiperefreshlayout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.why.project.recyclerviewloadmoredemo.recyclerview.WRecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="#00000000" android:divider="@null" android:listSelector="#00000000" android:scrollbars="none" /> </android.support.v4.widget.SwipeRefreshLayout> <!-- 無數據區域 --> <include layout="@layout/public_skeleton_screen_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> </RelativeLayout>
package com.why.project.recyclerviewloadmoredemo; import android.content.Context; 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.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.Toast; import com.why.project.recyclerviewloadmoredemo.adapter.NewsAdapter; import com.why.project.recyclerviewloadmoredemo.bean.NewsBean; import com.why.project.recyclerviewloadmoredemo.recyclerview.WRecyclerView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private Context mContext; /**下拉刷新組件*/ private SwipeRefreshLayout swipe_container; private WRecyclerView mRecyclerView; private ArrayList<NewsBean> listitemList; private NewsAdapter mNewsAdapter; private int curPageIndex = 1;//當前頁數 private int totalPage = 1;//總頁數 private int pageSize = 10;//每一頁的列表項數目 private int position = 1;//序號 private LinearLayout nodata_layout;//暫無數據區域 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; initViews(); initSwipeRefreshView();//初始化SwipeRefresh刷新控件 initDatas(); } private void initViews() { mRecyclerView = findViewById(R.id.recycler_view); nodata_layout = findViewById(R.id.nodata_layout); } /** * 初始化SwipeRefresh刷新控件 */ private void initSwipeRefreshView() { swipe_container = (SwipeRefreshLayout) findViewById(R.id.list_swiperefreshlayout); //設置進度條的顏色主題,最多能設置四種 swipe_container.setColorSchemeResources(R.color.swiperefresh_color_1, R.color.swiperefresh_color_2, R.color.swiperefresh_color_3, R.color.swiperefresh_color_4); //調整進度條距離屏幕頂部的距離 scale:true則下拉的時候從小變大 swipe_container.setProgressViewOffset(true, 0, dip2px(mContext,10)); } /** * dp轉px * 16dp - 48px * 17dp - 51px*/ private int dip2px(Context context, float dpValue) { float scale = context.getResources().getDisplayMetrics().density; return (int)((dpValue * scale) + 0.5f); } private void initDatas() { //===================網絡請求獲取列表===================== //解決在第二頁的時候進行查詢的時候返回到該界面時顯示的仍是第二頁數據 position = 1; curPageIndex = 1; mNewsAdapter = null;//將列表適配器置空,不然查詢界面點擊肯定按鈕的時候沒法更新列表數據 //初始化集合 listitemList = new ArrayList<NewsBean>(); //設置佈局管理器 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(linearLayoutManager); initListData(); } //初始化第一頁數據 private void initListData() { //模擬網絡請求返回數據 getBackDataList(curPageIndex,pageSize); } private void getBackDataList(int curPageIndex, int pageSize) { //模擬網絡請求獲取數據(通常curPageIndex、pageSize都須要傳過去,這裏是模擬,因此沒有用到pageSize) String responseStr = getStringFromAssert(MainActivity.this,"pagenum"+curPageIndex + ".txt"); //模擬網絡請求的回調方法 onBefore(); onResponse(responseStr); new Handler().postDelayed(new Runnable() { @Override public void run() { onAfter();//模擬網絡請求必定的延遲後執行 } },2000); } private void onBefore() { if(!swipe_container.isRefreshing()) {//實現當下拉刷新的時候,不須要顯示加載對話框 Toast.makeText(mContext,"顯示加載框",Toast.LENGTH_SHORT).show(); //showProgressDialog();//顯示進度加載框 } } private void onResponse(String response) { try { if (response != null && !"".equals(response) && !"{}".equals(response)){ if(response.startsWith("jsonp(")){ response = response.substring(6,response.length() - 1); } JSONObject responseObj = new JSONObject(response); if(responseObj.getString("flag").equals("success")){ JSONArray listArray = responseObj.getJSONArray("data"); if(listArray.length() > 0){//若是有篩選功能,則須要使用大於等於,若是沒有,只是單純的刷新,則使用大於便可【不過由於fail的狀況下顯示無數據區域,因此此處去掉==0的狀況】 //計算總頁數 int totalPage = responseObj.getInt("total") % pageSize == 0 ? responseObj.getInt("total") / pageSize : responseObj.getInt("total") / pageSize + 1;//計算總頁數 ArrayList<NewsBean> listitemList_temp = new ArrayList<NewsBean>(); for(int i=0;i<listArray.length();i++){ JSONObject listItemObj = listArray.getJSONObject(i); NewsBean newsBean = new NewsBean(); newsBean.setNewsId(listItemObj.getString("newsId")); newsBean.setNewsTitle(listItemObj.getString("newsTitle")); listitemList_temp.add(newsBean); } showList(totalPage,listitemList_temp); }else { showListFail("數據內容爲空"); } }else { showListFail("數據內容爲空"); } } else { showListFail("數據內容爲空"); } }catch (JSONException e) { Toast.makeText(mContext,"服務器數據解析異常,請聯繫管理員!",Toast.LENGTH_SHORT).show(); }catch (Exception e) { Toast.makeText(mContext,"服務器數據解析異常,請聯繫管理員!",Toast.LENGTH_SHORT).show(); } } //顯示列表 public void showList(int totalPage, List<NewsBean> mNewsBeanList){ this.totalPage = totalPage; if(curPageIndex == 1){ listitemList.clear();//下拉刷新,須要清空集合,由於刷新的是第一頁數據【解決下拉刷新後馬上上拉加載崩潰的bug,方案二】 } if(mNewsBeanList != null && mNewsBeanList.size() > 0) { switchNoDataVisible(false);//顯示列表,隱藏暫無數據區域 for (NewsBean newsBean : mNewsBeanList) { listitemList.add(newsBean); } } } private void onAfter() { Toast.makeText(mContext,"隱藏加載框",Toast.LENGTH_SHORT).show(); //dismissProgressDialog();//隱藏進度加載框 if(curPageIndex == 1){//若是首頁數據爲空或者小於每頁展示的條數,則禁用上拉加載功能 if(listitemList.size() < pageSize){ mRecyclerView.setPullLoadEnable(false);//禁用上拉加載功能 }else{ mRecyclerView.setPullLoadEnable(true);//啓用上拉加載功能 } } //設置適配器 if(mNewsAdapter == null){ //設置適配器 mNewsAdapter = new NewsAdapter(this, listitemList); mRecyclerView.setAdapter(mNewsAdapter); //添加分割線 //設置添加刪除動畫 //調用ListView的setSelected(!ListView.isSelected())方法,這樣就能及時刷新佈局 mRecyclerView.setSelected(true); }else{ mNewsAdapter.notifyDataSetChanged(); } stopRefreshAndLoading();//中止刷新和上拉加載 //初始化監聽事件 initEvents(); } //獲取列表數據失敗 public void showListFail(String msg) { //當第一頁數據爲空的時候,纔會執行 if(curPageIndex == 1){ //擴展:顯示無數據佔位圖 switchNoDataVisible(true); }else{ Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show(); } } /**切換無數據和列表展示/隱藏*/ private void switchNoDataVisible(boolean showNoData){ if(showNoData){ nodata_layout.setVisibility(View.VISIBLE); swipe_container.setVisibility(View.GONE); }else if(nodata_layout.getVisibility() == View.VISIBLE){ nodata_layout.setVisibility(View.GONE); swipe_container.setVisibility(View.VISIBLE); } } private void initEvents() { //爲SwipeRefreshLayout佈局添加一個Listener,下拉刷新 swipe_container.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refreshList();//刷新列表 } }); //自定義上拉加載的監聽 mRecyclerView.setWRecyclerListener(new WRecyclerView.WRecyclerViewListener() { @Override public void onLoadMore() { Log.w(TAG, "onLoadMore-正在加載"); curPageIndex = curPageIndex + 1; if (curPageIndex <= totalPage) { initListData();//更新列表項集合 } else { //到達最後一頁了 Toast.makeText(mContext,"我也是有底線滴",Toast.LENGTH_SHORT).show(); //隱藏正在加載的區域 stopRefreshAndLoading(); } } }); //列表適配器的點擊監聽事件 mNewsAdapter.setOnItemClickLitener(new NewsAdapter.OnItemClickLitener() { @Override public void onItemClick(View view, int position) { } @Override public void onItemLongClick(View view, int position) { } }); } /**刷新列表*/ private void refreshList() { mRecyclerView.setPullLoadEnable(false);//禁用上拉加載功能 mRecyclerView.setPullRefresh(true);//設置處於下拉刷新狀態中 curPageIndex = 1; position = 1; //listitemList.clear();//下拉刷新,須要清空集合,由於刷新的是第一頁數據【解決下拉刷新後馬上上拉加載崩潰的bug,方案二】 initListData();//更新列表項集合 } /** * 中止刷新和上拉加載 */ private void stopRefreshAndLoading() { //檢查是否處於刷新狀態 if(swipe_container.isRefreshing()){ //顯示或隱藏刷新進度條,通常是在請求數據的時候設置爲true,在數據被加載到View中後,設置爲false。 swipe_container.setRefreshing(false); } //若是正在加載,則獲取數據後中止加載動畫 if(mRecyclerView.ismPullLoading()){ mRecyclerView.stopLoadMore();//中止加載動畫 } mRecyclerView.setPullRefresh(false);//設置處於下拉刷新狀態中[否] } /*===========讀取assets目錄下的js字符串文件(js數組和js對象)===========*/ /** * 訪問assets目錄下的資源文件,獲取文件中的字符串 * @param filePath - 文件的相對路徑,例如:"listdata.txt"或者"/www/listdata.txt" * @return 內容字符串 * */ public String getStringFromAssert(Context mContext, String filePath) { String content = ""; // 結果字符串 try { InputStream is = mContext.getResources().getAssets().open(filePath);// 打開文件 int ch = 0; ByteArrayOutputStream out = new ByteArrayOutputStream(); // 實現了一個輸出流 while ((ch = is.read()) != -1) { out.write(ch); // 將指定的字節寫入此 byte 數組輸出流 } byte[] buff = out.toByteArray();// 以 byte 數組的形式返回此輸出流的當前內容 out.close(); // 關閉流 is.close(); // 關閉流 content = new String(buff, "UTF-8"); // 設置字符串編碼 } catch (Exception e) { Toast.makeText(mContext, "對不起,沒有找到指定文件!", Toast.LENGTH_SHORT) .show(); } return content; } }
無
Error: Inconsistency detected. Invalid item position 11(offset:11).state:37 RecyclerView
Scrapped or attached views may not be recycled
手把手教你實現RecyclerView的下拉刷新和上拉加載更多
你必須瞭解的RecyclerView的五大開源項目-解決上拉加載、下拉刷新和添加Header、Footer等問題