前言:

因为Flutter集成Unity时所使用的插件对Flutter和Dart版本有要求,无法适配新版的Flutter,也就无法适配和新版Flutter一起的Dart3.0,所以第一步其实是先对项目组其他人写的Flutter项目进行降级,将Flutter3.27.4降级为Flutter3.3.10,同时其对应的Dart也从3.6.2降级为2.18.6,再将Unity项目在合适位置接入。


1.降级时遇到的问题

主要有两个,一是语法不兼容,Dart3.0和Dart2.0写法可能不一样,所以不能直接复制粘贴,需要检查并手动修改这些不兼容的语法,把3.0退回到2.0的风格。二是项目依赖的包可能不兼容,新Flutter项目可能依赖了一些只支持Dart3.0的包,如果有包不支持Dart2.18,需要修改pubspec.yaml中的依赖版本,降级到Dart 2支持的版本。所以我选择在粘贴代码时查看修改语法,并手动添加依赖。

不过降级过程中只遇到了两处错误:

1.Dart2.0和3.0不兼容

//lib/main.dart
onGenerateRoute: (RouteSettings settings) {
        DebugLog.info('Generating route for: ${settings.name} with arguments: ${settings.arguments}');
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (context) => MainScaffold());
          case '/details':
            if (settings.arguments is Map<String, dynamic>) {
              final args = settings.arguments as Map<String, dynamic>;
              final int? itemId = args['id'] as int?; // 安全地获取和转换参数
              if (itemId != null) {
                return null;
              }
            }

          case '/settings':

          default:
          
        }
        return null;
      },

在 Dart 2 版本里,switch-case 不允许case 穿透(fall-through),也就是说:

  1. 每个 case 代码块必须用 break、return、throw 或类似语句明确终止。
  2. 如果没有,编译器会报错。

但是Dart 3 引入了对 switch 语句更灵活的语法支持,特别是支持了模式匹配的增强,并且允许了更加灵活的 case 块语法。具体体现在:

  1. Dart 3 允许 case 代码块不写 break 或 return,只要不发生隐式穿透。
  2. 只要没有明确的穿透行为,Dart 3 会自动理解控制流的结束。
  3. 有点类似其他现代语言中更灵活的 switch-case。

所以修改加上明确的终止语句,如下:

//lib/mian.dart

onGenerateRoute: (RouteSettings settings) {
          DebugLog.info('Generating route for: ${settings.name} with arguments: ${settings.arguments}');
          switch (settings.name) {
            case '/':
              return MaterialPageRoute(builder: (context) => MainScaffold());

            case '/details':
              if (settings.arguments is Map<String, dynamic>) {
                final args = settings.arguments as Map<String, dynamic>;
                final int? itemId = args['id'] as int?;
                if (itemId != null) {
                  // 这里应该返回一个有效的页面路由:
                  return MaterialPageRoute(builder: (context) => DetailsPage(itemId: itemId));
                } else {
                  // 如果itemId为空,可以返回错误页面或首页,避免返回null
                  return MaterialPageRoute(builder: (context) => MainScaffold());
                }
              } else {
                // 参数类型不对,跳转默认页面
                return MaterialPageRoute(builder: (context) => MainScaffold());
              }

            case '/settings':
              return MaterialPageRoute(builder: (context) => SettingPage());

            default:
            // 未匹配到路由时,显示一个默认页面或返回null
              return MaterialPageRoute(
                  builder: (context) => Scaffold(
                    appBar: AppBar(title: Text('页面未找到')),
                    body: Center(child: Text('未找到路由: ${settings.name}')),
                  ));
          }
        },
//lib/details_page.dart
import 'package:flutter/material.dart';
import 'package:lingo_pals/utils/app_colors.dart';

class DetailsPage extends StatelessWidget {
  final int itemId;
  const DetailsPage({required this.itemId, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('详情页')),
      body: Center(
        child: Text(
          'Item ID: $itemId',
          style: TextStyle(
            fontSize: 24,
            color: AppColors.textPrimary,
          ),
        ),
      ),
    );
  }
}

2.Flutter渲染问题

在新版本中,emoji 被自动缩放为适合 Text 所在布局的默认字体大小。 在旧版本中,emoji 被当作普通字符处理,使用系统字体直接渲染,导致大小不一致、对齐失常等问题。正常应该是P2:

 

先找到渲染emoji的代码

Text('👓', style: TextStyle(fontSize: 80)),

给 emoji 加入固定字体 + 调整大小

Text(
            '👓',
            style: TextStyle(
              fontSize: 60,
              fontFamilyFallback: ['EmojiOne', 'NotoColorEmoji', 'AppleColorEmoji'], // 针对不同平台 fallback
            ),
          ),

然后把所有有关emoji的地方找到并改成新代码即可 

2.接入Unity页面

使用插件的方法前文已经写过,在该项目里重新配置一遍就行。然后将Unity界面接到项目的AR实景界面:

先添加一个打开unity窗口的按钮

//lib/ar_page.dart

import 'package:flutter/material.dart';
import 'package:lingo_pals/view/android/base_widget/base_app_bar.dart';
import 'package:lingo_pals/view/android/base_widget/new_scaffold.dart';
import 'package:lingo_pals/view/android/feature_pages/unity_view_page.dart';

import '../../../utils/app_colors.dart';


class ArPage extends StatelessWidget{
  const ArPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BasePage(
        appBar: BaseAppBar(
          titleWidget: Container(),
          actions: [],
          leading: IconButton(
            icon: const Icon(Icons.arrow_back),
            onPressed: () => Navigator.of(context).pop(),
          ),
        ),
        pageBody: ArField()
    );
  }
}

class ArField extends StatelessWidget {
  const ArField({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            '👓',
            style: TextStyle(
              fontSize: 60,
              fontFamilyFallback: ['EmojiOne', 'NotoColorEmoji', 'AppleColorEmoji'], // 针对不同平台 fallback
            ),
          ),
          SizedBox(height: 20),
          Text(
            'AR页面',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: AppColors.textPrimary,
            ),
          ),
          SizedBox(height: 10),
          Text(
            '这里将启动AR功能',
            style: TextStyle(
              fontSize: 16,
              color: AppColors.textSecondary,
            ),
          ),
          const SizedBox(height: 30),
          ElevatedButton.icon(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(builder: (_) => const UnityViewPage()),
              );
            },
            icon: const Icon(Icons.play_arrow),
            label: const Text('启动 AR 功能'),
          ),
        ],
      ),
    );
  }
}

启动unity窗口渲染: 

//lib/unity_view_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_unity_widget/flutter_unity_widget.dart';

class UnityViewPage extends StatefulWidget {
  const UnityViewPage({Key? key}) : super(key: key);

  @override
  State<UnityViewPage> createState() => _UnityDemoScreenState();
}

class _UnityDemoScreenState extends State<UnityViewPage> {
  static final GlobalKey<ScaffoldState> _scaffoldKey =
  GlobalKey<ScaffoldState>();
  UnityWidgetController? _unityWidgetController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: SafeArea(
        bottom: false,
        child: WillPopScope(
          onWillPop: () async {
         
            return true;
          },
          child: Container(
            color: Colors.yellow,
            child: UnityWidget(
              onUnityCreated: onUnityCreated,
            ),
          ),
        ),
      ),
    );
  }
  
  void onUnityCreated(controller) {
    _unityWidgetController = controller;
  }
}

3.效果

接入项目

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐