杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 项目简介与功能总览
    • 运行环境与快速开始
    • 工程结构与文档布局
  • 架构与核心机制

    • 插件架构与原生平台桥接
    • 基类管理器与常量体系
    • 事件流与接收通知机制
  • 蓝牙连接与设备管理

    • 蓝牙连接与状态管理
    • 设备信息、配置与按键设置
    • 双设备连接与多链路管理
    • 数据传输与自定义命令
  • 音乐与媒体控制

    • 设备音乐与手机音乐播放控制
    • 音量与音频输出管理
  • 音效与音频模式

    • 均衡器与音效调节
    • 音频模式与降噪(ANC)设置
    • Auracast 音频广播
  • 设备功能控制

    • 闹钟管理
    • FM 收音机控制
    • 灯光控制
    • 充电仓与彩屏仓管理
  • 示例应用:杰理之家 Demo

    • 应用框架与交互组件
    • 设置、多语言与调试
  • 接口参考与文档中心

    • 发送接口参考
    • 接收接口与事件参考
    • 官方文档与集成指南

音频模式与降噪(ANC)设置

本文档介绍 JL_Home Demo 中音频模式与降噪(ANC,Active Noise Cancellation)设置的完整实现:从 Flutter UI 层的模式选择弹窗(AncModeManager)、设备设置页的调用入口,到底层 JL_BLEKit 原生框架中的 ANC 数据模型,说明用户如何切换"关闭 / 降噪 / 通透"三种音频模式以及该能力如何与设备侧交互。

Purpose and Scope

本页覆盖以下内容:

  • AncModeManager 的职责、三种 ANC 模式的定义与 UI 呈现机制
  • 设备设置页 DeviceSettingsPage 如何集成 ANC 模式选择入口
  • 本地化文案(AppLocalizations)在 ANC 模式选择中的使用方式
  • 与 iOS 原生框架 JL_BLEKit(JLModel_ANC.h、JLAutoConfigAnc.h)的分层关系

本页不展开与 ANC 无直接关系的内容:EQ 均衡器相关的实现属于同目录下的兄弟页面(例如 advanced_eq_page.dart 对应的"高级 EQ 设置"页面),OTA 升级、翻译、面对面传输等能力分别由各自的目录项覆盖。

Overview

在真无线耳机(TWS)等 JL 芯片音频设备中,ANC(主动降噪)与音频模式是用户最常调节的音频效果之一。设备通常支持三种模式:

模式值语义用户感知
0关闭(off)不进行任何降噪处理,环境声自然进入
1降噪(noise cancelling)主动消除环境噪声,适合通勤/嘈杂场景
2通透(transparent mode)麦克风采集环境声并回放,佩戴耳机也能听到周围声音

本仓库的 Demo 采用"UI 管理器 + 回调"的设计:AncModeManager 只负责渲染一个模态底部弹窗(showModalBottomSheet)并收集用户选择,把选中的模式值通过 onAncModeUpdated 回调交还给调用方 DeviceSettingsPage,由调用方决定如何把模式值下发给设备。这种解耦让 UI 层不关心 BLE 协议细节,也让同一套选择 UI 可以被多个入口复用。

从工程结构上看,这是一个三层协作:

  1. 页面层(Presentation):DeviceSettingsPage 检测到用户点击"噪声控制"入口后调用管理器;
  2. 交互层(Interaction):AncModeManager 弹出单选弹窗,将选择结果以 int 模式值回传;
  3. 设备层(Device):调用方将模式值写入 JL_BLEKit 设备控制通道(原生框架侧,对应 ANC 模型)。

Architecture

下图展示 ANC 音频模式设置的整体架构与数据流向:

flowchart TD
    subgraph sg_UI["UI 层(Flutter)"]
        SettingsPage["DeviceSettingsPage"]
        AncManager["AncModeManager"]
        BottomSheet["Modal Bottom Sheet<br/>(3 个单选 ListTile)"]
    end

    subgraph sg_I18n["本地化层"]
        Loc["AppLocalizations<br/>noiseControl / off / noiseCancelling / transparentMode"]
    end

    subgraph sg_Callback["设备控制层"]
        Callback["onAncModeUpdated(int) 回调"]
        DeviceSDK["JL_BLEKit(iOS 原生框架)<br/>JLModel_ANC / JLAutoConfigAnc"]
    end

    SettingsPage -->|"实例化并调用 showAncModeDialog"| AncManager
    AncManager -->|"showModalBottomSheet"| BottomSheet
    AncManager -->|"读取文案"| Loc
    BottomSheet -->|"用户选择模式 0 / 1 / 2"| Callback
    SettingsPage -->|"传入回调"| Callback
    Callback -->|"下发设备命令"| DeviceSDK

各模块职责说明:

  • DeviceSettingsPage(device_settings_page.dart):设备设置页,持有 AncModeManager 实例并在"噪声控制"菜单项被点击时触发弹窗。它是模式值的最终接收者,负责将用户选择转发给设备侧。
  • AncModeManager(anc_mode_manager.dart):无状态 UI 管理器,唯一职责是展示 ANC 模式选择弹窗并把结果通过回调传出。类本身不持有任何设备连接状态,可被多次、多入口复用。
  • AppLocalizations:本地化资源访问器。弹窗标题、三个模式选项的显示文本全部来自本地化键,保证文案随系统语言切换。
  • onAncModeUpdated 回调:页面层与管理器之间的解耦契约。管理器不关心模式值如何生效,只负责把用户的选择传回页面。
  • JL_BLEKit:iOS 原生 BLE 框架。框架内提供了 ANC 相关头文件(JLModel_ANC.h 定义了 ANC 数据模型,JLAutoConfigAnc.h 提供 ANC 自动配置接口),是模式值最终落地的设备协议层。

核心实现分析

AncModeManager:无状态 ANC 模式选择管理器

AncModeManager 是一个没有成员字段、没有构造参数的工具类,核心是一个公开方法 showAncModeDialog。它使用 Flutter 的 showModalBottomSheet 弹出模态底部弹窗,弹窗内通过私有方法 _buildAncModeOption 渲染三个单选条目:

void showAncModeDialog({
  required BuildContext context,
  required AppLocalizations loc,
  required int currentAncMode,
  required Function(int) onAncModeUpdated,
}) {
  showModalBottomSheet(
    context: context,
    backgroundColor: Colors.white,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
    ),
    builder: (context) {
      return Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              loc.noiseControl,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Color(0xFF242424),
              ),
            ),
            const SizedBox(height: 16),
            _buildAncModeOption(
                context,
                loc.off,
                0, // 关闭
                currentAncMode,
                    () => onAncModeUpdated(0) // 关闭
            ),
            _buildAncModeOption(
                context,
                loc.noiseCancelling,
                1, // 降噪
                currentAncMode,
                    () => onAncModeUpdated(1) // 降噪
            ),
            _buildAncModeOption(
                context,
                loc.transparentMode,
                2, // 通透
                currentAncMode,
                    () => onAncModeUpdated(2) // 通透
            ),
            const SizedBox(height: 8),
          ],
        ),
      );
    },
  );
}

来源:anc_mode_manager.dart

设计意图解读:

  • 模式值即协议值:代码注释明确标注 0 // 关闭、1 // 降噪、2 // 通透。这三个整数与设备端 ANC 模式枚举一一对应,因此 UI 层直接使用 int 传输,省去了枚举映射层,回传给页面后可直接用于设备命令。
  • currentAncMode 驱动选中态:调用方把设备当前生效的模式传入,弹窗据此高亮当前项,形成"当前状态回显"——用户打开弹窗即可看到设备此刻处于哪种模式。
  • 文案全部本地化:标题用 loc.noiseControl,三个选项分别用 loc.off、loc.noiseCancelling、loc.transparentMode。管理器的 UI 代码里没有硬编码任何用户可见字符串,这是为了支持多语言 Demo 的国际化要求。
  • 回调而非命令:onAncModeUpdated 接收一个 int 参数。管理器不感知设备连接状态、不处理写入失败——这些职责全部上抛给页面层,保证管理器可被任意入口复用。

单选条目渲染:_buildAncModeOption

每个模式选项都是一个 ListTile,通过 currentAncMode == mode 判断选中态,选中时显示蓝色实心单选图标、未选中显示灰色空心图标:

Widget _buildAncModeOption(
    BuildContext context,
    String title,
    int mode,
    int currentAncMode,
    VoidCallback onTap,
    ) {
  final isSelected = currentAncMode == mode;

  return ListTile(
    contentPadding: const EdgeInsets.symmetric(horizontal: 0),
    leading: Icon(
      isSelected ? Icons.radio_button_checked : Icons.radio_button_off,
      color: isSelected ? const Color(0xFF007AFF) : const Color(0xFFCCCCCC),
    ),
    title: Text(
      title,
      style: TextStyle(
        fontSize: 16,
        color: isSelected ? const Color(0xFF007AFF) : const Color(0xFF242424),
        fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal,
      ),
    ),
    onTap: () {
      Navigator.pop(context);
      onTap();
    },
  );
}

来源:anc_mode_manager.dart

设计意图解读:

  • 先关弹窗、再触发回调:onTap 中先执行 Navigator.pop(context) 关闭底部弹窗,然后才调用 onTap()(即包装后的 onAncModeUpdated(mode))。这保证了用户点击后界面立即响应、弹窗不会残留,属于典型的"即时反馈"交互模式。
  • 视觉反馈集中在颜色与图标:选中项使用主题蓝 0xFF007AFF,未选中项使用灰 0xFFCCCCCC 图标 + 深灰 0xFF242424 文字,与弹窗标题颜色一致,整体视觉风格统一。
  • 无动画/无自定义过渡:直接使用 ListTile 默认波纹反馈,保持 Demo 简洁。

调用方集成:DeviceSettingsPage

DeviceSettingsPage 是 ANC 弹窗的唯一调用入口。它在页面类中持有管理器实例:

final _ancModeManager = AncModeManager();

来源:device_settings_page.dart

当用户点击设置项且标题匹配"噪声控制"(loc.noiseControl)时,页面转发给管理器:

} else if (title == loc.noiseControl) {
  _ancModeManager.showAncModeDialog(
    context: context,
    ...

来源:device_settings_page.dart

设计意图解读:

  • 页面把"哪个设置项被点击"翻译成"调用哪个管理器",每个设置项一个分支(if / else if 链),结构直观、易于扩展。
  • 由于 showAncModeDialog 需要 currentAncMode 与 onAncModeUpdated 两个与设备状态相关的参数,页面层在此处扮演了"设备状态持有者"的角色——它知道当前模式,也知道如何让模式生效。这正好印证了分层原则:UI 细节下沉到管理器,设备逻辑保留在页面。

核心流程

用户切换 ANC 模式的端到端时序

下图描述从用户点击"噪声控制"入口到模式值回传页面层的完整交互时序:

sequenceDiagram
    participant U as 用户
    participant P as DeviceSettingsPage
    participant M as AncModeManager
    participant L as AppLocalizations
    participant CB as onAncModeUpdated 回调

    U->>P: 点击"噪声控制"设置项
    P->>M: showAncModeDialog(context, loc, currentAncMode, onAncModeUpdated)
    M->>L: 读取 noiseControl / off / noiseCancelling / transparentMode 文案
    M->>M: showModalBottomSheet 构建弹窗
    M-->>U: 弹出底部单选弹窗(高亮当前模式)
    U->>M: 点击某个模式条目(0 关闭 / 1 降噪 / 2 通透)
    M->>M: Navigator.pop 关闭弹窗
    M->>CB: onAncModeUpdated(selectedMode)
    CB-->>P: 页面接收模式值并刷新本地状态
    P-->>U: 界面更新(下次打开弹窗高亮新选择)

关键控制流说明

  1. 入口判断:DeviceSettingsPage 在设置项点击处理器中按标题匹配 loc.noiseControl,命中后调用 showAncModeDialog。这意味着文案键同时承担了"设置项标识"与"界面标题"双重职责,若本地化文案被修改,匹配逻辑需要同步调整。
  2. 弹窗构建:管理器读取本地化文案,创建 showModalBottomSheet。backgroundColor: Colors.white 与圆角 Radius.circular(12) 固定了弹窗外观,MainAxisSize.min 使弹窗高度仅包裹内容。
  3. 选中态回显:currentAncMode 决定三个 ListTile 中哪一个显示为选中(蓝色实心单选图标)。
  4. 模式提交:用户点击条目后先 pop 关闭弹窗,随即通过 onAncModeUpdated(mode) 把模式值(0/1/2)传回页面。
  5. 设备生效(页面层职责):页面拿到模式值后刷新本地 UI 状态,并将该值下发给设备——设备侧写入由 JL_BLEKit 原生框架完成(对应 ANC 模型与自动配置接口)。由于浏览预算限制,页面回调中设备命令发送的具体实现细节未在本页展开验证。

使用示例

示例一:在页面中弹出 ANC 模式选择弹窗

DeviceSettingsPage 中的实际调用方式——传入当前模式与更新回调:

_ancModeManager.showAncModeDialog(
  context: context,
  loc: loc,
  currentAncMode: currentAncMode,
  onAncModeUpdated: (mode) {
    // 页面在此处接收用户选择的模式值(0/1/2)
    // 并负责将模式写入设备(JL_BLEKit 通道)
  },
);

来源:device_settings_page.dart(参数结构见 anc_mode_manager.dart)

示例二:管理器内部构建三种模式选项

展示模式值与文案的绑定关系(来自 showAncModeDialog 弹窗内容构建部分):

_buildAncModeOption(
    context,
    loc.off,
    0, // 关闭
    currentAncMode,
        () => onAncModeUpdated(0) // 关闭
),
_buildAncModeOption(
    context,
    loc.noiseCancelling,
    1, // 降噪
    currentAncMode,
        () => onAncModeUpdated(1) // 降噪
),
_buildAncModeOption(
    context,
    loc.transparentMode,
    2, // 通透
    currentAncMode,
        () => onAncModeUpdated(2) // 通透
),

来源:anc_mode_manager.dart

示例三:单选条目的选中态渲染逻辑

复用一个 _buildAncModeOption 渲染三个选项,选中态完全由 currentAncMode == mode 推导:

final isSelected = currentAncMode == mode;

return ListTile(
  contentPadding: const EdgeInsets.symmetric(horizontal: 0),
  leading: Icon(
    isSelected ? Icons.radio_button_checked : Icons.radio_button_off,
    color: isSelected ? const Color(0xFF007AFF) : const Color(0xFFCCCCCC),
  ),
  title: Text(
    title,
    style: TextStyle(
      fontSize: 16,
      color: isSelected ? const Color(0xFF007AFF) : const Color(0xFF242424),
      fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal,
    ),
  ),
  onTap: () {
    Navigator.pop(context);
    onTap();
  },
);

来源:anc_mode_manager.dart

配置选项:ANC 模式值定义

ANC 模式通过整数枚举在 UI 层与设备层之间传递,取值与语义如下:

模式值本地化键显示文案(示例)语义典型场景
0loc.off关闭关闭降噪功能安静环境、省电
1loc.noiseCancelling降噪主动降噪通勤、嘈杂环境
2loc.transparentMode通透通透/环境音模式需要感知周围声音(过马路、对话)

该枚举定义于 anc_mode_manager.dart 的模式选项构建逻辑中;设备侧对应的数据模型见 JL_BLEKit 框架头文件 JLModel_ANC.h 与自动配置接口 JLAutoConfigAnc.h。

API 参考

void showAncModeDialog({required BuildContext context, required AppLocalizations loc, required int currentAncMode, required Function(int) onAncModeUpdated})

展示 ANC 模式选择底部弹窗。弹窗列出"关闭 / 降噪 / 通透"三个单选选项,用户选择后关闭弹窗并把模式值通过回调传出。

参数:

参数类型必填说明
contextBuildContext是用于 showModalBottomSheet 与 Navigator.pop 的构建上下文
locAppLocalizations是本地化资源访问器,提供弹窗标题与三个模式选项的文案
currentAncModeint是设备当前生效的 ANC 模式值(0/1/2),用于弹窗选中态回显
onAncModeUpdatedFunction(int)是用户确认模式后的回调,参数为选中的模式值(0/1/2)

返回: void。模式值不通过返回值传递,而是通过回调异步送达,与 Flutter 事件驱动的 UI 模型一致。

异常: 方法本身不显式抛异常;showModalBottomSheet 依赖有效的 BuildContext,若传入已销毁页面的 context 会触发 Flutter 框架级断言错误。

来源:anc_mode_manager.dart

Widget _buildAncModeOption(BuildContext context, String title, int mode, int currentAncMode, VoidCallback onTap)

构建单个 ANC 模式单选条目(私有方法)。根据 currentAncMode == mode 决定选中态:选中显示蓝色实心单选图标与蓝色文字,未选中显示灰色空心图标与深灰文字。点击时先 Navigator.pop(context) 关闭弹窗,再执行 onTap。

参数:

参数类型必填说明
contextBuildContext是用于 Navigator.pop
titleString是选项显示文案(来自本地化)
modeint是该选项对应的 ANC 模式值
currentAncModeint是当前生效模式,用于选中态判断
onTapVoidCallback是点击条目后的动作(通常包装 onAncModeUpdated(mode))

返回: Widget(ListTile)。

来源:anc_mode_manager.dart

故障模式、边界情况与并发

边界情况

  • 非法 currentAncMode 值:若传入的当前模式不在 {0, 1, 2} 范围内,三个选项的 isSelected 全部为 false,弹窗呈现"无选中项"状态。代码中没有任何兜底高亮逻辑——这是有意为之的简化:非法状态在设备侧不应出现,若出现则应由页面层在调用前纠正。
  • 弹窗与设备状态不同步:弹窗展示期间设备模式可能被其他入口(如物理按键、App 其他页面)改变,弹窗内的高亮在打开瞬间定格。Demo 未实现弹窗内实时监听设备状态更新,属于已知简化。
  • 回调的"即点即发"语义:onTap 中先 pop 再回调,回调是同步的。如果回调内执行耗时操作(如 BLE 写入),会阻塞 UI 线程;页面层在设计回调时应考虑异步下发。

失败模式

  • 设备断开后仍可弹出弹窗:showAncModeDialog 不检查设备连接状态,断开连接时用户仍可切换模式,回调后写入自然失败。错误处理完全依赖页面层的连接状态管理与 SDK 错误回调。
  • 写入失败无 UI 反馈:管理器不感知写入结果,弹窗关闭后不提供"设置失败"提示;设备侧是否成功写入取决于页面层与 JL_BLEKit 的交互实现。

并发与状态一致性

  • AncModeManager 是无状态类:不持有任何可变成员,多个页面/入口可安全共享同一个实例,不存在线程安全问题。
  • 模式状态(currentAncMode)由页面层持有,是单一数据源;弹窗只是其"只读快照 + 写入提议"。因此并发场景下只需保证页面层对该状态的管理是串行的(单线程 Flutter 事件循环天然满足)。

性能与运维考量

  • 开销极低:弹窗为纯 UI 构建,无网络、无磁盘、无轮询。三个 ListTile 的构建成本可忽略,MainAxisSize.min 保证不渲染多余空间。
  • 无缓存/无定时器:管理器无任何生命周期资源,不需要 dispose,不存在内存泄漏风险。
  • 扩展模式的成本:新增一种 ANC 模式只需在 showAncModeDialog 中追加一个 _buildAncModeOption 调用、在本地化文件中新增文案键、并在设备协议层确认模式值即可;三个改动点相互独立,便于横向扩展。

扩展点

  1. 新增 ANC 模式:在 showAncModeDialog 的 children 列表追加 _buildAncModeOption(...),并保证模式值不与现有值冲突;同时补充 AppLocalizations 文案键。
  2. 更换弹窗风格:AncModeManager 内部固定了 Colors.white 背景与 12 圆角,若要适配深色主题或自定义样式,只需修改该类的弹窗构建代码,不影响调用方。
  3. 复用选择 UI:由于管理器不依赖设备状态(模式值由参数传入),其他入口(如设备控制页、快捷面板)可以复用同一弹窗,只需提供各自的 currentAncMode 与回调。
  4. 替换交互模式:若未来需要"点击后即时生效并显示加载态",可在回调前增加异步等待——由于 pop 与回调解耦,这一改动被限制在管理器内部。

测试情况

未在仓库中发现针对 AncModeManager 的单元测试或 Widget 测试文件。该类的可测性较高:纯函数式输入(currentAncMode、文案)与输出(回调值),且不依赖设备连接,适合补充 Widget 测试验证三选项渲染、选中态高亮与回调值正确性。

相关链接

相关文档页面

  • 音频效果目录下的 EQ 均衡器相关页面(advanced_eq_page.dart 对应的"高级 EQ 设置"目录项),与 ANC 同属音频效果设置能力,可相互参考 UI 管理器的设计模式。
  • 设备设置页(DeviceSettingsPage)所属的"设备设置"目录项,涵盖连接状态、其他设备参数入口。

核心源文件

  • anc_mode_manager.dart:AncModeManager 完整实现(弹窗构建、单选渲染、模式值定义)。
  • device_settings_page.dart:ANC 弹窗的调用入口(第 42 行实例化、第 132-134 行调用)。
  • JLModel_ANC.h:JL_BLEKit 原生框架中的 ANC 数据模型定义。
  • JLAutoConfigAnc.h:JL_BLEKit 原生框架中的 ANC 自动配置接口。

注:设备侧 BLE 命令的实际发送逻辑位于 iOS 原生框架 JL_BLEKit 中(本 Demo 为 iOS 工程),Flutter 侧通过桥接调用;由于本次浏览预算限制,页面回调中命令下发的具体代码路径未在本页展开,建议结合设备设置页完整源码与 JL_BLEKit 头文件进一步阅读。

Prev
均衡器与音效调节
Next
Auracast 音频广播