【android】自定义组合控件PullToRefreshRecyeclerView

来源:互联网 发布:六壬课推算法 编辑:程序博客网 时间:2024/05/26 07:29

场景:自从Android 5.0发布以来,越来越多的开发者开始接触RecyeclerView,但是RecyclerView如何实现下拉刷新,上拉加在更多。于是我就偷懒 写了一个,以供大家参考和学习,以待大家改进。



构思:想必大家对SwipeRefreshLayout这个控件有一定了解,没错本次自定义组合控件也就是SwipeRefreshLayout与RecyeclerView的组合。

那么我们一步一步来实现:

1.首先写一个组合布局如下:pulltorefreshrecyclerview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@android:color/white"    android:orientation="vertical" >    <android.support.v4.widget.SwipeRefreshLayout        android:id="@+id/all_swipe"        android:layout_width="match_parent"        android:layout_height="match_parent" >        <android.support.v7.widget.RecyclerView            android:id="@+id/recycler_view"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:scrollbars="vertical" />    </android.support.v4.widget.SwipeRefreshLayout></LinearLayout>
2.声明一个类PulltoRefreshRecyclerView.java继承自LinearLayout

a.声明一个接口

public interface RefreshLoadMoreListener {public void onRefresh();public void onLoadMore();}
在Activity或者Fragment中去实现PulltoRefreshRecyclerView.RefreshLoadMoreListener接口

PulltoRefreshRecyclerView调用刷新和加在更多,实质上是回调Activity或者Fragment实现的接口方法。

b.即 在PulltoRefreshRecyclerView有loadMore和refresh方法

public void loadMore() {if (mRefreshLoadMoreListner != null && hasMore) {mRefreshLoadMoreListner.onLoadMore();}}
public void refresh() {if (mRefreshLoadMoreListner != null) {mRefreshLoadMoreListner.onRefresh();}}
c.那么就需要拿到这个实现的刷新加载监听实例

PulltoRefreshRecyclerView实现一个设置监听的方法

public void setRefreshLoadMoreListener(RefreshLoadMoreListener listener) {mRefreshLoadMoreListner = listener;}

d.RecyeclerView是基于Adapter的开发,那么这里也需要可以设置Adapter

public void setAdapter(RecyclerView.Adapter adapter) {if (adapter != null)recyclerView.setAdapter(adapter);}

e.考虑一下特殊的需求,有时禁止刷新,有时禁止加载更多那么我们来完善一下

public void setPullLoadMoreEnable(boolean enable) {this.hasMore = enable;}public boolean getPullLoadMoreEnable() {return hasMore;}public void setPullRefreshEnable(boolean enable) {swipeRfl.setEnabled(enable);}public boolean getPullRefreshEnable() {return swipeRfl.isEnabled();}
只要对相应的enable传入true或false则可进行开关控制

f.RecyeclerView可以是水平也可以是垂直,把这项功能也加入一下默认我们就让它为垂直


public void setOrientation(int orientation) {if (orientation != 0 && orientation != 1) {layoutManager.setOrientation(VERTICAL);} else {layoutManager.setOrientation(HORIZONTAL);}}public int getOrientation() {return layoutManager.getOrientation();}

7.SwipeRefreshLayout的停止刷新效果

public void stopRefresh() {isRefresh = false;swipeRfl.setRefreshing(false);}

g.刷新原理和加载更多原理

刷新实际上是添加SwipeRefreshLayout的OnRefreshListener监听,在符合!isRefresh的前提下回调RefreshLoadMoreListener的onRefresh方法

加载更多原理是添加RecyeclerView的OnScrollListener监听,在符合hasMore且显示最后一项的前提下回调

RefreshLoadMoreListener的onLoadMore方法



2.那么我们来看看具体的PulltoRefreshRecyclerView代码

package com.jabony.recyclerpulltorefresh;import android.content.Context;import android.support.v4.widget.SwipeRefreshLayout;import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.view.LayoutInflater;import android.widget.LinearLayout;/** *  * @author jabony * @since 2015年3月31日 16:06:59 * */public class PulltoRefreshRecyclerView extends LinearLayout {/** * 垂直方向 */static final int VERTICAL = LinearLayoutManager.VERTICAL;/** * 水平方向 */static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;/** * 内容控件 */private RecyclerView recyclerView;/** * 刷新布局控件 */private SwipeRefreshLayout swipeRfl;private LinearLayoutManager layoutManager;/* * 刷新布局的监听 */private OnRefreshListener mRefreshListener;/** * 内容控件滚动监听  */private RecyclerView.OnScrollListener mScrollListener;/** * 内容适配器 */private RecyclerView.Adapter mAdapter;/* * 刷新加载监听事件 */private RefreshLoadMoreListener mRefreshLoadMoreListner;/** * 是否可以加载更多 */private boolean hasMore = true;/** * 是否正在刷新 */private boolean isRefresh = false;/** * 是否正在加载更多 */private boolean isLoadMore = false;public PulltoRefreshRecyclerView(Context context) {super(context);// TODO Auto-generated constructor stub}@SuppressWarnings("deprecation")public PulltoRefreshRecyclerView(Context context, AttributeSet attrs) {super(context, attrs);// 导入布局LayoutInflater.from(context).inflate(R.layout.pulltorefreshrecyclerview, this, true);swipeRfl = (SwipeRefreshLayout) findViewById(R.id.all_swipe);recyclerView = (RecyclerView) findViewById(R.id.recycler_view);// 加载颜色是循环播放的,只要没有完成刷新就会一直循环,color1>color2>color3>color4// swipeRfl.setColorScheme(Color.BLUE, Color.GREEN, Color.RED,// Color.YELLOW);/** * 监听上拉至底部滚动监听 */mScrollListener = new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);//最后显示的项int lastVisibleItem = layoutManager.findLastVisibleItemPosition();int totalItemCount = layoutManager.getItemCount();// lastVisibleItem >= totalItemCount - 4 表示剩下4个item自动加载// dy>0 表示向下滑动/*if (hasMore && (lastVisibleItem >= totalItemCount - 1)&& dy > 0 && !isLoadMore) {isLoadMore = true;loadMore();}*//** * 无论水平还是垂直 */if (hasMore && (lastVisibleItem >= totalItemCount - 1)&& !isLoadMore) {isLoadMore = true;loadMore();}}};/** * 下拉至顶部刷新监听 */mRefreshListener = new OnRefreshListener() {@Overridepublic void onRefresh() {if (!isRefresh) {isRefresh = true;refresh();}}};swipeRfl.setOnRefreshListener(mRefreshListener);recyclerView.setHasFixedSize(true);layoutManager = new LinearLayoutManager(context);layoutManager.setOrientation(LinearLayoutManager.VERTICAL);recyclerView.setLayoutManager(layoutManager);recyclerView.setOnScrollListener(mScrollListener);}public void setOrientation(int orientation) {if (orientation != 0 && orientation != 1) {layoutManager.setOrientation(VERTICAL);} else {layoutManager.setOrientation(HORIZONTAL);}}public int getOrientation() {return layoutManager.getOrientation();}public void setPullLoadMoreEnable(boolean enable) {this.hasMore = enable;}public boolean getPullLoadMoreEnable() {return hasMore;}public void setPullRefreshEnable(boolean enable) {swipeRfl.setEnabled(enable);}public boolean getPullRefreshEnable() {return swipeRfl.isEnabled();}public void setLoadMoreListener() {recyclerView.setOnScrollListener(mScrollListener);}public void loadMore() {if (mRefreshLoadMoreListner != null && hasMore) {mRefreshLoadMoreListner.onLoadMore();}}/** * 加载更多完毕,为防止频繁网络请求,isLoadMore为false才可再次请求更多数据 */public void setLoadMoreCompleted(){isLoadMore = false;}public void stopRefresh() {isRefresh = false;swipeRfl.setRefreshing(false);}public void setRefreshLoadMoreListener(RefreshLoadMoreListener listener) {mRefreshLoadMoreListner = listener;}public void refresh() {if (mRefreshLoadMoreListner != null) {mRefreshLoadMoreListner.onRefresh();}}public void setAdapter(RecyclerView.Adapter adapter) {if (adapter != null)recyclerView.setAdapter(adapter);}public interface RefreshLoadMoreListener {public void onRefresh();public void onLoadMore();}}

3.那么我们来看看如何使用

package com.example.pulltorefreshrecyeclerviewdemo;import java.util.ArrayList;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.MenuItem;import android.widget.LinearLayout;import com.jabony.recyclerpulltorefresh.PullRefreshRecyclerView;/** *  * @author jabony * @since 2015年3月31日 16:08:56 */public class MainActivity extends Activity implementsPullRefreshRecyclerView.RefreshLoadMoreListener {private PullRefreshRecyclerView prrv;private ActivityAdapter allActAdapter;private JSONObject response = null;private int page = 0;/** * 全部活动数据源 */private ArrayList<ActivityBean> actAllList = new ArrayList<ActivityBean>();private String jsonStr = "{\"datas\":{\"count\":\"6\",\"list\":[{\"id\":\"311\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"},{\"id\":\"302\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"},{\"id\":\"311\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"},{\"id\":\"311\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"},{\"id\":\"311\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"},{\"id\":\"311\",\"end_date\":\"1437235200\",\"shopid\":\"387\",\"subject\":\"金鹏会员里程百日冲刺,最高可获35000里程奖励\",\"is_collected\":\"2\",img:\"http://jfgj.wadiankeji.com/Uploads/2015-03-26/5513bd4120d64.jpg\",\"collec_num\":\"1\",\"start_date\":\"1428681600\",\"url\":\"http://jfgj.wadiankeji.com/api/html/detail/actid/311\"}]},\"status\":\"10000\",\"msg\":\"faxian\"}";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/** * 绑定组合控件 */prrv = (PullRefreshRecyclerView) findViewById(R.id.prrv);/** * 初始化适配器 */allActAdapter = new ActivityAdapter(actAllList, MainActivity.this);/** * 下拉上拉加载更多监听 */prrv.setRefreshLoadMoreListener(this);/** * 禁用刷新 */// prrv.setPullRefreshEnable(false);/** * 禁用加载更多 */// prrv.setPullLoadMoreEnable(false);/** * 设置布局方向 */prrv.setVertical();/** * 设置适配器 */prrv.setAdapter(allActAdapter);/** * 首次进入刷新 */prrv.refresh();}/** * 加载分页数据 */public void loadNetDatas(int currentPage) {if (currentPage > 2) {prrv.setPullLoadMoreEnable(false);return;}try {response = new JSONObject(jsonStr);String msg = null;int count = 0;int status = 0;if (!response.isNull("datas")) {JSONObject dataObj = response.getJSONObject("datas");if (!dataObj.isNull("count")) {count = dataObj.getInt("count");}if (!dataObj.isNull("list")) {JSONArray listArray = dataObj.getJSONArray("list");int size = listArray.length();for (int i = 0; i < size; i++) {JSONObject obj = listArray.getJSONObject(i);ActivityBean ab = new ActivityBean();if (!obj.isNull("id")) {ab.setActId(obj.getString("id"));}if (!obj.isNull("shopid")) {ab.setShopId(obj.getString("shopid"));}if (!obj.isNull("is_top")) {if ("1".equals(obj.getString("is_top"))) {ab.setTop(true);}}if (!obj.isNull("img")) {ab.setImgUrl(obj.getString("img"));}if (!obj.isNull("subject")) {ab.setActTitile(obj.getString("subject"));}if (!obj.isNull("start_date")) {if (obj.getString("start_date") != null&& !"".equals(obj.getString("start_date"))) {ab.setStartDate(obj.getLong("start_date"));}}if (!obj.isNull("end_date")) {if (obj.getString("end_date") != null&& !"".equals(obj.getString("end_date"))) {ab.setEndDate(obj.getLong("end_date"));}}if (!obj.isNull("collec_num")) {String collcNum = obj.getString("collec_num");if (collcNum == null || "".equals(collcNum)) {ab.setCollectNum(0);} else {ab.setCollectNum(obj.getInt("collec_num"));}collcNum = null;}if (!obj.isNull("is_collected")) {ab.setCollected(obj.getInt("is_collected") == 1 ? true: false);}if (!obj.isNull("url")) {ab.setUrl(obj.getString("url"));}actAllList.add(ab);/** * 刷新适配器 */allActAdapter.notifyDataSetChanged();/** * 如果刷新则停止刷新 */prrv.stopRefresh();/** * 加载更多完毕 */prrv.setLoadMoreCompleted();/** * 如果没有更多数据则设置不可加载更多 */// prrv.setPullLoadMoreEnable(false);}}}} catch (JSONException e) {// TODO Auto-generated catch blocke.printStackTrace();}}@Overridepublic void onRefresh() {// TODO Auto-generated method stubprrv.setPullRefreshEnable(true);prrv.setPullLoadMoreEnable(true);actAllList.clear();page = 1;loadNetDatas(page);}@Overridepublic void onLoadMore() {// TODO Auto-generated method stubpage++;loadNetDatas(page);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_delete) {actAllList.clear();allActAdapter.notifyDataSetChanged();page = 1;prrv.customExceptView(R.drawable.no_data, "这里空空如也");return true;}if (id == R.id.action_orintation) {int orientation = prrv.getOrientation();if (orientation == LinearLayout.HORIZONTAL) {prrv.setVertical();} else {prrv.setHorizontal();}return true;}if (id == R.id.action_refreshable) {if (prrv.getPullRefreshEnable()) {prrv.setPullRefreshEnable(!prrv.getPullRefreshEnable());} else {prrv.setPullRefreshEnable(!prrv.getPullRefreshEnable());}return true;}if (id == R.id.action_loadmoreable) {if (prrv.getPullLoadMoreEnable()) {prrv.setPullLoadMoreEnable(!prrv.getPullLoadMoreEnable());} else {prrv.setPullLoadMoreEnable(!prrv.getPullLoadMoreEnable());}return true;}if (id == R.id.action_stoprefresh) {prrv.stopRefresh();return true;}if (id == R.id.action_scrollToTop) {prrv.scrollToTop();return true;}return super.onOptionsItemSelected(item);}}


4.看上去已经搞定了,但你会发现需要引入一个库项目没错RecyclerView的库项目

这都好办,我们考虑的重点不在这里,这样就完美了吗?我们在使用的时候在布局中这么写,看是没有什么

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.jabony.recyclerpulltorefresh.MainActivity" >    <com.jabony.recyclerpulltorefresh.PullRefreshRecyclerView        android:id="@+id/prrv"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>

5.我们能不能把自定义的View布局省掉?

答案当然是肯定的:我们来做个小实验在原来采用

// 导入布局
LayoutInflater.from(context).inflate(R.layout.pulltorefreshrecyclerview, this, true);

在老的方式做下调整,完全脱离布局,代码写布局

LinearLayout rootLl = new LinearLayout(context);rootLl.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));<span style="color:#ff0000;">mExceptView = initExceptionView(context);//异常布局</span>mExceptView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));mExceptView.setVisibility(View.GONE);swipeRfl = new SwipeRefreshLayout(context);swipeRfl.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));FrameLayout bootLl = new FrameLayout(context);bootLl.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));recyclerView = new RecyclerView(context);recyclerView.setVerticalScrollBarEnabled(true);recyclerView.setHorizontalScrollBarEnabled(true);recyclerView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));bootLl.addView(recyclerView);bootLl.addView(mExceptView);swipeRfl.addView(bootLl);rootLl.addView(swipeRfl);this.addView(rootLl);
这样就把需要有xml布局引入的布局改动为纯代码的,虽然不怎么完美,但是测试使用是没有问题了。

6.没错我引入了异常布局,为了交互友好,允许用户半自定义异常界面


也就是其实布局什么样子 我早就定好了,你只需要按照方法传图片的id和提示的字符串,你一定会好奇到底是什么样子的,马上贴图。

那么刷新呢,非常简单,点击图片可以刷新操作,这样就看似比较完善了。


7.总结一下:

PulltoRefreshRecyclerView是在SwipeRefreshLayout和RecyclerView的基础上组合改造的。

需要实现OnRefreshListener的方法

可以控制能否刷新,能否加在更多

可以控制显示的方向

提供回到Top的方法

停止刷新效果

可以半定义异常界面的效果


下载地址:

自定义组合下拉刷新上拉加载更多控件



2 0