Flutter for OpenHarmony:第三方库实战 chewie 视频播放器UI组件详解

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


🎯 前言:为什么需要 Chewie?

在移动应用开发中,虽然 video_player 提供了基础的视频播放能力,但它缺少播放控制UI。这时候就需要 chewie 了!

场景一 :短视频应用需要完整的播放控制界面
场景二 :在线教育应用需要专业的视频播放器
场景三 :直播应用需要自定义播放控制
场景四 :视频分享应用需要美观的播放界面
场景五:企业培训应用需要带字幕的视频播放

chewie 是基于 video_player 的高级封装,提供了开箱即用的播放控制UI,支持 Material Design 和 Cupertino 风格,让你无需从零开发播放器界面!

🚀 核心能力一览

功能特性 详细说明 OpenHarmony 支持
Material风格UI Android风格的播放控制界面
Cupertino风格UI iOS风格的播放控制界面
Desktop风格UI 桌面端播放控制界面
全屏播放 支持全屏模式切换
字幕支持 内置字幕显示功能
自定义控制 可自定义播放控制按钮
播放速度调节 支持倍速播放
自动播放 支持视频自动播放
循环播放 支持视频循环播放
进度条拖动 支持拖动进度条跳转
音量控制 支持音量调节
缩放平移 支持视频缩放和平移

支持的视频源

视频源类型 用途 OpenHarmony 支持
网络视频 播放在线视频
本地文件 播放本地视频文件
资源文件 播放应用内置视频

📱 如何运行这些示例

运行步骤

  1. 创建新项目或使用现有项目
  2. 配置依赖(见下方)
  3. 配置权限(见下方)
  4. 复制示例代码到 lib/main.dart
  5. 运行应用:flutter run

⚠️ 常见问题

  • 问题:视频无法播放

    • 解决:检查网络权限配置,确保视频URL可访问
  • 问题:控制界面不显示

    • 解决:确保 ChewieController 正确初始化

⚙️ 环境准备:三步走

第一步:添加依赖

📄 pubspec.yaml

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  
  # 添加 video_player 依赖(使用 pub.dev 版本)
  video_player: ^2.9.1
  
  # 添加 chewie 依赖(OpenHarmony 适配版本)
  chewie:
    git:
      url: https://atomgit.com/openharmony-sig/fluttertpc_chewie.git

dev_dependencies:
  flutter_test:
    sdk: flutter
  
  # 添加 video_player_ohos 鸿蒙平台支持
  video_player_ohos:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_packages.git"
      path: "packages/video_player/video_player_ohos"
  
  # 添加 chewie_ohos 鸿蒙平台支持
  chewie_ohos:
    git:
      url: https://atomgit.com/openharmony-sig/fluttertpc_chewie.git
      path: ohos

执行命令:

bash 复制代码
flutter pub get

第二步:配置权限

2.1 配置网络权限

📄 ohos/entry/src/main/module.json5

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:network_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}
2.2 配置权限说明

📄 ohos/entry/src/main/resources/base/element/string.json

json 复制代码
{
  "string": [
    {
      "name": "network_reason",
      "value": "播放网络视频"
    }
  ]
}

第三步:导入包

在 Dart 文件中导入:

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

📚 基础用法:从简单开始

示例1:基础视频播放器

最简单的视频播放器实现:

dart 复制代码
class BasicVideoPlayer extends StatefulWidget {
  const BasicVideoPlayer({super.key});

  @override
  State<BasicVideoPlayer> createState() => _BasicVideoPlayerState();
}

class _BasicVideoPlayerState extends State<BasicVideoPlayer> {
  late VideoPlayerController _videoPlayerController;
  ChewieController? _chewieController;

  @override
  void initState() {
    super.initState();
    _initializePlayer();
  }

  Future<void> _initializePlayer() async {
    _videoPlayerController = VideoPlayerController.networkUrl(
      Uri.parse('https://media.w3.org/2010/05/sintel/trailer.mp4'),
    );
  
    await _videoPlayerController.initialize();
  
    _chewieController = ChewieController(
      videoPlayerController: _videoPlayerController,
      autoPlay: true,
      looping: false,
    );
  
    setState(() {});
  }

  @override
  void dispose() {
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('基础视频播放器')),
      body: Center(
        child: _chewieController != null &&
                _chewieController!.videoPlayerController.value.isInitialized
            ? Chewie(controller: _chewieController!)
            : const CircularProgressIndicator(),
      ),
    );
  }
}

核心要点

  • 先创建 VideoPlayerController
  • 等待视频初始化完成
  • 创建 ChewieController 并传入 VideoPlayerController
  • 使用 Chewie widget 显示播放器

🎨 进阶用法:自定义配置

示例2:自动播放和循环播放

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  autoPlay: true,        // 自动播放
  looping: true,         // 循环播放
  showControls: true,    // 显示控制界面
);

示例3:自定义控制界面显示时间

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  autoPlay: true,
  hideControlsTimer: const Duration(seconds: 3),  // 3秒后隐藏控制界面
);

示例4:添加字幕支持

dart 复制代码
final subtitles = [
  Subtitle(
    index: 0,
    start: Duration.zero,
    end: const Duration(seconds: 10),
    text: '这是第一段字幕',
  ),
  Subtitle(
    index: 1,
    start: const Duration(seconds: 10),
    end: const Duration(seconds: 20),
    text: '这是第二段字幕',
  ),
];

_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  autoPlay: true,
  subtitle: Subtitles(subtitles),
  subtitleBuilder: (context, subtitle) => Container(
    padding: const EdgeInsets.all(10.0),
    child: Text(
      subtitle.toString(),
      style: const TextStyle(
        color: Colors.white,
        fontSize: 16,
        backgroundColor: Colors.black54,
      ),
    ),
  ),
);

示例5:自定义附加选项

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  autoPlay: true,
  additionalOptions: (context) {
    return <OptionItem>[
      OptionItem(
        onTap: () {
          // 切换视频源
          _switchVideoSource();
        },
        iconData: Icons.live_tv_sharp,
        title: '切换视频',
      ),
      OptionItem(
        onTap: () {
          // 分享视频
          _shareVideo();
        },
        iconData: Icons.share,
        title: '分享',
      ),
    ];
  },
);

示例6:全屏播放控制

dart 复制代码
// 进入全屏
_chewieController?.enterFullScreen();

// 退出全屏
_chewieController?.exitFullScreen();

// 切换全屏状态
_chewieController?.toggleFullScreen();

示例7:播放控制

dart 复制代码
// 播放
await _chewieController?.videoPlayerController.play();

// 暂停
await _chewieController?.videoPlayerController.pause();

// 跳转到指定位置
await _chewieController?.videoPlayerController.seekTo(
  const Duration(seconds: 30),
);

// 设置音量(0.0 - 1.0)
await _chewieController?.videoPlayerController.setVolume(0.5);

// 设置循环播放
await _chewieController?.videoPlayerController.setLooping(true);

🎯 完整示例:专业视频播放器

下面是一个功能完整的视频播放器示例,包含多视频源切换、全屏控制、字幕显示等功能:

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chewie 视频播放器',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ProfessionalVideoPlayer(),
    );
  }
}

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

  @override
  State<ProfessionalVideoPlayer> createState() => _ProfessionalVideoPlayerState();
}

class _ProfessionalVideoPlayerState extends State<ProfessionalVideoPlayer> {
  late VideoPlayerController _videoPlayerController;
  ChewieController? _chewieController;
  int _currentVideoIndex = 0;

  // 视频源列表
  final List<String> _videoUrls = [
    'https://media.w3.org/2010/05/sintel/trailer.mp4',
    'http://vjs.zencdn.net/v/oceans.mp4',
  ];

  @override
  void initState() {
    super.initState();
    _initializePlayer();
  }

  Future<void> _initializePlayer() async {
    _videoPlayerController = VideoPlayerController.networkUrl(
      Uri.parse(_videoUrls[_currentVideoIndex]),
    );

    await _videoPlayerController.initialize();

    _createChewieController();
    setState(() {});
  }

  void _createChewieController() {
    // 创建字幕
    final subtitles = [
      Subtitle(
        index: 0,
        start: Duration.zero,
        end: const Duration(seconds: 10),
        text: const TextSpan(
          children: [
            TextSpan(
              text: '欢迎',
              style: TextStyle(color: Colors.red, fontSize: 22),
            ),
            TextSpan(
              text: '使用',
              style: TextStyle(color: Colors.green, fontSize: 20),
            ),
            TextSpan(
              text: 'Chewie',
              style: TextStyle(color: Colors.blue, fontSize: 18),
            ),
          ],
        ),
      ),
      Subtitle(
        index: 1,
        start: const Duration(seconds: 10),
        end: const Duration(seconds: 20),
        text: '这是一个专业的视频播放器',
      ),
    ];

    _chewieController = ChewieController(
      videoPlayerController: _videoPlayerController,
      autoPlay: true,
      looping: true,
      hideControlsTimer: const Duration(seconds: 2),
    
      // 添加自定义选项
      additionalOptions: (context) {
        return <OptionItem>[
          OptionItem(
            onTap: _switchVideo,
            iconData: Icons.live_tv_sharp,
            title: '切换视频',
          ),
        ];
      },
    
      // 配置字幕
      subtitle: Subtitles(subtitles),
      subtitleBuilder: (context, dynamic subtitle) => Container(
        padding: const EdgeInsets.all(10.0),
        decoration: BoxDecoration(
          color: Colors.black54,
          borderRadius: BorderRadius.circular(8),
        ),
        child: subtitle is InlineSpan
            ? RichText(text: subtitle)
            : Text(
                subtitle.toString(),
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                ),
              ),
      ),
    );
  }

  Future<void> _switchVideo() async {
    await _videoPlayerController.pause();
    _currentVideoIndex = (_currentVideoIndex + 1) % _videoUrls.length;
    await _initializePlayer();
  }

  @override
  void dispose() {
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('专业视频播放器'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Column(
        children: [
          // 视频播放器
          Expanded(
            child: Center(
              child: _chewieController != null &&
                      _chewieController!.videoPlayerController.value.isInitialized
                  ? Chewie(controller: _chewieController!)
                  : const Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        CircularProgressIndicator(),
                        SizedBox(height: 20),
                        Text('加载中...'),
                      ],
                    ),
            ),
          ),
        
          // 控制按钮
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 8,
                  offset: const Offset(0, -2),
                ),
              ],
            ),
            child: Column(
              children: [
                // 全屏按钮
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton.icon(
                    onPressed: () {
                      _chewieController?.enterFullScreen();
                    },
                    icon: const Icon(Icons.fullscreen),
                    label: const Text('全屏播放'),
                  ),
                ),
                const SizedBox(height: 8),
              
                // 视频信息
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    Column(
                      children: [
                        const Icon(Icons.video_library, color: Colors.blue),
                        const SizedBox(height: 4),
                        Text(
                          '视频 ${_currentVideoIndex + 1}/${_videoUrls.length}',
                          style: const TextStyle(fontSize: 12),
                        ),
                      ],
                    ),
                    Column(
                      children: [
                        const Icon(Icons.subtitles, color: Colors.green),
                        const SizedBox(height: 4),
                        const Text(
                          '字幕已启用',
                          style: TextStyle(fontSize: 12),
                        ),
                      ],
                    ),
                    Column(
                      children: [
                        const Icon(Icons.loop, color: Colors.orange),
                        const SizedBox(height: 4),
                        const Text(
                          '循环播放',
                          style: TextStyle(fontSize: 12),
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

完整示例说明

  1. 多视频源切换:支持在多个视频之间切换
  2. 字幕显示:支持富文本字幕和普通文本字幕
  3. 自定义选项:在播放器菜单中添加自定义按钮
  4. 全屏控制:提供全屏播放功能
  5. 循环播放:视频自动循环播放
  6. 加载状态:显示加载进度指示器

🎨 UI风格切换

Chewie 支持三种UI风格:

Material Design 风格(Android)

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  // 默认使用 Material Design 风格
);

Cupertino 风格(iOS)

通过设置主题的 platform 来切换:

dart 复制代码
MaterialApp(
  theme: ThemeData(
    platform: TargetPlatform.iOS,  // 使用 iOS 风格
  ),
  home: YourWidget(),
);

Desktop 风格(桌面端)

dart 复制代码
MaterialApp(
  theme: ThemeData(
    platform: TargetPlatform.windows,  // 使用桌面风格
  ),
  home: YourWidget(),
);

📊 ChewieController 核心属性

基础配置

属性 类型 说明 默认值
videoPlayerController VideoPlayerController 视频播放控制器(必需) -
autoPlay bool 是否自动播放 false
looping bool 是否循环播放 false
showControls bool 是否显示控制界面 true
showControlsOnInitialize bool 初始化时是否显示控制界面 true

控制界面配置

属性 类型 说明 默认值
hideControlsTimer Duration 控制界面自动隐藏时间 3秒
showOptions bool 是否显示选项按钮 true
optionsTranslation OptionsTranslation 选项按钮文本翻译 null
draggableProgressBar bool 进度条是否可拖动 true

高级配置

属性 类型 说明 默认值
startAt Duration 起始播放位置 0
zoomAndPan bool 是否允许缩放和平移 false
maxScale double 最大缩放比例 2.5
subtitle Subtitles 字幕配置 null
subtitleBuilder SubtitleBuilder 字幕构建器 null
additionalOptions AdditionalOptions 自定义选项按钮 null

🎯 常用方法

播放控制方法

dart 复制代码
// 播放
await _chewieController?.play();

// 暂停
await _chewieController?.pause();

// 跳转到指定位置
await _chewieController?.seekTo(Duration(seconds: 30));

// 设置音量
await _chewieController?.setVolume(0.5);

// 设置循环播放
await _chewieController?.setLooping(true);

全屏控制方法

dart 复制代码
// 进入全屏
_chewieController?.enterFullScreen();

// 退出全屏
_chewieController?.exitFullScreen();

// 切换全屏状态
_chewieController?.toggleFullScreen();

视频切换方法

dart 复制代码
// 切换视频源
Future<void> switchVideo(String newUrl) async {
  await _videoPlayerController.pause();
  
  _videoPlayerController = VideoPlayerController.networkUrl(
    Uri.parse(newUrl),
  );
  
  await _videoPlayerController.initialize();
  
  // 使用 copyWith 更新控制器
  _chewieController = _chewieController!.copyWith(
    videoPlayerController: _videoPlayerController,
    autoPlay: true,
  );
  
  setState(() {});
}

🔧 高级技巧

技巧1:自定义进度条颜色

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  materialProgressColors: ChewieProgressColors(
    playedColor: Colors.red,        // 已播放部分颜色
    handleColor: Colors.blue,        // 拖动手柄颜色
    backgroundColor: Colors.grey,    // 背景颜色
    bufferedColor: Colors.lightGreen, // 缓冲部分颜色
  ),
);

技巧2:自定义占位图

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  placeholder: Container(
    color: Colors.grey,
    child: const Center(
      child: Icon(
        Icons.play_circle_outline,
        size: 100,
        color: Colors.white,
      ),
    ),
  ),
);

技巧3:监听播放状态

dart 复制代码
_videoPlayerController.addListener(() {
  if (_videoPlayerController.value.isPlaying) {
    print('视频正在播放');
  } else {
    print('视频已暂停');
  }
  
  if (_videoPlayerController.value.position == 
      _videoPlayerController.value.duration) {
    print('视频播放完成');
  }
});

技巧4:处理错误

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  errorBuilder: (context, errorMessage) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(
            Icons.error_outline,
            color: Colors.red,
            size: 60,
          ),
          const SizedBox(height: 16),
          Text(
            '播放出错: $errorMessage',
            style: const TextStyle(color: Colors.white),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {
              // 重新加载视频
              _initializePlayer();
            },
            child: const Text('重试'),
          ),
        ],
      ),
    );
  },
);

💡 最佳实践

1. 资源管理

dart 复制代码
@override
void dispose() {
  // 先释放 ChewieController
  _chewieController?.dispose();
  // 再释放 VideoPlayerController
  _videoPlayerController.dispose();
  super.dispose();
}

2. 异步初始化

dart 复制代码
Future<void> _initializePlayer() async {
  try {
    _videoPlayerController = VideoPlayerController.networkUrl(
      Uri.parse(videoUrl),
    );
  
    await _videoPlayerController.initialize();
  
    _chewieController = ChewieController(
      videoPlayerController: _videoPlayerController,
      autoPlay: true,
    );
  
    if (mounted) {
      setState(() {});
    }
  } catch (e) {
    print('初始化失败: $e');
    // 显示错误提示
  }
}

3. 网络视频优化

dart 复制代码
// 使用较低质量的视频作为预览
final previewUrl = 'https://example.com/video_preview.mp4';
final hdUrl = 'https://example.com/video_hd.mp4';

// 先加载预览版本
_videoPlayerController = VideoPlayerController.networkUrl(
  Uri.parse(previewUrl),
);

// 用户点击高清按钮后切换
void switchToHD() async {
  final currentPosition = await _videoPlayerController.position;
  
  _videoPlayerController = VideoPlayerController.networkUrl(
    Uri.parse(hdUrl),
  );
  
  await _videoPlayerController.initialize();
  await _videoPlayerController.seekTo(currentPosition);
  await _videoPlayerController.play();
}

🐛 常见问题与解决方案

问题1:视频无法播放

原因

  • 网络权限未配置
  • 视频URL不可访问
  • 视频格式不支持

解决方案

dart 复制代码
// 1. 检查权限配置
// 2. 验证视频URL
// 3. 使用支持的视频格式(MP4、WebM等)

// 添加错误处理
_videoPlayerController.addListener(() {
  if (_videoPlayerController.value.hasError) {
    print('播放错误: ${_videoPlayerController.value.errorDescription}');
  }
});

问题2:控制界面不显示

原因

  • showControls 设置为 false
  • ChewieController 未正确初始化

解决方案

dart 复制代码
_chewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  showControls: true,  // 确保设置为 true
  showControlsOnInitialize: true,
);

问题3:全屏后无法返回

原因

  • 系统导航栏被隐藏

解决方案

dart 复制代码
// 监听全屏状态
_chewieController?.addListener(() {
  if (_chewieController!.isFullScreen) {
    // 进入全屏
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
  } else {
    // 退出全屏
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  }
});

问题4:内存占用过高

原因

  • 多个视频同时加载
  • 未正确释放资源

解决方案

dart 复制代码
// 切换视频前先释放旧资源
Future<void> switchVideo(String newUrl) async {
  await _videoPlayerController.pause();
  await _videoPlayerController.dispose();
  
  _videoPlayerController = VideoPlayerController.networkUrl(
    Uri.parse(newUrl),
  );
  
  await _videoPlayerController.initialize();
  setState(() {});
}

📚 API 参考

ChewieController 构造函数

dart 复制代码
ChewieController({
  required VideoPlayerController videoPlayerController,
  bool autoPlay = false,
  bool looping = false,
  bool showControls = true,
  bool showControlsOnInitialize = true,
  Duration? hideControlsTimer,
  bool showOptions = true,
  OptionsTranslation? optionsTranslation,
  bool draggableProgressBar = true,
  Duration? startAt,
  bool zoomAndPan = false,
  double maxScale = 2.5,
  Subtitles? subtitle,
  SubtitleBuilder? subtitleBuilder,
  AdditionalOptions? additionalOptions,
  ChewieProgressColors? materialProgressColors,
  Widget? placeholder,
  ErrorBuilder? errorBuilder,
})

主要方法

方法 返回类型 说明
play() Future<void> 播放视频
pause() Future<void> 暂停视频
seekTo(Duration) Future<void> 跳转到指定位置
setVolume(double) Future<void> 设置音量
setLooping(bool) Future<void> 设置循环播放
enterFullScreen() void 进入全屏
exitFullScreen() void 退出全屏
toggleFullScreen() void 切换全屏状态
dispose() void 释放资源

🎓 总结

通过本文,你已经掌握了:

✅ Chewie 的基本概念和核心能力

✅ 如何配置依赖和权限

✅ 基础视频播放器的实现

✅ 自定义播放控制和UI风格

✅ 字幕显示和自定义选项

✅ 全屏控制和视频切换

✅ 常见问题的解决方案

Chewie 让视频播放变得简单而专业!它基于 video_player 提供了完整的UI解决方案,让你无需从零开发播放器界面。无论是短视频应用、在线教育还是企业培训,Chewie 都能满足你的需求。


🔗 相关资源


💡 提示:如果你在使用过程中遇到问题,欢迎在社区提问交流!

相关推荐
Sun_gentle2 小时前
android studio创建flutter项目
android·flutter·android studio
Haha_bj2 小时前
Flutter——List.map()
flutter·app
梵得儿SHI3 小时前
Vue3 生态工具实战宝典:UI 组件库 + 表单验证全解析(Element Plus/Ant Design Vue/VeeValidate)
前端·vue.js·ui·elementplus·vue性能优化·antdesignvue·表单验证方案
Unity游戏资源学习屋3 小时前
【Unity UI资源包】GUI Pro - Casual Game 专为休闲手游打造的专业级UI资源包
ui·unity
LawrenceLan4 小时前
30.Flutter 零基础入门(三十):GridView 网格布局 —— 九宫格与商品列表必学
开发语言·前端·flutter·dart
fifiAmx4 小时前
Flutter 接入RevenueCat后台配置相关
flutter
不爱吃糖的程序媛4 小时前
Flutter Orientation 插件在鸿蒙平台的使用指南
flutter·华为·harmonyos
2501_921930834 小时前
Flutter for OpenHarmony:三方库引入 geocoding 地理编码详解
flutter