【android】自定义组合控件PullToRefreshRecyeclerView
来源:互联网 发布:上海哪家java培训机构 编辑:程序博客网 时间:2024/06/02 07:31
场景:自从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>
a.声明一个接口
- public interface RefreshLoadMoreListener {
- public void onRefresh();
- public void onLoadMore();
- }
在PulltoRefreshRecyclerView调用刷新和加在更多,实质上是回调Activity或者Fragment实现的接口方法。
b.即 在PulltoRefreshRecyclerView有loadMore和refresh方法
- public void loadMore() {
- if (mRefreshLoadMoreListner != null && hasMore) {
- mRefreshLoadMoreListner.onLoadMore();
- }
- }
- public void refresh() {
- if (mRefreshLoadMoreListner != null) {
- mRefreshLoadMoreListner.onRefresh();
- }
- }
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();
- }
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() {
- @Override
- public 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() {
- @Override
- public 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 implements
- PullRefreshRecyclerView.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\"}";
- @Override
- protected 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 block
- e.printStackTrace();
- }
- }
- @Override
- public void onRefresh() {
- // TODO Auto-generated method stub
- prrv.setPullRefreshEnable(true);
- prrv.setPullLoadMoreEnable(true);
- actAllList.clear();
- page = 1;
- loadNetDatas(page);
- }
- @Override
- public void onLoadMore() {
- // TODO Auto-generated method stub
- page++;
- loadNetDatas(page);
- }
- @Override
- public 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;
- }
- @Override
- public 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);
6.没错我引入了异常布局,为了交互友好,允许用户半自定义异常界面
也就是其实布局什么样子 我早就定好了,你只需要按照方法传图片的id和提示的字符串,你一定会好奇到底是什么样子的,马上贴图。
那么刷新呢,非常简单,点击图片可以刷新操作,这样就看似比较完善了。
7.总结一下:
PulltoRefreshRecyclerView是在SwipeRefreshLayout和RecyclerView的基础上组合改造的。
需要实现OnRefreshListener的方法
可以控制能否刷新,能否加在更多
可以控制显示的方向
提供回到Top的方法
停止刷新效果
可以半定义异常界面的效果
下载地址:
自定义组合下拉刷新上拉加载更多控件
- 【android】自定义组合控件PullToRefreshRecyeclerView
- 【android】自定义组合控件PullToRefreshRecyeclerView
- Android自定义组合控件
- android自定义组合控件
- Android自定义组合控件
- android组合自定义控件
- android 自定义组合控件
- android 自定义组合控件
- android 自定义组合控件
- Android自定义组合控件
- android自定义组合控件
- Android 自定义组合控件
- android自定义组合控件
- android 自定义组合控件
- android 自定义组合控件
- android 自定义组合控件
- Android自定义组合控件
- Android自定义组合控件
- 接口
- KVO浅析与实例
- sublime text 插件的删除方法
- spring学习笔记(21)——声明式事务
- mysql复合索引、普通索引总结
- 【android】自定义组合控件PullToRefreshRecyeclerView
- Linux系统中 eclipse下编译C++(使用v8引擎运行JavaScript脚本)
- 代码简单生成uml图的小工具 Lumpy
- IOS高级开发十大问题 Cocoa&Objective-c高级开发问题
- Sublime Text3 快捷键汇总及设置快捷键配置环境变量
- linux 内核学习资料
- 欢迎使用CSDN-markdown编辑器
- Sublime text3 插件ColorPicker(调色板)不能使用快捷键
- 文章标题