Skip to content

Latest commit

 

History

History
164 lines (143 loc) · 5.99 KB

实战技巧.md

File metadata and controls

164 lines (143 loc) · 5.99 KB

[TOC]

统一的Material Design

在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的表现形式是很常用的手段,系统提供的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对象
      ),
    );
  }
}

JSON的序列化和反序列化

对于少量的序列化需求,我们可以在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库来自动生成序列化和反序列化部分的代码。

dispose释放资源

我们在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();
    }