如何你懂得Xamarin.Forms框架的基本原理,那么你就可以将本文档当作你开始Flutter开发的不错的起点。本文档旨在帮助Xamarin.Forms开发者
你的Android和iOS知识以及技能组合在构建Flutter时都是有价值的,因为Flutter依赖的原生系统配置都与你配置Xamarin.Forms原生项目时一样.Flutter框架与你创建一个单独的界面时也是一样的,这在多个平台中同样适用。
本文档可用做可指导手册来翻查与你需求最为相关的问题。
本系列上部分:
1.项目设置(上)
2.视图(上)
3.导航(上)
4.异步UI (上)
5.项目结构与资源(下)
6.应用程序生命周期(下)
7.布局(下)
8.手势检测和触摸事件处理(下)
9.列表视图和适配器(下)
10.文本处理(下)
11.表单录入(下)
12. Flutter插件(下)
13.主题(样式)(下)
14.数据库与本地存储(下)
15.通知(下)
五、项目结构与资源
5.1 如何储存我的图片文件?
Xamarin.Forms 没有独立于平台的存储图像的方法, 您必须放置图片在 iOS 的 xcasset
文件夹, 或 Android 的 drawable
文件夹中。
Android和iOS将资源(resources)和资产(assets)视为不同的项目,而 Flutter 应用程序只有资产(assets)。Resources/drawable-*
文件夹中的所有资源都放在一个 Flutter 的资产文件夹中。
Flutter 遵循一种与 iOS 类似的简单的基于密度(density-based)的格式。资产可能是 1.0x
、2.0x
、3.0x
或任何其他倍数。Flutter 没有 dp
,但是有逻辑像素,这基本上是与设备无关像素相同。用所谓 devicePixelRatio
表示单个逻辑像素中物理像素的比例。
与 Android 的密度桶相等的是:
Android density qualifier | Flutter pixel ratio |
---|---|
ldpi |
0.75x |
mdpi |
1.0x |
hdpi |
1.5x |
xhdpi |
2.0x |
xxhdpi |
3.0x |
xxxhdpi |
4.0x |
资产位于任意文件夹中— Flutter 没有预定义的文件夹结构。在 pubspec.yaml
文件中声明资产(带有位置),Flutter 就会得到它们。
注意,在 Flutter 1.0 beta 2 之前的版本中,Flutter 中定义的资产并不能从原生一侧访问, 反之亦然,原生资产和资源对 Flutter 无效,就像他们被放在单独的文件夹中。
在 Flutter beta 2 版本中,资产都被存储在原生的资产文件夹中,并且可以通过 Android 的资产管理器(AssetManager)
从原生一侧被访问。
在 Flutter beta 2 版本中,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 new AssetImage("images/a_dot_burr.jpeg");
或者可以直接在一个 Image
widget 中访问:
@override
Widget build(BuildContext context) {
return new Image.asset("images/my_image.png");
}
更多详尽的信息可以在 在 Flutter 中添加资产和图像 中找到。
5.2 在哪里存储字符串?如何处理本地化?
与 .NET 拥有 resx
文件不同,Flutter 目前没有一个专门的字符串类资源系统。此时,最佳实践是将复制文本作为静态字段保存在类中,并从那里访问它们。举个例子:
class Strings {
static String welcomeMessage = "Welcome To Flutter";
}
那么在你的代码中,你可以像这样访问你的字符串:
new Text(Strings.welcomeMessage)
默认情况下,Flutter 的字符串只支持美式英语。如果你需要添加其他语音的支持,可以包含 flutter_localizations
包。你可能还需要添加 Dart的 intl
包来使用 i10n 装置,例如日期、时间的格式化。
dependencies:
# ...
flutter_localizations:
sdk: flutter
intl: "^0.15.6"
使用 flutter_localizations
包时,要在应用程序的 widget 上指定 localizationsDelegates
和 supportedLocales
:
import 'package:flutter_localizations/flutter_localizations.dart';
new 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
定义了应用程序支持哪些本地化。上面的示例使用了一个 MaterialApp
,因此它为基本 widget 本地化值提供了一个 GlobalWidgetsLocalizations
,为Material widget 的本地化提供了一个 MaterialWidgetsLocalizations
。如果你的应用程序使用 WidgetsApp
,你就不需要后者了。请注意,这两个委托包含“默认”值,但是如果您希望它们也本地化,则需要为您自己的应用程序的可本地化副本提供一个或多个委托。
初始化后, WidgetsApp
(或 MaterialApp
)为您创建一个 Localizations
widget,其中包含您指定的委托。设备的当前区域设置总是可以从当前上下文的 Localizations
widget (以 Locale
对象的形式)或使用 Window.locale
访问。
要访问本地化的资源,请使用 Localizations.of()
方法去访问一个由给定委托提供的特定本地化类。使用 intl_translation
包将可翻译的文本拷贝到 arb 文件中进行翻译,并将其导入到应用程序中与 intl
一起使用。
要了解更多关于 Flutter 国际化和本地化的细节,请查阅 国际化指南, 它有带和不带 intl
包的示例代码。
5.3 我的项目文件在哪里?
Xamarin.Forms 中有一个 csproj
文件。在 Flutter 中最接近的它的是 pubspec.yaml, 其中包含包依赖项和各种项目细节。就像 .NET Standard,相同目录中的文件被认为是项目的一部分。
5.4 Nuget 的等价物是什么?如何添加依赖项?
在 .NET 生态系统中,原生 Xamarin 项目和 Xamarin.Forms 项目都可以访问 Nuget 和内置的包管理系统。Flutter 应用程序包含一个原生Android 应用程序,原生 iOS 应用程序 和 Flutter 应用程序。
在Android中,您可以通过向Gradle添加构建脚本来添加依赖项。而在iOS中,你可以通过添加到 Podfile
来添加依赖项。
Flutter 使用 Dart 自己的构建系统和 Pub 包管理器。这些工具将原生 Android 和 iOS 封装应用程序的构建委托给各自的构建系统。
一般来说,使用 pubspec.yaml
来声明要在 Flutter 中使用的外部依赖项。Pub 是一个寻找 Flutter 包的好地方。
六、应用程序生命周期
6.1 如何侦听应用程序的生命周期事件?
在 Xamarin.Forms 中,拥有一个包含 OnStart
,、OnResume
和 OnSleep
的应用程序
。在 Flutter 中,您可以通过挂钩到 WidgetsBinding
观察者并监听 didChangeAppLifecycleState()
更改事件来监听类似的生命周期事件。
可观察的生命周期事件有:
inactive
inactive
— 应用程序处于非活动状态,并且没有接收用户输入。此事件仅适用于iOS。
paused
resumed
suspending
应用程序暂时暂停。此事件仅限 Android。
有关这些状态的含义的更多细节,可参考 AppLifecycleStatus
文档。
AppLifecycleStatus
文档链接:https://api.flutter.dev/flutter/dart-ui/AppLifecycleState-class.html
七、布局
7.1 什么东西与 StackLayout 等效?
在 Xamarin.Forms 中,可以创建一个带水平或垂直方向
的 StackLayout
。Flutter 也有类似的方法,不过您将使用 Row
或 Column
widget。
如果您注意到除了“Row” 和“Column” widget 之外,这两个代码示例是相同的。这些子元素是相同的,可以利用这个特性开发丰富的布局,这些布局可以随着时间的推移而改变。
7.2 什么东西与网格(Grid) 等价?
与Grid
最接近的对等项是 GridView
。这比您在 Xamarin.Forms 中习惯使用的功能强大得多。 GridView
在内容超出其可视空间时自动滚动。
GridView.count(
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisCount: 2,
// Generate 100 widgets that display their index in the List
children: List.generate(100, (index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
}),
);
您可能在 Xamarin.Forms 中使用 Grid
来实现覆盖其他 widget 的 widget。在 Flutter 中,您可以使用 Stack
widget 来完成这一操作。
这个示例创建了两个相互重叠的图标。
child: new Stack(
children: [
new Icon(Icons.add_box, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)),
new Positioned(
left: 10.0,
child: new Icon(Icons.add_circle, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)),
),
],
),
7.3 有什么等同于 ScrollView ?
在 Xamarin.Forms 中,ScrollView
封装了 VisualElement
,如果内容大于设备屏幕,它就会滚动。
在 Flutter 中,最接近的是 SingleChildScrollView
widget。您只需用想要可滚动的内容来填充 widget。
@override
Widget build(BuildContext context) {
return new SingleChildScrollView(
child: new Text('Long Content'),
);
}
如果您想在滚动条中包含许多项,即使是不同的Widget
类型,也可以使用 ListView
。这可能看起来有点过火,但在 Flutter 中,它比 Xamarin.Forms 的回到平台特定控件的 ListView
优化得多,松散得多。
@override
Widget build(BuildContext context) {
return new ListView(
children: [
new Text('Row One'),
new Text('Row Two'),
new Text('Row Three'),
new Text('Row Four'),
],
);
}
7.4 在 Flutter 中如何处理横向过渡 ?
通过在 AndroidManifest.xml 中设置 configChanges
属性,可以自动处理横向转换。
android:configChanges="orientation|screenSize"
八、手势检测和触摸事件处理
8.1 如何在 Flutter 中向 widget 添加手势识别器?
在 Xamarin.Forms 中,元素(Element)
可能包含一个可供附加(attach)的单击事件。许多元素还包含一个与此事件关联的 命令
。或者你可以使用 TapGestureRecognizer
。在 Flutter 中有两种非常相似的方式:
1. 如果 widget 支持事件发现(detection),那么可以将函数传递给它并在函数中处理它:
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: () {
print("click");
},
child: new Text("Button"));
}
2. 如果 widget 不支持事件发现,则将 widget 封装在手势检测器(GestureDetector)中,并将函数传递给“onTap”参数。
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new GestureDetector(
child: new FlutterLogo(
size: 200.0,
),
onTap: () {
print("tap");
},
),
));
}
}
8.2 我如何处理 widget 上的其他手势?
在 Xamarin.Forms 中你可以在VisualElement
中添加一个手势识别器(GestureRecognizer)
。您通常只能使用 TapGestureRecognizer
、PinchGestureRecognizer
和 PanGestureRecognizer
,除非您构建了自己的实现。
在Flutter中,使用手势检测器,你可以监听到各种各样的手势,比如:
单击
onTapDown
当指尖在特定位置与屏幕接触产生点击事件。
onTapUp
当指尖触发的点击事件已经停止在特定位置与屏幕接触。
onTap
一个点击事件已经发生
onTapCancel
触发了 onTapDown
事件之后的指尖没有导致点击事件。
双击
onDoubleTap
用户在同一位置连续快速点击屏幕两次。
长按
onLongPress
指尖长时间保持与屏幕在同一位置的接触。
垂直拖动
onVerticalDragStart
指尖与屏幕接触后,可能开始垂直移动。
onVerticalDragUpdate
指尖与屏幕接触并在垂直方向上移动得更远。
onVerticalDragEnd
指尖在之前与屏幕接触并垂直移动,当不再与屏幕接触时触发这个事件。当它停止与屏幕接触时,它会以特定的速度移动。
水平拖动
onHorizontalDragStart
指尖与屏幕接触,开始水平移动时触发。
onHorizontalDragUpdate
指尖与屏幕接触并在水平方向上移动得更远。
onHorizontalDragEnd
指尖在之前与屏幕接触并水平移动,当不再与屏幕接触时会触发这个事件。当它停止与屏幕接触时,它正在以特定的速度移动。
下面的例子展示了一个手势检测器
,它可以在双击下旋转 Flutter 的 logo:
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new GestureDetector(
child: new RotationTransition(
turns: curve,
child: new FlutterLogo(
size: 200.0,
)),
onDoubleTap: () {
if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
),
));
}
}
九、列表视图和适配器
9.1 在 Flutter 中,与列表视图等价的是什么?
在 Flutter 中与 ListView
等价的是……一个 ListView
!
在一个 Xamarin.Forms 的 ListView
中,你可以创建一个 ViewCell
可能还有一个 DataTemplateSelector
并将其传递到 ListView
中,该视图将用您的DataTemplateSelector
或者 ViewCell
的返回数据渲染每一行。但是,您通常必须确保打开单元格回收,否则会遇到内存问题和会使滚动速度变慢。
由于 Flutter 的不可变的 widget 模式,您将一个 widget 列表传递给您的 ListView
,Flutter 会负责确保滚动速度快且平稳。
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new ListView(children: _getListData()),
);
}
_getListData() {
List widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row $i")));
}
return widgets;
}
}
9.2 如何知道哪个列表项被点击了?
在 Xamarin.Forms 中,ListView 拥有一个ItemTapped
方法能找出哪个列表项被单击了。您可能还使用了许多其他技术,比如检查 SelectedItem
或EventToCommand
的行为何时会发生更改。
在 Flutter 中,使用传入 widget 提供的触摸处理。
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new ListView(children: _getListData()),
);
}
_getListData() {
List widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(new GestureDetector(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Text("Row $i")),
onTap: () {
print('row tapped');
},
));
}
return widgets;
}
}
9.3 如何动态更新 ListView ?
在 Xamarin.Forms 中,如果将 ItemsSource
属性绑定到一个 ObservableCollection
,就只需要更新视图模型中的列表。另一种方法是,你可以给属性 ItemsSource
分配一个新的 列表
。
在 Flutter 中,情况略有不同。如果您要在 setState()
内更新 widget 列表,您将很快看到您的数据在视觉上没有发生变化。这是因为当 setState()
被调用时,Flutter 的渲染引擎会检查 widget 树是否发生了更改。当它到达您的 ListView
时,会执行 ==
检查,并确定这两个 ListView
是相同的。没有任何更改,就不需要更新。
要更新 ListView
的有一个简单方法,请在 setState()
中创建一个新 列表
,并将数据从旧列表复制到新列表。虽然这种方法很简单,但不推荐用于大型数据集,如下例所示。
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State {
List widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new ListView(children: widgets),
);
}
Widget getRow(int i) {
return new GestureDetector(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Text("Row $i")),
onTap: () {
setState(() {
widgets = new List.from(widgets);
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
推荐的、高效的、有效的列表构建方法是使用 ListView.Builder
。在您有一个动态列表或一个包含大量数据的列表时,这种方法非常棒。这基本上相当于 Android 上的 RecyclerView
,它会自动回收列表元素:
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State {
List widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return new GestureDetector(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Text("Row $i")),
onTap: () {
setState(() {
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
与创建一个“列表视图” 相比,创建一个 ListView.builder
需要接受两个关键参数:列表的初始长度和 ItemBuilder
函数。
ItemBuilder 函数类似于 Android 适配器中的 getView
函数;它接受一个位置,并返回您希望的在该位置呈现的行。
最后,但也是最重要的,要注意 onTap()
函数不再重新创建列表, 而是用 .add
添加给它的。
更多信息,请访问
十、文本处理
10.1 如何在文本(Text) widget 上设置自定义字体?
在 Xamarin.Forms 中,您必须在每个原生项目中添加自定义字体。然后在你的 元素
中,你会使用 filename#fontname
给 FontFamily
属性分配这个字体名,而在iOS中只使用 fontname
。
在 Flutter 中,将字体文件放在一个文件夹中,并在 pubspec.yaml
中引用它,这跟导入图像的方式类似。
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
将字体分配给你的 Text
widget:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new Center(
child: new Text(
'This is a custom font text',
style: new TextStyle(fontFamily: 'MyCustomFont'),
),
),
);
}
10.2 如何设置文本 widget 的样式?
除了字体,您还可以在文本
widget 上定制其他样式元素。 文本
widget 的样式参数接受一个 TextStyle
对象,您可以在其中定制许多参数,比如:
color
decoration
decorationColor
decorationStyle
fontFamily
fontSize
fontStyle
fontWeight
hashCode
height
inherit
letterSpacing
textBaseline
wordSpacing
十一、表单录入
Xamarin.Forms 的元素
允许您直接查询元素
来确定它的任何属性的状态,或者它被绑定到视图模型
中的属性。
在 Flutter 中检索信息是由专门的 widget 处理的,这是跟原来的习惯不同的。如果你有一个 TextField
或 TextFormField
,你可以提供一个 TextEditingController
来检索用户输入:
class _MyFormState extends State {
// Create a text controller and use it to retrieve the current value
// of the TextField.
final myController = new TextEditingController();
@override
void dispose() {
// Clean up the controller when disposing of the widget.
myController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Retrieve Text Input'),
),
body: new Padding(
padding: const EdgeInsets.all(16.0),
child: new TextField(
controller: myController,
),
),
floatingActionButton: new FloatingActionButton(
// When the user presses the button, show an alert dialog with the
// text that the user has typed into our text field.
onPressed: () {
return showDialog(
context: context,
builder: (context) {
return new AlertDialog(
// Retrieve the text that the user has entered using the
// TextEditingController.
content: new Text(myController.text),
);
},
);
},
tooltip: 'Show me the value!',
child: new Icon(Icons.text_fields),
),
);
}
}
你可以在 Flutter 实用教程 中的 获取文本框的输入值 找到更多的信息和完整的代码清单。
11.2 在入口的“占位符”与什么等价?
在 Xamarin.Forms 中,一些元素
支持占位符(Placeholder)
属性,可以给它赋一个值。如:
在 Flutter 中,通过在文本 widget 的装饰器构造函数参数中添加 InputDecoration
对象,可以轻松地为输入显示“提示”或占位符文本。
body: new Center(
child: new TextField(
decoration: new InputDecoration(hintText: "This is a hint"),
)
)
11.3 如何显示验证错误?
使用 Xamarin.Forms 时,如果您希望提供验证错误的可视化提示,则需要创建新属性和 虚拟元素(VisualElement)
来包围具有验证错误的元素。
在 Flutter 中,我们将 InputDecoration
对象传递给文本 widget 的装饰器构造函数。
然而,您不希望从显示错误开始。相反,当用户输入无效数据时,应该更新状态,并传递一个新的 InputDecoration
对象。
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State {
String _errorText;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new Center(
child: new TextField(
onSubmitted: (String text) {
setState(() {
if (!isEmail(text)) {
_errorText = 'Error: This is not an email';
} else {
_errorText = null;
}
});
},
decoration: new InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
),
),
);
}
_getErrorText() {
return _errorText;
}
bool isEmail(String em) {
String emailRegexp =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailRegexp);
return regExp.hasMatch(em);
}
}
十二、Flutter插件
12.1 与硬件、第三方服务和平台交互
12.1.1 应该如何与平台以及平台原生代码交互?
Flutter 不直接在底层平台上运行代码。相反,构成一个 Flutter 应用程序的 Dart 代码是在设备上原生运行的,“绕开”了平台提供的 SDK。这意味着,例如,当您在 Dart 中执行网络请求时,它将直接运行在 Dart 上下文中。在编写原生应用程序时,您通常不会使用 Android 或 iOS 的 API。Flutter 应用程序仍然作为视图驻留在原生应用程序的 ViewController
或 Activity
中,但您不能直接访问这个或原生框架。
这并不意味着 Flutter 应用程序不能与这些原生 API 或您自己的任何原生代码交互。Flutter 提供 平台通道, 可以与托管 Flutter 视图的 ViewController
或 Activity
通信和交换数据。平台通道本质上是一个异步消息传递机制,它将 Dart 代码与 ViewController
或 Activity
宿主以及它所运行的 iOS 或 Android 框架桥接起来。例如,您可以使用平台通道在原生端执行一个方法,或者从设备的传感器检索一些数据。
除了直接使用平台通道外,您还可以使用各种预制插件,它们封装了针对特定目标的原生代码和Dart代码。例如,您可以使用插件直接从Flutter访问相机交卷和设备相机,而无需编写自己的集成。插件可以在在 Pub、Dart 和 Flutter 的开源包存储库中找到。有些包可能支持iOS上的本地集成,有些支持Android,还有两者都兼而有之的。
如果在Pub上找不到适合您需求的插件,您可以 编写自己的插件 并在Pub上发布。
使用 geolocator 社区插件.
image_picker 是流行的访问相机的插件。
使用 flutter_facebook_login 社区插件来通过 Facebook 登录。
大多数 Firebase 功能被第一方插件 覆盖。
firebase_admob for Firebase AdMob
firebase_analytics for Firebase Analytics
firebase_auth for Firebase Auth
firebase_database for Firebase RTDB
firebase_storage for Firebase Cloud Storage
firebase_messaging for Firebase Messaging (FCM)
flutter_firebase_ui for quick Firebase Auth integrations (Facebook, Google, Twitter and email)
cloud_firestore for Firebase Cloud Firestore
你也可以在 Pub 上找一些第三方 Firebase 插件,它们覆盖了第一方插件没有直接覆盖的区域。
如果有 Flutter 或它的社区插件没有的指定平台的功能,可以根据开发包与插件 页面自己构建。
简单地说,Flutter 的插件架构很像在 Android 中使用事件总线:您发出一条消息,让接收方处理并向您发回一个结果。在这个例子中,接收方是运行在 Android 或 iOS 上的原生代码。
十三、主题 (样式)
13.1 如何美化我的应用程序?
Flutter 附带了一个内建的漂亮的 Material Design 实现,它处理了许多您通常会做的样式和主题需求
Xamarin.Forms 确实有一个全局的 资源字典
,可以为你的应用程序共享样式。另外,预览版目前还支持主题。
在 Flutter 中,需要在最顶级 widget 中声明主题。
要在应用程序中充分利用 Material 组件,需要声明一个最顶级 widget MaterialApp
作为应用程序的入口点。MaterialApp 是一个方便的 widget,它封装了许多实现Material Design的应用程序通常需要的各种 widget。它通过添加 Material 的指定功能来构建一个 WidgetsApp。
还可以使用一个 WidgetApp
作为应用程序的 widget,它提供了一些相同的功能,但没有 MaterialApp
丰富。
要定制任何子组件的颜色和样式,请将主题数据(ThemeData)
对象传递给MaterialApp
widget。例如,在下面的代码中,主色调设置为蓝色,文本选择颜框色为红色。
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
textSelectionColor: Colors.red
),
home: new SampleAppPage(),
);
}
}
十四、数据库与本地存储
14.1 如何访问共享首选项或用户默认值?
Xamarin.Forms 开发者可能会熟悉 Xam.Plugins.Settings
插件。
在 Flutter 中,使用 Shared Preferences 插件 就可以访问相同的功能。这个插件封装了 用户默认值
和等同 Android 的 共享首选项
。
14.2 在 Flutter 中如何访问 SQLite
在 Xamarin.Forms 中大多数应用会使用 sqlite-net-pcl
插件来访问 SQLite 数据库。
在 Flutter 中,使用 SQFlite 插件来访问这个功能。
十五、通知
15.1 如何设置通知推送?
在 Android 中,你可以利用 Firebase Cloud Messaging 来给应用程序设置通知推送。
在 Flutter 中,通过 Firebase_Messaging 插件 来访问这个功能。更多关于使用 Firebase Cloud Messaging API 的信息,可以参考 firebase_messaging 插件文档。
(完)