欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

RecyclerView下拉刷新上拉加载

程序员文章站 2024-03-31 11:47:28
一 、前言 最近实在太忙,一个多礼拜没有更新文章了,于是今晚加班加点把demo写出来,现在都12点了才开始写文章。 1.我们的目标 把recyclerview下拉刷新...

一 、前言

最近实在太忙,一个多礼拜没有更新文章了,于是今晚加班加点把demo写出来,现在都12点了才开始写文章。

1.我们的目标

把recyclerview下拉刷新上拉加载更多加入到我们的开发者头条app中。

2.效果图

RecyclerView下拉刷新上拉加载

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);
  }

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!