Android compose屏幕适配终极解决方案

缘起

随着华为推出三折叠手机之后,各大厂商也是层出不穷相继推出折叠屏,以及大屏设备也慢慢贯穿我们的工作和生活,作为开发者的我们也要紧跟时代发展

屏幕适配也逐渐重新回归到我们的视野当中去

并且从 Android16 之后强制设置屏幕横向竖向都不再提供支持,适配大屏设备也是每个开发者都要考虑的事情

如何让一个软件程序能够同时运行在不同的设备上并且根据屏幕的大小尺寸有不同的显示效果呢?

那如何优雅的实现大屏适配呢?官方也给我们提供了支持 window size classes

window size classes

大屏适配的本质就是要根据不同的屏幕尺寸来显示不同的界面布局。

Compose中我们可以借助WindowSizeClass来获取当前设备的屏幕尺寸类型,然后针对不同的设备进行显示不同的效果从而达到不同设备显示不同的效果

通过图片我们可以很清楚的进行分辨,当宽度小于600dp时就属于Compact(紧凑)类型的设备,位于600dp和800dp之间就属于Medium(中等)类型设备,在840dp及以上就属于Expanded(扩展)类型的设备,高度就同理可得

根据宽度高度的类型我们就可以实时获取当前设备是那种设备,就可以针对性开始我们的屏幕适配

来看一组代码

kotlin 复制代码
enum class UIValues {
    COMPACT,//紧凑
    MEDIUM,//中等
    EXPANDED//扩展
}

private fun processWindowSizeClassWithOnly(sizeClass: WindowSizeClass): UIValues {
    return when {
        sizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND) -> {
            UIValues.EXPANDED
        }

        sizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) -> {
            UIValues.MEDIUM
        }

        else -> {
            UIValues.COMPACT
        }
    }
}

private fun processWindowSizeClassHeightOnly(sizeClass: WindowSizeClass): UIValues {
    return when {
        sizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_EXPANDED_LOWER_BOUND) -> {
            UIValues.EXPANDED
        }

        sizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND) -> {
            UIValues.MEDIUM
        }

        else -> {
            UIValues.COMPACT
        }
    }

}


enum class DeviceConfiguration {
    MOBILE_PORTRAIT, //手机竖屏       PORTRAIT竖屏 LANDSCAPE 横屏
    MOBILE_LANDSCAPE,//手机横屏
    TABLE_PORTRAIT,//平板竖屏
    TABLE_LANDSCAPE,//平板横屏
    DESKTOP;


    companion object {
        fun fromWindowSizeClass(sizeClass: WindowSizeClass): DeviceConfiguration {
            val withType = processWindowSizeClassWithOnly(sizeClass)
            val heightType = processWindowSizeClassHeightOnly(sizeClass)

            return when {
                withType == UIValues.COMPACT && heightType == UIValues.MEDIUM -> MOBILE_PORTRAIT
                withType == UIValues.COMPACT && heightType == UIValues.EXPANDED -> MOBILE_PORTRAIT
                withType == UIValues.EXPANDED && heightType == UIValues.COMPACT -> MOBILE_LANDSCAPE
                withType == UIValues.MEDIUM && heightType == UIValues.EXPANDED -> TABLE_PORTRAIT
                withType == UIValues.EXPANDED && heightType == UIValues.MEDIUM -> TABLE_LANDSCAPE
                else -> {
                    DESKTOP
                }
            }
        }

        //宽度
        fun windowSizeClassWithWidth(sizeClass: WindowSizeClass): UIValues{
            return processWindowSizeClassWithOnly(sizeClass)
        }

        fun windowSizeClassWithHeight(sizeClass: WindowSizeClass):UIValues{
            return processWindowSizeClassHeightOnly(sizeClass)
        }
    }
}

通过调用DeviceConfiguration.fromWindowSizeCkass()传入windowSizeClass参数,得到当前设备的宽度类型和高度类型,根据宽度/高度的类型从而区分当前是什么设备,是手机横屏?手机竖屏? 平板横屏?平板竖屏等。

我们可以根据设备类型动态设置不同的底部导航栏。

上文中有提到折叠屏,屏幕界面显示也要考虑到折叠屏是单屏啊,还是正在折叠,折叠完毕多屏显示的状态,根据不同的状态来显示不同的界面内容使我们的软件能够有更加人性化的体验

kotlin 复制代码
    val foldingDevicePosture = when {
        //折叠屏状态 半开
        isBookPosture(foldFeature) -> DevicePosture.BookPosture(foldFeature.bounds)

        //折叠屏状态 分离平坦
        isSeparating(foldFeature) -> DevicePosture.Separating(
            foldFeature.bounds,
            foldFeature.orientation
        )

        else -> DevicePosture.NormalPosture
    }

    //结合窗口宽度尺寸和折叠状态决定内容布局
    val widthWindowSize = DeviceConfiguration.windowSizeClassWithWidth(windowSize)
    val heightWindowSize = DeviceConfiguration.windowSizeClassWithHeight(windowSize)
    val contentType = when (widthWindowSize) {
        //紧凑
        UIValues.COMPACT -> ReplyContentType.SINGLE_PAGE
        //中等屏
        UIValues.MEDIUM -> if (foldingDevicePosture != DevicePosture.NormalPosture) {
            ReplyContentType.DUAL_PAGE
        } else {
            ReplyContentType.SINGLE_PAGE
        }
        //扩展屏
        UIValues.EXPANDED -> ReplyContentType.DUAL_PAGE
    }

根据注释也可以得到信息,当我们位于半开(正在打开)或者分离平坦的时候,说明我们正在打开是同多屏幕这时候不再是单页显示,界面中我们就可以从单页的ui调整为多屏的ui进行显示。

demo演示


这里分别显示的是 手机显示/折叠屏显示/平板显示的效果,我们可以看到在不同尺寸的屏幕下所展示的效果是不同的

以及我在开发过程中 试图预览的情况下的一些效果 分别对应的是400 * 900 700 * 500 500 * 700 1100 * 600 600 * 1100的效果

compose和普通xml声明式的优点

1.实时预览

kotlin 复制代码
@Preview(showBackground = true, widthDp = 600, heightDp = 1100)
@Composable
fun ReplyAppPreviewDesktopPortrait() {
    WindowSizeDemoTheme {
        WindowSizeApp(
            windowSize = WindowSizeClass(600f,1100f),
            displayFeatures = emptyList(),
        )
    }
}

看此段代码 通过@Preview注解的方法 可以直接在编写代码的同时就预览到效果 不同像之前xml编写的时候还需要运行才可以看到真是的效果,并且compose支持 动态视图 在切换模式后就可以直接预览和操作动态视图的界面 更加方便我们查看一些动态数据。

从而避免像普通xml布局写完代码优化一点点调试界面需要一次次运行的尴尬

2.组件复用

在编写代码的初期 我们可以将每一块显示的方法进行封装,这样在其他界面有相同的界面后者类似的界面时,我们可以非常方便的调用封装好的方法体从而减少二次重复操作的问题

3.状态驱动

Compose 内置状态管理机制,通过remember、mutableStateOf、StateFlow等 API,让状态与 UI 强绑定,UI会自动响应状态变化,减少手动更新视图的代码,可以让开发者更多的精力放在业务逻辑上。

希望次篇文章对于多屏幕适配方面对你有用

参考文章 guolin 写给初学者的Jetpack Compose教程,大屏设备适配

相关推荐
2501_916007472 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun4 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户2018792831678 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子8 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82278 小时前
安卓接入Max广告源
android
齊家治國平天下8 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO8 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel8 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢8 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱