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只会影响更改的内容

原文地址:https://www.cnblogs.com/sea520/p/12044288.html

时间: 2024-11-05 21:55:40

Flutter入门篇的相关文章

Flutter入门篇(二)- 第一个APP

在上一篇文章中以简单的方式对Flutter自己提供的演示进行了一个简单的分析,当然那是远远不够.本来打算为大家带来官网上的无限下拉刷新的案例,但是发现这里的有些东西实在是太超前了,作为Flutter入门篇,当然不能这么随意,以为了让大家都能够学有所得,所以今天给大家带来了自己手撸的一个登录. 简单分析布局 我们都知道,一个简单的登录需要至少需要3步: 输入账号 输入密码 点击登录 那么我们的布局也就至少需要3个widget,为什么说至少呢?因为往往布局使用的widget都是大于操作步骤的.这里跟

Flutter入门篇(三)- 如何实现登录动画效果

在上一篇的时候,我们讲解了怎么做一个登录界面,但是之后呢?完全是草草结尾的感觉嘛,这不,接下来就是给大家详细说说,这个登录里面不得鸟的故事.先来看一个登录的过程~~ 分析 可能上面的gif图不是很真切,这上面展示了两个功能: 颜色变换的闪屏页面 动画效果的登录页面 有没有感觉这样的登录好像还不错呢,哈哈哈,接下来就详细分析一下这其中的玄机~~ 路由 一般我们的页面跳转都会涉及到路由,路由就是从一个页面跳转到另一个页面的过程,就比如Android中的Activity或IOS中的ViewContro

Flutter入门篇(一)

距离Google发布Flutter已经有很长一段时间了,又是一门新的技术,那么我们到底是学呢还是学呢还是学呢?不要问我,我不知道,鬼特么知道我这辈子还要学习多少东西.其实新技术的出现也意味着,老技术会面临淘汰危机,而你将面临着失业危机.用一句话来说:你永远不知道意外和惊喜哪个先来~~ 环境搭建 Flutter的安装就不在这里演示了,可以从下面几个网站上学习安装. Flutter官网 Flutter中文网 Flutter社区 这些网站也通过丰富的Flutter学习资料 Flutter的第一个应用

《Java从入门到放弃》入门篇:springMVC数据校验

昨天我们扯完了数据传递,今天我们来聊聊数据校验的问题.来,跟着我一起读:计一噢叫,一按艳. 在springMVC中校验数据也非常简单,spring3.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架. Spring的DataBinder在进行数据绑定时,会同时调用校验框架完成数据校验工作. 具体使用步骤如下: 1)导入数据校验的JAR包 2)在springmvc的配置文件中添加校验Bean 3)修改实体类,在属性上加上校验的注解 4)修改昨天的login4方法,加上校验的相关代码

Vue学习笔记入门篇——组件的使用

本文为转载,原文:Vue学习笔记入门篇--组件的使用 组件定义 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 组件使用 注册 注册一个全局组件,你可以使用 Vue.component(tagName, options).组件在注册之后,便可以在父实例的模块中以自定义元素 的形式使用.

Vue学习笔记入门篇——组件的内容分发(slot)

本文为转载,原文:Vue学习笔记入门篇--组件的内容分发(slot) 介绍 为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板.这个过程被称为 内容分发 (或 "transclusion" 如果你熟悉 Angular).Vue.js 实现了一个内容分发 API,使用特殊的 'slot' 元素作为原始内容的插槽. 编译作用域 在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译.假定模板为: <child-component> {{ messa

漫游Kafka入门篇之简单介绍

原文地址:http://blog.csdn.net/honglei915/article/details/37564521 介绍 Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语: Kafka将消息以topic为单位进行归纳. 将向Kafka topic发布消息的程序成为producers. 将预订topics并消费消息的程序成为consumer. Kafka以集群的方式运行,

现代C++学习笔记之二入门篇2,数据转换

static_cast:    这种强制转换只会在编译时检查. 如果编译器检测到您尝试强制转换完全不兼容的类型,则static_cast会返回错误. 您还可以使用它在基类指针和派生类指针之间强制转换,但是,编译器在无法分辨此类转换在运行时是否是安全的. dynamic_cast: dynamic_cast在运行时检查基类指针和派生类指针之间的强制转换. dynamic_cast 是比 static_cast 更安全的强制类型转换,但运行时检查会带来一些开销. const_cast:    con

软件测试系列之入门篇

一.你知道软件测试有多重要吗? 在国际上,软件测试(软件质量控制)是一件非常重要的工程工作,测试也作为一个非常独立的职业.在IBM.Microsoft等开发大型系统软件公司,很多重要项目的开发测试人员的比例能够达到1:2甚至1:4. 在国内软件测试的地位还不够高,并且大多只停留在软件单元测试.集成测试和功能测试上.软件测试从业人员的数量同实际需求有不小差距,国内软件企业中开发人员与测试人员数量一般为5:1,因此,国内的软件测试产业化还有待开发和深掘. 说到这里不知道你反应是高兴还是失望?但是我却