欢迎加入 CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

前面几篇讲的都是"怎么把一个库适配到鸿蒙"。这一篇换个方向:用一个别人已经适配好的库,把它用进真实界面里

选的库是 CPF-Flutter 组织下的 light_sensor——读设备环境光传感器的插件。挑它的原因有两个:API 小到只有两个方法,讲清楚不费劲;但它能撑起一个真正有价值的交互——界面跟着环境光自适应。晚上关灯刷手机被屏幕亮得刺眼,这个问题就是靠光照度解决的。

环境准备:本文只讲使用本身,不重复环境搭建步骤。Flutter for OpenHarmony SDK、DevEco Studio、模拟器/真机的完整配置见官方指引:
https://atomgit.com/CPF-Flutter/flutter_samples/blob/master/docs/ohos/getting-started/flutter-oh-env-setup.md

完整示例工程:light_adaptive_demo(见文末)。

在这里插入图片描述


一、接入:从 CPF-Flutter 组织拉依赖

light_sensor 已经被 CPF-Flutter 适配过鸿蒙版本,直接按 git + TAG 引入:

dependencies:
  light_sensor:
    git:
      url: https://atomgit.com/CPF-Flutter/fluttertpc_light_sensor.git
      ref: 3.0.2-ohos-1.0.0-beta.1
flutter pub get

这个库不需要任何权限。环境光传感器在鸿蒙上不涉及隐私数据,module.json5 里不用声明 requestPermissions,装完就能读。

二、API 只有两个方法

整个 Dart 层的对外接口就这些:

方法返回用途
LightSensor.hasSensor()Future<bool>设备有没有光线传感器
LightSensor.luxStream()Stream<int>环境光照度数据流,单位 Lux

luxStream() 推的是整数 Lux,不是小数——原生侧做了取整再上报,所以 Dart 层的 Stream<int> 拿着就能直接用,不用自己再转类型。

三、先探针再订阅

hasSensor() 这一步别省。不是所有设备都有环境光传感器(部分平板、车机、模拟器镜像就没有),不检测直接 listen 的话,拿到的就是一个永远不吐数据的流,界面上表现为"一直转圈",很难查。

Future<void> _bootstrap() async {
  final bool hasSensor = await LightSensor.hasSensor();
  if (!mounted) {
    return;
  }
  setState(() => _hasSensor = hasSensor);
  if (!hasSensor) {
    return;   // 没传感器就别订阅,直接给提示
  }
  _subscription = LightSensor.luxStream().listen(_onLux);
}

hasSensor() 和下面要订阅的 luxStream() 是两条独立的通道,前者走 MethodChannel、后者走 EventChannel,互不影响——所以"能检测到传感器"和"能收到数据"要分别验证,不能拿其中一个的成败去推断另一个。

四、把 Lux 变成界面:先分档

原始的光照度是个连续整数,从 0 到几万,直接拿来做 UI 判断会很难写。中间加一层档位,代码就清爽了:

enum LightLevel {
  pitchDark('漆黑', 0, 10),
  dim('昏暗', 10, 100),
  indoor('室内', 100, 1000),
  bright('明亮', 1000, 10000),
  glaring('强光', 10000, 1 << 30);

  const LightLevel(this.label, this.min, this.max);
  final String label;
  final int min;
  final int max;

  static LightLevel of(int lux) {
    for (final level in values) {
      if (lux >= level.min && lux < level.max) {
        return level;
      }
    }
    return glaring;
  }
}

档位区间不用纠结精确数值,参考量级就够:10 Lux 以下是几乎全黑,100 左右是昏暗的室内,几百到一千是普通室内照明,一万以上是户外强光或直射灯下

分完档,再给每档挂一套配色和一个建议屏幕亮度:

_LevelStyle get style {
  switch (this) {
    case LightLevel.pitchDark:
      return const _LevelStyle(
        pageBackground: Color(0xFF0B0B0D),
        foreground: Color(0xFFE8E8EA),
        suggestedBrightness: 0.12,
        tip: '环境很暗,界面转深色并压低亮度,减少刺眼',
      );
    case LightLevel.indoor:
      return const _LevelStyle(
        pageBackground: Color(0xFFF3F4F7),
        foreground: Color(0xFF1B1D21),
        suggestedBrightness: 0.55,
        tip: '常见室内光,用浅色主题、正常亮度即可',
      );
    // …其余三档同理
  }
}

五、订阅与释放

订阅本身没什么特别的,但有两处必须写全。

第一,回调里要判 mounted 传感器的回调来自原生侧,到达时机不受页面控制:

_subscription = LightSensor.luxStream().listen(
  (int lux) {
    if (!mounted) {
      return;
    }
    setState(() {
      _lux = lux;
      _history.add(lux);
      if (_history.length > 48) {
        _history.removeAt(0);   // 只留最近 48 个采样,用来画趋势
      }
    });
  },
  onError: (Object error, StackTrace _) {
    if (!mounted) {
      return;
    }
    setState(() => _error = '光照数据流中断:$error');
  },
);

第二,dispose 里必须 cancel() 传感器是长连接,不取消的话页面销毁后回调继续打过来,setState 直接抛异常:


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

onError 也建议带上。原生侧读传感器失败时会往流里发错误,不接的话就是一个未处理的异步异常。

顺带一提:这个流的上报间隔大约是 100 ms,也就是每秒十次重建。对一个只显示读数的界面完全够用;如果要在回调里做重活(比如实时算平均值、写日志),建议自己加一层节流,别在每个事件里都干重活。

六、实测:模拟器上注入光照看界面反应

鸿蒙模拟器支持直接注入光照度,所以这个库的自适应效果在模拟器上就能完整验证,不依赖真机的物理传感器。

devecocli emulator sensor --target "Pura X View" --light-intensity 320

分别在三个档位注入,界面给出的判定和建议亮度如下:

注入光照度界面读数判定档位建议屏幕亮度页面配色
4242 Lux昏暗30%深色(R21 G22 B26
320320 Lux室内55%浅色(R243 G244 B247
3000030000 Lux强光100%暖白(R255 G248 B230

配色那一列是截屏后采样页面背景得到的实际像素值——深色和暖白差了非常多,说明主题确实跟着环境光换了,不是只改了个文案。

在这里插入图片描述

在这里插入图片描述

顺带说一句:模拟器里这个传感器默认读数是 0。第一次跑起来看到界面直接进"漆黑"档,会以为是渲染坏了,其实是环境光没注入——用上面那条命令给个值就正常了。

七、已知限制

  • 档位阈值是主观的。上面那张表是我按常见场景定的,不同应用该有自己的标准(比如阅读类可以更保守,工具类可以更激进)。插件只负责给 Lux,怎么用是应用的事。
  • 光照度变化是异步的。从关灯到界面变色有一次传感器上报的延迟,别指望"零延迟响应"。界面切换我都加了 300 ms 的动画过渡,避免忽明忽暗闪一下。
  • 不是所有设备都有这个传感器。所以第三节那个 hasSensor() 探针必须有,并且要给"没有传感器"的降级路径——没有光感就退回到用户手动设置的主题。

小结

这个库只有两个方法,但把"如何用好一个传感器插件"的要点都覆盖到了:

  • 先探针再订阅——hasSensor() 确认设备真的有能力,再开流;
  • 分档再映射——连续值直接驱动 UI 会很难维护,中间加一层档位;
  • 订阅要成套——回调判 mounted、流带 onErrordisposecancel
  • 模拟器能验——鸿蒙模拟器可注入光照度,自适应效果不用等真机。

示例工程 light_adaptive_demo 完整实现了上面这套(一个自适应阅读页:当前光照度卡片 + 自适应预览 + 建议亮度条 + 最近采样趋势条)。

依赖配置再贴一次:

dependencies:
  light_sensor:
    git:
      url: https://atomgit.com/CPF-Flutter/fluttertpc_light_sensor.git
      ref: 3.0.2-ohos-1.0.0-beta.1

欢迎加入 CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Flutter 三方库鸿蒙适配清单:https://atomgit.com/oh-flutter/flutter-ohos-adaptation-checklist

Logo

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

更多推荐