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

前言

在上一篇文章中,我们已经实现了游戏的所有界面和逻辑代码,并且在 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BurpSuite—-Target模块(目标模块)

前言 本文主要介绍BurpSuite—-Target模块(目标模块)的相关内容 关于BurpSuite的安装可以看一下之前这篇文章: http://t.csdn.cn/cavWt Target功能 目标工具包含了SiteMap,用你的目标应用程序的详细信息。它可以让你定义哪些对象在范围上为你目前的工…

「车型分析」控制系统典型应用车型 —— 辊筒AGV

辊筒AGV (Roller conveyor ) 是一种常见的AGV机器人类型,它利用辊筒和轮子在巷道中实现货物的搬运和运输,可实现托盘物品的卸载和运输等功能, 具有更高的灵活性、适应性和效率。本文将基于这款市场上常见的AGV进行一次简单的介绍。 1 车型介绍: 辊筒AGV…

Java基础学习(18)反射、动态代理

Java基础学习 一、反射1.1 什么是反射1.2 获取class对象 二、综合练习2.1 保存信息2.2 文件的动态创建 三、动态代理3.1 什么是动态代理3.2 创建代理 一、反射 1.1 什么是反射 反射允许对封装类的字段,方法和构造函数的信息进行编程访问 个人理解: 就是…

Mysql常见的索引模型

目录 有序数组哈希表二叉搜索树B-TreeBTree 有序数组 我们指定一个列为索引,然后按照这个列的值排序,以有序数据存放入数据表中,如下所示 这样,我们在查找数据的时候,就可以通过id这个列,在数据表中进行二…

坚持刷题2个月,终于......

最近一个读者和我反馈,他坚持刷题2个月,终于去了他梦寐以求的大厂,薪资涨幅非常可观,期间面字节跳动还遇到了原题…并表示目前国内的大厂和一些独角兽,已经越来越效仿硅谷公司的做法,通过面试给定题&#x…

zookeeper的安装使用

zookeeper的安装使用 一、下载安装 https://zookeeper.apache.org/ 点击 download 以我自己的安装为例,linux,3.8.0 准备3台linux服务器:192.168.10.128、192.168.10.129、192.168.10.130 1.上传解压 把apache-zookeeper-3.8.0-bin.tar.gz 上传到 /usr/local/zo…

耗时162天,从华为外包5k转岗正式员工15k,经历的心酸只有自己知道····

一提及外包测试,大部分人的第一印象就是:工作强度大,技术含量低,没有归属感! 本人毕业于某普通二本院校非计算机专业,跨专业入行测试,至今有近 5年工作经验。 第一份测试工作是在华为做了两年外…

Github Copilot 的补强工具Github Copilot Labs的常用功能介绍

一、什么是Github Copilot Labs Github Copilot Labs是由GitHub推出的一款基于人工智能技术的代码协作工具,旨在协助开发者更加快速、高效地编写代码。该工具使用了机器学习技术,通过学习大量的开源代码和编写实践,提供了对于代码变量、函数…

多激光雷达手眼标定

手眼标定方法已经有很多博客进行解析,但是都是针对机器人的手(夹爪)眼睛(相机)进行标定。例如: 标定学习笔记(四)-- 手眼标定详解 手眼标定_全面细致的推导过程 本文主要描述多激光…

四、数据仓库详细介绍(规范)

大家好,这是数据仓库系列的第三个话题,排序在架构之后、建模之前。为什么会提的这么靠前呢? 因为规范约束的是数仓建设的全流程,以及后续的迭代和运维。事实上,数仓规范文档,应该随着架构设计文档&#xf…

Java 与排序算法(5):归并排序

一、归并排序 归并排序(Merge Sort)是一种基于分治思想的排序算法。它将待排序的数组分成两个长度相等的子数组,然后对这两个子数组分别进行归并排序,最后将两个排好序的子数组合并成一个有序的数组。 具体实现过程如下&#xf…

要做存储业务,我解析了一个项目的源码

最近在做存储相关的业务,更具体的来说是存储相关的研发,于是就上网查了一下相关的资料,思虑再三打算从最简单的 Json 数据交换格式开始研究。 JSON是独立于编程语言的数据交换格式,几乎所有与网络开发相关的语言都有JSON函数库&am…

基于Java+SpringMvc+vue+element实现高效学生社团平台管理

基于JavaSpringMvcvueelement实现高效学生社团平台管理 博主介绍:5年java开发经验,专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

oracle数据库当中用户的创建,添加,授权,以及表的创建与表的简单介绍,以及在oracle数据库当中的约束以及约束条件的简单介绍

系列文章目录 (3条消息) oracle数据库简介 文章目录 系列文章目录 前言 一、用户的创建 1.1、创建命令 1.2、给予scott用户权限 1.3、以scott用户进行连接登录 二、表和表的设计原则 2.1、表的概念 2.1.1、表是从属于用户的 2.1.2、表是逻辑表(概念表),不…

gpt.4.0-gpt 国内版

gpt 使用 GPT(Generative Pre-trained Transformer)是一种预训练的语言模型,可用于多种自然语言处理任务,如情感分析、文本分类、文本生成等。下面是使用GPT的一些步骤和建议: 确定任务和数据集:首先&…

Hibernate 快速入门

Hibernate 快速入门 〇、前言一、搭建 Hibernate 项目步骤1:新建 Java 项目附录1:新建Java项目中的相关文件信息步骤2:添加 Hibernate 框架支持附录2:添加Hibernate框架支持后,Java项目中的相关文件信息步骤3:其他关键配置1、添加数据库驱动包(本文以MySQL为例)2、配置…

C++11 列表初始化initializer_list

引子 C11,是继C98后的一次有力更新,引进了很多好用的语法,STL也添加了几个新容器,也解决了很多的问题。本篇博客就学习一下C11列表初始化的新语法和 initializer_list 文章目录 引子一. 列表初始化二. initializer_list结束语 一…

计算机底层知识

汇编语言(机器语言)的执行过程 汇编语言的本质:机器语言的助记符 其实他就是机器语言 计算机通电->CPU读取内存中程序(电信号输入) ->时钟发生器不断震荡通电 ->推动CPU内部一步一步执行(执行多…

安卓开发 | 将Vue项目打包为app

知识目录 一、写在前面✨二、Hbuilder X准备💕2.1 Hbuilder X简介2.2 下载 三、打包💕3.1 获取dist目录3.2 新建5app3.3 替换文件3.4 编写manifast.json文件3.5 app云打包 四、总结撒花😊 一、写在前面✨ 大家好!我是初心&#xf…

OJ练习第107题——二叉搜索子树的最大键值和

二叉搜索子树的最大键值和 力扣链接:1373. 二叉搜索子树的最大键值和 题目描述 给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。 二叉搜索树的定义如下: 任意节点的左子树中的键值都 小于 此节点的键值。 任意…