美文网首页
Flutter核心原理Widget(2)

Flutter核心原理Widget(2)

作者: 小小的开发人员 | 来源:发表于2019-12-03 11:37 被阅读0次

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

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

    • 状态访问不便。

      试想一下,如果我们的StatefulWidget有很多状态,而每次状态改变都要调用build方法,由于状态是保存在State中的,如果build方法在StatefulWidget中,那么build方法和状态分别在两个类中,那么构建时读取状态将会很不方便!试想一下,如果真的将build方法放在StatefulWidget中的话,由于构建用户界面过程需要依赖State,所以build方法将必须加一个State参数,大概是下面这样:

        Widget build(BuildContext context, State state){
            //state.counter
            ...
        }
      

      这样的话就只能将State的所有状态声明为公开的状态,这样才能在State类外部访问状态!但是,将状态设置为公开后,状态将不再具有私密性,这就会导致对状态的修改将会变的不可控。但如果将build()方法放在State中的话,构建过程不仅可以直接访问状态,而且也无需公开私有状态,这会非常方便。

    • 继承StatefulWidget不便。

      例如,Flutter中有一个动画widget的基类AnimatedWidget,它继承自StatefulWidget类。AnimatedWidget中引入了一个抽象方法build(BuildContext context),继承自AnimatedWidget的动画widget都要实现这个build方法。现在设想一下,如果StatefulWidget 类中已经有了一个build方法,正如上面所述,此时build方法需要接收一个state对象,这就意味着AnimatedWidget必须将自己的State对象(记为_animatedWidgetState)提供给其子类,因为子类需要在其build方法中调用父类的build方法,代码可能如下:

      class MyAnimationWidget extends AnimatedWidget{
          @override
          Widget build(BuildContext context, State state){
            //由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
            //所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState
            //暴露给其子类   
            super.build(context, _animatedWidgetState)
          }
      }
      
      

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

    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';
    

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

    基础组件

    • Text:该组件可让您创建一个带格式的文本。
    • RowColumn: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于Web开发中的Flexbox布局模型。
    • Stack: 取代线性布局 (译者语:和Android中的FrameLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝对定位(absolute positioning )布局模型设计的。
    • ContainerContainer 可让您创建矩形视觉元素。container 可以装饰一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。

    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了,因为它们内部已经引入过了。

    相关文章

      网友评论

          本文标题:Flutter核心原理Widget(2)

          本文链接:https://www.haomeiwen.com/subject/ewhcgctx.html