在Flutter开发中,我们可以通过定义
Theme
,复用颜色和字体样式,从而让整个app的设计看起来更一致。
一. Theme主题的使用
Theme分为:全局Theme和局部Theme
主题有两个作用:
- 设置了主题之后,某些Widget会自动使用主题的样式(比如AppBar的颜色)
- 将某些样式放到主题中统一管理,在应用程序的其它地方直接引用
1.1. 全局Theme
全局Theme会影响整个app的颜色和字体样式。
使用起来非常简单,只需要向MaterialApp构造器传入ThemeData
即可。
- 如果没有设置Theme,Flutter将会使用预设的样式。
- 当然,我们可以对它进行定制。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/**
* 1.一旦设置了主题, 那么应用程序中的某些Widget, 就会直接使用主题的样式
* 2.可以通过Theme.of(context).textTheme.body2拿到,然后使用
*/
return MaterialApp(
title: 'Flutter Demo',
// 全局主题
theme: ThemeData(
// 1.亮度 Brightness.dark就是暗黑模式,一般我们不设置
brightness: Brightness.light,
// 2.primarySwatch传入不是Color, 而是MaterialColor,是继承于Color的
// 包含了primaryColor和accentColor
// 设置完之后,导航条的颜色,TabBar的颜色,Switch的颜色,floatingActionButton的颜色都会改变
primarySwatch: Colors.red,
// 3.primaryColor: 单独设置导航和TabBar的颜色,会覆盖primarySwatch
primaryColor: Colors.orange,
// 4.accentColor: 单独设置FloatingActionButton、Switch的颜色
accentColor: Colors.green,
// 5.Button的主题
buttonTheme: ButtonThemeData(
// 默认是88x36
height: 25,
minWidth: 10,
buttonColor: Colors.yellow
),
// 6.Card的主题
cardTheme: CardTheme(
// 背景颜色
color: Colors.greenAccent,
// 阴影
elevation: 10
),
// 7.Text的主题
textTheme: TextTheme(
// Metarial默认使用的文本,使用的就是body1
body1: TextStyle(fontSize: 16, color: Colors.red),
// 就是body1的粗体
body2: TextStyle(fontSize: 20),
display1: TextStyle(fontSize: 14),
display2: TextStyle(fontSize: 16),
display3: TextStyle(fontSize: 18),
display4: TextStyle(fontSize: 20),
)
),
home: HYHomePage(),
);
}
}
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页")
),
body: Center(
child: Column(
children: <Widget>[
Text("Hello World"),
Text("Hello World", style: TextStyle(fontSize: 14),),
Text("Hello World", style: TextStyle(fontSize: 20),),
Text("Hello World", style: Theme.of(context).textTheme.body2,),
Text("Hello World", style: Theme.of(context).textTheme.display3,),
// 安卓风格的Switch,受primarySwatch的影响
Switch(value: true, onChanged: (value) {},),
// iOS风格的Switch,颜色默认是绿色的,不受主题影响,可以通过activeColor修改颜色
CupertinoSwitch(value: true, onChanged: (value) {}, activeColor: Colors.red,),
RaisedButton(child: Text("R"), onPressed: () {},),
Card(child: Text("你好啊,李银河", style: TextStyle(fontSize: 50),),)
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
title: Text("首页"),
icon: Icon(Icons.home)
),
BottomNavigationBarItem(
title: Text("分类"),
icon: Icon(Icons.category)
)
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) {
return HYDetailPage();
}
));
},
),
);
}
}
1.2. 局部Theme
如果某个具体的Widget不希望直接使用全局的Theme,而希望自己来定义,应该如何做呢?非常简单,只需要在Widget的父节点包裹一下Theme即可。
创建另外一个新的页面,在新的页面的Scaffold外,包裹了一个Theme,并且设置data为一个新的ThemeData。但是,我们很多时候并不是想完全使用一个新的主题,而是在之前的主题基础之上进行修改:
class HYDetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Theme(
// 如果传的是个新创建的ThemeData,那么子界面所有的主题都会使用这个新创建的,全局主题就不会生效了
// 如果我们使用copyWith,就是先拿到全局主题,然后再传入参数覆盖,这样局部主题和全局主题就都还在
data: Theme.of(context).copyWith(
primaryColor: Colors.purple
),
child: Scaffold(
appBar: AppBar(
title: Text("详情页"),
backgroundColor: Colors.purple,
),
body: Center(
child: Text("detail pgae"),
),
// 修改按钮的颜色为pink
floatingActionButton: Theme(
data: Theme.of(context).copyWith(
colorScheme: Theme.of(context).colorScheme.copyWith(
secondary: Colors.pink
)
),
child: FloatingActionButton(
child: Icon(Icons.pets),
onPressed: () {
},
),
),
),
);
}
}
1.3. Flutter中文网错误
但是这里有一个注意事项:accentColor在这里并不会被覆盖。
为什么不能覆盖呢?https://github.com/material-components/material-components-flutter-codelabs/issues/106
我摘抄一点官方人员的回复:
其实官网文档中之前也出现了类似的错误,比如Flutter中文网之前是翻译官方文档的,https://flutterchina.club/cookbook/design/themes/其中就有该错误。
后来官网文档中对这个问题进行了修正:
二. 暗黑Theme适配
2.1. darkTheme
目前很多应用程序都需要适配暗黑模式,Flutter中如何做到暗黑模式的适配呢?
事实上,MaterialApp中有theme和dartTheme两个参数,dartTheme就是对应暗黑模式下的主题。
在Flutter中,我们不需要判断用户是否开启了暗黑模式,Flutter会自动帮我们判断,如果用户打开了暗黑模式,会自动使用dartTheme。
开发中,一般我们单独创建一个文件,用于维护各种模式:
import 'package:flutter/material.dart';
class HYAppTheme {
static const double smallFontSize = 16;
static const double normalFontSize = 22;
static const double largeFontSize = 24;
static final Color norTextColors = Colors.red;
static final Color darkTextColors = Colors.green;
// 使用静态属性
static final ThemeData norTheme = ThemeData(
primarySwatch: Colors.yellow,
textTheme: TextTheme(
body1: TextStyle(fontSize: normalFontSize, color: norTextColors)
)
);
static final ThemeData darkTheme = ThemeData(
primarySwatch: Colors.grey,
textTheme: TextTheme(
body1: TextStyle(fontSize: normalFontSize, color: darkTextColors)
)
);
}
导入之后直接使用就行:
import 'package:flutter/material.dart';
import 'package:learn_flutter/_13_theme/shared/app_theme.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
// 正常主题
theme: HYAppTheme.norTheme,
// 暗黑主题
darkTheme: HYAppTheme.darkTheme,
home: HYHomePage(),
);
}
}
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: Text("Hello World"),
),
);
}
}