按鈕!按鈕?


在 Flutter 中,有各式各樣的按鈕,該怎麼選擇呢?就操作面上來說,就是按了會有反應的東西,就畫面設計上來說,最好的方式就是有圖有真相吧!因此〈Material Components widgets〉這類像是藝廊式的參考頁面,就是最方便的參考管道。

那麼這個主題就這樣了,可以結束了…XD

嗯…也不是這樣,來看一下 API 架構好了,以〈第一個 Flutter 專案〉中看過的 FloatingActionButton 來說,它直接繼承了 StatelessWidget

Widget > StatelessWidget > FloatingActionButton

然後,在介紹 Flutter 的按鈕元件相關文件上,常會出現的 FlatButtonOutlineButtonRaisedButton,它們都是 MaterialButton 的子類別:

Widget > StatelessWidget > MaterialButton > FlatButton
Widget > StatelessWidget > MaterialButton > OutlineButton
Widget > StatelessWidget > MaterialButton > RaisedButton

如果你看一下它們的原始碼,這些按鈕元件的 build 傳回的 Widget,都是 RawMaterialButton,而它是 StatefulWidget 的子類別:

Widget > StatefulWidget > RawMaterialButton

其實如果你看過 FloatingActionButton 的原始碼,會發現它的 build 中,也會建立 RawMaterialButton 實例,build 傳回的 Widget,子樹中會包含該實例。

RawMaterialButton 顧名思義,包含了許多「按鈕」的基本定義,除了 onPressedonLongPressed 事件特性之外,也包含了一些顏色設定、陰影、墨水動畫效果等,Flutter 中有許多按鈕,是基於 RawMaterialButton 來設計。

也就是說,如果 Flutter 本身提供的各式按鈕你都不滿意的話,也可以透過 RawMaterialButton 來自訂,例如,最常有人問的是,Text 能不能有 onPressed 事件?透過 RawMaterialButton 就可以!

import 'package:flutter/material.dart';

void main() => runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: TextButton('按我',
            onPressed: () => print('被按了XD'),
          )
        )
      ),
    )
);

class TextButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;

  TextButton(this.text, {this.onPressed});

  @override
  Widget build(BuildContext context) {
    return RawMaterialButton(
      child: Text(text),
      onPressed: onPressed,
    );
  }
}

來看一下效果:

按鈕!按鈕?

嗯?最後長按時那個效果是怎麼回事?那是 RawMaterialButton 內部使用了 InkWell 元件做出來的墨水渲染效果,可以藉由 splashColor 來改變顏色,如果想要讓上例中的文字更像個按鈕,可以如下:

import 'package:flutter/material.dart';

void main() => runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: TextButton('按我',
            onPressed: () => print('被按了XD'),
          )
        )
      ),
    )
);

class TextButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;

  TextButton(this.text, {this.onPressed});

  @override
  Widget build(BuildContext context) {
    return RawMaterialButton(
      child: Text(text),
      onPressed: onPressed,
      fillColor: Colors.lightGreen,
      splashColor: Colors.red,
    );
  }
}

執行時的效果會是:

按鈕!按鈕?

方才談到,RawMaterialButton 包含了許多「按鈕」的基本定義,也就是傳統上你認知的…嗯…「按鈕」!如果你想做的是這類的按鈕,可以透過 RawMaterialButton 來自訂,只要它提供的效果你可以接受就好。

然而,也許你想做個沒有「按鈕」概念的按鈕,畢竟 Material Design 嘛!例如,想要有個 onPressed 事件就好,這可以透過 GestureDetector 來達到:

import 'package:flutter/material.dart';

void main() => runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: TextButton('按我',
            onPressed: () => print('被按了XD'),
          )
        )
      ),
    )
);

class TextButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;

  TextButton(this.text, {this.onPressed});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Text(text),
      onTap: onPressed == null ? () => {} : onPressed,
    );
  }
}

這次就不會有顏色、陰影、動畫了:

按鈕!按鈕?

不過呢!onPressed 其實也是「按鈕」概念下的東西,手機是平面吧!平面怎麼會有 onPressed 這種壓下的概念呢?最多也就是 click 之類的吧!也就是說,既然都用了 GestureDetector,就用手勢上的事件定義就可以了,onPressed 的隱喻是有些多餘。

不過真要細談的話,GestureDetector 也是觸控事件(Pointer Event)的進一步封裝,這要談下去,會涉及事件處理的細節,之後專門談事件時再來說了。