概览
有些应用专为横屏设计,例如平板上的摄像头站。但无论用户如何放置设备,按钮在左侧还是右侧,他们仍然期望画面方向正确。
KlugLayout 将 Android 屏幕方向设置为两侧横屏。应用保持横屏,但会随设备旋转 180 度。它还会报告当前的显示旋转角度,因此应用可以根据设备的持握方式调整自身内容,例如摄像头画面。
折叠屏设备会在应用运行期间改变屏幕。Galaxy Z Fold 或 Pixel Fold 展开时是平板,合上时是手机;Galaxy Z Flip 则从高挑的主显示屏切换到小巧、近乎正方形的外屏。当设备将应用移到另一块屏幕时,AIR 舞台并不总能收到尺寸变化事件,屏幕大小和摄像头挖孔的启动值仍是第一块屏幕的数值。KlugLayout 在原生层监视这一变化,并告诉应用它当前处于哪种屏幕上。
KlugLayout 每天都在我们的应用中用于 Android 平板,这就是我们的概念验证。
折叠屏设备
KlugLayout 将当前窗口归入三种类别之一,并在类别变化时发送事件:
| tablet | 大屏显示。示例:展开的 Galaxy Z Fold 或 Pixel Fold。 |
| phone | 手机尺寸的显示屏。示例:合上的 Fold,或 Flip 的主显示屏。 |
| cover | 小型外屏。示例:Galaxy Z Flip 的外屏。 |
Fold 合上时,横屏锁定会暂停,以便手机尺寸的外屏使用竖屏,设备再次展开后锁定恢复。应用还会获得当前屏幕的大小和摄像头挖孔信息,因此折叠后构建的布局可适配其所显示的屏幕。
在我们的应用中,观看端会在每次折叠或外屏变化时退出并重新登录,因此实时流总能在新屏幕上干净地启动。在 Flip 外屏上,界面按普通手机布局并缩小以适配。
已在 Android 模拟器中使用 Pixel 9 Pro Fold 和 Galaxy Z Flip 设备配置文件测试。
iPhone Duo
iPhone Duo 是 Apple 的首款折叠屏设备:合上时是手机,展开时是小型平板。AIR 自己无法跟随这种变化。设备展开时,iOS 可能让应用保持在带黑边的手机窗口中,不会收到尺寸变化事件,启动时的屏幕数值仍是外屏的数值。
KlugLayout 在 iOS 上检测应用所处的屏幕,并发送与 Android 上相同的 DEVICE_TYPE_CHANGED 事件:Duo 合上时为 "phone",展开时为 "tablet"。同一份代码适用于两个平台。
状态:基本布局已解决。合上和展开的布局、应用运行期间的折叠与展开、旋转展开状态的设备以及居中的原生对话框均可正常工作。推流和摄像头设置仍需在真机上测试。
没有理由等待 AIR 52:从 2027 年 4 月起,上传到 App Store 的应用必须使用 iOS 27 SDK 构建,此后 AIR 应用将把 iPhone Duo 视为它本来的样子,即折叠屏设备。KlugLayout 现在就能处理,使用 AIR 51.4.1 即可。
已在 Xcode 27 模拟器 (iPhone Duo) 中使用 HARMAN AIR 51.4.1 测试。
特性
- 自成一体:一个 ANE,无需添加核心库或依赖 ANE
- 两侧横屏,随设备旋转
- 折叠检测:tablet、phone 或 Flip 外屏,每次变化都会触发事件
- iPhone Duo:合上时为 phone,展开时为 tablet,事件与 Android 上相同
- Fold 合上期间自动解除横屏锁定
- 当前屏幕的大小和摄像头挖孔,折叠之后同样适用
- 不会切换到竖屏
- 当前显示旋转角度:0、90、180 或 270 度
- 从应用启动起即生效
- 在其他平台上不执行任何操作,因此代码可以保留在跨平台应用中
支持的平台
入门
将扩展添加到您的应用描述文件中:
<extensions>
<extensionID>com.klug.layout</extensionID>
</extensions>
面向 Android 16 的应用还应在清单附加内容的 <application> 中使用此属性,以允许在大屏幕上固定屏幕方向:
<property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY" android:value="true"/>
代码示例
四个完整流程,取自在我们应用中运行的 API。调用会立即返回;屏幕变化以 StatusEvent 的形式到达。完整文档 →
import com.klug.layout.KlugLayout; import flash.display.StageAspectRatio; import flash.events.StageOrientationEvent; var layout:KlugLayout = KlugLayout.getInstance(); // isSupported is false on desktop platforms: every call below is then a no-op, // so this code can stay in a cross-platform app stage.autoOrients = true; stage.setAspectRatio( StageAspectRatio.LANDSCAPE ); // first AIR's landscape ... layout.setSensorLandscape(); // ... then both sides on Android // (on iOS LANDSCAPE already allows both sides) // turn your own content, such as a camera picture, with the device updatePicture(); stage.addEventListener( StageOrientationEvent.ORIENTATION_CHANGE, function( e:StageOrientationEvent ):void { updatePicture(); }); function updatePicture():void { var rotation:int = layout.displayRotation; // 0, 90, 180 or 270; -1 = unknown if ( rotation != -1 ) rotateCameraPicture( rotation ); // your app function }
import flash.events.StatusEvent; layout.addEventListener( StatusEvent.STATUS, onLayout ); layout.watchFold( stage ); // pass the stage: iOS needs it for the iPhone Duo // events report changes only; read the class once for the start var startClass:String = layout.deviceClass; // "phone", "tablet", "cover" or "" (unknown) showLayoutFor( startClass != "" ? startClass : chooseFromStage() ); // your own default function onLayout( e:StatusEvent ):void { if ( e.code != KlugLayout.DEVICE_TYPE_CHANGED ) return; showLayoutFor( e.level ); // "tablet" or "phone"; "cover" on a Galaxy Z Flip (Android) } // fold-only behaviour, kept away from normal phones and tablets if ( layout.isFoldable ) enableFoldHints(); // your app function
function showLayoutFor( deviceClass:String ):void { // size of the current window in pixels, also after the device moved the app // to its other display; -1 = unknown, then fall back to the stage var w:int = layout.windowWidth > 0 ? layout.windowWidth : stage.stageWidth; var h:int = layout.windowHeight > 0 ? layout.windowHeight : stage.stageHeight; // top inset of the current display (Android: camera cutout, from Android 9) var top:int = Math.max( 0, layout.safeInsetTop ); // -1 = unknown if ( deviceClass == "tablet" ) buildTabletViews( w, h, top ); // your app functions else { buildPhoneViews( w, h, top ); if ( deviceClass == "cover" ) scaleToFit( w, h ); // phone layout, scaled down } }
// while a Fold is closed the lock is suspended on its own and comes back when it is opened. // release() is for screens where the app itself leaves landscape (Android; no-op on iOS). function openPortraitScreen():void { layout.release(); // remove the landscape lock stage.setAspectRatio( StageAspectRatio.PORTRAIT ); // AIR decides again } function openStationScreen():void { stage.setAspectRatio( StageAspectRatio.LANDSCAPE ); layout.setSensorLandscape(); // both landscape sides again }
API 概览
| KlugLayout.getInstance() | 返回唯一实例。 |
| isSupported | 在 Android 和 iOS 上为 True。在其他平台为 False;此时每次调用都不执行任何操作。 |
| setSensorLandscape() | 两侧横屏,随设备旋转。请在 stage.setAspectRatio(StageAspectRatio.LANDSCAPE) 之后调用。适用于 Android;在 iOS 上,LANDSCAPE 本身已允许两侧横屏。 |
| displayRotation | 当前显示旋转角度(单位为度:0、90、180、270),或 -1。 |
| watchFold( stage ) | 开始监视折叠和外屏变化,无需横屏锁定也可使用。在 iOS 上,iPhone Duo 需要传入 stage。 |
| isFoldable | 在折叠屏硬件上为 True:Android 折叠屏设备(Android 11 或更高版本)和 iPhone Duo。 |
| release() | 解除横屏锁定,例如当应用切换到手机布局时。适用于 Android;在 iOS 上不执行任何操作。 |
| deviceClass | 当前类别:"phone"、"tablet" 或 "cover";未知时为 ""。在 iOS 上,仅在 iPhone Duo 上调用 watchFold(stage) 之后可用。 |
| windowWidth / windowHeight | 当前窗口的大小(像素),会随设备切换到另一块屏幕;未知时为 -1。 |
| safeInsetTop | 当前屏幕的顶部边距(像素):Android 上为摄像头挖孔(Android 9 或更高版本),iOS 上为顶部安全区域;未知时为 -1。 |
| DEVICE_TYPE_CHANGED | 每次类别变化时发送的 StatusEvent code。 |
事件
| deviceTypeChanged | phone · tablet · cover(cover:Galaxy Z Flip 外屏,Android)。在 watchFold(stage) 之后发送,在 Android 上也会在 setSensorLandscape() 之后发送;仅在发生变化时发送。 |
购买 KlugLayout
一个 AIR 应用的年度许可,含 12 个月内的所有更新。付款后,您将通过电子邮件收到扩展、ActionScript 库和文档,通常在一个工作日内。许可与条款
要求
| AIR SDK | 51.x (HARMAN) |
| Android | Android 6.0 (API 23) 或更高版本;摄像头挖孔需 Android 9 (API 28) 起 |
| iOS | iOS 13 或更高版本;iPhone Duo 已使用 AIR 51.4.1 测试 |
| 权限 | 无 |