Flutter中的SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子组件。

1、属性及含义

SingleChildScrollView({
  this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
  this.reverse = false, //决定可滚动组件的初始滚动位置是在“头”还是“尾”,false在“头”,true在“尾”
  this.padding, //内边距
  bool primary, //是否使用widget树中默认的`PrimaryScrollController`
  this.physics, //决定可滚动组件如何响应用户操作,滑动到边界时,出现弹性(ios)还是微光(android)
  this.controller,//接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。默认是PrimaryScrollController。
  this.child,//子控件,只能包含一个。
})
SingleChildScrollView常用属性值含义
scrollDirection滚动方向,默认是垂直方向
reverse决定可滚动组件的初始滚动位置是在“头”还是“尾”,false在“头”,true在“尾”,默认false
padding内边距
primary是否使用widget树中默认的PrimaryScrollController,当scrollDirection值为Axis.vertical,并且没有指定controller时,primary默认为true.
physics决定可滚动组件如何响应用户操作,滑动到边界时,出现弹性(ios)还是微光(android),ClampingScrollPhysics:Android下微光效果。BouncingScrollPhysics:iOS下弹性效果。
controller接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。默认是PrimaryScrollController
child子控件,只能包含一个。

需要注意的是,通常SingleChildScrollView只应在期望的内容不会超过屏幕太多时使用,这是因为SingleChildScrollView不支持基于Sliver的延迟实例化模型,所以如果预计视口可能包含超出屏幕尺寸太多的内容时,那么使用SingleChildScrollView将会非常昂贵(性能差),此时应该使用一些支持Sliver延迟加载的可滚动组件,如ListView

2、基本概念:基于Sliver的延迟构建

通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!为此,Flutter中提出一个Sliver(中文为”薄片“的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个”薄片“(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为”基于Sliver的延迟构建模型“。可滚动组件中有很多都支持基于Sliver的延迟构建模型,如ListView、GridView,但是也有不支持该模型的,如SingleChildScrollView。

3、示例

  • 垂直滚动

下面是一个将大写字母A-Z沿垂直方向显示的例子。


    String str =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return Scaffold(
      // 显示进度条
      appBar: new AppBar(title: new Text("滚动控件")),
      body: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        padding: EdgeInsets.all(16.0),
        child: Center(
          child: Column(
            //动态创建一个List<Widget>
            children: str
                .split("")
                //每一个字母都用一个Text显示,字体为原来的两倍
                .map((c) => Text(
                      c,
                      textScaleFactor: 2.0,
                    ))
                .toList(),
          ),
        ),
      ),
    );

垂直滚动效果图:

  • 水平滚动

下面是一个将大写字母A-Z沿水平方向显示的例子。

   Widget build(BuildContext context) {
     String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
     return Scaffold(
       // 显示进度条
       appBar: new AppBar(title: new Text("滚动控件")),
       body: SingleChildScrollView(
         scrollDirection: Axis.horizontal,
         padding: EdgeInsets.all(16.0),
         child: Center(
           child: Row(
             //动态创建一个List<Widget>
             children: str
                 .split("")
                 //每一个字母都用一个Text显示,字体为原来的两倍
                 .map((c) => Text(
                       c,
                       textScaleFactor: 2.0,
                     ))
                 .toList(),
           ),
         ),
       ),
     );

水平滚动效果图:

4、ScrollController监听滚动

通过ScrollController可以监听SingleChildScrollView滚动。

简易监听示例:

         ScrollController mController = new ScrollController();
          mController.addListener(() {
            //监听滚动事件,打印滚动位置
            mController.addListener(() {
              print(mController.offset); //打印滚动位置
            });
          });
            SingleChildScrollView(
              ……
              controller: mController,
              child: ……
            ),

5、实现“回到顶部”功能

通过ScrollController监听SingleChildScrollView滚动,实现“回到顶部”功能。

  • 完整示例

      import 'package:flutter/material.dart';
      
      class SingleChildScrollViewDemo extends StatefulWidget {
        @override
        SingleChildScrollViewDemoState createState() {
          return SingleChildScrollViewDemoState();
        }
      }
      
      class SingleChildScrollViewDemoState extends State<SingleChildScrollViewDemo> {
        ScrollController mController = new ScrollController();
        bool showToTopBtn = false; //是否显示“返回到顶部”按钮
      
        @override
        void initState() {
          //监听滚动事件,打印滚动位置
          mController.addListener(() {
            print(mController.offset); //打印滚动位置
            if (mController.offset < 200 && showToTopBtn) {
              setState(() {
                showToTopBtn = false;
              });
            } else if (mController.offset >= 200 && showToTopBtn == false) {
              setState(() {
                showToTopBtn = true;
              });
            }
          });
        }
      
        @override
        Widget build(BuildContext context) {
          String str =
              "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
          return Scaffold(
              // 显示进度条
              appBar: new AppBar(title: new Text("滚动控件")),
              floatingActionButton: !showToTopBtn
                  ? null
                  : FloatingActionButton(
                      child: Icon(Icons.arrow_upward),
                      onPressed: () {
                        //返回到顶部时执行动画
                        mController.animateTo(.0,
                            duration: Duration(milliseconds: 200),
                            curve: Curves.ease);
                      }),
              body: Column(
                children: <Widget>[
                  Expanded(
                    child: SingleChildScrollView(
                      scrollDirection: Axis.vertical,
                      padding: EdgeInsets.all(16.0),
                      controller: mController,
                      child: Center(
                        child: Column(
                          //动态创建一个List<Widget>
                          children: str
                              .split("")
                              //每一个字母都用一个Text显示,字体为原来的两倍
                              .map((c) => Text(
                                    c,
                                    textScaleFactor: 2.0,
                                  ))
                              .toList(),
                        ),
                      ),
                    ),
                  )
                ],
              ));
        }
      }
      
      void main() {
        runApp(new MaterialApp(
          title: "滚动控件案例",
          theme: new ThemeData(primaryColor: Colors.deepOrangeAccent),
          home: new SingleChildScrollViewDemo(),
        ));
      }
    

“回到顶部”效果图: