Flutter 显示刘海(Display Cutout)与屏幕旋转集成测试:display_cutout_rotation 深度解析
Flutter 显示刘海Display Cutout与屏幕旋转集成测试display_cutout_rotation 深度解析【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter本文围绕 Flutter 仓库中的 display_cutout_rotation 集成测试展开它验证 Android 设备的显示刘海cutout在竖屏/横屏旋转时能否通过MediaQuery.of(context).displayFeatures被 Dart 应用代码正确感知。读完本文你将掌握该测试的本地运行方式、devicelab 自动化调度原理、测试用例的断言逻辑以及从引擎侧DisplayFeature类型到框架侧 API 的完整数据链路。测试目标验证刘海在旋转时位置的实时更新Android 自 API 28 起在开发者选项中提供模拟刘海display cutout emulation能力API 30 起该行为稳定可用。display_cutout_rotation测试的核心场景是当设备开启模拟刘海后旋转屏幕时原本位于顶部的刘海竖屏应报告为位于左侧landscapeLeft应用侧的DisplayFeature数据必须随之正确更新。测试假设设备上已启用开发者选项com.android.internal.display.cutout.emulation.tall并在以下三种场景中断言竖屏portraitUp时存在唯一一个 cutout 类型特征且其bounds.top 0刘海贴顶横屏向左landscapeLeft时同样只有一个 cutout且bounds.left 0刘海贴左侧在同一 App 实例内从竖屏旋转到横屏特征查询结果能从顶部切换为左侧验证旋转过程中数据的动态更新。项目结构与运行方式该测试工程位于dev/integration_tests/display_cutout_rotation/是一个标准的 Flutter 集成测试应用由三部分组成lib/main.dart被测的极简 App负责把当前刘海状态以文本形式渲染出来integration_test/display_cutout_test.dart基于integration_test包的端到端测试用例pubspec.yaml依赖flutter、flutter_test、flutter_driver与integration_test均来自 SDK并设置了resolution: workspace参与仓库的 workspace 解析publish_to: none表明其为私有工程。按照 README 的说明本地运行有两种方式# 方式一在工程目录内直接运行集成测试 cd dev/integration_tests/display_cutout_rotation flutter test integration_test/display_cutout_test.dart # 方式二通过 devicelab 测试运行器执行 cd dev/devicelab dart bin/test_runner.dart test -t android_display_cutout方式二走的是 devicelab 任务体系android_display_cutout任务的真实定义在 dev/devicelab/lib/tasks/integration_tests.dart 中的createDisplayCutoutTest()。源码注释明确给出了适用前提设备必须启用开发者设置且为 Android API 30 及以上运行时通过getprop ro.build.version.sdk校验SDK 小于 30 直接判失败仅支持 Android 设备非AndroidDevice直接抛TaskResult.failure任务setup阶段执行cmd overlay enable com.android.internal.display.cutout.emulation.tall在设备上注入合成刘海Synthetic notch注释特别提示该命令会导致正在运行的 Android Activity 被重新创建tearDown阶段执行cmd overlay disable移除合成刘海避免污染后续测试。也就是说README 中假设设备已启用com.android.internal.display.cutout.emulation.tall这一前提在 CI 路径下由 devicelab 任务自动完成注入与清理本地手动运行则需自行开启。被测应用把刘海状态可视化的最小实现lib/main.dart 中只有约 50 行代码MyApp的build方法核心逻辑如下final ListDisplayFeature displayFeatures MediaQuery.of(context).displayFeatures; displayFeatures.retainWhere( (DisplayFeature feature) feature.type DisplayFeatureType.cutout, ); // ... final Rect cutout displayFeatures[0].bounds; if (cutout.top 0) { text CutoutTop; } else if (cutout.left 0) { text CutoutLeft; } else { text CutoutNeither; } return MaterialApp( debugShowCheckedModeBanner: false, home: Text(Cutout status: $text, key: Key(text)), );源码注释解释了这段对测试而言并非必需的逻辑存在的目的它在人工视觉调试或观看远端设备录像时很有用——屏幕上直接显示CutoutTop/CutoutLeft/CutoutNone/CutoutMany/CutoutNeither之一的状态文本同时Text组件还携带一个与内容一致的Key。测试代码正是依赖这个Text元素通过find.byType(Text)定位BuildContext进而读取MediaQuery数据因此注释强调Tests assume there is some text element displayed。测试用例详解旋转、轮询与断言integration_test/display_cutout_test.dart 使用IntegrationTestWidgetsFlutterBinding.ensureInitialized()初始化后包含三个testWidgets用例与一个tearDown1. 竖屏时刘海在顶部await setOrientationAndWaitUntilRotation(tester, DeviceOrientation.portraitUp); await tester.pumpWidget(const MyApp()); final BuildContext context tester.element(find.byType(Text)); final IterableDisplayFeature displayFeatures getCutouts(tester, context); expect(displayFeatures.length, 1, reason: Single cutout display feature expected); expect(displayFeatures.first.bounds.top, 0, ...);断言的reason写得很有工程经验如果top不为 0提示测试设备上是否已有真实摄像头刘海或 window inset——即合成刘海与真机硬件刘海可能叠加导致断言失败时能快速定位原因。2. 横屏向左时刘海在左侧与用例 1 对称旋转至DeviceOrientation.landscapeLeft后断言displayFeatures.first.bounds.left 0。3. 旋转过程中刘海位置动态更新这是唯一不重新pumpWidget新实例、而是复用同一MyApp实例的用例先验证竖屏下top 0再调用setOrientationAndWaitUntilRotation切到landscapeLeft并重新pumpWidget然后重新获取BuildContexttester.element(find.byType(Text))再次查询再断言left 0。它验证的关键点是旋转后MediaQuery.displayFeatures的数据不是陈旧的会随窗口布局变化被框架刷新。旋转等待与特征过滤的两个辅助函数旋转等待函数体现了对底层异步链路的理解源码注释写道Rotations have an async communication to engine which then has an async communication to the android operating system——Dart 层发起旋转请求后经由引擎与 Android 系统两次异步通信才能完成因此需要轮询Futurevoid setOrientationAndWaitUntilRotation( WidgetTester tester, DeviceOrientation orientation, ) async { await SystemChrome.setPreferredOrientations(DeviceOrientation[orientation]); // 将 DeviceOrientation 映射为期望的 Orientationportrait/landscape while (true) { final BuildContext context tester.element(find.byType(Text)); if (context.mounted expectedOrientation MediaQuery.of(context).orientation) { break; } await tester.pumpAndSettle(); } }getCutouts则封装了对displayFeatures的过滤逻辑只保留feature.type DisplayFeatureType.cutout的特征排除fold、hinge等其他类型IterableDisplayFeature getCutouts(WidgetTester tester, BuildContext context) { final ListDisplayFeature displayFeatures MediaQuery.of(context).displayFeatures; return displayFeatures.where( (DisplayFeature feature) feature.type DisplayFeatureType.cutout, ); }tearDown中调用SystemChrome.setPreferredOrientations(DeviceOrientation[])恢复设备默认方向注释说明这是为了避免测试污染test pollution确保后续用例或同一设备上的其他任务不受方向设置影响。底层原理DisplayFeature 数据从 Android 到 Dart 的链路从源码结构看DisplayFeature在本仓库中是一条贯穿 Android 嵌入层、引擎 Dart 层的数据模型Dart 引擎侧engine/src/flutter/lib/ui/platform_dispatcher.dart 定义了DisplayFeature类文档注释明确 This is populated only on Android包含三个字段bounds以逻辑像素表示、被硬件特征占据的区域在双屏设备上坐标系从左屏或上屏的左上角 (0,0) 起算横跨两块屏幕及中间视觉空间typeDisplayFeatureType其中hinge与cutout会遮挡显示区域fold不会state折叠/铰链的姿态如postureFlat、postureHalfOpened而对cutout类型恒为DisplayFeatureState.unknown——构造函数中甚至有断言强制这一约束!identical(type, cutout) || identical(state, unknown)Android 引擎侧engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java 中存在对应的 JavaDisplayFeature类与DisplayFeatureType枚举注释说明其基于androidx.window.layout.DisplayFeature并增加了对 cutout 的支持其中CUTOUT(3)明确对应android.view.DisplayCutout即 Android 系统对摄像头/传感器开孔区域的原生抽象。这解释了测试断言的坐标系语义bounds.top 0表示切出区域紧贴窗口顶部bounds.left 0表示紧贴左侧——正是合成刘海在竖屏/横屏下的预期位置。关键前提与限制结合测试源码与 devicelab 任务实现可以归纳出运行和解读该测试的几个约束约束来源说明仅 Androidintegration_tests.dartdevice is! AndroidDevice时任务直接失败Android API 30integration_tests.dart开发者设置项在 API 28 加入、行为在 API 30 才完整需开发者选项 合成刘海README手动运行时须先启用com.android.internal.display.cutout.emulation.talldevicelab 路径由setup自动注入断言对真实硬件刘海敏感display_cutout_test.dart真机自带的摄像头开孔会叠加在合成刘海之上导致top/left 0断言或恰好一个 cutout断言失败断言 reason 中已给出排查提示cutout的state恒为 unknownplatform_dispatcher.dart引擎层构造断言保证姿态信息仅对折叠类特征有意义小结display_cutout_rotation用不到 200 行代码构建了一个高价值的端到端回归场景通过 devicelab 注入合成刘海、通过SystemChrome.setPreferredOrientations 轮询模拟真实旋转、再通过MediaQuery.displayFeatures断言 cutout 的bounds随方向正确迁移。它既验证了 Android 嵌入层到引擎再到框架的DisplayFeature数据通路也覆盖了旋转这类跨异步边界的时序场景是理解 Flutter 如何感知折叠屏/刘海等硬件显示特征的一个精确而完整的入口。【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考