当前位置: 移动技术网 > IT编程>移动开发>Android > Android RefreshLayout实现下拉刷新布局

Android RefreshLayout实现下拉刷新布局

2019年07月24日  | 移动技术网IT编程  | 我要评论

淘男网推广,闫治民,久佰年

项目中需要下拉刷新的功能,但是这个view不是listview这类的控件,需要viewgroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。

  于是翻出xlistview的源码看是一点一点看,再大致理解了xlisview源码,终于决定自己动手啦

  为了省事,headview还是用了xlistview的headview,省了很多事:)

  下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends scrollview 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:

1、scrollview下只能有一个子控件view ,虽然在    scroll下添加一个viewgroup,然后讲headview动态添加进前面的viewgroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!

2、  scrollview内嵌listview时会发生    冲突,还需要去重写listview。于是放弃换个思路!

 关于上面的原因1:动态添加headview进scrollview的中groupview中,可以在重写scrollview的onviewadded()方法,将初始化时解析的headview添加进子groupview

@override 
public void onviewadded(view child) { 
  super.onviewadded(child); 
  //因为headview要在最上面,最先想到的就是vertical的linearlayout 
  linearlayout linearlayout = (linearlayout) getchildat(0); 
  linearlayout.addview(view, 0); 
} 

  换个思路,通过extends linearlayout来实现吧!
先做准备工作,我们需要一个headerview以及要获取到headerview的高度,还有初始时layout的高度

private void initview(context context) { 
   mheaderview = new srefreshheader(context); 
   mheaderviewcontent = (relativelayout) mheaderview.findviewbyid(r.id.slistview_header_content); 
   setorientation(vertical); 
   addview(mheaderview, 0); 
   getheaderviewheight(); 
   getviewheight(); 
 } 

mheaderview = new srefreshheader(context);
通过构造方法实例化headerview

mheaderviewcontent = (relativelayout)

mheaderview.findviewbyid(r.id.slistview_header_content);

 这是解析headerview内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mheaderview来获取高度,点进构造方法里可以看到如下代码

// 初始情况,设置下拉刷新view高度为0 
layoutparams lp = new layoutparams(layoutparams.match_parent, 0); 
mcontainer = (linearlayout) layoutinflater.from(context).inflate(r.layout.listview_head_view_layout, null); 
w(mcontainer, lp); 

如果直接获取mheaderview的高度 那肯定是0
getheaderviewheight();
getviewheight();

分别是获取headerview的高度和layout的初始高度

/** 
  * 获取headview高度 
  */ 
  private void getheaderviewheight() { 
    viewtreeobserver vto2 = mheaderviewcontent.getviewtreeobserver(); 
    vto2.addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { 
      @override 
      public void ongloballayout() { 
        mheaderviewheight = mheaderviewcontent.getheight(); 
        mheaderviewcontent.getviewtreeobserver().removeglobalonlayoutlistener(this); 
      } 
    }); 
  } 
 
  /** 
  * 获取srefreshlayout当前实例的高度 
  */ 
  private void getviewheight() { 
    viewtreeobserver thisview = getviewtreeobserver(); 
    thisview.addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { 
      @override 
      public void ongloballayout() { 
        srefreshlayout.this.mheight = srefreshlayout.this.getheight(); 
        srefreshlayout.this.getviewtreeobserver().removeglobalonlayoutlistener(this); 
      } 
    }); 
  } 

准备工作完成了,接下来就是要成下拉操作了
到这里,肯定一下就想到了ontouchevent()方法,是的!现在就开始在这里施工

实现下拉一共 会经历三个过程
action_up→action_move→action_up
在action_up事件中,也就是手指按下的时候,我们需要做的只是记录按下时候的坐标

switch (ev.getaction()) { 
      case motionevent.action_down: 
        //记录起始高度 
        mlasty = ev.getrawy();//记录按下时的y坐标 
        break; 

然后就是action_move事件了,这里是最重要的,因为下拉时headview和layout的高度变化都在这里进行

case motionevent.action_move: 
       if (!isrefreashing) 
         isrefreashing = true; 
       final float deltay = ev.getrawy() - mlasty; 
       mlasty = ev.getrawy(); 
       updateheaderviewheight(deltay / 1.8f);//按一定比例缩小移动距离 
       updateheight(); 
       break; 

里面的updateheaderviewheight和updateheight分别是改变headerview的高度和layout的高度

 private void updateheight() { 
    viewgroup.layoutparams lp = getlayoutparams(); 
    //更新当前layout实例高度为headerview高度加上最初的layout高度 
    //如果不更新layout 会造成内容高度压缩 无法保持比例 
    lp.height = (mheight + mheaderview.getvisiableheight()); 
    setlayoutparams(lp); 
  } 
 
  private void updateheaderviewheight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factheight = (int) (space - mheaderviewheight); 
    if (mheaderview.getstatus() != srefreshheader.state_refreshing) { 
      //如果不处于刷新中同时如果高度 
      if (mheaderview.getvisiableheight() < mheaderviewheight * 2 && mheaderview.getstatus() != srefreshheader.state_normal) { 
        mheaderview.setstate(srefreshheader.state_normal); 
      } 
      if (mheaderview.getvisiableheight() > mheaderviewheight * 2 && mheaderview.getstatus() != srefreshheader.state_ready) { 
        mheaderview.setstate(srefreshheader.state_ready); 
      } 
    } 
    mheaderview.setvisiableheight((int) space 
        + mheaderview.getvisiableheight()); 
  } 

更新header高度时,通过下拉的距离来判断是否到达刷新的距离,上面代码中我设定的是到达mheaderview初始高度的两倍,就进入“释放刷新”的状态,如果没有达到则保持“下拉刷新”的状态
headerview中的状态一共设定了3个分别是

public final static int state_normal = 0;//下拉刷新 
 public final static int state_ready = 1;//释放刷新 
 public final static int state_refreshing = 2;//刷新中 

更新高度的方法headerview和layout都是相同的,就是原高度加上移动的距离重新赋给headerview或者layout

mheaderview.setvisiableheight((int) space 
               + mheaderview.getvisiableheight()); 

最后就是action_up事件了就是手指离开屏幕的时候,在这里我们需要根据headerview目前状态来决定headerview的最终状态!

case motionevent.action_up: 
        //松开时 
        //避免点击事件触发 
        if (!isrefreashing) 
          break; 
        //如果headview状态处于ready状态 则说明松开时应该进入refreshing状态 
        if (mheaderview.getstatus() == srefreshheader.state_ready) { 
          mheaderview.setstate(srefreshheader.state_refreshing); 
        } 
        //根据状态重置srefreshlayout当前实例和headview高度 
        resetheadview(mheaderview.getstatus()); 
        reset(mheaderview.getstatus()); 
        mlasty = -1;//重置坐标 
        break; 

resetheadview和reset分别是重置headerview高度和layout高度的方法

private void reset(int status) { 
    viewgroup.layoutparams lp = getlayoutparams(); 
    switch (status) { 
      case srefreshheader.state_refreshing: 
        lp.height = mheight + mheaderviewheight; 
        break; 
      case srefreshheader.state_normal: 
        lp.height = mheight; 
        break; 
    } 
    setlayoutparams(lp); 
  } 
 
  private void resetheadview(int status) { 
    switch (status) { 
      case srefreshheader.state_refreshing: 
        mheaderview.setvisiableheight(mheaderviewheight); 
        break; 
      case srefreshheader.state_normal: 
        mheaderview.setvisiableheight(0); 
        break; 
    } 
  } 

实现方式也是一样的。根据状态来判断,如果是处于刷新中,那headerview应该正常显示,并且高度是初始的高度,如果处于normal,也就是"下拉刷新"状态,那么说未触发刷新,重置时,headerview应该被隐藏,也就是高度重置为0

到这里下拉刷新操作也基本完成了,还需要加一个回调接口进行通知

interface onrefreshlistener { 
    void onrefresh(); 
  } 

case motionevent.action_up: 
        //松开时 
        //避免点击事件触发 
        if (!isrefreashing) 
          break; 
        //如果headview状态处于ready状态 则说明松开时应该进入refreshing状态 
        if (mheaderview.getstatus() == srefreshheader.state_ready) { 
          mheaderview.setstate(srefreshheader.state_refreshing); 
          if (monrefreshlistener != null) 
            monrefreshlistener.onrefresh(); 
        } 
        //根据状态重置srefreshlayout当前实例和headview高度 
        resetheadview(mheaderview.getstatus()); 
        reset(mheaderview.getstatus()); 
        mlasty = -1;//重置坐标 
        break; 

好,到这里就基本完成了,试试效果吧。咦,发现一个问题,嵌套listview的时候为什么这个layout不能执行下拉刷新!仔细想想应该是事件分发的问题,还需要处理一下事件的拦截!
关于事件拦截的处理,阅读了鸿洋大神写的viewgroup事件分发的博客和android-ultra-pull-to-refresh的部分源码,从中找到了解决办法:

@override 
  public boolean onintercepttouchevent(motionevent ev) { 
    abslistview abslistview = null; 
    for (int n = 0; n < getchildcount(); n++) { 
      if (getchildat(n) instanceof abslistview) { 
        abslistview = (listview) getchildat(n); 
        logs.v("查找到listview"); 
      } 
    } 
    if (abslistview == null) 
      return super.onintercepttouchevent(ev); 
    switch (ev.getaction()) { 
      case motionevent.action_down: 
        mstarty = ev.getrawy(); 
        break; 
      case motionevent.action_move: 
        float space = ev.getrawy() - mstarty; 
        logs.v("space:" + space); 
        if (space > 0 && !abslistview.canscrollvertically(-1) && abslistview.getfirstvisibleposition() == 0) { 
          logs.v("拦截成功"); 
          return true; 
        } else { 
          logs.v("不拦截"); 
          return false; 
        } 
    } 
    return super.onintercepttouchevent(ev); 
  } 

其中

if (space > 0 && !abslistview.canscrollvertically(-1) && abslistview.getfirstvisibleposition() == 0)
space即移动的距离 canscrollvertically()是判断listview能否在垂直方向上滚动,参数为负数时代表向上,为正数时代码向下滚动,最后一个就是listview第一个可见的item的postion

加上上面的事件拦截处理,一个可以满足开头提到的需求的viewgroup也就完成了!

下面贴上layout的源码和headerview(直接使用的xlistview的headerview)的源码

public class srefreshlayout extends linearlayout { 
  private srefreshheader mheaderview; 
  private relativelayout mheaderviewcontent; 
  private boolean isrefreashing; 
  private float mlasty = -1;//按下的起始高度 
  private int mheaderviewheight;//headerview内容高度 
  private int mheight;//布局高度 
  private float mstarty; 
 
  interface onrefreshlistener { 
    void onrefresh(); 
  } 
 
  public onrefreshlistener monrefreshlistener; 
 
  public srefreshlayout(context context) { 
    super(context); 
    initview(context); 
  } 
 
  public srefreshlayout(context context, attributeset attrs) { 
    super(context, attrs); 
    initview(context); 
  } 
 
  public srefreshlayout(context context, attributeset attrs, int defstyleattr) { 
    super(context, attrs, defstyleattr); 
    initview(context); 
  } 
 
  private void initview(context context) { 
    mheaderview = new srefreshheader(context); 
    mheaderviewcontent = (relativelayout) mheaderview.findviewbyid(r.id.slistview_header_content); 
    setorientation(vertical); 
    addview(mheaderview, 0); 
    getheaderviewheight(); 
    getviewheight(); 
  } 
 
  /** 
   * 获取headview高度 
   */ 
  private void getheaderviewheight() { 
    viewtreeobserver vto2 = mheaderviewcontent.getviewtreeobserver(); 
    vto2.addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { 
      @override 
      public void ongloballayout() { 
        mheaderviewheight = mheaderviewcontent.getheight(); 
        mheaderviewcontent.getviewtreeobserver().removeglobalonlayoutlistener(this); 
      } 
    }); 
  } 
 
  /** 
   * 获取srefreshlayout当前实例的高度 
   */ 
  private void getviewheight() { 
    viewtreeobserver thisview = getviewtreeobserver(); 
    thisview.addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { 
      @override 
      public void ongloballayout() { 
        srefreshlayout.this.mheight = srefreshlayout.this.getheight(); 
        srefreshlayout.this.getviewtreeobserver().removeglobalonlayoutlistener(this); 
      } 
    }); 
  } 
 
  @override 
  public boolean onintercepttouchevent(motionevent ev) { 
    abslistview abslistview = null; 
    for (int n = 0; n < getchildcount(); n++) { 
      if (getchildat(n) instanceof abslistview) { 
        abslistview = (listview) getchildat(n); 
        logs.v("查找到listview"); 
      } 
    } 
    if (abslistview == null) 
      return super.onintercepttouchevent(ev); 
    switch (ev.getaction()) { 
      case motionevent.action_down: 
        mstarty = ev.getrawy(); 
        break; 
      case motionevent.action_move: 
        float space = ev.getrawy() - mstarty; 
        logs.v("space:" + space); 
        if (space > 0 && !abslistview.canscrollvertically(-1) && abslistview.getfirstvisibleposition() == 0) { 
          logs.v("拦截成功"); 
          return true; 
        } else { 
          logs.v("不拦截"); 
          return false; 
        } 
    } 
    return super.onintercepttouchevent(ev); 
  } 
 
  @override 
  public boolean ontouchevent(motionevent ev) { 
    if (mlasty == -1) 
      mlasty = ev.getrawy(); 
    switch (ev.getaction()) { 
      case motionevent.action_down: 
        //记录起始高度 
        mlasty = ev.getrawy();//记录按下时的y坐标 
        break; 
      //手指离开屏幕时 
      case motionevent.action_up: 
        //松开时 
        //避免点击事件触发 
        if (!isrefreashing) 
          break; 
        //如果headview状态处于ready状态 则说明松开时应该进入refreshing状态 
        if (mheaderview.getstatus() == srefreshheader.state_ready) { 
          mheaderview.setstate(srefreshheader.state_refreshing); 
          if (monrefreshlistener != null) 
            monrefreshlistener.onrefresh(); 
        } 
        //根据状态重置srefreshlayout当前实例和headview高度 
        resetheadview(mheaderview.getstatus()); 
        reset(mheaderview.getstatus()); 
        mlasty = -1;//重置坐标 
        break; 
      case motionevent.action_move: 
        if (!isrefreashing) 
          isrefreashing = true; 
        final float deltay = ev.getrawy() - mlasty; 
        mlasty = ev.getrawy(); 
        updateheaderviewheight(deltay / 1.8f);//按一定比例缩小移动距离 
        updateheight(); 
        break; 
    } 
    return super.ontouchevent(ev); 
  } 
 
 
  private void reset(int status) { 
    viewgroup.layoutparams lp = getlayoutparams(); 
    switch (status) { 
      case srefreshheader.state_refreshing: 
        lp.height = mheight + mheaderviewheight; 
        break; 
      case srefreshheader.state_normal: 
        lp.height = mheight; 
        break; 
    } 
    setlayoutparams(lp); 
  } 
 
  private void resetheadview(int status) { 
    switch (status) { 
      case srefreshheader.state_refreshing: 
        mheaderview.setvisiableheight(mheaderviewheight); 
        break; 
      case srefreshheader.state_normal: 
        mheaderview.setvisiableheight(0); 
        break; 
    } 
  } 
 
  private void updateheight() { 
    viewgroup.layoutparams lp = getlayoutparams(); 
    //更新当前layout实例高度为headerview高度加上最初的layout高度 
    //如果不更新layout 会造成内容高度压缩 无法保持比例 
    lp.height = (mheight + mheaderview.getvisiableheight()); 
    setlayoutparams(lp); 
  } 
 
  private void updateheaderviewheight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factheight = (int) (space - mheaderviewheight); 
    if (mheaderview.getstatus() != srefreshheader.state_refreshing) { 
      //如果不处于刷新中同时如果高度 
      if (mheaderview.getvisiableheight() < mheaderviewheight * 2 && mheaderview.getstatus() != srefreshheader.state_normal) { 
        mheaderview.setstate(srefreshheader.state_normal); 
      } 
      if (mheaderview.getvisiableheight() > mheaderviewheight * 2 && mheaderview.getstatus() != srefreshheader.state_ready) { 
        mheaderview.setstate(srefreshheader.state_ready); 
      } 
    } 
    mheaderview.setvisiableheight((int) space 
        + mheaderview.getvisiableheight()); 
  } 
 
 
  public void stoprefresh() { 
    if (mheaderview.getstatus() == srefreshheader.state_refreshing) { 
      mheaderview.setstate(srefreshheader.state_normal); 
      resetheadview(srefreshheader.state_normal); 
      reset(srefreshheader.state_normal); 
    } 
  } 
 
  public void setonrefreshlistener(onrefreshlistener onrefreshlistener) { 
    this.monrefreshlistener = onrefreshlistener; 
  } 
} 

public class srefreshheader extends linearlayout { 
  private linearlayout mcontainer; 
  private int mstate = state_normal; 
 
  private animation mrotateupanim; 
  private animation mrotatedownanim; 
 
  private final int rotate_anim_duration = 500; 
 
  public final static int state_normal = 0;//下拉刷新 
  public final static int state_ready = 1;//释放刷新 
  public final static int state_refreshing = 2;//刷新中 
  private imageview mheadarrowimage; 
  private textview mheadlastrefreashtimetxt; 
  private textview mheadhinttxt; 
  private textview mheadlastrefreashtxt; 
  private progressbar mrefreshingprogress; 
 
  public srefreshheader(context context) { 
    super(context); 
    initview(context); 
  } 
 
  /** 
   * @param context 
   * @param attrs 
   */ 
  public srefreshheader(context context, attributeset attrs) { 
    super(context, attrs); 
    initview(context); 
  } 
 
  private void initview(context context) { 
    // 初始情况,设置下拉刷新view高度为0 
    layoutparams lp = new layoutparams(layoutparams.match_parent, 0); 
    mcontainer = (linearlayout) layoutinflater.from(context).inflate(r.layout.listview_head_view_layout, null); 
    addview(mcontainer, lp); 
    setgravity(gravity.bottom); 
 
    mheadarrowimage = (imageview) findviewbyid(r.id.slistview_header_arrow); 
    mheadlastrefreashtimetxt = (textview) findviewbyid(r.id.slistview_header_time); 
    mheadhinttxt = (textview) findviewbyid(r.id.slistview_header_hint_text); 
    mheadlastrefreashtxt = (textview) findviewbyid(r.id.slistview_header_last_refreash_txt); 
    mrefreshingprogress = (progressbar) findviewbyid(r.id.slistview_header_progressbar); 
 
    mrotateupanim = new rotateanimation(0.0f, -180.0f, 
        animation.relative_to_self, 0.5f, animation.relative_to_self, 
        0.5f); 
    mrotateupanim.setduration(rotate_anim_duration); 
    mrotateupanim.setfillafter(true); 
    mrotatedownanim = new rotateanimation(-180.0f, 0.0f, 
        animation.relative_to_self, 0.5f, animation.relative_to_self, 
        0.5f); 
    mrotatedownanim.setduration(rotate_anim_duration); 
    mrotatedownanim.setfillafter(true); 
  } 
 
  public void setstate(int state) { 
    if (state == mstate) return; 
 
    if (state == state_refreshing) {  // 显示进度 
      mheadarrowimage.clearanimation(); 
      mheadarrowimage.setvisibility(view.invisible); 
      mrefreshingprogress.setvisibility(view.visible); 
    } else {  // 显示箭头图片 
      mheadarrowimage.setvisibility(view.visible); 
      mrefreshingprogress.setvisibility(view.invisible); 
    } 
    switch (state) { 
      case state_normal: 
        if (mstate == state_ready) { 
          mheadarrowimage.startanimation(mrotatedownanim); 
        } 
        if (mstate == state_refreshing) { 
          mheadarrowimage.clearanimation(); 
        } 
        mheadhinttxt.settext("下拉刷新"); 
        break; 
      case state_ready: 
        if (mstate != state_ready) { 
          mheadarrowimage.clearanimation(); 
          mheadarrowimage.startanimation(mrotateupanim); 
          mheadhinttxt.settext("松开刷新"); 
        } 
        break; 
      case state_refreshing: 
        mheadhinttxt.settext("正在刷新"); 
        break; 
      default: 
    } 
 
    mstate = state; 
  } 
 
  public void setvisiableheight(int height) { 
    if (height < 0) 
      height = 0; 
    layoutparams lp = (layoutparams) mcontainer 
        .getlayoutparams(); 
    lp.height = height; 
    mcontainer.setlayoutparams(lp); 
  } 
 
 
  public int getstatus() { 
    return mstate; 
  } 
 
  public int getvisiableheight() { 
    return mcontainer.getheight(); 
  } 
} 

最后是布局文件

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:gravity="bottom"> 
 
  <relativelayout 
    android:id="@+id/slistview_header_content" 
    android:layout_width="match_parent" 
    android:layout_height="60dp"> 
 
    <linearlayout 
      android:id="@+id/slistview_header_text" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_centerinparent="true" 
      android:gravity="center" 
      android:orientation="vertical"> 
 
      <textview 
        android:id="@+id/slistview_header_hint_text" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="下拉刷新" /> 
 
      <linearlayout 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_margintop="3dp"> 
 
        <textview 
          android:id="@+id/slistview_header_last_refreash_txt" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:text="上次刷新时间" 
          android:textsize="12sp" /> 
 
        <textview 
          android:id="@+id/slistview_header_time" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:textsize="12sp" /> 
      </linearlayout> 
    </linearlayout> 
 
 
    <progressbar 
      android:id="@+id/slistview_header_progressbar" 
      android:layout_width="30dp" 
      android:layout_height="30dp" 
      android:layout_centervertical="true" 
      android:layout_toleftof="@id/slistview_header_text" 
      android:visibility="invisible" /> 
 
    <imageview 
      android:id="@+id/slistview_header_arrow" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_alignleft="@id/slistview_header_progressbar" 
      android:layout_centervertical="true" 
      android:layout_toleftof="@id/slistview_header_text" 
      android:src="@drawable/mmtlistview_arrow" /> 
 
  </relativelayout> 
 
</linearlayout> 

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网