Flutter核心原理Widget(2)

2019-12-03  本文已影响0人  小小的开发人员

为什么要将build方法放在State中,而不是放在StatefulWidget中?

这主要是为了提高开发的灵活性。如果将build()方法在StatefulWidget中则会有两个问题:

这样很显然是不合理的,因为

  1. AnimatedWidget的状态对象是AnimatedWidget内部实现细节,不应该暴露给外部。
  2. 如果要将父类状态暴露给子类,那么必须得有一种传递机制,而做这一套传递机制是无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。

综上所述,可以发现,对于StatefulWidget,将build方法放在State中,可以给开发带来很大的灵活性。

在Widget树中获取State对象

由于StatefulWidget的的具体逻辑都在其State中,所以很多时候,我们需要获取StatefulWidget对应的State对象来调用一些方法,比如Scaffold组件对应的状态类ScaffoldState中就定义了打开SnackBar(路由页底部提示条)的方法。我们有两种方法在子widget树中获取父级StatefulWidget的State对象。

通过Context获取

context对象有一个ancestorStateOfType(TypeMatcher)方法,该方法可以从当前节点沿着widget树向上查找指定类型的StatefulWidget对应的State对象。下面是实现打开SnackBar的示例:

Scaffold(
  appBar: AppBar(
    title: Text("子树中获取State对象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父级最近的Scaffold对应的ScaffoldState对象
          ScaffoldState _state = context.ancestorStateOfType(
              TypeMatcher<ScaffoldState>());
          //调用ScaffoldState的showSnackBar来弹出SnackBar
          _state.showSnackBar(
            SnackBar(
              content: Text("我是SnackBar"),
            ),
          );
        },
        child: Text("显示SnackBar"),
      );
    }),
  ),
);

上面示例运行后,点击”显示SnackBar“,效果如图所示:

一般来说,如果StatefulWidget的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其State对象;如果StatefulWidget的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其State对象。但是通过context.ancestorStateOfType获取StatefulWidget的状态的方法是通用的,我们并不能在语法层面指定StatefulWidget的状态是否私有,所以在Flutter开发中便有了一个默认的约定:如果StatefulWidget的状态是希望暴露出的,应当在StatefulWidget中提供一个of静态方法来获取其State对象,开发者便可直接通过该方法来获取;如果State不希望暴露,则不提供of方法。这个约定在Flutter SDK里随处可见。所以,上面示例中的Scaffold也提供了一个of方法,我们其实是可以直接调用它的:

...//省略无关代码
// 直接通过of静态方法来获取ScaffoldState 
ScaffoldState _state=Scaffold.of(context); 
_state.showSnackBar(
  SnackBar(
    content: Text("我是SnackBar"),
  ),
);

通过GlobalKey

Flutter还有一种通用的获取State对象的方法——通过GlobalKey来获取! 步骤分两步:

  1. 给目标StatefulWidget添加GlobalKey

    //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
    static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
    ...
    Scaffold(
        key: _globalKey , //设置key
        ...  
    )
    
    
  2. 通过GlobalKey来获取State对象

    _globalKey.currentState.openDrawer()
    
    

GlobalKey是Flutter提供的一种在整个APP中引用element的机制。如果一个widget设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该widget对象、globalKey.currentElement来获得widget对应的element对象,如果当前widget是StatefulWidget,则可以通过globalKey.currentState来获得该widget对应的state对象。

注意:使用GlobalKey开销较大,如果有其他可选方案,应尽量避免使用它。另外同一个GlobalKey在整个widget树中必须是唯一的,不能重复。

Flutter SDK内置组件库介绍

Flutter提供了一套丰富、强大的基础组件,在基础组件库之上Flutter又提供了一套Material风格(Android默认的视觉风格)和一套Cupertino风格(iOS视觉风格)的组件库。要使用基础组件库,需要先导入:

import 'package:flutter/widgets.dart';

下面我们介绍一下常用的组件。

基础组件

Material组件

Flutter提供了一套丰富的Material组件,它可以帮助我们构建遵循Material Design设计规范的应用程序。Material应用程序以MaterialApp 组件开始, 该组件在应用程序的根部创建了一些必要的组件,比如Theme组件,它用于配置应用的主题。 是否使用MaterialApp完全是可选的,但是使用它是一个很好的做法。在之前的示例中,我们已经使用过多个Material 组件了,如:ScaffoldAppBarFlatButton等。要使用Material 组件,需要先引入它:

import 'package:flutter/material.dart';

Cupertino组件

Flutter也提供了一套丰富的Cupertino风格的组件,尽管目前还没有Material 组件那么丰富,但是它仍在不断的完善中。值得一提的是在Material 组件库中有一些组件可以根据实际运行平台来切换表现风格,比如MaterialPageRoute,在路由切换时,如果是Android系统,它将会使用Android系统默认的页面切换动画(从底向上);如果是iOS系统,它会使用iOS系统默认的页面切换动画(从右向左)。由于在前面的示例中还没有Cupertino组件的示例,下面我们实现一个简单的Cupertino组件风格的页面:

//导入cupertino widget库
import 'package:flutter/cupertino.dart';

class CupertinoTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text("Cupertino Demo"),
      ),
      child: Center(
        child: CupertinoButton(
            color: CupertinoColors.activeBlue,
            child: Text("Press"),
            onPressed: () {}
        ),
      ),
    );
  }
}

下面是在iPhoneX上页面效果截图:

总结

Flutter提供了丰富的组件,在实际的开发中你可以根据需要随意使用它们,而不必担心引入过多组件库会让你的应用安装包变大,这不是web开发,dart在编译时只会编译你使用了的代码。由于Material和Cupertino都是在基础组件库之上的,所以如果我们的应用中引入了这两者之一,则不需要再引入flutter/widgets.dart了,因为它们内部已经引入过了。

上一篇下一篇

猜你喜欢

热点阅读