// 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
属性表示文本是否应在软换行符(例如句点或逗号)之间断开。body
属性中;(Scafford等)mainAxisAlignment
和crossAxisAlignment
属性来对齐其子项。 对于行(Row)来说,主轴是水平方向,横轴垂直方向。对于列(Column)来说,主轴垂直方向,横轴水平方向。 // 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'),
],
),
],
),
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
该runApp函数接受给定的Widget并使其成为widget树的根。 在此示例中,widget树由两个widget:Center(及其子widget)和Text组成。框架强制根widget覆盖整个屏幕,这意味着文本“Hello, world”会居中显示在屏幕上。
RenderObject
,它会计算并描述widget的几何形状。Stack
允许子 widget 堆叠, 你可以使用 Positioned
来定位他们相对于Stack
的上下左右四条边的位置。BoxDecoration
, 如 background、一个边框、或者一个阴影。 Container
也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container
可以使用矩阵在三维空间中对其进行变换。flutter
的值设置为:uses-material-design: true
。这允许我们可以使用一组预定义Material icons。name: my_app
flutter:
uses-material-design: true
MaterialApp
widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator
, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator
可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp
完全是可选的,但是使用它是一个很好的做法。GestureDetector
为其他widget提供可选的回调。 例如,IconButton
、 RaisedButton
、 和FloatingActionButton
,它们都有一个onPressed
回调,它会在用户点击该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中,这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。
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 {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children: [
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
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中管理状态。
// ParentWidget 为 TapboxB 管理状态.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State {
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 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'));
}
}
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: {
'/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');
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更改事件来监听生命周期事件
您可以监听到的生命周期事件是
技术类型 | 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
类。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 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
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)。
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 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."); });