JetpackCompose的响应式布局实践 | 实验分享

前言

在前面几篇我们已经熟悉了MVI的业务,做了网络、数据库封装、做了导航探索甚至还做了一个KSP库,这些内容可以让我完成一些简单的需求开发了,但是我最近做前端的界面,突然想起了响应式,这就让我想起Android的响应式了,我们之前得靠SlidingPaneLayout或者Fragment来做这件事,这很繁琐不是吗?这就让我探索起了在Compose上的响应式。

谷歌的想法

我们可以看到Android自己对大屏设备的适配想法。

支持不同的屏幕尺寸 | Android 开发者 | Android Developers (google.cn)

我们暂时不谈及响应式后界面内容的变化,那么从图片我们可以看出,随着尺寸的变化,导航栏的位置,大小都发生了改变。

我在前端 做适配的时候也会做类似的事情,比如在桌面端 变为移动端 时,将抽屉导航换到顶部导航 ,将网格布局的Item项数减少 ,将横向的Flex布局改为垂直排布的,是的在这个适配过程中我不希望内容减少,我希望的是它可以去合适的地方展示。

Android自己也有类似的说法,不过它提及了我们可能需要在不同分辨率使用 不同的Model,这适合于下面这样的界面。

比如你的数据类型可能变多,比如在大屏幕页展示更多种类的内容,或者下面这种,可以直接展示文章内容的,你可能需要准备更多。

这就留给各位开发者继续探索了。

问题分析与解决

我们现在要在Jtepack Compose完成响应式,即不同界面展示不同的样式,我们将前端的办法进行迁移,我们在前端要通过媒体查询 来设置断点,以此完成响应式,那么我们在安卓是否也是可以呢?答案当然是可以的!

那么我就去想怎么样获取到窗口大小了 我在刚刚的官网当中就发现了这个内容,官方指出,Jetpack Compose中有窗口尺寸相关API androidx.compose.material3.windowsizeclass | Android Developers (google.cn)

好,calculateWindowSizeClass这个API相当简单,传入上下文即可获得窗口大小,这个方法返回了widthSizeClassheightSizeClass对应了前面官网提出的两个响应式方式(在我第一个粘贴的网址可见)。

但是有问题,这个东西在Compose用就要Context ,但是我不可能处处写LocalContext.current这很麻烦,我们需要想想办法。

解决方案

最终,我们落到的点就是,需要用calculateWindowSizeClass这个方法,但是我们需要为他进行封装。

kotlin 复制代码
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)  
@Composable  
fun getWidthSizeClass(context: Context = LocalContext.current): WindowWidthSizeClass {  
return calculateWindowSizeClass(context as ComponentActivity).widthSizeClass  
}

我们封装下这个方法,让它只能在Compose函数内使用,我们对他注解@Composable,这个注解本身是给一个Compose函数注解的,在这个注解的方法内可以写Compose的UI,那么就可以调用LocalContext.current,这样我们就可以给这个函数一个默认值,让它不需要传Context进去。

啊哈,现在我们使用时只需要调用getWidthSizeClass()即可获取到窗口宽度!

响应式实践

下面内容推荐拉下源码边看边读,文章中不会粘贴完整代码。

FoodChoice: 食选,解决生活中每天吃饭,吃什么,做什么,怎么做的问题.

底部导航转侧边导航

底部导航栏

首先让我们试试看,将底部导航变化到侧边导航上,这个问题在Compose里可以说相当好办了,我们所有的界面反馈都是被状态驱动的。

这是在Scaffold 组件上的底部导航,其实是否转换到侧边导航,底部导航要做的就是隐藏 或者显示 ,我们在上一篇文章导航实践里也说了这里动画的原因,就是切换界面时好看一些,让界面动起来,现在我们也可以把条件加在这里,当界面是Compact时并且需要底部导航那么就展示。

这样我们就实现底部导航只在手机设备 或者说小屏幕设备展示。

侧边导航栏

侧边导航栏比较特殊,它不在Scaffold 的插槽中,而是在其Content中,在图上我圈出的部分,就是侧边导航栏。

此外,这里的条件是 viewStates.isShowBottomBar并且getWidthSizeClass() > WindowWidthSizeClass.Compact,什么意思呢,就是底部导航展示,你也展示,因为需要底部导航也就意味着需要侧边导航 ,但是他们是二选一的,这就是后一个条件,窗口大小必须大于小窗口大小。

这样,比手机设备大的窗口就显示侧边导航,而手机设备就展示底部导航。

网格布局转换

上面食选的响应式还有个有意思的地方就在于不同设备下展示的Item数不同。

这就要提到我们的LazyVerticalGrid了,这个组件可以定义columns,也就是一行展示多少个,我们这里用when来完成,小屏幕一个,中形屏幕两个,大屏幕三个。

怎么样?在Jetpack Compose上做响应式是如此的简单,另外最近KMM也在走向完善和成熟,大家可以将这个想法迁移到跨平台上去做,我相信使用体验也是很不错的。

重构小课堂

从上一篇文章大家会发现文末会有一个小课堂了,后面每篇应该都会有,主要讲一讲之前设计的问题和新的想法,订阅和长期阅读这个专栏的读者可以看看,假如只是对本文感兴趣那下面的内容可能对你帮助不是很大。

我发现切换页面时,隐藏底部和顶部导航的动画会卡,进一步说就是首页切换下一个界面时会卡顿,这让我百思不得其解,我最开始认为是第二页数据加载多导致的,后来我减轻了一些数据发现的确有效果,但是不明显。

对于这个问题我就仔细看了下这个切换动画,我发现除了我自己写的AnimatedVisibility外,好像还有别的东西,切换时会有一个淡入的效果,但是我没写过这样的动画啊,这时我就将视线转到了NavHost,我们看看这个方法里有啥。

啊?真有自带的动画!!!

但这并不是我想要的,于是我们赶快给NavHost去除默认动画,干掉之后我发现界面切换卡顿不明显了,减少数据会更流畅。

因此我认为NavHost的动画,与我自己的动画重叠后发生了这个问题,给大家分享出来算是个坑。

网络请求改良

这是CookFoodInfoRepository,在data模块 ,是我们定义的一个数据源,还记得我们早期是怎么同步数据的吗?

我们用runCatching来捕获异常,这样虽然也很不错,但是我们下面拿值却要用runCatching的结果调用getOrNull,再通过空判断决定是否更新。

这样显然有一些麻烦,我们还有一个另一个手段,那就是flow,我们可以直接在流中捕获异常,而且我们不需要判空了,因此现在把请求结果作为流来处理,还记得吗Retrofit本身支持协程,配合Flow也很不错。

但是请求是个耗时操作,需要挂起,我们需要flowOn来转换下不同的线程池,这显然是一个重复工作,我们把他封装起来。

kotlin 复制代码
@OptIn(ExperimentalTypeInference::class)  
fun <T> makeRequestInFlow(@BuilderInference requestBlock: suspend FlowCollector<T>.() -> Unit): Flow<T> {  
// 切换流到IO线程,特别的,由于协程上下文传递,ViewModel处理意图本身就是在IO里,可以不可以这么做,观察后是否  
return flow(block = requestBlock).flowOn(Dispatchers.IO)  
}

我在data模块新建了Request.kt的文件,我们对Flow进行一次简单的封装。

最后我们的同步函数内就变为了这样,用emit发送请求结果给下游消费,其实感觉也不差,flow能做的也很强。

当然这种做法不一定对,我个人现在喜欢用Flow来处理,后面有其他改动再分享给大家。

文末

非常感谢各位看到这里,如果对本文章的内容感兴趣,可以Star项目看看。

FoodChoice: 食选,解决生活中每天吃饭,吃什么,做什么,怎么做的问题。

文章内容如果有问题或者你有更好的解决方案,欢迎在评论区告诉我。

相关推荐
alexhilton20 小时前
端侧RAG实战指南
android·kotlin·android jetpack
Kapaseker1 天前
2026年,我们还该不该学编程?
android·kotlin
Kapaseker2 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
BoomHe3 天前
Now in Android 架构模式全面分析
android·android jetpack
Kapaseker3 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish4 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker4 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker5 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
黄林晴5 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
A0微声z7 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin