内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

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

2019-01-23 20:39 出处:清屏网 人气: 评论(0

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

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 ,如果你有更好的方式解决这个问题或者有什么不明白的地方,都请告诉我,由衷感谢。

分享给小伙伴们:
本文标签: NestedScrollFlutter

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号