新的按鈕與按鈕主題
摘要
#Flutter 中新增了一組基本 Material 按鈕 Widget 和主題。原始類別已被棄用,最終將會移除。整體目標是讓按鈕更具彈性,並且更容易透過建構函式參數或主題來設定。
FlatButton
、RaisedButton
和 OutlineButton
等元件已被分別替換為 TextButton
、ElevatedButton
和 OutlinedButton
。每個新的按鈕類別都有其自身的主題:TextButtonTheme
、ElevatedButtonTheme
和 OutlinedButtonTheme
。原本的 ButtonTheme
類別已不再使用。按鈕的外觀由一個 ButtonStyle
物件指定,而不是一大組元件參數和屬性。這大致上與使用 TextStyle
物件定義文字外觀的方式相當。新的按鈕主題也使用 ButtonStyle
物件進行配置。ButtonStyle
本身只是一組視覺屬性的集合。這些屬性中的許多屬性都使用 MaterialStateProperty
定義,這表示它們的值可以取決於按鈕的狀態。
背景
#我們沒有嘗試原地發展現有的按鈕類別及其主題,而是引入了新的替代按鈕元件和主題。除了讓我們擺脫原地發展現有類別所帶來的向後相容性迷宮之外,新的名稱也使 Flutter 與 Material Design 規範同步,該規範對按鈕元件使用新的名稱。
舊元件 | 舊主題 | 新元件 | 新主題 |
---|---|---|---|
FlatButton | ButtonTheme | TextButton | TextButtonTheme |
RaisedButton | ButtonTheme | ElevatedButton | ElevatedButtonTheme |
OutlineButton | ButtonTheme | OutlinedButton | OutlinedButtonTheme |
新的主題遵循 Flutter 大約在一年前為新的 Material 元件採用的「標準化」模式。主題屬性和元件建構函式參數預設為 null。非 null 的主題屬性和元件參數指定元件預設值的覆寫。實作和記錄預設值是按鈕元件元件的唯一責任。預設值本身主要基於整體 Theme 的 colorScheme 和 textTheme。
在視覺上,新的按鈕看起來有些不同,因為它們符合目前的 Material Design 規範,並且它們的顏色是根據整體 Theme 的 ColorScheme 配置的。在邊距、圓角半徑和懸停/焦點/按下回饋方面還有其他細微差異。
許多應用程式只需將新的類別名稱替換為舊的即可。具有黃金影像測試或按鈕外觀已使用建構函式參數或原始 ButtonTheme
配置的應用程式可能需要查閱遷移指南和後面的介紹性資料。
API 變更:使用 ButtonStyle 取代個別樣式屬性
#除了簡單的使用案例外,新按鈕類別的 API 與舊類別不相容。新按鈕和主題的視覺屬性使用單個 ButtonStyle
物件配置,類似於如何使用 TextStyle
物件配置 TextField
或 Text
元件。大多數 ButtonStyle
屬性都使用 MaterialStateProperty
定義,因此單個屬性可以根據按鈕的按下/焦點/懸停等狀態表示不同的值。
按鈕的 ButtonStyle
並不定義按鈕的視覺屬性,它定義按鈕預設視覺屬性的覆寫,其中預設屬性由按鈕元件本身計算。例如,要覆寫 TextButton
的所有狀態的預設前景(文字/圖示)顏色,可以寫入
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
),
onPressed: () { },
child: Text('TextButton'),
)
這種覆寫很常見;然而,在許多情況下,還需要覆寫文字按鈕用來指示其懸停/焦點/按下狀態的覆蓋顏色。這可以透過將 overlayColor
屬性新增至 ButtonStyle
來完成。
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return Colors.blue.withOpacity(0.04);
if (states.contains(MaterialState.focused) ||
states.contains(MaterialState.pressed))
return Colors.blue.withOpacity(0.12);
return null; // Defer to the widget's default.
},
),
),
onPressed: () { },
child: Text('TextButton')
)
顏色 MaterialStateProperty
只需要為應該覆寫其預設值的顏色傳回值。如果它傳回 null,則將改用元件的預設值。例如,僅覆寫文字按鈕的焦點覆蓋顏色
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.focused))
return Colors.red;
return null; // Defer to the widget's default.
}
),
),
onPressed: () { },
child: Text('TextButton'),
)
styleFrom()
ButtonStyle 實用方法
#Material Design 規範根據色彩配置的主要顏色定義按鈕的前景和覆蓋顏色。主要顏色以不同的不透明度呈現,具體取決於按鈕的狀態。為了簡化建立包含所有取決於色彩配置顏色的屬性的按鈕樣式,每個按鈕類別都包含一個靜態 styleFrom() 方法,該方法會從一組簡單的值建構 ButtonStyle
,包括它所依賴的 ColorScheme
顏色。
此範例會建立一個按鈕,該按鈕使用指定的原色和 Material Design 規範中的不透明度來覆寫其前景顏色及其覆蓋顏色。
TextButton(
style: TextButton.styleFrom(
primary: Colors.blue,
),
onPressed: () { },
child: Text('TextButton'),
)
TextButton
文件指出,當按鈕停用時,前景顏色基於色彩配置的 onSurface
顏色。要也使用 styleFrom() 覆寫它
TextButton(
style: TextButton.styleFrom(
primary: Colors.blue,
onSurface: Colors.red,
),
onPressed: null,
child: Text('TextButton'),
)
如果您嘗試建立 Material Design 變體,則使用 styleFrom()
方法是建立 ButtonStyle
的首選方式。最彈性的方法是直接定義 ButtonStyle
,並為要覆寫其外觀的狀態使用 MaterialStateProperty
值。
ButtonStyle 預設值
#新的按鈕類別等元件會根據整體主題的 colorScheme
和 textTheme
以及按鈕的目前狀態計算其預設值。在少數情況下,它們還會考慮整體主題的色彩配置是淺色還是深色。每個按鈕都有一個受保護的方法,該方法會根據需要計算其預設樣式。雖然應用程式不會直接呼叫此方法,但其 API 文件說明了所有預設值。當按鈕或按鈕主題指定 ButtonStyle
時,只有按鈕樣式的非 null 屬性才會覆寫計算的預設值。按鈕的 style
參數會覆寫對應按鈕主題指定的非 null 屬性。例如,如果 TextButton
樣式的 foregroundColor
屬性為非 null,它會覆寫 TextButonTheme
樣式的相同屬性。
如前所述,每個按鈕類別都包含一個稱為 styleFrom
的靜態方法,該方法會從一組簡單的值建構 ButtonStyle,包括它所依賴的 ColorScheme
顏色。在許多常見情況下,使用 styleFrom
建立一個覆寫預設值的單次性 ButtonStyle
最簡單。當自訂樣式的目的是覆寫預設樣式所依賴的色彩配置顏色之一(如 primary
或 onPrimary
)時,尤其如此。對於其他情況,您可以直接建立 ButtonStyle
物件。這樣做可以讓您控制所有按鈕可能狀態(如按下、懸停、停用和焦點)的視覺屬性值,例如顏色。
遷移指南
#使用以下資訊將您的按鈕遷移到新的 API。
還原原始按鈕視覺效果
#在許多情況下,只需從舊的按鈕類別切換到新的按鈕類別即可。前提是大小/形狀的微小變更以及顏色可能發生的較大變更不是問題。
為了在這些情況下保留原始按鈕的外觀,可以定義盡可能與原始按鈕相符的按鈕樣式。例如,以下樣式使 TextButton
看起來像預設的 FlatButton
final ButtonStyle flatButtonStyle = TextButton.styleFrom(
primary: Colors.black87,
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
);
TextButton(
style: flatButtonStyle,
onPressed: () { },
child: Text('Looks like a FlatButton'),
)
同樣地,若要使 ElevatedButton
看起來像預設的 RaisedButton
final ButtonStyle raisedButtonStyle = ElevatedButton.styleFrom(
onPrimary: Colors.black87,
primary: Colors.grey[300],
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
);
ElevatedButton(
style: raisedButtonStyle,
onPressed: () { },
child: Text('Looks like a RaisedButton'),
)
OutlinedButton
的 OutlineButton
樣式稍微複雜一些,因為在按下按鈕時,外框的顏色會變更為主要顏色。外框的外觀由 BorderSide
定義,您將使用 MaterialStateProperty
來定義按下的外框顏色
final ButtonStyle outlineButtonStyle = OutlinedButton.styleFrom(
foregroundColor: Colors.black87,
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
).copyWith(
side: MaterialStateProperty.resolveWith<BorderSide?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 1,
);
}
return null;
},
),
);
OutlinedButton(
style: outlineButtonStyle,
onPressed: () { },
child: Text('Looks like an OutlineButton'),
)
若要還原整個應用程式中按鈕的預設外觀,您可以在應用程式的主題中設定新的按鈕主題
MaterialApp(
theme: ThemeData.from(colorScheme: ColorScheme.light()).copyWith(
textButtonTheme: TextButtonThemeData(style: flatButtonStyle),
elevatedButtonTheme: ElevatedButtonThemeData(style: raisedButtonStyle),
outlinedButtonTheme: OutlinedButtonThemeData(style: outlineButtonStyle),
),
)
若要還原應用程式部分按鈕的預設外觀,您可以使用 TextButtonTheme
、ElevatedButtonTheme
或 OutlinedButtonTheme
包裹元件子樹狀結構。例如
TextButtonTheme(
data: TextButtonThemeData(style: flatButtonStyle),
child: myWidgetSubtree,
)
遷移具有自訂顏色的按鈕
#以下章節涵蓋以下 FlatButton
、RaisedButton
和 OutlineButton
顏色參數的使用
textColor
disabledTextColor
color
disabledColor
focusColor
hoverColor
highlightColor*
splashColor
新的按鈕類別不支援單獨的醒目提示顏色,因為它不再是 Material Design 的一部分。
遷移具有自訂前景和背景顏色的按鈕
#原始按鈕類別的兩個常見自訂是 FlatButton
的自訂前景顏色,或 RaisedButton
的自訂前景和背景顏色。使用新的按鈕類別產生相同的結果很簡單
FlatButton(
textColor: Colors.red, // foreground
onPressed: () { },
child: Text('FlatButton with custom foreground/background'),
)
TextButton(
style: TextButton.styleFrom(
primary: Colors.red, // foreground
),
onPressed: () { },
child: Text('TextButton with custom foreground'),
)
在這種情況下,TextButton
的前景(文字/圖示)顏色以及其懸停/焦點/按下的覆蓋顏色將基於 Colors.red
。依預設,TextButton
的背景填滿顏色是透明的。
遷移具有自訂前景和背景顏色的 RaisedButton
RaisedButton(
color: Colors.red, // background
textColor: Colors.white, // foreground
onPressed: () { },
child: Text('RaisedButton with custom foreground/background'),
)
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red, // background
onPrimary: Colors.white, // foreground
),
onPressed: () { },
child: Text('ElevatedButton with custom foreground/background'),
)
在這種情況下,按鈕對色彩配置主要顏色的使用相對於 TextButton
是反轉的:主要顏色是按鈕的背景填滿顏色,而 onPrimary
是前景(文字/圖示)顏色。
遷移具有自訂覆蓋顏色的按鈕
#覆寫按鈕的預設焦點、懸停、醒目提示或快顯色彩較不常見。FlatButton
、RaisedButton
和 OutlineButton
類別具有這些依賴狀態的顏色的個別參數。新的 TextButton
、ElevatedButton
和 OutlinedButton
類別改用單個 MaterialStateProperty<Color>
參數。新的按鈕允許指定所有顏色的依賴狀態的值,原始按鈕僅支援指定現在所謂的「overlayColor」。
FlatButton(
focusColor: Colors.red,
hoverColor: Colors.green,
splashColor: Colors.blue,
onPressed: () { },
child: Text('FlatButton with custom overlay colors'),
)
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.focused))
return Colors.red;
if (states.contains(MaterialState.hovered))
return Colors.green;
if (states.contains(MaterialState.pressed))
return Colors.blue;
return null; // Defer to the widget's default.
}),
),
onPressed: () { },
child: Text('TextButton with custom overlay colors'),
)
新版本更加靈活,儘管不太緊湊。在原始版本中,不同狀態的優先順序是隱式(且未記錄)且固定的,在新版本中,它是明確的。對於頻繁指定這些顏色的應用程式,最簡單的遷移路徑是定義一個或多個符合上述範例的 ButtonStyles
- 並且僅使用 style 參數 - 或定義一個封裝三個顏色參數的無狀態包裝元件。
遷移具有自訂停用顏色的按鈕
#這是一個相對罕見的自訂。FlatButton
、RaisedButton
和 OutlineButton
類別具有 disabledTextColor
和 disabledColor
參數,這些參數定義按鈕的 onPressed
回呼為 null 時的背景和前景顏色。
依預設,所有按鈕都使用色彩配置的 onSurface
顏色,停用前景顏色的不透明度為 0.38。只有 ElevatedButton
具有非透明的背景顏色,其預設值是 onSurface
顏色,不透明度為 0.12。因此,在許多情況下,人們只需使用 styleFrom
方法來覆寫停用顏色
RaisedButton(
disabledColor: Colors.red.withOpacity(0.12),
disabledTextColor: Colors.red.withOpacity(0.38),
onPressed: null,
child: Text('RaisedButton with custom disabled colors'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(onSurface: Colors.red),
onPressed: null,
child: Text('ElevatedButton with custom disabled colors'),
)
為了完全控制停用顏色,必須根據 MaterialStateProperties
明確定義 ElevatedButton
的樣式
RaisedButton(
disabledColor: Colors.red,
disabledTextColor: Colors.blue,
onPressed: null,
child: Text('RaisedButton with custom disabled colors'),
)
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return Colors.red;
return null; // Defer to the widget's default.
}),
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return Colors.blue;
return null; // Defer to the widget's default.
}),
),
onPressed: null,
child: Text('ElevatedButton with custom disabled colors'),
)
與之前的情況一樣,如果在此遷移中經常出現,則有明顯的方法可以使新版本在應用程式中更加緊湊。
遷移具有自訂高度的按鈕
#這也是一個相對罕見的自訂。通常,只有 ElevatedButton
(最初稱為 RaisedButtons
) 包含高度變更。對於與基準高度成比例的高度(根據 Material Design 規範),可以非常簡單地覆寫所有這些高度。
依預設,停用按鈕的高度為 0,其餘狀態是相對於基準 2 定義的
disabled: 0
hovered or focused: baseline + 2
pressed: baseline + 6
因此,若要遷移已定義所有高度的 RaisedButton
RaisedButton(
elevation: 2,
focusElevation: 4,
hoverElevation: 4,
highlightElevation: 8,
disabledElevation: 0,
onPressed: () { },
child: Text('RaisedButton with custom elevations'),
)
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 2),
onPressed: () { },
child: Text('ElevatedButton with custom elevations'),
)
若要任意覆寫一個高度,如按下的高度
RaisedButton(
highlightElevation: 16,
onPressed: () { },
child: Text('RaisedButton with a custom elevation'),
)
ElevatedButton(
style: ButtonStyle(
elevation: MaterialStateProperty.resolveWith<double?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed))
return 16;
return null;
}),
),
onPressed: () { },
child: Text('ElevatedButton with a custom elevation'),
)
遷移具有自訂形狀和邊框的按鈕
#原始 FlatButton
、RaisedButton
和 OutlineButton
類別都提供一個 shape 參數,該參數定義按鈕的形狀及其外框的外觀。對應的新類別及其主題支援分別使用 OutlinedBorder shape
和 BorderSide side
參數來指定按鈕的形狀及其邊框。
在此範例中,原始 OutlineButton
版本為其醒目提示(按下)狀態的邊框指定與其他狀態相同的顏色。
OutlineButton(
shape: StadiumBorder(),
highlightedBorderColor: Colors.red,
borderSide: BorderSide(
width: 2,
color: Colors.red
),
onPressed: () { },
child: Text('OutlineButton with custom shape and border'),
)
OutlinedButton(
style: OutlinedButton.styleFrom(
shape: StadiumBorder(),
side: BorderSide(
width: 2,
color: Colors.red
),
),
onPressed: () { },
child: Text('OutlinedButton with custom shape and border'),
)
大多數新的 OutlinedButton
元件的樣式參數(包括其形狀和邊框)可以使用 MaterialStateProperty
值指定,也就是說它們可以根據按鈕的狀態而具有不同的值。若要在按下按鈕時指定不同的邊框顏色,請執行以下操作
OutlineButton(
shape: StadiumBorder(),
highlightedBorderColor: Colors.blue,
borderSide: BorderSide(
width: 2,
color: Colors.red
),
onPressed: () { },
child: Text('OutlineButton with custom shape and border'),
)
OutlinedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all<OutlinedBorder>(StadiumBorder()),
side: MaterialStateProperty.resolveWith<BorderSide>(
(Set<MaterialState> states) {
final Color color = states.contains(MaterialState.pressed)
? Colors.blue
: Colors.red;
return BorderSide(color: color, width: 2);
}
),
),
onPressed: () { },
child: Text('OutlinedButton with custom shape and border'),
)
時程表
#在版本中登陸:1.20.0-0.0.pre
在穩定版本中:2.0.0
參考資料
#API 文件
按鈕樣式
按鈕樣式按鈕
ElevatedButton
ElevatedButtonTheme
ElevatedButtonThemeData
OutlinedButton
OutlinedButtonTheme
OutlinedButtonThemeData
TextButton
TextButtonTheme
TextButtonThemeData
相關 PR
除非另有說明,本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-07-07。 檢視原始碼 或 回報問題。