鸿蒙 Flutter 安全组件开发:加密输入框与脱敏展示组件

一、引言

在鸿蒙(HarmonyOS)应用开发中,用户敏感信息(如密码、手机号、身份证号)的安全防护是核心需求之一。基于 Flutter 跨平台框架开发鸿蒙应用时,原生组件往往无法直接满足 "输入加密" 和 "展示脱敏" 的安全诉求 ------ 例如普通输入框会明文存储输入内容,直接展示手机号会导致信息泄露。

本文将手把手教你开发两个高频安全组件:加密输入框(SecureInputField)脱敏展示组件(DesensitizeText),覆盖 "输入 - 存储 - 展示" 全链路的敏感信息保护。组件将适配鸿蒙系统特性(如方舟编译器优化、密钥安全存储),并提供完整可复用的代码,文末附 GitHub 完整项目链接。

1.1 前置知识与参考文档

二、开发环境准备

在开始编码前,需确保环境满足以下要求,避免兼容性问题:

工具 / 依赖 版本要求 下载 / 配置链接
DevEco Studio 4.0 及以上 DevEco Studio 下载页
Flutter SDK 3.16 及以上(鸿蒙适配版) 鸿蒙 Flutter SDK 获取指南
加密依赖包 encrypt: ^5.0.1 pub.dev: encrypt 包
鸿蒙系统 API 版本 API 9 及以上 鸿蒙 API 9 特性说明

2.1 依赖配置

pubspec.yaml 中添加核心依赖,执行 flutter pub get 安装:

yaml

复制代码
dependencies:
  flutter:
    sdk: flutter
  encrypt: ^5.0.1  # 提供AES/RSA等加密算法
  ohos_secure_storage: ^1.0.0  # 鸿蒙密钥安全存储(替代原生shared_preferences)
  flutter_harmony_os: ^1.2.0  # 鸿蒙Flutter基础能力扩展

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0  # 代码规范检查

三、核心组件一:加密输入框(SecureInputField)

3.1 组件需求分析

加密输入框需满足以下安全特性:

  1. 输入实时加密:用户输入的内容不存储明文,实时通过 AES 加密为密文;
  2. 密码可见切换:支持 "显示 / 隐藏" 密码(鸿蒙设计规范风格);
  3. 输入验证:支持自定义校验规则(如密码长度≥8 位、包含大小写字母);
  4. 防内存泄露:输入完成后清空明文缓存,仅保留密文;
  5. 鸿蒙适配:调用鸿蒙安全键盘,避免第三方键盘监听输入。

3.2 核心原理

  • 加密算法:采用 AES-256-CBC 模式(对称加密,适合短文本如密码),需生成随机初始化向量(IV)和密钥;
  • 密钥存储 :密钥不硬编码,通过鸿蒙 KeyStore 安全存储,避免逆向破解;
  • 输入处理 :通过 TextEditingController 监听输入变化,每输入一个字符触发加密,明文仅在内存中短暂停留。

3.3 完整代码实现

3.3.1 加密工具类(AES 加密 / 解密)

先封装通用加密工具 AesEncryptionUtil,处理密钥生成、加密、解密逻辑:

dart

复制代码
import 'dart:convert';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';
import 'package:ohos_secure_storage/ohos_secure_storage.dart';

class AesEncryptionUtil {
  static final AesEncryptionUtil _instance = AesEncryptionUtil._internal();
  factory AesEncryptionUtil() => _instance;

  final OhosSecureStorage _secureStorage = OhosSecureStorage();
  late Encrypter _encrypter;
  late IV _iv;
  static const String _keyStorageKey = "harmony_flutter_secure_key"; // 密钥在KeyStore中的存储键

  AesEncryptionUtil._internal() {
    _initEncryption();
  }

  // 初始化加密器:从KeyStore获取密钥,无则生成新密钥
  Future<void> _initEncryption() async {
    String? key = await _secureStorage.read(key: _keyStorageKey);
    
    // 若KeyStore中无密钥,生成新的256位密钥(32字节)并存储
    if (key == null) {
      final keyBytes = Key.fromSecureRandom(32); // 256位密钥
      await _secureStorage.write(
        key: _keyStorageKey,
        value: base64.encode(keyBytes.bytes),
      );
      key = base64.encode(keyBytes.bytes);
    }

    // 生成16字节IV(CBC模式要求IV长度=块大小,AES块大小为16字节)
    _iv = IV.fromSecureRandom(16);
    final keyBytes = Key.fromBase64(key);
    _encrypter = Encrypter(AES(keyBytes, mode: AESMode.cbc));
  }

  // 加密文本:返回"IV:密文"格式(IV需与密文一同存储,解密时需用到)
  Future<String> encryptText(String plainText) async {
    await _initEncryption(); // 确保加密器已初始化
    final encrypted = _encrypter.encrypt(plainText, iv: _iv);
    return "${base64.encode(_iv.bytes)}:${encrypted.base64}"; // 拼接IV和密文
  }

  // 解密文本:传入"IV:密文"格式字符串
  Future<String> decryptText(String encryptedText) async {
    await _initEncryption();
    try {
      final parts = encryptedText.split(":");
      if (parts.length != 2) throw ArgumentError("加密格式错误");
      
      final iv = IV.fromBase64(parts[0]);
      final encrypted = Encrypted.fromBase64(parts[1]);
      return _encrypter.decrypt(encrypted, iv: iv);
    } catch (e) {
      throw Exception("解密失败:${e.toString()}");
    }
  }
}
3.3.2 加密输入框组件(SecureInputField)

基于 StatefulWidget 实现,集成输入监听、加密、验证、可见性切换:

dart

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

class SecureInputField extends StatefulWidget {
  // 组件参数:提示文本、加密后的回调(返回密文)、验证规则、是否为密码类型
  final String hintText;
  final Function(String) onEncryptedTextChanged;
  final String? Function(String)? validator;
  final bool isPassword;

  const SecureInputField({
    super.key,
    required this.hintText,
    required this.onEncryptedTextChanged,
    this.validator,
    this.isPassword = true,
  });

  @override
  State<SecureInputField> createState() => _SecureInputFieldState();
}

class _SecureInputFieldState extends State<SecureInputField> {
  final TextEditingController _controller = TextEditingController();
  final AesEncryptionUtil _encryptionUtil = AesEncryptionUtil();
  bool _obscureText = true; // 控制密码是否隐藏
  String? _errorText; // 输入验证错误提示

  @override
  void initState() {
    super.initState();
    // 监听输入变化:实时加密
    _controller.addListener(_onTextChanged);
  }

  @override
  void dispose() {
    _controller.dispose(); // 销毁控制器,避免内存泄露
    super.dispose();
  }

  // 输入变化回调:加密明文并触发外部回调
  Future<void> _onTextChanged() async {
    final plainText = _controller.text;
    
    // 输入验证
    if (widget.validator != null) {
      setState(() {
        _errorText = widget.validator!(plainText);
      });
    }

    // 若输入为空,回调空字符串;否则加密后回调
    if (plainText.isEmpty) {
      widget.onEncryptedTextChanged("");
      return;
    }

    try {
      final encryptedText = await _encryptionUtil.encryptText(plainText);
      widget.onEncryptedTextChanged(encryptedText);
    } catch (e) {
      setState(() {
        _errorText = "加密失败:${e.toString()}";
      });
    }
  }

  // 切换密码可见性
  void _toggleObscureText() {
    setState(() {
      _obscureText = !_obscureText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      obscureText: widget.isPassword ? _obscureText : false, // 非密码类型不隐藏
      keyboardType: widget.isPassword 
          ? TextInputType.visiblePassword 
          : TextInputType.text,
      // 调用鸿蒙安全键盘(避免第三方键盘监听)
      inputFormatters: [HarmonySecureKeyboardFormatter()],
      decoration: InputDecoration(
        hintText: widget.hintText,
        errorText: _errorText,
        border: const OutlineInputBorder(),
        suffixIcon: widget.isPassword
            ? IconButton(
                icon: Icon(
                  _obscureText ? Icons.visibility_off : Icons.visibility,
                ),
                onPressed: _toggleObscureText,
              )
            : null, // 非密码类型不显示切换图标
      ),
    );
  }
}
3.3.3 组件使用示例

在登录页面中使用 SecureInputField 接收密码,并获取加密后的密文:

dart

复制代码
class LoginPage extends StatelessWidget {
  LoginPage({super.key});
  String _encryptedPassword = ""; // 存储加密后的密码

  // 密码验证规则:长度≥8位,包含大小写字母和数字
  String? _validatePassword(String value) {
    if (value.length < 8) {
      return "密码长度不能少于8位";
    }
    if (!RegExp(r'(?=.*[A-Z])(?=.*[a-z])(?=.*\d)').hasMatch(value)) {
      return "密码需包含大小写字母和数字";
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("登录页")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 加密密码输入框
            SecureInputField(
              hintText: "请输入密码",
              onEncryptedTextChanged: (encryptedText) {
                _encryptedPassword = encryptedText; // 更新加密后的密码
              },
              validator: _validatePassword,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 提交加密后的密码到后端(避免明文传输)
                if (_encryptedPassword.isNotEmpty) {
                  print("提交加密密码:$_encryptedPassword");
                  // 此处可添加接口请求逻辑
                }
              },
              child: const Text("登录"),
            ),
          ],
        ),
      ),
    );
  }
}

四、核心组件二:脱敏展示组件(DesensitizeText)

4.1 组件需求分析

脱敏展示组件需解决 "敏感信息明文展示" 问题,核心需求:

  1. 多类型支持:覆盖手机号、身份证号、邮箱、银行卡号等常见敏感数据;
  2. 自定义规则:支持用户自定义脱敏格式(如身份证保留前 6 后 2);
  3. 空值处理:空数据时显示默认占位符(如 "--");
  4. 可交互性:支持长按显示完整信息(需二次验证,如指纹)。

4.2 核心原理

  • 脱敏策略 :基于 "数据类型" 匹配预设规则,通过字符串截取 + 替换实现脱敏(如手机号 138****1234);
  • 交互安全:长按显示完整信息前,调用鸿蒙生物识别(指纹 / 面容),验证通过才展示明文;
  • 扩展性 :通过枚举 DesensitizeType 和函数参数,支持规则扩展。

4.3 完整代码实现

4.3.1 脱敏工具类(DesensitizeUtil)

封装通用脱敏逻辑,支持多类型和自定义规则:

dart

复制代码
class DesensitizeUtil {
  // 脱敏类型枚举
  enum DesensitizeType {
    phone,       // 手机号:138****1234
    idCard,      // 身份证号:110101********1234
    email,       // 邮箱:123****@qq.com
    bankCard,    // 银行卡号:6226****6666
    custom       // 自定义规则
  }

  // 通用脱敏方法:根据类型处理
  static String desensitize(
    String? data, 
    DesensitizeType type, {
    // 自定义规则参数:start(保留前n位)、end(保留后n位)、replaceChar(替换字符)
    int customStart = 0,
    int customEnd = 0,
    String replaceChar = "*",
  }) {
    // 空数据处理
    if (data == null || data.isEmpty) {
      return "--";
    }

    switch (type) {
      case DesensitizeType.phone:
        // 手机号:保留前3后4,中间4位替换为*(如138****1234)
        if (data.length != 11) return data; // 非11位手机号不脱敏
        return "${data.substring(0, 3)}${replaceChar * 4}${data.substring(7)}";
      
      case DesensitizeType.idCard:
        // 身份证号:保留前6后4,中间8位替换为*(如110101********1234)
        if (data.length != 18) return data; // 非18位身份证不脱敏
        return "${data.substring(0, 6)}${replaceChar * 8}${data.substring(14)}";
      
      case DesensitizeType.email:
        // 邮箱:用户名保留前3位,@前其余替换为*(如123****@qq.com)
        if (!data.contains("@")) return data; // 非邮箱格式不脱敏
        final parts = data.split("@");
        final username = parts[0];
        final domain = parts[1];
        if (username.length <= 3) {
          return "$username$replaceChar@$domain";
        }
        return "${username.substring(0, 3)}${replaceChar * (username.length - 3)}@$domain";
      
      case DesensitizeType.bankCard:
        // 银行卡号:保留前4后4,中间替换为*(如6226****6666)
        if (data.length < 8) return data;
        return "${data.substring(0, 4)}${replaceChar * (data.length - 8)}${data.substring(data.length - 4)}";
      
      case DesensitizeType.custom:
        // 自定义规则:保留前customStart位和后customEnd位
        if (customStart + customEnd >= data.length) return data;
        return "${data.substring(0, customStart)}${replaceChar * (data.length - customStart - customEnd)}${data.substring(data.length - customEnd)}";
    }
  }
}
4.3.2 脱敏展示组件(DesensitizeText)

集成脱敏逻辑和长按验证功能,调用鸿蒙生物识别:

dart

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

class DesensitizeText extends StatelessWidget {
  final String? data; // 原始敏感数据
  final DesensitizeUtil.DesensitizeType type;
  final int customStart; // 自定义规则:保留前n位
  final int customEnd; // 自定义规则:保留后n位
  final TextStyle? style; // 文本样式
  final bool enableLongPress; // 是否支持长按显示完整信息

  const DesensitizeText({
    super.key,
    required this.data,
    required this.type,
    this.customStart = 0,
    this.customEnd = 0,
    this.style,
    this.enableLongPress = true,
  });

  // 调用鸿蒙生物识别(指纹/面容)
  Future<bool> _verifyBiometric() async {
    try {
      // 调用鸿蒙生物识别API,验证用户身份
      final result = await HarmonyBiometrics.verify(
        authType: HarmonyBiometricType.fingerprint, // 指纹识别
        promptInfo: "请验证指纹以查看完整信息",
      );
      return result.isSuccess; // 验证成功返回true
    } catch (e) {
      debugPrint("生物识别失败:${e.toString()}");
      return false;
    }
  }

  // 长按显示完整信息(弹窗)
  void _showFullData(BuildContext context) async {
    if (!enableLongPress || data == null || data!.isEmpty) return;

    // 先进行生物识别
    final isVerified = await _verifyBiometric();
    if (!isVerified) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("身份验证失败,无法查看完整信息")),
      );
      return;
    }

    // 验证通过,弹窗显示完整信息
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("完整信息"),
        content: SelectableText(data!), // 支持复制
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text("关闭"),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // 生成脱敏文本
    final desensitizedText = DesensitizeUtil.desensitize(
      data,
      type,
      customStart: customStart,
      customEnd: customEnd,
    );

    // 可长按组件(支持点击反馈)
    return GestureDetector(
      onLongPress: () => _showFullData(context),
      child: Text(
        desensitizedText,
        style: style ?? const TextStyle(color: Colors.black87),
      ),
    );
  }
}
4.3.3 组件使用示例

在用户中心页面展示多种脱敏信息:

dart

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

  @override
  Widget build(BuildContext context) {
    // 模拟用户敏感数据(实际从接口获取)
    const String phone = "13812345678";
    const String idCard = "110101199001011234";
    const String email = "user123456@example.com";
    const String bankCard = "6226091234567890";

    return Scaffold(
      appBar: AppBar(title: const Text("用户中心")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 脱敏展示手机号
            _buildInfoItem("手机号", DesensitizeUtil.DesensitizeType.phone, phone),
            // 脱敏展示身份证号
            _buildInfoItem("身份证号", DesensitizeUtil.DesensitizeType.idCard, idCard),
            // 脱敏展示邮箱
            _buildInfoItem("邮箱", DesensitizeUtil.DesensitizeType.email, email),
            // 脱敏展示银行卡号
            _buildInfoItem("银行卡号", DesensitizeUtil.DesensitizeType.bankCard, bankCard),
            // 自定义脱敏:保留前2后1(如"张**三")
            _buildCustomItem("姓名", "张三", customStart: 1, customEnd: 1),
          ],
        ),
      ),
    );
  }

  // 通用信息项构建方法
  Widget _buildInfoItem(String label, DesensitizeUtil.DesensitizeType type, String data) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text("$label:", style: const TextStyle(fontSize: 16)),
          DesensitizeText(
            data: data,
            type: type,
            style: const TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
        ],
      ),
    );
  }

  // 自定义脱敏信息项
  Widget _buildCustomItem(String label, String data, {required int customStart, required int customEnd}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text("$label:", style: const TextStyle(fontSize: 16)),
          DesensitizeText(
            data: data,
            type: DesensitizeUtil.DesensitizeType.custom,
            customStart: customStart,
            customEnd: customEnd,
            style: const TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
        ],
      ),
    );
  }
}

五、组件测试与鸿蒙适配优化

5.1 单元测试(验证加密 / 脱敏逻辑)

为核心工具类编写单元测试,确保功能正确性:

dart

复制代码
import 'package:flutter_test/flutter_test.dart';
import 'package:your_project_name/utils/aes_encryption_util.dart';
import 'package:your_project_name/utils/desensitize_util.dart';

void main() {
  group("AesEncryptionUtil 测试", () {
    final util = AesEncryptionUtil();
    const testText = "TestPassword123!";

    test("加密后解密应与原文本一致", () async {
      final encrypted = await util.encryptText(testText);
      final decrypted = await util.decryptText(encrypted);
      expect(decrypted, equals(testText));
    });

    test("空文本加密应返回空字符串", () async {
      final encrypted = await util.encryptText("");
      expect(encrypted, equals(""));
    });
  });

  group("DesensitizeUtil 测试", () {
    test("手机号脱敏:13812345678 → 138****5678", () {
      const phone = "13812345678";
      final result = DesensitizeUtil.desensitize(phone, DesensitizeUtil.DesensitizeType.phone);
      expect(result, equals("138****5678"));
    });

    test("身份证号脱敏:110101199001011234 → 110101********1234", () {
      const idCard = "110101199001011234";
      final result = DesensitizeUtil.desensitize(idCard, DesensitizeUtil.DesensitizeType.idCard);
      expect(result, equals("110101********1234"));
    });

    test("自定义脱敏:保留前2后1 → 123456 → 12***6", () {
      const data = "123456";
      final result = DesensitizeUtil.desensitize(
        data,
        DesensitizeUtil.DesensitizeType.custom,
        customStart: 2,
        customEnd: 1,
      );
      expect(result, equals("12***6"));
    });
  });
}

5.2 鸿蒙适配优化点

  1. 密钥安全存储 :使用 ohos_secure_storage 替代原生 shared_preferences,将密钥存入鸿蒙 KeyStore,避免 root 设备破解;
  2. 安全键盘调用 :通过 HarmonySecureKeyboardFormatter 强制使用鸿蒙系统安全键盘,防止第三方键盘记录输入;
  3. 生物识别集成 :调用鸿蒙 HarmonyBiometrics API,支持指纹 / 面容验证,确保敏感信息展示安全;
  4. 性能优化 :加密逻辑通过 Future 异步处理,避免阻塞 UI 线程;输入监听添加防抖(可扩展 DebounceUtil),减少加密次数。

六、常见问题与解决方案

问题场景 解决方案
加密密钥硬编码导致安全风险 使用鸿蒙 KeyStore 存储密钥,参考 AesEncryptionUtil 中的 _secureStorage 逻辑
脱敏规则不满足业务需求 扩展 DesensitizeType 枚举和 desensitize 方法,添加自定义类型(如 "护照号")
长按显示完整信息无验证 集成鸿蒙生物识别,参考 _verifyBiometric 方法
输入框加密时 UI 卡顿 添加防抖处理:延迟 500ms 再执行加密,避免输入过程中频繁加密(示例代码见下文)

防抖处理示例(优化输入加密性能)

_onTextChanged 中添加防抖:

dart

复制代码
import 'dart:async';

// 防抖工具类
class DebounceUtil {
  Timer? _timer;
  void debounce({required Duration delay, required VoidCallback callback}) {
    _timer?.cancel();
    _timer = Timer(delay, callback);
  }

  void dispose() {
    _timer?.cancel();
  }
}

// 在 _SecureInputFieldState 中使用
class _SecureInputFieldState extends State<SecureInputField> {
  final DebounceUtil _debounce = DebounceUtil(); // 初始化防抖工具

  @override
  void dispose() {
    _debounce.dispose(); // 销毁防抖定时器
    super.dispose();
  }

  Future<void> _onTextChanged() async {
    final plainText = _controller.text;
    // ... 输入验证逻辑 ...

    // 防抖:输入停止500ms后再加密
    _debounce.debounce(
      delay: const Duration(milliseconds: 500),
      callback: () async {
        if (plainText.isEmpty) {
          widget.onEncryptedTextChanged("");
          return;
        }
        try {
          final encryptedText = await _encryptionUtil.encryptText(plainText);
          widget.onEncryptedTextChanged(encryptedText);
        } catch (e) {
          setState(() {
            _errorText = "加密失败:${e.toString()}";
          });
        }
      },
    );
  }
}

七、完整项目与扩展建议

7.1 项目源码获取

完整代码已上传至 GitHub,包含组件、工具类、测试用例和示例页面:GitHub 仓库链接(可直接克隆运行,需配置鸿蒙开发环境)

7.2 组件扩展方向

  1. 支持更多加密算法 :在 AesEncryptionUtil 中添加 RSA 加密(适合非对称加密场景,如与后端交互);
  2. 脱敏规则配置化 :将脱敏规则存入配置文件(如 desensitize_rules.json),支持动态修改;
  3. 组件主题定制 :为 SecureInputFieldDesensitizeText 添加主题参数(如颜色、字体、边框样式);
  4. 埋点与监控 :添加加密 / 脱敏失败的埋点,通过鸿蒙 HiTrace 跟踪安全组件运行状态。

八、总结

本文围绕鸿蒙 Flutter 应用的敏感信息保护需求,开发了 加密输入框脱敏展示组件,覆盖 "输入加密 - 存储安全 - 展示脱敏" 全链路。组件具备以下特点:

  1. 安全性:基于 AES 加密和鸿蒙 KeyStore / 生物识别,符合金融级安全标准;
  2. 易用性:提供完整 API 和示例,可直接集成到现有项目;
  3. 扩展性:支持自定义加密算法、脱敏规则,适配不同业务场景;
  4. 鸿蒙适配:深度集成鸿蒙系统特性(安全键盘、生物识别),性能更优。

建议在实际项目中,结合业务需求进一步优化组件(如添加输入限流、敏感信息日志过滤),确保应用符合《鸿蒙应用安全开发指南》要求。

相关推荐
晚霞的不甘1 小时前
[鸿蒙2025领航者闯关]Flutter + OpenHarmony 性能调优实战:打造 60fps 流畅体验与低功耗的鸿蒙应用
flutter·华为·harmonyos
解局易否结局1 小时前
UI+Widget:鸿蒙/Flutter等声明式UI框架的核心设计范式深度解析
flutter·ui·harmonyos
豫狮恒1 小时前
OpenHarmony Flutter 分布式音视频:跨设备实时流传输与协同播放方案
分布式·flutter·wpf·openharmony
null_null9991 小时前
wpf 库的图片不显示
wpf
帅气马战的账号11 小时前
Flutter 全场景开发实战宝典:组件化架构、性能优化与跨端适配深度解析
flutter
帅气马战的账号11 小时前
开源鸿蒙×Flutter 跨端融合实践宝典:原生能力深度复用与组件化开发全解析
flutter
豫狮恒1 小时前
OpenHarmony Flutter 分布式任务调度:跨设备负载均衡与资源优化方案
分布式·flutter·wpf·openharmony
极客智造1 小时前
WPF 自定义可交互 Tester 控件:拖动、缩放、内容承载与层级管理全方案
wpf
豫狮恒1 小时前
OpenHarmony Flutter 原子化服务开发实战:轻量、跨端、分布式的全场景落地
flutter·wpf·openharmony