Flutter扩展NestedScrollView(一)固定头引起的bug解决

这一篇的篇幅估计很多,请先买好瓜子汽水前排坐好,开车了..

NestedScrollView是一个复杂的组件,它跟Sliver系列是一伙的,最下层是个CustomScrollView。

银色系列的东东很多,我们下面来一一介绍一下。

1.CustomScrollView

是银组件的老祖宗,全部的银都放在这个里面。

2.SliverList ,它是一个显示儿童线性列表的条子。

3.SliverFixedExtentList ,它是一个更有效的条子,显示沿着滚动轴具有相同范围的子项的线性列表。比SliverList多一个就是相同的行高。这样性能会更好

4.SliverPrototypeExtentList SliverPrototypeExtentList将其子项排列在沿着主轴的一条线上,从零偏移开始,没有间隙。每个子项的约束程度与沿主轴的prototypeItem和沿横轴的SliverConstraints.crossAxisExtent的程度相同。

5.SliverGrid ,它是一个显示2D儿童阵列的条子。可以设置每行的个数的网格

6.SliverPadding ,这是一条在另一条棉条周围增加空白的条子。

7.SliverPersistentHeader 条子滚动到视口前缘时尺寸不同的条子。这是SliverAppBar用于缩小/增长效果的布局基元。

非常好用的组件,SliverAppBar就是用这个实现的。这个组件的特点是可以创建出随着滑动变化的可以已固定的元素,大家经常用的什么吸顶组件可以用这个很方便的构建,后面我会使用这个写一个自定义效果的SliverAppbar。

8.SliverAppBar ,它是一个显示标题的条子,可以在滚动视图滚动时展开和浮动。

9.SliverToBoxAdapter 当你想把一个非银的控件放在CustomScrollview里面的时候,你需要用这个包裹一下。

10.SliverSafeArea 通过足够的填充来插入另一条条子以防止操作系统入侵的条子。例如,这将使条子缩进足以避开屏幕顶部的状态栏。为了防止各种边界的越界,比如说越过顶部的状态栏

11.SliverFillRemaining调整 其子项的大小以在十字轴中填充视口并填充主轴中视口中的剩余空间。使用这个它会填充完剩余视里面的全部空间

12.SliverOverlapAbsorber ** SliverOverlapAbsorberHandle**这个上面2个是官方专门为了解决我们今天主角** NestedScrollView**中固定组件对身体里面Scroll状态影响的,但官方做的不够完美。

看源码是一件好玩的事情,大家跟我一起来吧。

flutterpackagesflutterlibsrcwidgetsnested_scroll_view.dart

首先我们看看第一个问题,从官方文档中的样品可以看到NestedScrollView

DefaultTabController(
  length:_tabs.length,//这是制表符的数量。
  child:NestedScrollView(
    headerSliv​​erBuilder :( BuildContext context,bool innerBoxIsScrolled){
      //这些是在“外部”滚动视图中显示的条子。
      return <Widget> [
        SliverOverlapAbsorber(
          //此小部件采用SliverAppBar的重叠行为,
          //并将其重定向到下面的SliverOverlapInjector。如果是
          //缺少,那么嵌套的“内部”滚动视图是可能的
          //下面最终在SliverAppBar下面甚至在内部
          //滚动视图认为它尚未滚动。
          //如果只构建“headerSliv​​erBuilder”,则不需要这样做
          //与下一个条子不重叠的小部件。
          handle:NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          孩子:SliverAppBar(
            title:const Text('Books'),//这是应用栏中的标题。
            固定的:真的,
            expandedHeight:150.0,
            //“forceElevated”属性导致SliverAppBar显示
            // 一个影子。“innerBoxIsScrolled”参数为true时为true
            //内部滚动视图滚动超出其“零”点,即
            //当它似乎在SliverAppBar下方滚动时。
            //如果没有这个,就会出现阴影出现的情况
            //或者不恰当地出现,因为SliverAppBar是
            //实际上并没有意识到内在的准确位置
            //滚动视图。
            forceElevated:innerBoxIsScrolled,
            底部:TabBar(
              //这些是放在标签栏中每个标签中的小部件。
              tabs:_tabs.map((String name)=> Tab(text:name))。toList(),
            )
          )
        )
      ]。
    },
    body:TabBarView(
      //这些是选项卡视图下方的选项卡视图的内容。
      children:_tabs.map((String name){
        返回SafeArea(
          顶部:假,
          bottom:false,
          孩子:建造者(
            //需要此Builder来提供“内部”的BuildContext
            // NestedScrollView,以便sliverOverlapAbsorberHandleFor()可以
            //找到NestedScrollView。
            builder:(BuildContext context){
              返回CustomScrollView(
                //应该留下“控制器”和“主要”成员
                //取消设置,以便NestedScrollView可以控制它
                //内部滚动视图。
                //如果设置了“controller”属性,则滚动
                //视图不会与NestedScrollView相关联。
                // PageStorageKey对于此ScrollView应该是唯一的;
                //它允许列表记住它的滚动位置
                //标签视图不在屏幕上。
                key:PageStorageKey <String>(name),
                条:<Widget> [
                  SliverOverlapInjector(
                    //这是上面SliverOverlapAbsorber的另一面。
                    handle:NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  )
                  SliverPadding(
                    padding:const EdgeInsets.all(8.0),
                    //在此示例中,内部滚动视图具有
                    //固定高度列表项,因此使用
                    // SliverFixedExtentList。但是,人们可以使用任何一个
                    //这里的sliver小部件,例如SliverList或SliverGrid。
                    条子:SliverFixedExtentList(
                      //此示例中的项目固定为48像素
                      //高 这符合Material Design规范
                      // ListTile小部件
                      itemExtent:48.0,
                      代表:SliverChildBuilderDelegate(
                        (BuildContext context,int index){
                          //为每个孩子调用此构建器。
                          //在这个例子中,我们只为每个列表项编号。
                          return ListTile(
                            title:Text('Item $ index'),
                          );
                        },
                        // SliverChildBuilderDelegate的childCount
                        //指定此内部列表的子项数
                        // 具有。在此示例中,每个选项卡都有一个列表
                        //正好30项,但这是任意的。
                        childCount:30,
                      )
                    )
                  )
                ]
              );
            },
          )
        );
      })。toList(),
    )
  )
)
复制代码

可以看到官方用一个SliverOverlapAbsorber包裹了SliverAppbar,在下面身体里面,每一个列表的上面都加了个SliverOverlapInjector。实际效果就是SliverOverlapInjector的高度就等于SliverAppbar的Pinned的高度。如果不加入这些代码,当body里面的列表滚动到SliverAppbar下方的时候..依然可以继续向上滚动,也就是说身体的滚动最上面点为0,而不是SliverAppbar的固​​定高度。

为什么会出现这种情况呢?这要从Sliver的老祖宗CustomScrollView说起来。可能很多人发现,这些Sliver小部件(可以滚动的那种)没有ScrollController这个东西(CustomScrollview和NestedScrollView除外)。其实当你把Sliver Widgets(可以滚动的那种)放到CustomScrollView里面的时候将由CustomScrollView来统一处理各种Sliver Widgets(可以滚动的那种),每个Sliver Widgets(可以滚动的那种)都会附加各自的ScrollPosition。比如说第一个列表滚动到头了,第2个列表就会开始处理对应的的scrollPosition,将出现在检视区里面的元素渲染出来。

在我们的主角NestedScrollView当中,有2个ScrollController。

class _NestedScrollController扩展ScrollController {
  _NestedScrollController(
      this.coordinator,{
        double initialScrollOffset = 0.0,
        字符串debugLabel,
复制代码

一个是内部,一个外部。外部负责headerSliv​​erBuilder里面的滚动小部件内部是负责身体里面​​的滚动小部件当外滚动到底了之后,就会看看内里面是否有能滚动的东东,开始滚动。

为了解决1问题,我们这里需要来处理外这个ScrollController里面控制的_NestedScrollPosition,问题1在于,当头部里面有多个钉扎的插件的时候,我们外能滚动的程度。应该要去减掉这个固定的总的高度。这样当滚动到固定的组件下方的时候。我们就会开始滚动内。

在_NestedScrollPosition里面

// _NestedScrollPosition由a的内部和外部视口使用
// NestedScrollView。它跟踪用于那些视口的偏移量,并且知道
//关于_NestedScrollCoordinator,以便在触发活动时
//这个班级,他们可以推迟或受到协调员的影响。
class _NestedScrollPosition扩展了ScrollPosition
    实现ScrollActivityDelegate {
  _NestedScrollPosition({
    @required ScrollPhysics物理,
    @required ScrollContext上下文,
    double initialPixels = 0.0,
    ScrollPosition oldPosition,
    字符串debugLabel,
    @required this.coordinator,
  }):super(
复制代码

我重写了applyContentDimensions方法

@override
  bool applyContentDimensions(double minScrollExtent,double maxScrollExtent){
    if(debugLabel =='outer'&&
        coordinator.pinnedHeaderSliv​​erHeightBuilder!= null){
      maxScrollExtent =
          maxScrollExtent  -  coordinator.pinnedHeaderSliv​​erHeightBuilder();
      maxScrollExtent = math.max(0.0,maxScrollExtent);
    }
    return super.applyContentDimensions(minScrollExtent,maxScrollExtent);
  }
复制代码

pinnedHeaderSliv​​erHeightBuilder是我从最外层传递进来的用于获取当时Pinned为真的全部Sliver header的高度..在这里把外部最大的滚动范围减去了Pinned的总的高度,这样我们就完美解决了问题。 1

示例代码

在我的demo里面.pinned的高度由status bar + appbar + 1个或者2个tabbar组成。这里为什么要用个功能而不是直接传递个算好的高度呢?因为在我的案例里面这个pinned的高度是会改变的。

var tabBarHeight = primaryTabBar.preferredSize.height;
    var pinnedHeaderHeight =
        // statusBa height
        statusBarHeight +
            //在标题中固定SliverAppBar高度
            kToolbarHeight +
            //在标题中固定标签栏高度
            (primaryTC.index == 0?tabBarHeight * 2:tabBarHeight);
    返回NestedScrollViewRefreshIndicator(
      onRefresh:onRefresh,
      child:extended.NestedScrollView(
        headerSliv​​erBuilder:(c,f){
          return _buildSliverHeader(primaryTabBar);
        },
        //
        pinnedHeaderSliv​​erHeightBuilder :(){
          return pinnedHeaderHeight;
        },
复制代码

最后放上Github extended_nested_scroll_view,如果你有更好的方式解决这个问题或者有什么不明白的地方,都请告诉我,由衷感谢。

过两天我把flutter大纲给大家弄出来

想学习更多Android知识,或者获取相关资料请加入Android技术开发交流2群:935654177。本群可免费获取Gradle,RxJava,小程序,Hybrid,移动架构,NDK,React Native,性能优化等技术教程!

《Flutter扩展NestedScrollView(一)固定头引起的bug解决》 image

    原文作者:Android征途
    原文地址: https://www.jianshu.com/p/604766eea93c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞