RecyclerView是Android5.0以后推出的新控件,相比于ListView可定制性更大,大有取代ListView之势。下面这篇博客主要来实现RecyclerView的上拉加载更多功能。
基本思路是让RecyclerView的Adapter加载两种布局,第一个布局来显示主界面,第二个布局来显示上拉加载时的提示信息,让RecyclerView监听是否滑动到最后一个item,如果是,则调用上拉刷新的逻辑,拉取远程数据,并显示第二个布局。等加载完毕时,刷新
Adapter,并隐藏第二个布局。下面分析代码。
要加载两种不同的布局,Adapter要重写getItemViewType方法。
@Override public int getItemViewType(int position) { if (position == dataList.size()) return 1; else return 0; }
onCreateViewHolder根据viewtype加载ViewHolder.
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; if (viewType == 0) { view = LayoutInflater.from(context).inflate(R.layout.grid_redu_item, parent, false); return new NormalHolder(view); } else { view = LayoutInflater.from(context).inflate(R.layout.sample_common_list_footer, parent, false); mFooterHolder = new FooterHolder(view); return mFooterHolder; } }
注意getItemCount长度要加1。
@Override public int getItemCount() { return dataList.size() + 1; }
NormalHolder
1 class NormalHolder extends RecyclerView.ViewHolder { 2 ImageView img; 3 TextView name; 4 5 public NormalHolder(View itemView) { 6 super(itemView); 7 img = (ImageView) itemView.findViewById(R.id.homepage_grid_picpic); 8 name = (TextView) itemView.findViewById(R.id.homepage_grid_name); 9 } 10 11 public void setData(int position) { 12 imageLoader.displayImage(context, imgUrls[position % imgUrls.length], img); 13 name.setText(dataList.get(position)); 14 } 15 }
FooterHolder
1 public class FooterHolder extends RecyclerView.ViewHolder { 2 View mLoadingViewstubstub; 3 View mEndViewstub; 4 View mNetworkErrorViewstub; 5 6 public FooterHolder(View itemView) { 7 super(itemView); 8 mLoadingViewstubstub = itemView.findViewById(R.id.loading_viewstub); 9 mEndViewstub = itemView.findViewById(R.id.end_viewstub); 10 mNetworkErrorViewstub = itemView.findViewById(R.id.network_error_viewstub); 11 } 12 13 //根据传过来的status控制哪个状态可见 14 public void setData(LoadingFooter.FooterState status) { 15 Log.d("TAG", "reduAdapter" + status + ""); 16 switch (status) { 17 case Normal: 18 setAllGone(); 19 break; 20 case Loading: 21 setAllGone(); 22 mLoadingViewstubstub.setVisibility(View.VISIBLE); 23 break; 24 case TheEnd: 25 setAllGone(); 26 mEndViewstub.setVisibility(View.VISIBLE); 27 break; 28 case NetWorkError: 29 setAllGone(); 30 mNetworkErrorViewstub.setVisibility(View.VISIBLE); 31 break; 32 default: 33 break; 34 } 35 36 } 37 38 //全部不可见 39 void setAllGone() { 40 if (mLoadingViewstubstub != null) { 41 mLoadingViewstubstub.setVisibility(View.GONE); 42 } 43 if (mEndViewstub != null) { 44 mEndViewstub.setVisibility(View.GONE); 45 } 46 if (mNetworkErrorViewstub != null) { 47 mNetworkErrorViewstub.setVisibility(View.GONE); 48 } 49 } 50 51 }
FooterHolder布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/loading_view" android:layout_width="match_parent" android:layout_height="40dp" android:gravity="center" android:orientation="vertical" tools:layout_height="wrap_content"> <include android:id="@+id/loading_viewstub" layout="@layout/sample_common_list_footer_loading" android:layout_width="match_parent" android:layout_height="40dp" /> <include android:id="@+id/end_viewstub" layout="@layout/sample_common_list_footer_end" android:layout_width="match_parent" android:layout_height="40dp" /> <include android:id="@+id/network_error_viewstub" layout="@layout/sample_common_list_footer_network_error" android:layout_width="match_parent" android:layout_height="40dp" /> </LinearLayout>
显示效果:
主要逻辑放在Fragment里。
首先我们要绑定为RecyclerView绑定一个监听器,监听RecyclerView是否滑到了底部。
Listener代码:
package com.yctime.truelove.LoadMore; 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.view.View; import com.yctime.truelove.ImageLoader.UILPauseOnScrollListener; /** * Created by xjx * <p/> * 继承自RecyclerView.OnScrollListener,一:可以监听到是否滑动到页面最低部。二:滑动时停止加载图片 */ public class EndlessRecyclerOnScrollListener extends UILPauseOnScrollListener { /** * 当前RecyclerView类型 */ protected LayoutManagerType layoutManagerType; /** * 最后一个的位置 */ private int[] lastPositions; /** * 最后一个可见的item的位置 */ private int lastVisibleItemPosition; /** * 当前滑动的状态 */ private int currentScrollState = 0; @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManagerType == null) { if (layoutManager instanceof LinearLayoutManager) { layoutManagerType = LayoutManagerType.LinearLayout; } else if (layoutManager instanceof GridLayoutManager) { layoutManagerType = LayoutManagerType.GridLayout; } else if (layoutManager instanceof StaggeredGridLayoutManager) { layoutManagerType = LayoutManagerType.StaggeredGridLayout; } else { throw new RuntimeException( "Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager"); } } switch (layoutManagerType) { case LinearLayout: lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); break; case GridLayout: lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); break; case StaggeredGridLayout: StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; if (lastPositions == null) { lastPositions = new int[staggeredGridLayoutManager.getSpanCount()]; } staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions); lastVisibleItemPosition = findMax(lastPositions); break; } } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); currentScrollState = newState; RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); int visibleItemCount = layoutManager.getChildCount(); int totalItemCount = layoutManager.getItemCount(); if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && (lastVisibleItemPosition) >= totalItemCount - 1)) { onLoadNextPage(recyclerView); } } /** * 取数组中最大值 * * @param lastPositions * @return */ private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max; } public void onLoadNextPage(final View view) { } public static enum LayoutManagerType { LinearLayout, StaggeredGridLayout, GridLayout } }
下面是Fragment完整代码:
1 package com.yctime.truelove.fragment; 2 3 4 import android.support.v4.app.Fragment; 5 import android.support.v7.widget.DefaultItemAnimator; 6 import android.support.v7.widget.GridLayoutManager; 7 import android.support.v7.widget.LinearLayoutManager; 8 import android.support.v7.widget.RecyclerView; 9 import android.util.Log; 10 import android.view.LayoutInflater; 11 import android.view.View; 12 13 import com.yctime.truelove.LoadMore.EndlessRecyclerOnScrollListener; 14 import com.yctime.truelove.LoadMore.LoadingFooter; 15 import com.yctime.truelove.MainActivity; 16 import com.yctime.truelove.Utils.NetworkUtils; 17 import com.yctime.truelove.login.R; 18 19 import java.util.ArrayList; 20 21 22 /** 23 * A simple {@link Fragment} subclass. 24 */ 25 public class HomeReDuFragment extends BaseFragment { 26 27 private RecyclerView mRecyclerView; 28 private GridAdapter_Redu gridReDuAdapter; 29 // 服务器端一共多少条数据 30 private static final int TOTAL_COUNTER = 50; 31 // 每一页展示多少条数据 32 private static final int REQUEST_COUNT = 12; 33 // 已经获取到多少条数据了 34 private int mCurrentCounter = 0; 35 //模拟的数据源 36 private ArrayList<String> dataList; 37 38 39 protected LoadingFooter.FooterState mState = LoadingFooter.FooterState.Normal; 40 41 protected void setState(LoadingFooter.FooterState mState) { 42 this.mState = mState; 43 ((MainActivity) mContext).runOnUiThread(new Runnable() { 44 @Override 45 public void run() { 46 changeAdaperState(); 47 } 48 }); 49 } 50 51 //改变底部bottom的样式 52 protected void changeAdaperState() { 53 if (gridReDuAdapter != null && gridReDuAdapter.mFooterHolder != null) { 54 gridReDuAdapter.mFooterHolder.setData(mState); 55 } 56 } 57 58 public HomeReDuFragment() { 59 } 60 61 62 @Override 63 protected View initView() { 64 View mView = LayoutInflater.from(mContext).inflate(R.layout.homepage_viewpager_item_redu, null); 65 mRecyclerView = (RecyclerView) mView.findViewById(R.id.home_page_recyclerview); 66 return mView; 67 } 68 69 @Override 70 protected void initData() { 71 initGridView(); 72 } 73 74 75 private View initGridView() { 76 mRecyclerView.setHasFixedSize(true); 77 //滑动暂停加载网络图片,而且可以监听recycler是否滑动到底部 78 mRecyclerView.addOnScrollListener(mOnScrollListener); 79 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); 80 gridReDuAdapter = new GridAdapter_Redu(mContext); 81 gridReDuAdapter.addAll(getRemoteData()); 82 mRecyclerView.setAdapter(gridReDuAdapter); 83 GridLayoutManager layoutManager = new GridLayoutManager(mContext, 3); 84 layoutManager.setOrientation(LinearLayoutManager.VERTICAL); 85 layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 86 @Override 87 public int getSpanSize(int position) { 88 //如果是最后一个item,则设置占据3列,否则占据1列 89 boolean isFooter = position == gridReDuAdapter.getItemCount() - 1; 90 return isFooter ? 3 : 1; 91 } 92 }); 93 mRecyclerView.setLayoutManager(layoutManager); 94 return mRecyclerView; 95 } 96 97 private EndlessRecyclerOnScrollListener mOnScrollListener = new EndlessRecyclerOnScrollListener() { 98 @Override 99 public void onLoadNextPage(View view) { 100 super.onLoadNextPage(view); 101 102 if (mState == LoadingFooter.FooterState.Loading) { 103 Log.d("@TAG", "the state is Loading, just wait.."); 104 return; 105 } 106 107 if (mCurrentCounter < TOTAL_COUNTER) { 108 // loading more 109 requestData(); 110 Log.d("TAG", "请求数据"); 111 } else { 112 //the end 113 setState(LoadingFooter.FooterState.TheEnd); 114 } 115 } 116 }; 117 118 119 /** 120 * 模拟请求网络 121 */ 122 private void requestData() { 123 setState(LoadingFooter.FooterState.Loading); 124 new Thread() { 125 @Override 126 public void run() { 127 super.run(); 128 try { 129 Thread.sleep(1000); 130 } catch (InterruptedException e) { 131 e.printStackTrace(); 132 } 133 if (NetworkUtils.isNetAvailable(mContext)) { 134 //模拟请求远程数据 135 gridReDuAdapter.addAll(getRemoteData()); 136 //加载完毕时 137 setState(LoadingFooter.FooterState.Normal); 138 Log.d("TAG", mCurrentCounter + ""); 139 } else { 140 //模拟一下网络请求失败的情况 141 setState(LoadingFooter.FooterState.NetWorkError); 142 } 143 } 144 }.start(); 145 } 146 147 //模拟请求数据 148 private ArrayList<String> getRemoteData() { 149 if (dataList == null) 150 dataList = new ArrayList<>(); 151 //每次都清空一下 152 dataList.clear(); 153 //要减去adapter最后一页 154 for (int i = 0; i < REQUEST_COUNT; i++) { 155 if (dataList.size() + mCurrentCounter >= TOTAL_COUNTER) { 156 break; 157 } 158 dataList.add("账号" + (mCurrentCounter + i)); 159 } 160 mCurrentCounter += dataList.size(); 161 return dataList; 162 } 163 164 165 }
需要注意的是这个方法:
//改变底部bottom的样式 protected void changeAdaperState() { if (gridReDuAdapter != null && gridReDuAdapter.mFooterHolder != null) { gridReDuAdapter.mFooterHolder.setData(mState); } }
Adapter的应用调用Adapter里面的方法,来切换Adaper的样式。
Adapter完整代码:
1 package com.yctime.truelove.fragment; 2 3 import android.content.Context; 4 import android.content.Intent; 5 import android.support.v7.widget.RecyclerView; 6 import android.util.Log; 7 import android.view.LayoutInflater; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 import com.yctime.truelove.ImageLoader.MyImageLoader; 14 import com.yctime.truelove.ImageLoader.UILImageLoader; 15 import com.yctime.truelove.LoadMore.LoadingFooter; 16 import com.yctime.truelove.drawer.MyZoneActivity; 17 import com.yctime.truelove.login.R; 18 19 import java.util.ArrayList; 20 21 22 public class GridAdapter_Redu extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 23 24 25 public String[] imgUrls = { 26 "http://img5.duitang.com/uploads/item/201402/22/20140222113830_X3ddd.jpeg", 27 "http://v1.qzone.cc/avatar/201403/01/10/36/531147afa4197738.jpg!200x200.jpg", 28 "http://g.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=e4d7ed147af40ad115b1cfe7621c3de9/b7fd5266d016092445b47837d50735fae6cd340d.jpg", 29 "http://img5q.duitang.com/uploads/item/201502/19/20150219182507_vGVaK.jpeg", 30 "http://p1.qqyou.com/touxiang/uploadpic/2013-3/12/2013031212190118646.jpg", 31 "http://img5.duitang.com/uploads/item/201412/08/20141208221323_YVJFk.png", 32 "http://cdn.duitang.com/uploads/item/201408/02/20140802222651_GWuU2.png", 33 "http://ent.dzwww.com/yulezhuanti/mtcbg/201510/W020151027467479100669.jpg", 34 "http://p1.qqyou.com/touxiang/uploadpic/2013-3/10/2013031009323656495.jpg", 35 "http://p1.qqyou.com/touxiang/uploadpic/2013-3/12/2013031212295986807.jpg", 36 "http://f.hiphotos.baidu.com/zhidao/wh%3D600%2C800/sign=10742594d739b6004d9b07b1d9601912/9f2f070828381f30ec9eabdeab014c086f06f0c5.jpg", 37 "http://a.hiphotos.baidu.com/zhidao/wh%3D600%2C800/sign=5bda8a18a71ea8d38a777c02a73a1c76/5882b2b7d0a20cf4598dc37c77094b36acaf9977.jpg", 38 "http://a1.att.hudong.com/36/98/300001051406133039983418031.jpg" 39 }; 40 public Context context; 41 MyImageLoader imageLoader = new UILImageLoader(); 42 private ArrayList<String> dataList = new ArrayList<>(); 43 44 45 private final int NORMALLAYOUT = 0; 46 private final int FOOTERLAYOUT = 1; 47 public FooterHolder mFooterHolder; 48 49 public GridAdapter_Redu(Context context) { 50 this.context = context; 51 } 52 53 public void addAll(ArrayList<String> list) { 54 int lastIndex = this.dataList.size(); 55 if (this.dataList.addAll(list)) { 56 notifyItemRangeInserted(lastIndex, list.size()); 57 } 58 } 59 60 @Override 61 public int getItemViewType(int position) { 62 if (position == dataList.size()) 63 return FOOTERLAYOUT; 64 else 65 return NORMALLAYOUT; 66 } 67 68 69 @Override 70 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 71 View view; 72 if (viewType == NORMALLAYOUT) { 73 view = LayoutInflater.from(context).inflate(R.layout.grid_redu_item, parent, false); 74 return new NormalHolder(view); 75 } else { 76 view = LayoutInflater.from(context).inflate(R.layout.sample_common_list_footer, parent, false); 77 mFooterHolder = new FooterHolder(view); 78 return mFooterHolder; 79 } 80 } 81 82 @Override 83 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 84 if (holder instanceof NormalHolder) { 85 //点击事件 86 holder.itemView.setOnClickListener(new View.OnClickListener() { 87 @Override 88 public void onClick(View v) { 89 Intent intent = new Intent(context, MyZoneActivity.class); 90 context.startActivity(intent); 91 } 92 }); 93 ((NormalHolder) holder).setData(position); 94 } 95 } 96 97 @Override 98 public int getItemCount() { 99 return dataList.size() + 1; 100 } 101 102 103 class NormalHolder extends RecyclerView.ViewHolder { 104 ImageView img; 105 TextView name; 106 107 public NormalHolder(View itemView) { 108 super(itemView); 109 img = (ImageView) itemView.findViewById(R.id.homepage_grid_picpic); 110 name = (TextView) itemView.findViewById(R.id.homepage_grid_name); 111 } 112 113 public void setData(int position) { 114 imageLoader.displayImage(context, imgUrls[position % imgUrls.length], img); 115 name.setText(dataList.get(position)); 116 } 117 } 118 119 public class FooterHolder extends RecyclerView.ViewHolder { 120 View mLoadingViewstubstub; 121 View mEndViewstub; 122 View mNetworkErrorViewstub; 123 124 public FooterHolder(View itemView) { 125 super(itemView); 126 mLoadingViewstubstub = itemView.findViewById(R.id.loading_viewstub); 127 mEndViewstub = itemView.findViewById(R.id.end_viewstub); 128 mNetworkErrorViewstub = itemView.findViewById(R.id.network_error_viewstub); 129 } 130 131 //根据传过来的status控制哪个状态可见 132 public void setData(LoadingFooter.FooterState status) { 133 Log.d("TAG", "reduAdapter" + status + ""); 134 switch (status) { 135 case Normal: 136 setAllGone(); 137 break; 138 case Loading: 139 setAllGone(); 140 mLoadingViewstubstub.setVisibility(View.VISIBLE); 141 break; 142 case TheEnd: 143 setAllGone(); 144 mEndViewstub.setVisibility(View.VISIBLE); 145 break; 146 case NetWorkError: 147 setAllGone(); 148 mNetworkErrorViewstub.setVisibility(View.VISIBLE); 149 break; 150 default: 151 break; 152 } 153 154 } 155 156 //全部不可见 157 void setAllGone() { 158 if (mLoadingViewstubstub != null) { 159 mLoadingViewstubstub.setVisibility(View.GONE); 160 } 161 if (mEndViewstub != null) { 162 mEndViewstub.setVisibility(View.GONE); 163 } 164 if (mNetworkErrorViewstub != null) { 165 mNetworkErrorViewstub.setVisibility(View.GONE); 166 } 167 } 168 169 } 170 171 }
运行效果: