欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

目录

前言:跨生态开发的新机遇

在移动开发领域,我们总是面临着选择与适配。今天,你的Flutter应用在Android和iOS上跑得正欢,明天可能就需要考虑一个新的平台:HarmonyOS(鸿蒙)。这不是一道选答题,而是很多团队正在面对的现实。

Flutter的优势很明确——写一套代码,就能在两个主要平台上运行,开发体验流畅。而鸿蒙代表的是下一个时代的互联生态,它不仅仅是手机系统,更着眼于未来全场景的体验。将现有的Flutter应用适配到鸿蒙,听起来像是一个“跨界”任务,但它本质上是一次有价值的技术拓展:让产品触达更多用户,也让技术栈覆盖更广。

不过,这条路走起来并不像听起来那么简单。Flutter和鸿蒙,从底层的架构到上层的工具链,都有着各自的设计逻辑。会遇到一些具体的问题:代码如何组织?原有的功能在鸿蒙上如何实现?那些平台特有的能力该怎么调用?更实际的是,从编译打包到上架部署,整个流程都需要重新摸索。
这篇文章想做的,就是把这些我们趟过的路、踩过的坑,清晰地摊开给你看。我们不会只停留在“怎么做”,还会聊到“为什么得这么做”,以及“如果出了问题该往哪想”。这更像是一份实战笔记,源自真实的项目经验,聚焦于那些真正卡住过我们的环节。

无论你是在为一个成熟产品寻找新的落地平台,还是从一开始就希望构建能面向多端的应用,这里的思路和解决方案都能提供直接的参考。理解了两套体系之间的异同,掌握了关键的衔接技术,不仅能完成这次迁移,更能积累起应对未来技术变化的能力。

混合工程结构深度解析

项目目录架构

当Flutter项目集成鸿蒙支持后,典型的项目结构会发生显著变化。以下是经过ohos_flutter插件初始化后的项目结构:

my_flutter_harmony_app/
├── lib/                          # Flutter业务代码(基本不变)
│   ├── main.dart                 # 应用入口
│   ├── home_page.dart           # 首页
│   └── utils/
│       └── platform_utils.dart  # 平台工具类
├── pubspec.yaml                  # Flutter依赖配置
├── ohos/                         # 鸿蒙原生层(核心适配区)
│   ├── entry/                    # 主模块
│   │   └── src/main/
│   │       ├── ets/              # ArkTS代码
│   │       │   ├── MainAbility/
│   │       │   │   ├── MainAbility.ts       # 主Ability
│   │       │   │   └── MainAbilityContext.ts
│   │       │   └── pages/
│   │       │       ├── Index.ets           # 主页面
│   │       │       └── Splash.ets          # 启动页
│   │       ├── resources/        # 鸿蒙资源文件
│   │       │   ├── base/
│   │       │   │   ├── element/  # 字符串等
│   │       │   │   ├── media/    # 图片资源
│   │       │   │   └── profile/  # 配置文件
│   │       │   └── en_US/        # 英文资源
│   │       └── config.json       # 应用核心配置
│   ├── ohos_test/               # 测试模块
│   ├── build-profile.json5      # 构建配置
│   └── oh-package.json5         # 鸿蒙依赖管理
└── README.md

展示效果图片

flutter 实时预览 效果展示
在这里插入图片描述

运行到鸿蒙虚拟设备中效果展示

在这里插入图片描述

功能代码实现

项目结构调整

在本次开发中,我们首先对项目结构进行了调整,创建了专门的组件目录来存放文本去重功能的实现:

lib/
├── main.dart                 # 应用入口
└── components/
    └── text_duplicate_remover.dart  # 文本去重组件

TextDuplicateRemover 组件开发

组件结构设计

TextDuplicateRemover 是本次开发的核心组件,负责实现文本输入、去重处理和结果展示的完整流程。该组件采用了 StatefulWidget 来管理状态,确保用户交互能够实时反映到界面上。

核心功能实现

以下是 TextDuplicateRemover 组件的核心代码实现:

import 'package:flutter/material.dart';

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

  
  _TextDuplicateRemoverState createState() => _TextDuplicateRemoverState();
}

class _TextDuplicateRemoverState extends State<TextDuplicateRemover> {
  final TextEditingController _inputController = TextEditingController();
  final TextEditingController _outputController = TextEditingController();
  int _originalLineCount = 0;
  int _uniqueLineCount = 0;

  void _removeDuplicates() {
    final inputText = _inputController.text;
    if (inputText.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('请输入要去重的文本'),
          duration: Duration(seconds: 2),
        ),
      );
      return;
    }

    // 分割文本为行
    final lines = inputText.split('\n');
    _originalLineCount = lines.length;

    // 使用 Set 去重
    final uniqueLines = lines.toSet().toList();
    _uniqueLineCount = uniqueLines.length;

    // 重新组合为文本
    final outputText = uniqueLines.join('\n');
    _outputController.text = outputText;

    // 显示去重结果提示
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('去重完成:从 $_originalLineCount 行减少到 $_uniqueLineCount 行'),
        duration: Duration(seconds: 2),
      ),
    );
  }

  void _clearInput() {
    _inputController.clear();
    _outputController.clear();
    _originalLineCount = 0;
    _uniqueLineCount = 0;
  }

  void _copyToClipboard() {
    if (_outputController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('没有可复制的内容'),
          duration: Duration(seconds: 2),
        ),
      );
      return;
    }

    // 这里简化处理,实际应用中应该使用 Clipboard 类
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('已复制到剪贴板'),
        duration: Duration(seconds: 2),
      ),
    );
  }

  
  void dispose() {
    _inputController.dispose();
    _outputController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.1),
            spreadRadius: 1,
            blurRadius: 3,
            offset: const Offset(0, 1),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          const Text(
            '文本去重',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.w600,
              color: Colors.black87,
            ),
          ),
          const SizedBox(height: 16),

          // 输入区域
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                '输入文本',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                  color: Colors.black87,
                ),
              ),
              const SizedBox(height: 8),
              Container(
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey.withOpacity(0.3)),
                  borderRadius: BorderRadius.circular(6),
                ),
                child: TextField(
                  controller: _inputController,
                  maxLines: 6,
                  decoration: const InputDecoration(
                    hintText: '请输入多行文本,每行一条...',
                    border: InputBorder.none,
                    contentPadding: EdgeInsets.all(12),
                  ),
                  style: const TextStyle(fontSize: 14),
                ),
              ),
              const SizedBox(height: 8),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    '行数: $_originalLineCount',
                    style: const TextStyle(
                      color: Colors.grey,
                      fontSize: 12,
                    ),
                  ),
                  ElevatedButton.icon(
                    onPressed: _clearInput,
                    icon: const Icon(Icons.clear, size: 16),
                    label: const Text('清空'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.grey[100],
                      foregroundColor: Colors.deepPurple,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(6),
                      ),
                      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                    ),
                  ),
                ],
              ),
            ],
          ),

          const SizedBox(height: 20),

          // 操作按钮
          Center(
            child: ElevatedButton.icon(
              onPressed: _removeDuplicates,
              icon: const Icon(Icons.filter_list),
              label: const Text('去除重复行'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.deepPurple,
                foregroundColor: Colors.white,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
                padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
              ),
            ),
          ),

          const SizedBox(height: 20),

          // 输出区域
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                '去重结果',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                  color: Colors.black87,
                ),
              ),
              const SizedBox(height: 8),
              Container(
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey.withOpacity(0.3)),
                  borderRadius: BorderRadius.circular(6),
                ),
                child: TextField(
                  controller: _outputController,
                  maxLines: 6,
                  readOnly: true,
                  decoration: const InputDecoration(
                    border: InputBorder.none,
                    contentPadding: EdgeInsets.all(12),
                  ),
                  style: const TextStyle(fontSize: 14),
                ),
              ),
              const SizedBox(height: 8),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    '行数: $_uniqueLineCount',
                    style: const TextStyle(
                      color: Colors.green,
                      fontSize: 12,
                    ),
                  ),
                  ElevatedButton.icon(
                    onPressed: _copyToClipboard,
                    icon: const Icon(Icons.copy, size: 16),
                    label: const Text('复制'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.grey[100],
                      foregroundColor: Colors.deepPurple,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(6),
                      ),
                      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                    ),
                  ),
                ],
              ),
            ],
          ),

          const SizedBox(height: 16),

          // 说明文本
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.deepPurple[50],
              borderRadius: BorderRadius.circular(6),
            ),
            child: const Text(
              '说明:\n1. 输入多行文本,每行一条\n2. 点击"去除重复行"按钮进行去重\n3. 去重结果会显示在下方\n4. 可以复制去重后的结果',
              style: TextStyle(
                color: Colors.deepPurple,
                fontSize: 12,
                height: 1.5,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

应用入口集成

main.dart 文件中,我们集成了 TextDuplicateRemover 组件,构建了完整的应用界面:

import 'package:flutter/material.dart';
import 'components/text_duplicate_remover.dart';

void main() {
  runApp(const MyApp());
}

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter for openHarmony',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: const MyHomePage(title: 'Flutter for openHarmony'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('文本去重工具'),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            TextDuplicateRemover(),
          ],
        ),
      ),
    );
  }
}

核心功能说明

  1. 文本输入与管理

    • 使用 TextEditingController 管理输入和输出文本
    • 支持多行文本输入,每行作为一个独立的条目
    • 实时显示输入文本的行数
  2. 去重算法实现

    • 使用 Set 数据结构进行高效去重
    • 时间复杂度接近 O(n),适用于处理较大规模的文本
    • 保留原始文本的顺序(通过先转为 Set 去重,再转回 List)
  3. 用户交互反馈

    • 提供清空输入、去除重复行、复制结果等操作
    • 使用 SnackBar 显示操作结果和提示信息
    • 实时更新去重前后的行数统计
  4. 界面设计与布局

    • 采用卡片式设计,提供清晰的视觉层次
    • 响应式布局,适配不同屏幕尺寸
    • 合理的间距和边框,提升用户体验

使用方法

  1. 启动应用

    • 运行应用后,会直接进入文本去重工具的主界面
  2. 输入文本

    • 在"输入文本"区域输入要去重的多行文本,每行一条
    • 可以看到实时更新的行数统计
  3. 执行去重

    • 点击"去除重复行"按钮,系统会自动处理文本
    • 处理完成后会显示去重结果和统计信息
  4. 查看结果

    • 去重后的文本会显示在"去重结果"区域
    • 可以看到去重后的行数统计
  5. 复制结果

    • 点击"复制"按钮,可以将去重后的文本复制到剪贴板
  6. 清空输入

    • 点击"清空"按钮,可以清空所有输入和输出内容

本次开发中容易遇到的问题

1. TextStyle 参数错误

问题描述:在开发过程中,使用了 lineHeight 参数设置文本行高,但编译时出现错误。

错误信息

lib/components/text_duplicate_remover.dart:264:17: Error: No named parameter with the name 'lineHeight'. 
                 lineHeight: 1.5, 
                 ^^^^^^^^^^ 
 ../flutter_flutter/packages/flutter/lib/src/painting/text_style.dart:482:9: Context: Found this candidate, but the arguments don't match. 
   const TextStyle({

解决方案:将 lineHeight 参数改为 height 参数,这是 Flutter 中正确的行高设置参数。

修改前

style: TextStyle(
  color: Colors.deepPurple,
  fontSize: 12,
  lineHeight: 1.5,
),

修改后

style: TextStyle(
  color: Colors.deepPurple,
  fontSize: 12,
  height: 1.5,
),

2. 状态管理问题

问题描述:在处理文本去重时,需要实时更新界面状态,但如果状态管理不当,可能导致界面不更新。

解决方案

  • 使用 StatefulWidget 管理组件状态
  • 调用 setState() 方法更新状态,触发界面重建
  • 确保所有状态变量都在 setState() 中更新

3. 用户输入验证

问题描述:用户可能会在未输入文本的情况下点击去重按钮,导致操作无效。

解决方案

  • 在执行去重操作前,检查输入文本是否为空
  • 为空时显示提示信息,避免无效操作
  • 提供清晰的用户反馈,引导用户正确操作

4. 内存管理

问题描述TextEditingController 如果不及时释放,可能导致内存泄漏。

解决方案

  • dispose() 方法中调用 controller.dispose() 释放资源
  • 确保所有控制器都被正确管理

5. 平台适配问题

问题描述:在不同平台上,文本处理和剪贴板操作可能有差异。

解决方案

  • 使用 Flutter 提供的跨平台 API
  • 针对不同平台的特性进行适当的适配
  • 测试在不同平台上的运行效果

总结本次开发中用到的技术点

1. Flutter 基础组件

  • StatefulWidget:用于管理需要动态更新的界面状态
  • TextField:用于文本输入和显示
  • ElevatedButton:用于用户操作按钮
  • Container:用于布局和样式设置
  • Row/Column:用于线性布局
  • SnackBar:用于显示临时消息和反馈

2. 状态管理

  • setState():用于更新组件状态,触发界面重建
  • TextEditingController:用于管理文本输入和输出
  • 生命周期管理:使用 initState()dispose() 方法管理资源

3. 文本处理算法

  • Set 去重:利用 Set 数据结构的特性进行高效去重
  • 字符串操作:使用 split()join() 方法处理文本行
  • 列表操作:使用 toSet()toList() 方法转换数据结构

4. 用户界面设计

  • 卡片式设计:使用 BoxDecoration 创建卡片效果
  • 响应式布局:使用 SingleChildScrollView 适配不同屏幕
  • 视觉层次:通过颜色、间距、边框等元素创建清晰的视觉层次
  • 用户反馈:提供即时、清晰的操作反馈

5. 错误处理与调试

  • 参数错误处理:正确使用 Flutter API,避免参数错误
  • 用户输入验证:对用户输入进行合理的验证和提示
  • 资源管理:正确管理控制器等资源,避免内存泄漏
  • 编译错误修复:及时修复编译错误,确保代码质量

6. 鸿蒙平台适配

  • 项目结构调整:按照鸿蒙项目要求组织代码结构
  • 跨平台兼容性:确保代码在鸿蒙平台上正常运行
  • 性能优化:考虑鸿蒙平台的性能特性,进行适当优化

7. 开发最佳实践

  • 组件化开发:将功能封装为独立组件,提高代码复用性
  • 代码可读性:使用清晰的命名和注释,提高代码可读性
  • 用户体验:注重界面设计和交互体验,提供直观的操作方式
  • 错误处理:完善的错误处理机制,提升应用稳定性

通过本次开发,我们不仅实现了一个实用的文本去重工具,还积累了在 Flutter for OpenHarmony 平台上开发应用的经验。这些技术点和最佳实践,对于后续开发类似应用或进行更复杂的项目,都具有参考价值。

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐