Android RefreshLayout实现下拉刷新布局
项目中需要下拉刷新的功能,但是这个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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Android RefreshLayout实现下拉刷新布局
-
Android 仿硅谷新闻下拉刷新/上拉加载更多
-
Android中RecyclerView布局代替GridView实现类似支付宝的界面
-
Android 利用ViewPager+GridView实现首页导航栏布局分页效果
-
Android下拉刷新以及GridView使用方法详解
-
Android属性动画实现布局的下拉展开效果
-
Android UI设计系列之自定义ListView仿QQ空间阻尼下拉刷新和渐变菜单栏效果(8)
-
Android开发中下拉刷新如何实现
-
Android程序开发之使用PullToRefresh实现下拉刷新和上拉加载
-
Android仿今日头条APP实现下拉导航选择菜单效果