项目中需要下拉刷新的功能,但是这个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,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,0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f,0.0f,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 RefreshLayout实现下拉刷新布局所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)