mcpField
H
how-to-build-a-native-cross-platform-project-with-flutter

how-to-build-a-native-cross-platform-project-with-flutter

github.com/rodydavis开发6071 次安装2 周前更新
代码仓库

关于 how-to-build-a-native-cross-platform-project-with-flutter

介绍如何用 Flutter 构建面向移动端、Web 和桌面的项目,并通过条件导入选择平台实现及回退方案。

主要功能

  1. 01Flutter 跨平台项目设置
  2. 02按平台条件导入
  3. 03不支持平台的回退实现
  4. 04按平台打开 URL

适用场景

  1. 01在移动端和 Web 间共享 Flutter 代码
  2. 02将导入路由到平台专属实现
  3. 03在 Web 和移动端采用不同方式打开 URL

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rodydavis/skills --skill how-to-build-a-native-cross-platform-project-with-flutter

安装后在 Claude Code 中输入/how-to-build-a-native-cross-platform-project-with-flutter确认已加载。

详细介绍

如何使用 Flutter 构建原生跨平台项目

在同一个项目中导入 dart:html 和 dart:io!

简而言之:最终源码在这里。

到目前为止,你可以使用 Flutter 创建可在 iOS/Android、Web 和桌面端运行的项目,但只能共享纯 Dart 插件。

Flutter 在 Google I/O 上推出了 Flutter for web,当时它是一个临时分支,要求你将导入语句从 import 'package:flutter/material.dart'; 改为 import 'package:flutter_web/material.dart';。

你可以想象,这对代码库来说确实很麻烦,因为你必须创建一个分支并修改导入语句。这也意味着你无法导入需要在某个路径上运行或依赖 Flutter 的任何软件包。现在,合并终于完成了。你不再需要修改导入语句!

README 配图

现在你可以使用任何插件、运行调试器、创建包含 Web 文件夹的新 Flutter 项目、使用 Web 插件,等等……

免责声明

你需要使用最新版本的 Flutter 才能正常运行。

下载 Flutter

README 配图

如果你刚接触 Flutter,可以查看这份实用指南,了解如何逐步创建新项目。

README 配图

创建一个名为 flutter_x 的新项目,它应该如下所示:

README 配图

你也可以在这里下载起始项目。

你的代码应该如下所示:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

为了确保一切正常,请运行项目并在 iOS/Android 上进行测试。

README 配图

你应该能看到计数器应用正在运行,且工作正常。现在退出,然后在 Chrome 上运行。它应该会显示在设备列表中。你也可以从命令行运行 flutter run -d chrome。

README 配图

请注意,Web 端暂时还不支持热重载。

README 配图

现在你的项目应该如下所示。

打开 pubspec.yaml 并导入以下软件包。

dependencies:
  universal_html:
  url_launcher:

你也可以删除 pubspec.yaml 中自动生成的注释。

此时,你的 pubspec.yaml 应如下所示:

name: flutter_x
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

cupertino_icons: ^0.1.2
  universal_html: ^1.1.0
  url_launcher: ^5.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:

uses-material-design: true

默认情况下,如果你要检查设备是移动设备还是 Web 设备,在尝试导入不适用于该平台的插件时,会在编译时出错。为了解决这个问题,我们将使用动态导入。

README 配图

在 plugins 文件夹中创建一个 url_launcher 文件夹,并在其中创建 url_launcher.dart、mobile.dart、web.dart 和 unsupported.dart 文件。

在 url_launcher.dart 文件中添加以下内容:

export 'unsupported.dart'
    if (dart.library.html) 'web.dart'
    if (dart.library.io) 'mobile.dart';

这样会在运行时选择正确的文件,并在不受支持时提供回退方案。

为避免边缘情况,你需要为导入设置回退方案。在 unsupported.dart 中添加以下内容:

class UrlUtils {
  UrlUtils._();

static void open(String url, {String name}) {
    throw 'Platform Not Supported';
  }
}

要使其正常工作,三个文件中的 UrlUtils 类及其公开方法必须保持一致。请务必先设置 unsupported.dart,然后将该文件复制到 mobile.dart 和 web.dart,以确保没有拼写错误。

现在,你应该有 3 个文件,每个文件中的类都包含上述代码。

在 mobile.dart 中添加以下内容:

import 'package:url_launcher/url_launcher.dart';

class UrlUtils {
  UrlUtils._();

static void open(String url, {String name}) async {
    if (await canLaunch(url)) {
      await launch(url);
    }
  }
}

这会分别在 Safari 视图控制器或 Android 默认浏览器中打开链接。

在 web.dart 中添加以下内容:

import 'package:universal_html/prefer_universal/html.dart' as html;

class UrlUtils {
  UrlUtils._();

static void open(String url, {String name}) {
    html.window.open(url, name);
  }
}

这将在浏览器中打开一个新窗口,并转到指定链接。

在屏幕中央添加一个按钮。ui/home/screen.dart 应如下所示:

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: RaisedButton(
        child: Text('Open Flutter.dev'),
        onPressed: () {},
      )),
    );
  }
}

将 onPressed 更新为以下内容:

onPressed: () {
    try {
        UrlUtils.open('[https://flutter.dev'](https://flutter.dev'));
    } catch (e) {
        print('Error -> $e');
    }
},

现在导入 UrlUtils 时,请务必导入正确的 URI。

README 配图

请确保仅导入 import 'package:flutter_x/plugins/url_launcher/url_launcher.dart';。

你也可以使用相对导入。

你的 UI 代码现在应如下所示:

import 'package:flutter/material.dart';

import '../../plugins/url_launcher/url_launcher.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: RaisedButton(
        child: Text('Open Flutter.dev'),
        onPressed: () {
          try {
            UrlUtils.open('[https://flutter.dev'](https://flutter.dev'));
          } catch (e) {
            print('Error -> $e');
          }
        },
      )),
    );
  }
}

你的应用在 web 上应如下所示:

README 配图

点击按钮后……

README 配图

在 iOS/Android 上运行时,应用应如下所示:

README 配图

点击按钮后……

README 配图

恭喜!你完成了 🎉

README 配图

最终项目位于此处。

如有任何问题,请随时联系!

评论

支持 Markdown