Flutter-OH 插件volume_controller 在鸿蒙平台的使用指南

欢迎大家加入开源鸿蒙跨平台社区

前言

在 HarmonyOS 应用开发中,经常需要控制或监听系统媒体音量,例如音乐播放器、视频应用、游戏等场景。volume_controller 是一款支持鸿蒙平台的 Flutter 音量控制插件,提供简洁的 API 实现音量获取、设置、监听和静音功能。

需求背景:Flutter 应用在鸿蒙设备上需要与系统音量交互,实现与 Android/iOS 一致的用户体验。

插件简介:volume_controller 是跨平台系统音量控制插件,支持 Android、iOS、macOS、Windows、Linux 和 HarmonyOS。

文章目标:帮助开发者在鸿蒙平台上快速集成 volume_controller,掌握基本用法和常见场景实现。

一、插件简介

1.1 什么是 volume_controller

volume_controller 是一个 Flutter 插件,提供统一的 Dart API 来控制和监听各平台(包括 HarmonyOS)的系统媒体音量。通过 MethodChannel 与原生平台通信,支持获取、设置音量,监听音量变化,以及静音/取消静音。

1.2 核心功能

  • getVolume:获取当前系统媒体音量(0.0~1.0)
  • setVolume:设置系统媒体音量
  • addListener:监听系统音量变化,支持可选获取初始音量
  • removeListener:移除音量监听
  • isMuted:检查当前是否静音
  • setMute:静音或取消静音

1.3 支持的功能(鸿蒙平台)

功能 鸿蒙支持 说明
getVolume 获取媒体音量
setVolume 需申请 ACCESS_NOTIFICATION_POLICY 权限
addListener 通过系统 volumeChange 事件实现
removeListener 取消监听
isMuted 基于音量是否为 0 判断
setMute 静音/恢复
showSystemUI 鸿蒙不支持调节时显示系统音量条

二、环境准备

2.1 开发环境要求

工具 版本要求 说明
Flutter SDK 3.35.8-ohos-0.0.2+ 支持 HarmonyOS 的 Flutter 版本
Dart SDK 3.9.2+ 随 Flutter SDK 一起安装
DevEco Studio 6.1.0+ HarmonyOS 官方 IDE
HarmonyOS SDK 5.1.0(18)+ HarmonyOS 开发工具包

2.2 检查 Flutter 环境

flutter doctor -v

确保输出中包含「HarmonyOS toolchain」且状态为 ✓。

2.3 创建 Flutter 项目

若尚未有项目,可先创建:

flutter create my_volume_app
cd my_volume_app

三、安装配置

3.1 添加依赖

方式一:Git 依赖(推荐)

dependencies:
  volume_controller:
    git:
      url: https://github.com/kurenai7968/volume_controller
      ref: master

方式二:本地路径依赖

若已克隆仓库到本地:

dependencies:
  volume_controller:
    path: ../volume_controller

方式三:pub.dev 依赖

若插件已发布到 pub.dev(当前需确认是否包含 ohos 支持):

dependencies:
  volume_controller: ^3.4.2

3.2 安装依赖包

flutter pub get

3.3 验证安装

lib/main.dart 中尝试导入:

import 'package:volume_controller/volume_controller.dart';

若无报错,说明安装成功。

四、快速开始

4.1 最简单的使用示例

以下示例展示如何获取当前音量并设置音量:

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

void main() => runApp(const MyApp());

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('音量控制示例')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              FutureBuilder<double>(
                future: VolumeController.instance.getVolume(),
                builder: (context, snapshot) {
                  final vol = snapshot.data ?? 0.0;
                  return Text('当前音量: ${(vol * 100).toStringAsFixed(0)}%');
                },
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () async {
                  await VolumeController.instance.setVolume(0.5);
                },
                child: const Text('设置为 50%'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

4.2 运行应用

连接鸿蒙真机,执行:

flutter run

选择鸿蒙设备后,应用将安装并运行。调节设备音量键,可验证插件是否正常工作(若已实现监听,界面会更新)。

五、详细使用指南

5.1 基本用法

VolumeController 采用单例模式,通过 VolumeController.instance 获取实例:

final controller = VolumeController.instance;

5.2 获取当前音量

// 异步获取,返回 0.0~1.0
double volume = await VolumeController.instance.getVolume();
print('当前音量: $volume');  // 例如 0.75 表示 75%

5.3 设置音量

// 设置为 80%
await VolumeController.instance.setVolume(0.8);

// 静音
await VolumeController.instance.setVolume(0.0);

5.4 监听音量变化

import 'dart:async';

late StreamSubscription<double> _subscription;


void initState() {
  super.initState();
  _subscription = VolumeController.instance.addListener(
    (volume) {
      print('音量变化: $volume');
      setState(() => _currentVolume = volume);
    },
    fetchInitialVolume: true,  // 是否在监听时立即返回当前音量
  );
}


void dispose() {
  _subscription.cancel();  // 或调用 VolumeController.instance.removeListener();
  super.dispose();
}

5.5 静音与取消静音

// 静音
await VolumeController.instance.setMute(true);

// 取消静音(恢复静音前的音量)
await VolumeController.instance.setMute(false);

// 检查是否静音
bool isMuted = await VolumeController.instance.isMuted();

5.6 错误处理

try {
  await VolumeController.instance.setVolume(0.5);
} catch (e) {
  print('设置音量失败: $e');
  // 可能是权限未授予或设备不支持
}

六、实际应用场景

6.1 音乐播放器音量控制

在音乐播放器中,通常需要 Slider 调节音量,并实时显示当前音量:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:volume_controller/volume_controller.dart';

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

  
  State<MusicPlayerPage> createState() => _MusicPlayerPageState();
}

class _MusicPlayerPageState extends State<MusicPlayerPage> {
  final _controller = VolumeController.instance;
  late StreamSubscription<double> _subscription;
  double _volume = 0.5;

  
  void initState() {
    super.initState();
    _subscription = _controller.addListener((v) {
      setState(() => _volume = v);
    }, fetchInitialVolume: true);
  }

  
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.volume_up, size: 48),
          const SizedBox(height: 16),
          Text('音量: ${(_volume * 100).toStringAsFixed(0)}%'),
          Slider(
            value: _volume,
            onChanged: (v) => _controller.setVolume(v),
          ),
        ],
      ),
    );
  }
}

关键点说明

  • 使用 addListener 保持 UI 与系统音量同步(用户按音量键时界面会更新)
  • dispose 时务必 cancel 订阅,避免内存泄漏

6.2 视频播放器静音按钮

点击按钮切换静音状态:

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

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

  
  State<VideoPlayerPage> createState() => _VideoPlayerPageState();
}

class _VideoPlayerPageState extends State<VideoPlayerPage> {
  bool _isMuted = false;

  Future<void> _toggleMute() async {
    _isMuted = !_isMuted;
    await VolumeController.instance.setMute(_isMuted);
    setState(() {});
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 视频播放区域
          Center(child: Text('视频内容')),
          // 静音按钮
          Positioned(
            right: 16,
            bottom: 16,
            child: IconButton(
              icon: Icon(_isMuted ? Icons.volume_off : Icons.volume_up),
              onPressed: _toggleMute,
            ),
          ),
        ],
      ),
    );
  }
}

关键点说明

  • setMute(true) 会保存当前音量,setMute(false) 时恢复
  • 可根据 isMuted 状态切换图标显示

6.3 设置页音量调节

在设置页面提供音量调节入口:

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

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

  
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  double _volume = 0.5;

  
  void initState() {
    super.initState();
    _loadVolume();
  }

  Future<void> _loadVolume() async {
    final v = await VolumeController.instance.getVolume();
    setState(() => _volume = v);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListTile(
        title: const Text('媒体音量'),
        subtitle: Text('${(_volume * 100).toStringAsFixed(0)}%'),
        trailing: SizedBox(
          width: 120,
          child: Slider(
            value: _volume,
            onChanged: (v) async {
              await VolumeController.instance.setVolume(v);
              setState(() => _volume = v);
            },
          ),
        ),
      ),
    );
  }
}

关键点说明

  • 进入页面时通过 getVolume 获取当前值
  • Slider 的 onChanged 中调用 setVolume 并更新本地状态

6.4 游戏内音量快捷控制

游戏场景中,根据用户操作快速静音/恢复:

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

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

  
  State<GamePage> createState() => _GamePageState();
}

class _GamePageState extends State<GamePage> {
  bool _wasMutedBeforePause = false;

  void _onGamePaused() async {
    _wasMutedBeforePause = await VolumeController.instance.isMuted();
    await VolumeController.instance.setMute(true);
  }

  void _onGameResumed() async {
    if (!_wasMutedBeforePause) {
      await VolumeController.instance.setMute(false);
    }
  }

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) => _onGamePaused(),
      onTapUp: (_) => _onGameResumed(),
      child: const Center(child: Text('游戏内容')),
    );
  }
}

关键点说明

  • 暂停时静音,恢复时根据暂停前状态决定是否取消静音
  • 可根据实际游戏逻辑调整调用时机

七、注意事项和最佳实践

7.1 生命周期管理

  • 使用 addListener 时,必须在 dispose 中调用 subscription.cancel()removeListener()
  • 避免在已 dispose 的 Widget 中继续调用插件方法

7.2 权限配置(鸿蒙)

ohos/entry/src/main/module.json5 中声明:

"requestPermissions": [
  {"name": "ohos.permission.ACCESS_NOTIFICATION_POLICY"}
]

否则 setVolume、setMute 可能失败。

7.3 异步处理

  • getVolume、setVolume、isMuted、setMute 均为异步方法,需使用 await 或 .then()
  • 在 initState 中调用异步方法时,注意 setState 的时机,避免在 mount 之前调用

7.4 错误处理最佳实践

Future<void> safeSetVolume(double v) async {
  try {
    await VolumeController.instance.setVolume(v);
  } on PlatformException catch (e) {
    if (e.code == 'VOLUME_ERROR') {
      debugPrint('音量设置失败: ${e.message}');
      // 可提示用户检查权限
    }
  }
}

7.5 性能考虑

  • addListener 会注册系统级事件监听,仅在需要时添加,不用时及时取消
  • 避免在 addListener 回调中执行耗时操作,以免阻塞 UI

八、常见问题解答

Q1:鸿蒙上 setVolume 调用后没有效果?

:检查是否在 module.json5 中申请了 ohos.permission.ACCESS_NOTIFICATION_POLICY 权限,并在系统设置中授予应用相应权限。部分设备可能需要在「设置 > 应用 > 权限」中手动开启。

Q2:addListener 后按音量键,回调不触发?

:确认已正确调用 addListener,且未在 dispose 前调用 removeListener。鸿蒙端通过 AudioVolumeManager.on('volumeChange') 实现,若插件实现有误可能导致无响应,可参考 README.OpenHarmony_CN.md 检查原生实现。

Q3:showSystemUI 在鸿蒙上有效吗?

:无效。showSystemUI 仅在 Android 和 iOS 上支持,鸿蒙平台调节音量时不会显示系统音量条,该参数会被忽略。

Q4:如何判断当前是否静音?

:使用 await VolumeController.instance.isMuted()。在鸿蒙上,通常通过音量是否为 0 判断。

Q5:setMute(false) 后音量是多少?

:会恢复为调用 setMute(true) 之前的音量。若从未调用过 setMute(true),则恢复为 0.5(50%)。

Q6:可以在多个页面同时 addListener 吗?

:可以,但每个监听会独立收到回调。注意在各自页面 dispose 时取消自己的订阅,避免重复监听和泄漏。

Q7:模拟器上能测试吗?

:建议在真机上测试。鸿蒙模拟器对音频 API 的支持可能不完整,真机测试更可靠。

九、完整示例代码

以下为完整的可运行示例,包含音量显示、Slider 调节、静音按钮和监听:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:volume_controller/volume_controller.dart';

void main() => runApp(const MyApp());

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '音量控制示例',
      home: const VolumeDemoPage(),
    );
  }
}

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

  
  State<VolumeDemoPage> createState() => _VolumeDemoPageState();
}

class _VolumeDemoPageState extends State<VolumeDemoPage> {
  final _controller = VolumeController.instance;
  late StreamSubscription<double> _subscription;
  double _volume = 0;
  bool _isMuted = false;

  
  void initState() {
    super.initState();
    _subscription = _controller.addListener((v) {
      setState(() => _volume = v);
    }, fetchInitialVolume: true);
    _controller.isMuted().then((m) => setState(() => _isMuted = m));
  }

  
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('音量控制')),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('当前音量: ${(_volume * 100).toStringAsFixed(0)}%',
                style: Theme.of(context).textTheme.headlineSmall),
            const SizedBox(height: 24),
            Slider(
              value: _volume,
              onChanged: (v) => _controller.setVolume(v),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => _controller.setMute(true),
                  child: const Text('静音'),
                ),
                const SizedBox(width: 16),
                ElevatedButton(
                  onPressed: () => _controller.setMute(false),
                  child: const Text('取消静音'),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Text('静音状态: $_isMuted'),
          ],
        ),
      ),
    );
  }
}

十、总结

关键要点回顾

  1. 通过 VolumeController.instance 获取单例,API 与 Android/iOS 一致
  2. 鸿蒙需申请 ACCESS_NOTIFICATION_POLICY 权限才能设置音量
  3. addListener 后务必在 dispose 中 cancel,避免泄漏
  4. showSystemUI 在鸿蒙上不支持,可忽略

下一步

参考资料

Logo

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

更多推荐