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

Android RefreshLayout实现下拉刷新布局

程序员文章站 2024-03-04 13:40:11
项目中需要下拉刷新的功能,但是这个view不是listview这类的控件,需要viewgroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,...

项目中需要下拉刷新的功能,但是这个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也就完成了!

Android RefreshLayout实现下拉刷新布局

下面贴上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> 

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