Flutter
Table of Contents
1. Flutter 简介
Flutter 是 Google 开发的一套全新的跨平台、开源 UI 框架,支持 Android、iOS 系统开发。
参考:
Flutter widget: Flutter widget index
Flutter API:Flutter API reference documentation
Flutter Samples:https://flutter.github.io/samples
在已有项目上进行混合开发,可参考:Add Flutter to existing app
1.1. 架构简介
1.2. 环境安装
下面以 Mac 系统为例,介绍一下 Flutter 环境的安装。
下载 Flutter SDK:
$ curl -O https://storage.googleapis.com/flutter_infra/releases/stable/macos/flutter_macos_v1.7.8+hotfix.4-stable.zip
解压到 ~/development 目录中:
$ cd ~/development $ unzip ~/Downloads/flutter_macos_v1.7.8+hotfix.4-stable.zip
设置 PATH 环境变量:
$ export PATH="$PATH:${HOME}/development/flutter/bin"
提前下载开发过程中可能用到的二进制文件:
$ flutter precache --verbose
检测是否存在问题:
$ flutter doctor --verbose
注意观察上面命令输出的问题,进行下一步操作前先解决它们。
1.3. 第一个 Flutter 项目
创建 Flutter 项目:
$ flutter create myapp # 会创建样板程序,主要源码在 lib/main.dart 中 $ cd myapp
先确保你的模拟器已经启动(注:终端中执行 open -a Simulator
可以启动 iOS 模拟器)。可以通过下面命令检测模拟器是否启动(输出显示已经启动了 Android 模拟器):
$ flutter devices # 列出目前的设备 1 connected device: Android SDK built for x86 • emulator-5554 • android-x86 • Android 9 (API 28) (emulator)
然后执行下面命令即可在模拟器中运行项目:
$ flutter run # 运行项目,如果有多个可用设备, 可以使用 -d 指定设备
项目成功运行后,会显示如图 2 所示的界面。每点击一下右下角的按钮,屏幕中间的数字就会加一,这个项目的分析可以参考节 3.1。
Figure 2: 第一个 Flutter 项目
参考:https://flutter.dev/docs/get-started/test-drive?tab=terminal
1.4. 生成 Android Apk
执行 flutter build apk
可以生成 Android apk,第一次时间可以比较长,请启用 --verbose
选项以查看进度:
$ flutter build apk --verbose # 生成 Android apk
1.4.1. 不依赖 Android Studio 生成 Apk
要在服务器上自动打包 flutter 项目为 Android Apk,但服务器没有安装 X 环境,无法使用 Android Studio。下面介绍不依赖 Android Studio 情况下,编译 flutter 项目的步骤。
第一步,安装 Android SDK。
先安装“Command line tools”,再使用其中包含的工具 sdkmanager
来安装 Android SDK。
下面步骤将安装“Command line tools”,并将其解压到目录 ${HOME}/android-sdk/ 中。
$ mkdir ${HOME}/android-sdk/ $ cd ${HOME}/android-sdk/ $ curl -O https://dl.google.com/android/repository/commandlinetools-linux-5842447_latest.zip $ unzip ~/commandlinetools-linux-5842447_latest.zip $ ls -F ${HOME}/android-sdk/ tools/ commandlinetools-linux-5842447_latest.zip # 可以看到 zip 解压后的内容都在 tools 目录中
安装完成后,设置下面环境变量:
# Android HOME export ANDROID_HOME=${HOME}/android-sdk export PATH=$ANDROID_HOME/tools:$PATH export PATH=$ANDROID_HOME/tools/bin:$PATH export PATH=$ANDROID_HOME/platform-tools:$PATH
再通过 sdkmanager
安装 Android SDK(下面安装的是 Android-29,如果有更新的版本,你可以安装更新的版本):
$ sdkmanager --sdk_root=${HOME}/android-sdk "platforms;android-29" $ sdkmanager --sdk_root=${HOME}/android-sdk "platform-tools" $ sdkmanager --sdk_root=${HOME}/android-sdk "patcher;v4" $ sdkmanager --sdk_root=${HOME}/android-sdk "build-tools;29.0.2"
安装完成后,执行下面命令以接受所有 Licenses:
$ sdkmanager --licenses
第二步,安装 Flutter SDK。
前面已经介绍过,这里省略,假定 flutter 命令已经可用。
执行下面命令在 Flutter 中配置 Android SDK 的位置:
$ flutter config --android-sdk ${HOME}/android-sdk
现在环境都已经准备好,进入到 Flutter 项目根目录中,运行 flutter build apk --verbose
即可打包项目为 Android Apk 了。
参考:https://dev.to/jay_js/setting-up-flutter-without-android-studio-olo
2. Hello World 程序分析
下面是 Flutter 版本的 Hello World 程序(把 flutter create myapp
生成的 lib/main.dart 改为下面内容即可):
// Copyright 2018 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( child: Text('Hello World'), ), ), ); } }
说明:
- 上面例子中,创建了一个 MaterialApp ,Material 是 Google 推出的应用程序视觉设计语言,Flutter 中提供了大量的 Material Widget。
- 这个应用程序扩展自 StatelessWidget, 在 Flutter 中,几乎所有东西都是 Widget,如 alignment, padding, and layout 都是 Widget。
- Scaffold Widget 来自于 Material 库,可以认为是一个脚手架,它提供了侧边栏,title,还有底部的 sheets 等等。
- Widget 的主要工作都在其
build()
方法中。 - 例子中的 body 包含了一个 Center Widget(用于居中布局),而 Center Widget 包含了一个 Text Widget(用于显示文本)。
上面代码中,语法 MaterialApp(title: xx, home: yy)
是调用 MaterialApp 类的构造函数,这个构造函数采用了 Named parameters ,所以调用时要指定参数名称(如 title,home 等等)。在 Dart 2.0 中,调用构造函数时关键字 new
可以省略,如果不省略(即例子中 MaterialApp/Scaffold/AppBar/Text/Center 前面都增加一个 new
关键字),则看起来很繁琐。
MaterialApp 的构造函数还有很多其它参数,通过设置它们,可以改变其行为。例如指定参数 theme
可改变 MaterialApp 的主题颜色(如红色):
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( child: Text('Hello World'), ), ), theme: ThemeData( // 和前面相比,增加了从本行开始往下的三行代码 primaryColor: Colors.red, ), ); } }
执行上面代码,得到图 3 所示程序,它的主题颜色改为了红色(不再是默认的蓝色)。
Figure 3: Hello World(主题颜色改为了红色)
3. Widgets
Flutter 中,几乎一切都是 Widget,Widget 采用现代“响应式”框架构建,借鉴于前端框架 React。其中心思想是用 Widget 构建你的 UI。 Widget 描述了他们的视图在给定其当前配置和状态时应该看起来像什么,当 Widget 的状态发生变化时,Widget 会重新构建 UI,Flutter 会对比前后变化的不同,以确定底层渲染树从一个状态转换到下一个状态所需的最小更改(注:类似于 React/Vue 中虚拟 DOM 的 diff 算法)。
当我们编写应用程序时,往往是创建新的 Widget,它继承于 StatelessWidget(无状态组件)或者 StatefulWidget(有状态组件,即可以包含变化的数据)。Widget 的主要工作都在其 build()
函数中,在这个函数中定义其所包含的子组件。
StatelessWidget 的使用实例可以参考前面介绍的 Hello World,下面将介绍 StatefulWidget 的使用实例。
3.1. StatefulWidget
Flutter 中用 StatefulWidget 来表示“有状态组件”。下面就是使用命令 flutter create myapp
创建的项目的主要源码:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); 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.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // 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 is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. 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. ); } }
MyHomePage
有状态,它继承于 StatefulWidget。你可能会奇怪,为什么 StatefulWidget
(MyHomePage
) 和 State
(_MyHomePageState
) 被分为两个组件。 在 Flutter 中, StatefulWidget
和 State
具有不同的生命周期, Widget 是临时对象,用于构建应用程序的当前展示形式;而 State 对象是持久的,其数据在其 build()
方法多次调用之间不会丢失。
对状态的修改必须在 setState()
中进行,随后 build()
会自动执行。Flutter 中的状态控制如图 4 所示。
Figure 4: State Lifecycle
4. Layouts
前面提到过,在 Flutter 中,几乎一切都是 Widget,布局类也是 Widget。
Figure 5: 3 icons with a label under each one
Figure 6: 3 icons with a label under each one
其相应的 Widget 树如图 7 所示。
Figure 7: Diagram of the widget tree
Container 是一个重要的布局类,支持 padding, margins, borders, or background color 等等。