一种无痕过渡下拉刷新控件的实现思路

一种无痕过渡下拉刷新控件的实现思路

相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞!

1.市面一些下拉刷新控件普遍缺陷演示

以直播吧APP为例:

第1种情况:

滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动。

原因:
下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件即滑动控件,因此滑动控件不能滑动。

这里写图片描述

第2种情况:

滑动控件滑动到某个非0位置时,这时下拉回0位置时,可以看到下拉刷新头部没有被拉出来。

原因:
滑动控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被滑动控件消费掉了,父控件即下拉刷新控件消费不了滑动事件,因此下拉刷新头部没有被拉出来。

这里写图片描述

可能大部分人觉得无关痛痒,把手指抬起再下拉就可以了,but对于强迫症的我而言,能提供一个无痕过渡才是最符合操作逻辑的,因此接下来我来讲解下实现的思路。

2.实现的思路讲解

2.1.事件分发机制简介(来源于Android开发艺术探索)

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法的关系伪代码
public boolean dispatchTouchEvent(MotionEvent ev) { 
    boolean consume = false;
    if(onInterceptTouchEvent(ev)) { 
        consume = onTouchEvent(ev);
    } else { 
        consume = child.dispatchTouchEvent(ev); 
    }
    return consume; 
}

1.由代码可知若当前View拦截事件,就交给自己的onTouchEvent去处理,否则就丢给子View继续走相同的流程。
2.事件传递顺序:Activity -> Window -> View,如果View都不处理,最终将由Activity的onTouchEvent
处理,是一种责任链模式的实现。
3.正常情况,一个事件序列只能被一个View拦截且消耗。
4.某个View一旦决定拦截,这一个事件序列只能由它处理,并且它的onInterceptTouchEvent不会再被调用
5.不消耗ACTION_DOWN,则事件序列都会由其父元素处理。

2.2.一般下拉刷新的实现思路猜想

首先,下拉刷新控件作为一个容器,需要重写onInterceptTouchEvent和onTouchEvent这两个方法,然后在onInterceptTouchEvent中判断ACTION_DOWN事件,根据子控件的滑动距离做出判断,若还没滑动过,则onInterceptTouchEvent返回true表示其拦截事件,然后在onTouchEvent中进行下拉刷新的头部显示隐藏的逻辑处理;若子控件滑动过了,不拦截事件,onInterceptTouchEvent返回false,后续其下拉刷新的头部显示隐藏的逻辑处理就无法被调用了。

2.3.无痕过渡下拉刷新控件的实现思路

从2.2中可以看出,要想无痕过渡,下拉刷新控件不能拦截事件,这时候你可能会问,既然把事件给了子控件,后续拉刷新头部逻辑怎么实现呢?
这时候就要用到一般都忽略的事件分发方法dispatchTouchEvent了,此方法在ViewGroup默认返回true表示分发事件,即使子控件拦截了事件,父布局的dispatchTouchEvent仍然会被调用,因为事件是传递下来的,这个方法必定被调用。
所以我们可以在dispatchTouchEvent时对子控件的滑动距离做出判断,在这里把下拉刷新的头部的逻辑处理掉,同时在函数调用return super.dispatchTouchEvent(event) 前把event的action设置为ACTION_CANCEL,这样子子控件就不会响应滑动的操作。

3.代码实现

3.1.确定需求

  • 需要适配任意控件,例如RecyclerView、ListView、ViewPager、WebView以及普通的不能滑动的View
  • 不能影响子控件原来的事件逻辑
  • 暴露方法提供手动调用刷新功能
  • 可以设置禁止下拉刷新功能

3.2.代码讲解

需要的变量

public class RefreshLayout extends LinearLayout {
   
    // 隐藏的状态
    private static final int HIDE = 0;
    // 下拉刷新的状态
    private static final int PULL_TO_REFRESH = 1;
    // 松开刷新的状态
    private static final int RELEASE_TO_REFRESH = 2;
    // 正在刷新的状态
    private static final int REFRESHING = 3;
    // 正在隐藏的状态
    private static final int HIDING = 4;
    // 当前状态
    private int mCurrentState = HIDE;

    // 头部动画的默认时间(单位:毫秒)
    public static final int DEFAULT_DURATION = 200;

    // 头部高度
    private int mHeaderHeight;
    // 内容控件的滑动距离
    private int mContentViewOffset;
    // 最小滑动响应距离
    private int mScaledTouchSlop;
    // 记录上次的Y坐标
    private float mLastMotionY;
    // 记录一开始的Y坐标
    private float mInitDownY;
    // 响应的手指
    private 
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值