AlipayPullRefresh

Introduction: 支付宝首页下拉刷新
More: Author   ReportBugs   
Tags:

支付宝首页下拉刷新

支付宝首页的下拉刷新效果不走寻常路,跟我们理解的很不一样。它在下拉刷新时,分成上下两段(topLayout & bottomLayout),loading 动画处于中间的位置。在拖拽的时候,就像是从中间撕开的样子。

真要细细琢磨起来,这样的下拉刷新效果,确实挺难搞的。在页面中的任何一处都能上下拖动,相当考验细心和耐心。

截图

效果图如下:

点击图片可查看截屏视频

原理说明

或许很多人都想到了 CoordinatorLayout,诚然,CoordinatorLayout 是距离这种下拉刷新效果最近的官方控件。但是,有一些体验上的问题,却是 CoordinatorLayout 也无能为力的。比如说:在 topLayout 按下触摸,向下拖动时,怎么把 loading 动画慢慢显示出来? 或者,topLayout 向上拖动,以较快的速度松手时,fling 效果如何传达到 bottomLayout?

或许你会说,我们自定义 CoordinatorLayout...

我不否认这可能是一种可行的方案,如果你对其源码足够了解,如果你对 Nesting 机制和 behavior 有足够的掌控力。

可是我也想问,如果这些如果都能成真的话,干嘛不来一次全新的旅程?

CoordinatorLayout 和 Nesting 机制告诉我们,一次 Touch 拖动事件,并不是一次性消费的,而是可以被多个 View 同时消费。如果你涉猎过足够多的系统源码,会知道 Nesting 机制的核心是 MotionEvent 有一个 bug 级的方法 offsetLocation。这是一个 public 方法,我们在处理 Touch 事件时一样可以调用。

然而无论如何,CoordinatorLayout 规规矩矩做事,本本分分做 View,跟我们理解的 Touch 事件分发机制是并不冲突的。

我曾经做过一个试验:

  1. FrameLayout 包含两个子 View,第一个子 View 是 ScrollView,放在底部;第二个子 View 是 TextView(MATCH_PARENAT)放在顶部,用作遮罩,背景透明;
  2. 我用手指滑动屏幕,ScrollView 可以正常滚动;
  3. 将 TextView 设置成可点击的,setClickable(true),用手指滑动屏幕时,ScrollView 无法正常滚动。
  4. 自定义 FrameLayout,onInterceptTouchEvent 和 onTouchEvent 在自行处理的同时,也转发给 scrollView,scrollView 可以正常滑动;

关键代码如下:

public class AlipayContainerLayout extends FrameLayout {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        scrollView.onInterceptTouchEvent(ev);
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scrollView.onTouchEvent(event);
        return true;
    }
}

你可能会说:这扰乱了 Touch 事件的分发流程,简直就是在胡搞!
是的,不得不承认这确实是耍流氓,是一种违法行为!呵呵,淡定,不要鸡冻!我能想起很多开源框架,用反射、用 Hook 的方式欺骗系统、绕过检查等等,跟它们比起来,我们只能算轻微的投机倒把而已(捂脸),不要方!

那么,支付宝的首页刷新是不是可以理解成这样:

  1. FrameLayout 包含两个子 View,第一个子 View 是 ScrollView,第二个子 View 是 topLayout;
  2. ScrollView 顶部留白,占位用;
  3. ScrollView 消费 Touch 事件;如果触摸滑动落在 topLayout,则 Touch 事件由 FrameLayout 转发给 ScrollView;如果触摸滑动落在 ScrollView 内部,则 ScrollView 调用自身的 Touch 消费即可;

感兴趣的同学可以先这么试试看。

可以透露的是,你一定会遇到林林总总的麻烦。不用怕,这些都是考验,走过九九八十一道坎,你会对系统底层的理解更进一步;而且,前文提及 offsetLocation 是个好东西,能用好这个彩蛋,绝对是一件值得开心的事情。

使用方法

  1. layout 布局文件
    <com.stone.alipay.library.AlipayContainerLayout
     android:id="@+id/home_container_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     alipay:progressColor="@color/statusBarColor"
     alipay:progressCenterOffset="3dp"
     alipay:progressHeight="@dimen/alipay_progress_height" />
    
  2. java 代码使用

     containerLayout = findViewById(R.id.home_container_layout)
     containerLayout.setDecorator(new AlipayContainerLayout.Decorator() {
         @Override
         public View getContentView() {
             // 内部滑动的 scrollView content
             View contentView = initContentView(inflater);
             return contentView;
         }
    
         @Override
         public View getTopLayout() {
             // 顶部悬浮的 topLayout
             topLinearLayout = (TopLinearLayout) initTopLayout(inflater);
             return topLinearLayout;
         }
     });
    
     // 2. 下拉刷新
     scrollView = containerLayout.getScrollView();
     scrollView.setOnRefreshListener(new AlipayScrollView.OnRefreshListener() {
         @Override
         public void onRefresh() {
             // 下拉刷新回调,请求网络数据
             requestNetwork();
         }
     });
    
     // 3. 顶部视差效果绑定
     scrollView.setScrollChangeListener(new AlipayScrollView.ScrollChangeListener() {
         @Override
         public void onScrollChange(int scrollY) {
             parallaxScroll(scrollY);
         }
     });
     topLinearLayout.bindParallax(scrollView, topBlueLayout);
    

demo APK 下载

点击下载

Apps
About Me
Google+: Trinea trinea
GitHub: Trinea