Switch 與 Checkbox


Switch 就是切換開關,透過 value 特性設定 truefalse,來改變 Switch 建構時的初始外觀,true 會繪製為「開」的外觀,false 是繪製為「關」的外觀。

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Text('Openhome.cc')),
      body: Row(
        children: [
          Switch(value: true, onChanged: (value) => print(value)),
          Switch(value: false, onChanged: (value) => print(value))
        ],
      ),
    )
  )
);

顯示結果如下:

Switch 與 Checkbox

當你操作 Switch 時,會觸發 onChanged,注意,不一定要切換過去,只要有動到,就算只是點一下,也會觸發 onChanged,處理器的 value 會是 Switch 建構時指定的 value 之相反值,例如,點選上例第一個 Switch,始終都會傳入 false,就算你怎麼切換,一定都是 false

這是怎麼一回事呢?官方文件上說到,Switch 本身不維護任何狀態,這說法有點模糊,Switch 繼承了 StatefulWidget,內部是有維護外觀的狀態,因此你才可以在 Switch 上切換外觀,官方文件上說到不維護的狀態,應該是指 value 的狀態,不會隨著你操作後的外觀而變動,value 始終都會是 Switch 建構時指定的值。

乍看這元件設計上有點怪異,外觀與 value 並不同步?onChanged 是有動到元件就會觸發?傳入的 value 始終都會是 Switch 建構時指定的值?

onChanged 的設計,應該是為了使用者的可用性,只要點選,程式設計上就可以改變外觀,不一定要拉到另一端;至於不維護外觀與 value 的同步,應該是為了分開 UI 狀態及應用程式狀態的概念,這是近來前端狀態管理時,許多開發者在倡導的設計方式。

總之,既然它是這麼設計了,那麼該怎麼上例該怎麼做,才會讓 Switch 外觀狀態切換後,應用程式有對應的開、關狀態呢?

import 'package:flutter/material.dart';

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

class PowerSavingSwitch extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _PowerSavingState();
}

class _PowerSavingState extends State<PowerSavingSwitch> {
  bool saving = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('省電模式'),
        Switch(
          value: saving,         // 依 saving 決定 Switch 外觀狀態
          onChanged: (value) {
            setState(() {
              saving = !saving;  // 改變 saving
            });
          })
      ],
    );
  }
}

這麼一來,只要點選,就會自動切換,不用自行拉到另一頭了:

Switch 與 Checkbox

至於 Checkbox 應該不用太多著墨了,就是核取方塊的外觀,可以是雙態或三態,這是透過 tristate 特性來控制;類似地,也是外觀狀態與應用程式狀態應該分離的設計概念,因此使用上要注意的,與 Switch 相同,或者應該說,Flutter 鼓勵外觀狀態與應用程式狀態應該分離,有不少元件都是這麼設計。

直接來看個 Checkbox 的範例(只是把上例的 Switch 字眼都換為 Checkbox 罷了)

import 'package:flutter/material.dart';

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

class PowerSavingCheckbox extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _PowerSavingState();
}

class _PowerSavingState extends State<PowerSavingCheckbox> {
  bool saving = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('省電模式'),
        Checkbox(
          value: saving,
          onChanged: (value) {
            setState(() {
              saving = !saving;
            });
          })
      ],
    );
  }
}

執行結果如下:

Switch 與 Checkbox