频道栏目
首页 > 资讯 > 其他 > 正文

RefreshLayout 可下拉刷新的布局

16-10-29        来源:[db:作者]  
收藏   我要投稿

好久没写博客了,这几天为了一个功能搞的有点头大。项目中需要下拉刷新的功能,但是这个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);
//addView(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();
    }
}

最后是布局文件


    

        

            

            

                

                
            
        


        

        

    

相关TAG标签
上一篇:CollapsingToolbarLayout 介绍和简单使用
下一篇:AppBarLayout 介绍和简单实用
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站