RefreshRecyclerView下拉刷新,加载更多
2016-06-29 17:24
429 查看
ListView已经用了很多年了,后来又有了RecyclerView,基本可以代替ListView/GridView了,还有瀑布流的模式,加上各种特效,于是就尝试用RecyclerView替代listview。
如果UI要求不严格,那么有一个很简单的方式实现下拉刷新:SwipeRefreshLayout (具体介绍参考:http://blog.csdn.net/dalancon/article/details/46125667),然后基本你就可以开开心心做完了。
但是如果你们的UI或者产品说,我们要一个什么什么样的功能(反正就是不造轮子不行的需求),那只能自己做了。
先看一下效果:
简单说一下思路:
下拉刷新:
1.自定义一个线性布局linearlayout,里面先添加一个header,然后添加recycclerview。
2.计算出header的高度h1,然后设置linearlayout布局的的padding为-h1(也可以设置margin为-h,都可以实现,本文采用的是padding)
3.利用消息传递机制及对recyclerview的滚动监听来判断是否可以下拉刷新。
4.根据用户的手势移动来设置padding值,再加上一些回滚效果就ok了。
加载更多:
1.首先写一个BaseAdapter抽象类继承
2.在BaseAdapter中添加一个footerViewHolder,并设置开关,如果有加载更多的时候,就显示。如果没有,则不显示。
3.监听RecyclerView的滚动,如果滚动到末尾并且有加载更多,则调用加载更多的方法。
有了上面的思路写起来就简单多了。
接下来分析代码:
下拉刷新:
(已经很详细的注释了,就不多说了,这里有一个注意点就是:
加载更多:
只需要继承此类即可实现加载更多的功能。
RecyclerView卡顿优化(1)主要介绍了基于此进行的进一步优化,源码中也已经更新了最新的优化功能。
源码地址:RefreshRecyclerView源码
如果UI要求不严格,那么有一个很简单的方式实现下拉刷新:SwipeRefreshLayout (具体介绍参考:http://blog.csdn.net/dalancon/article/details/46125667),然后基本你就可以开开心心做完了。
但是如果你们的UI或者产品说,我们要一个什么什么样的功能(反正就是不造轮子不行的需求),那只能自己做了。
先看一下效果:
简单说一下思路:
下拉刷新:
1.自定义一个线性布局linearlayout,里面先添加一个header,然后添加recycclerview。
2.计算出header的高度h1,然后设置linearlayout布局的的padding为-h1(也可以设置margin为-h,都可以实现,本文采用的是padding)
3.利用消息传递机制及对recyclerview的滚动监听来判断是否可以下拉刷新。
4.根据用户的手势移动来设置padding值,再加上一些回滚效果就ok了。
加载更多:
1.首先写一个BaseAdapter抽象类继承
import android.support.v7.widget.RecyclerView.Adapter;
2.在BaseAdapter中添加一个footerViewHolder,并设置开关,如果有加载更多的时候,就显示。如果没有,则不显示。
3.监听RecyclerView的滚动,如果滚动到末尾并且有加载更多,则调用加载更多的方法。
有了上面的思路写起来就简单多了。
接下来分析代码:
下拉刷新:
public class RefreshRecyclerView extends LinearLayout{ public static final long ONE_MINUTE = 60 * 1000;//一分钟的毫秒值,用于判断上次的更新时间 public static final long ONE_HOUR = 60 * ONE_MINUTE;//一小时的毫秒值,用于判断上次的更新时间 public static final long ONE_DAY = 24 * ONE_HOUR;//一天的毫秒值,用于判断上次的更新时间 public static final long ONE_MONTH = 30 * ONE_DAY;//一月的毫秒值,用于判断上次的更新时间 public static final long ONE_YEAR = 12 * ONE_MONTH;//一年的毫秒值,用于判断上次的更新时间 private String MYRECYCLERVIEW = "MyRecyclerView";//保存用的 private int id;//用来区分不同页面 private LinearLayout headerView;//刷新头部布局 public static final int SCROLL_SPEED = -10;//下拉头部回滚的速度 private int topPadding;//距离顶部的padding值 private RecyclerView mRecyclerView;//列表控件 private LinearLayoutManager mLayoutManager;//RecyclerView布局管理器 private int firstVisibleItemPostion;//第一个可见item的position private int mHeaderHeight;//头部高度 private boolean hasMore;//设置是否有加载更多(用户设置) private boolean hasRefresh;//设置是否有下拉刷新(用户设置) private boolean canRefresh;//是否可以下拉刷新(逻辑判断) private boolean isLoading;//列表是否正在刷新或加载更多中 private boolean canScroll;//列表是否可以滚动(刷新加载中禁止用户进行列表操作) private LoadLinsteners loadLinsteners;//加载监听器 private ScrollLinsteners scrollLinsteners;//滚动监听器 private BaseAdapter mAdapter;//Item适配器 //头部相关 private TextView description, updated_at;//描述 private Long lastUpdateTime;//上次更新时间 private ImageView arrow;//箭头 private ProgressBar progress_bar; private ImageView iv_head;//头部图片 private Context context; private int readCount;//判断当前列表最大滚动位置 public RefreshRecyclerView(Context context) { this(context, null); } public RefreshRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; initContents(); } /** * 初始化 */ private void initContents() { //默认可下拉刷新,无加载更多 canRefresh = true; hasMore = false; hasRefresh = true; isLoading = false; canScroll = true; //头部 headerView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.item_recyler_header, null); iv_head = (ImageView)headerView.findViewById(R.id.iv_head); description = (TextView)headerView.findViewById(R.id.description); updated_at = (TextView)headerView.findViewById(R.id.updated_at); arrow = (ImageView)headerView.findViewById(R.id.arrow); progress_bar = (ProgressBar)headerView.findViewById(R.id.progress_bar); //测量并获取头部高度 measureView(headerView); mHeaderHeight = headerView.getMeasuredHeight(); //列表 mRecyclerView = new RecyclerView(context); mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); //创建默认的线性LayoutManager mLayoutManager = new LinearLayoutManager(getContext()); mRecyclerView.setLayoutManager(mLayoutManager); //添加布局 setOrientation(VERTICAL); addView(headerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1)); //隐藏头部 setTopPadding(-mHeaderHeight); setLinsteners(); /** * 添加旋转动画 */ Animation animation= AnimationUtils.loadAnimation(context, R.anim.anim_rotate_earth); LinearInterpolator lin = new LinearInterpolator(); animation.setInterpolator(lin); iv_head.startAnimation(animation); } /** * 设置布局管理器 * @param layoutManager */ public void setmLayoutManager(LinearLayoutManager layoutManager){ this.mLayoutManager = layoutManager; mRecyclerView.setLayoutManager(mLayoutManager); } /** * 获取布局管理器 * @return */ public RecyclerView.LayoutManager getmLayoutManager(){ return mLayoutManager; } /** * 设置adapter * @param mAdapter */ public void setAdapter(BaseAdapter mAdapter){ this.mAdapter = mAdapter; mRecyclerView.setAdapter(mAdapter); } /** * 获取recyclerview * @return */ public RecyclerView getmRecyclerView(){ return mRecyclerView; } /** * 加载监听器 */ public interface LoadLinsteners{ void onLoadMore(); void onRefresh(); } /** * 设置监听器 * @param loadLinsteners * @param id */ public void setLoadLinsteners(LoadLinsteners loadLinsteners, int id){ this.loadLinsteners = loadLinsteners; this.id = id; refreshUpdatedAtValue(); } /** * 设置监听器 * @param loadLinsteners */ public void setLoadLinsteners(LoadLinsteners loadLinsteners){ this.loadLinsteners = loadLinsteners; this.id = -1; refreshUpdatedAtValue(); } /** * 滚动监听接口 */ public interface ScrollLinsteners{ void onScrolled(int firstVisibleItem, int dx, int dy); } /** * 设置滚动监听 * @param scrollLinsteners */ public void setScrollLinsteners(ScrollLinsteners scrollLinsteners){ this.scrollLinsteners = scrollLinsteners; } /** * 获取最大滚动位置 * @return */ public int getReadCount(){ return readCount; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(!canScroll) {//禁止事件传递 return true; }else { return false; } } private float moveY, startY = -1, dY; private void setLinsteners() { /** * 添加触摸监听,实现下拉效果 */ mRecyclerView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (canRefresh && firstVisibleItemPostion == 0 && !isLoading && hasRefresh) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startY = -1; Log.i("move---", "down:-1"); break; case MotionEvent.ACTION_MOVE: moveY = event.getRawY(); Log.i("move----1", "startY:"+ startY + " moveY:" + moveY + " dY:" + dY + " headerHeight:" + mHeaderHeight); if(startY == -1) { startY = moveY; } dY = moveY - startY; Log.i("move----2", "startY:"+ startY + " moveY:" + moveY + " dY:" + dY + " headerHeight:" + mHeaderHeight); if (dY > 30) { dY = dY - 30; int maxLength = mHeaderHeight * 6; if (dY < maxLength && dY >= 30) { description.setText(R.string.pull_to_refresh); } else if (dY >= maxLength) { description.setText(R.string.release_to_refresh); dY = maxLength; } if(dY > mHeaderHeight){ dY = (dY + mHeaderHeight)/2; } setTopPadding((int)(dY - mHeaderHeight)); } else { Log.i("move---", "reset"); setTopPadding(- mHeaderHeight); return false; } break; case MotionEvent.ACTION_UP: Log.i("move---", "up:-1"); if (dY > mHeaderHeight / 2) { isLoading = true; canScroll = false; canRefresh = false; description.setText(R.string.refreshing); arrow.setVisibility(View.GONE); progress_bar.setVisibility(View.VISIBLE); new RefreshingTask().execute(); if(loadLinsteners != null) { loadLinsteners.onRefresh(); } } else { startY = -1; new HideHeaderTask().execute(); return false; } dY = 0; startY = -1; moveY = 0; break; } return true; } return false; } }); /** * 添加滚动监听,判断加载更多 */ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); firstVisibleItemPostion = mLayoutManager.findFirstVisibleItemPosition(); int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); int totalItemCount = mLayoutManager.getItemCount(); if (readCount <= lastVisibleItem) { readCount = lastVisibleItem; } //lastVisibleItem >= totalItemCount - 4 表示剩下4个item自动加载 // dy > 0 表示向下滑动 if (lastVisibleItem >= totalItemCount - 4 && dy >= 0) { if (hasMore && !isLoading) {//可以下拉刷新并且没有加载中 isLoading = true; canScroll = true; if (loadLinsteners != null) { loadLinsteners.onLoadMore(); } } } //如果列表在头部 if (firstVisibleItemPostion == 0 && mLayoutManager.getChildAt(0).getTop() == 0 && hasRefresh) { Log.i("move---", "recyclerview:reset:-1"); canRefresh = true; startY = -1; } else { canRefresh = false; } if (scrollLinsteners != null) { scrollLinsteners.onScrolled(firstVisibleItemPostion, dx, dy); } } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } }); } /** * 设置顶部padding值 */ public void setTopPadding(int top){ topPadding = top; setPadding(0, top, 0, 0); } /** * 停止下拉刷新 */ public void onStopRefresh(){ new HideHeaderTask().execute(); canRefresh = true; isLoading = false; canScroll = true; arrow.setVisibility(View.GONE); progress_bar.setVisibility(View.GONE); saveTv_refresh_time(); } /** * 停止加载更多 */ public void onStopMore(){ isLoading = false; } /** * 保存刷新时间 */ public void saveTv_refresh_time(){ if(id == -1)return; saveSPLongData(MYRECYCLERVIEW + id, System.currentTimeMillis()); refreshUpdatedAtValue(); } public void setId(int id){ this.id = id; } /** * 是否可以加载更多 * @param hasMore */ public void setHasMore(boolean hasMore){ this.hasMore = hasMore; if(mAdapter != null) { mAdapter.setHasMore(hasMore); } } /** * 是否可以下拉刷新 * @param hasRefresh */ public void setHasRefresh(boolean hasRefresh){ this.hasRefresh = hasRefresh; } /** * 测量布局 */ private void measureView(View child) { ViewGroup.LayoutParams params = child.getLayoutParams(); if (params == null) { params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, params.width); int lpHeight = params.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } /** * 刷新下拉头中上次更新时间的文字描述。 */ private void refreshUpdatedAtValue() { if(id == -1)return; lastUpdateTime = getSPLongData(MYRECYCLERVIEW + id); long currentTime = System.currentTimeMillis(); long timePassed = currentTime - lastUpdateTime; long timeIntoFormat; String updateAtValue; if (lastUpdateTime == -1) { updateAtValue = getResources().getString(R.string.not_updated_yet); } else if (timePassed < 0) { updateAtValue = getResources().getString(R.string.time_error); } else if (timePassed < ONE_MINUTE) { updateAtValue = getResources().getString(R.string.updated_just_now); } else if (timePassed < ONE_HOUR) { timeIntoFormat = timePassed / ONE_MINUTE; String value = timeIntoFormat + "分钟"; updateAtValue = String.format(getResources().getString(R.string.updated_at), value); } else if (timePassed < ONE_DAY) { timeIntoFormat = timePassed / ONE_HOUR; String value = timeIntoFormat + "小时"; updateAtValue = String.format(getResources().getString(R.string.updated_at), value); } else if (timePassed < ONE_MONTH) { timeIntoFormat = timePassed / ONE_DAY; String value = timeIntoFormat + "天"; updateAtValue = String.format(getResources().getString(R.string.updated_at), value); } else if (timePassed < ONE_YEAR) { timeIntoFormat = timePassed / ONE_MONTH; String value = timeIntoFormat + "个月"; updateAtValue = String.format(getResources().getString(R.string.updated_at), value); } else { timeIntoFormat = timePassed / ONE_YEAR; String value = timeIntoFormat + "年"; updateAtValue = String.format(getResources().getString(R.string.updated_at), value); } updated_at.setText(updateAtValue); } /** * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。 */ class RefreshingTask extends AsyncTask<Void, Integer, Integer> { @Override protected Integer doInBackground(Void... params) { int tp = topPadding; long curMills = System.currentTimeMillis(); while (true) { long nowMills = System.currentTimeMillis(); if(nowMills - curMills >= 10){ curMills = nowMills; tp = tp + SCROLL_SPEED * 2; if (tp <= 0) { tp = 0; break; } publishProgress(tp); } } return tp; } @Override protected void onProgressUpdate(Integer... tp) { setTopPadding(tp[0]); } @Override protected void onPostExecute(Integer tp) { setTopPadding(tp); } } /** * 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。 */ class HideHeaderTask extends AsyncTask<Void, Integer, Integer> { @Override protected Integer doInBackground(Void... params) { int tp = topPadding; long curMills = System.currentTimeMillis(); while (true) { long nowMills = System.currentTimeMillis(); if(nowMills - curMills >= 10) { curMills = nowMills; tp = tp + SCROLL_SPEED; if (tp <= -mHeaderHeight) { tp = -mHeaderHeight; break; } publishProgress(tp); } } return tp; } @Override protected void onProgressUpdate(Integer... tp) { setTopPadding(tp[0]); } @Override protected void onPostExecute(Integer tp) { setTopPadding(tp); } } /** * 获取账户管理sp * @return */ private SharedPreferences getAccountSP() { return context.getSharedPreferences("account", Context.MODE_PRIVATE); } /** * 保存Long值 * @param key * @param value */ public void saveSPLongData(String key, long value) { getAccountSP().edit().putLong(key, value).commit(); } /** * 获取存储的Long值 * @param key * @return */ public Long getSPLongData(String key) { return getAccountSP().getLong(key, -1); } }
(已经很详细的注释了,就不多说了,这里有一个注意点就是:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' <span style="color:#ff0000;">compile 'com.android.support:design:23.2.0'</span> }红色区域是需要注意的,如果你用的是23.3.0会有问题。。。原因未知。。。其它版本未测试)
加载更多:
public abstract class BaseAdapter<T,K extends ViewHolder> extends Adapter<K> { protected final LayoutInflater layoutInflater; public List<T> mMessages; private static final int TYPE_FOOTER = -2; public boolean hasMore; public Context context; private int mResources; private ItemOnclickLinstener itemOnclickLinstener; public BaseAdapter(Context context, List<T> mMessages, int mResources) { this.mMessages = mMessages; this.context = context; this.mResources = mResources; layoutInflater = LayoutInflater.from(context); hasMore = false; } @Override public K onCreateViewHolder(ViewGroup parent, int viewType) { // type == TYPE_FOOTER 返回footerView if (viewType == TYPE_FOOTER) { View view = layoutInflater.inflate(R.layout.item_footer, null); view.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return (K)new FooterViewHolder(view); }else { final K vh = createViewHolder(viewType, parent); vh.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(itemOnclickLinstener != null) { itemOnclickLinstener.setItemOnclickLinsteners(v, vh.getLayoutPosition(), vh.getItemViewType()); } } }); return vh; } } @Override public void onBindViewHolder(final K viewHolder, final int position) { if(!(hasMore && position == getItemCount() -1)){ setOnBindViewHolder(viewHolder, position); } } @Override public int getItemCount() { int size = 0; if(mMessages != null){ size = mMessages.size(); } return hasMore ? size + 1 : size; } @Override public int getItemViewType(int position) { if (position + 1 == getItemCount() && hasMore) { return TYPE_FOOTER; } else { return setItemViewType(position); } } /** * footer ViewHolder */ class FooterViewHolder extends ViewHolder { public FooterViewHolder(View view) { super(view); } } /** * 是否显示加载更多 * @param hasMore */ public void setHasMore(boolean hasMore){ this.hasMore = hasMore; } /** * 创建ViewHolder * @param viewType * @return */ protected abstract K createViewHolder(int viewType, ViewGroup parent); /** * 设置ViewHolder类型 * @param position * @return */ protected abstract int setItemViewType(int position); /** * 数据填充 */ protected abstract void setOnBindViewHolder(K viewHolder, int position); /** * Item点击事件 * @param * @return */ public void setItemOnClickLinsteners(ItemOnclickLinstener itemOnclickLinstener){ this.itemOnclickLinstener = itemOnclickLinstener; } /** * item点击接口 */ public interface ItemOnclickLinstener{ void setItemOnclickLinsteners(View v, int postion, int type); } protected View getView(int resource){ View view = layoutInflater.inflate(resource, null); view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return view; } protected View getView(){ View view = layoutInflater.inflate(mResources, null); view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return view; } protected View getView(ViewGroup parent){ View view = layoutInflater.inflate(mResources, parent, false); view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return view; } }
只需要继承此类即可实现加载更多的功能。
RecyclerView卡顿优化(1)主要介绍了基于此进行的进一步优化,源码中也已经更新了最新的优化功能。
源码地址:RefreshRecyclerView源码
相关文章推荐
- 高仿QQ下拉刷新之LoadView
- 【论文笔记】未分类_2016
- Android textview边框绘制
- 应用系统基础构建设计开发实践(一)
- maven搭建
- WebService之XFire组件开发
- Netbeans配置OpenCv
- sqlyog写存储过程中的注意事项
- Android开发笔记:如何使用预先制作好的SQLite数据库(整理自网络)
- 添加语言后语言列表没有显示或者显示空白和乱码
- phpcms-v9的时间标签
- Hibernate执行原生sql
- Android动画
- jenkins2 javahelloworld
- ubuntu下使用git, github
- MapReduce任务运行到running job卡住
- mybatis 根据Map字段对应值更新
- 基于postgresql+postGIS+QGIS+MapServer的WebGIS部署
- EXCHANGE2010公用文件夹迁移至EXCHANGE2016报错
- 账号二次登陆,强制当前账号下线