跟我一起使用 compose 做一个跨平台的黑白棋游戏(4)移植到compose-jb实现跨平台

news2024/11/22 14:37:43

前言

在上一篇文章中,我们已经实现了游戏的所有界面和逻辑代码,并且在 Android 上已经可以正常运行。

这篇文章我们将讲解如何将其从使用 jetpack compose 修改为使用 compose-jb 从而实现跨平台。

老规矩,先看效果图:

s1

可以看到,桌面端效果和移动端几乎没有差别,而且在移植过程中几乎没有修改代码,几乎就是直接复制过来就可以用了。

移植过程

准备工作

在开始之前,我们需要换一下 IDE,不再使用 Android Studio 而是改为使用 IntelliJ IDEA 。

其实这里直接使用 Android Studio 也是完全没问题,毕竟 Android Studio 本来就是魔改自 IDEA 社区版的。

而我之所以要换成 IDEA 只是因为 IDEA 的新建项目自带了 Kotlin Multiplatform 模版,而这其中包括了 Compose Multiplatform 模版。

所以我可以直接使用模版创建项目,这样就不用自己建一堆文件夹和文件了。

说简单点其实就是为了偷懒,当然这里说的是完全新建一个跨平台项目,如果你是直接 clone 我的项目或者其他 compose 跨平台项目那就没必要非得用 IDEA。

如果你是新建项目,强烈建议还是使用 IDEA 的模版吧,不然自己手动创建容易出错。

s2

选择如上的项目模版后按照提示一步一步确定即可。

项目包结构

新建好项目后,项目的包结构如图:

s3

其中,根目录下 desktop 、 android 目录分别为安卓和桌面端项目的原生目录。

而 common 则为通用目录,它下面又分了很多目录:

androidMain 目录是安卓的代码(和资源)目录,在编译安卓程序时,其中的代码和资源会被拷贝到根目录下的 android 中。

desktopMain 同理,只不过这个是桌面端目录。

而 commonMain 则是平台无关的通用代码,无论编译的是什么平台都会参与编译。

其他 *Test 是测试代码目录,咱们用不上就先不用管了。

复制代码

知道了各个目录的作用后,我们应该把代码复制到哪儿已经显而易见了。

咱们先把原本项目中的 gameLogic 、 gameView 、 viewModel 三个包中的文件全部复制到 common/src/commonMain/kotlin/包名 目录下,复制完后结构如下:

s4

一般来说不会有什么问题,因为新建项目时使用的模版已经帮我们把导入的依赖改好了。

虽然现在使用的代码没有变,但是实际上导入的包已经不是 jetpack compose 的包了。

如果复制文件过去后有什么问题,按照提示改好即可。

复制资源

由于我们项目中使用到了一些图片,所以需要我们把这些图片分别复制到Android和desktoi的资源目录中:

s5

android 的资源需要复制到 /common/src/androidMain/res 中,因为在安卓中我们使用的是 drawable 资源,所以需要我们在 res 目录中新建一个 drawable 目录,并把资源放到这个目录中,这里其实和原生安卓的资源一样的。

desktop 的资源需要放到 /common/src/desktopMain/resources 目录下。

适配差异代码

其实在写原生安卓程序的时候我们就说过,加载图片的方式安卓和桌面端不一样,所以需要我们单独抽出一个函数,方便现在移植的时候修改。

当时我也以为这可能是这个项目中唯一有差异的地方,没想到复制过来后又发现了两处差异代码,接下来就让我们看看。

首先介绍一下对于平台差异代码应该怎么解决。

我们只需要在 commonMain 中用 expect 声明一个函数,不要写具体实现:

expect fun loadImageBitmap(resourceName: Resource): ImageBitmap

然后分别在 androidMain 和 desktopMain 中实现这个函数:

desktop:

actual fun loadImageBitmap(resourceName: Resource): ImageBitmap {
    val resPath = when (resourceName) {
        Resource.WhiteChess -> "white_chess.png"
        Resource.BlackChess -> "black_chess.png"
        Resource.Background -> "mood.png"
    }

    return useResource(resPath) { androidx.compose.ui.res.loadImageBitmap(it) }
}

android:

@Composable
actual fun loadImageBitmap(resourceName: Resource): ImageBitmap {
    val resId = when (resourceName) {
        Resource.WhiteChess -> R.drawable.white_chess
        Resource.BlackChess -> R.drawable.black_chess
        Resource.Background -> R.drawable.mood
    }
    return ImageBitmap.imageResource(id = resId)
}

其中的 Resource 是我自己定义的一个枚举类:

enum class Resource {
    WhiteChess,
    BlackChess,
    Background
}

这个枚举类定义了项目中用到的三个资源图片:白子图片、黑子图片、棋盘背景。

对了,为什么我之前的参数类型写的是 String 而现在要改成自定义枚举类,然后在实现中自己去解析?

哈哈,因为我实际写的时候才发现,由于界面代码写在了 commonMain 中,所以是没有 R 这个资源类的,也就是说我没法直接引用资源 ID,仔细想想也是,明明代码是放在平台无关的通用代码中,怎么可能会让使用安卓特有的 R 类呢。

所以,我们界面中加载图片的代码也要对应的改一下:

改之前:

val backgroundImage = loadImageBitmap(resourceName = R.drawable.mood.toString())
val whiteChess = loadImageBitmap(resourceName = R.drawable.white_chess.toString())
val blackChess = loadImageBitmap(resourceName = R.drawable.black_chess.toString())

改之后:

val backgroundImage = loadImageBitmap(resourceName = Resource.Background)
val whiteChess = loadImageBitmap(resourceName = Resource.WhiteChess)
val blackChess = loadImageBitmap(resourceName = Resource.BlackChess)

除此之外,还有一个地方的代码也是需要适配一下,那就是获取屏幕宽度:

val screenWidth = LocalConfiguration.current.screenWidthDp

之前我是万万没想到,这个居然是安卓的特有代码,仔细想想好像确实,这个代码返回的是读取系统配置文件的数据,桌面端确实没有这个东西,而且桌面端的窗口大小是可变的啊。

所以我们需要改一下。

expect: expect fun chessboardSize(): Int

android

@Composable
actual fun chessboardSize(): Int {
    return LocalConfiguration.current.screenWidthDp
}

desktop

actual fun chessboardSize(): Int {
    return 300
}

这里因为我们获取屏幕宽度的目的只是为了设置棋盘大小,所以对于桌面端我直接写死了一个值。

最后一个差异代码其实不用适配,但是由于我强迫症,不改总觉得不舒服,所以我还是改了。

那就是 Dialog 这个 composable ,在 jetpack compsoe 中,第一个必须参数的名字是 onDismissRequest 而在 compose-jb 中却叫做 onCloseRequest ……

其实在使用的时候不写参数名就可以不用适配了,但是我感觉不写不舒服,所以就得适配一下了:

expect: expect fun BaseDialog(onCloseRequest: () -> Unit, content: @Composable (() -> Unit))

android

@Composable
actual fun BaseDialog(
    onCloseRequest: () -> Unit,
    content: @Composable () -> Unit
) {
    Dialog(
        onDismissRequest = onCloseRequest,
        content = content
    )
}

desktop

@Composable
actual fun BaseDialog(
    onCloseRequest: () -> Unit,
    content: @Composable () -> Unit
) {
    Dialog(
        onCloseRequest = onCloseRequest,
        content = {
            content()
        }
    )
}

开始运行!

自此,移植就全部完成了!

我们来看一下运行效果。

桌面端:

在终端中输入: ./gradlew run

或者依次选择 Gradle - desktop - compose desktop - run

s6

移动端:

直接在菜单中运行即可

s7

运行效果:

s1

总结

截止到现在,我们终于完成了所有的界面和逻辑代码,并且成功移植到了 compsoe-jb 实现了跨平台运行。

但是还有亿些小细节需要我们好好的优化一下,这个就留到下一篇文章了,或者如果能写的东西不多的话我就不再写一篇新文章了,我就直接把更新代码提交到 github 得了,所以欢迎大家 star 这个项目。

项目源码地址:reversiChessCompose-Github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

063:cesium设置带边界线材质(material-7)

第063个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置带边界折线材质,请参考源代码,了解PolylineOutlineMaterialProperty的应用。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共89行)相关API参考…

Python-matplotlib中的pie(饼)图

Python-matplotlib中的pie(饼)图 %matplotlib inline import matplotlib.pyplot as pltm 51212 f 40742 m_perc m/(mf) f_perc f/(mf)colors [navy,lightcoral] labels ["Male","Female"]plt.figure(figsize(8,8)) paches,te…

为什么不胜任的人,反而获得晋升?

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID:jishulingdaoli) 也许你有过这样的经历,自己勤勤恳恳地干活,每个月却只拿着微薄的薪水,有些人明明无法胜任工作,却像坐了火箭一样飞速晋升。这种现象在现实生活中无处不在…

3699元还配同价位最好屏幕!Redmi Book 14评测:几乎完美的“水桶”轻薄本

一、前言:4K价位最好屏幕 不久前,有网友让我推荐一台4000元价位的轻薄本,笔者直接选了一台搭载i5-13500H处理器且价格仅售4299元的某一线品牌产品。 但是,事后才发现不对,因为这款极具性价比的笔记本竟然用了45%NTSC色…

MIT6.824 lecture5上课笔记(涉及到Lab2A)- Go threads and raft

总结:本节课讲解了一些会在lab2中使用到的go的多线程技巧,会给一些简单的demo,lab2中可能会借鉴这些demo。 详细的Lab2 raft算法实现源码,请参考我的个人仓库(记得点颗星星), 配合readme食用更佳。 MIT6.…

ChatGPT 使用 拓展资料:如何善用大语言模型的推理能力?

ChatGPT 使用 拓展资料:如何善用大语言模型的推理能力?

ChatGPT Plus 会员续费扣款失败如何处理

扣款失败 笔者由于开通 ChatGPT Plus 会员比较早,3月26日一个月就到期了,但是最近几天注意到,虚拟卡上也没有提醒我扣费,还是能继续使用 GPT-4.0,就很奇怪,于是就研究了一番。 PS: 如果有小伙伴还不会开通 …

python 文件操作 , 异常处理 , 模块和包

文件操作 1.写数据 # open(name, mode) # name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)。 # mode:设置打开文件的模式(访问模式):只读、写入、追加等。 #1.打开文件---通道建立--申请资源 # w 模式会清空之前的内…

【三维激光扫描】实验04:SiScan基于点云的量测功能

SiScan软件提供的基于点云的量测功能有:两点距离量测、多点距离量测、点到面距离量测、高度量测、坡度量测、角度量测、投影面积量测等等。 文章目录 一、两点距离量测二、多点距离量测三、点到面距离量测四、高度量测五、坡度量测六、角度量测七、投影面积量测一、两点距离量…

分布式与并行计算—并向算法实现

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 原始串行快速排序算法中有“分而治之”的递归调用部分,在每次选择pivoit并把序列按照小于pivoIt和大于pivoit分成两类后,左右两部分的递归排序可以并发执行。 运行时间 为了减小偶然性因素造成的时间差…

postgresql|数据库|插件学习(一)---postgresql-12的内置插件pg_stat_statements的启用和使用

前言: 插件就是原软件的扩展功能。postgresql有非常多的各种各样的插件,当然了,插件不安装对于我们使用数据库并没有什么太多的影响,可能只是不舒服一些而已,但有一些插件我们如果有安装,那么,对…

【NMI 2021】从生物学角度看进化计算(6个生物进化特征)

A biological perspective on evolutionary computation “生物学视角下的进化计算”,总结了进化算法——通过对比模拟和真实的进化,指出当前进化算法与生物进化存在的六点差异,并指出了对应的改进方案。 何为进化算法? 将任意问…

第二部分——长难句——第二章——复合句——第二节——状语从句

最后一类了哦 一,状语从句的概述 (一)状语从句的含义 一个句子作状语表达描述性的信息 一个主句可以组合好几个状语从句,因为可以表述不同方面的信息。 (二)状语从句的写法 状语从句的连接词叫做从属连…

Java--内部类学习笔记

本文介绍了什么是内部类,Java中的内部类:实例内部类. 静态内部类.局部内部类.匿名内部类的语法和注意事项,以及每个内部类的运用场景,以及简单介绍了匿名内部类更简洁更特殊的语法->lambda表达式 Java内部类学习笔记 一.什么是内部类?二. 内部类的分类1.实例内部类2.静态内…

【计算机视觉 | 扩散模型】新论文 | DragGAN论文:如果甲方想把大象 P 转身,你只需要拖动 GAN 就好了

文章目录 一、论文说明二、前言三、论文理解四、实验4.1 定性评估4.2 定量评估4.3 讨论 一、论文说明 2023年5月18日提交的论文,华人一作。 论文地址: https://arxiv.org/pdf/2305.10973.pdf项目地址: https://vcai.mpi-inf.mpg.de/projec…

pg事务:子事务

什么是子事务? 一般事务只能整体提交或回滚,而子事务允许部分事务回滚。 SAVEPOINT p1 在事务里面打上保存点标记。不能直接提交子事务,子事务也是通过事务的提交而提交。不过可以通过ROLLBACK TO SAVEPOINT p1回滚到该保存点。 子事务在大…

人工智能基础部分19-强化学习的原理和简单应用,一看就懂

大家好,我是微学AI,今天给大家介绍一下人工智能基础部分19-强化学习的原理和简单应用,随着人工智能的不断发展,各种新兴技术不断涌现。作为人工智能的一个重要分支,强化学习近年来受到了广泛关注。本文将介绍强化学习的…

基于C++的物资管理系统的设计与实现

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 一 需求分析 程序需实现以下功能: 新物资信息录入(编号、名称、库存) 查询已录入的所有物资信息(编号或名称为索引) 添加物资信息(编号或名称为索引&…

Python 初识基础

Python 初识基础 一、Python 变量定义使用二、Python 是弱类型的语言三、Python 注释四、Python 编码规范五、Python 标识命名规格六、Python 保留字表七、Python 内置函数表 一、Python 变量定义使用 声明:变量名 value值1、变量的值不是一成不变的,它…