Flutter嵌套滚动视图插件extended_nested_scroll_view的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter嵌套滚动视图插件extended_nested_scroll_view的使用

extended_nested_scroll_view

pub package GitHub stars GitHub forks GitHub license GitHub issues

NestedScrollView 是Flutter中用于实现复杂滚动效果的重要组件。但是它存在一些问题,如固定标题栏的高度问题、TabView内部可滚动组件同步问题等。extended_nested_scroll_view 插件扩展了 NestedScrollView,解决了这些问题,并且提供了更多的功能。

主要解决的问题

  1. pinned sliver header issue:修复了固定标题栏高度的问题。
  2. inner scrollables in tabview sync issue:修复了TabView内部可滚动组件同步的问题。
  3. do without ScrollController in NestedScrollView’s body:在 NestedScrollView 的主体部分不使用 ScrollController

Web demo for ExtendedNestedScrollView

示例代码

Example for issue 1

给定固定标题栏的总高度,在 pinnedHeaderSliverHeightBuilder 回调中返回该高度。

var tabBarHeight = primaryTabBar.preferredSize.height;
var pinnedHeaderHeight =
    // 状态栏高度
    statusBarHeight +
        // 固定 SliverAppBar 高度
        kToolbarHeight;

ExtendedNestedScrollView(
  pinnedHeaderSliverHeightBuilder: () {
    return pinnedHeaderHeight;
  },
);

Example for issue 2

我们通常通过以下方式保持列表的滚动位置:

scene onlyOneScrollInBody description
AutomaticKeepAliveClientMixin true 滚动位置不会被销毁,设置 onlyOneScrollInBody 为 true,以便我们知道哪个列表是激活的。
PageStorageKey false 滚动位置会被销毁,PageStorageKey 只记录位置信息,ExtendedNestedScrollView 中的滚动位置始终是单一的。
ExtendedNestedScrollView(
  onlyOneScrollInBody: true,
)

ExtendedVisibilityDetector

提供 ExtendedVisibilityDetector 来指出哪个列表是可见的。

ExtendedVisibilityDetector(
  uniqueKey: const Key('Tab1'),
  child: ListView(),
)

Do without ScrollController in NestedScrollView’s body

由于我们不能为 NestedScrollView 的主体部分设置 ScrollController(这会破坏 InnerScrollControllerNestedScrollView 中的行为),因此提供了以下示例来展示如何在不使用 ScrollController 的情况下实现某些功能:

完整示例Demo

下面是一个完整的示例代码,展示了如何使用 extended_nested_scroll_view 插件:

import 'package:flutter/material.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'extended_nested_scroll_view demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  final List<String> _tabs = ['Tab 1', 'Tab 2'];
  TabController? _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: _tabs.length, vsync: this);
  }

  @override
  void dispose() {
    _tabController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('extended_nested_scroll_view Demo'),
        bottom: TabBar(
          controller: _tabController,
          tabs: _tabs.map((String name) => Tab(text: name)).toList(),
        ),
      ),
      body: ExtendedNestedScrollView(
        pinnedHeaderSliverHeightBuilder: () {
          return kToolbarHeight + MediaQuery.of(context).padding.top;
        },
        onlyOneScrollInBody: true,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            SliverOverlapAbsorber(
              handle: ExtendedNestedScrollView.sliverOverlapAbsorberHandleFor(context),
              sliver: SliverAppBar(
                title: Text('Pinned Header'),
                floating: false,
                pinned: true,
                expandedHeight: 200.0,
                flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                    'https://via.placeholder.com/350x150',
                    fit: BoxFit.cover,
                  ),
                ),
              ),
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: _tabs.map((String name) {
            return Builder(
              builder: (BuildContext context) {
                return ExtendedVisibilityDetector(
                  uniqueKey: Key(name),
                  child: ListView.builder(
                    itemCount: 50,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(title: Text('$name Item $index'));
                    },
                  ),
                );
              },
            );
          }).toList(),
        ),
      ),
    );
  }
}

这个示例展示了如何使用 extended_nested_scroll_view 插件创建一个带有固定标题栏和多个标签页的嵌套滚动视图。每个标签页包含一个可滚动的列表,并且可以通过 ExtendedVisibilityDetector 来检测当前可见的列表。


更多关于Flutter嵌套滚动视图插件extended_nested_scroll_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter嵌套滚动视图插件extended_nested_scroll_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用 extended_nested_scroll_view 插件在 Flutter 中实现嵌套滚动视图的代码示例。这个插件允许你在一个滚动视图中嵌套另一个滚动视图,这在实现复杂的 UI 布局时非常有用。

首先,确保你已经在 pubspec.yaml 文件中添加了 extended_nested_scroll_view 依赖:

dependencies:
  flutter:
    sdk: flutter
  extended_nested_scroll_view: ^x.y.z  # 请替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来是一个完整的 Flutter 应用示例,展示了如何使用 ExtendedNestedScrollView

import 'package:flutter/material.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: NestedScrollViewDemo(),
    );
  }
}

class NestedScrollViewDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Extended Nested Scroll View Demo'),
      ),
      body: ExtendedNestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              title: Text('Header'),
              pinned: true,
              expandedHeight: 200.0,
              flexibleSpace: FlexibleSpaceBar(
                background: Image.network(
                  'https://via.placeholder.com/800x200',
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ];
        },
        body: Column(
          children: <Widget>[
            // 第一个滚动区域
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: List.generate(
                  20,
                  (index) => Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Container(
                      width: 100,
                      height: 100,
                      color: Colors.primary.withOpacity(0.5),
                      child: Center(child: Text('Item $index')),
                    ),
                  ),
                ),
              ),
            ),
            // 第二个滚动区域(垂直滚动)
            Expanded(
              child: SingleChildScrollView(
                child: Column(
                  children: List.generate(
                    50,
                    (index) => Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Container(
                        height: 50,
                        color: Colors.accent.withOpacity(0.5),
                        child: Center(child: Text('Item $index')),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

解释

  1. ExtendedNestedScrollView:这是插件提供的主要组件,它允许嵌套滚动视图。
  2. headerSliverBuilder:这是一个回调函数,用于构建头部(通常是一个 SliverAppBar)。
  3. SingleChildScrollView:用于在水平或垂直方向上创建滚动区域。
  4. SliverAppBar:一个可以固定在顶部的 AppBar,并可以在滚动时扩展或收缩。

在这个示例中,我们创建了一个包含头部(带有图片的背景)的嵌套滚动视图,头部下面是两个滚动区域:

  • 第一个区域是水平滚动的,包含 20 个水平排列的项。
  • 第二个区域是垂直滚动的,包含 50 个垂直排列的项。

这个示例展示了如何使用 extended_nested_scroll_view 插件在 Flutter 中实现复杂的嵌套滚动视图布局。你可以根据需要调整这个示例来适应你的应用。

回到顶部