首页 > 系统 > Android > 正文

Android RefreshLayout实现下拉刷新布局

2019-12-12 04:55:13
字体:
来源:转载
供稿:网友

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

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表