zoukankan      html  css  js  c++  java
  • Flutter入门篇

    环境搭建

    Flutter的安装就不在这里演示了,可以从下面几个网站上学习安装。

    这些网站也通过丰富的Flutter学习资料

    Flutter的第一个应用

    在创建一个Flutter应用后,我们可以看到如下的demo代码。(其中注释是个人翻译,如有不正确请谅解)

    import 'package:flutter/material.dart';
    
    //应用启动
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // 这个App的根Widget
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo', //应用名
          theme: ThemeData(
            // 这个应用的主题
            //
            // 你用 "flutter run"运行这个应用,你将看到一个蓝色的ToolBar。
            // 你也可以改变下面primarySwatch 的值,从Colors.blue变成 Colors.green。
            // 然后执行 "hot reload" ,可以看到计数器并没有恢复初始状态0,这个应用也并没有重启。
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      // 我们可以知道这个MyHomePage 的 widget 是这个应用的首页,而且它是有状态的,
      //这就意味着下面定义的State对象中的字段能够影响应用的显示。
    
     //这个类是这个状态的配置类,它所持有的这个title值是其父类提供的,
     //被创建状态的方法使用,在Widget的子类中总是被标记为“final”
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          // 回调setState去告诉Flutter framework 已经有一些状态发生了改变,
          // 让下面这个返回Widget的build方法去展示更新的内容。当然,如果我们没有回调
          // 这个setState,那么build方法也不会被调用,也就不会有什么更新展示。
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        // 这个方法在每次setState的时候被调用,例如上面的_incrementCounter方法。
        //
        // Flutter framework 是被优化过的,所以它的重新运行build方法是非常快速的,只需要
        // 运行你需要更新的内容,不需要去分别所有的widgets的实例。
        return Scaffold(
          appBar: AppBar(
            // 我们能够使用在App.build方法中创建的值,并且赋值
            // Here we take the value from the MyHomePage object that was created by
            // the App.build method, and use it to set our appbar title.
            title: Text(widget.title),
          ),
          body: Center(
            // Center是一个布局Widget,它提供一个child 并且规定了只能居于父类的正中心
            child: Column(
              // Column 也是一个布局Widget,它有一系列的子布局并且这些子布局都是垂直方向的。
              // 默认情况下,Column会调整它自己的大小去适应子级的横向大小。
              //
              // 调用 "debug painting"可以看每一个widget的线框
              //
              // Column 有大量的属性去控制自己的大小和它子级的位置,这里使用了mainAxisAlignment
              // 让其子布局内容是垂直方向排列。
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    
    

    运行Flutter

    我使用的是Android Studio 创建的Flutter应用,可以看到如下所示的编译界面

     
    图片来自网络
    • 点击Run (就是那个绿色的三角)之后我们可以看到如下运行结果:
     
    image
    • 点击蓝色的“+”号我们可以看到,中间的数字一直在增加,所以demo给我的是一个简单计数器的实现

    Demo分析

    我们从官网知道Flutter是用Dart语言进行编码的,我们是不是需要单独去学习掌握这门语言呢?在我看来是不需要的,因为单独去学习一门新的语言的过程是很枯燥的,我们可以从Demo中去学习,这样更高效一些。所以我们来分析一下上述例子给了我们一个怎样的知识点。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    

    通过上述代码我们知道:

    • 首先导入了一个叫做materialdart文件。
    • 通过一个main()方法调用了MyApp的一个类。
    • 这个MyApp就是这个应用的入口。(根据runApp可知)

    对于一个Flutter小白就会有疑问了:

    • 为什么要导入material的文件呢?
      遇到这样不明白的地方,我们就可以去官网查资料了,官网给的回答如下:

    Flutter提供了许多widgets,可帮助您构建遵循Material Design的应用程序。Material应用程序以MaterialApp widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp完全是可选的,但是使用它是一个很好的做法。


    也就是说主要是为了向开发者提供已经实现好了的material设计风格,我们可以进入(Windows下Ctrl +鼠标左键,Mac下Command+鼠标左键material.dart源码,可以发现如下:

    library material;
    
    export 'src/material/about.dart';
    export 'src/material/animated_icons.dart';
    ...
    // 很多,这里就不占用大量篇幅
    export 'widgets.dart';
    

    从官网我们知道已经有大量的widgets供给我们使用,那么这些在哪里呢?
    当然就是上面的widgets.dart文件了,我们进入这个文件中可以看到内容大致如下:

    export 'src/widgets/animated_cross_fade.dart';
    ...
    export 'src/widgets/framework.dart';
    ...
    export 'src/widgets/will_pop_scope.dart';
    

    也是不同的dart文件,我们进入第一个animated_cross_fade

    class AnimatedCrossFade extends StatefulWidget {
    /// Creates a cross-fade animation widget.
        ...
    }
    

    从给的注释可以知道,这就是一个带淡入淡出动画的Widget,这个Widget继承自StatefulWidget,可以看到StatefulWidget也就是继承自Widget

    abstract class StatelessWidget extends Widget {
      /// Initializes [key] for subclasses.
      const StatelessWidget({ Key key }) : super(key: key);
    
      /// Creates a [StatelessElement] to manage this widget's location in the tree.
      ///
      /// It is uncommon for subclasses to override this method.
      @override
      StatelessElement createElement() => StatelessElement(this);
    
      @protected
      Widget build(BuildContext context);
    }
    
    
    abstract class StatefulWidget extends Widget {
      /// Initializes [key] for subclasses.
      const StatefulWidget({ Key key }) : super(key: key);
    
      /// Creates a [StatefulElement] to manage this widget's location in the tree.
      ///
      /// It is uncommon for subclasses to override this method.
      @override
      StatefulElement createElement() => StatefulElement(this);
      @protected
      State createState();
    }
    

    到此我们惊奇的发现,Demo代码中的MyApp继承的StatelessWidget原来也在这里,但是MyHomePage却继承自StatefulWidget,这是为什么呢?这就会引出第二个问题:

    • StatelessWidgetStatefulWidget 的区别是什么呢?

    StatefulWidget可以拥有状态,这些状态在widget生命周期中是可以变的,而StatelessWidget是不可变的。
    StatefulWidget至少由两个类组成:

    • 一个StatefulWidget类。
    • 一个 State类; StatefulWidget类本身是不变的,但是 State类中持有的状态在widget生命周期中可能会发生变化。

    StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget


    这也就是为什么MyApp是继承自StatelessWidget 而 MyHomePage 继承自StatefulWidget:

    • MyApp中不需要更改状态,仅仅嵌套一个MyHomePage 的Widget
    • 而MyHomePage 要继承StatefulWidget的原因是:通过点击+去增加数字大小,改变了显示的状态,所以需要继承StatefulWidget

    分析执行方式

    我们回到 MyApp这个类的build方法中,可以看到它返回了一个MaterialApp的一个Widget,在前面说过,Material Design的应用是以MaterialApp widget开始的,所以返回了一个MaterialApp

    return MaterialApp(
          title: 'Flutter Demo', //应用名
          theme: ThemeData(
            primarySwatch: Colors.blue, // 主题色
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'), // 首页
        );
    

    从上可以知道由于计数是一个可变的更新状态,那么就需要两个类去实现:

    • 一个继承自StatefulWidget, 就是我们的MyHomePage
    • 一个继承自State用于维护这个状态,也就是我们的_MyHomePageState
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      // 我们可以知道这个MyHomePage 的 widget 是这个应用的首页,而且它是有状态的,
      //这就意味着下面定义的State对象中的字段能够影响应用的显示。
    
     //这个类是这个状态的配置类,它所持有的这个title值是其父类提供的,
     //被创建状态的方法使用,在Widget的子类中总是被标记为“final”
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    

    MyHomePage这个类里面没有太多内容:

    • 通过构造方法将title值传入
    • 通过createState 返回了一个_MyHomePageState的有状态的State

    到此处我们知道了实际上对数据的操作肯定就在_MyHomePageState中:

    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          // 回调setState去告诉Flutter framework 已经有一些状态发生了改变,
          // 让下面这个返回Widget的build方法去展示更新的内容。当然,如果我们没有回调
          // 这个setState,那么build方法也不会被调用,也就不会有什么更新展示。
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        // 这个方法在每次setState的时候被调用,例如上面的_incrementCounter方法。
        //
        // Flutter framework 是被优化过的,所以它的重新运行build方法是非常快速的,只需要
        // 运行你需要更新的内容,不需要去分别所有的widgets的实例。
        return Scaffold(
          appBar: AppBar(
            // 我们能够使用在App.build方法中创建的值,并且赋值
            title: Text(widget.title),
          ),
          body: Center(
            // Center是一个布局Widget,它提供一个child 并且规定了只能居于父类的正中心
            child: Column(
              // Column 也是一个布局Widget,它有一系列的子布局并且这些子布局都是垂直方向的。
              // 默认情况下,Column会调整它自己的大小去适应子级的横向大小。
              //
              // 调用 "debug painting"可以看每一个widget的线框
              //
              // Column 有大量的属性去控制自己的大小和它子级的位置,这里使用了mainAxisAlignment
              // 让其子布局内容是垂直方向排列。
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    

    可以看出这里提供了两个方法:build_incrementCounter,我们已经知道一般Widget里面的build方法返回的是一个页面布局。

    _incrementCounter的实现内容很简单:
    就是使用setState方法去自增这个_counter,但此处一点要注意,更改状态一定要使用 setState,如果不调用 setState将不会有任何的改变,即使你自增了这个_counter。(可以自己尝试一下)

    我们从注释中可知,这个build方法在每次更新状态(setState)的时候进行调用,我们在build的方法中增加一行打印的代码进行验证:

    @override
      Widget build(BuildContext context) {
        print("build again");
        return Scaffold(
            ...
        );
    

    发现果然是每一次点击+就会调用一次build方法,那这就会引出一个问题:这样每一次都进行更新,会影响新能吗?


    Flutter framework 被优化于快速重启运行,只需要运行你需要更新的内容,不需要去分别重新构建所有的widgets的实例。


    所以完全不必担心这个每次都执行build方法会影响性能。

    从整体的布局我们知道,build返回了一个Scaffold的widget:

    class Scaffold extends StatefulWidget {
      /// Creates a visual scaffold for material design widgets.
      const Scaffold({
        Key key,
        this.appBar,
        this.body,
        this.floatingActionButton,
        this.floatingActionButtonLocation,
        this.floatingActionButtonAnimator,
        this.persistentFooterButtons,
        this.drawer,
        this.endDrawer,
        this.bottomNavigationBar,
        this.bottomSheet,
        this.backgroundColor,
        this.resizeToAvoidBottomPadding,
        this.resizeToAvoidBottomInset,
        this.primary = true,
        this.extendBody = false,
        this.drawerDragStartBehavior = DragStartBehavior.down,
      }) : assert(primary != null),
           assert(extendBody != null),
           assert(drawerDragStartBehavior != null),
           super(key: key);
    
    

    可以知道,这个也是继承于StatefulWidget,里面有很多可以设置的初始值,这里使用到了三个:

    • appBar-布局标题栏
    • body-内容显示区域
    • floatingActionButton-浮动按钮
    return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
    

    将从MyApp携带的title赋值给appBar的title,让其显示在界面顶端。内容(body)使用了一个Center居中布局,让其child(也是一个widget)只能显示在当前正中位置。

    class Center extends Align {
      /// Creates a widget that centers its child.
      const Center({ Key key, double widthFactor, double heightFactor, Widget child })
        : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
    }
    

    紧接着返回了一个Column的child,这个widget 是纵向排列,可有有一系列的子集,所以在Column 布了两个Text,一个显示固定文本,一个显示可变的文本:

    class Column extends Flex {
      Column({
        Key key,
        MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
        MainAxisSize mainAxisSize = MainAxisSize.max,
        CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
        TextDirection textDirection,
        VerticalDirection verticalDirection = VerticalDirection.down,
        TextBaseline textBaseline,
        List<Widget> children = const <Widget>[],
      }) : super(
        children: children,
        key: key,
        direction: Axis.vertical,
        mainAxisAlignment: mainAxisAlignment,
        mainAxisSize: mainAxisSize,
        crossAxisAlignment: crossAxisAlignment,
        textDirection: textDirection,
        verticalDirection: verticalDirection,
        textBaseline: textBaseline,
      );
    }
    

    需要注意的是,对变量的占位符使用的$符号,就跟java中使用%是一样的。
    最后一个就是我们点击事件的按钮floatingActionButton,通过onPressed去调用_incrementCounter方法实现自增计数。
    整个运行的流程就到这里算是讲完了。

    Hot Reload(热加载)

    在文章开始的时候我们知道,我们有一个一道雷一样的图标,那就是Hot Reload,这个怎么个意思呢?就是说你如果更新了你的代码,不用重新运行整个都重新运行,直接使用这个就可以了,可以很迅速的将你更新的内容重新显示。
    在这里有一个很有意思的事情:
    我们点击+让计数器显示到1,然后将主题的颜色改成绿色:primarySwatch: Colors.green,

    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
           // 主题从蓝色更改为绿色
            primarySwatch: Colors.green,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    

    然后点击hot reload 会发现,我们的主题更改为绿色了,但是我的计数器显示的数字仍然是1,并没有变成0。这也印证了上面的那句话,hot reload只会更改所需要更改的内容,不会影响全部。

    总结

    这里在做一个总结,希望对才你有所帮助

    • flutter中,绝大部分可使用的内容都是widget
    • 如果只是显示内容,不涉及更改状态,就是用StatelessWidget;如果涉及状态的变更就是用StatefulWidget
    • StatefulWidget的实现需要两步:一个是需要创建继承StatefulWidget的类;另一个就是创建继承State的类,一般在State中控制整个状态。
    • 更新状态一定要调用setState方法,不然不会起作用
    • hot reload只会影响更改的内容



  • 相关阅读:
    (转载)SAPI 包含sphelper.h编译错误解决方案
    C++11标准的智能指针、野指针、内存泄露的理解(日后还会补充,先浅谈自己的理解)
    504. Base 7(LeetCode)
    242. Valid Anagram(LeetCode)
    169. Majority Element(LeetCode)
    100. Same Tree(LeetCode)
    171. Excel Sheet Column Number(LeetCode)
    168. Excel Sheet Column Title(LeetCode)
    122.Best Time to Buy and Sell Stock II(LeetCode)
    404. Sum of Left Leaves(LeetCode)
  • 原文地址:https://www.cnblogs.com/sea520/p/12044288.html
Copyright © 2011-2022 走看看