下拉刷新

这篇文章适合看了众多讲解下拉刷新、视图测量与绘制、事件分发仍然模糊不清的同学,android下拉刷新控件不知从何时起已经成为项目标配,所以熟悉下拉刷新控件变得尤为重要,本文将从下拉刷新控件入手,顺便学习下自定义控件和事件分发机制。
我们可以点进当下可拓展性最高、最流行的下拉刷新项目:android-Ultra-Pull-To-Refresh,发现里面有个核心类PtrFrameLayout,乍一看1368行,不管你晕不晕,反正我是晕的。好了,我们来个简易版的(Ps:引用NsRefreshLayout项目的代码来讲解,并且都只是伪代码或部分代码):

一、定义些可拓展的属性

像这样写在values/attrs.xml里(ps:其实你写在strings.xml里都没问题,xml的名字也可以自己取,只需要保证根标签是declare-styleaqle即可,而且这样分开写更方便查找):

<declare-styleable name="PtrFrameLayout">
     <attr name="ptr_pull_to_fresh" format="boolean" />
</declare-styleable>
二、获取到这些属性

确保在每三个构造函数里都可以拿到这些属性

public PtrFrameLayout(Context context) {
        this(context, null);
    }

    public PtrFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout, 0, 0);
        mPullToRefresh = arr.getBoolean(R.styleable.PtrFrameLayout_ptr_pull_to_fresh, mPullToRefresh);
        arr.recycle();
    }
三、给布局添加头布局或底布局

在方法onFinishInflate()里以addView的形式添加自定义的头布局或者底布局,并使用第二部接收的值来填充属性(比如颜色,字体什么的)。

四、重写事件分发

目前主要有2种方式来重写事件分发。

  1. 重写onInterceptTouchEvent和onTouchEvent的方式
  • 重写onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
      case MotionEvent.ACTION_MOVE: {
               //判断是下拉刷新还是上拉加载更多
            if (disY > 0 && !canChildScrollUp() && mPullRefreshEnable) {
                        mCurrentAction = ACTION_PULL_DOWN_REFRESH;
                        return true;
            } else {
               return super.onInterceptTouchEvent(ev);
            }
}

对touch事件为MOVE类型的进行判断处理,如果满足拦截条件,进行拦截并返回true,如不满足条件或是类型不是MOVE的其他touch事件,执行super.onInterceptTouchEvent(ev),代表不拦截,由系统帮我们向下传递,遇到需要消费该事件的content(比如listview滑动)消费掉就完事了

  • 重写onTouchEvent

public boolean onTouchEvent(MotionEvent event){
case MotionEvent.ACTION_MOVE: {
handleScroll(dy);//处理头试图和内容视图的下滑
return true;//return super 或者false都没事
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
return releaseTouch();//处理释放操作,这里的返回值同上
}
}
“`
原著在move那里返回的是true,其实无论返回什么都是可以的,因为已经重写了onInterceptTouchEvent,当遇到满足下拉刷新的情况自然会走自己的OnTouchEvent(),如果没有不满足条件,由于你是parentView,而且没有拦截事件,所以子布局优先消费,也跟你无关了,哈哈

  1. 重写dispatchTouchEvent的方式
    android-Ultra-Pull-To-Refresh就是用的这种方式,当满足刷新条件时return true表示自己处理了,如果没满足条件,执行super.dispatchTouchEventSupper(e)继续分发给孩子,个人觉得这种方式干预了系统的分发事件,毕竟孩子的所有触摸事件都是通过这个方法得来的,说没就没了,不像onInterceptTouchEvent拦截了还会丢给孩子一个cancel事件,所以这种系统级的事情就要我们自己去写了,不过瘾。。。
五、布局位移

上个部分有个伪代码handleScroll(),是用来位移孩子的方法,下面统计下位移视图的几种方式:

  1. layout()
  2. bringToFront()(需配合requestLayout使用)
  3. LayoutParams
  4. padding(设为负值就隐藏了)
  5. transactionY,transactionX
  6. offsetLeftAndRight() offsetTopAndBottom()
  7. Scroller
  8. scrollTo() scrollBy()
    以上8种其实可以分为2类,1234需要requestLayout(),所以当频繁调用时会卡,5678只是影响drawable部分,频繁调用也很顺滑。

总结

如果你想通过看完上面的内容,然后自己写一个完美的下拉刷新控件,我想还是需要很长时间的,毕竟里面还有很多的小细节没有涉及到,我只是帮大家把轮子分解得简单点,对轮子有一个大体的认知,小细节的地方不明白也不影响大局观嘛,毕竟写代码还是要有一个上帝视角,谁也不能一口吃个大胖子。

相关链接

    原文作者:太阳晒得我丶好干瘪
    原文地址: https://www.jianshu.com/p/a4f0ffb38a50
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞