乐趣区

关于flutter:Flutter-NestedScrollView-内嵌视图滚动行为一致

Flutter NestedScrollView 内嵌视图滚动行为统一

视频

https://www.bilibili.com/video/BV1Gh4y1571p/

前言

上一节讲了 CustomScrollView,能够发现有的中央滚动并不是很连贯。

这时候就须要 NestedScrollView 来解决了。

明天会写一个如下图的例子来实现滚动统一。

原文 https://ducafecat.com/blog/flutter-sliver-nested-scroll-view

参考

https://api.flutter.dev/flutter/widgets/NestedScrollView-clas…

https://api.flutter.dev/flutter/widgets/SliverOverlapAbsorber…

https://api.flutter.dev/flutter/widgets/SliverOverlapInjector…

知识点 NestedScrollView

NestedScrollView 是 Flutter 中的一个 Widget,它能够嵌套多个滚动视图,例如 ListViewGridViewSliverAppBar 等。NestedScrollView 能够让多个滚动视图联动滚动,从而实现一些简单的交互成果。

常见的业务场景:

  • 一个页面上有多个可滚动的区域,而且这些区域之间的滚动是互相独立的,然而它们的滚动行为须要协调一致,比方一个列表和一个悬浮的顶部栏。
  • 实现相似于网易云音乐个人主页的成果,即在滚动过程中,一个悬浮的头部会被逐步放大,同时顶部的导航栏会突变隐没,直到最初整个头部齐全占据整个屏幕。
  • 在列表中嵌套一个可滚动的子列表,例如在一个电商利用中,展现一个大分类下的多个小分类,每个小分类上面又有多个商品。

NestedScrollViewCustomScrollView 都是反对自定义滚动视图的 Widget。它们的区别在于,CustomScrollView 能够通过增加多个 Sliver 来实现简单的滚动视图成果,而 NestedScrollView 则是将多个滚动视图嵌套在一起,并提供了一些不便的接口来协调它们之间的滚动。因而,NestedScrollView 的应用场景更加适宜于多个可滚动区域之间须要协调滚动的状况。

步骤

NestedScrollView 分为头部和内容两个局部,咱们别离来实现。

第一步:实现 NestedScrollView 头部

lib/nested.dart

编写头部组件函数,创立页面 NestedScrollPage

class NestedScrollPage extends StatefulWidget {const NestedScrollPage({super.key});

  @override
  State<NestedScrollPage> createState() => _NestedScrollPageState();
}

class _NestedScrollPageState extends State<NestedScrollPage> {final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"];

筹备 _tabs 数据

build 函数

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DefaultTabController(
        length: _tabs.length,
        child: NestedScrollView(headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[_buildHeader(context, innerBoxIsScrolled),
            ];
          },
          body: _buildTabBarView(),),
      ),
    );
  }

headerSliverBuilder 头部实现函数

  // 头部
  Widget _buildHeader(BuildContext context, bool innerBoxIsScrolled) {
    return // SliverOverlapAbsorber 的作用是解决重叠滚动成果,// 避免 CustomScrollView 中的滚动视图与其余视图重叠。SliverOverlapAbsorber(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
      sliver:
          // SliverAppBar 的作用是创立可折叠的顶部应用程序栏,// 它能够随着滚动而滑动或固定在屏幕顶部,并且能够与其余 Sliver 小部件一起应用。SliverAppBar(title: const Text('滚动一致性'),
        pinned: true,
        elevation: 6, // 影深
        expandedHeight: 300.0,
        forceElevated: innerBoxIsScrolled, // 为 true 时开展有暗影
        flexibleSpace: FlexibleSpaceBar(
          background: Image.asset(
            "assets/images/banner-bg.jpg",
            fit: BoxFit.cover,
          ),
        ),

        // 底部固定栏
        bottom: MyCustomAppBar(
          child: Column(
            children: [
              Container(
                color: Colors.greenAccent,
                child: const Center(child: Text('固定高度内容')),
              ),
              TabBar(
                tabs: _tabs
                    .map((String name) => Tab(text: name,))
                    .toList(),),
            ],
          ),
        ),
      ),
    );
  }

SliverOverlapAbsorber 与 SliverOverlapInjector,作用是避免 CustomScrollView 中的滚动视图与其余视图重叠。

编写 MyCustomAppBar 悬停 Bar

lib/app_bar.dart

import 'package:flutter/material.dart';

class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget {
  final Widget child;

  const MyCustomAppBar({super.key, required this.child});

  @override
  Widget build(BuildContext context) {return child;}

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight + 20.0);
}

第二步:实现 NestedScrollView 内容

lib/nested.dart

TabBarView 混入各种状况:横向滚动、固定高度、SliverList 列表

  Widget _buildTabBarView() {
    return TabBarView(children: _tabs.map((String name) {
        return SafeArea(
          top: false,
          bottom: false,
          child: Builder(builder: (BuildContext context) {
              return CustomScrollView(key: PageStorageKey<String>(name),
                slivers: <Widget>[
                  // SliverOverlapInjector 的作用是解决重叠滚动成果,// 确保 CustomScrollView 中的滚动视图不会与其余视图重叠。SliverOverlapInjector(
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  ),

                  // 横向滚动
                  SliverToBoxAdapter(
                    child: SizedBox(
                      height: 100,
                      child: PageView(
                        children: [
                          Container(
                            color: Colors.yellow,
                            child: const Center(child: Text('横向滚动')),
                          ),
                          Container(color: Colors.green),
                          Container(color: Colors.blue),
                        ],
                      ),
                    ),
                  ),

                  // 固定高度内容
                  SliverToBoxAdapter(
                    child: Container(
                      height: 100,
                      color: Colors.greenAccent,
                      child: const Center(child: Text('固定高度内容')),
                    ),
                  ),

                  // 列表
                  buildContent(name),

                  // 固定高度内容
                  SliverToBoxAdapter(
                    child: Container(
                      height: 100,
                      color: Colors.greenAccent,
                      child: const Center(child: Text('固定高度内容')),
                    ),
                  ),

                  // 列表 100 行
                  SliverList(
                    delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item $index'));
                      },
                      childCount: 100,
                    ),
                  ),
                ],
              );
            },
          ),
        );
      }).toList(),);
  }

SliverOverlapInjector 的作用是解决重叠滚动成果,

确保 CustomScrollView 中的滚动视图不会与其余视图重叠。

内容列表

  // 内容列表
  Widget buildContent(String name) => SliverPadding(padding: const EdgeInsets.all(8.0),
        sliver: SliverFixedExtentList(
          itemExtent: 48.0,
          delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
              return ListTile(title: Text('$name - $index'),
              );
            },
            childCount: 50,
          ),
        ),
      );

启动

lib/main.dart

import 'package:flutter/material.dart';

import 'nested.dart';

void main() {runApp(const MyApp());
}

class MyApp extends StatelessWidget {const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      // home: const MyPageView(),
      home: const NestedScrollPage(),);
  }
}

间接设置 home 进入 NestedScrollPage 界面

最初残缺代码:

lib/app_bar.dart

import 'package:flutter/material.dart';

class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget {
  final Widget child;

  const MyCustomAppBar({super.key, required this.child});

  @override
  Widget build(BuildContext context) {return child;}

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight + 20.0);
}

lib/nested.dart

import 'package:flutter/material.dart';

import 'app_bar.dart';

class NestedScrollPage extends StatefulWidget {const NestedScrollPage({super.key});

  @override
  State<NestedScrollPage> createState() => _NestedScrollPageState();
}

class _NestedScrollPageState extends State<NestedScrollPage> {final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DefaultTabController(
        length: _tabs.length,
        child: NestedScrollView(headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[_buildHeader(context, innerBoxIsScrolled),
            ];
          },
          body: _buildTabBarView(),),
      ),
    );
  }

  // 头部
  Widget _buildHeader(BuildContext context, bool innerBoxIsScrolled) {
    return // SliverOverlapAbsorber 的作用是解决重叠滚动成果,// 避免 CustomScrollView 中的滚动视图与其余视图重叠。SliverOverlapAbsorber(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
      sliver:
          // SliverAppBar 的作用是创立可折叠的顶部应用程序栏,// 它能够随着滚动而滑动或固定在屏幕顶部,并且能够与其余 Sliver 小部件一起应用。SliverAppBar(title: const Text('滚动一致性'),
        pinned: true,
        elevation: 6, // 影深
        expandedHeight: 300.0,
        forceElevated: innerBoxIsScrolled, // 为 true 时开展有暗影
        flexibleSpace: FlexibleSpaceBar(
          background: Image.asset(
            "assets/images/banner-bg.jpg",
            fit: BoxFit.cover,
          ),
        ),

        // 底部固定栏
        bottom: MyCustomAppBar(
          child: Column(
            children: [
              Container(
                color: Colors.greenAccent,
                child: const Center(child: Text('固定高度内容')),
              ),
              TabBar(
                tabs: _tabs
                    .map((String name) => Tab(text: name,))
                    .toList(),),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTabBarView() {
    return TabBarView(children: _tabs.map((String name) {
        return SafeArea(
          top: false,
          bottom: false,
          child: Builder(builder: (BuildContext context) {
              return CustomScrollView(key: PageStorageKey<String>(name),
                slivers: <Widget>[
                  // SliverOverlapInjector 的作用是解决重叠滚动成果,// 确保 CustomScrollView 中的滚动视图不会与其余视图重叠。SliverOverlapInjector(
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  ),

                  // 横向滚动
                  SliverToBoxAdapter(
                    child: SizedBox(
                      height: 100,
                      child: PageView(
                        children: [
                          Container(
                            color: Colors.yellow,
                            child: const Center(child: Text('横向滚动')),
                          ),
                          Container(color: Colors.green),
                          Container(color: Colors.blue),
                        ],
                      ),
                    ),
                  ),

                  // 固定高度内容
                  SliverToBoxAdapter(
                    child: Container(
                      height: 100,
                      color: Colors.greenAccent,
                      child: const Center(child: Text('固定高度内容')),
                    ),
                  ),

                  // 列表
                  buildContent(name),

                  // 固定高度内容
                  SliverToBoxAdapter(
                    child: Container(
                      height: 100,
                      color: Colors.greenAccent,
                      child: const Center(child: Text('固定高度内容')),
                    ),
                  ),

                  // 列表 100 行
                  SliverList(
                    delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item $index'));
                      },
                      childCount: 100,
                    ),
                  ),
                ],
              );
            },
          ),
        );
      }).toList(),);
  }

  Widget buildContent(String name) => SliverPadding(padding: const EdgeInsets.all(8.0),
        sliver: SliverFixedExtentList(
          itemExtent: 48.0,
          delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
              return ListTile(title: Text('$name - $index'),
              );
            },
            childCount: 50,
          ),
        ),
      );
}

代码

https://github.com/ducafecat/flutter_develop_tips/blob/main/flutter_application_sliver_scroll/lib/nested.dart

小结

应用 NestedScrollView 是一个十分弱小和灵便的 widget,能够实现许多常见的滚动视图布局,例如带有悬浮题目的列表视图,或者带有可开展 / 折叠局部的折叠面板。

感激浏览本文

如果我有什么错?请在评论中让我晓得。我很乐意改良。


© 猫哥
ducafecat.com

end

退出移动版