RecyclerView下拉刷新上拉加载
一 、前言
最近实在太忙,一个多礼拜没有更新文章了,于是今晚加班加点把demo写出来,现在都12点了才开始写文章。
1.我们的目标
把recyclerview下拉刷新上拉加载更多加入到我们的开发者头条app中。
2.效果图
3.实现步骤
- 找一个带上拉刷新下载加载更多的recyclerview开源库,我们要站在巨人的肩膀上
- 下载下来自己先运行下demo,然后看看是不是我们需要的功能,觉得不错就把module依赖进来,整合主项目。
- 整合进来了之后,我们肯定需要进行修改,例如我这边就有滑动冲突,有多个headview等问题。
二 、具体实现
1.寻找recyclerview上拉刷新下载加载开源库
我们找开源项目肯定首选github,去搜索一下一大堆,如果效果图是你想要的功能的话,然后找排名靠前,收藏比较多的项目吧,我找的项目是commonpulltorefresh,支持listview,recyclerview,gridview,swiperefreshlayout等常用控件。我跑了一下demo,没啥bug,挺好用的。
2.加入项目中
1).module导入进来,然后主项目依赖一下,这里有不会的看我另外一篇文章android studio 入门,里面有讲到android studio添加项目依赖。
2).代码实现,我们这边就是修改selectedfragment
首先我们看布局文件的变化,在recyclerview外面包裹了自定义的一个类ptrclassicframelayout,内部实现了下拉刷新,上拉加载更多。还可以设置自定义属性,都是啥意思我就不解释了,有兴趣的点击github上那个链接,讲解的很详细。
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.chanven.lib.cptr.ptrclassicframelayout xmlns:cube_ptr="http://schemas.android.com/apk/res-auto" android:id="@+id/test_recycler_view_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f0f0f0" cube_ptr:ptr_duration_to_close="200" cube_ptr:ptr_duration_to_close_header="700" cube_ptr:ptr_keep_header_when_refresh="true" cube_ptr:ptr_pull_to_fresh="false" cube_ptr:ptr_ratio_of_header_height_to_refresh="1.2" cube_ptr:ptr_resistance="1.8"> <android.support.v7.widget.recyclerview android:id="@+id/test_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"/> </com.chanven.lib.cptr.ptrclassicframelayout> </linearlayout>
再来看oncreateview方法,这个代码就不解释了。
@override public view oncreateview(layoutinflater inflater, viewgroup container,bundle savedinstancestate){ view rootview = layoutinflater.from(getactivity()).inflate(r.layout.fragment_selected, null); ptrclassicframelayout = (ptrclassicframelayout) rootview.findviewbyid(r.id.test_recycler_view_frame); mrecyclerview = (recyclerview) rootview.findviewbyid(r.id.test_recycler_view); mrecyclerview.setlayoutmanager(new linearlayoutmanager(getactivity())); init(); return rootview; }
在oncreateview里面调用了init()方法,我们来瞧瞧怎么实现的。这里解释一下为什么要对适配器进行包装,这样的目的在包装类里面处加入头部,底部view,处理点击事件。大家拿到源码了之后自己也可以看看。
private void init() { //初始化适配器 selectedadapter = new selectedrecycleradapter(getactivity()); //对适配器进行封装 madapter = new recycleradapterwithhf(selectedadapter); //把滚动banner加入头部 madapter.addcarouse(initcarouselhead()); mrecyclerview.setadapter(madapter); ptrclassicframelayout.setptrhandler(ptrdefaulthandler);//设置下拉监听 ptrclassicframelayout.setonloadmorelistener(onloadmorelistener);//设置上拉监听 ptrclassicframelayout.setloadmoreenable(true);//设置可以加载更多 }
madapter.addcarouse(initcarouselhead()); 初始化一个滚动banner,然后加入适配器头部。这个我前面的教程应该已经讲过了。。
//初始化 private view initcarouselhead(){ view headview = layoutinflater.from(getactivity()).inflate(r.layout.fragment_selected_header,mrecyclerview,false); tvcontent=(textview) headview.findviewbyid(r.id.tv_content); tvcontent.settext(carousepagestr[0]); viewpager = (viewpager)headview.findviewbyid(r.id.viewpager); selectedpageradapter=new selectedpageradapter(getactivity(),carousepagerselectview); viewpager.setoffscreenpagelimit(2); viewpager.setcurrentitem(0); viewpager.addonpagechangelistener(onpagechangelistener); viewpager.setadapter(selectedpageradapter); viewgroup group = (viewgroup) headview.findviewbyid(r.id.viewgroup);// 初始化底部显示控件 tips = new imageview[3]; for (int i = 0; i < tips.length; i++){ imageview imageview = new imageview(getactivity()); if (i == 0) { imageview.setbackgroundresource(r.mipmap.page_indicator_focused); } else { imageview.setbackgroundresource(r.mipmap.page_indicator_unfocused); } tips[i] = imageview; linearlayout.layoutparams layoutparams = new linearlayout.layoutparams(new layoutparams(layoutparams.wrap_content,layoutparams.wrap_content)); layoutparams.leftmargin = 10;// 设置点点点view的左边距 layoutparams.rightmargin = 10;// 设置点点点view的右边距 group.addview(imageview, layoutparams); } timer = new timer(true);//初始化计时器 timer.schedule(task, 0, carousel_time);//延时0ms后执行,3000ms执行一次 return headview; }
selectedrecycleradapter 必须继承recyclerview.adapter
这玩意跟listview的适配器差不多,用过listview适配器的应该一看就懂了。
首先会调用getitemcount,知道我要显示多少item。
知道了行数然后就是循环调用oncreateviewholder跟onbindviewholder了,oncreateviewholder就是创建一个item的view,onbindviewholder就会把上次创建的item的view传入进来,还有一个下标,这样我们就能给每一行赋值,这两个方法都是先后一起调用。item回收重用的机制应该跟listview一样的。
public class selectedrecycleradapter extends recyclerview.adapter<recyclerview.viewholder> { private list<selectedarticle> selectedarticles; private layoutinflater inflater; public selectedrecycleradapter(context context) { super(); inflater = layoutinflater.from(context); selectedarticles = new arraylist<selectedarticle>(); initdata(); } private void initdata() { for (int i = 0; i < 10; i++) { selectedarticle selectedarticle = new selectedarticle(i, "android开发666", i, i, ""); selectedarticles.add(selectedarticle); } } public void loadmore(int page) { for (int i = 0; i < 5; i++) { selectedarticle selectedarticle = new selectedarticle(i, "第" + page + "页数据", i, i, ""); selectedarticles.add(selectedarticle); } } public void getfirst() { selectedarticles.clear(); initdata(); } @override public int getitemcount() { return selectedarticles.size(); } @override public void onbindviewholder(recyclerview.viewholder viewholder, int position) { selectedrecyclerholder holder = (selectedrecyclerholder) viewholder; selectedarticle selectedarticle = selectedarticles.get(position); holder.title.settext(selectedarticle.gettitle()); holder.like.settext("" + selectedarticle.getlikenumber()); holder.comment.settext("" + selectedarticle.getcommentnumber()); } @override public recyclerview.viewholder oncreateviewholder(viewgroup viewholder, int position) { view view = inflater.inflate(r.layout.fragment_selected_item, null); return new selectedrecyclerholder(view); } public class selectedrecyclerholder extends recyclerview.viewholder { private textview title;//标题 private textview like;//喜欢数量 private textview comment;评论数量 public selectedrecyclerholder(view view) { super(view); title = (textview) view.findviewbyid(r.id.tv_title); like = (textview) view.findviewbyid(r.id.tv_like); comment = (textview) view.findviewbyid(r.id.tv_comment); } } }
3.解决整合进来的bug
滑动冲突
当我们上拉到顶部把标题栏挤出屏幕外的时候,进行下拉会触发recyclerview的下拉事件,正确的情况应该是显示toolbar.
1).recyclerview下拉刷新的时候先判断toolbar有没有显示。如果toolbar没有显示就不处理。
2).appbarlayout有一个addonoffsetchangedlistener方法,在appbarlayout的布局偏移量发生改变时被调用。
在mainfragment里面进行监听
appbarlayout= (appbarlayout) rootview.findviewbyid(r.id.appbarlayout); appbarlayout.addonoffsetchangedlistener(onoffsetchangedlistener);
然后在回调函数中,把值给selectedfragment,
private appbarlayout.onoffsetchangedlistener onoffsetchangedlistener=new appbarlayout.onoffsetchangedlistener() { @override public void onoffsetchanged(appbarlayout appbarlayout, int i){ //i>=0 toolbar全部显示 selectedfragment.setpullrefresh(i>=0); system.out.println("i值:"+i); } };
3).在selectedfragment中,继续把值传给ptrframelayout
public void setpullrefresh(boolean pullrefresh) { ptrclassicframelayout.setpullrefresh(pullrefresh); }
4.在ptrframelayout里面用一个实例变量接收这个值
private boolean pullrefresh=true; public void setpullrefresh(boolean pullrefresh) { this.pullrefresh = pullrefresh; }
4).找到ptrframelayout类的dispatchtouchevent事件,这个方法是处理屏幕的触摸事件的。
@override public boolean dispatchtouchevent(motionevent e) { if (!isenabled() || mcontent == null || mheaderview == null) { system.out.println("都是空的..."); return dispatchtoucheventsupper(e); } int action = e.getaction(); switch (action) { case motionevent.action_up: system.out.println("弹起..."); case motionevent.action_cancel: system.out.println("取消..."); // if(pullrefresh){ mptrindicator.onrelease(); if (mptrindicator.hasleftstartposition()) { if (debug) { ptrclog.d(log_tag, "call onrelease when user release"); } system.out.println("call onrelease when user release"); onrelease(false); if (mptrindicator.hasmovedafterpresseddown()) { sendcancelevent(); return true; } } return dispatchtoucheventsupper(e); // } case motionevent.action_down: system.out.println("按下..."); mhassendcancelevent = false; mptrindicator.onpressdown(e.getx(), e.gety()); mscrollchecker.abortifworking(); mpreventforhorizontal = false; // the cancel event will be sent once the position is moved. // so let the event pass to children. // fix #93, #102 return dispatchtoucheventsupper(e); case motionevent.action_move: system.out.println("移动..."); if(pullrefresh){//toolbar显示 mlastmoveevent = e; mptrindicator.onmove(e.getx(), e.gety()); float offsetx = mptrindicator.getoffsetx(); float offsety = mptrindicator.getoffsety(); if (mdisablewhenhorizontalmove && !mpreventforhorizontal && (math.abs(offsetx) > mpagingtouchslop && math.abs(offsetx) > math.abs(offsety))) { if (mptrindicator.isinstartposition()) { mpreventforhorizontal = true; } } if (mpreventforhorizontal) { return dispatchtoucheventsupper(e); } boolean movedown = offsety > 0; boolean moveup = !movedown; boolean canmoveup = mptrindicator.hasleftstartposition(); if (debug) { boolean canmovedown = mptrhandler != null && mptrhandler.checkcandorefresh(this, mcontent, mheaderview); ptrclog.v(log_tag, "action_move: offsety:%s, currentpos: %s, moveup: %s, canmoveup: %s, movedown: %s: canmovedown: %s", offsety, mptrindicator.getcurrentposy(), moveup, canmoveup, movedown, canmovedown); } // disable move when header not reach top if (movedown && mptrhandler != null && !mptrhandler.checkcandorefresh(this, mcontent, mheaderview)) { return dispatchtoucheventsupper(e); } if ((moveup && canmoveup) || movedown) { // system.out.println("是否下拉刷新:"+pullrefresh+"偏移量是多少:"+offsety); movepos(offsety); return true; } } } return dispatchtoucheventsupper(e); }
我就改了一行代码,在action==motionevent.action_move的时候,先判断我们传入的pullrefresh是否为true。。。
顶部加入轮播
recyclerview头部底部加入view,前面我们介绍过了,都是适配器的封装类recycleradapterwithhf来控制。从效果图中,我们可以看出,轮播的view是加入头部的,找到recycleradapterwithhf类,看看源码依葫芦画瓢就可以了。
1).得有一个保存view的集合,其实用一个变量也行,因为我们只有一个轮播view.
private list<view> mcarouse = new arraylist<view>();//保存轮播view //可以添加轮播view public void addcarouse(view view){ mcarouse.add(view); }
2).定义一个常量,用于类型判断
public static final int type_carouse = 7900;
3).在getitemviewtype里面加入轮播的类型
@override public final int getitemviewtype(int position) { // check what type our position is, based on the assumption that the // order is headers > items > footers if (isheader(position)) { return type_header; } else if (mcarouse.size()>0&&mheaders.size()==position){ //判断集合个数&&position==0 这个时候mheaders里面是没有值的 return type_carouse; }else if (isfooter(position)) { return type_footer; } int type = getitemviewtypehf(getrealposition(position)); if (type == type_header || type == type_footer|| type == type_carouse) { throw new illegalargumentexception("item type cannot equal " + type_header + " or " + type_footer); } return type; }
4).oncreateviewholder里面也要修改一下,就是在if里面多加了个&&.无论是头部,底部,轮播的view,都是添加到framelayout里面的。
@override public final recyclerview.viewholder oncreateviewholder(viewgroup viewgroup, int type) { // if our position is one of our items (this comes from // getitemviewtype(int position) below) if (type != type_header && type != type_footer && type != type_carouse) { viewholder vh = oncreateviewholderhf(viewgroup, type); return vh; // else we have a header/footer } else { // create a new framelayout, or inflate from a resource framelayout framelayout = new framelayout(viewgroup.getcontext()); // make sure it fills the space framelayout.setlayoutparams(new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent)); return new headerfooterviewholder(framelayout); } }
5).onbindviewholder这里为item绑定数据,其实就是第四步返回的itemview绑定数据.
@override public final void onbindviewholder(final recyclerview.viewholder vh, int position){ // check what type of view our position is if (isheader(position)) { view v = mheaders.get(position); // add our view to a header view and display it prepareheaderfooter((headerfooterviewholder) vh, v); }else if(mcarouse.size()>0&&position==mheaders.size()){//这个时候mheaders.size()值为0 // system.out.println("有多少个头view:"+mheaders.size()+"值等于多少:"+(mheaders.size()-1)); view v = mcarouse.get(mheaders.size());//取出轮播的view prepareheaderfooter((headerfooterviewholder) vh, v); } else if (isfooter(position)) { view v = mfooters.get(position - getitemcounthf() - mheaders.size()); // add our view to a footer view and display it prepareheaderfooter((headerfooterviewholder) vh, v); } else { vh.itemview.setonclicklistener(new myonclicklistener(vh)); vh.itemview.setonlongclicklistener(new myonlongclicklistener(vh)); // it's one of our items, display as required onbindviewholderhf(vh, getrealposition(position)); } }
6).我们从第五步看到头部底部轮播view最后都会调用prepareheaderfooter方法。看到这方法的源码,其实就是把类型对应的view,添加到item中.
private void prepareheaderfooter(headerfooterviewholder vh, view view) { // if it's a staggered grid, span the whole layout if (mmanagertype == type_manager_staggered_grid) { staggeredgridlayoutmanager.layoutparams layoutparams = new staggeredgridlayoutmanager.layoutparams (viewgroup.layoutparams.match_parent,viewgroup.layoutparams.wrap_content); layoutparams.setfullspan(true); vh.itemview.setlayoutparams(layoutparams); } // if the view already belongs to another layout, remove it if (view.getparent() != null) { ((viewgroup) view.getparent()).removeview(view); } // empty out our framelayout and replace with our header/footer vh.base.removeallviews(); vh.base.addview(view); }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
推荐阅读
-
Android手把手教大家制作APP首页(下拉刷新、自动加载)
-
Android中RecyclerView上拉下拉,分割线,多条目的实例代码
-
iOS实现MJRefresh下拉刷新(上拉加载)使用详解
-
iOS tableView上拉刷新显示下载进度的问题及解决办法
-
Android实现上拉加载更多以及下拉刷新功能(ListView)
-
Android实现第三方登录的上拉展开,下拉隐藏,下拉隐藏示例
-
Android RecyclerView自定义上拉和下拉刷新效果
-
Android Recyclerview实现上拉加载更多功能
-
05_微信小程序之scroll-view下拉刷新以及上拉加载
-
jQuery实现的上拉刷新功能组件示例