Flutter(九)之Flutter滚动Widget(二)

二、常用的滚动Widget

1、ListView

在Flutter中,ListView 是一种非常常见且强大的组件,用于展示一系列连续的数据项。无论你是想创建一个简单的消息列表,还是一个复杂的商品展示页面,ListView 都能帮你轻松实现。
ListView 是最常用的滚动组件之一,适合于展示线性排列的数据列表。它支持垂直和水平方向的滚动。

ListView(
  children: <Widget>[
    ListTile(title: Text('Item 1')),
    ListTile(title: Text('Item 2')),
    // 更多项目...
  ],
)

2、GridView

在 Flutter 中,GridView 是一个非常有用的组件,用于以网格形式展示数据。无论是图片墙、产品列表还是图标菜单,GridView 都能提供灵活且高效的解决方案。
当需要以网格形式展示数据时,GridView 是一个很好的选择。它可以创建固定大小或适应性大小的网格。

GridView.count(
  crossAxisCount: 2, // 每行显示两个项目
  children: List.generate(100, (index) {
    return Center(child: Text('Item $index'));
  }),
)

3、SingleChildScrollView

在 Flutter 中,SingleChildScrollView 是一个非常有用的组件,用于包裹那些内容超出屏幕范围的单个子部件。与 ListView 和 GridView 不同,SingleChildScrollView 更适合用于包含少量直接子部件的场景,比如长表单、长文本等。
如果你的内容超出了屏幕的尺寸,但是又不需要列表那样的复用机制,可以考虑使用 SingleChildScrollView。

SingleChildScrollView(
  child: Column(
    children: [
      Text('Header'),
      Text('Body content that is too long to fit on the screen...'),
    ],
  ),
)

3.1、SingleChildScrollView 基本用法

3.1.1、创建一个简单的 SingleChildScrollView

最简单的 SingleChildScrollView 只需要一个 child 参数来指定要显示的子部件。下面是一个示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('SingleChildScrollView Example'),
        ),
        body: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              Container(
                height: 200,
                color: Colors.red,
                child: Center(child: Text('Section 1')),
              ),
              Container(
                height: 200,
                color: Colors.green,
                child: Center(child: Text('Section 2')),
              ),
              Container(
                height: 200,
                color: Colors.blue,
                child: Center(child: Text('Section 3')),
              ),
              // 更多项目...
            ],
          ),
        ),
      ),
    );
  }
}

3.1.2、滚动方向

默认情况下,SingleChildScrollView 是垂直滚动的。如果你需要水平滚动,可以通过 scrollDirection 参数来设置:

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: <Widget>[
      Container(
        width: 200,
        color: Colors.red,
        child: Center(child: Text('Section 1')),
      ),
      Container(
        width: 200,
        color: Colors.green,
        child: Center(child: Text('Section 2')),
      ),
      Container(
        width: 200,
        color: Colors.blue,
        child: Center(child: Text('Section 3')),
      ),
      // 更多项目...
    ],
  ),
)

3.1.3、滚动方向

默认情况下,SingleChildScrollView 是垂直滚动的。如果你需要水平滚动,可以通过 scrollDirection 参数来设置:

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: <Widget>[
      Container(
        width: 200,
        color: Colors.red,
        child: Center(child: Text('Section 1')),
      ),
      Container(
        width: 200,
        color: Colors.green,
        child: Center(child: Text('Section 2')),
      ),
      Container(
        width: 200,
        color: Colors.blue,
        child: Center(child: Text('Section 3')),
      ),
      // 更多项目...
    ],
  ),
)

3.2、SingleChildScrollView 的高级用法

3.2.1、控制滚动位置

你可以通过 Controller 来控制 SingleChildScrollView 的滚动位置。首先需要创建一个 ScrollController 实例:

final ScrollController _controller = ScrollController();

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Scroll Control Example'),
      actions: [
        IconButton(
          icon: Icon(Icons.arrow_upward),
          onPressed: () {
            _controller.animateTo(
              0.0,
              duration: Duration(seconds: 1),
              curve: Curves.easeInOut,
            );
          },
        ),
      ],
    ),
    body: SingleChildScrollView(
      controller: _controller,
      child: Column(
        children: <Widget>[
          Container(
            height: 200,
            color: Colors.red,
            child: Center(child: Text('Section 1')),
          ),
          Container(
            height: 200,
            color: Colors.green,
            child: Center(child: Text('Section 2')),
          ),
          Container(
            height: 200,
            color: Colors.blue,
            child: Center(child: Text('Section 3')),
          ),
          // 更多项目...
        ],
      ),
    ),
  );
}

3.2.2、监听滚动事件

如果你想在用户滚动时执行某些操作,可以使用 NotificationListener 来监听滚动事件:

NotificationListener<ScrollNotification>(
  onNotification: (ScrollNotification notification) {
    if (notification.metrics.pixels == notification.metrics.maxScrollExtent) {
      print('到达底部');
    }
    return true;
  },
  child: SingleChildScrollView(
    child: Column(
      children: <Widget>[
        Container(
          height: 200,
          color: Colors.red,
          child: Center(child: Text('Section 1')),
        ),
        Container(
          height: 200,
          color: Colors.green,
          child: Center(child: Text('Section 2')),
        ),
        Container(
          height: 200,
          color: Colors.blue,
          child: Center(child: Text('Section 3')),
        ),
        // 更多项目...
      ],
    ),
  ),
)

3.3、SingleChildScrollView 的限制与注意事项

3.3.1、子部件的高度限制

SingleChildScrollView 的子部件必须有一个确定的高度(或宽度,取决于滚动方向)。如果子部件的高度或宽度不确定,可能会导致布局错误。例如,使用 Expanded 或 Flexible 作为子部件会导致错误。

3.3.2、性能考虑

虽然 SingleChildScrollView 适用于包含少量直接子部件的场景,但如果子部件数量过多,建议使用 ListView 或 GridView,因为它们具有更好的性能优化机制。

3.4、优化与最佳实践

3.4.1、确保子部件有明确的尺寸

确保 SingleChildScrollView 的子部件有明确的高度或宽度,以避免布局错误。例如,使用 Container 并设置 height 或 width:

SingleChildScrollView(
  child: Column(
    children: <Widget>[
      Container(
        height: 200,
        color: Colors.red,
        child: Center(child: Text('Section 1')),
      ),
      Container(
        height: 200,
        color: Colors.green,
        child: Center(child: Text('Section 2')),
      ),
      Container(
        height: 200,
        color: Colors.blue,
        child: Center(child: Text('Section 3')),
      ),
      // 更多项目...
    ],
  ),
)

3.4.2、使用 ConstrainedBox 设置约束

如果子部件的高度或宽度不确定,可以使用 ConstrainedBox 来设置最小和最大约束:

SingleChildScrollView(
  child: ConstrainedBox(
    constraints: BoxConstraints(
      minHeight: 600, // 最小高度
      maxHeight: 800, // 最大高度
    ),
    child: Column(
      children: <Widget>[
        Container(
          color: Colors.red,
          child: Center(child: Text('Section 1')),
        ),
        Container(
          color: Colors.green,
          child: Center(child: Text('Section 2')),
        ),
        Container(
          color: Colors.blue,
          child: Center(child: Text('Section 3')),
        ),
        // 更多项目...
      ],
    ),
  ),
)

3.5、总结

SingleChildScrollView 是 Flutter 中一个非常有用的组件,适用于包含少量直接子部件的场景。

4、CustomScrollView

CustomScrollView 是 Flutter 中一个非常强大的组件,用于创建复杂的滚动效果,如带有头部的列表、可折叠的顶部栏等。它允许你组合多个 Sliver 组件来构建自定义的滚动布局。
对于更复杂的需求,比如实现类似iOS的可折叠顶部栏(SliverAppBar)或者其他高级滚动效果,CustomScrollView 提供了极大的灵活性。

CustomScrollView(
  slivers: <Widget>[
    SliverAppBar(
      expandedHeight: 200.0,
      flexibleSpace: FlexibleSpaceBar(
        title: Text('SliverAppBar'),
      ),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return Container(
            height: 50.0,
            color: Colors.white,
            child: Center(child: Text('Item $index')),
          );
        },
        childCount: 30,
      ),
    ),
  ],
)

4.1、CustomScrollView 基本用法

4.1.1、创建一个简单的 CustomScrollView

最简单的 CustomScrollView 只需要一个 slivers 参数来指定要显示的 Sliver 组件列表。下面是一个示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('CustomScrollView Example'),
        ),
        body: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
              expandedHeight: 200.0,
              flexibleSpace: FlexibleSpaceBar(
                title: Text('CustomScrollView'),
                background: Image.network(
                  'https://via.placeholder.com/350x150',
                  fit: BoxFit.cover,
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return ListTile(
                    title: Text('Item $index'),
                  );
                },
                childCount: 30,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

4.2、CustomScrollView 的高级用法

4.2.1、使用 SliverAppBar

SliverAppBar 是一个非常常用的 Sliver 组件,用于创建可折叠的顶部栏。它可以在滚动时自动展开或收缩。

SliverAppBar(
  expandedHeight: 200.0,
  floating: false, // 是否浮动
  pinned: true, // 是否固定
  flexibleSpace: FlexibleSpaceBar(
    title: Text('CustomScrollView'),
    background: Image.network(
      'https://via.placeholder.com/350x150',
      fit: BoxFit.cover,
    ),
  ),
)

4.2.2、使用 SliverList

SliverList 用于创建普通的列表项。它类似于 ListView,但更适合在 CustomScrollView 中使用。

SliverList(
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return ListTile(
        title: Text('Item $index'),
      );
    },
    childCount: 30,
  ),
)

4.2.3、使用 SliverGrid

SliverGrid 用于创建网格布局。它类似于 GridView,但更适合在 CustomScrollView 中使用。

SliverGrid(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2, // 每行显示两个项目
    mainAxisSpacing: 10.0, // 主轴间距
    crossAxisSpacing: 10.0, // 次轴间距
  ),
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return Card(
        child: Center(
          child: Text('Item $index'),
        ),
      );
    },
    childCount: 30,
  ),
)

4.2.4、SliverPersistentHeader

SliverPersistentHeader 用于创建固定在顶部的头部组件。它可以在滚动时保持固定,直到被新的头部组件覆盖。

SliverPersistentHeader(
  delegate: MyPersistentHeaderDelegate(),
  pinned: true, // 是否固定
)

class MyPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  @override
  double get minExtent => 50.0;

  @override
  double get maxExtent => 100.0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: Text('Persistent Header'),
    );
  }

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return false;
  }
}

4.3、监听滚动事件

如果你想在用户滚动时执行某些操作,可以使用 NotificationListener 来监听滚动事件:

NotificationListener<ScrollNotification>(
  onNotification: (ScrollNotification notification) {
    if (notification.metrics.pixels == notification.metrics.maxScrollExtent) {
      print('到达底部');
    }
    return true;
  },
  child: CustomScrollView(
    slivers: <Widget>[
      SliverAppBar(
        expandedHeight: 200.0,
        flexibleSpace: FlexibleSpaceBar(
          title: Text('CustomScrollView'),
          background: Image.network(
            'https://via.placeholder.com/350x150',
            fit: BoxFit.cover,
          ),
        ),
      ),
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return ListTile(
              title: Text('Item $index'),
            );
          },
          childCount: 30,
        ),
      ),
    ],
  ),
)

4.4、优化与最佳实践

4.4.1、性能优化

  • 使用 SliverList 和 SliverGrid 的 SliverChildBuilderDelegate 来动态生成列表项,避免一次性创建大量子部件。
  • 确保每个 Sliver 组件的构建逻辑尽可能简洁高效。

4.4.2、自定义 Sliver 组件

你可以根据需要自定义 Sliver 组件的样式和布局。例如,使用 Column 和 Row 组合来创建复杂的头部组件:

SliverAppBar(
  expandedHeight: 200.0,
  flexibleSpace: FlexibleSpaceBar(
    title: Text('CustomScrollView'),
    background: Stack(
      fit: StackFit.expand,
      children: [
        Image.network(
          'https://via.placeholder.com/350x150',
          fit: BoxFit.cover,
        ),
        Positioned(
          bottom: 16.0,
          left: 16.0,
          right: 16.0,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'CustomScrollView',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                '这是一个复杂的头部组件',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16.0,
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
)

4.5、总结

CustomScrollView 是 Flutter 中一个非常强大的组件,它允许你组合多个 Sliver 组件来创建自定义的滚动布局。

三、优化与注意事项

  • 性能优化:对于大型列表,确保使用 ListView.builder 或者 GridView.builder 而不是直接将所有子部件放入 children 列表中。这样可以按需构建可见部分的子部件,提高性能。
  • 滚动监听:可以通过 NotificationListener<ScrollNotification> 监听滚动事件,这对于实现某些特定功能非常有用,比如加载更多数据或者改变UI状态。
  • 自定义滚动物理:通过设置 physics 属性来自定义滚动行为,例如禁止滚动、添加回弹效果等。

四、总结

Flutter 的滚动组件不仅强大而且灵活,能够满足从简单到复杂的各种需求。正确理解和运用这些组件,可以显著提升应用程序的用户体验。

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

推荐阅读更多精彩内容