从直板到折叠屏:腾讯Kuikly如何适配 iPhone Duo?

文章目录

Kuikly 是腾讯开源的高性能跨端框架,基于 Kotlin Multiplatform 技术,覆盖 Android、iOS、HarmonyOS、H5、微信小程序、Mac 六大平台,支撑业务日活用户超5亿。折叠屏产品发展迅速,苹果新推出的 iPhone Duo 带来了不同以往的布局和交互方式,对于跨端页面来说需要处理更多平台差异,带来了更多适配工作。Kuikly 采用原生 UI 渲染,可以很好适配系统的新特性,本文结合具体示例,介绍如何借助 Kuikly 复用跨端代码,做好折叠屏的适配工作。

背景#

过往折叠屏适配的核心,主要围绕处理开合、旋转与分屏场景引发的视图尺寸变更做处理。对于跨端页面,基于响应式布局并结合四边安全区域适配,基本可以覆盖绝大多数的场景。而 iPhone Duo 引入全新的设备特征:屏幕存在多处需要避让的局部区域,同时导航栏、工具栏由系统跟随设备形态动态调整。这一变化对跨端页面适配构成新的约束:若维持跨端层自绘导航栏、工具栏的方案,不仅避让区域计算逻辑复杂度高,且无法对齐原生的交互行为。为了实现最佳效果,似乎只能由各平台差异化实现,牺牲跨端方案的效率优势。

针对这一问题,Kuikly 依托原生 UI 渲染的优势,在原有响应式布局和安全区域能力基础上,进一步对齐系统能力,提供保留区域信息与系统导航栏工具栏的原生桥接,侧边栏位的布局与行为交由系统原生组件处理,而页面结构、按钮配置与交互逻辑仍在跨端代码里统一维护,兼顾了原生体验与跨端效率。


折叠屏基本特点#

屏幕更大。 展开后的内屏接近一台小平板,一屏能显示的内容比普通手机多不少。很多原来要翻几页、点几次才能看到的信息,现在可以放在同一屏里。

屏幕尺寸会变。用户随时可能把手机合上或打开,App 会在外屏和内屏之间切换,拿到的宽高也跟着变。打开后再分屏或旋转,可用空间还会继续变化。按开合程度,划分三种形态:

  • 折叠态:合上后使用较小的外屏,适合单栏聚焦。

  • 展开态:完全展开,更大的屏幕,适合充分展示内容。

  • 悬停态:半折状态,适合分开呈现内容和操作。

折叠屏的特点给页面设计和开发带来了新的要求:布局要随屏幕尺寸调整,也要用好更大的空间,让内容展示和操作更高效。


iPhone Duo 新变化#

图片

图片1 iPhone Duo的各形态

iPhone Duo 区别于以往的折叠屏适配,还要关注额外的变化:

  • Vertical Controls(垂直控制栏)

iPhone Duo 最容易被用户感知到的变化,是系统控制区的位置。过去所有折叠屏设备在系统控制区位置都是沿用直板手机形态,顶部导航,底部 Tab Bar,中间内容。但苹果在折叠屏上把导航栏、工具栏和 Tab 控制放到屏幕侧边,为内容留出更多纵向空间。而且在分屏情况下,状态栏会分别出现在左右两侧,系统响应式容器会有相对应自适应能力。


图片

图片2iPhone Duo的垂直控制栏

  • Reserved Regions(保留区域)#

过去布局主要通过安全区避开系统 UI。在iPhone Duo上,还要关注屏幕内部的摄像头和折叠位置。内外屏摄像头可能占用局部空间,设备部分展开时,中间的折叠区域也会影响内容排列。苹果通过预留区域描述这些位置及其生效状态,App 可以据此避开遮挡,或把内容安排到折叠区域两侧。

图片

图片3iPhone Duo的保留区域

苹果官方给出的设备指南是:「可调整大小是你的 App 在 iPhone Duo 上的关键功能。如果你的 App 已在 iPad 和 Mac 上运行,或者你已在 iPhone Mirroring 中准备好让 App 调整大小,那么你已经为支持 iPhone Duo 打下了坚实基础。如果没有,请采用标准布局控件和容器,并使用大小类和场景几何来改进 App 的大小调整功能。然后,处理保留区域(例如折叠区域)来调整 App 大小并处理折叠布局。评估排列视图是否可以协助 App 布局来处理折叠区域。

在某些姿态下,iPhone Duo 会将导览栏、工具栏和标签栏一起垂直显示在显示屏侧边。请检查并整理 App 中的栏,以确保常用控制始终可见,且在空间有限时,较少使用的控制会出现在溢出菜单中。」

跨端适配痛点#

对跨端页面来说,难点在于复用同一套代码的同时,还要适应各端不同的系统行为。布局可以随尺寸调整,但安全区、折叠区域、系统栏的位置和行为需要分别接入;单栏切换到多栏时,还要保持页面跳转、返回和操作状态的一致。

这些差异都交给使用方处理,页面中就会增加大量平台判断,未来还可能出现更多设备形态,逐端、逐页适配的成本会持续增加。因此,跨端框架需要把通用布局和交互留给业务复用,将设备差异封装成可用的能力,既减轻适配负担,也让页面能够利用各端的设备特性,这也是 Kuikly 的适配思路。

总体可以把适配划分成两部分:通用的尺寸和布局变化,以及预留区域、系统侧栏等设备特有的能力。Kuikly 采用原生 UI 渲染,前者可以沿用已有的响应式布局,后者通过预留区域、系统导航栏和工具栏等实现原生能力的接入,业务页面仍然使用 Kuikly 配置内容和交互。下面结合具体示例,看看现有页面如何适配折叠屏,并利用 Duo 的设备特性。

Kuikly 适配指南#

1. 布局随可用空间变化#

Kuikly 从一开始就支持响应式刷新和 Flex 布局。折叠、展开或分屏带来的尺寸变化,可以沿用这套机制处理。

在 attr 中使用 pageData.pageViewWidth 和 pageData.pageViewHeight 计算尺寸,宿主上报尺寸变化后,相关布局属性会自动更新。结合 flex(1f) 分配剩余空间,可以减少固定尺寸和手动高度计算。

展开后不必一味拉伸。阅读、表单等内容可限制最大宽度并居中,多出的空间用于留白、增加列表列数或展示详情。

// KuiklyDSLView {    attr {        flex(1f)    }    View {        attr {              width(ctx.pageData.pageViewWidth * 0.8f)            maxWidth(600f)        }         }} // Kuikly ComposeDSLval pageWidth = LocalConfiguration.current.pageViewWidthBox(Modifier.width(minOf(pageWidth * 0.8f, 600f).dp)) {     /* ... */ } 

图片

图片4折叠屏展开页面布局变化样例

2. 安全区域和避让#

安全区域仍然是多设备适配的关键。不同设备的刘海、系统栏和手势区域不同,文字和操作控件需要按当前安全区域避让。旋转、分屏或开合时,安全距离也可能变化,布局应随之更新,而不是预留固定的顶部、底部间距。

Kuikly 通过 pageData.safeAreaInsets 提供上、下、左、右的安全距离。把安全区域绑定到内容边距,就能随安全区变化调整布局:

// KuiklyDSLView {    attr {        flex(1f)        paddingTop(ctx.pageData.safeAreaInsets.top)        paddingLeft(ctx.pageData.safeAreaInsets.left)        paddingBottom(ctx.pageData.safeAreaInsets.bottom)        paddingRight(ctx.pageData.safeAreaInsets.right)    }    // 需要安全区保护的前景内容}   // Kuikly ComposeDSLval configuration = LocalConfiguration.currentconfiguration.pageViewWidth                                // 订阅尺寸变化val safe = configuration.pageData.safeAreaInsets Box(Modifier.fillMaxSize().background(bgColor)) {          // 背景铺满    Column(        Modifier.fillMaxSize().absolutePadding(            left = safe.left.dp, top = safe.top.dp,            right = safe.right.dp, bottom = safe.bottom.dp,        )    ) {        // 需要安全区保护的前景内容    }}

过去App做适配页面主要用 Top 和 Bottom 处理避让导航栏和底部状态栏。而Duo 的导航栏和工具栏可能移到侧边,因此也要关注 left、right,并结合系统栏的实际占位处理左右空间,不能只留上下边距。

在iPhoneDuo上,除了安全区域外,还要关注预留区域,处理摄像头、状态栏遮挡和折叠带来的布局限制。

  • 遮挡区域:避免文字、按钮等关键内容与它重叠。

  • 分割区域:需要分区的页面,可以把内容安排到折叠区域两侧;

Kuikly 通过 pageData.reservedRegions 提供区域的位置、大小、类型和当前是否生效,区域使用 x、y、width、height 描述,坐标相对 Kuikly 根视图,计算规避后的坐标。

如果希望背景、图片等内容延伸到顶部,可以不对整页统一添加 paddingTop,通过 pageData.reservedRegions 对当前生效的遮挡区域做局部避让。

// KuiklyDSLView {    attr {        val pageWidth = ctx.pageData.pageViewWidth        val occlusion = ctx.pageData.reservedRegions.firstOrNull {            it.active && it.kind == ReservedRegionKind.OCCLUSION && it.x >= pageWidth / 2f        }        // 伪代码:有右侧遮挡时,避让右边位置 right         val paddingRight = if (occlusion != null) pageWidth - occlusion.x + 12f else 12f        absolutePosition(right = paddingRight)    }} // Kuikly ComposeDSLval configuration = LocalConfiguration.currentval pageWidth = configuration.pageViewWidthval occlusion = configuration.pageData.reservedRegions.firstOrNull {    it.active && it.kind == ReservedRegionKind.OCCLUSION && it.x >= pageWidth / 2f}val badgeRight = if (occlusion != null) pageWidth - occlusion.x + 12f else 12f Box(Modifier.fillMaxSize().background(bgColor)) {           // 背景延伸到顶部    Text(        "角标",        Modifier.align(Alignment.TopEnd)            .absolutePadding(top = 12.dp, right = badgeRight.dp),    )}

图片

图片5避让iPhone Duo的遮挡区域

3. 空间变大后,怎么展示内容#

展开后不一定要把所有元素放大。常见做法有三种:拉伸、调整布局和多栏展示,具体选哪种取决于页面内容。

拉伸:让适合扩展的内容占用更多空间

文本、画布等内容,可以扩展显示区域。普通按钮、图标和字号通常不用跟着同比例放大。

阅读页则要限制正文宽度,避免展开后每行文字过长。多出来的空间可以留白,不必强行填满。

调整布局:重排页面,或者增加列数

如:图文卡片可以由上下排改为左右排,信息流可以增加列数。Kuikly 的
WaterfallList(KuiklyDSL)、LazyVerticalStaggeredGrid(ComposeDSL)支持设置列表宽度和列数:

// KuiklyDSLWaterfallList {    attr {        flex(1f)        listWidth(ctx.pagerData.pageViewWidth)        columnCount(if (ctx.pagerData.pageViewWidth >= 600f) 4 else 2)        itemSpacing(10f)        lineSpacing(10f)    }    // 普通卡片不写死宽度,由列表按列宽分配} // Kuikly ComposeDSLval pageWidth = LocalConfiguration.current.pageViewWidthLazyVerticalStaggeredGrid(    columns = StaggeredGridCells.Fixed(if (pageWidth >= 600f) 4 else 2),    modifier = Modifier.fillMaxSize(),    verticalItemSpacing = 10.dp,                          // 对应 lineSpacing    horizontalArrangement = Arrangement.spacedBy(10.dp),  // 对应 itemSpacing) {    items(feed, key = { it.id }) { item ->        FeedCard(item, Modifier.fillMaxWidth())   // 卡片不写死宽度,由网格按列宽分配    }}

图片

图片6瀑布流列数随屏幕大小展开

双栏:同时展示有关联的内容

列表与详情、目录与正文等关联内容,可以在宽屏下并排展示,窄屏下仍按单栏逐级访问。外屏和内屏应保持一致的内容和操作逻辑,展开后只是把原来分步查看的信息同时呈现,让用户感觉仍在使用同一个 App。

详细可以参考:「iPhone Duo来了,腾讯 Kuikly 助 App 从容适配 | 跨平台框架—tds-Kuikly」

图片
图片

图片7双栏布局在折叠屏上的表现

4. 导航栏和工具栏#

在 iPhone Duo 上,导航栏和工具栏可以随设备姿态和容器布局调整到侧边,为内容留出更多纵向空间。页面除了适应尺寸变化,也要跟着系统栏的位置调整内容区域。

Kuikly DSL:通过 UINavigationBar 和 UIToolbar 接入宿主的原生系统栏。业务侧配置标题、图标、按钮和点击事件,系统根据容器和姿态处理栏的位置与排列。例如,添加一个返回按钮:

UINavigationBar {    attr {        tintColor(Color.BLACK)    }    items {        navbarItems {            navbarItem {                attr {                    placement(UIBarButtonItemPlacement.Leading)                    symbol("chevron.backward")                    title("返回")                    index(0)                    axis(UIBarAxis.VerticalPreferred)                }                event {                    click {                        ctx.acquireModule<RouterModule>(RouterModule.MODULE_NAME)                            .closePage()                    }                }            }        }    }}

Kuikly ComposeDSL:可以用 MakeKuiklyComposeNode 挂一个 DSL 容器,在容器里声明它:

@Composablefun SystemNavigationBar(onBack: () -> Unit) {    val currentOnBack by rememberUpdatedState(onBack)    MakeKuiklyComposeNode<DivView>(        factory = { DivView() },        modifier = Modifier.size(0.dp),      // 系统栏只配置宿主 navigationItem,不参与布局        viewInit = {            UINavigationBar {                attr { tintColor(KuiklyColor.BLACK) }                items {                    navbarItems {                        navbarItem {                            attr {                                placement(UIBarButtonItemPlacement.Leading)                                symbol("chevron.backward")                                title("返回")                                index(0)                                axis(UIBarAxis.VerticalPreferred)                            }                            event { click { currentOnBack() } }                        }                    }                }            }        },    )}

封装通用导航栏,统一各端的导航和避让

整体来看,跨端差异主要在系统导航栏、工具栏的使用方式和空间占位。为方便适配,业务可以封装一个通用导航栏,统一导航、返回按钮和工具栏配置,把各端的导航与避让集中由组件处理。

iPhone 端接入系统导航栏和工具栏。在 Duo 的侧置布局下,根据栏的实际位置、安全区和生效的预留区域调整导航与正文空间;其他端或未接入系统栏的页面,沿用常规导航布局,按 safeAreaInsets 处理顶部、底部及所在边的安全距离。

正文使用容器分配的剩余空间,不再重复添加安全区边距。这样,导航样式和避让规则可以集中维护,适配新设备时也不必逐页修改。

这样既能利用 Duo 的系统侧栏等设备特性,也能让业务页面保持统一的配置方式。后续适配新设备时,主要在组件内部补充对应实现和避让规则,不必逐页修改。

图片

图片8自定义导航样例在 非iPhone / iPhone / iPhone Duo 上的表现

总结#

新设备形态的出现,也带来了更多布局调整和平台适配工作。Kuikly 致力于通过跨端代码复用和原生能力封装,减轻业务的重复适配负担,让页面在保持一致的内容和操作逻辑同时,也能利用各端的设备特性。未来面对更多设备形态,我们也希望把通用适配能力沉淀到框架中,让业务更专注于内容和体验。

参考引用#

关于 Kuikly#

  • 👉 GitHub 仓库
  • 📚 官方文档

Kuikly 框架属于腾讯端服务联盟(tds.qq.com)的重要成员,欢迎关注及了解更多信息:

  • 腾讯端服务官网:TDS 腾讯端服务
  • Kuikly 官网:跨平台框架

原文来自 腾讯 技术团队,版权归原作者。

正文采集于 ,内容更新以原文为准。