OverscrollNotification不起效果引起的Flutter感悟分享

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

问题

先说下问题与解决思路,以及解决方案。
当我们想要监听一个widget的滑动状态时,可以使用:NotificationListener
在我目前空余时间写的一个flutter项目中,有一个十分复杂的组件,需要用到这东西。
要实现下面这个功能。

Feb-25-2019 00-37-43.gif

这个UI由哪些功能点

  • 当listview的第一个条目显示出来的时候,此时继续下拉,整个listview下移
  • 当listview处于最底部时,向上拖拽时,整个listview上移
  • 当手离开屏幕时,如果listview的最高高度处于屏幕高度二分之一以上,整个listview自动滚动到最顶部
  • 当手离开屏幕时,如果listview的最高高度处于屏幕高度二分之一以下,整个listview自动滚动到最底部

这篇博客呢,讲的就是关于功能点一的。当listview的第一个条目显示出来的时候,此时继续下拉。我要处理这个情况的UI。

由这个问题,引发的解决问题的思路,以及关于学习新姿势的一些思考与感悟。

PS: 为了达到完美的效果,这个需求,我搞了一周~~

NotificationListener的使用


final GlobalKey _key = GlobalKey();
  @override
  Widget build(BuildContext context) { 
    final Widget child = NotificationListener<ScrollStartNotification>(
      key: _key,
      child: NotificationListener<ScrollUpdateNotification>(
        child: NotificationListener<OverscrollNotification>(
          child: NotificationListener<ScrollEndNotification>(
            child: widget.child,
            onNotification: (ScrollEndNotification notification) { 
              return false;
            },
          ),
          onNotification: (OverscrollNotification notification) { 
            return false;
          },
        ),
        onNotification: (ScrollUpdateNotification notification) {
          return false;
        },
      ),
      onNotification: (ScrollStartNotification scrollUpdateNotification) { 
        return false;
      },
    );

    return child;
  }

其中,

  • ScrollStartNotification 组件开始滑动
  • ScrollUpdateNotification 组件位置发生改变
  • OverscrollNotification 表示窗口小组件未更改它的滚动位置,因为更改会导致滚动位置超出其滚动范围
  • ScrollEndNotification 组件已经停止滚动

Demo


body: SafeArea(
            child: NotificationListener<ScrollStartNotification>(
          child: NotificationListener<OverscrollNotification>(
            child: ListView.builder(
                itemBuilder: (BuildContext context, int index) {
                  return Text('data=$index');
                },
                itemCount: 100),
            onNotification: (OverscrollNotification notification) {
              print('OverscrollNotification');
            },
          ),
          onNotification: (ScrollStartNotification notification) {
            print('ScrollStartNotification');
          },
        ))

在Android中效果

在Android中效果

可以看到刚开始下拉的时候,回调的是ScrollStartNotificationonNotification方法,之后都是OverscrollNotification。

在ios中效果

在ios中效果

可以看到OverscrollNotification不会被调用,调用的是ScrollStartNotification

在我的一些复杂UI效果中,需要在OverscrollNotification回调中做一些事情。
当ScrollView滚动到顶部时,继续下拉时。在Android平台中,OverscrollNotification会被调用;在iOS平台的真机中,OverscrollNotification不会被调用,调用的是ScrollStartNotification。这就造成了平台的不一致性。我也尝试了Google一下,但是…我看到这个问题的时候,问题还没解决。后来我就解决了,然后给了他回答。这个后面再说。

屏幕快照 2019-02-24 下午9.23.50.png

问题OverscrollNotification在Android中正常调用;在iOS的真机中,无法调用。

定位原因

分析NotificationListener的onNotification调用栈。

  • Step 1 翻源码

ListView是继承自ScrollView的。我们跟着ScrollView的build方法,一步步向上级查询,可以看到scroll_activity.dart的下面几个跟OverscrollNotification相关的方法:

scroll_activity

这就明了多了。

  • Step 2 翻源码

继续上溯,进入到scroll_position.dart,看到OverscrollNotification被实际调用的方法:

scroll_postion.png

  • Step 3OverscrollNotification能否被调用的判断位置
OverscrollNotification能否被调用
  • Step 4 分析applyBoundaryConditions方法

@protected
  double applyBoundaryConditions(double value) {
    final double result = physics.applyBoundaryConditions(this, value);//这里physics来控制返回值
    assert(() {
      final double delta = value - pixels;
      if (result.abs() > delta.abs()) {
        throw FlutterError(
          '${physics.runtimeType}.applyBoundaryConditions returned invalid overscroll value.\n'
          'The method was called to consider a change from $pixels to $value, which is a '
          'delta of ${delta.toStringAsFixed(1)} units. However, it returned an overscroll of '
          '${result.toStringAsFixed(1)} units, which has a greater magnitude than the delta. '
          'The applyBoundaryConditions method is only supposed to reduce the possible range '
          'of movement, not increase it.\n'
          'The scroll extents are $minScrollExtent .. $maxScrollExtent, and the '
          'viewport dimension is $viewportDimension.'
        );
      }
      return true;
    }());
    return result;
  }

physicsScrollPhysics的实例。
在进入到physics.applyBoundaryConditions(this, value);applyBoundaryConditions方法中


 ///
  /// [BouncingScrollPhysics] returns zero. In other words, it allows scrolling
  /// past the boundary unhindered.
  ///
  /// [ClampingScrollPhysics] returns the amount by which the value is beyond
  /// the position or the boundary, whichever is furthest from the content. In
  /// other words, it disallows scrolling past the boundary, but allows
  /// scrolling back from being overscrolled, if for some reason the position
  /// ends up overscrolled.
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    if (parent == null)
      return 0.0;
    return parent.applyBoundaryConditions(position, value);
  }

注释中,写着BouncingScrollPhysics的滑动不受阻碍,可以一直滑动。也就是在iOS平台的ScrollView中,可以一直下拉。也就是,我上面的demo效果。对于ClampingScrollPhysics无法继续下拉。

  • step 5 parent的具体实现
    继续debug源码。

  • physics在Android中的实现

physics

physics.applyBoundaryConditions在Android中由 ClampingScrollPhysics 完成

ClampingScrollPhysics.applyBoundaryConditions


@override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    assert(() {
      if (value == position.pixels) {
        throw FlutterError(
          '$runtimeType.applyBoundaryConditions() was called redundantly.\n'
          'The proposed new position, $value, is exactly equal to the current position of the '
          'given ${position.runtimeType}, ${position.pixels}.\n'
          'The applyBoundaryConditions method should only be called when the value is '
          'going to actually change the pixels, otherwise it is redundant.\n'
          'The physics object in question was:\n'
          '  $this\n'
          'The position object in question was:\n'
          '  $position\n'
        );
      }
      return true;
    }());
    if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
      return value - position.pixels;
    if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
      return value - position.pixels;
    if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
      return value - position.minScrollExtent;
    if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
      return value - position.maxScrollExtent;
    return 0.0;
  }

  • physics在iOS中的具体实现
physics在iOS中的具体实现

physics.applyBoundaryConditions在iOS中由 BouncingScrollPhysics 完成

BouncingScrollPhysics.applyBoundaryConditions

  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) => 0.0;

在Android平台中,会对applyBoundaryConditions的返回值做处理,不为零的时候(看下step3),是会调用OverscrollNotification.onNotification;但是对于iOS平台,由于默认一直返回0.0,故不会调用。

原来如此

由于我这里需要的是Android的效果,所以需要将physics的具体实现更改为ClampingScrollPhysics即可,正好,

ScrollView构造

我们将physics的实现变更为ClampingScrollPhysics,完美解决。

屏幕快照 2019-02-24 下午11.51.38.png

拓展思维

如果,我们将physics的实现变更为BouncingScrollPhysics,会发生什么?

Feb-24-2019 23-55-02.gif

完美的在Android上实现了,同iOS一样的可以一直下拉的listview效果。

彩蛋

思考为什么两个平台physics的具体实现不同

这个原因,也就是相当于physics什么时候被初始化的。我就不娓娓道来了,我这边翻阅并且debug源码找到了出处。在scroll_configuration.dart文件中,有下面一段代码:


  /// The scroll physics to use for the platform given by [getPlatform].
  ///
  /// Defaults to [BouncingScrollPhysics] on iOS and [ClampingScrollPhysics] on
  /// Android.
  ScrollPhysics getScrollPhysics(BuildContext context) {
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
        return const BouncingScrollPhysics();
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return const ClampingScrollPhysics();
    }
    return null;
  }

可以看到,不同的平台,返回的值是不用的。返回的结果,也验证了我们刚才debug的结果。小惊喜:,看TargetPlatform.fuchsia,看来fuchsia系统即将到来。

Flutter要统一天下啊~

屏幕快照 2019-02-25 上午12.01.56.png

共勉

学习一门新系统知识,一定要知其然并知其所以然。如果,我直接设置physics的值,不会学习到实质性的知识。明白了原理才能掌控全局。之前看一些Android大神的博客,很多东西,都是翻阅源码debug而来的。况且当下Flutter的相关有深度有见地的资料不多的情况下,我也是被逼的,没办法。只有翻阅源码了。翻过了源码,却获得了意外之喜,收获了更多知识。
最后一句话,与君共勉:勤而学之,柳暗花明又一村

PS:最终实现的开头效果的源码与思路,里面涉及到手势识别、类似Android的事件分发、动画、滑动监听以及解刨源码等等。估计要写很多字~~有时间再来一篇博客。
flutter issues已经提交了相关建议。

博客地址

Flutter 豆瓣客户端,诚心开源
Flutter Container
Flutter SafeArea
Flutter Row Column MainAxisAlignment Expanded
Flutter Image全解析
Flutter 常用按钮总结
Flutter ListView豆瓣电影排行榜
Flutter Card
Flutter Navigator&Router(导航与路由)
OverscrollNotification不起效果引起的Flutter感悟分享
Flutter 上拉抽屉实现
Flutter 豆瓣客户端,诚心开源
Flutter 更改状态栏颜色

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,595评论 4 59
  • 前两天看到后海深圳湾片区的各种幕墙,集中的这个片区有不少的玻璃幕墙建筑。其中深圳湾一号的幕墙比其他建筑的要明亮,色...
    goldenwhale阅读 135评论 0 0
  • 开店前期,你须把原材料备好分类号。 至少半个月到一个月的量。 用量开始的时候不可能算得很准。 原则:宁多勿少! 注...
    芒果战鳄鱼阅读 2,372评论 0 4
  • 我有一个朋友,十七岁师范毕业便从鹰潭跟着现在的丈夫来到我们这个小城安家立业。两人一起分在城关小学,每天双...
    佛说随喜阅读 565评论 1 4
  • 今天很想写写我的爸爸,爸爸去世已经一年多了,但他始终活在我的心里。每想起他,我都会想到曾经所拥有的浓浓的父爱...
    治愈家庭教育阅读 283评论 2 1