资讯中心

给Android工程师的Flutter入门手册(一)

📅 2026/8/27 18:50:03
给Android工程师的Flutter入门手册(一)
前言这是笔者作为一个Android工程师入门Flutter的学习笔记笔者不想通过一种循规蹈矩的方式来学习先学Dart语言然后学习Flutter的基本使用再到实践应用这样的步骤。这样的方式有点无趣且效率较低。笔者觉得对于已经有Android基础的来说通过类比Android的方式来学习Flutter掌握核心基础概念后直接开发实践应用在这个过程中去学习其中的知识比如Dart语法、深入的知识点。这是笔者的一次学习尝试并将其记录下来本篇是该系列的第一篇主要内容是1视图在Flutter中对应什么概念如何布局Widget2Android中的Intent在Flutter中的对应什么3Flutter中如何在页面间导航与Activity层的数据如何传递4Flutter中如何实现网络请求和数据处理视图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的状态。有两种状态的WidgetStatelessWidget无状态 没有状态信息的 Widget用于描述用户界面的一部分不依赖于除了对象中的配置信息以外的任何东西的场景类似Android中 一个展示图标的ImageView整个过程是不会变的。StatefulWidget有状态比如根据HTTP请求返回的数据或者用户的交互来动态地更新界面那么你就必须使用StatefulWidget并告诉Flutter框架Widget的状态(State) 更新了以便Flutter可以更新这个Widget。无状态Widget和有状态Widget 本质上是行为一致的。它们每一帧都会重建不同之处在于StatefulWidget有一个跨帧存储和恢复状态数据的State对象。比如TextWidget 就是一个普通的StatelessWidget 它没有相关联的状态信息只是渲染传入构造器的信息所以内部的数据是没办法更新的。Text( I like Flutter!, style: TextStyle(fontWeight: FontWeight.bold), );如果想动态更新内部的文本就需要借助StatefulWidget将Text Widget嵌入一个StatefulWidget中例如下面的Scaffold是StatefulWidgetclass _SampleAppPageState extends StateSampleAppPage { ​ 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 StateSampleAppPage { 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 StateMyFadeTest 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请使用TickerProviderStateMixin3.CurvedAnimation: 为非线性曲线4.override initState覆盖此方法以执行初始化这取决于此对象插入树中的位置即 [context]或用于配置此对象的小部件即 [widget]。Canvas进行绘制在Android中你可以使用Canvas和Drawable将图片和形状绘制到屏幕上。Flutter也有一个类似于Canvas的 API因为它基于相同的底层渲染引擎Skia。Flutter有两个帮助你用画布 (canvas) 进行绘制的类CustomPaint和CustomPainter后者可以实现自定义的绘制算法。举个例子实现一个手写笔迹功能/// ..的作用级联运算符 (.. or ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法。 class SignatureState extends StateSignature { ListOffset? _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 ListOffset? 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和一个标签来创建自定义按钮而不是继承ElevatedButtonclass 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和RouteRoute是应用内屏幕和页面的抽象Navigator是管理路径route的工具。一个route对象大致对应于一个Activity但是它的含义是不一样的。 Navigator 可以通过对route进行压栈和弹栈操作实现页面的跳转。Navigator的工作原理和栈相似你可以将想要跳转到的route压栈 push方法想要返回的时候将route出栈 pop方法在Flutter中你有多种不同的方式在页面间导航定义一个route名字的MapMaterialApp直接导航到一个routeWidgetAppFlutter接收原生Activity的数据在Android原生层面在我们的Activity中处理分享的文本数据然后Flutter再通过使用MethodChannel获取这个数据。Android端ActivityconfigureFlutterEngine里通过 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 StateSampleAppPage { 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的共享数据方法 Futurevoid getSharedText() async { var sharedData await platform.invokeMethod(getSharedText); if (sharedData ! null) { setState(() { dataShared sharedData; }); } } }遇到的问题Unresolved reference: FlutterActivity异步UIDart有一个单线程执行的模型Dart的单线程模型并不意味着你需要以会导致 UI 冻结的阻塞操作的方式来运行所有代码。可以使用 Dart 语言提供的异步工具例如async/await来执行异步任务用await修饰的网络操作完成再调用setState()更新 UI就会触发widget子树的重建并更新数据。Futurevoid 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之间的双向通讯原理是向对方的队列里写入任务Futurevoid 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 Futurevoid 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 关键字的异步函数中使用awaitFuturevoid 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()返回一个MapString, dynamic这意味着你在运行时以前都不知道值的类型。使用这个方法你失去了大部分的静态类型语言特性类型安全、自动补全以及最重要的编译时异常。你的代码会立即变得更加容易出错。Flutter中也有可利用第三方库json_serializable来实现自动序列化JSON数据可以参考JSON 和序列化数据class _SampleAppPage extends StateSampleAppPage { ​ // 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()); } ​ Futurevoid 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 Futurevoid 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/…给 Android 开发者的 Flutter 指南Widget 动画Dart之Mixins的with用法JSON 和序列化数据作者树獭非懒链接https://juejin.cn/post/7199840152217600057最后如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓