Compose Multiplatform 正式官宣,与 Flutter 迟早必有一战?
7月底 Compose for Android 1.0 刚刚发布,紧接着 8月4日 JetBrains 就宣布了 Compose Multiplatform 的最新进展,目前已进入 alpha 阶段。
Compose 作为一个声明式UI框架,除了渲染部分需借助平台能力以外,其他大部分特性可以做到平台无关。尤其是 Kotlin 这样一门跨平台语言,早就为日后的 UI 跨平台奠定了基础。
Compose Multiplatform 将整合现有的三个 Compose 项目:Android、Desktop、Web,未来可以像 Kotlin Multiplatform Project 一样,在一个工程下开发跨端应用,统一的声明式范式让代码在最大程度上实现复用,真正做到write once,run anywhere 。如今进入 alpah 阶段标志着其 API 也日渐成熟,相信不久的未来正式版就会与大家见面。
我们通过官方 todoapp 的例子,提前体验一下 Compose Multiplatform 的魅力
https://github.com/JetBrains/compose-jb/tree/master/examples/todoapp
todoapp 工程
- todoapp
- common:平台无关代码
- compose-ui :UI层可复用代码(兼容 Android 与 Desktop)
- main:逻辑层可复用代码(首页)
- edit:逻辑层可复用代码(编辑)
- root:逻辑层入口、导航管理(
main
与eidt
间页面跳转) - utils:工具类
- database:数据库
- android:平台相关代码,
Activity
等 - desktop:平台相关代码,
application
等 - web:平台相关,
index.html
等 - ios:compose-ui 尚不支持 ios,但通过KMM配合SwiftUI可以实现iOS端代码
- common:平台无关代码
项目基于 Model-View-Intent(aka MVI) 打造,Model层、ViewModel层 代码几乎可以 100% 复用,View层在 desktop 和 Android 也可实现大部分复用,web 有一定特殊性需要单独适配。
除了 Jetpack Compose 以外,项目中使用了多个基于 KM 的三方框架,保证了上层的开发范式在多平台上的一致体验:
KM三方库 | 说明 |
---|---|
Decompose | 数据通信(BLoC) |
MVIKotlin | 跨平台MVI |
Rektive | 异步响应式库 |
SQLDelight | 数据库 |
todoapp 代码
平台入口代码
对比一下 Android端 与 Desktop端 的入口代码
//todoapp/android/src/main/java/example/todo/android/MainActivity.kt
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val root = todoRoot(defaultComponentContext())setContent {ComposeAppTheme {Surface(color = MaterialTheme.colors.background) {TodoRootContent(root)}}}}private fun todoRoot(componentContext: ComponentContext): TodoRoot =TodoRootComponent(componentContext = componentContext,storeFactory = LoggingStoreFactory(TimeTravelStoreFactory(DefaultStoreFactory())),database = DefaultTodoSharedDatabase(TodoDatabaseDriver(context = this)))
}
//todoapp/desktop/src/jvmMain/kotlin/example/todo/desktop/Main.ktfun main() {overrideSchedulers(main = Dispatchers.Main::asScheduler)val lifecycle = LifecycleRegistry()val root = todoRoot(DefaultComponentContext(lifecycle = lifecycle))application {val windowState = rememberWindowState()LifecycleController(lifecycle, windowState)Window(onCloseRequest = ::exitApplication,state = windowState,title = "Todo") {Surface(modifier = Modifier.fillMaxSize()) {MaterialTheme {DesktopTheme {TodoRootContent(root)}}}}}
}private fun todoRoot(componentContext: ComponentContext): TodoRoot =TodoRootComponent(componentContext = componentContext,storeFactory = DefaultStoreFactory(),database = DefaultTodoSharedDatabase(TodoDatabaseDriver()))
- TodoRootContent:根Composable,View层入口
- TodoRootComponent:根状态管理器,ViewModel层入口
- DefaultStoreFactory:创建 Store,管理状态
- DefaultTodoShareDatabase:M层,数据管理
TodoRootContent
和 TodoRootComponent
分别是 View 层和 ViewModel 层的入口,TodoRootComponent 管理着全局状态,即页面导航状态。
可以看到,Android 与 Desktop 在 View 、 VM 、M等各层都进行了大面积复用,
VM层代码
MVI 中虽然没有 ViewModel,但是有等价概念,从习惯出发我们暂且称之为 VM 层。 VM层其实就是状态的管理场所,我们以首页的 mian
为例
//todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainComponent.ktclass TodoMainComponent(componentContext: ComponentContext,storeFactory: StoreFactory,database: TodoSharedDatabase,private val output: Consumer<Output>
) : TodoMain, ComponentContext by componentContext {private val store =instanceKeeper.getStore {TodoMainStoreProvider(storeFactory = storeFactory,database = TodoMainStoreDatabase(database = database)).provide()}override val models: Value<Model> = store.asValue().map(stateToModel)override fun onItemClicked(id: Long) {output(Output.Selected(id = id))}override fun onItemDoneChanged(id: Long, isDone: Boolean) {store.accept(Intent.SetItemDone(id = id, isDone = isDone))}override fun onItemDeleteClicked(id: Long) {store.accept(Intent.DeleteItem(id = id))}override fun onInputTextChanged(text: String) {store.accept(Intent.SetText(text = text))}override fun onAddItemClicked() {store.accept(Intent.AddItem)}
}
了解 MVI 的朋友对上面的代码应该非常熟悉,store
管理状态并通过 models
对UI暴露,所有数据流单向流动。 Value<Model>
是 Decompose
库中的类型,可以理解为跨平台的 LiveData
View层代码
@Composable
fun TodoRootContent(component: TodoRoot) {Children(routerState = component.routerState, animation = crossfadeScale()) {when (val child = it.instance) {is Child.Main -> TodoMainContent(child.component)is Child.Edit -> TodoEditContent(child.component)}}
}
TodoRootContent
内部很简单,就是根据导航切换不同的页面。
具体看一下TodoMainContent
@Composable
fun TodoMainContent(component: TodoMain) {val model by component.models.subscribeAsState() Column {TopAppBar(title = { Text(text = "Todo List") })Box(Modifier.weight(1F)) {TodoList(items = model.items,onItemClicked = component::onItemClicked,onDoneChanged = component::onItemDoneChanged,onDeleteItemClicked = component::onItemDeleteClicked)}TodoInput(text = model.text,onAddClicked = component::onAddItemClicked,onTextChanged = component::onInputTextChanged)}
}
subscribeAsState()
在 Composable 中订阅了 Models 的状态,从而驱动 UI 刷新。Column
、Box
等 Composalbe 在 Descktop 和 Android 端会分别进行平台渲染。
web端代码
最后看一下web端实现。
Compose For Web 的 Composalbe 大多基于 DOM 设计,无法像 Android 和 Desktop 的 Composable 那样复用,但是 VM 和 M 层仍然可以大量复用:
//todoapp/web/src/jsMain/kotlin/example/todo/web/App.kt
fun main() {val rootElement = document.getElementById("root") as HTMLElementval lifecycle = LifecycleRegistry()val root =TodoRootComponent(componentContext = DefaultComponentContext(lifecycle = lifecycle),storeFactory = DefaultStoreFactory(),database = DefaultTodoSharedDatabase(todoDatabaseDriver()))lifecycle.resume()renderComposable(root = rootElement) {Style(Styles)TodoRootUi(root)}
}
将 TodoRootComponent
传给 UI, 协助进行导航管理
@Composable
fun TodoRootUi(component: TodoRoot) {Card(attrs = {style {position(Position.Absolute)height(700.px)property("max-width", 640.px)top(0.px)bottom(0.px)left(0.px)right(0.px)property("margin", auto)}}) {val routerState by component.routerState.subscribeAsState()Crossfade(target = routerState.activeChild.instance,attrs = {style {width(100.percent)height(100.percent)position(Position.Relative)left(0.px)top(0.px)}}) { child ->when (child) {is TodoRoot.Child.Main -> TodoMainUi(child.component)is TodoRoot.Child.Edit -> TodoEditUi(child.component)}}}
}
TodoMainUi
的实现如下:
@Composable
fun TodoMainUi(component: TodoMain) {val model by component.models.subscribeAsState()Div(attrs = {style {width(100.percent)height(100.percent)display(DisplayStyle.Flex)flexFlow(FlexDirection.Column, FlexWrap.Nowrap)}}) {Div(attrs = {style {width(100.percent)property("flex", "0 1 auto")}}) {NavBar(title = "Todo List")}Ul(attrs = {style {width(100.percent)margin(0.px)property("flex", "1 1 auto")property("overflow-y", "scroll")}}) {model.items.forEach { item ->Item(item = item,onClicked = component::onItemClicked,onDoneChanged = component::onItemDoneChanged,onDeleteClicked = component::onItemDeleteClicked)}}Div(attrs = {style {width(100.percent)property("flex", "0 1 auto")}}) {TodoInput(text = model.text,onTextChanged = component::onInputTextChanged,onAddClicked = component::onAddItemClicked)}}
}
最后
在 Jetpack Compose Runtime 与 NodeTree 管理
一文中,我曾介绍过 Compose 跨平台的技术基础,如今配合各种 KM 三方库,使得开发生态更加完整。 Compose Multiplatform 全程基于 Kotlin 打造,上下游同构,相对于 Flutter 和 RN 更具优势,未来可期。
Compose Multiplatform 正式官宣,与 Flutter 迟早必有一战?相关推荐
- Compose Multiplatform 正式官宣,与 Flutter 必有一战?
作者 | fundroid 来源 | AndroidPub 7月底 Compose for Android 1.0 刚刚发布,紧接着 8月4日 JetBrains 就宣布了 Compose Multi ...
- Windows 11 正式官宣:全新 UI、支持安卓 App、应用商店 0 抽成!
整理 | 郑丽媛 出品 | CSDN(ID:CSDNnews) "下一代 Windows 即将到来,这是十年来最重要的更新之一." 5 月微软 Build 开发者大会上微软 CEO ...
- OPPO Find X3通过网站推广正式官宣,打破常规探索高端旗舰新突破!
早在三月初OPPO就已经开始针对OPPO Find X3新系列产品做出预热,从选择姜文作为新系列高端旗舰产品的代言人就可以看出,此次OPPO有着全新的考量.借用姜文经典电影中的台词"什么TM ...
- miui12怎么自定义开机动画_MIUI12正式官宣,5天后发布!网友:发布是发布,12月才能更新?...
小编在前段时间看到知名数码博主表示小米将会在月底正式发布小米的最新系统MIUI12,而且将会不少重磅的新功能,这不今天MIUI官方正式官宣,MIUI12将和小米的5G新品在5天后,也即时4月27日正式 ...
- 华为鸿蒙os系统转正,华为鸿蒙OS系统正式官宣,转正工作提上日程,明年多款终端将使用...
华为鸿蒙OS系统相信很多小伙伴都不陌生,作为国内现如今顶尖的科技企业.华为这些年的发展也是十分迅速的,而再快速的发展过程中.更多的用户对于华为的新款系统也充满了好奇,要知道一款属于国人自己的国产系统. ...
- 互联网晚报 | 12月27日 星期一 | 蚂蚁消费金融增资220亿;小米MIUI13正式官宣;《蜘蛛侠3》登顶全球年度票房冠军...
今日看点 ✦ 华为:今年鸿蒙智联认证设备发货超1亿台,新增合作伙伴超1900家 ✦ 小米MIUI13正式官宣:将于12月28日与小米12系列同台亮相 ✦ 蚂蚁消费金融增资220亿元,新引入中国信达.舜 ...
- android 呼吸灯权限_小米新机搭载炫彩呼吸灯酷到爆;三星顶级旗舰Note 10正式官宣...
各位,早上好!即日起,魅族Meizu Pay京津冀互联互通卡免费开卡,你期待吗? 1,苦等436天!国产超优秀ROM终于换代 7月2日,有网友曝光了魅族Flyme 8的海报图片,图片显示:你所期待的我 ...
- 疑似荣耀30S将于今日正式官宣:首发搭载全新麒麟820芯片
[TechWeb]虽然疫情对各行各业都产生了非常大的影响,但似乎并没有太多影响到手机圈新机的推出,从目前亮相的旗舰新机来看,今年的杰作丝毫不逊于往年.随着第一季度即将结束,消费者的目光也逐渐集中在了仅 ...
- 定了!华为“最强5G旗舰手机”正式官宣:3月26日见
在昨晚举行的华为终端产品与战略线上发布会上,华为一口气推出了包括华为MateXs.华为MatePad Pro 5G.华为路由AX3系列.新款华为MateBook X Pro等在内的多款新品,但值得注意 ...
最新文章
- mysql limit 限制查询记录数
- stm32c8t6的can通信实验代码_TCP的连接建立与关闭状态及数据传输通信过程【含有 PHP socket API 测试实验代码】...
- 使用ExecutorService来停止线程服务
- 搭建Windows Embedded Compact 7开发环境
- Asp.Net 构架(HttpModule 介绍) - Part.3
- 思维导图学Java编程思想
- 专属设计师的专业领域导航网站
- linux 抓包工具_03-Python爬虫工程师-抓包工具
- Ps 初学者教程,如何在图片中创造双色效果?
- 团队选题报告(团队)
- 计算坐标系中两个点之间的距离c语言,如何求坐标系中两点间的距离
- java计算机毕业设计网上书城网站源码+系统+数据库+lw文档+mybatis+运行部署
- WIN7下建行捷德U盾支付
- luffcc项目-04-登录防水墙认证(滑动图片验证码)、在登录认证中接入防水墙、前端获取显示并校验验证码
- G003-186-18
- idea中GIT版本回退、
- 电脑蓝屏c000021a代码错误
- Unity 3D游戏开发 - U3D入门 | 游戏场景基本操作
- 读此一席话,胜读十年书:最牛情场职场语录大全
- 亚马逊做精品选品怎么选?
热门文章
- 西门子PLC 以太网通讯
- UR机器人TCP通讯示例 详细例程,手把手教会你
- Python编写一个程序来计算 BMI 值。
- 吉米_王:乌班图下安装pycharm的方式
- Spring boot JPA+Gradle+QueryDSL 完美配置生成Q文件依赖
- stm8L-----ADC获取芯片内部参考电压
- Vue实例--音乐播放器:歌单数据接口分析
- MySQL查询(DQL)之基础查询+条件查询+排序查询
- SQL Server - 数据库(创建,修改管理-删除)-T-SQL 语句
- PEWIN32 PRO site key