轉載請標明出處:
http://blog.csdn.net/developer_jiangqq/article/details/49992269
本文出自:【江清清的博客】
(一).前言:
【好消息】個人網站已經上線運行,后面博客以及技術干貨等精彩文章會同步更新,請大家關注收藏:http://www.lcode.org
話說RecyclerView已經面市很久,也在很多應用中得到廣泛的使用,在整個開發者圈子里面也擁有很不錯的口碑,那說明RecyclerView擁有比ListView,GridView之類控件有很多的優點,例如:數據綁定,Item View創建,View的回收以及重用等機制。前三三篇文章已經貢呢更新了以下三個部分:
- RecyclerView控件的基本使用,包括基礎,進階,高級部分,動畫之類(點擊進入)
- RecyclerView控件的實戰實例(點擊進入)
- RecyclerView控件集合AA(Android Annotations)注入框架實例(點擊進入)
本來這個專題不打算更新,不過前兩天看到各位童鞋還是挺積極的評論到,希望可以更新RecyclerView加入下拉刷新和上拉加載更多的功能。正好昨天周末,所以我這邊也就實現了這樣的功能,今天更新一下。具體代碼已經上傳到下面的項目中,歡迎各位去star和fork一下。
FastDev4Android框架項目地址:https://github.com/jiangqqlmj/FastDev4Android
RecyclerView實現的列表,默認情況下面是不帶下拉刷新和上拉記載更多效果的,但是我在我們的實際項目當中,為了提高用戶體驗,這種效果一般都需要實現,在我以前的博客中已經重寫了ListView(Android 列表下拉刷新組件PullToRefreshListView使用)實現上拉刷新和上拉加載更多效果。現在我們已經學會了ListView,GridView的替代品RecyclerView的基本使用方法,那么必不可少的也需要實現上拉刷新和上拉加載更多的效果了。今天我們會通過兩種方式來實現,具體會采用Android的另一控件SwipeRefreshLayout。
(二).SwipeRefreshLayout介紹:
SwipeRefrshLayout是Google官方更新的一個Widget,可以實現下拉刷新的效果。該控件集成自ViewGroup在support-v4兼容包下,不過我們需要升級supportlibrary的版本到19.1以上。基本使用的方法如下:
- setOnRefreshListener(OnRefreshListener):添加下拉刷新監聽器
- setRefreshing(boolean):顯示或者隱藏刷新進度條
- isRefreshing():檢查是否處於刷新狀態
- setColorSchemeResources():設置進度條的顏色主題,最多設置四種,以前的setColorScheme()方法已經棄用了。
具體使用效果下面我們會看到。
(三).RecyclerView+SwpieRefreshLayout實現下拉刷新效果:
1.SwipeRefreshLayout本身自帶下拉刷新的效果,那么我們可以選擇在RecyclerView布局外部嵌套一層SwipeRefreshLayout布局即可,具體布局文件如下:
- <?xmlversionxmlversion="1.0" encoding="utf-8"?>
- <LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"android:layout_width="match_parent"
- android:layout_height="match_parent">
- <includelayoutincludelayout="@layout/common_top_bar_layout"/>
- <android.support.v4.widget.SwipeRefreshLayout
- android:id="@+id/demo_swiperefreshlayout"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scrollbars="vertical"
- >
- <android.support.v7.widget.RecyclerView
- android:id="@+id/demo_recycler"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- ></android.support.v7.widget.RecyclerView>
- </android.support.v4.widget.SwipeRefreshLayout>
- </LinearLayout>
2.接着在Activity中獲取SwipeRefreshLayout控件並且設置OnRefreshListener監聽器,同時實現里邊的onRefresh()方法,在該方法中進行網絡請求最新數據,然后刷新RecyclerView列表同時設置SwipeRefreshLayout的進度Bar的隱藏或者顯示效果。具體代碼如下:
- demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
- @Override
- public void onRefresh() {
- Log.d("zttjiangqq","invoke onRefresh...");
- new Handler().postDelayed(newRunnable() {
- @Override
- public void run() {
- List<String> newDatas = new ArrayList<String>();
- for (int i = 0; i <5; i++) {
- int index = i + 1;
- newDatas.add("new item" + index);
- }
- adapter.addItem(newDatas);
- demo_swiperefreshlayout.setRefreshing(false);
- Toast.makeText(RecyclerRefreshActivity.this, "更新了五條數據...", Toast.LENGTH_SHORT).show();
- }
- }, 5000);
- }
- });
3.除此之外我們也來看一下Adapter和Activity中的其他代碼,也方便各位童鞋查看。
- RecyclerRefreshActivity.java
- public class RecyclerRefreshActivity extends BaseActivity {
- private LinearLayout top_bar_linear_back;
- private TextView top_bar_title;
- private SwipeRefreshLayout demo_swiperefreshlayout;
- private RecyclerView demo_recycler;
- private RefreshRecyclerAdapter adapter;
- private LinearLayoutManager linearLayoutManager;
- private int lastVisibleItem;
- @Override
- protected void onCreate(BundlesavedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.recycler_refresh_layout);
- top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
- top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
- top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
- top_bar_title.setText("RecyclerView下拉刷新,下拉加載更多...");
- demo_swiperefreshlayout=(SwipeRefreshLayout)this.findViewById(R.id.demo_swiperefreshlayout);
- demo_recycler=(RecyclerView)this.findViewById(R.id.demo_recycler);
- //設置刷新時動畫的顏色,可以設置4個
- demo_swiperefreshlayout.setProgressBackgroundColorSchemeResource(android.R.color.white);
- demo_swiperefreshlayout.setColorSchemeResources(android.R.color.holo_blue_light,
- android.R.color.holo_red_light,android.R.color.holo_orange_light,
- android.R.color.holo_green_light);
- demo_swiperefreshlayout.setProgressViewOffset(false, 0, (int) TypedValue
- .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
- .getDisplayMetrics()));
- linearLayoutManager=new LinearLayoutManager(this);
- linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);
- demo_recycler.setLayoutManager(linearLayoutManager);
- //添加分隔線
- demo_recycler.addItemDecoration(new AdvanceDecoration(this, OrientationHelper.VERTICAL));
- demo_recycler.setAdapter(adapter = new RefreshRecyclerAdapter(this));
- demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
- @Override
- public void onRefresh() {
- Log.d("zttjiangqq","invoke onRefresh...");
- new Handler().postDelayed(newRunnable() {
- @Override
- public void run() {
- List<String> newDatas = new ArrayList<String>();
- for (int i = 0; i <5; i++) {
- int index = i + 1;
- newDatas.add("new item" + index);
- }
- adapter.addItem(newDatas);
- demo_swiperefreshlayout.setRefreshing(false);
- Toast.makeText(RecyclerRefreshActivity.this, "更新了五條數據...", Toast.LENGTH_SHORT).show();
- }
- }, 5000);
- }
- });
- class CustomOnClickListenerimplements View.OnClickListener{
- @Override
- public void onClick(View v) {
- RecyclerRefreshActivity.this.finish();
- }
- }
- }
- RefreshRecyclerAdapter.java
- public class RefreshRecyclerAdapter extends RecyclerView.Adapter<RefreshRecyclerAdapter.ViewHolder>{
- private LayoutInflater mInflater;
- private List<String> mTitles=null;
- public RefreshRecyclerAdapter(Context context){
- this.mInflater=LayoutInflater.from(context);
- this.mTitles=new ArrayList<String>();
- for (int i=0;i<20;i++){
- int index=i+1;
- mTitles.add("item"+index);
- }
- }
- /**
- * item顯示類型
- * @param parent
- * @param viewType
- * @return
- */
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
- //這邊可以做一些屬性設置,甚至事件監聽綁定
- //view.setBackgroundColor(Color.RED);
- ViewHolder viewHolder=new ViewHolder(view);
- return viewHolder;
- }
- /**
- * 數據的綁定顯示
- * @param holder
- * @param position
- */
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.item_tv.setText(mTitles.get(position));
- holder.itemView.setTag(position);
- }
- @Override
- public int getItemCount() {
- return mTitles.size();
- }
- //自定義的ViewHolder,持有每個Item的的所有界面元素
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public TextView item_tv;
- public ViewHolder(View view){
- super(view);
- item_tv = (TextView)view.findViewById(R.id.item_tv);
- }
- }
- //添加數據
- public void addItem(List<String> newDatas) {
- //mTitles.add(position, data);
- //notifyItemInserted(position);
- newDatas.addAll(mTitles);
- mTitles.removeAll(mTitles);
- mTitles.addAll(newDatas);
- notifyDataSetChanged();
- }
- public void addMoreItem(List<String> newDatas) {
- mTitles.addAll(newDatas);
- notifyDataSetChanged();
- }
- }
以上重要地方的注釋已經加上去了。
5.運行效果如下:
(四).RecyclerView設置滾動事件加入上拉加載更多功能
下面我們再來看RecyclerView和相關類的一些特性,
LayoutManger給我們提供了以下幾個方法來讓開發者方便的獲取到屏幕上面的頂部item和頂部item相關的信息:
- findFirstVisibleItemPosition()
- findFirstCompletlyVisibleItemPosition()
- findLastVisibleItemPosition()
- findLastCompletlyVisibleItemPosition()
同時通過Recycler.Adapter的getItemCount()方法可以輕松獲取到RecyclerView列表中Item View的個數。
那么下面我們通過監聽滑動(滾動)事件,然后在里邊判斷是否已經滑動到最底部來加載更多的數據,使用方法如下:
- //RecyclerView滑動監聽
- demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- List<String> newDatas = new ArrayList<String>();
- for (int i = 0; i< 5; i++) {
- int index = i +1;
- newDatas.add("more item" + index);
- }
- adapter.addMoreItem(newDatas);
- }
- },1000);
- }
- }
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView,dx, dy);
- lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();
- }
- });
運行效果如下:
(五).升級RecyclerView加入FootView實現上拉加載
上面我們雖然已經實現了上拉加載更多的效果,但是還比較丑陋,最起碼要讓用戶知道確實在上拉加載的過程吧,例如加載一個底部的進度布局。這樣一想,那么我們就按照ListView方式addFootView()唄,不過很可惜的是RecyclerView沒有給我們提供addFootView()方法,那該怎么樣辦呢?我們來看RecyclerView.Apapter類:
我們要實現一個自定義Adapter一定需要實現onCreateViewHolder(ViewGroup paren,int viewType)方法,注意看方法中的第二個參數viewType,是不是想到布局類型了,也就是說該也支持多套布局顯示的,那么查看基類中的所有方法如下:
上面有一個方法getItemType(),這個就和ListView的Adapter的實現差不多了,那么我們這邊可以使用多套布局給RecyclerView加入一個FootView布局即可。RefreshFootAdapter.java具體實現流程如下:
1.加入布局狀態標志-用來判斷此時加載是普通Item還是foot view:
private static final int TYPE_ITEM =0; //普通Item View
private static final intTYPE_FOOTER = 1; //頂部FootView
2.重寫getItemCount()方法,返回的Item數量在數據的基礎上面+1,增加一項FootView布局項
- public intgetItemCount() {
- return mTitles.size()+1;
- }
3.重寫getItemViewType方法來判斷返回加載的布局的類型
- public int getItemViewType(int position) {
- // 最后一個item設置為footerView
- if (position + 1 == getItemCount()) {
- return TYPE_FOOTER;
- } else {
- return TYPE_ITEM;
- }
- }
4.接着onCreateViewHolder(ViewGroup parent,int viewType)加載布局的時候根據viewType的類型來選擇指定的布局創建,返回即可:
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- //進行判斷顯示類型,來創建返回不同的View
- if(viewType==TYPE_ITEM){
- Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
- //這邊可以做一些屬性設置,甚至事件監聽綁定
- //view.setBackgroundColor(Color.RED);
- ItemViewHolder itemViewHolder=new ItemViewHolder(view);
- return itemViewHolder;
- }else if(viewType==TYPE_FOOTER){
- Viewfoot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);
- //這邊可以做一些屬性設置,甚至事件監聽綁定
- //view.setBackgroundColor(Color.RED);
- FootViewHolder footViewHolder=new FootViewHolder(foot_view);
- return footViewHolder;
- }
- return null;
- }
5.最后進行判斷數據的時候(onBindViewHolder),判斷holder的類型來進行判定數據即可.
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- if(holder instanceof ItemViewHolder) {
- ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));
- holder.itemView.setTag(position);
- }else if(holder instanceof FootViewHolder){
- FootViewHolderfootViewHolder=(FootViewHolder)holder;
- switch (load_more_status){
- case PULLUP_LOAD_MORE:
- footViewHolder.foot_view_item_tv.setText("上拉加載更多...");
- break;
- case LOADING_MORE:
- footViewHolder.foot_view_item_tv.setText("正在加載更多數據...");
- break;
- }
- }
- }
6.整個RefreshFootAdapter完整代碼如下:
- packagecom.chinaztt.fda.adapter;
- public class RefreshFootAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
- //上拉加載更多
- public static final int PULLUP_LOAD_MORE=0;
- //正在加載中
- public static final int LOADING_MORE=1;
- //上拉加載更多狀態-默認為0
- private int load_more_status=0;
- private LayoutInflater mInflater;
- private List<String> mTitles=null;
- private static final intTYPE_ITEM = 0; //普通Item View
- private static final intTYPE_FOOTER = 1; //頂部FootView
- public RefreshFootAdapter(Context context){
- this.mInflater=LayoutInflater.from(context);
- this.mTitles=new ArrayList<String>();
- for (int i=0;i<20;i++){
- int index=i+1;
- mTitles.add("item"+index);
- }
- }
- /**
- * item顯示類型
- * @param parent
- * @param viewType
- * @return
- */
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- //進行判斷顯示類型,來創建返回不同的View
- if(viewType==TYPE_ITEM){
- Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
- //這邊可以做一些屬性設置,甚至事件監聽綁定
- //view.setBackgroundColor(Color.RED);
- ItemViewHolder itemViewHolder=new ItemViewHolder(view);
- return itemViewHolder;
- }else if(viewType==TYPE_FOOTER){
- Viewfoot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);
- //這邊可以做一些屬性設置,甚至事件監聽綁定
- //view.setBackgroundColor(Color.RED);
- FootViewHolder footViewHolder=new FootViewHolder(foot_view);
- return footViewHolder;
- }
- return null;
- }
- /**
- * 數據的綁定顯示
- * @param holder
- * @param position
- */
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- if(holder instanceof ItemViewHolder) {
- ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));
- holder.itemView.setTag(position);
- }else if(holder instanceof FootViewHolder){
- FootViewHolder footViewHolder=(FootViewHolder)holder;
- switch (load_more_status){
- case PULLUP_LOAD_MORE:
- footViewHolder.foot_view_item_tv.setText("上拉加載更多...");
- break;
- case LOADING_MORE:
- footViewHolder.foot_view_item_tv.setText("正在加載更多數據...");
- break;
- }
- }
- }
- /**
- * 進行判斷是普通Item視圖還是FootView視圖
- * @param position
- * @return
- */
- @Override
- public int getItemViewType(int position) {
- // 最后一個item設置為footerView
- if (position + 1 == getItemCount()) {
- return TYPE_FOOTER;
- } else {
- return TYPE_ITEM;
- }
- }
- @Override
- public int getItemCount() {
- return mTitles.size()+1;
- }
- //自定義的ViewHolder,持有每個Item的的所有界面元素
- public static class ItemViewHolder extends RecyclerView.ViewHolder {
- public TextView item_tv;
- public ItemViewHolder(View view){
- super(view);
- item_tv = (TextView)view.findViewById(R.id.item_tv);
- }
- }
- /**
- * 底部FootView布局
- */
- public static class FootViewHolder extends RecyclerView.ViewHolder{
- private TextView foot_view_item_tv;
- public FootViewHolder(View view) {
- super(view);
- foot_view_item_tv=(TextView)view.findViewById(R.id.foot_view_item_tv);
- }
- }
- //添加數據
- public void addItem(List<String> newDatas) {
- //mTitles.add(position, data);
- //notifyItemInserted(position);
- newDatas.addAll(mTitles);
- mTitles.removeAll(mTitles);
- mTitles.addAll(newDatas);
- notifyDataSetChanged();
- }
- public void addMoreItem(List<String> newDatas) {
- mTitles.addAll(newDatas);
- notifyDataSetChanged();
- }
- /**
- * //上拉加載更多
- * PULLUP_LOAD_MORE=0;
- * //正在加載中
- * LOADING_MORE=1;
- * //加載完成已經沒有更多數據了
- * NO_MORE_DATA=2;
- * @param status
- */
- public void changeMoreStatus(int status){
- load_more_status=status;
- notifyDataSetChanged();
- }
- }
同時該Adaper中我還定義一個changeMoreStatus()方法和兩個字符串常量可以來進行修改FootView中字符串提醒文本的。
7.整體Activity中還是設置監聽RecyclerView的滾動事件.代碼和第一個例子差不多,不過RecyclerView需要設置這邊的Adapter了,demo_recycler.setAdapter(adapter= new RefreshFootAdapter(this));
- demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {
- adapter.changeMoreStatus(RefreshFootAdapter.LOADING_MORE);
- newHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- List<String> newDatas = new ArrayList<String>();
- for (int i = 0; i< 5; i++) {
- int index = i +1;
- newDatas.add("more item" + index);
- }
- adapter.addMoreItem(newDatas);
- adapter.changeMoreStatus(RefreshFootAdapter.PULLUP_LOAD_MORE);
- }
- }, 2500);
- }
- }
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView,dx, dy);
- lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();
- }
- });
查看代碼之后,大家肯定也發現在onScrollStateChanged()方法中,Adapter.addMoreItem()和Adapter.changeMoreStatus()方法內部都調用了notifyDataSetChanged()方法,也就是說這邊刷新了兩次,針對這個問題,大家可以把第二個方法放入到addMoreItem()內部去,調用一次刷新操作接口。同時也可以自己按照實際需求進行優化。
8.運行效果如下:
(六).最后總結
今天我們通過SwipeRefreshLayout和RecyclerView結合以及改造Adapter方式實現了RecyclerView的下拉刷新和上拉加載更多的效果。
本次具體實例注釋過的全部代碼已經上傳到FastDev4Android項目中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:
https://github.com/jiangqqlmj/FastDev4Android 同時歡迎大家star和fork整個開源快速開發框架項目~
本人錄制AA(Android Annotations)注入框架的視頻教程已經上線了,歡迎大家前往觀看。http://www.cniao5.com/course/10074
尊重原創,轉載請注明:From Sky丶清(http://blog.csdn.net/developer_jiangqq) 侵權必究!