[TOC]
在MaterialApp控件构造中,系统为我们提供了几个可以很方便地控制整个程序UI风格的入参:
- theme:ThemeData类型,提供颜色、字体等配置参数。当此值为null时,系统会构造一个默认的主题风格
- darkTheme:ThemeData类型。当此值为null时,系统会使用theme参数提供的数据代替
- themeMode:主题模式,可选light、dark,对应上面两个参数,另可选system,跟随手机系统的主题模式
因此,当我们构造MaterialApp控件时,可以传入由UI设计师编制的UI方案。
class MyApp extends StatelessWidget{
Widget build(BuildContext context){
return MaterialApp(
...
theme: ThemeData(
primaryColor: Colors.deepOrange,
),
...
);
}
}
而当我们在构建页面时,可以使用Theme.of(context)方法获取到该ThemeData对象。
class SomePage extends StatelessWidget{
Widget build(BuildContext context){
return Container(
color: Theme.of(context).primaryColor //Colors.deepOrange
);
}
}
除了主题色,ThemeData还提供了很多其他的颜色以及文字风格,基本就是为了满足Material Design来设计的。具体其他的可选参数参看官方文档或者简书文档Flutter: Theme
初学Flutter开发,控件尺寸溢出屏幕的情况经常出现。究其原因,主要是因为各种型号的手机屏幕物理尺寸不同,而Flutter中对于width、height传入的数值又是与设备分辨率无关的Logic Pixel。
因此对于某些情况,我们可以获取屏幕的尺寸来进行百分比计算获得控件的具体尺寸,可以保证在不同尺寸的屏幕上应用的表现一致。
class SomePage extends StatelessWidget{
Widget build(BuildContext context) {
final screen = MediaQuery.of(context).size; //获取屏幕尺寸
return Padding(
padding: EdgeInsets.symmetric(
horizontal: screen.width * 0.08, //水平padding为屏幕宽度的8%
vertical: screen.height * 0.08, //垂直padding为屏幕高度的8%
),
child: Container(
color: Colors.red,
),
);
}
}
上面的例子,可以保证在任何设备上,页面都有一个相同比例的padding,水平方向是屏幕宽度的8%,垂直方向是屏幕高度的8%。
从外部传入参数来修改Widget的表现形式是很常用的手段,系统提供的Widget构造就提供了大量的可以配置的参数,而我们自定义控件时也应参考官方的写法来实现。
例如StatelessWidget
class StatelessDemo extends StatelessWidget{
final title;
const SomePage({Key key, this.title = "NoTitle"}) : super(key: key);
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(title),
),
);
}
}
无状态控件的传参和使用比较简单,但对于有状态控件,传参后参数并不能直接在State类内直接使用。有的人会想到在State构造时向其传参,但其实并没有这个必要。
State对象内部有一个指向StatefulWidget的widget引用,我们在构造控件时直接使用即可。
class StatefulDemo extends StatefulWidget {
final title;
const StatefulDemo({Key key, this.title = "NoTitle"}) : super(key: key);
_StatefulDemoState createState() => _StatefulDemoState();
}
class _StatefulDemoState extends State<StatefulDemo> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title), //直接使用widget引用即可指向StatefulDemo对象
),
);
}
}
对于少量的序列化需求,我们可以在class内实现fromJson构造和toJson方法来快速地进行序列化和反序列化
main() {
final jsonStr = '{"name":"jack", "age":18}';
final person = Person.fromJson(json.decode(jsonStr));
print(person); //Person(name=jack, age=18)
final encoded = json.encode(person);
print(encoded); //{"name":"jack","age":18}
}
class Person {
final String name;
final int age;
Person.fromJson(Map<String, dynamic> json)
: name = json["name"],
age = json["age"];
toJson() => {
"name": name,
"age": age,
};
@override
String toString() {
return "Person(name=$name, age=$age)";
}
}
对于较大的序列化需求,可以使用json_serializable库来自动生成序列化和反序列化部分的代码。
我们在StatefulWidget中构造各种controller、订阅数据流生成的StreamSubscription、监听事件的listener等对象和操作,需要在dispose方法中进行释放,否则会发生一些奇奇怪怪的bug。
class SomeState extends State<SomePage>{
...
@override
void initState(){
controller = TabController();
controller.addListener(tabListener);
subscription = stream.listen(onData);
super.initState();
}
@override
void dispose(){
controller.removeListener(tabListener);
controller.dispose();
subscription.cancel();
super.dispose();
}
...
}
另外,根据StatefulWidget的生命周期可知,initState方法必然执行,因此在dispose时对于controller和subscription不必判断是否为空。若从程序健壮性考虑,可以使用“?.”表达。
@override
void dispose(){
controller?.removeListener(tabListener);
controller?.dispose();
subscription?.cancel();
}