捲動單一元件 SingleChildScrollView


如果元件實際尺寸超出可用空間,解決的方式之一是使用 SingleChildScrollView,例如在〈使用輸入欄位〉中,就使用了 SingleChildScrollView,避免鍵盤出現時因可用空間縮小,而造成輸入欄位無法繪製完全,而出現黃黑斑紋條。

SingleChildScrollView 顧名思義,只能指定一個子元件,通常這是在你的子元件內容固定,或者是子元件中清單之類的元件數量不多時使用,因為 SingleChildScrollView 沒有延遲載入的效果,如果子元件清單之類的元件數量很多,或者是隨時需要動態載入,SingleChildScrollView 就不適合(這時可考慮 ListView 之類的其他元件)。

SingleChildScrollView 只能指定一個子元件,如果這個子元件中需要清單之類的元件,雖然可以用 ColumnRow 之類的來組合,不過使用 ListBody 會更方便,例如〈使用輸入欄位〉中的例子,也可以使用 ListBody 改寫如下:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Text('Openhome.cc')),
      body: Login(),
    )
  )
);

class Login extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Login();
}

class _Login extends State<Login> {
  final nameController = TextEditingController();
  final passwdController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();   
    passwdController.dispose(); 
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(  // SingleChildScrollView
      child: ListBody(             // 搭配 ListBody
        children: [
          TextField(
            decoration: InputDecoration(
                labelText: '帳號',
                hintText: '使用者名稱或郵件',
                prefixIcon: Icon(Icons.person)
            ),
            controller: nameController,
          ),
          TextField(
            decoration: InputDecoration(
              labelText: '密碼',
              prefixIcon: Icon(Icons.lock),
            ),
            obscureText: true,
            controller: passwdController,
          ),
          FlatButton(
            child: Text('登入'),
            onPressed: () {
              print('名稱:${nameController.text}');
              print('密碼:${passwdController.text}');
            },
          )
        ]
      )
    );
  }
}

SingleChildScrollView 有一些特性,例如 scrollDirectionphysicscontroller 等,這些特性實際上是給內部的 Scrollable 使用,Flutter 中一些可捲動元件,內部會是由 Scrollable 實際在控制捲動的行為,你不太會自行使用 Scrollable,因為捲動本身是個很複雜的行為,必須控制事件、版面、繪圖等,基本上只要先知道有 Scrollable 的存在,而內部使用了 Scrollable 的元件,都會有這類可設定的特性就可以了。

例如,可捲動元件的捲動方向,可以是垂直與水平:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Text('Openhome.cc')),
      body: Body(),
    )
  )
);

class Body extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Body();
}

class _Body extends State<Body> {
  final scrollController = ScrollController();
  @override
  void dispose() {
    scrollController.dispose();    // 釋放控制器
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var children = List<Widget>();
    for(var i = 0; i < 20; i++) {
      children.add(RawMaterialButton(
        child: Text('$i'),
        onPressed: () {
          print(scrollController.offset); // 取得捲動偏移值
        },
        fillColor: Colors.lightGreen,
        splashColor: Colors.red,
      ));
    }

    return SingleChildScrollView(
      controller: scrollController,
      scrollDirection: Axis.horizontal,   // 水平捲動
      child: Center(
        child: Container(
          height: 50,
          child: ListBody(
              mainAxis: Axis.horizontal,  // 水平排列
              children: children
          )
        ),
      )
    );
  }
}

範例中也示範了如何使用 ScrollController,以取得捲動偏移值,基本上與〈使用輸入欄位〉中控制器的使用方式類似,執行後效果如下:

捲動單一元件