RecyclerView下拉刷新上拉加载
下拉刷新:
上拉加载:
说一下第一张图,顶端的黑边是做gif图的时候出现的,不明其意,尴尬咯!下面进入正题。
下拉刷新
思路:一切复杂的事情都是先从简单入手,分为以下步骤:
1. 我们可以把头部的下拉刷新布局当做列表的一个item,就是头部item,它的布局肯定是跟我们内容不一样的,这个好办,RecyclerView.Adapter类提供了一个getItemViewType(int position)方法,这个方法可以让我们轻松实现一个列表可以有不同布局。
2. 我们发现当我们下拉的时候,该布局的内容是需要改变的,这个也容易,只要在适配类添加方法就可以办到,比如我这个例子只是简单的改变文字描述
private TextView tvContentHead; class HeadViewHolder extends RecyclerView.ViewHolder{ public HeadViewHolder(View itemView) { super(itemView); tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead); } } public void setText(String text){ tvContentHead.setText(text); }
setText() 就是我给外部提供的方法,HeadViewHoler类就是我们的头部Item
3. 我们的列表是到达顶部时,才执行下拉刷新的,所以我们应当要判断列表是否到达顶部,RecyclerView可以监听OnScrollListener类,并且实现onScrolled(RecyclerView recyclerView, int dx, int dy)方法,这个方法是监听列表的滑动,那怎么样才知道列表已经滑动到顶部了呢?网上有很多资料,列举了几个方法,但是我觉得最有信服力是canScrollVertically方法,下面看代码
this.addOnScrollListener(new OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (recyclerView.canScrollVertically(-1)){ isTop = false; }else { isTop = true; } if (!recyclerView.canScrollVertically(1)){ pullRefreshListener.isLoading(); } } });
recyclerView.canScrollVertically(-1) 这个方法返回false时,说明列表已经滑动到顶部了,这时我们就可以操作下拉刷新了
4. 下面是整个下拉刷新的核心内容 已经到达顶部了,我们怎么样才可以让他有下拉的感觉呢? 我这里使用一个技巧,说到这个技巧我都想笑,那就是外边距,对,没错,我就是通过控制列表的上边外边距实现的下拉刷新,开始时,我们的头部本来就是存在的,所以我们把marginTop设置为负头部的高度值,这样给人的感觉就是没有头部内容了,所以当我们到底顶部的时候,就可以通过控制上边外边距来控制下来效果,当然,还有解决滑动冲突,下面看代码:
this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: y = event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (!isTop) { y = event.getRawY(); } else { float mY = event.getRawY(); float dY = mY - y; if (dY >= 0 && isOpenRefresh) { int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); layoutParams.setMargins(0, top, 0, 0); setLayoutParams(layoutParams); if (dY >= headHeight) { isRefresh = true; dropDownRefreshListener.isRefreshTip(); } else { isRefresh = false; dropDownRefreshListener.noRefreshTip(); } return true; } } break; case MotionEvent.ACTION_UP: if (isTop) { if (isRefresh) { dropDownRefreshListener.isRefreshing(); }else { dropDownRefreshListener.noRefresh(); } } break; } return false; } }); }
实现onTouch,当到达顶部时,就计算下来距离,然后改变上边外边距,当下拉到一定数值时,提示用户可以可以刷新了。我这里是使用了回调dropDownRefreshListener.isRefreshTip();方便用户自定义信息。当我们正在执行下拉操作时,onTouch返回true,直接消费,其他操作直接返回false,这样就解决了滑动冲突。当手指离开屏幕时,如果isRefesh为true,说明可以执行刷新操作,否则反之。
5. 我们的接口都提供出来了,那就看看我们的实现吧,其实很简单,上代码吧
myAadapter = new MyTestAadapter(this,list); recyclerView.setAdapter(myAadapter); recyclerView.setRefresh(true); recyclerView.onDropDownResfreshListener(new MyRecyclerView.DropDownRefreshListener() { @Override public void isRefreshing() { myAadapter.setText("正在刷新"); //模拟网络加载数据,这里使用延迟策略 new Handler().postDelayed(new Runnable() { @Override public void run() { //加载完成 recyclerView.hideRefeshLayout(); myAadapter.setText("下拉刷新"); } }, 4000); } @Override public void noRefresh() { recyclerView.hideRefeshLayout(); myAadapter.setText("下拉刷新"); } @Override public void isRefreshTip() { myAadapter.setText("松开刷新"); } @Override public void noRefreshTip() { myAadapter.setText("下拉刷新"); } });
recyclerView.setRefresh(true)表示开启下拉刷新,四个回调方法一看就明白了。
上拉加载
上拉加载就比较简单了,
添加一个底部item,跟头部同理,判断是否到达底部,
if (!recyclerView.canScrollVertically(1)){ pullRefreshListener.isLoading(); }到达底部则实现回调,下面看看接口的实现:
recyclerView.onPullResfreshListener(new MyRecyclerView.PullRefreshListener() { @Override public void isLoading() { if (!islaod){ islaod = true; myAadapter.setFooterText("正在加载"); //模拟网络加载数据,这里使用延迟策略 new Handler().postDelayed(new Runnable() { @Override public void run() { List<String> list = new ArrayList<>(); for (int i=0;i<10;i++){ list.add("加载内容"+i); } myAadapter.addData(list); islaod = false; //加载完成 myAadapter.setFooterText("上拉加载"); } }, 4000); } } });
当正在加载时,如果用户继续上拉,就会再次回调isLoading,isLaod是为了防止这个情况的发生。延迟策略是模拟一下数据加载。
下面发一下完整代码:
MyRecyclerView类:
public class MyRecyclerView extends RecyclerView { private float y; private boolean isTop = false; private boolean isRefresh = false; private boolean isOpenRefresh = false; private int topMargin; private int headHeight = 200; public MyRecyclerView(Context context) { this(context,null); } public MyRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.addOnScrollListener(new OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (recyclerView.canScrollVertically(-1)){ isTop = false; }else { isTop = true; } if (!recyclerView.canScrollVertically(1)){ pullRefreshListener.isLoading(); } } }); this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: y = event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (!isTop) { y = event.getRawY(); } else { float mY = event.getRawY(); float dY = mY - y; if (dY >= 0 && isOpenRefresh) { int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); layoutParams.setMargins(0, top, 0, 0); setLayoutParams(layoutParams); if (dY >= headHeight) { isRefresh = true; dropDownRefreshListener.isRefreshTip(); } else { isRefresh = false; dropDownRefreshListener.noRefreshTip(); } return true; } } break; case MotionEvent.ACTION_UP: if (isTop) { if (isRefresh) { dropDownRefreshListener.isRefreshing(); }else { dropDownRefreshListener.noRefresh(); } } break; } return false; } }); } /** * 是否开启下拉刷新 * @param isOpenRefresh */ public void setRefresh(boolean isOpenRefresh){ this.isOpenRefresh = isOpenRefresh; if (isOpenRefresh){ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)getLayoutParams(); topMargin = layoutParams.topMargin; // Log.e("setRefresh","topMargin="+topMargin); } } /** * 隐藏下拉刷新布局 */ public void hideRefeshLayout(){ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); layoutParams.setMargins(0, topMargin, 0, 0); setLayoutParams(layoutParams); isRefresh = false; } public void onDropDownResfreshListener(DropDownRefreshListener dropDownRefreshListener){ this.dropDownRefreshListener = dropDownRefreshListener; } public interface DropDownRefreshListener{ /** * 当手指离开屏幕时,下拉距离符合加载要求,执行这个方法 */ void isRefreshing(); /** * 当手指离开屏幕时,下拉距离不符合加载要求,执行这个方法 */ void noRefresh(); /** * 当手指还在屏幕上时,下拉距离符合加载要求,执行这个方法 */ void isRefreshTip(); /** * 当手指还在屏幕上时,下拉距离不符合加载要求,执行这个方法 */ void noRefreshTip(); } private DropDownRefreshListener dropDownRefreshListener; public void onPullResfreshListener(PullRefreshListener pullRefreshListener){ this.pullRefreshListener = pullRefreshListener; } public interface PullRefreshListener{ /** * 当上拉距离符合加载要求,执行这个方法 */ void isLoading(); } private PullRefreshListener pullRefreshListener; }
MyTestAadapter类:
public class MyTestAadapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context; private LayoutInflater inflater; private List<String> list; public static final int HEAD = 1; public static final int DEF_VIEW = 2; public static final int FOOTER = 3; public MyTestAadapter(Context context, List<String> list){ this.context = context; this.inflater = LayoutInflater.from(context); this.list = list; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder = null; switch (viewType){ case DEF_VIEW: viewHolder = new DefViewHolder(inflater.inflate(R.layout.recycler_item_def,parent,false)); break; case HEAD: viewHolder = new HeadViewHolder(inflater.inflate(R.layout.recycler_item_head,parent,false)); break; case FOOTER: viewHolder = new FooterViewHolder(inflater.inflate(R.layout.recycler_item_footer,parent,false)); break; } return viewHolder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)){ case DEF_VIEW: DefViewHolder defViewHolder = (DefViewHolder) holder; defViewHolder.tvContent.setText(list.get(position-1)); break; case HEAD: break; case FOOTER: break; } } @Override public int getItemCount() { return list.size()==0?0:list.size()+2; } @Override public int getItemViewType(int position) { if (position==0){ return HEAD; } if (list.size()>0 && list.size()+1==position){ return FOOTER; }else { return DEF_VIEW; } } public void addData(List<String> stringList){ for (String s : stringList){ list.add(s); } notifyItemRangeInserted(getItemCount()-2,stringList.size()); } class DefViewHolder extends RecyclerView.ViewHolder{ private TextView tvContent; public DefViewHolder(View itemView) { super(itemView); tvContent = (TextView) itemView.findViewById(R.id.tvContent); } } private TextView tvContentHead; class HeadViewHolder extends RecyclerView.ViewHolder{ public HeadViewHolder(View itemView) { super(itemView); tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead); } } public void setText(String text){ tvContentHead.setText(text); } private TextView tvContentFooter; class FooterViewHolder extends RecyclerView.ViewHolder{ public FooterViewHolder(View itemView) { super(itemView); tvContentFooter = (TextView) itemView.findViewById(R.id.tvContentFooter); } } public void setFooterText(String text){ tvContentFooter.setText(text); } }
demo地址: http://download.csdn.net/download/u012992345/10015294