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

目录

前言

在移动应用开发中,搜索功能是一项常见且重要的能力。传统的精确搜索要求用户输入完全匹配的内容,这给用户带来了不便。模糊搜索技术通过计算字符串之间的相似度,能够在用户输入不完整或存在拼写错误的情况下,仍然找到相关的结果,大大提升了用户体验。本次开发旨在将Flutter生态中的fuzzywuzzy库适配到OpenHarmony平台,实现模糊搜索功能,为开发者提供一套简洁、高效的搜索解决方案。

随着OpenHarmony生态的不断发展,越来越多的Flutter开发者希望将现有的应用迁移到鸿蒙平台。然而,由于平台差异和API限制,直接使用Flutter的第三方库可能会遇到各种问题。本次适配工作不仅解决了fuzzywuzzy库在OpenHarmony上的使用问题,也为其他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 实时预览 效果展示
在这里插入图片描述

运行到鸿蒙虚拟设备中效果展示
在这里插入图片描述

功能代码实现

1. fuzzywuzzy 库模拟实现

为了在OpenHarmony平台上实现模糊搜索功能,我们首先创建了一个模拟的fuzzywuzzy库,实现了核心的字符串相似度计算和模糊搜索功能。

核心实现代码

// 模拟 fuzzywuzzy 库的实现
class FuzzyWuzzy {
  // 计算两个字符串之间的相似度(0-100)
  int ratio(String s1, String s2) {
    if (s1.isEmpty || s2.isEmpty) return 0;
    if (s1 == s2) return 100;
    
    // 简单的相似度计算实现
    // 实际的 fuzzywuzzy 使用更复杂的算法,如 Levenshtein 距离
    final length = s1.length > s2.length ? s1.length : s2.length;
    int matches = 0;
    
    for (int i = 0; i < s1.length && i < s2.length; i++) {
      if (s1[i] == s2[i]) {
        matches++;
      }
    }
    
    return (matches / length * 100).round();
  }
  
  // 模糊搜索,返回匹配度最高的结果
  List<Map<String, dynamic>> extract(
    String query, 
    List<String> choices, 
    {int limit = 5, int cutoff = 0}
  ) {
    final results = <Map<String, dynamic>>[];
    
    for (final choice in choices) {
      final score = ratio(query, choice);
      if (score >= cutoff) {
        results.add({
          'value': choice,
          'score': score
        });
      }
    }
    
    // 按分数排序
    results.sort((a, b) => (b['score'] as int).compareTo(a['score'] as int));
    
    // 返回限制数量的结果
    return results.take(limit).toList();
  }
  
  // 提取单个最佳匹配
  Map<String, dynamic>? extractOne(String query, List<String> choices, {int cutoff = 0}) {
    final results = extract(query, choices, limit: 1, cutoff: cutoff);
    return results.isNotEmpty ? results[0] : null;
  }
}

实现说明

  1. 核心功能

    • ratio 方法:计算两个字符串之间的相似度,返回0-100的分数
    • extract 方法:根据搜索 query 从 choices 列表中提取匹配度最高的结果
    • extractOne 方法:提取单个最佳匹配结果
  2. 实现细节

    • 使用简单的字符匹配算法计算相似度
    • 实际的 fuzzywuzzy 库使用更复杂的 Levenshtein 距离算法
    • 支持设置结果数量限制和匹配度阈值
    • 结果按匹配度分数降序排序
  3. 使用方法

    final fuzzyWuzzy = FuzzyWuzzy();
    // 计算字符串相似度
    final score = fuzzyWuzzy.ratio('北京', '北京市');
    // 模糊搜索
    final results = fuzzyWuzzy.extract('苹果', ['苹果', '香蕉', '橙子'], limit: 2);
    // 提取单个最佳匹配
    final bestMatch = fuzzyWuzzy.extractOne('狗', ['猫', '狗', '牛']);
    

2. FuzzySearchWidget 组件实现

为了提供直观的用户界面,我们创建了 FuzzySearchWidget 组件,用于展示模糊搜索功能。

核心实现代码

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

class FuzzySearchWidget extends StatefulWidget {
  const FuzzySearchWidget({super.key});

  
  State<FuzzySearchWidget> createState() => _FuzzySearchWidgetState();
}

class _FuzzySearchWidgetState extends State<FuzzySearchWidget> {
  final TextEditingController _searchController = TextEditingController();
  List<Map<String, dynamic>> _searchResults = [];
  bool _isSearching = false;
  
  // 搜索匹配源数据
  final List<String> _items = [
    // 中国主要城市
    '北京市', '上海市', '广州市', '深圳市', '杭州市',
    '成都市', '武汉市', '南京市', '西安市', '重庆市',
    '天津市', '苏州市', '郑州市', '长沙市', '沈阳市',
    // 常见水果
    '苹果', '香蕉', '橙子', '葡萄', '草莓',
    '西瓜', '桃子', '梨', '芒果', '菠萝',
    // 常见动物
    '猫', '狗', '牛', '羊', '马',
    '鸡', '鸭', '鱼', '鸟', '兔子',
    // 常见颜色
    '红色', '蓝色', '绿色', '黄色', '橙色',
    '紫色', '黑色', '白色', '灰色', '棕色',
  ];

  final FuzzyWuzzy _fuzzyWuzzy = FuzzyWuzzy();

  void _performSearch() {
    if (_searchController.text.isEmpty) {
      setState(() {
        _searchResults = [];
      });
      return;
    }

    setState(() {
      _isSearching = true;
    });

    // 模拟搜索延迟
    Future.delayed(const Duration(milliseconds: 300), () {
      final results = _fuzzyWuzzy.extract(
        _searchController.text,
        _items,
        limit: 10,
        cutoff: 30,
      );

      setState(() {
        _searchResults = results;
        _isSearching = false;
      });
    });
  }

  
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          const Text(
            '模糊搜索',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 20.0),
          TextField(
            controller: _searchController,
            decoration: InputDecoration(
              labelText: '输入搜索内容',
              border: const OutlineInputBorder(),
              suffixIcon: IconButton(
                icon: const Icon(Icons.search),
                onPressed: _performSearch,
              ),
            ),
            onSubmitted: (_) => _performSearch(),
          ),
          const SizedBox(height: 20.0),
          if (_isSearching)
            const Center(
              child: CircularProgressIndicator(),
            ),
          if (!_isSearching && _searchResults.isNotEmpty)
            Expanded(
              child: ListView.builder(
                itemCount: _searchResults.length,
                itemBuilder: (context, index) {
                  final result = _searchResults[index];
                  return Card(
                    margin: const EdgeInsets.symmetric(vertical: 5.0),
                    child: ListTile(
                      title: Text(result['value']),
                      trailing: Container(
                        padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2.0),
                        decoration: BoxDecoration(
                          color: Colors.blue.shade100,
                          borderRadius: BorderRadius.circular(10.0),
                        ),
                        child: Text(
                          '${result['score']}%',
                          style: const TextStyle(
                            fontSize: 12.0,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                      onTap: () {
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text('选择了: ${result['value']}'),
                            duration: const Duration(seconds: 1),
                          ),
                        );
                      },
                    ),
                  );
                },
              ),
            ),
          if (!_isSearching && _searchResults.isEmpty && _searchController.text.isNotEmpty)
            const Center(
              child: Text('没有找到匹配的结果'),
            ),
          const SizedBox(height: 30.0),
          Card(
            elevation: 2.0,
            margin: const EdgeInsets.symmetric(horizontal: 0),
            child: Padding(
              padding: const EdgeInsets.all(15.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '功能说明:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 14.0,
                    ),
                  ),
                  const SizedBox(height: 8.0),
                  Text('• 输入搜索内容,点击搜索按钮或按回车键进行模糊搜索'),
                  Text('• 搜索结果按匹配度排序,显示匹配百分比'),
                  Text('• 点击搜索结果可查看详细信息'),
                  Text('• 支持中文模糊搜索'),
                  const SizedBox(height: 15.0),
                  const Text(
                    '搜索匹配源:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 14.0,
                    ),
                  ),
                  const SizedBox(height: 8.0),
                  Text('• 中国主要城市:北京市、上海市、广州市、深圳市、杭州市等'),
                  Text('• 常见水果:苹果、香蕉、橙子、葡萄、草莓等'),
                  Text('• 常见动物:猫、狗、牛、羊、马等'),
                  Text('• 常见颜色:红色、蓝色、绿色、黄色、橙色等'),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

实现说明

  1. 核心功能

    • 提供搜索输入界面
    • 实现模糊搜索功能
    • 展示搜索结果,按匹配度排序
    • 显示匹配百分比
    • 支持点击搜索结果查看详细信息
  2. 实现细节

    • 使用 StatefulWidget 管理组件状态
    • 实现 _performSearch 方法处理搜索逻辑
    • 使用 Future.delayed 模拟搜索延迟,提升用户体验
    • 提供丰富的搜索匹配源数据,包括城市、水果、动物和颜色
    • 实现错误处理,显示无结果提示
    • 在界面上展示搜索匹配源,方便用户了解可搜索内容
  3. 使用方法

    • 在需要使用模糊搜索功能的页面中直接引入该组件
    • 例如在 main.dart 中:
    import 'fuzzy_search_widget.dart';
    
    // 在 build 方法中使用
    
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: const FuzzySearchWidget(),
      );
    }
    

3. 首页集成实现

将模糊搜索组件集成到应用首页,直接展示功能效果。

核心实现代码

import 'package:flutter/material.dart';
import 'fuzzy_search_widget.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: Text(widget.title),
      ),
      body: const FuzzySearchWidget(),
    );
  }
}

实现说明

  1. 集成方式

    • main.dart 中导入 FuzzySearchWidget 组件
    • MyHomePagebuild 方法中直接使用该组件
    • 无需按钮跳转,直接在首页展示功能
  2. 用户体验

    • 简洁明了的界面布局
    • 直观的操作流程
    • 实时的反馈机制
    • 美观的视觉效果

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

  1. 平台兼容性问题

    • 问题:Flutter的第三方库在OpenHarmony平台上可能存在兼容性问题
    • 解决方案:通过模拟实现核心功能,避免直接依赖平台特定的API,确保在OpenHarmony上正常运行
  2. 搜索算法性能问题

    • 问题:当搜索匹配源数据量较大时,可能会遇到性能问题
    • 解决方案:在实际应用中,可以考虑使用更高效的搜索算法,如二分查找或索引技术,或者对搜索过程进行异步处理
  3. 中文搜索问题

    • 问题:中文模糊搜索可能需要特殊处理,如分词、拼音匹配等
    • 解决方案:在实际应用中,可以集成专门的中文处理库,支持拼音搜索和分词搜索
  4. 搜索结果排序问题

    • 问题:搜索结果的排序可能不够准确,影响用户体验
    • 解决方案:可以根据实际需求调整排序算法,考虑更多因素如搜索词位置、词频等
  5. 用户输入处理问题

    • 问题:用户可能输入特殊字符或过长的搜索词,影响搜索效果
    • 解决方案:实现输入验证和处理,对特殊字符进行过滤,对过长的搜索词进行截断或提示

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

  1. Flutter组件开发

    • 使用 StatefulWidgetStatelessWidget 构建UI
    • 利用 setState 管理组件状态
    • 实现异步操作和错误处理
  2. 模糊搜索算法

    • 实现字符串相似度计算
    • 实现搜索结果排序和筛选
    • 支持设置搜索参数如限制数量和匹配度阈值
  3. 用户界面设计

    • 响应式布局设计
    • 美观的视觉效果
    • 直观的用户交互
    • 良好的错误反馈机制
  4. 异步编程

    • 使用 Future.delayed 模拟异步操作
    • 实现加载状态和错误处理
  5. OpenHarmony适配

    • 通过模拟实现适配OpenHarmony平台
    • 确保代码在不同平台上的兼容性
    • 优化用户体验,适应不同设备的屏幕尺寸
  6. 代码组织

    • 模块化设计,将功能拆分为独立的组件
    • 清晰的代码结构和命名规范
    • 良好的注释和文档

本次开发成功实现了Flutter三方库fuzzywuzzy在OpenHarmony平台的适配,为开发者提供了一套完整的模糊搜索解决方案。通过模块化的设计和清晰的实现,不仅满足了功能需求,也为后续的扩展和维护提供了便利。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐