iOS开发者学习Flutter

Flutter for iOS 开发者

本文档适用那些但愿将现有 iOS 经验应用于 Flutter 的开发者。若是你拥有 iOS 开发基础,那么你可使用这篇文档开始学习 Flutter 的开发。javascript

开发 Flutter 时,你的 iOS 经验和技能将会大有裨益,由于 Flutter 依赖于移动操做系统的众多功能和配置。Flutter 是用于为移动设备构建用户界面的全新方式,但它也有一个插件系统用于和 iOS(及 Android)进行非 UI 任务的通讯。若是你是 iOS 开发专家,则你没必要将 Flutter 完全从新学习一遍。html

你能够将此文档做为 cookbook,经过跳转并查找与你的需求最相关的问题。java

Views

UIView 至关于 Flutter 中的什么?

在 iOS 中,构建 UI 的过程当中将大量使用 view 对象。这些对象都是 UIView 的实例。它们能够用做容器来承载其余的 UIView,最终构成你的界面布局。ios

在 Flutter 中,你能够粗略地认为 Widget 至关于 UIView 。Widget 和 iOS 中的控件并不彻底等价,但当你试图去理解 Flutter 是如何工做的时候,你能够认为它们是“声明和构建 UI 的方法”。算法

然而,Widget 和 UIView 仍是有些区别的。首先,widgets 拥有不一样的生存时间:它们一直存在且保持不变,直到当它们须要被改变。当 widgets 和它们的状态被改变时,Flutter 会构建一颗新的 widgets 树。做为对比,iOS 中的 views 在改变时并不会被从新建立。可是与其说 views 是可变的实例,不如说它们被绘制了一次,而且直到使用 setNeedsDisplay() 以后才会被从新绘制。数据库

此外,不像 UIView,因为不可变性,Flutter 的 widgets 很是轻量。这是由于它们自己并非什么控件,也不会被直接绘制出什么,而只是 UI 的描述。编程

Flutter 包含了 Material 组件库。这些 widgets 遵循了 Material 设计规范。MD 是一个灵活的设计系统,而且为包括 iOS 在内的全部系统进行了优化json

可是用 Flutter 实现任何的设计语言都很是的灵活和富有表现力。在 iOS 平台,你可使用 Cupertino widgets 来构建遵循了 Apple’s iOS design language 的界面。canvas

我怎么来更新 Widgets?

在 iOS 上更新 views,只须要直接改变它们就能够了。在 Flutter 中,widgets 是不可变的,并且不能被直接更新。你须要去操纵 widget 的 state。安全

这也正是有状态的和无状态的 widget 这一律念的来源。一个 StatelessWidget 正如它听起来同样,是一个没有附加状态的 widget。

StatelessWidget 在你构建初始化后再也不进行改变的界面时很是有用。

举个例子,你可能会用一个 UIImageView 来展现你的 logo image 。若是这个 logo 在运行时不会改变,那么你就能够在 Flutter 中使用 StatelessWidget 。

若是你但愿在发起 HTTP 请求时,依托接收到的数据动态的改变 UI,请使用 StatefulWidget。当 HTTP 请求结束后,通知 Flutter 框架 widget 的 State 更新了,好让系统来更新 UI。

有状态和无状态的 widget 之间一个很是重要的区别是,StatefulWidget 拥有一个 State 对象来存储它的状态数据,并在 widget 树重建时携带着它,所以状态不会丢失。

若是你有疑惑,请记住如下规则:若是一个 widget 在它的 build 方法以外改变(例如,在运行时因为用户的操做而改变),它就是有状态的。若是一个 widget 在一次 build 以后永远不变,那它就是无状态的。可是,即使一个 widget 是有状态的,包含它的父亲 widget 也能够是无状态的,只要父 widget 自己不响应这些变化。

下面的例子展现了如何使用一个 StatelessWidget 。一个常见的 StatelessWidget 是 Textwidget。若是你查看 Text 的实现,你会发现它是 StatelessWidget 的子类。

Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), );

阅读上面的代码,你可能会注意到 Text widget 并不显示地携带任何状态。它经过传入给它的构造器的数据来渲染,除此以外再无其余。

可是,若是你但愿 I like Flutter 在点击 FloatingActionButton 时动态的改变呢?

为了实现这个,用 StatefulWidget 包裹 Text widget,并在用户点击按钮时更新它。

举个例子:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎么对 widget 布局?个人 Storyboard 在哪?

在 iOS 中,你可能会用 Storyboard 文件来组织 views,并对它们设置约束,或者,你可能在 view controller 中使用代码来设置约束。在 Flutter 中,你经过编写一个 widget 树来声明你的布局。

下面这个例子展现了如何展现一个带有 padding 的简单 widget:

你能够给任何的 widget 添加 padding,这很像 iOS 中约束的功能。

你能够在 widget catalog 中查看 Flutter 提供的布局。

我怎么在个人约束中添加或移除组件?

在 iOS 中,你在父 view 中调用 addSubview() 或在子 view 中调用 removeFromSuperview() 来动态地添加或移除子 views。在 Flutter 中,因为 widget 不可变,因此没有和 addSubview() 直接等价的东西。做为替代,你能够向 parent 传入一个返回 widget 的函数,并用一个布尔值来控制子 widget 的建立。

下面这个例子展现了在点击 FloatingActionButton 时如何动态地切换两个 widgets:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎么对 widget 作动画?

在 iOS 中,你经过调用 animate(withDuration:animations:) 方法来给一个 view 建立动画。在 Flutter 中,使用动画库来包裹 widgets,而不是建立一个动画 widget。

在 Flutter 中,使用 AnimationController 。这是一个能够暂停、寻找、中止、反转动画的 Animation<double> 类型。它须要一个 Ticker 当 vsync 发生时来发送信号,而且在每帧运行时建立一个介于 0 和 1 之间的线性插值(interpolation)。你能够建立一个或多个的 Animation 并附加给一个 controller。

例如,你可能会用 CurvedAnimation 来实现一个 interpolated 曲线。在这个场景中,controller 是动画过程的“主人”,而 CurvedAnimation 计算曲线,并替代 controller 默认的线性模式。

当构建 widget 树时,你会把 Animation 指定给一个 widget 的动画属性,好比 FadeTransition 的 opacity,并告诉控制器开始动画。

下面这个例子展现了在点击 FloatingActionButton 以后,如何使用 FadeTransition 来让 widget 淡出到 logo 图标:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

更多信息,请参阅 Animation & Motion widgets, Animations tutorial 以及 Animations overview

我该怎么绘图?

在 iOS 上,你经过 CoreGraphics 来在屏幕上绘制线条和形状。Flutter 有一套基于 Canvas 类的不一样的 API,还有 CustomPaint 和 CustomPainter 这两个类来帮助你绘图。后者实现你在 canvas 上的绘图算法。

想要学习如何实现一个笔迹画笔,请参考 Collin 在 StackOverflow 上的回答。

class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List<Offset> points; void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], paint); } } bool shouldRepaint(SignaturePainter other) => other.points != points; } class Signature extends StatefulWidget { SignatureState createState() => SignatureState(); } class SignatureState extends State<Signature> { List<Offset> _points = <Offset>[]; Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite), ); } } 

Widget 的透明度在哪里?

在 iOS 中,什么东西都会有一个 .opacity 或是 .alpha 的属性。在 Flutter 中,你须要给 widget 包裹一个 Opacity widget 来作到这一点。

我怎么建立自定义的 widgets?

在 iOS 中,你编写 UIView 的子类,或使用已经存在的 view 来重载并实现方法,以达到特定的功能。在 Flutter 中,你会组合(composing)多个小的 widgets 来构建一个自定义的 widget(而不是扩展它)。

举个例子,若是你要构建一个 CustomButton ,并在构造器中传入它的 label?那就组合 RaisedButton 和 label,而不是扩展 RaisedButton

class CustomButton extends StatelessWidget { final String label; CustomButton(this.label); 

而后就像你使用其余任何 Flutter 的 widget 同样,使用你的 CustomButton:

导航

我怎么在不一样页面之间跳转?

在 iOS 中,你可使用管理了 view controller 栈的 UINavigationController 来在不一样的 view controller 之间跳转。

Flutter 也有相似的实现,使用了 Navigator 和 Routes。一个路由是 App 中“屏幕”或“页面”的抽象,而一个 Navigator 是管理多个路由的 widget 。你能够粗略地把一个路由对应到一个 UIViewController。Navigator 的工做原理和 iOS 中 UINavigationController 很是类似,当你想跳转到新页面或者重新页面返回时,它能够 push() 和 pop() 路由。

在页面之间跳转,你有一对选择:

  • 具体指定一个由路由名构成的 Map。(MaterialApp)
  • 直接跳转到一个路由。(WidgetApp)

下面是构建一个 Map 的例子:

void main() { runApp(MaterialApp( home: MyAppHome(), // becomes the route named '/' routes: <String, WidgetBuilder> { '/a': (BuildContext context) => MyPage(title: 'page A'), '/b': (BuildContext context) => MyPage(title: 'page B'), '/c': (BuildContext context) => MyPage(title: 'page C'), }, )); } 

经过把路由的名字 push 给一个 Navigator 来跳转:

Navigator.of(context).pushNamed('/b');

Navigator 类不只用来处理 Flutter 中的路由,还被用来获取你刚 push 到栈中的路由返回的结果。经过 await等待路由返回的结果来达到这点。

举个例子,要跳转到“位置”路由来让用户选择一个地点,你可能要这么作:

Map coordinates = await Navigator.of(context).pushNamed('/location');

以后,在 location 路由中,一旦用户选择了地点,携带结果一块儿 pop() 出栈:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

我怎么跳转到其余 App?

在 iOS 中,要跳转到其余 App,你须要一个特定的 URL Scheme。对系统级别的 App 来讲,这个 scheme 取决于 App。为了在 Flutter 中实现这个功能,你能够建立一个原平生台的整合层,或者使用现有的 plugin,例如 url_launcher

线程和异步

我怎么编写异步的代码?

Dart 是单线程执行模型,可是它支持 Isolate(一种让 Dart 代码运行在其余线程的方式)、事件循环和异步编程。除非你本身建立一个 Isolate ,不然你的 Dart 代码永远运行在 UI 线程,并由 event loop 驱动。Flutter 的 event loop 和 iOS 中的 main loop 类似——Looper 是附加在主线程上的。

Dart 的单线程模型并不意味着你写的代码必定是阻塞操做,从而卡住 UI。相反,使用 Dart 语言提供的异步工具,例如 async / await ,来实现异步操做。

举个例子,你可使用 async / await 来让 Dart 帮你作一些繁重的工做,编写网络请求代码而不会挂起 UI:

loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } 

一旦 await 到网络请求完成,经过调用 setState() 来更新 UI,这会触发 widget 子树的重建,并更新相关数据。

下面的例子展现了异步加载数据,并用 ListView 展现出来:

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

更多关于在后台工做的信息,以及 Flutter 和 iOS 的区别,请参考下一章节。

你是怎么把工做放到后台线程的?

因为 Flutter 是单线程而且跑着一个 event loop 的(就像 Node.js 那样),你没必要为线程管理或是开启后台线程而操心。若是你正在作 I/O 操做,如访问磁盘或网络请求,安全地使用 async / await 就完事了。若是,在另外的状况下,你须要作让 CPU 执行繁忙的计算密集型任务,你须要使用 Isolate来避免阻塞 event loop。

对于 I/O 操做,经过关键字 async,把方法声明为异步方法,而后经过await关键字等待该异步方法执行完成(译者语:这和javascript中是相同的):

loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } 

这就是对诸如网络请求或数据库访问等 I/O 操做的典型作法。

然而,有时候你须要处理大量的数据,这会致使你的 UI 挂起。在 Flutter 中,使用 Isolate 来发挥多核心 CPU 的优点来处理那些长期运行或是计算密集型的任务。

Isolates 是分离的运行线程,而且不和主线程的内存堆共享内存。这意味着你不能访问主线程中的变量,或者使用 setState() 来更新 UI。正如它们的名字同样,Isolates 不能共享内存。

下面的例子展现了一个简单的 isolate,是如何把数据返回给主线程来更新 UI 的:

loadData() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message SendPort sendPort = await receivePort.first; List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts"); setState(() { widgets = msg; }); } // The entry point for the isolate static dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (var msg in port) { String data = msg[0]; SendPort replyTo = msg[1]; String dataURL = data; http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(json.decode(response.body)); } } Future sendReceive(SendPort port, msg) { ReceivePort response = ReceivePort(); port.send([msg, response.sendPort]); return response.first; } 

这里,dataLoader() 是一个运行于本身独立执行线程上的 Isolate。在 isolate 里,你能够执行 CPU 密集型任务(例如解析一个庞大的 json),或是计算密集型的数学操做,如加密或信号处理等。

你能够运行下面的完整例子:

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:async'; import 'dart:isolate'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

我怎么发起网络请求?

在 Flutter 中,使用流行的 http package 作网络请求很是简单。它把你可能须要本身作的网络请求操做抽象了出来,让发起请求变得简单。

要使用 http 包,在 pubspec.yaml 中把它添加为依赖:

dependencies:
  ...
  http: ^0.11.3+16

发起网络请求,在 http.get() 这个 async 方法中使用 await :

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; [...] loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } } 

我怎么展现一个长时间运行的任务的进度?

在 iOS 中,在后台运行耗时任务时你会使用 UIProgressView

在 Flutter 中,使用一个 ProgressIndicator widget。经过一个布尔 flag 来控制是否展现进度。在任务开始时,告诉 Flutter 更新状态,并在结束后隐去。

在下面的例子中,build 函数被拆分红三个函数。若是 showLoadingDialog() 是 true (当 widgets.length == 0 时),则渲染 ProgressIndicator。不然,当数据从网络请求中返回时,渲染 ListView 。

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

工程结构、本地化、依赖和资源

我怎么在 Flutter 中引入 image assets?多分辨率怎么办?

iOS 把 images 和 assets 做为不一样的东西,而 Flutter 中只有 assets。被放到 iOS 中 Images.xcasset 文件夹下的资源在 Flutter 中被放到了 assets 文件夹中。assets 能够是任意类型的文件,而不只仅是图片。例如,你能够把 json 文件放置到 my-assets 文件夹中。

my-assets/data.json

在 pubspec.yaml 文件中声明 assets:

assets:
 - my-assets/data.json

而后在代码中使用 AssetBundle 来访问它:

import 'dart:async' show Future; import 'package:flutter/services.dart' show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString('my-assets/data.json'); } 

对于图片,Flutter 像 iOS 同样,遵循了一个简单的基于像素密度的格式。Image assets 多是 1.0x2.0x 3.0x 或是其余的任何倍数。这些所谓的 devicePixelRatio 传达了物理像素到单个逻辑像素的比率。

Assets 能够被放置到任何属性文件夹中——Flutter 并无预先定义的文件结构。在 pubspec.yaml文件中声明 assets (和位置),而后 Flutter 会把他们识别出来。

举个例子,要把一个叫 my_icon.png 的图片放到 Flutter 工程中,你可能想要把存储它的文件夹叫作 images。把基础图片(1.0x)放置到 images 文件夹中,并把其余变体放置在子文件夹中,并接上合适的比例系数:

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

接着,在 pubspec.yaml 文件夹中声明这些图片:

assets:
 - images/my_icon.jpeg

你能够用 AssetImage 来访问这些图片:

return AssetImage("images/a_dot_burr.jpeg");

或者在 Image widget 中直接使用:

更多细节,参见 Adding Assets and Images in Flutter

我在哪里放置字符串?我怎么作本地化?

不像 iOS 拥有一个 Localizable.strings 文件,Flutter 目前并无一个用于处理字符串的系统。目前,最佳实践是把你的文本拷贝到静态区,并在这里访问。例如:

class Strings { static String welcomeMessage = "Welcome To Flutter"; } 

而且这样访问你的字符串:

Text(Strings.welcomeMessage) 

默认状况下,Flutter 只支持美式英语字符串。若是你要支持其余语言,请引入 flutter_localizations 包。你可能也要引入  intl 包来支持其余的 i10n 机制,好比日期/时间格式化。

dependencies:
  # ...
  flutter_localizations:
    sdk: flutter
  intl: "^0.15.6"

要使用 flutter_localizations 包,还须要在 app widget 中指定 localizationsDelegates 和 supportedLocales

import 'package:flutter_localizations/flutter_localizations.dart'; MaterialApp( localizationsDelegates: [ // Add app-specific localization delegate[s] here GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ const Locale('en', 'US'), // English const Locale('he', 'IL'), // Hebrew // ... other locales the app supports ], // ... ) 

这些代理包括了实际的本地化值,而且 supportedLocales 定义了 App 支持哪些地区。上面的例子使用了一个 MaterialApp ,因此它既有 GlobalWidgetsLocalizations 用于基础 widgets,也有 MaterialWidgetsLocalizations 用于 Material wigets 的本地化。若是你使用 WidgetsApp ,则无需包括后者。注意,这两个代理虽然包括了“默认”值,但若是你想让你的 App 本地化,你仍须要提供一或多个代理做为你的 App 本地化副本。

当初始化时,WidgetsApp 或 MaterialApp 会使用你指定的代理为你建立一个  Localizationswidget。Localizations widget 能够随时从当前上下文中访问设备的地点,或者使用 Window.locale

要访问本地化文件,使用 Localizations.of() 方法来访问提供代理的特定本地化类。如需翻译,使用  intl_translation 包来取出翻译副本到 arb 文件中。把它们引入 App 中,并用 intl 来使用它们。

更多 Flutter 中国际化和本地化的细节,请访问 internationalization guide ,那里有不使用 intl 包的示例代码。

注意,在 Flutter 1.0 beta 2 以前,在 Flutter 中定义的 assets 不能在原生一侧被访问。原生定义的资源在 Flutter 中也不可用,由于它们在独立的文件夹中。

Cocoapods 至关于什么?我该如何添加依赖?

在 iOS 中,你把依赖添加到 Podfile 中。Flutter 使用 Dart 构建系统和 Pub 包管理器来处理依赖。这些工具将本机 Android 和 iOS 包装应用程序的构建委派给相应的构建系统。

若是你的 Flutter 工程中的 iOS 文件夹中拥有 Podfile,请仅在你为每一个平台集成时使用它。整体来讲,使用 pubspec.yaml 来在 Flutter 中声明外部依赖。一个能够找到优秀 Flutter 包的地方是 Pub

ViewControllers

ViewController 至关于 Flutter 中的什么?

在 iOS 中,一个 ViewController 表明了用户界面的一部分,最经常使用于一个屏幕,或是其中一部分。它们被组合在一块儿用于构建复杂的用户界面,并帮助你拆分 App 的 UI。在 Flutter 中,这一任务回落到了 widgets 中。就像在界面导航部分提到的同样,一个屏幕也是被 widgets 来表示的,由于“万物皆 widget!”。使用 Navigator 在 Route 之间跳转,或者渲染相同数据的不一样状态。

我该怎么监听 iOS 中的生命周期事件?

在 iOS 中,你能够重写 ViewController 中的方法来补货它的视图的生命周期,或者在 AppDelegate 中注册生命周期的回调函数。在 Flutter 中没有这两个概念,但你能够经过 hook WidgetsBinding 观察者来监听生命周期事件,并监听 didChangeAppLifecycleState() 的变化事件。

可观察的生命周期事件有:

  • inactive - 应用处于不活跃的状态,而且不会接受用户的输入。这个事件仅工做在 iOS 平台,在 Android 上没有等价的事件。
  • paused - 应用暂时对用户不可见,虽然不接受用户输入,可是是在后台运行的。
  • resumed - 应用可见,也响应用户的输入。
  • suspending - 应用暂时被挂起,在 iOS 上没有这一事件。

更多关于这些状态的细节和含义,请参见  AppLifecycleStatus documentation 。

布局

UITableView 和 UICollectionView 至关于 Flutter 中的什么?

在 iOS 中,你可能用 UITableView 或 UICollectionView 来展现一个列表。在 Flutter 中,你能够用 ListView 来达到类似的实现。在 iOS 中,你经过代理方法来肯定行数,每个 index path 的单元格,以及单元格的尺寸。

因为 Flutter 中 widget 的不可变特性,你须要向 ListView 传递一个 widget 列表,Flutter 会确保滚动是快速且流畅的。

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎么知道列表的哪一个元素被点击了?

iOS 中,你经过 tableView:didSelectRowAtIndexPath: 代理方法来实现。在 Flutter 中,使用传递进来的 widget 的 touch handle:

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎么动态地更新 ListView?

在 iOS 中,你改变列表的数据,并经过 reloadData() 方法来通知 table 或是 collection view。

在 Flutter 中,若是你想经过 setState() 方法来更新 widget 列表,你会很快发现你的数据展现并无变化。这是由于当 setState() 被调用时,Flutter 渲染引擎会去检查 widget 树来查看是否有什么地方被改变了。当它获得你的 ListView 时,它会使用一个 == 判断,而且发现两个 ListView 是相同的。没有什么东西是变了的,所以更新不是必须的。

一个更新 ListView 的简单方法是,在 setState() 中建立一个新的 list,并把旧 list 的数据拷贝给新的 list。虽然这样很简单,但当数据集很大时,并不推荐这样作:

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

一个推荐的、高效的且有效的作法是,使用 ListView.Builder 来构建列表。这个方法在你想要构建动态列表,或是列表拥有大量数据时会很是好用。

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

与建立一个 “ListView” 不一样,建立一个 ListView.builder 接受两个主要参数:列表的初始长度,和一个 ItemBuilder 方法。

ItemBuilder 方法和 cellForItemAt 代理方法很是相似,它接受一个位置,而且返回在这个位置上你但愿渲染的 cell。

最后,也是最重要的,注意 onTap() 函数里并无从新建立一个 list,而是 .add 了一个 widget。

ScrollView 至关于 Flutter 里的什么?

在 iOS 中,你给 view 包裹上 ScrollView 来容许用户在须要时滚动你的内容。

在 Flutter 中,最简单的方法是使用 ListView widget。它表现得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,由于你能够给它的 widget 作垂直排布:

更多关于在 Flutter 总如何排布 widget 的文档,请参阅 layout tutorial

手势检测及触摸事件处理

我怎么给 Flutter 的 widget 添加一个点击监听者?

在 iOS 中,你给一个 view 添加 GestureRecognizer 来处理点击事件。在 Flutter 中,有两种方法来添加点击监听者:

  1. 若是 widget 自己支持事件监测,直接传递给它一个函数,并在这个函数里实现响应方法。例如,RaisedButton widget 拥有一个 RaisedButton 参数:

  2. 若是 widget 自己不支持事件监测,则在外面包裹一个 GestureDetector,并给它的 onTap 属性传递一个函数:

    class SampleApp extends StatelessWidget { 

我怎么处理 widget 上的其余手势?

使用 GestureDetector 你能够监听更广阔范围内的手势,好比:

  • Tapping
    • onTapDown — 在特定位置轻触手势接触了屏幕。
    • onTapUp — 在特定位置产生了一个轻触手势,并中止接触屏幕。
    • onTap — 产生了一个轻触手势。
    • onTapCancel — 触发了 onTapDown 但没能触发 tap。
  • Double tapping
    • onDoubleTap — 用户在同一个位置快速点击了两下屏幕。
  • Long pressing
    • onLongPress — 用户在同一个位置长时间接触屏幕。
  • Vertical dragging
    • onVerticalDragStart — 接触了屏幕,而且可能会垂直移动。
    • onVerticalDragUpdate — 接触了屏幕,并继续在垂直方向移动。
    • onVerticalDragEnd — 以前接触了屏幕并垂直移动,并在中止接触屏幕前以某个垂直的速度移动。
  • Horizontal dragging
    • onHorizontalDragStart — 接触了屏幕,而且可能会水平移动。
    • onHorizontalDragUpdate — 接触了屏幕,并继续在水平方向移动。
    • onHorizontalDragEnd — 以前接触屏幕并水平移动的触摸点与屏幕分离。

下面这个例子展现了一个 GestureDetector 是如何在双击时旋转 Flutter 的 logo 的:

AnimationController controller; CurvedAnimation curve; 

主题和文字

我怎么给 App 设置主题?

Flutter 实现了一套漂亮的 MD 组件,而且开箱可用。它接管了一大堆你须要的样式和主题。

为了充分发挥你的 App 中 MD 组件的优点,声明一个顶级 widget,MaterialApp,用做你的 App 入口。MaterialApp 是一个便利组件,包含了许多 App 一般须要的 MD 风格组件。它经过一个 WidgetsApp 添加了 MD 功能来实现。

可是 Flutter 足够地灵活和富有表现力来实现任何其余的设计语言。在 iOS 上,你能够用 Cupertino library 来制做遵照  Human Interface Guidelines 的界面。查看这些 widget 的集合,请参阅 Cupertino widgets gallery

你也能够在你的 App 中使用 WidgetApp,它提供了许多类似的功能,但不如 MaterialApp 那样强大。

对任何子组件定义颜色和样式,能够给 MaterialApp widget 传递一个 ThemeData 对象。举个例子,在下面的代码中,primary swatch 被设置为蓝色,而且文字的选中颜色是红色:

class SampleApp extends StatelessWidget { 

我怎么给 Text widget 设置自定义字体?

在 iOS 中,你在项目中引入任意的 ttf 文件,并在 info.plist 中设置引用。在 Flutter 中,在文件夹中放置字体文件,并在 pubspec.yaml 中引用它,就像添加图片那样。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

而后在你的 Text widget 中指定字体:

我怎么给个人 Text widget 设置样式?

除了字体之外,你也能够给 Text widget 的样式元素设置自定义值。Text widget 接受一个  TextStyle 对象,你能够指定许多参数,好比:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

表单输入

Flutter 中表单怎么工做?我怎么拿到用户的输入?

咱们已经提到 Flutter 使用不可变的 widget,而且状态是分离的,你可能会好奇在这种情境下怎么处理用户的输入。在 iOS 中,你常常在须要提交数据时查询组件当前的状态或动做,但这在 Flutter 中是怎么工做的呢?

在表单处理的实践中,就像在 Flutter 中任何其余的地方同样,要经过特定的 widgets。若是你有一个 TextField 或是 TextFormField,你能够经过 TextEditingController 来得到用户输入:

class _MyFormState extends State<MyForm> { // Create a text controller and use it to retrieve the current value. // of the TextField! final myController = TextEditingController(); 

你能够在这里得到更多信息,或是完整的代码列表: Retrieve the value of a text field,来自 Flutter Cookbook 。

Text field 中的 placeholder 至关于什么?

在 Flutter 中,你能够轻易地经过向 Text widget 的装饰构造器参数重传递 InputDecoration 来展现“小提示”,或是占位符文字:

body: Center( child: TextField( decoration: InputDecoration(hintText: "This is a hint"), ), ) 

我怎么展现验证错误信息?

就像展现“小提示”同样,向 Text widget 的装饰器构造器参数中传递一个 InputDecoration

然而,你并不想在一开始就显示错误信息。相反,当用户输入了验证信息,更新状态,并传入一个新的 InputDecoration 对象:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

和硬件、第三方服务以及平台交互

我怎么和平台,以及平台的原生代码交互?

Flutter 的代码并不直接在平台之下运行,相反,Dart 代码构建的 Flutter 应用在设备上以原生的方式运行,却“侧步躲开了”平台提供的 SDK。这意味着,例如,你在 Dart 中发起一个网络请求,它就直接在 Dart 的上下文中运行。你并不会用上日常在 iOS 或 Android 上使用的原生 API。你的 Flutter 程序仍然被原平生台的 ViewController 管理做一个 view,可是你并不会直接访问 ViewController 自身,或是原生框架。

但这并不意味着 Flutter 不能和原生 API,或任何你编写的原生代码交互。Flutter 提供了 platform channels ,来和管理你的 Flutter view 的 ViewController 通讯和交互数据。平台管道本质上是一个异步通讯机制,桥接了 Dart 代码和宿主 ViewController,以及它运行于的 iOS 框架。你能够用平台管道来执行一个原生的函数,或者是从设备的传感器中获取数据。

除了直接使用平台管道以外,你还可使用一系列预先制做好的 plugins。例如,你能够直接使用插件来访问相机胶卷或是设备的摄像头,而没必要编写你本身的集成层代码。你能够在 Pub 上找到插件,这是一个 Dart 和 Flutter 的开源包仓库。其中一些包可能会支持集成 iOS 或 Android,或二者都可。

若是你在 Pub 上找不到符合你需求的插件,你能够本身编写 ,而且发布在 Pub 上

我怎么访问 GPS 传感器?

使用 location 社区插件。

我怎么访问摄像头?

image_picker 在访问摄像头时很是经常使用。

我怎么登陆 Facebook?

登陆 Facebook 可使用 flutter_facebook_login 社区插件。

我怎么使用 Firebase 特性?

大多数 Firebase 特性被  first party plugins 包含了。这些第一方插件由 Flutter 团队维护:

你也能够在 Pub 上找到 Firebase 的第三方插件。

我怎建立本身的原生集成层?

若是有一些 Flutter 和社区插件遗漏的平台相关的特性,能够根据  developing packages and plugins页面构建本身的插件。

Flutter 的插件结构,简要来讲,就像 Android 中的 Event bus。你发送一个消息,并让接受者处理并反馈结果给你。在这种状况下,接受者就是在 Android 或 iOS 上的原生代码。

数据库和本地存储

我怎么在 Flutter 中访问 UserDefaults?

在 iOS 中,你可使用属性列表来存储键值对的集合,即咱们熟悉的 UserDefaults。

在 Flutter 中,可使用  Shared Preferences plugin 来达到类似的功能。它包裹了 UserDefaluts 以及 Android 上等价的 SharedPreferences 的功能。

CoreData 至关于 Flutter 中的什么?

在 iOS 中,你经过 CoreData 来存储结构化的数据。这是一个 SQL 数据库的上层封装,让查询和关联模型变得更加简单。

在 Flutter 中,使用 SQFlite 插件来实现这个功能。

通知

我怎么推送通知?

在 iOS 中,你须要向苹果开发者平台中注册来容许推送通知。

在 Flutter 中,使用 firebase_messaging 插件来实现这一功能。

更多使用 Firebase Cloud Messaging API 的信息,请参阅 firebase_messaging 插件文档。

 
 
文章来源: Flutter中文网
相关文章
相关标签/搜索