与其说是文章不如说是笔记避免遗忘,最终实现的效果:点击这里
开启一个hello world
为了方便讲解,使用
flutter create hello-world
命令,快速生成一个flutter项目
打开入口文件main.js
,找到MyApp这个类,可以看到build函数里最上层Widget为MaterialApp
,设置一些列默认配置,包括theme的设置
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
可以看到里面已经有了一个配置项primarySwatch,这里定义的是程序的主色调(默认就是蓝色),更改下值:
primarySwatch: Colors.red,
before | after |
---|---|
可以看到这就是最简单的换肤功能,我们只指定了primarySwatch
,但实际上flutter会把各个组件的深浅变化自动算出来
关于主题
主色调primarySwatch
上一节发现更改primarySwatch会自动实现各个小组件的换肤,接下来扒一扒它的实现
MaterialColor的类型
primarySwatch实际上并不是Color类型(不是一个值),而是一个MaterialColor,查看Colors.red可以发现它是一组值,平时使用的Colors.red实际上是Colors.red[500]
static const MaterialColor red = MaterialColor(
_redPrimaryValue,
<int, Color>{
50: Color(0xFFFFEBEE),
100: Color(0xFFFFCDD2),
200: Color(0xFFEF9A9A),
300: Color(0xFFE57373),
400: Color(0xFFEF5350),
500: Color(_redPrimaryValue),
600: Color(0xFFE53935),
700: Color(0xFFD32F2F),
800: Color(0xFFC62828),
900: Color(0xFFB71C1C),
},
);
ThemeData对primarySwatch的使用
点击ThemeData进入theme_data.dart文件查看源码,找到实例化部
先忽略各个变量的意义,搜索primarySwatch
可以看到多个变量的默认值实际上是用的衍生值,例如:
- primaryColor,accentColor使用的为primarySwatch[500]
- textSelectionColor,backgroundColor使用的为primarySwatch[200]
- textSelectionHandleColor使用的为primarySwatch[200]
Material组件虽多,但都是通过primarySwatch进行设置的,十分的便于管理
修改组件的主题
虽然通过primarySwatch可以统一颜色,但是有些情况下我想更个性化一点,比如appbar我想用绿色而非主题色,那么就需要ThemeData里单独配置
ThemeData(
...
// appbar
appBarTheme: AppBarTheme(
backgroundColor: Colors.green,
),
)
before | after |
---|---|
修改交互组件的主题
Material除了静态组件外,还有带有交互的组件,例如:raido,elevatedButton,inputDecoration,flutter提供了一个MaterialState枚举值来表示,里面有几种状态:
enum MaterialState {
hovered,
focused,
pressed,
dragged,
selected,
disabled,
error,
}
以elevatedButton为例,我想实现一个默认是红色,点击是绿色的按钮
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.pressed)) {
return Colors.green;
}
return Colors.red;
})),
),
关于MaterialStateProperty:https://api.flutter-io.cn/flutter/material/MaterialStateProperty-class.html
关于字号
ThemeData里可以通过textTheme属性,修改字号的默认值
Material标准
TextTheme的标准:https://www.material.io/design/typography/the-type-system.html#type-scale
查找Material组件里的字号
可惜这里只标有字号,但不知道Material组件使用的哪个,比如ListTitle的title属性到底是使用的body1还是body2
我个人是通常是看Material的组件库,找到侧边栏的theme,里面有用到的字体/字号/颜色之类的一些东西
常用归纳
详细列表: https://api.flutter-io.cn/flutter/material/TextTheme-class.html
bodyText2
subtitle1
用于列表中的主要文本(例如ListTile.title)
headline6
标题 6 →文本样式?
用于应用栏和对话框中的主要文本(例如,AppBar.title 和AlertDialog.title)
headline5
用于对话框中的大文本(例如,showDatePicker显示的对话框中的月份和年份)
button
用于ElevatedButton、TextButton和OutlinedButton上的文本。
关于Dark Mode
dark mode是ios提供的一个深色模式,flutter自身其实已经做了一定的适配,直接将brightness设置为dark
ThemeData(
brightness: Brightness.dark,
)
适配原理
打开darkmode模式发现之前设置的primarySwatch自动被替换为黑灰色,要知道怎么实现的还是得打开ThemeData的源码
可以看到当isDark
为true时,primarySwatch将替换为Colors.grey,走的是另一套规则,所以想要自定义黑暗模式的配置,需要单独写一套配置项
大体可以不变(默认配色已经很好了),可以将accentColor,textSelectionHandleColor等使用tealAccent[200]颜色的小部件统一更换为主题色primarySwatch或其他
ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.red,
accentColor: Colors.red,
...
),
效果:
开发中如何使用主题
解决样式统一
虽然将Material ui修改为自己想要的设计,但是平时开发很多组件都是自己做的,比如自定义一个button
Container(
width: 150,
color: Colors.red,
child: Center(
child: Text('注册'),
),
)
Container默认情况下是没有颜色,这里的颜色是我们手写的,这种方式就没办法统一样式,例如我改主题色就需要每一个使用到的地方全部修改一遍
Theme.of方法
可以通过Theme.of来获取当前主题的ThemeData对象,当ThemeData对象发生改变时会rebuild,上文中的Colors.red可以替换为Theme.of(context).primaryColor
Container(
width: 150,
color: Theme.of(context).primaryColor,
child: Center(
child: Text('注册'),
),
)
该用什么?
ThemeData里包含的颜色多种多样,而且命名是Material自带的,很多时候在开发自定义空间的时候不太清楚什么时候用什么变量
例如,要设置页面的默认背景色,是用backgroundColor,还是scaffoldBackgroundColor?
使用规范
变量名 | 描述 | 是否随dark mode变化 |
---|---|---|
accentColor | 主题色 | false |
scaffoldBackgroundColor | 页面背景色 | true |
cardColor | 组件的背景色 | true |
dividerTheme.color | 分割线颜色 | true |
案例
以feed-card为例,通过👇伪代码即可做出颜色分明的组件
# 页面颜色
- scaffoldBackgroundColor
# 卡片颜色
- cardColor
# 子空间颜色
- scaffoldBackgroundColor
字体字号
这个没什么好说的,将Material这个库提供的配置和自己这边设计师的规范保持一致就是了,使用的时候直接Theme.of(context).textTheme.bodyText2
就是了
配置案例
https://www.yuque.com/jinxuanzheng/mmxqgf/hm46oe#RbOzR
一键换肤功能
只换主题色的话就比较简单了,实际上就是修改primarySwatch颜色,并通知页面rebuild
颜色
先定义好几个预先设计的颜色,一定要是MaterialColor格式
List<MaterialColor> swatchCollect = [
// B站粉
MaterialColor(
0xffe67e9C,
const <int, Color>{
50: const Color(0xfff7edf0),
100: const Color(0xfff5e0e6),
200: const Color(0xfff1c7d3),
300: const Color(0xffeeb0c2),
400: const Color(0xffea97af),
500: const Color(0xffe67e9C),
600: const Color(0xffe67e9C),
700: const Color(0xffe67e9C),
800: const Color(0xffe67e9C),
900: const Color(0xffe67e9C),
},
),
// 天空蓝
MaterialColor(
0xff62aef0,
const <int, Color>{
50: const Color(0xffeaf1f8),
100: const Color(0xffdbeaf7),
200: const Color(0xffbcbdf5),
300: const Color(0xff9fccf4),
400: const Color(0xff80bdf2),
500: const Color(0xff62aef0),
600: const Color(0xff62aef0),
700: const Color(0xff62aef0),
800: const Color(0xff62aef0),
900: const Color(0xff62aef0),
}
)
];
Provider
更换皮肤需要重新rebuild,这时候需要一个状态管理,定义好之后用provider包裹一下根节点
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
provider: ^5.0.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
# AppInfoProvider.dart
class AppInfoProvider with ChangeNotifier {
// 初始值
MaterialColor curPrimarySwatch = swatchCollect[0];
// 修改颜色
changePrimarySwatch(int idx) {
curPrimarySwatch = swatchCollect[idx];
notifyListeners();
}
}
应用
# main.dart
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (_) => AppInfoProvider())],
child: MyApp(),
),
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<AppInfoProvider>(
builder: (context, appInfo, _) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: appInfo.curPrimarySwatch,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
# home.dart
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
context.read<AppInfoProvider>().changePrimarySwatch(1);
},
child: Container(
width: 150,
height: 70,
color: Theme.of(context).primaryColor,
child: Center(
child: Text('更换皮肤', style: TextStyle(color: Colors.white)),
),
),
)
],
),
),
);
}
}
效果
扩展
如果想定义更复杂的策略,而不仅仅是换颜色,自己定义一个ThemeDataList即可
return Consumer<AppInfoProvider>(
builder: (context, appInfo, _) => MaterialApp(
title: 'Flutter Demo',
theme: appInfo.curTheme,
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
开启dark Mode
使用brightness: Brightness.dark
即可打开暗色模式,不过有些小地方需要调整
配置项
暗色模式有些设置项和亮色模式在逻辑上(可能是我自己)不通用,比如accentColor并非在暗色下并非使用主题色,而是一个 Colors.tealAccent[200],所以如果需要继续使用主题色需要单独配置accentColor: appInfo.curPrimarySwatch
如果没有太多个性化需要,只需要把所有使用的Colors.tealAccent[200]的颜色覆盖一次就好(可以点击ThemeData找到源码,全文搜索)
class CustomThemeData {
static ThemeData light(primarySwatch) {
return ThemeData(
brightness: Brightness.light,
primaryColor: primarySwatch[500],
//...
);
}
static ThemeData dark(primarySwatch) {
return ThemeData(
brightness: Brightness.dark,
primaryColor: primarySwatch[500],
accentColor: primarySwatch[500],
// appbar不希望用主题色设置
appBarTheme: AppBarTheme(
backgroundColor: Colors.grey[900],
// titleTextStyle: TextStyle(color: Colors.white),
textTheme: TextTheme(
headline6: TextStyle(fontSize: 20, color: Colors.white),
),
),
//...
);
}
}
扩充Provider
根据暗色/亮色模式分别选择自己的配置项
class AppInfoProvider with ChangeNotifier {
MaterialColor curPrimarySwatch = swatchCollect[0];
ThemeData curTheme = CustomThemeData.light(swatchCollect[0]);
bool mode = true; // true 亮色 false 暗色
// todo:这个可以改一下与下面合并
changePrimarySwatch(int idx) {
curPrimarySwatch = swatchCollect[idx];
notifyListeners();
}
changeTheme(bool isDark) {
mode = isDark;
if (mode != true) {
curTheme = CustomThemeData.light(curPrimarySwatch);
} else {
curTheme = CustomThemeData.dark(curPrimarySwatch);
}
notifyListeners();
}
}
应用
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Consumer<AppInfoProvider>(
builder: (context, appInfo, _) => MaterialApp(
title: 'Flutter Demo',
theme: appInfo.curTheme,
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
context.read<AppInfoProvider>().changePrimarySwatch(1);
},
child: Container(
width: 150,
height: 70,
color: Theme.of(context).primaryColor,
child: Center(
child: Text('更换皮肤', style: TextStyle(color: Colors.white)),
),
),
),
GestureDetector(
onTap: () {
print('object');
var _curMode = context.read<AppInfoProvider>().mode;
context.read<AppInfoProvider>().changeTheme(!_curMode);
},
child: Container(
margin: EdgeInsets.only(top: 30),
width: 150,
height: 70,
color: Theme.of(context).primaryColor,
child: Center(
child: Text('更换模式', style: TextStyle(color: Colors.white)),
),
),
)
],
),
),
);
}
}
效果
ThemeData配置demo
官方文档: https://api.flutter-io.cn/flutter/material/ThemeData-class.html
除了主色调外,对于各个组件的不同表现还会有自定义需求,比如appBar,divider,bottomNavigationBar,errorColor等等,但这个类实在是太过于复杂,很多配置默认值其实就够用了
为了节约时间,下面枚举除了一下常用配置项,有更细致的需求再查文档就好了(里面有一些设置和默认值相同,为了方便小伙伴儿理解放出来重新覆盖下)
/** 浅色模式 */
Color mainColor = primarySwatch[500];
ThemeData(
// 浅色模式
brightness: Brightness.light,
// 首要颜色
primarySwatch: primarySwatch,
// 小部件的前景色(旋钮、文本、覆盖边缘效果等)
accentColor: mainColor,
// 小部件背景色
cardColor: Colors.white,
// 主题弱化色
backgroundColor: primarySwatch[200],
// 页面默认色
scaffoldBackgroundColor: Color.fromRGBO(244, 244, 244, 1),
// appbar
appBarTheme: AppBarTheme(
backgroundColor: Colors.white,
titleTextStyle: XdkFonts.normal,
),
// 分割线
dividerTheme: DividerThemeData(
color: Color.fromRGBO(236, 236, 236, 1),
thickness: 1,
space: 1,
),
// 底bar设置
bottomNavigationBarTheme: BottomNavigationBarThemeData(
selectedItemColor: Colors.black87,
selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),
type: BottomNavigationBarType.fixed,
),
// input框默认值
inputDecorationTheme: InputDecorationTheme(
fillColor: Color.fromRGBO(210, 210, 210, .2),
),
// elevatedButton
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(mainColor)),
),
// icon默认颜色
iconTheme: IconThemeData(
color: Color.fromRGBO(51, 51, 51, 1),
size: Adapt.px(40),
),
textTheme: TextTheme(
headline6: 18,
bodyText2: 16,
subtitle2: 14,
caption: 12,
),
colorScheme: ThemeData().colorScheme.copyWith(
brightness: Brightness.light,
primary: mainColor,
error: XdkColors.errorColor,
),
// 警告色
errorColor: XdkColors.errorColor,
);
}
/** 深色模式 */
Color mainColor = primarySwatch[500];
TextTheme originTextTheme = ThemeData.dark().textTheme;
ThemeData(
brightness: Brightness.dark,
// 首要颜色
primaryColor: Colors.grey[900],
primarySwatch: Colors.grey,
primaryColorBrightness: Brightness.dark,
// 主题弱化色
backgroundColor: Colors.grey[200],
// 页面默认色
scaffoldBackgroundColor: Colors.grey[800],
// 小部件的前景色(旋钮、文本、覆盖边缘效果等)
accentColor: mainColor,
// 小部件背景色
cardColor: Colors.grey[850],
// 对话框颜色
dialogBackgroundColor: Colors.grey[850],
// Meterial按钮颜色
buttonColor: Colors.grey[600],
// // 选中在泼墨动画期间使用的突出显示颜色,或用于指示菜单中的项
// highlightColor: Colors.transparent,
// // 定义由InkWell和InkResponse反应产生的墨溅的外观
// splashFactory: NoSplashFactory(),
// 底bar颜色
bottomAppBarColor: Colors.grey[850],
// icon默认颜色
iconTheme: IconThemeData(
color: Colors.grey[100],
size: Adapt.px(40),
),
// 字体风格
textTheme: TextTheme(
headline6: originTextTheme.headline6.merge(XdkFonts.headline6),
bodyText2: originTextTheme.bodyText2.merge(XdkFonts.normal),
subtitle2: originTextTheme.subtitle2.merge(XdkFonts.subTitle),
caption: originTextTheme.caption.merge(XdkFonts.caption),
),
// 颜色风格
colorScheme: ThemeData().colorScheme.copyWith(
brightness: Brightness.dark,
primary: mainColor,
error: XdkColors.errorColor,
),
// 警告色
errorColor: XdkColors.errorColor,
// 分割线风格
dividerTheme: DividerThemeData(
thickness: 1,
space: 1,
color: Colors.grey[800],
),
// appBar
appBarTheme: AppBarTheme(
backgroundColor: Colors.grey[900],
titleTextStyle: XdkFonts.normal,
),
// 底bar风格
bottomNavigationBarTheme: BottomNavigationBarThemeData(
selectedItemColor: mainColor,
selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),
type: BottomNavigationBarType.fixed,
),
// 输入框风格
inputDecorationTheme: InputDecorationTheme(
fillColor: Color.fromRGBO(210, 210, 210, .2),
),
// 单选风格
radioTheme: RadioThemeData(
fillColor: MaterialStateProperty.resolveWith(
(states) {
if (states.contains(MaterialState.selected)) {
return mainColor;
}
return null;
},
),
),
// elevated按钮风格
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(mainColor),
),
),
);
最终效果
dark mode
light | dark |
---|---|
colors
相关资料
Material Design
flutter 的默认ui是基于google出品的Material Design,包括不限于配色/字号/基础组件/icon,想要自定义自己的主题,了解必不可少
ThemeData部分中文翻译
- brightness - Brightness类型,应用程序的整体主题亮度。用于按钮等小部件,以确定在不使用主色(primaryColor)或强调色(accentColor)时选择什么颜色。当亮度较暗时,画布、卡片和原色都较暗。当亮度为光时,画布和卡片的颜色是明亮的,原色的暗度根据原色亮度变化。当亮度较暗时,原色(primaryColor)与卡片和画布颜色的对比度较差;当亮度较暗时,用白色或亮蓝色来对比。
- primarySwatch - MaterialColor 类型,Material 主题中定义一种颜色,它具有十种颜色阴影的颜色样本。值越大颜色越深,10个有效的index分别为:50,100,200,…,900。默认是取中间值500。
- primaryColor - Color类型,App主要部分的背景色(ToolBar,Tabbar等)
- primaryColorBrightness - Brightness类型,primaryColor的亮度,用于确定设置在primaryColor上部的文本和图标颜色(如:工具栏文本(toolbar text))。
- primaryColorLight - Color类型,primaryColor的较浅版本
- primaryColorDark - Color类型,primaryColor的较深版本
- accentColor - Color类型,前景色(按钮、文本、覆盖边缘效果等)
- accentColorBrightness - Brightness类型,accentColor的亮度。用于确定位于accentColor上部的文本和图标颜色(例如,浮动操作按钮(FloatingButton)上的图标)
- canvasColor - Color类型,MaterialType.canvas Material的默认颜色。
- scaffoldBackgroundColor - Color类型,作为Scaffold下的Material默认颜色,用于materia应用程序或app内页面的背景色。
- bottomAppBarColor - Color类型,bottomAppBarColor的默认颜色。这可以通过指定BottomAppBar.color来覆盖。
- cardColor - Color类型,用在卡片(Card)上的Material的颜色。
- dividerColor - Color类型,分隔符(Dividers)和弹窗分隔符(PopupMenuDividers)的颜色,也用于ListTiles和DataTables的行之间。要创建使用这种颜色的合适的边界,请考虑Divider.createBorderSide。
- highlightColor - Color类型,用于墨水喷溅动画或指示菜单被选中时的高亮颜色
- splashColor - Color类型,墨水溅出的颜色
- splashFactory - InteractiveInkFeatureFactory类型,定义InkWall和InkResponse产成的墨水喷溅时的外观。
- selectedRowColor - Color类型,用于高亮选定行的颜色。
- unselectedWidgetColor - Color类型,小部件处于非活动(但启用)状态时使用的颜色。例如,未选中的复选框。通常与accentColor形成对比。
- disabledColor - Color类型,无效的部件(widget)的颜色,不管它们的状态如何。例如,一个禁用的复选框(可以选中或不选中)。
- buttonColor - Color类型,Material中RaisedButtons使用的默认填充色。
- buttonTheme - ButtonThemeData类型,定义按钮小部件的默认配置,如RaisedButton和FlatButton。
- secondaryHeaderColor - Color类型,有选定行时PaginatedDataTable标题的颜色
- textSelectionColor - Color类型,文本字段(如TextField)中文本被选中的颜色。
- cursorColor - Color类型,在 Material-style 文本字段(如TextField)中光标的颜色。
- textSelectionHandleColor - Color类型,用于调整当前选定文本部分的句柄的颜色。
- backgroundColor - Color类型,与primaryColor对比的颜色(例如 用作进度条的剩余部分)。
- dialogBackgroundColor - Color类型,Color类型,Dialog元素的背景色
- indicatorColor - Color类型,TabBar中选项选中的指示器颜色。
- hintColor - Color类型,用于提示文本或占位符文本的颜色,例如在TextField中。
- errorColor - Color类型,用于输入验证错误的颜色,例如在TextField中。
- toggleableActiveColor - Color类型,用于突出显示切换Widget(如Switch,Radio和Checkbox)的活动状态的颜色。
- fontFamily - String类型,字体类型
- textTheme - TextTheme类型,与卡片和画布对比的文本颜色
- primaryTextTheme - TextTheme类型,与primary color形成对比的文本主题。
- accentTextTheme - TextTheme类型,与accent color形成对比的文本主题。
- inputDecorationTheme - InputDecorationTheme类型,InputDecorator、TextField和TextFormField的默认InputDecoration值基于此主题
- iconTheme - IconThemeData类型,与卡片和画布颜色形成对比的图标主题。
- primaryIconTheme - IconThemeData类型,与原色(primary color)形成对比的图标主题。
- accentIconTheme - IconThemeData类型,与前景色(accent color)形成对比的图标主题。
- sliderTheme - SliderThemeData类型,SliderThemeData类型,用于渲染Slider的颜色和形状。
- tabBarTheme - TabBarTheme类型, 一个主题,用于自定义选项卡栏指示器的尺寸、形状和颜色。
- chipTheme - ChipThemeData类型,用于Chip的颜色和样式
- platform - TargetPlatform类型,widget应该适应目标的平台。
- materialTapTargetSize - MaterialTapTargetSize类型,配置特定材料部件的hit测试大小。
- pageTransitionsTheme - PageTransitionsTheme类型,每个目标平台的默认MaterialPageRoute转换。
- colorScheme
- ColorScheme类型,一组13种颜色,可用于配置大多数组件的颜色属性。
- typography - Typography类型,用于配置TextTheme、primaryTextTheme和accentTextTheme的颜色和几何文本主题值。
Dark Mode
DarkMode是一个iOS里全新的界面风格,官方翻译为「深色」和「浅色」外观,flutter的ThemeData里内置了切换选项brightness