给Android工程师的Flutter入门手册
Posted 上马定江山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了给Android工程师的Flutter入门手册相关的知识,希望对你有一定的参考价值。
前言
这是笔者作为一个android
工程师入门Flutter
的学习笔记,笔者不想通过一种循规蹈矩的方式来学习:先学Dart
语言,然后学习Flutter
的基本使用,再到实践应用这样的步骤。这样的方式有点无趣且效率较低。
笔者觉得对于已经有Android
基础的来说,通过类比Android
的方式来学习Flutter
,掌握核心基础概念后,直接开发实践应用,在这个过程中去学习其中的知识比如Dart
语法、深入的知识点。这是笔者的一次学习尝试,并将其记录下来:
本篇是该系列的第一篇,主要内容是:
(1)视图在 Flutter
中对应什么概念?如何布局Widget?
(2)Android
中的Intent
在 Flutter
中的对应什么?
(3) Flutter
中如何在页面间导航?与Activity
层的数据如何传递?
(4) Flutter
中如何实现网络请求和数据处理?
视图
Android
中的 View
是显示在屏幕上的一切的基础。常见的控件比如按钮、工具栏、输入框都是 View
。
而 Flutter
中有个概念叫 Widget
,它是Flutter
中声明和构建 UI 的方式,可以粗略对比成 Android 中的 View,但 Widget
并非完全对应于 Android
中的 View
,它们是有差异的:
widget
有着不一样的生命周期:它们是不可变的,一旦需要变化则生命周期终止。任何时候widget
或它们的状态变化时,Flutter
框架都会创建一个新的 widget 树的实例
而Android
中的View
一般情况下只会绘制一次,除非调用 invalidate
才会重绘。
Flutter
的widget
很轻量,部分原因在于它们的不可变性。因为它们本身既非视图,也不会直接绘制任何内容,而是 UI 及其底层创建真正视图对象语义的描述。
Widget状态
在Android
中,你可以直接操作更新View
。然而在Flutter
中,Widget
是不可变的,无法被直接更新,你需要操作 Widget
的状态。有两种状态的Widget
:
-
StatelessWidget(无状态): 没有状态信息的 Widget,用于描述用户界面的一部分,不依赖于除了对象中的配置信息以外的任何东西的场景,类似
Android
中 一个展示图标的ImageView
,整个过程是不会变的。 -
StatefulWidget(有状态):比如根据
HTTP
请求返回的数据或者用户的交互来动态地更新界面,那么你就必须使用StatefulWidget
,并告诉Flutter
框架Widget
的``状态(
State) 更新了,以便
Flutter可以更新这个
Widget`。
无状态Widget和有状态Widget 本质上是行为一致的。它们每一帧都会重建,不同之处在于
StatefulWidget
有一个跨帧存储和恢复状态数据的State
对象。
比如 Text
Widget 就是一个普通的 StatelessWidget
, 它没有相关联的状态信息,只是渲染传入构造器的信息,所以内部的数据是没办法更新的。
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
);
如果想动态更新内部的文本就需要借助StatefulWidget
,将 Text Widget
嵌入一个 StatefulWidget
中,例如下面的Scaffold
是StatefulWidget
:
class _SampleAppPageState extends State<SampleAppPage>
String textToShow = 'I Try Learn Flutter';
void updateText()
setState(()
textToShow = "I Like Flutter!";
);
@override
Widget build(BuildContext context)
return Scaffold( // Scaffold 是 StatefulWidget
appBar: AppBar(title: const Text('Sample App')),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: updateText,
tooltip: 'Update Text',
child: const Icon(Icons.update),
),
);
Widget布局
在 Android
中,通过XML
文件定义布局,但是在Flutter
中,要通过一个 widget
树来定义布局的
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: const Text('Sample App'),
),
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 20.0, right: 30.0),
),
onPressed: () ,
child: const Text('Hello'),
),
),
);
如何添加/删除一个Widget?
在 Android
中,你通过调用父 View
的 addChild()
或 removeChild()
方法动态地添加或者删除子 View
。
在Flutter
中,由于Widget
是不可变的,所以没有类似 addChild()
这样的方法。
不过,我们可以给返回一个 Widget
的父Widget
传入一个方法,并通过布尔标记值控制子Widget
的创建。
举个例子:点击一个 FloatingActionButton 时在两个 widget 之间切换
class _SampleAppPageState extends State<SampleAppPage>
bool toggle = true;
int a = 1;
void _toggle()
setState(()
toggle = !toggle;
);
Widget _getToggleChild()
if (toggle)
return const Text("Toggle One");
else
a++;
return ElevatedButton(onPressed: () , child: Text('Toggle $a'));
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: Center(child: _getToggleChild()),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Widget',
child: const Icon(Icons.update)),
);
Widget动画
Android
既可以通过XML
文件定义动画,也可以调用View
对象的 animate()
方法。
在 Flutter
里,则使用动画库,通过将 Widget
嵌入一个动画 Widget
的方式实现 Widget
的动画效果。
AnimationController
是个特殊的 Animation
对象,每当硬件准备新帧时,他都会生成一个新值。默认情况下,AnimationController
在给定期间内会线性生成从 0.0 到 1.0 的数字。使用 .forward()
方法启动动画。
创建 AnimationController
的同时,也赋予了一个 vsync
参数。 vsync
的存在防止后台动画消耗不必要的资源。您可以通过添加 SingleTickerProviderStateMixin
或者TickerProviderStateMixin
到类定义,将有状态的对象用作 vsync
。
SingleTickerProviderStateMixin
只适用于单个AnimationController
的情况,如需使用多个AnimationController
,请使用TickerProviderStateMixin
举个例子:实现一个淡出的动画
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin
late AnimationController controller;
late CurvedAnimation curvedAnimation;
@override
void initState()
super.initState();
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
curvedAnimation =
CurvedAnimation(parent: (controller), curve: Curves.easeIn);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: FadeTransition(
opacity: curvedAnimation,
child: const FlutterLogo(size: 100.0),
),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade Animation',
onPressed: ()
controller.forward();
,
child: const Icon(Icons.brush),
),
);
上述代码解释:
1.with作用:Dart 支持 Mixin ,而 Mixin 能够更好的解决 多继承 中容易出现的问题, 如: 方法优先顺序混乱、参数冲突、类结构变得复杂化等等。结论上简单来说,就是相同方法被覆盖了,并且 with 后面的会覆盖前面的。
2.TickerProviderStateMixin 作用:使用Animation controller时,需要在控制器初始化时传递一个vsync参数,此时需要用到TickerProvider SingleTickerProviderStateMixin只适用于单个AnimationController的情况,如需使用多个AnimationController,请使用TickerProviderStateMixin
3.CurvedAnimation: 为非线性曲线
4.override initState:覆盖此方法以执行初始化,这取决于此对象插入树中的位置(即 [context])或用于配置此对象的小部件(即 [widget])。
Canvas进行绘制
在Android
中,你可以使用 Canvas
和 Drawable
将图片和形状绘制到屏幕上。
Flutter
也有一个类似于 Canvas
的 API,因为它基于相同的底层渲染引擎Skia
。
Flutter
有两个帮助你用画布 (canvas) 进行绘制的类: CustomPaint
和 CustomPainter
,后者可以实现自定义的绘制算法。
举个例子:实现一个手写笔迹功能
/// ..的作用:级联运算符 (.. or ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法。
class SignatureState extends State<Signature>
List<Offset?> _points = <Offset>[];
@override
Widget build(BuildContext context)
return GestureDetector(
onPanUpdate: (details)
setState(()
RenderBox? renderBox = context.findRenderObject() as RenderBox;
Offset localPosition =
renderBox.globalToLocal(details.globalPosition);
_points = List.from(_points)..add(localPosition);
);
,
onPanEnd: (details) => _points.add(null),
child: CustomPaint(
painter: SignaturePainter(_points), size: Size.infinite));
/// 自定义绘制算法,实现手写笔迹
class SignaturePainter extends CustomPainter
SignaturePainter(this.points);
final List<Offset?> points;
@override
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);
/// 如果新实例表示与旧实例不同的信息,则该方法应返回 true,否则应返回 false
@override
bool shouldRepaint(SignaturePainter oldDelegate) =>
oldDelegate.points != points;
自定义 Widget
在 Android
中,一般通过继承 View
类,或者使用已有的视图类,再重载或实现以达到特定效果的方法。
在 Flutter
中,通过 组合 更小的 Widget 来创建自定义 Widget(而不是继承它们)。
举个例子:自定义一个带标签的按钮
通过组合 ElevatedButton
和一个标签来创建自定义按钮,而不是继承 ElevatedButton
:
class CustomButton extends StatelessWidget
final String label;
const CustomButton(this.label, super.key);
@override
Widget build(BuildContext context)
return ElevatedButton(
onPressed: () ,
child: Text(label),
);
之后就可以和其他Widget
一样使用了:
@override
Widget build(BuildContext context)
return const Center(
child: CustomButton('自定义Widget'),
);
意图(Intent)
在Android
中,Intent
主要有两个使用场景:在Activity
之间进行导航,以及组件间通信。
Flutter
实际上并没有Activity
和Fragment
的对应概念。在Flutter
中你需要使用 Navigator
和 Route
在同一个 Activity
内的不同界面间进行跳转。
Navigator和Route
Route
是应用内屏幕和页面的抽象,Navigator
是管理路径route
的工具。一个 route
对象大致对应于一个 Activity
,但是它的含义是不一样的。 Navigator 可以通过对route
进行压栈和弹栈操作实现页面的跳转。
Navigator
的工作原理和栈相似,你可以将想要跳转到的 route
压栈 (push方法),想要返回的时候将route
出栈 (pop方法)
在 Flutter
中,你有多种不同的方式在页面间导航:
- 定义一个
route
名字的Map
(MaterialApp) - 直接导航到一个
route
(WidgetApp)
Flutter接收原生Activity的数据
在Android
原生层面(在我们的 Activity
中)处理分享的文本数据,然后Flutter
再通过使用 MethodChannel
获取这个数据。
Android
端 Activity
:configureFlutterEngine
里通过 call 在方法名getSharedText
里处理分享的数据,再通过 result
回掉给最终结果
class MainActivity : FlutterActivity()
companion object
private const val CHANNEL = "app.channel.shared.data"
...
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) ->
if (call.method.contentEquals("getSharedText"))
result.success(sharedText); //
sharedText = null;
);
Flutter
使用一个平台通道请求数据,数据便会从原生端发送过来:
class _SampleAppPageState extends State<SampleAppPage>
static const platform = MethodChannel('app.channel.shared.data');
String dataShared = 'No data';
@override
void initState()
super.initState();
getSharedText();
@override
Widget build(BuildContext context)
return Scaffold(body: Center(child: Text(dataShared)));
// 调用Activity的共享数据方法
Future<void> getSharedText() async
var sharedData = await platform.invokeMethod('getSharedText');
if (sharedData != null)
setState(()
dataShared = sharedData;
);
异步UI
Dart
有一个单线程执行的模型,Dart
的单线程模型并不意味着你需要以会导致 UI 冻结的阻塞操作的方式来运行所有代码。
可以使用 Dart 语言提供的异步工具,例如 async/await
来执行异步任务,用 await
修饰的网络操作完成,再调用 setState()
更新 UI,就会触发 widget
子树的重建并更新数据。
Future<void> loadData() async
var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
http.Response response = await http.get(dataURL);
setState(()
widgets = jsonDecode(response.body);
);
Isolate
有时候你可能需要处理大量的数据并挂起你的 UI。在Flutter
中,可以通过使用 Isolate
来利用多核处理器的优势执行耗时或计算密集的任务。
Dart
同时也支持 Isolate
(在另一个线程运行 Dart 代码的方法),它是一个事件循环和异步编程方式。除非你创建一个 Isolate
,否则你的Dart
代码会运行在主 UI 线程,并被一个事件循环所驱动。Flutter
的事件循环对应于Android
里的主 Looper
—即绑定到主线程上的 Looper
。
Isolate
之间通讯的方式:port 端口,可以很方便的实现Isolate
之间的双向通讯,原理是向对方的队列里写入任务
Future<void> loadData() async
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(
sendPort,
'https://jsonplaceholder.typicode.com/posts',
);
setState(()
widgets = msg;
);
static Future<void> dataLoader(SendPort sendPort) async
// 打开ReceivePort接收消息
ReceivePort port = ReceivePort();
// 通知任何其他这个 isolate 监听的端口。
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(Uri.parse(dataURL));
replyTo.send(jsonDecode(response.body));
Future sendReceive(SendPort port, msg)
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
网络数据处理
虽然http
包没有 OkHttp
中的所有功能,但是它抽象了很多通常你会自己实现的网络功能,这使其本身在执行网络请求时简单易用。
使用 async
和 await
的代码是异步的,但是看起来有点像同步代码。必须在带有 async 关键字的 异步函数 中使用 await
:
Future<void> checkVersion() async
var version = await lookUpVersion();
// Do something with version
尽管 async
函数可能会执行一些耗时操作,但是它并不会等待这些耗时操作完成,相反,异步函数执行时会在其遇到第一个 await
表达式时返回一个Future
对象,然后等待 await
表达式执行完毕后继续执行。 Future
对象代表一个“承诺”, await
表达式会阻塞直到需要的对象返回。
数据序列化
在 Flutter
中基础的序列化JSON
十分容易的。Flutter
有一个内置的 dart:convert
的库,这个库包含了一个简单的 JSON
编码器和解码器。将JSON
字符串作为方法的参数,调用 jsonDecode()
方法来解码 JSON。
不过这种方式虽然简单,但不好的是,jsonDecode()
返回一个 Map<String, dynamic>
,这意味着你在运行时以前都不知道值的类型。使用这个方法,你失去了大部分的静态类型语言特性:类型安全、自动补全以及最重要的编译时异常。你的代码会立即变得更加容易出错。
Flutter
中也有可利用第三方库json_serializable
来实现自动序列化JSON数据,可以参考JSON 和序列化数据
class _SampleAppPage extends State<SampleAppPage>
// TODO:可以优化,widgets弄成实体对象,通过自动序列化
List widgets = [];
@override
void initState()
super.initState();
loadData();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: const Text('Sample App'),
),
body: getBody());
Future<void> loadData() async
// 创建 receivePort 接受端口
ReceivePort receivePort = ReceivePort();
// 创建 Isolate,因为这是个异步操作,所以加上 await
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 创建 SendPort 发送端口
SendPort sendPort = await receivePort.first;
// 发送
List msg = await sendReceive(
sendPort,
'https://jsonplaceholder.typicode.com/posts',
);
setState(()
widgets = msg;
);
Future sendReceive(SendPort sendPort, address)
ReceivePort response = ReceivePort();
sendPort.send([address, response.sendPort]);
return response.first;
static Future<void> dataLoader(SendPort sendPort) async
ReceivePort port = ReceivePort();
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(Uri.parse(dataURL));
replyTo.send(jsonDecode(response.body));
developer.log(response.body);
Widget getBody()
bool showLoadingDialog = widgets.isEmpty;
if (showLoadingDialog)
return const Center(child: CircularProgressIndicator());
else
return ListView.builder(
itemCount: widgets.length,
itemBuilder: (context, position)
return getRow(position);
,
);
Widget getRow(int i)
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text("Row$i: $widgets[i]["title"]"),
);
参考
源代码地址:github.com/Kingwentao/…
作者:树獭非懒
链接:https://juejin.cn/post/7199840152217600057
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
以上是关于给Android工程师的Flutter入门手册的主要内容,如果未能解决你的问题,请参考以下文章
Android Flutter全家桶学习资料(从入门到实战)
久等了给高级 Android 工程师的进阶手册及经验总结,2022共勉