- 设置crossAxisAlignment属性值为CrossAxisAlignment.start,这会将该列中的子项左对齐。
- 获取主题色:Theme.of(context).primaryColor;
// The theme depends on the BuildContext because different parts of the tree // can have different themes. The BuildContext indicates where the build is // taking place and therefore which theme to use.
- 在行的主轴方向通过
MainAxisAlignment.spaceEvenly
平均的分配每个列占据的行空间 softwrap
属性表示文本是否应在软换行符(例如句点或逗号)之间断开。- 将Column(列)放入Expanded中会拉伸该列以使用该行中的所有剩余空闲空间。
- Container也是一个widget,允许您自定义其子widget。如果要添加填充,边距,边框或背景色,请使用Container来设置
- Flutter布局机制的核心就是widget。在Flutter中,几乎所有东西都是一个widget - 甚至布局模型都是widget。您在Flutter应用中看到的图像、图标和文本都是widget。 甚至你看不到的东西也是widget,例如行(row)、列(column)以及用来排列、约束和对齐这些可见widget的网格(grid)。
- 对于Material应用程序,您可以将widget直接添加到
body
属性中;(Scafford等) - 默认情况下,非Material应用程序不包含AppBar,标题或背景颜色。 如果您想在非Material应用程序中使用这些功能,您必须自己构建它们。此应用程序将背景颜色更改为白色,将文本更改为深灰色以模仿Material应用程序。
- 您可以控制行或列如何使用
mainAxisAlignment
和crossAxisAlignment
属性来对齐其子项。 对于行(Row)来说,主轴是水平方向,横轴垂直方向。对于列(Column)来说,主轴垂直方向,横轴水平方向。 - Expanded widget具有一个flex属性,它是一个整数,用于确定widget的弹性系数,默认弹性系数是1。
- 默认情况下,行或列沿着其主轴会尽可能占用尽可能多的空间,但如果要将孩子紧密聚集在一起,可以将mainAxisSize设置为MainAxisSize.min。显示效果 --> [ **** ]
// DefaultTextStyle.merge可以允许您创建一个默认的文本样式,该样式会被其
// 所有的子节点继承
var iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: new Container(
padding: new EdgeInsets.all(20.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new Column(
children: [
new Icon(Icons.kitchen, color: Colors.green[500]),
new Text('PREP:'),
new Text('25 min'),
],
),
new Column(
children: [
new Icon(Icons.timer, color: Colors.green[500]),
new Text('COOK:'),
new Text('1 hr'),
],
),
new Column(
children: [
new Icon(Icons.restaurant, color: Colors.green[500]),
new Text('FEEDS:'),
new Text('4-6'),
],
),
],
),
- widget分为两类:widgets library中的标准widget和Material Components library中的专用widget 。 任何应用程序都可以使用widgets library中的widget,但只有Material应用程序可以使用Material Components库。
- 以使用SizedBox来限制Card的大小
- Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改(译者语:类似于React/Vue中虚拟DOM的diff算法)。
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
该runApp函数接受给定的Widget并使其成为widget树的根。 在此示例中,widget树由两个widget:Center(及其子widget)和Text组成。框架强制根widget覆盖整个屏幕,这意味着文本“Hello, world”会居中显示在屏幕上。
- 一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为
RenderObject
,它会计算并描述widget的几何形状。 Stack
允许子 widget 堆叠, 你可以使用Positioned
来定位他们相对于Stack
的上下左右四条边的位置。- container 可以装饰为一个
BoxDecoration
, 如 background、一个边框、或者一个阴影。Container
也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外,Container
可以使用矩阵在三维空间中对其进行变换。 - 请确保在pubspec.yaml文件中,将
flutter
的值设置为:uses-material-design: true
。这允许我们可以使用一组预定义Material icons。
name: my_app
flutter:
uses-material-design: true
- 每个Flutter应用必须有一个main函数,作为入口;
- Material应用程序以
MaterialApp
widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator
, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator
可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp
完全是可选的,但是使用它是一个很好的做法。 - 许多widget都会使用一个
GestureDetector
为其他widget提供可选的回调。 例如,IconButton
、RaisedButton
、 和FloatingActionButton
,它们都有一个onPressed
回调,它会在用户点击该widget时被触发。 - 无状态widget从它们的父widget接收参数, 它们被存储在
final
型的成员变量中。 当一个widget被要求构建时,它使用这些存储的值作为参数来构建widget。
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
- 为了更有趣的方式对用户输入做出反应 - 应用程序通常会携带一些状态。 Flutter使用StatefulWidgets来满足这种需求。StatefulWidgets是特殊的widget,它知道如何生成State对象,然后用它来保持状态。
- 和RN的方法类似,Flutter也是通过setState方法,通知FrameWork进行页面的重新渲染&刷新; - StatefulWidgets;
- 为什么StatefulWidget和State是单独的对象。
在Flutter中,这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。
- 在Flutter中,事件流是“向上”传递的,而状态流是“向下”传递的(译者语:这类似于React/Vue中父子组件通信的方式:子widget到父widget是通过事件通信,而父到子是通过状态),重定向这一流程的共同父元素是State。
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: onPressed,
child: new Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
- Flutter框架会将新构建的widget与先前构建的widget进行比较,并仅将差异部分应用于底层
RenderObject
。 -
// The framework calls createState the first time a widget appears at a given // location in the tree. If the parent rebuilds and uses the same type of // widget (with the same key), the framework will re-use the State object // instead of creating a new State object.
-
在StatefulWidget调用
createState
之后,框架将新的状态对象插入树中,然后调用状态对象的initState
。 子类化State可以重写initState
,以完成仅需要执行一次的工作。 例如,您可以重写initState
以配置动画或订阅platform services。initState
的实现中需要调用super.initState
。当一个状态对象不再需要时,框架调用状态对象的
dispose
。 您可以覆盖该dispose
方法来执行清理工作。例如,您可以覆盖dispose
取消定时器或取消订阅platform services。dispose
典型的实现是直接调用super.dispose
。 -
您可以使用key来控制框架将在widget重建时与哪些其他widget匹配。默认情况下,框架根据它们的
runtimeType
和它们的显示顺序来匹配。 使用key
时,框架要求两个widget具有相同的key
和runtimeType
。Key在构建相同类型widget的多个实例时很有用。
-
stateless widget 没有内部状态. Icon、 IconButton, 和Text 都是无状态widget, 他们都是 StatelessWidget的子类。
stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新). Checkbox, Radio, Slider,InkWell, Form, and TextField 都是 stateful widgets, 他们都是 StatefulWidget的子类。
-
StatefulWidget:当widget的状态改变时,状态对象调用
setState()
,告诉框架重绘widget。 -
以下是管理状态的最常见的方法:
-
如何决定使用哪种管理方法?以下原则可以帮助您决定:
- 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父widget管理
- 如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由widget本身来管理.
如果有疑问,首选是在父widget中管理状态。
// ParentWidget 为 TapboxB 管理状态.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
-
在Android中,View是屏幕上显示的所有内容的基础, 按钮、工具栏、输入框等一切都是View。 在Flutter中,View相当于是Widget。然而,与View相比,Widget有一些不同之处。 首先,Widget仅支持一帧,并且在每一帧上,Flutter的框架都会创建一个Widget实例树(译者语:相当于一次性绘制整个界面)。 相比之下,在Android上View绘制结束后,就不会重绘,直到调用invalidate时才会重绘。
与Android的视图层次系统不同(在framework改变视图),而在Flutter中的widget是不可变的,这允许widget变得超级轻量。
-
这里要注意的重要一点是无状态和有状态 widget的核心特性是相同的。每一帧它们都会重新构建,不同之处在于StatefulWidget有一个State对象,它可以跨帧存储状态数据并恢复它。
-
记住这个规则:如果一个widget发生了变化(例如用户与它交互),它就是有状态的。但是,如果一个子widget对变化做出反应,而其父widget对变化没有反应,那么包含的父widget仍然可以是无状态的widget。
-
在Android中,您通过XML编写布局,但在Flutter中,您可以使用widget树来编写布局。
-
在Android中,您可以从父级控件调用addChild或removeChild以动态添加或删除View。 在Flutter中,因为widget是不可变的,所以没有addChild。相反,您可以传入一个函数,该函数返回一个widget给父项,并通过布尔值控制该widget的创建。
_getToggleChild() { if (toggle) { return new Text('Toggle One'); } else { return new MaterialButton(onPressed: () {}, child: new Text('Toggle Two')); } }
- React Native:【Es5/6 + React(理解JSX/State/props)】;Flutter:【Dart + 一切皆Widget】;两者有相似之处;
-
Flutter有两个类可以帮助您绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。
-
在Android中,您通常会继承View或已经存在的某个控件,然后覆盖其绘制方法来实现自定义View。
在Flutter中,一个自定义widget通常是通过组合其它widget来实现的,而不是继承。
-
Intent在Flutter中等价于什么?Flutter不具有Intents的概念,但如果需要的话,Flutter可以通过Native整合来触发Intents。
要在Flutter中切换屏幕,您可以访问路由以绘制新的Widget。 管理多个屏幕有两个核心概念和类:Route 和 Navigator。Route是应用程序的“屏幕”或“页面”的抽象(可以认为是Activity), Navigator是管理Route的Widget。Navigator可以通过push和pop route以实现页面切换。和Android相似,您可以在AndroidManifest.xml中声明您的Activities,在Flutter中,您可以将具有指定Route的Map传递到顶层MaterialApp实例;
void main() { runApp(new MaterialApp( home: new MyAppHome(), // becomes the route named '/' routes: <String, WidgetBuilder> { '/a': (BuildContext context) => new MyPage(title: 'page A'), '/b': (BuildContext context) => new MyPage(title: 'page B'), '/c': (BuildContext context) => new MyPage(title: 'page C'), }, )); }
然后,您可以通过Navigator来切换到命名路由的页面。
Navigator.of(context).pushNamed('/b');
- Dart是单线程执行模型,支持Isolates(在另一个线程上运行Dart代码的方式)、事件循环和异步编程。 除非您启动一个Isolate,否则您的Dart代码将在主UI线程中运行,并由事件循环驱动(译者语:和JavaScript一样)。例如,您可以在UI线程上运行网络请求代码而不会导致UI挂起(译者语:因为网络请求是异步的):
loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = JSON.decode(response.body); }); } 要更新UI,您可以调用setState,这会触发build方法再次运行并更新数据。 由于Flutter是单线程的,运行一个事件循环(如Node.js),所以您不必担心线程管理或者使用AsyncTasks、IntentServices。 要异步运行代码,可以将函数声明为异步函数,并在该函数中等待这个耗时任务
-
在Android上,当您继承AsyncTask时,通常会覆盖3个方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中没有这种模式的等价物,因为您只需等待一个长时间运行的函数,而Dart的事件循环将负责其余的事情。
但是,有时您可能需要处理大量数据,导致UI可能会挂起。
在这种情况下,与AsyncTask一样,在Flutter中,可以利用多个CPU内核来执行耗时或计算密集型任务。这是通过使用Isolates来完成的。
是一个独立的执行线程,它运行时不会与主线程共享任何内存。这意味着你不能从该线程访问变量或通过调用setState来更新你的UI。
-
okhttp在Flutter中的等价物是什么?“http”package - https://pub.dartlang.org/packages/http ,虽然“http” package 没有实现OkHttp的所有功能,但“http” package 抽象出了许多常用的API,可以简单有效的发起网络请求。可以通过dart:convert库进行JSON的解析;
-
在Android中,您可以在Gradle文件来添加依赖项。
在Flutter中,虽然在Flutter项目中的Android文件夹下有Gradle文件,但只有在添加平台相关所需的依赖关系时才使用这些文件。 否则,应该使用pubspec.yaml声明用于Flutter的外部依赖项。
-
在Android中,您可以覆盖Activity的方法来捕获Activity的生命周期回调。
在Flutter中您可以通过挂接到WidgetsBinding观察并监听didChangeAppLifecycleState更改事件来监听生命周期事件
您可以监听到的生命周期事件是
- resumed - 应用程序可见并响应用户输入。这是来自Android的onResume
- inactive - 应用程序处于非活动状态,并且未接收用户输入。此事件在Android上未使用,仅适用于iOS
- paused - 应用程序当前对用户不可见,不响应用户输入,并在后台运行。这是来自Android的暂停
- suspending - 该应用程序将暂时中止。这在iOS上未使用
- 开发依赖项是不包含在我们的应用程序源代码中的依赖项。
-
技术类型 UI渲染方式 性能 开发效率 动态化 框架代表 H5+原生 WebView渲染 一般 高 ✔️ Cordova、Ionic JavaScript+原生渲染 原生控件渲染 好 高 ✔️ RN、Weex 自绘UI+原生 调用系统API渲染 好 Flutter高, QT低 默认不支持 QT、Flutter 上表中开发语言主要指UI的开发语言,动态化主要指是否支持动态下发代码和是否支持热更新。值得注意的是Flutter的Release包默认是使用Dart AOT模式编译的,所以不支持动态化,但Dart还有JIT或snapshot运行方式,这些模式都是支持动态化的
-
Flutter:Skia + 基于JIT的快速开发周期 + 基于AOT的发布包;
-
可以简单认为Stateful widget 和Stateless widget有两点不同:
Stateful widget可以拥有状态,这些状态在widget生命周期中是可以变的,而Stateless widget是不可变的。
Stateful widget至少由两个类组成:
- 一个
StatefulWidget
类。 - 一个 State类;
StatefulWidget
类本身是不变的,但是 State类中持有的状态在widget生命周期中可能会发生变化。
setState
方法的作用是通知Flutter框架,有状态发生了改变,Flutter框架收到通知后,会执行build
方法来根据新的状态重新构建界面, Flutter 对此方法做了优化,使重新执行变的部分,所以你可以重新构建任何需要更新的东西,而无需分别去修改各个widget。- 路由相关
Navigator.push( context,
new MaterialPageRoute(builder: (context) {
return new NewRoute();
}));
},
)
MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute 是Material组件库的一个Widget,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画。
MaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
})
Navigator是一个路由管理的widget,它通过一个栈来管理一个路由widget集合。
- Future push(BuildContext context, Route route)
将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。【可以理解为Android中的startActivityForResult】
- bool pop(BuildContext context, [ result ])
将栈顶路由出栈,result为页面关闭时返回给上一个页面的数据。
- 命名路由:即给路由起一个名字,然后可以通过路由名字直接打开新的路由。
要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名称与哪个路由Widget对应。路由表的定义如下:
Map<String, WidgetBuilder> routes;
示例:
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
//注册路由表
routes:{
"new_page":(context)=>NewRoute(),
} ,
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
通过pushNamed进行路由跳转:
Future pushNamed(BuildContext context, String routeName,{Object arguments})
示例:
- 路由,并传递参数
Navigator.of(context).pushNamed("new_page", arguments: "hi");
- 获取参数
var args=ModalRoute.of(context).settings.arguments
- pubspec.yaml相关配置说明
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
dependencies:应用或包依赖的其它包或插件。
dev_dependencies:开发环境依赖的工具包(而不是flutter应用本身依赖的包)。
flutter:flutter相关的配置选项。
--------------------
除了可以添加最常用的pub仓库依赖,还可以添加本地及git依赖
--------------------
flutter:
assets:
- images/my_icon.png
- images/background.png
assets指定应包含在应用程序中的文件。 每个asset都通过相对于pubspec.yaml文件所在位置的显式路径进行标识。asset的声明顺序是无关紧要的。asset的实际目录可以是任意文件夹(在本示例中是images)。
- Dart异常捕获
Flutter 框架为我们在很多关键的方法进行了异常捕获。
如果我们想上报异常,只需要提供一个自定义的错误处理回调即可,如:
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details);
};
...
}
这样我们就可以处理那些Flutter为我们捕获的异常了。
在Flutter中,还有一些Flutter没有为我们捕获的异常,如调用空对象方法异常、Future中的异常。在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch
捕获,而异步异常则比较麻烦,如下面的代码是捕获不了Future
的异常的:
try{
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
print(e)
}
Dart中有一个runZoned(...)
方法,可以给执行对象指定一个Zone。Zone表示一个代码执行的环境范围,为了方便理解,读者可以将Zone类比为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常。
R runZoned<R>(R body(), {
Map zoneValues, // Zone 的私有数据,可以通过实例zone[key]获取,可以理解为每个“沙箱”的私有数据。
ZoneSpecification zoneSpecification,// Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出行为等
Function onError,// Zone中未捕获异常处理回调,如果开发者提供了onError回调或者通过ZoneSpecification.handleUncaughtError指定了错误处理回调,那么这个zone将会变成一个error-zone,该error-zone中发生未捕获异常(无论同步还是异步)时都会调用开发者提供的回调
})
这样一来,结合上面的FlutterError.onError
我们就可以捕获我们Flutter应用中全部错误了!需要注意的是,error-zone内部发生的错误是不会跨越当前error-zone的边界的,如果想跨越error-zone边界去捕获异常,可以通过共同的“源”zone来捕获,如:
var future = new Future.value(499);
runZoned(() {
var future2 = future.then((_) { throw "error in first error-zone"; });
runZoned(() {
var future3 = future2.catchError((e) { print("Never reached!"); });
}, onError: (e) { print("unused error handler"); });
}, onError: (e) { print("catches error of first error-zone."); });