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

Android实现RecyclerView下拉刷新效果

程序员文章站 2022-04-13 22:49:59
本文为大家分享了android实现recyclerview下拉刷新效果的具体代码,供大家参考,具体内容如下 思路 realpullrefreshview继承了一...

本文为大家分享了android实现recyclerview下拉刷新效果的具体代码,供大家参考,具体内容如下

思路

  • realpullrefreshview继承了一个linearlayout
  • 里面放置了一个刷新头布局,将其margin_top设置为负的刷新头的高度的
  • 再添加一个recyclerview
  • 触摸事件分发机制,当在特定条件下让realpullrefreshview拦截触摸事件,否则的话,不拦截,让recyclerview自己去处理触摸事件
  • 在手指下拉时,定义好不同的状态state,在不同状态下,处理不同的显示,这里讲不同状态下的刷新头如何显示,抽象为一个接口,用户可以实现这个接口,自定义刷新头的布局和动画
  • 加载更多的功能是利用recyclerview的多type布局实现的
  • 难点在于触摸事件的拦截,和认真处理各种滑动的问题

使用

xml

 <com.example.apple.quickdemo.realview.view.realpullrefreshview
    android:id="@+id/real_pull_refresh_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:refresh_header_view="@layout/headerview"/>


这是headerview

<?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="50dp"
       android:background="#ff0"
       android:orientation="horizontal"
       android:gravity="center">


  <textview
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="@color/coloraccent"
    android:visibility="visible"
    android:id="@+id/tv"
    android:gravity="center"
    android:text="下拉刷新"
    android:textsize="21sp"/>
  <imageview
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

    android:src="@mipmap/ic_launcher"
    android:id="@+id/iv"
    />

</linearlayout>

代码

mrealpullrefreshview.setlayoutmanager(mlayoutmanager);
    mrealpullrefreshview.setadapter(mmyadapte);
    //用户可以自定义自己的刷新头布局和动画
   //mrealpullrefreshview.setonpullshowviewlistener(new gifonpullshowviewlisterner(mrealpullrefreshview));

    mrealpullrefreshview.setonpulllistener(new realpullrefreshview.onpulllistener() {
      @override
      public void onrefresh() {

        mhandler.postdelayed(new runnable() {
          @override
          public void run() {
            mbodies.add(0, new body("新数据"+i++,100));
            mrealpullrefreshview.refreshfinish();
          }
        }, 3000);

      }

      @override
      public void onloadmore() {

        final list<body> more=new arraylist<body>();
        mhandler.postdelayed(new runnable() {
          @override
          public void run() {
            for (int i = 0; i < 3; i++) {
              more.add(new body("more+++"+i,100));

            }


            mbodies.addall(more);
            mrealpullrefreshview.loadmrefinish();
          }
        }, 1500);

      }
    });

自定义刷新头布局和动画

package com.example.apple.quickdemo.realview.show;

import android.animation.objectanimator;
import android.animation.valueanimator;
import android.util.log;
import android.view.view;
import android.widget.imageview;
import android.widget.textview;

import com.example.apple.quickdemo.r;
import com.example.apple.quickdemo.realview.view.realpullrefreshview;

/**
 * created by apple on 2017/7/9.
 */

public class implonpullshowviewlistener implements realpullrefreshview.onpullshowviewlistener {

  private textview mtv;
  private imageview miv;
  private objectanimator mani;
  view mheaderview;


  public implonpullshowviewlistener(realpullrefreshview realpullrefreshview) {
    mheaderview = realpullrefreshview.getrefreshheaderview();
    mtv = (textview) mheaderview.findviewbyid(r.id.tv);
    miv = (imageview) mheaderview.findviewbyid(r.id.iv);
    mani = objectanimator.offloat(miv, "rotation", -15, 15).setduration(300);
    mani.setrepeatcount(valueanimator.infinite);
    mani.setrepeatmode(valueanimator.reverse);
  }

  @override
  public void onpulldownrefreshstate(int scrolly, int headviewheight,int deltay) {
    mtv.settext("下拉刷新");
    float f = -((float) scrolly / (float) headviewheight);
    log.e("tag", f+ "");
    log.e("tag", -scrolly + "scrolly");


    miv.setscalex(f);
    miv.setscaley(f);
  }

  @override
  public void onreleaserefreshstate(int scrolly, int deltay) {
    mtv.settext("松手刷新");

  }

  @override
  public void onrefreshingstate() {
    mtv.settext("正在刷新");
    miv.setscalex(1.0f);
    miv.setscaley(1.0f);
    mani.start();

  }

  @override
  public void ondefaultstate() {
    if (mani.isrunning()){
      mani.end();
      miv.setrotation(0);
    }
  }

}

源码

package com.example.apple.quickdemo.realview.view;

import android.content.context;
import android.content.res.typedarray;
import android.support.annotation.nullable;
import android.support.v7.widget.gridlayoutmanager;
import android.support.v7.widget.linearlayoutmanager;
import android.support.v7.widget.recyclerview;
import android.support.v7.widget.staggeredgridlayoutmanager;
import android.util.attributeset;
import android.util.log;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.velocitytracker;
import android.view.view;
import android.view.viewgroup;
import android.widget.linearlayout;
import android.widget.scroller;
import android.widget.toast;

import com.example.apple.quickdemo.r;
import com.example.apple.quickdemo.realview.show.implonpullshowviewlistener;

import static android.content.contentvalues.tag;

/**
 * created by apple on 2017/7/7.
 * 下拉刷新
 */

public class realpullrefreshview extends linearlayout {


  private int mtouchslop;
  //  分别记录上次滑动的坐标
  private int mlastx = 0;
  private int mlasty = 0;

  //  分别记录上次滑动的坐标(onintercepttouchenvent)
  private int mlastxintercept = 0;
  private int mlastyintercept = 0;

  private scroller mscroller;
  private velocitytracker mvelocitytracker;


  private recyclerview.adapter madapter;

  public recyclerview getrecyclerview() {
    return mrecyclerview;
  }

  private recyclerview mrecyclerview;


  private int default = 0;
  private final int pull_down_refresh = 1;
  private final int release_refresh = 2;
  private final int refreshing = 3;
  private final int load_more = 4;
  private int state = default;


  private int rfreshheaderwidth;
  private int refreshheadviewheight;


  private onpulllistener monpulllistener;
  private view mrefreshheaderview;

  private recyclerview.layoutmanager mlayoutmanager;


  int refreshheadviewid;

  public void setlayoutmanager(recyclerview.layoutmanager manager) {
    this.mlayoutmanager = manager;
    mrecyclerview.setlayoutmanager(mlayoutmanager);

  }

  public void setadapter(recyclerview.adapter adapter) {
    this.madapter = adapter;
    mrecyclerview.setadapter(madapter);

  }

  public view getrefreshheaderview() {
    return mrefreshheaderview;
  }

  public void setonpullshowviewlistener(onpullshowviewlistener onpullshowviewlistener) {
    monpullshowviewlistener = onpullshowviewlistener;
  }

  private onpullshowviewlistener monpullshowviewlistener;

  public void setonpulllistener(onpulllistener onpulllistener) {
    monpulllistener = onpulllistener;
  }

  public realpullrefreshview(context context) {
    super(context);

    initview(context);

  }

  public realpullrefreshview(context context, @nullable attributeset attrs) {
    super(context, attrs);

    initattrs(context, attrs);
//   ★★★★★一个坑initattrs方法里的typedarray去获取属性时,第一次获取的属性全是0,他会马上重走一次构造方法,再次获取一次,才能获得正确的值
//   如果第一次获取的值为0,则不去initview
    if (refreshheadviewid != 0) {
      initview(context);
    }


  }


  public realpullrefreshview(context context, @nullable attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);

    initattrs(context, attrs);
    if (refreshheadviewid != 0) {
      initview(context);
    }
  }

  private void initattrs(context context, attributeset attrs) {

    typedarray typedarray = context.obtainstyledattributes(attrs, r.styleable.realpullrefreshview);

    try {

      refreshheadviewid = typedarray.getresourceid(r.styleable.realpullrefreshview_refresh_header_view, 0);


    } finally {
      typedarray.recycle();

    }


  }


  private void initview(context context) {

    mscroller = new scroller(getcontext());
    mvelocitytracker = velocitytracker.obtain();


//    添加headerview

//    ★ ★ ★ ★ ★ 注意不要用这个方法inflate布局,会导致layout的所有属性失效,height、width、margin
//    原因见 http://blog.csdn.net/zhaokaiqiang1992/article/details/36006467
//    ★ ★ ★ ★ ★ mrefreshheaderview = minflater.inflate(r.layout.headerview, null);

    mrefreshheaderview = layoutinflater.from(context).inflate(refreshheadviewid, this, false);
    addview(mrefreshheaderview);
//    }

//    以下代码主要是为了设置头布局的margintop值为-headerviewheight
//    注意必须等到一小会才会得到正确的头布局宽高
    postdelayed(new runnable() {
      @override
      public void run() {
        log.e("q11", refreshheadviewheight + "qqqqqqqqqq   " + mrefreshheaderview.getheight());
        rfreshheaderwidth = mrefreshheaderview.getwidth();
        refreshheadviewheight = mrefreshheaderview.getheight();

        marginlayoutparams lp = new linearlayout.layoutparams(rfreshheaderwidth, refreshheadviewheight);
        lp.setmargins(0, -refreshheadviewheight, 0, 0);
        mrefreshheaderview.setlayoutparams(lp);

      }
    }, 100);


//   添加recyclerview
    mrecyclerview = new recyclerview(context);
    addview(mrecyclerview, layoutparams.match_parent, viewgroup.layoutparams.match_parent);


//   这里我提供了一个默认的显示效果,如果用户不使用mrealpullrefreshview.setonpullshowviewlistener的话,会默认使用这个
//   用户可以实现onpullshowviewlistener接口,去实现自己想要的显示效果
    monpullshowviewlistener = new implonpullshowviewlistener(this);


    setloadmore();


  }

  private void setloadmore() {
    // 当目前的可见条目是所有数据的最后一个时,开始加载新的数据

    mrecyclerview.addonscrolllistener(new recyclerview.onscrolllistener() {

      @override
      public void onscrolled(recyclerview recyclerview, int dx, int dy) {
        super.onscrolled(recyclerview, dx, dy);

        int lastcompletelyvisibleitemposition = -1;
        if (mlayoutmanager instanceof linearlayoutmanager) {
          linearlayoutmanager manager = (linearlayoutmanager) mlayoutmanager;
          lastcompletelyvisibleitemposition = manager.findlastvisibleitemposition();

        } else if (mlayoutmanager instanceof gridlayoutmanager) {
          gridlayoutmanager manager = (gridlayoutmanager) mlayoutmanager;
          lastcompletelyvisibleitemposition = manager.findlastvisibleitemposition();

        } else if (mlayoutmanager instanceof staggeredgridlayoutmanager) {
          staggeredgridlayoutmanager manager = (staggeredgridlayoutmanager) mlayoutmanager;
          lastcompletelyvisibleitemposition = manager.findlastvisibleitempositions(new int[manager.getspancount()])[0];
        }
        if (lastcompletelyvisibleitemposition + 1 == madapter.getitemcount()) {
          if (monpulllistener != null && state == default) {
            state = load_more;
            monpulllistener.onloadmore();
          }
        }


      }
    });
  }


  @override
  public boolean onintercepttouchevent(motionevent event) {
    boolean intercepted = false;
    int x = (int) event.getx();
    int y = (int) event.gety();

    switch (event.getaction()) {
      case motionevent.action_down: {
        intercepted = false;
        /*if (state!=default||state!=refreshing){
        if (!mscroller.isfinished()) {
          mscroller.abortanimation();
        }}*/
        break;
      }
      case motionevent.action_move: {
        int deltax = x - mlastxintercept;
        int deltay = y - mlastyintercept;

        int firstcompletelyvisibleitemposition = -1;
        if (mlayoutmanager instanceof linearlayoutmanager) {
          linearlayoutmanager manager = (linearlayoutmanager) mlayoutmanager;
          firstcompletelyvisibleitemposition = manager.findfirstcompletelyvisibleitemposition();

        } else if (mlayoutmanager instanceof gridlayoutmanager) {
          gridlayoutmanager manager = (gridlayoutmanager) mlayoutmanager;
          firstcompletelyvisibleitemposition = manager.findfirstcompletelyvisibleitemposition();

        } else if (mlayoutmanager instanceof staggeredgridlayoutmanager) {
          staggeredgridlayoutmanager manager = (staggeredgridlayoutmanager) mlayoutmanager;
          firstcompletelyvisibleitemposition = manager.findfirstcompletelyvisibleitempositions(new int[manager.getspancount()])[0];
        }


        //        ******************这里说明什么规则下,拦截,其余代码不要动了,其余代码指的是处理滑动冲突的代码***************


        if (firstcompletelyvisibleitemposition == 0 && deltay > 0 && math.abs(deltay) > math.abs(deltax)) {//拉倒最顶部,继续往下拉,将拉出头布局,要父布局拦截
          intercepted = true;
        } else if (getscrolly() < 0) {//表示头布局已经向下拉出来,头布局已经显示了,要父布局拦截
          intercepted = true;

        } else if (deltay < 0) {
          intercepted = false;//不要父布局拦截了

        } else {
          intercepted = false;//不要父布局拦截了
        }
        //        ******************什么规则下,拦截***************

        break;
      }
      case motionevent.action_up: {
        intercepted = false;
        break;
      }
      default:
        break;
    }

    log.d(tag, "intercepted=" + intercepted);
    mlastx = x;
    mlasty = y;
    mlastxintercept = x;
    mlastyintercept = y;

    return intercepted;
  }


  /**
   * 下面不同布局,不同的滑动需求
   *
   * @param event
   * @return
   */
  @override
  public boolean ontouchevent(motionevent event) {
    mvelocitytracker.addmovement(event);
    int x = (int) event.getx();
    int y = (int) event.gety();
    switch (event.getaction()) {
      case motionevent.action_down: {
        if (!mscroller.isfinished()) {
          mscroller.abortanimation();
        }
        break;
      }
      case motionevent.action_move: {
        int deltax = x - mlastx;
        int deltay = y - mlasty;
        if (getscrolly() > 0) {
          //防止在正在刷新状态下,上拉出空白
        } else if (getscrolly() <= 0 && getscrolly() > -refreshheadviewheight * 5) {
//          最多下拉到头布局高度5倍的距离
          scrollby(0, -deltay / 2);
        }

        if (getscrolly() > -refreshheadviewheight && state != refreshing) {//头布局显示不全时,为下拉刷新pull_down_refresh状态
          state = pull_down_refresh;

          if (monpullshowviewlistener != null) {
            monpullshowviewlistener.onpulldownrefreshstate(getscrolly(), refreshheadviewheight, deltay);
          }

        }
        if (getscrolly() < -refreshheadviewheight && state != refreshing) {//头布局完全显示时,为释放刷新release_refresh状态
          state = release_refresh;
          if (monpullshowviewlistener != null) {
            monpullshowviewlistener.onreleaserefreshstate(getscrolly(), deltay);
          }

        }

        break;
      }
      case motionevent.action_up: {
        final int scrolly = getscrolly();
        //松手时,根据所处的状态,让布局滑动到不同的地方,做不同的操作
        switch (state) {

          case pull_down_refresh:
            state = default;
            //头布局没有完全显示,完全隐藏头布局
            smoothscrollby(0, -scrolly);
            break;
          case release_refresh:
            state = refreshing;
            smoothscrollby(0, -refreshheadviewheight - scrolly);


            if (monpullshowviewlistener != null) {
              monpullshowviewlistener.onrefreshingstate();
            }


            if (monpulllistener != null) {
              monpulllistener.onrefresh();
            }
            break;
          case refreshing:
            if (getscrolly() < -refreshheadviewheight) {
              smoothscrollby(0, -refreshheadviewheight - scrolly);
            } else {
              smoothscrollby(0, -scrolly);
            }
            break;
        }

        mvelocitytracker.clear();
        break;
      }
      default:
        break;
    }

    mlastx = x;
    mlasty = y;
    return true;
  }

  /**
   * 当用户使用完下拉刷新回调时,需要调用此方法,将头不去隐藏,将state恢复
   */
  public void refreshfinish() {
    smoothscrollby(0, 0 - getscrolly());
    getrecyclerview().getadapter().notifydatasetchanged();
    state = default;
    if (monpullshowviewlistener != null) {
      monpullshowviewlistener.ondefaultstate();
    }

    toast.maketext(getcontext(), "刷新成功!", toast.length_short).show();
  }

  /**
   * 当用户使用完加载更多后回调时,需要调用此方法,将state恢复
   */
  public void loadmrefinish() {
    getrecyclerview().getadapter().notifydatasetchanged();
    state = default;

    toast.maketext(getcontext(), "加载成功了!", toast.length_short).show();
  }

  /**
   * 在500毫秒内平滑地滚动多少像素点
   *
   * @param dx
   * @param dy
   */
  private void smoothscrollby(int dx, int dy) {
    mscroller.startscroll(0, getscrolly(), 0, dy, 500);
    invalidate();
  }

  @override
  public void computescroll() {
    if (mscroller.computescrolloffset()) {
      scrollto(mscroller.getcurrx(), mscroller.getcurry());
      postinvalidate();
    }
  }

  /**
   * 释放资源
   */
  @override
  protected void ondetachedfromwindow() {
    mvelocitytracker.recycle();
    super.ondetachedfromwindow();
  }


//  ***************


//  *****************

  /**
   * 回调接口
   */
  public interface onpulllistener {
    /**
     * 当下拉刷新正在刷新时,这时候可以去请求数据,记得最后调用refreshfinish()复位
     */
    void onrefresh();

    /**
     * 当加载更多时
     */
    void onloadmore();
  }


  /**
   * 回调接口,可以通过下面的回调,自定义各种状态下的显示效果
   * 可以根据下拉距离scrolly设计动画效果
   */
  public interface onpullshowviewlistener {

    /**
     * 当处于下拉刷新时,头布局显示效果
     *
     * @param scrolly    下拉的距离
     * @param headviewheight 头布局高度
     * @param deltay     movey-lastmovey,正值为向下拉
     */
    void onpulldownrefreshstate(int scrolly, int headviewheight, int deltay);

    /**
     * 当处于松手刷新时,头布局显示效果
     *
     * @param scrolly 下拉的距离
     * @param deltay movey-lastmovey,正值为向下拉
     */
    void onreleaserefreshstate(int scrolly, int deltay);

    /**
     * 正在刷新时,页面的显示效果
     */
    void onrefreshingstate();

    /**
     * 默认状态时,页面显示效果,主要是为了复位各种状态
     */
    void ondefaultstate();


  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。