Flutter课题汇报

what is flutter

Flutter概念

Flutter is a framework for building user interface with Dart 单一代码库可以生存广泛的目标平台的应用程序

与react native和uniapp进行比较

那么如何实现跨平台的渲染呢,因为不同的操作系统它暴露出来的渲染的方法当然会不同,如何实现一套代码在不同的操作系统上面都可以使用呢,这个就是跨平台框架再做的事情。

与其他的跨平台开发框架对比来说,就以我们比较熟悉的两个前端的跨平台的框架来比较。

那么其实我们前端也是有很多跨平台的框架的,比如说 react native 和 uniapp(就是我们公司现在正在用的这么一个框架,我们可以从这几个框架实现跨平台的方案来进行 flutter 的学习)

由于 Android 和 iOS 的 UI 渲染机制完全不同(分别基于 Skia 与 CoreGraphics,并最终通过 OpenGL / Metal 等图形接口驱动 GPU 绘制),跨平台框架在调用系统绘制接口时需要适配大量差异。 uni-app 的方案是利用各操作系统原生提供的 WebView 控件 作为统一渲染容器,借助 Web 技术完成界面绘制,并通过 JSBridge 与原生层通信,从而实现单一代码库、多平台一致的运行效果。

那么我们可以比较一下这三个跨平台的框架都是如何实现的。

  • uni-app 的做法是:开发者使用 HTML、CSS、JavaScript(基于 Vue 语法) 编写页面逻辑,框架在编译时会将这些代码打包成可在 WebView 中运行的资源文件。 当 App 启动后,WebView 负责渲染 UI 界面,而通过 JSBridge,JavaScript 层可以与原生层交互(如调用相机、蓝牙、定位等原生能力)。 这样,开发者只需维护一套前端代码,就能在 Android、iOS 乃至 Web 等多个平台上运行,实现真正的"一次开发,多端运行"。

  • react native,从名字就可以看出点端倪,native 就是原生的,在 uni-app 的虽然也通过 jsbrdge 与原生层交互,但 uni-app 渲染的是 WebView 内的网页内容,而 react native 完全是通过 jsbridge, React Native 最终渲染的是系统原生控件,而 uni-app 渲染的是 WebView 内的网页内容;两者都依赖 JSBridge,但 RN 的 Bridge 更深入地参与了 UI 渲染流程。

  • flutter,实现跨平台的方案就是使用自绘引擎,不使用对应系统的控件而是自己调用绘制图形的接口实现了自己的渲染引擎,所以无论在任何系统都能保证 ui 的一致性

对比项 uni-app React Native Flutter
技术栈 Vue / JavaScript React / JavaScript Dart
渲染方式 WebView(或 nvue 原生渲染) 原生控件渲染 自绘引擎(Skia)
性能 中等 接近原生 接近原生甚至更优
开发成本 低(Web 开发者易上手) 中(需前端+原生知识) 高(需学 Dart)
生态 成熟,小程序支持强 成熟,生态稳定 快速增长,生态完善
典型场景 中小型 App、小程序、混合应用 原生 App、高性能需求 高性能跨平台 App、全自定义 UI
代表产品 支付宝、头条小程序、快应用 Facebook、滴滴、携程 闪送、阿里咸鱼、京东
flutter

flutter又是如何实现的跨平台呢?我们都知道就是要将ui渲染到屏幕上面当然是绕不开硬件的,由于操作系统的不同,每个操作系统的控件都可能长得不太一样,flutter渲染这些ui不通过这些操作系统的控件,而是自己写了一套ui的渲染引擎,直接通过系统暴露的底层图形 API,所以无论在什么系统上面最终渲染出来的样式都是一致的。

那么flutter又是如何绘制的呢

css 复制代码
[Dart层:Widget树]
   ↓ 转换为
[RenderObject树]
   ↓ 交给
[Flutter Engine (C++)]
   ↓ 调用
[Skia 渲染引擎]
   ↓ 通过
[OpenGL / Metal / Vulkan]
   ↓ 最终输出
[屏幕帧缓存 Framebuffer]

┌──────────────────────────┐ │ Flutter Framework (Dart) │ ← 写的部分(Widget、State、布局逻辑) ├──────────────────────────┤ │ Flutter Engine (C++) │ ← 负责渲染、动画、GPU调用 ├──────────────────────────┤ │ Embedder (Platform) │ ← 与系统交互(Android/iOS) └──────────────────────────┘ widget是flutter开发中很重要的一个概念,widget就相当于flutter中的「积木」或者声明式ui,你描述给渲染引擎你需要什么样的ui,

Dart 语法

Dart 是一门 强类型、面向对象、支持函数式特性 的语言。

我们从几个核心特性来讲


① 类型系统(Static + Inference)

Dart 是 静态类型语言,但支持类型推断(inference)。

javascript 复制代码
let a = 1
a = 'a'
Dart 复制代码
var name = 'Ziven'; // 推断为 String
int age = 18;
final city = 'Tokyo'; // 运行时不可变
const country = 'Japan'; // 编译时常量

区别:

  • var:变量,可变
  • final:不可重新赋值(运行时确定)
  • const:编译时常量(编译时必须确定)

Dart 会在编译时检查类型错误,这是和 JS 最大的不同。


② 空安全(Null Safety)

Dart默认变量 不可为 null,除非你显式声明为可空类型。

ini 复制代码
String name = 'Ziven'; // 不能为 null
String? nickname; // 可以为 null

nickname = '小Z'; // ✅
nickname = null;  // ✅

访问可空对象前必须做判空:

scss 复制代码
print(nickname?.length ?? 0);

空安全让 Dart 在编译期就能防止空指针异常。避免出现对未赋值变量进行访问


③ 构造函数与类系统

Dart 一切皆对象,连函数也是对象。

kotlin 复制代码
class Person {
  final String name;
  int age;

  // 默认构造函数
  Person(this.name, this.age);

  // 命名构造函数
  Person.named({required this.name, this.age = 18});

  // 工厂构造函数(可返回缓存或子类)
  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(json['name'], json['age']);
  }
}

④ 异步机制(Future / async / await)

Dart 是 单线程事件驱动模型 (类似 JS),但提供强大的异步语法糖。 单线程的语言都会面临一个问题就是遇到耗费时间长的一些任务,会被阻塞,与 js 一样的是,Dart 的运行时模型也是基于事件循环。那大致的事件循环的机制就是在任务的执行栈中按照顺序一个一个执行任务,当遇到耗时长的任务(这种耗费时间长的任务也就是异步任务)为避免堵塞就将这些任务挂起,然后将这些异步任务对应的回调函数注册到 callback 注册表,当异步任务执行完成之后将对应的回调任务 push 进去执行栈执行。

dart 复制代码
Future<void> fetchData() async {
  print('开始请求...');
  final data = await getDataFromServer(); // 模拟异步
  print('请求结束: $data');
}

⑤ 集合操作与函数式特性

Dart 的集合类(List、Set、Map)支持链式与函数式操作:

ini 复制代码
var numbers = [1, 2, 3, 4];
var result = numbers.where((n) => n.isEven).map((n) => n * 2).toList();
print(result); // [4, 8]

还可以用集合推导(collection if / for):

ini 复制代码
var fruits = ['apple', 'banana', if (true) 'orange'];

⚙️Dart 特性与 JS 对比

Dart 的定位在某种程度上像"为 Flutter 优化过的 JS"。

但在语法、运行机制、性能上有明显不同👇

对比项 Dart JavaScript
类型系统 静态类型(编译期检查) 动态类型(运行期检查)
空安全 有(Null Safety) 无(undefined/null 问题多)
运行机制 JIT + AOT,可编译为机器码 解释执行(JIT 优化)
异步机制 Future + async/await(语法级支持) Promise + async/await

🌍 深入解释几个重点差异

① 编译模式差异

Dart 支持两种编译方式:

模式 说明
JIT (Just-In-Time) 热重载开发模式,用于快速调试
AOT (Ahead-Of-Time) 编译成机器码,用于发布版本(性能高)

所以比起react native和uni-app来说由于这个编译的特性,flutter的应用的启动更快。

③ 类型系统的本质差异

JS 是弱类型动态语言:

ini 复制代码
let x = "1" + 2; // "12"

Dart 是强类型静态语言:

ini 复制代码
var x = "1" + 2; // ❌ 编译错误

这让 Dart 的错误可以在编译期暴露,提升了可靠性。


🔍 小结

维度 Dart 优势
安全性 类型 + 空安全防止低级错误
性能 可直接编译为机器码,性能接近原生
并发 Isolate 模型更易控
Flutter 与 UI 层深度集成,跨平台统一开发体验

Flutter 的布局核心

Widget 的概念

  • Widget 是 Flutter UI 的基本构建块,相当于前端的组件或 HTML 元素。

  • Flutter 中一切都是 Widget:布局(Row/Column)、容器(Container)、文本(Text)、图片(Image)、按钮(ElevatedButton)等等。

  • 声明式 UI

    • Widget 本身不可变,只描述界面应该"长什么样"。
    • 具体的状态、数据变化由 State 控制。
    • 每次 setState(),Flutter 会用新的 Widget 描述界面,但复用旧的 State。

常用Widget以及布局约束

布局约束的本质上就是子组件的宽高收到父组件的约束,比如说父亲组件的宽200,那么子组件的宽度范围就在0~200。 超出约束的话就会出现布局报错。

Widget 类型 宽高默认行为 举例
容器型(Container, SizedBox) 遵守父约束,但可强制设置自身尺寸 Container(width:100)
布局型(Row, Column, Stack) 先看主轴规则、再看子元素情况 Row占满宽度但高取决于子
可扩展型(Expanded/Flexible) 会占据父组件剩余空间(主轴方向) Expanded(child: ...)
内容型(Text, Icon, Image) 尺寸取决于内容本身 Text('Hi') 宽高由文字决定
滚动型(ListView, SingleChildScrollView) 在可滚动方向上可以超出父容器 ListView 默认会尽量扩展

如何使用Widget描述一个组件呢?

Widget组件Demo

比如说我们想开发一个模块卡片的组件,他的样式如下,这就是一个

less 复制代码
// 模块卡片
Widget _buildCard(String title, int color, IconData icon, String routeName) {
  return GestureDetector(
    onTap: () {
      print('routername $routeName');
      Get.toNamed(routeName);
    },
    child: Container(
      width: 160,
      height: 140,
      margin: const EdgeInsets.only(right: 12),
      decoration: BoxDecoration(
        color: Color(color),
        borderRadius: BorderRadius.circular(20),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, size: 30, color: Colors.grey[700]),
          const SizedBox(height: 8),
          Text(
            title,
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.grey[800],
              fontSize: 14,
            ),
            textAlign: TextAlign.center,
          ),
        ],
      ),
    ),
  );
}

这样子我们就已经写好了一个按钮的样式,接下来就是把这个按钮渲染到页面中。渲染到页面上需要继承 StatelessWidget,StatefulWidget这两个类,然后我们对build方法重写,将想要渲染的widget返回就可以了,将我们的组件像搭积木一样,搭进去,把组件放到想放到的位置就行了 。

状态管理

在 Flutter 中,我们通过继承 StatelessWidget 或 StatefulWidget 来创建页面或组件,并在其中重写 build 方法,将各个 Widget 组合起来渲染到界面上。

这两者的主要区别在于是否需要维护可变状态:

对于不会随时间或交互变化的静态组件,使用 StatelessWidget;

对于需要根据用户操作或数据变化动态更新界面的组件,使用 StatefulWidget。

原生的状态管理

因为Widget是不可变的,因为本身widget就是对于界面的静态描述,Flutter 每次刷新 UI 时(例如 setState() 调用),会重新执行 build() 方法:

build() 返回一棵新的 Widget 树。

这时候框架会自动比较:

  • 旧的 Widget 树
  • 新的 Widget 树

然后找到差异,最小化更新底层的 RenderObject。

这种 diff 策略要求:

Widget 必须是纯描述、无副作用、不可变的。

less 复制代码
class TodoPage extends StatefulWidget {
  @override
  State<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  final TextEditingController _textController = TextEditingController();
  List<String> todos = [];

  void addTodo(String task) {
    if (task.isEmpty) return;
    setState(() {
      todos.add(task);
    });
    _textController.clear();
  }

  void removeTodo(int index) {
    setState(() {
      todos.removeAt(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: InputDecoration(
                      hintText: "添加新任务",
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () => addTodo(_textController.text),
                  child: const Text("添加"),
                )
              ],
            ),
            const SizedBox(height: 16),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (_, index) {
                  return ListTile(
                    title: Text(todos[index]),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => removeTodo(index),
                    ),
                  );
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

使用GetX进行状态管理

less 复制代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';

// ----------------- Todo 数据模型 -----------------
class Todo {
  String title;
  bool done;

  Todo({required this.title, this.done = false});
}

// ----------------- 控制器 -----------------
class TodoController extends GetxController {
  var todos = <Todo>[].obs;

  void addTodo(String title) {
    if (title.trim().isNotEmpty) {
      todos.add(Todo(title: title.trim()));
    }
  }

  void toggleDone(int index) {
    todos[index].done = !todos[index].done;
    todos.refresh();
  }

  void deleteTodo(int index) {
    todos.removeAt(index);
  }

  int get completed => todos.where((t) => t.done).length;
  int get pending => todos.length - completed;
}

// ----------------- 页面 -----------------
class TodoListPage extends StatelessWidget {
  TodoListPage({super.key});

  final TodoController controller = Get.put(TodoController());
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF7F9FB),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              // 标题
              const Text(
                "My Todo List",
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF004C6D),
                ),
              ),
              const SizedBox(height: 16),

              // 添加任务输入框
              Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _textController,
                      decoration: InputDecoration(
                        hintText: "添加新任务",
                        filled: true,
                        fillColor: Colors.white,
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(12),
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  ElevatedButton(
                    onPressed: () {
                      controller.addTodo(_textController.text);
                      _textController.clear();
                    },
                    style: ElevatedButton.styleFrom(
                      minimumSize: const Size(0, 55), // 高度 50
                      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: const Text("添加"),
                  )
                ],
              ),
              const SizedBox(height: 24),

              // Todo列表
              Expanded(
                child: Obx(() {
                  if (controller.todos.isEmpty) {
                    return const Center(
                      child: Text("暂无任务,快去添加吧!",
                          style: TextStyle(color: Colors.grey)),
                    );
                  }

                  return ListView.separated(
                    itemCount: controller.todos.length,
                    separatorBuilder: (_, __) => const SizedBox(height: 8),
                    itemBuilder: (context, index) {
                      final todo = controller.todos[index];
                      return Container(
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(12),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black12,
                              blurRadius: 4,
                              offset: const Offset(0, 2),
                            )
                          ],
                        ),
                        child: ListTile(
                          leading: Checkbox(
                            value: todo.done,
                            onChanged: (_) => controller.toggleDone(index),
                            activeColor: const Color(0xFF004C6D),
                          ),
                          title: Text(
                            todo.title,
                            style: TextStyle(
                              fontSize: 16,
                              decoration: todo.done
                                  ? TextDecoration.lineThrough
                                  : TextDecoration.none,
                              color: todo.done ? Colors.grey : Colors.black87,
                            ),
                          ),
                          trailing: IconButton(
                            icon: const Icon(Icons.delete_outline),
                            onPressed: () => controller.deleteTodo(index),
                            color: Colors.redAccent,
                          ),
                        ),
                      );
                    },
                  );
                }),
              ),

              // 已完成/未完成统计
              Obx(() => Padding(
                padding: const EdgeInsets.symmetric(vertical: 16),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      "未完成: ${controller.pending}",
                      style: const TextStyle(fontSize: 16),
                    ),
                    const SizedBox(width: 24),
                    Text(
                      "已完成: ${controller.completed}",
                      style: const TextStyle(fontSize: 16, color: Colors.green),
                    ),
                  ],
                ),
              )),
            ],
          ),
        ),
      ),
    );
  }
}

Demo

总结

方法 特点 场景
提升状态(Lift State Up) 父组件维护状态,子组件通过参数和回调修改 父子关系清晰,状态只在父子间共享
GetX / Provider / Riverpod 控制器或状态类管理状态,子组件直接访问修改 复杂应用,多组件或跨页面共享状态

路由管理

getX 同样能够进行路由管理,路由管理就是对 app 的所有的页面进行一个管理,包含路由跳转的行为,路由守卫的拦截。

路由表的注册

php 复制代码
  // lib/routes/app_pages.dart
import 'package:get/get.dart';
import 'package:op_flutter/pages/State/StateWhat/state_what.dart';
import 'package:op_flutter/pages/chatRoom/chart_room.dart';
import 'package:op_flutter/pages/home/home_page.dart';
import 'package:op_flutter/pages/requestDemo/request_demo.dart';
import 'package:op_flutter/pages/routeDemo/route_demo.dart';
import 'package:op_flutter/pages/routeDemo/route_guard.dart';
import '../pages/login/login_page.dart';

  /// 所有路由路径定义
  class AppRoutes {
    static const login = '/login';
    static const home = '/home';
    static const demo = '/stateDemo';

    // 状态管理模块
    static const stateWhat = '/state/what';
    static const stateHow = '/state/how';

    // 路由管理模块
    static const routerWhat = '/router/what';
    static const routerHow = '/router/how';
    static const routerGuard = '/guard';

    // 网络请求模块
    static const networkWhat = '/network/what';

    static const sdkAbility = '/sdk';
  }

  /// 所有路由页面配置
  class AppPages {
    static final routes = [
      GetPage(
        name: AppRoutes.login, // 路由路径
        page: () => LoginPage(), // 对应页面
      ),
      GetPage(
        name: AppRoutes.home, // 路由路径
        page: () => HomePage(), // 对应页面
      ),
      GetPage(name: AppRoutes.stateWhat, page: () => StateWhat()),
      GetPage(name: AppRoutes.routerWhat, page: () => RouteShowcasePage()),
      GetPage(name: AppRoutes.routerGuard, page: () => TodoSummaryPage(), middlewares: [TodoGuard()]),
      GetPage(name: AppRoutes.networkWhat, page: () => RequestDemoPage()),
      GetPage(name: AppRoutes.sdkAbility, page: () => ChatRoomPage())
    ];
  }

路由表注入

scala 复制代码
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Login Demo',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.white, // 全局白色背景
        primarySwatch: Colors.blue,            // 可选,全局主题色
      ),
      initialRoute: AppRoutes.login, // 默认启动页
      getPages: AppPages.routes, // 路由表
    );
  }
}

那么现在就可以控制路由的跳转了。 跳转的方法和我们的 vue-router 用法类似。

scss 复制代码
方法	路由栈行为	是否可传参数	异步返回
Get.to()	push	✅	✅
Get.off()	替换当前	✅	✅
Get.offAll()	清空栈	✅	✅
Get.toNamed()	push	✅	✅
Get.offNamed()	替换当前	✅	✅
Get.offAllNamed()	清空栈	✅	✅
Get.back()	pop	✅	✅
如何携带参数进行跳转

方式	使用方法	特点
arguments	Get.to(() => Page(), arguments: {...})	传任意对象,灵活
路径参数	Get.toNamed('/page/:id')	URL 风格,方便 Deep Link / Web

路由守卫怎么使用

scala 复制代码
/// 路由守卫:当 Todo 列表为空时禁止进入目标页
class TodoGuard extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    final todoCtrl = Get.find<TodoController>();
    if (todoCtrl.todos.isEmpty) {
      // 显示提示
      Future.microtask(() {
        Get.snackbar(
          '访问受限',
          '请先添加至少一个 Todo 项!',
          snackPosition: SnackPosition.BOTTOM,
          backgroundColor: Colors.redAccent.withOpacity(0.8),
          colorText: Colors.white,
          margin: const EdgeInsets.all(12),
        );
      });
      return const RouteSettings(name: AppRoutes.routerWhat); // 返回当前页
    }
    return null; // 允许访问
  }
}

Demo

网络请求

网络请求类封装

dart 复制代码
import 'package:dio/dio.dart';

class DioManager {
  // 单例模式
  static final DioManager _instance = DioManager._internal();
  factory DioManager() => _instance;

  late Dio dio;

  DioManager._internal() {
    BaseOptions options = BaseOptions(
      baseUrl: 'https://jsonplaceholder.typicode.com/', // 全局 baseUrl
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      headers: {
        'Content-Type': 'application/json',
      },
    );

    dio = Dio(options);

    // 添加拦截器
    dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          print('📤 请求: ${options.method} ${options.path}');
          print('请求参数: ${options.data}');
          return handler.next(options);
        },
        onResponse: (response, handler) {
          print('📥 响应: ${response.data}');
          return handler.next(response);
        },
        onError: (DioError e, handler) {
          print('❌ 错误: ${e.message}');
          return handler.next(e);
        },
      ),
    );
  }

  /// GET 请求
  Future<Response> get(String path,
      {Map<String, dynamic>? queryParameters, CancelToken? cancelToken}) async {
    try {
      Response response = await dio.get(
        path,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
      );
      return response;
    } on DioError catch (e) {
      throw _handleError(e);
    }
  }

  /// POST 请求
  Future<Response> post(String path,
      {dynamic data, Map<String, dynamic>? queryParameters, CancelToken? cancelToken}) async {
    try {
      Response response = await dio.post(
        path,
        data: data,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
      );
      return response;
    } on DioError catch (e) {
      throw _handleError(e);
    }
  }

  /// 错误处理
  Exception _handleError(DioError e) {
    switch (e.type) {
      case DioErrorType.connectionTimeout:
        return Exception('连接超时');
      case DioErrorType.receiveTimeout:
        return Exception('接收超时');
      case DioErrorType.badResponse:
        return Exception('服务器错误: ${e.response?.statusCode}');
      case DioErrorType.cancel:
        return Exception('请求取消');
      default:
        return Exception('未知错误: ${e.message}');
    }
  }
}

Demo

扩展能力

Flutter 本质上只是一个 UI 框架,运行在宿主平台之上,Flutter 本身是无法提供一些系统能力,比如使用蓝牙、相机、GPS等,因此要在 Flutter 中调用这些能力就必须和原生平台进行通信。目前Flutter 已经支持 iOS、Android、Web、macOS、Windows、Linux等众多平台,要调用特定平台 API 就需要写插件。插件是一种特殊的包,和纯 dart 包主要区别是插件中除了dart代码,还包括特定平台的代码,比如 image_picker 插件可以在 iOS 和 Android 设备上访问相册和摄像头。

Demo演示

相关推荐
环信2 小时前
实战教程|快速上线音视频通话:手把手教你实现呼叫与接听全流程
前端
Dgua2 小时前
✨TypeScript快速入门第一篇:从基础到 any、unknown、never 的实战解析
前端
海云前端13 小时前
Vue3 大屏项目投屏功能开发:多显示器适配实践
前端
技术小丁3 小时前
使用 HTML + JavaScript 实现酒店订房日期选择器(附完整源码)
前端·javascript
hashiqimiya3 小时前
harmonyos的鸿蒙的跳转页面的部署
开发语言·前端·javascript
向日葵同志443303 小时前
使用@univerjs纯前端渲染excel, 显示图片、链接、样式
前端·react.js·excel
可别3903 小时前
使用Worker打包报错
前端·vue.js
GIS瞧葩菜3 小时前
【无标题】
开发语言·前端·javascript·cesium
T___T3 小时前
彻底搞懂 CSS 盒子模型 box-sizing:小白也能看懂的布局核心
前端·面试