优秀的编程知识分享平台

网站首页 > 技术文章 正文

Flutter 对 iOS、Android(双端开发者)的快速理解(二)

nanyue 2024-09-26 15:31:04 技术文章 9 ℃

原文链接 http://tryenough.com/flutter09

更多教程 https://tryenough.com/type-flutter


Flutter怎么在不同页面之间跳转?

  • 在 iOS 中,你可以使用管理了 view controller 栈的 UINavigationController 来在不同的 view controller 之间跳转。
  • 在Android中,Intents主要有两种使用场景:在Activity之间切换,以及调用外部组件。
  • 在Flutter中切换屏幕,您可以访问路由以绘制新的Widget。 管理多个屏幕有两个核心概念和类:Route 和 Navigator。Route是应用程序的“屏幕”或“页面”的抽象(可以认为是Android中的Activity或iOS中的UIViewController), Navigator是管理Route的Widget。Navigator可以通过push和pop 路由以实现页面切换。

在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');

通过Navigator获取跳转到的页面的返回值:

例如跳转到location页面,使用关键字await等待结果:

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

在 location 页面中,一旦用户选择了地点,携带结果一起 pop() 出栈,上面的coordinates就能拿到结果:

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

Flutter怎么编写异步的代码?

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

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 {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Sample App',
 theme: ThemeData(
 primarySwatch: Colors.blue,
 ),
 home: SampleAppPage(),
 );
 }
}
class SampleAppPage extends StatefulWidget {
 SampleAppPage({Key key}) : super(key: key);
 @override
 _SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
 List widgets = [];
 @override
 void initState() {
 super.initState();
 loadData();
 }
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text("Sample App"),
 ),
 body: ListView.builder(
 itemCount: widgets.length,
 itemBuilder: (BuildContext context, int position) {
 return getRow(position);
 }));
 }
 Widget getRow(int i) {
 return Padding(
 padding: EdgeInsets.all(10.0),
 child: Text("Row ${widgets[i]["title"]}"));
 }
 loadData() async {
 String dataURL = "https://jsonplaceholder.typicode.com/posts";
 http.Response response = await http.get(dataURL);
 setState(() {
 widgets = json.decode(response.body);
 });
 }
}

这个例子使用了http包,要使用 http 包,在 pubspec.yaml 中把它添加为依赖:

dependencies:
 ...
 http: ^0.11.3+16

运行效果如下:



这就是对诸如网络请求或数据库访问等 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”是在它自己的独立执行线程中运行的隔离区,您可以在其中执行CPU密集型任务,例如解析大于1万的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 {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Sample App',
 theme: ThemeData(
 primarySwatch: Colors.blue,
 ),
 home: SampleAppPage(),
 );
 }
}
class SampleAppPage extends StatefulWidget {
 SampleAppPage({Key key}) : super(key: key);
 @override
 _SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
 List widgets = [];
 @override
 void initState() {
 super.initState();
 loadData();
 }
 showLoadingDialog() {
 if (widgets.length == 0) {
 return true;
 }
 return false;
 }
 getBody() {
 if (showLoadingDialog()) {
 return getProgressDialog();
 } else {
 return getListView();
 }
 }
 getProgressDialog() {
 return Center(child: CircularProgressIndicator());
 }
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text("Sample App"),
 ),
 body: getBody());
 }
 ListView getListView() => ListView.builder(
 itemCount: widgets.length,
 itemBuilder: (BuildContext context, int position) {
 return getRow(position);
 });
 Widget getRow(int i) {
 return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
 }
 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;
 }
}

效果如下:



Flutter中显示进度指示器loading

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

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
 runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
 @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<SampleAppPage> {
 List widgets = [];
 @override
 void initState() {
 super.initState();
 loadData();
 }
 showLoadingDialog() {
 if (widgets.length == 0) {
 return true;
 }
 return false;
 }
 getBody() {
 if (showLoadingDialog()) {
 return getProgressDialog();
 } else {
 return getListView();
 }
 }
 getProgressDialog() {
 return new Center(child: new CircularProgressIndicator());
 }
 @override
 Widget build(BuildContext context) {
 return new Scaffold(
 appBar: new AppBar(
 title: new Text("Sample App"),
 ),
 body: getBody());
 }
 ListView getListView() => new ListView.builder(
 itemCount: widgets.length,
 itemBuilder: (BuildContext context, int position) {
 return getRow(position);
 });
 Widget getRow(int i) {
 return new Padding(
 padding: new EdgeInsets.all(10.0),
 child: new Text("Row ${widgets[i]["title"]}"));
 }
 loadData() async {
 String dataURL = "https://jsonplaceholder.typicode.com/posts";
 http.Response response = await http.get(dataURL);
 setState(() {
 widgets = json.decode(response.body);
 });
 }
}

效果同上面的gif。

Flutter工程结构、本地化、依赖和资源如何引入?

图片等资源在 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这样简单的3种分辨率格式: 1x, 2x, and 3x。

举个例子,要把一个叫 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 中直接使用:

@override
Widget build(BuildContext context) {
 return Image.asset("images/my_image.png");
}

Flutter中的字符串资源

目前,最好的做法是创建一个名为Strings的类:

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

然后在你的代码中,你可以像访问你的字符串一样:

new Text(Strings.welcomeMessage)

鼓励Flutter开发者使用intl package 进行国际化和本地化

由于篇幅过长,文章将分为多部分,请继续关注本文其他部分。

原文链接 http://tryenough.com/flutter09

更多教程 https://tryenough.com/type-flutter

最近发表
标签列表