Skip to content

Scroll

SingleChildScrollView 是单子组件可滚动容器,适用于内容可能超出屏幕、需要滚动的场景。ScrollController 用于控制滚动位置和监听滚动事件。

构造函数

dart
SingleChildScrollView({
  Key? key,                                                        // 组件标识
  Axis scrollDirection = Axis.vertical,                            // 滚动方向
  bool reverse = false,                                           // 是否反向滚动
  EdgeInsetsGeometry? padding,                                   // 内边距
  bool primary,                                                   // 是否使用 PrimaryScrollController
  ScrollPhysics? physics,                                        // 滚动物理效果
  ScrollController? controller,                                   // 滚动控制器
  Widget? child,                                                  // 子组件
  DragStartBehavior dragStartBehavior = DragStartBehavior.start, // 拖拽行为
  Clip clipBehavior = Clip.hardEdge,                             // 裁剪行为
  String? restorationId,                                          // 状态恢复 ID
})

属性速查

属性类型默认值说明
scrollDirectionAxisvertical滚动方向
reverseboolfalse是否反向
paddingEdgeInsetsGeometry?null内边距
physicsScrollPhysics?平台默认滚动物理效果
controllerScrollController?null滚动控制器
primaryboolnull是否使用 PrimaryScrollController
childWidget?null子组件

常用值速查

scrollDirection — 滚动方向

说明
Axis.vertical垂直滚动(默认)
Axis.horizontal水平滚动

physics — 滚动物理效果

说明
BouncingScrollPhysics()iOS 风格:到边界后弹回
ClampingScrollPhysics()Android 风格:到边界后停止,显示光晕
AlwaysScrollableScrollPhysics()始终可滚动(即使内容不足一屏)
NeverScrollableScrollPhysics()禁止滚动
FixedExtentScrollPhysics()吸附到固定项(Picker 用)

ScrollController

ScrollController 用于控制滚动位置和监听滚动事件,使用后必须在 dispose() 中释放。

构造函数

dart
ScrollController({
  double initialScrollOffset = 0.0,    // 初始滚动偏移
  bool keepScrollOffset = true,        // 是否保存滚动位置
  String? debugLabel,                  // 调试标签
})

属性与方法

属性/方法类型说明
offsetdouble当前滚动偏移(只读)
positionScrollPosition当前滚动位置信息
hasClientsbool是否有绑定的 ScrollView
animateTo(offset, duration, curve)Future<void>动画滚动到指定位置
jumpTo(offset)void立即跳到指定位置(无动画)
dispose()void释放资源

滚动通知

使用 NotificationListener<ScrollNotification> 可以监听滚动事件,无需创建 ScrollController:

通知类型说明
ScrollStartNotification开始滚动
ScrollUpdateNotification滚动更新
ScrollEndNotification结束滚动
OverscrollNotification过度滚动(到边界后继续拉)
ScrollNotification所有滚动通知的基类

快速示例

基本可滚动页面

dart
SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    children: const [
      Text('内容1'),
      Text('内容2'),
      // ... 更多内容超出屏幕时自动滚动
    ],
  ),
)

水平滚动

dart
SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: [
      Container(width: 200, color: Colors.red),
      Container(width: 200, color: Colors.blue),
      Container(width: 200, color: Colors.green),
    ],
  ),
)

用 ScrollController 控制滚动位置

dart
class ScrollExample extends StatefulWidget {
  @override
  State<ScrollExample> createState() => _ScrollExampleState();
}

class _ScrollExampleState extends State<ScrollExample> {
  final _controller = ScrollController();

  void _scrollToTop() {
    _controller.animateTo(
      0,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
    );
  }

  void _scrollToBottom() {
    _controller.animateTo(
      _controller.position.maxScrollExtent,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            ElevatedButton(onPressed: _scrollToTop, child: const Text('顶部')),
            ElevatedButton(onPressed: _scrollToBottom, child: const Text('底部')),
          ],
        ),
        Expanded(
          child: SingleChildScrollView(
            controller: _controller,
            child: const Column(children: [/* ... */]),
          ),
        ),
      ],
    );
  }
}

监听滚动位置

dart
final _controller = ScrollController();

@override
void initState() {
  super.initState();
  _controller.addListener(() {
    print('当前偏移: ${_controller.offset}');
    if (_controller.offset >= _controller.position.maxScrollExtent - 100) {
      // 快到底部,加载更多
    }
  });
}

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

使用 NotificationListener 监听滚动

dart
NotificationListener<ScrollNotification>(
  onNotification: (notification) {
    if (notification is ScrollStartNotification) {
      print('开始滚动');
    } else if (notification is ScrollUpdateNotification) {
      print('滚动中: ${notification.scrollDelta}');   // 本次滚动增量
    } else if (notification is ScrollEndNotification) {
      print('结束滚动');
    }
    return false;   // false = 允许继续冒泡
  },
  child: SingleChildScrollView(
    child: Column(children: const [/* ... */]),
  ),
)

回到顶部按钮

dart
class BackToTopExample extends StatefulWidget {
  @override
  State<BackToTopExample> createState() => _BackToTopExampleState();
}

class _BackToTopExampleState extends State<BackToTopExample> {
  final _controller = ScrollController();
  bool _showBackToTop = false;

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      setState(() {
        _showBackToTop = _controller.offset > 300;
      });
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        SingleChildScrollView(
          controller: _controller,
          child: const Column(children: [/* ... */]),
        ),
        if (_showBackToTop)
          Positioned(
            right: 16,
            bottom: 16,
            child: FloatingActionButton.small(
              child: const Icon(Icons.arrow_upward),
              onPressed: () => _controller.animateTo(
                0,
                duration: const Duration(milliseconds: 300),
                curve: Curves.easeInOut,
              ),
            ),
          ),
      ],
    );
  }
}

常见错误

Column 内容溢出

dart
// ❌ 错误:Column 内容超出屏幕报溢出
Column(
  children: [
    TextField(...),
    TextField(...),
    TextField(...),   // 键盘弹出后溢出
  ],
)

// ✅ 修复:用 SingleChildScrollView 包裹
SingleChildScrollView(
  child: Column(
    children: [
      TextField(...),
      TextField(...),
    ],
  ),
)

SingleChildScrollView 内使用 Expanded

dart
// ❌ 错误:Expanded 需要无限空间,但 SingleChildScrollView 给了有限空间
SingleChildScrollView(
  child: Column(
    children: [
      Expanded(child: ...),    // 报错!
    ],
  ),
)

// ✅ 修复:使用固定高度或 SizedBox
SingleChildScrollView(
  child: Column(
    children: [
      SizedBox(height: 200, child: ...),
    ],
  ),
)

ScrollController 未释放

dart
// ❌ 错误:忘记释放 Controller
final _controller = ScrollController();

// ✅ 必须在 dispose 中释放
@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

下一步

基于 Flutter 官方文档整理