用Jetpack Compose Desktop极简配置做一个Windows桌面时间显示器(compose框架入门向)

简介: compose的模板配置多少有些臃肿,如果只做单一平台多少是会简单一些的。但几乎没怎么见过配置很简单的例子,都是套那些模板,我觉得没必要搞那么复杂,那么本文就做一个非常简单的只有几行代码的小例子

眼看着微软的winuiMAUI用起来还不如以前的winformWPF,很多桌面开发人员都想尝试新兴平台和语言,传统的网页套壳方面除了很多年前就爆火的Electron,紧跟在后面的还有React Nativetauri,UI毕竟是网页前端一贯的优势,天生就是干这个的,做起来确实方便。但也并非只能考虑套壳写html,其他新兴语言也纷纷加入windows桌面开发的市场,dartFluttergo-langwailsfyne,都很有很多应用采用,他们胜在一个语法好看,维护也得力,跟那堆浏览器套壳相比性能要强些。当然最早搞跨平台的JVM平台也不能一直只用祖传SwingJavaFX了,Jetpack Compose依靠Android官方的背书做大做强,也进入到桌面开发的市场。

尽管这些框架都是以多平台自称,但并非所有应用都面向所有用户,单一平台的开发需求还是有很多的,尤其是windows这样一个体量的平台,做好这一个就难。不过上面说的这些框架的配置都是一上来就搞多平台,多多少少有些臃肿,如果只做单一平台多少是会简单一些的。但几乎没怎么见过配置很简单的例子,都是套那些模板,我觉得没必要搞那么复杂,那么本文就做一个非常简单的只有几行代码的小例子:时间显示器

缘起

可能有人会觉得,什么时间显示器,做这么个东西也太没用了,windows任务栏右下角不是就一直在显示时间吗?其实我就是为了这个东西才写的这例子,有的系统会显示,有的则不会,那么怎么修改呢?windows经典技能修改注册表HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced,新建DWORD(32bit)项,命名ShowSecondsInSystemClock,赋值1就显示秒。

image.png
image.png

终于显示秒了,看着挺简单,但细想是真的太搞笑了,这么简单的需求都要去改注册表,就没办法在控制面板设置,真是绝了,而且现在这系统耗费这么多资源的情况下,却连个秒都不舍得显示了,所以我特意做个时间显示器,连毫秒一起显示,帮Windows把时间显示全。

配置

首先不需要在IDEA或跑到Android studio去选什么模板,因为无论选什么模板,都给你搞一大堆配置,目录结构也不舒服。我们空白文件夹起步,先写gradle的配置,settings.gradle.kts就配置下rootProject.name就好,当然这个也并非必要,不写这个文件都没关系。然后写build.gradle.kts的部分:

import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
    kotlin("jvm") version "1.8.20"
    id("org.jetbrains.compose") version "1.4.0"
}
dependencies {
    implementation(compose.desktop.currentOs)
}

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            targetFormats(TargetFormat.Exe)
            packageName = "时间显示器"
            packageVersion = "1.0.0"
        }
    }
}

就这么简单就妥了,目录照样用传统的src\main\kotlin,一个平台有什么好建那么多main文件夹的,然后建个kotlin文件名为Main.kt,然后准备写main()方法,kotlin会自动为这个main()方法生成一个名为MainKt的对象名。

窗口绘制

下面就直接在新建的Main.kt中绘窗口,和flutter有些像:

fun main() = application {
    Window(
        state = rememberWindowState(
            size = DpSize(350.dp, 100.dp),
            position = WindowPosition.Aligned(Alignment.Center)
        ),
        resizable = false,
        title = "时间显示器",
        onCloseRequest = ::exitApplication
    ) {
        App()
    }
}

这个部分可以说一眼就能看清在干什么,main()不用说,这个application就是Application.desktop.kt中的application(),就两个参数。上面默认composable{}的是他的第2个参数content,就是在这里面绘制应用内容。如果你想关闭程序后仍在后台运行一些东西可以更改他的第1个参数exitProcessOnExitfalse

然后就是调用Window,是在Window.desktop.kt中的Window(),这个参数多但都可以字面理解,上面的代码分别赋值了状态(窗口大小,定位),禁止手动调大小,标题,关闭事件。请注意这个关闭事件onCloseRequest和上面提到的那个不一样,这个决定你是否关闭,就像Windows记事本notepad那个经典的“你想将更改保存到 无标题 吗?

就是这图:

image.png

所以你写onCloseRequest = {}的话,他就真的不关了,只能去任务管理器关。然后就是这个Window里面的内容App(),下面就实现这个方法,首先这种方法是在绘制嘛,自然要特殊对待,也就要给注解的,两个注解:@Composable@Preview,然后是内容:

@Composable
@Preview
fun App() {
    MaterialTheme {
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize()
        ) {
            Text("时间")
        }
    }
}

这段就是就是先定一个皮肤MaterialTheme,但不要一觉得是皮肤就能换,这东西要写的话也就一个material没别的,然后就是定个铺满窗口的Box,第一个参数contentAlignment跟名字意思一样就是居中,第二个modifier是大小和padding的设置,这个值除了用前面窗口传下来的就是用Modifier这个类,他除了元素的尺寸大小还可以定一个debug用的诊断信息参数inspectorInfo,但用处不大。前端网页开发人员可能一看到这里面只有padding就不能设置margin吗,用过flutter的可能就会一笑,其实Box这种单纯的容器就是用来做margin的,具体到一些组件的padding可能有的还有自己独特的padding参数,不用这个modifier设置。

下面就是效果:

image.png

可以看到确实定在中间了,那么下一步就是显示日期

显示日期

如果你第一时间就想到java.util.Datejava.text.SimpleDateFormat这俩祸害,请缓一下,千万别用这两个历史遗留问题满满的类了!日期我们用java1.8就引入的LocalDateTime.now(),他对应的格式化是DateTimeFormatter,他们的注释都写了大量的例子可以看,使用其实很简单就是:

val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")
var time = dateFormatter.format(LocalDateTime.now())

这个格式化是挺厉害的可以自己灵活的用代码去拼接,如果有多个日期格式需求就不用和以前一样定义多个SimpleDateFormat,像下面这样就能控制:

val timeFormatter = DateTimeFormatterBuilder()
    .append(DateTimeFormatter.ISO_LOCAL_DATE)
    .appendLiteral(' ')
    .appendValue(ChronoField.HOUR_OF_DAY, 2)
    .appendLiteral(':')
    .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
    .optionalStart()
    .appendLiteral(':')
    .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
    .optionalStart()
    .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 1, true)
    .toFormatter()

可以用java.time.format.DateTimeFormatterBuilder来动态的构建日期格式化,用java.time.temporal.ChronoField来选中日期类型,这一段格式化是和前面完全一样的,当然了这里不用这种写法,只是了解一下。

然后把下面的Text("时间")改成Text(time)就行了,然后再运行:

image.png

可以看到时间了,但这个当然没办法动态变化了,这时候你可能想到多线程来持续修改时间,但这可是kotlin,用协程就好了!他就像java拖了很久才在JDK19出的虚拟线程,用Thread.startVirtualThread()来启动,由于这不是个长期支持版本,我想大部分人最多还是只会用到JDK17,没办法用到这个好玩意。但kotlin就不一样了,先天自带这个好东西,但是要想在compose直接跑这个,我们还需要在build.gradle.kts那边引入一个小小的包kotlinx-coroutines-swing

dependencies {
    implementation(compose.desktop.currentOs)
    runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
}

然后可以在text定义的下面启动最简单的MainScope协程:

MainScope().launch {
    while (true) {
        time = dateFormatter.format(LocalDateTime.now())
        delay(100)
    }
}

这段不用说自然是放到time定义的与MaterialTheme之间的地方,写起来也是很简单呐,但只是这样是没效果的,因为这个变量修改想想就知道不可能直接传递给UI的,需要给一个state,所以修改一下time的定义:

var time by mutableStateOf(dateFormatter.format(LocalDateTime.now()))

这个写by来代替=赋值是有好处的,改完time仍然是String类型,后面可以直接用=去修改和获取,否则因为是MutableState类型,要调用get()set(),这个语法糖确实黑科技,其他平台几乎做不到。下面效果就出来了:

e5b33d9719574ae2a2675dee73dae862~tplv-k3u1fbpfcp-watermark.gif

下面我又想到Windows时间显示的一大痛点,居然不能复制!想记录时间只能看着手敲,这也太蠢了,所以我特地做个复制按钮吧!

复制日期

复制自然要访问剪切板,剪切板不必用awt那个破Toolkit.getDefaultToolkit().systemClipboard了,可以用LocalClipboardManager.current轻松搞定,写起来简单的多,在time定义的下面再定义这个剪切板对象:

val clipboard = LocalClipboardManager.current

然后改下Text(text)的位置,给他套个Row对象让他和按钮排一行,然后放入这个按钮:

Row(
    verticalAlignment = Alignment.CenterVertically
) {
    Text(time)
    Button(modifier = Modifier.padding(horizontal = 10.dp), onClick = {
        clipboard.setText(AnnotatedString(time))
    }) {
        Text("复制")
    }
}

Row需要设定下垂直对齐,因为按钮比文本长一些,然后就是Button,如前文所说用Modifier定义下padding控制与左边文本的距离,从这里就可以看到其实这个padding就是前端网页的margin,想调padding是用他的contentPadding属性来调的。然后在onClick中赋值剪切板即可,按下按钮就复制:

image.png

综合源代码

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import kotlinx.coroutines.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Composable
@Preview
fun App() {
    val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")
    var time by mutableStateOf(dateFormatter.format(LocalDateTime.now()))
    val clipboard = LocalClipboardManager.current
    MainScope().launch {
        while (true) {
            time = dateFormatter.format(LocalDateTime.now())
            delay(100)
        }
    }
    MaterialTheme {
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize()
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(time)
                Button(modifier = Modifier.padding(horizontal = 10.dp), onClick = {
                    clipboard.setText(AnnotatedString(time))
                }) {
                    Text("复制")
                }
            }
        }
    }
}

fun main() = application {
    Window(
        state = rememberWindowState(
            size = DpSize(350.dp, 100.dp),
            position = WindowPosition.Aligned(Alignment.Center)
        ),
        resizable = false,
        title = "时间显示器",
        onCloseRequest = ::exitApplication
    ) {
        App()
    }
}

可以看到就一个配置文件,一个代码文件,几十行代码(一大半还是import、括号符号、空行),相当的简洁。本篇属于composekotlin的入门向教学,后续会根据此文展开说一些其他内容,已经在编辑中。

本文写作于2023年6月27日并发布于lyrieek的掘金,于2023年7月19日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
2天前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
7 0
|
8天前
|
Docker Windows 容器
Windows Docker Desktop 无法启动 自动退出报错信息为:Docker Desktop -Unexpected WsL error An unexpected error was e
Windows Docker Desktop 无法启动 自动退出报错信息为:Docker Desktop -Unexpected WsL error An unexpected error was e
26 0
|
17天前
|
XML 移动开发 Android开发
构建高效安卓应用:采用Jetpack Compose实现动态UI
【4月更文挑战第10天】 在现代移动开发中,用户界面的流畅性和响应性对于应用的成功至关重要。随着技术的不断进步,安卓开发者寻求更加高效和简洁的方式来构建动态且吸引人的UI。本文将深入探讨Jetpack Compose这一革新性技术,它通过声明式编程模型简化了UI构建过程,并提升了性能与跨平台开发的可行性。我们将从基本概念出发,逐步解析如何利用Jetpack Compose来创建具有数据动态绑定能力的安卓应用,同时确保应用的高性能和良好用户体验。
15 0
|
19天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
1月前
|
应用服务中间件 nginx Windows
windows下快速安装nginx 并配置开机自启动
windows下快速安装nginx 并配置开机自启动
windows下快速安装nginx 并配置开机自启动
|
1月前
|
算法 应用服务中间件 网络安全
windows下采用 nginx配置websocket支持wss流程
windows下采用 nginx配置websocket支持wss流程
|
1月前
|
存储 Oracle Java
windows配置java环境
windows配置java环境
44 0
|
1月前
|
XML API Android开发
【Android 从入门到出门】第三章:使用Hilt处理Jetpack Compose UI状态
【Android 从入门到出门】第三章:使用Hilt处理Jetpack Compose UI状态
26 4
|
1月前
|
弹性计算 网络安全 Apache
windows server2012服务器下PHPstudy配置ssl证书(https配置)
windows server2012服务器下PHPstudy配置ssl证书(https配置)
71 0
|
1月前
|
安全 数据安全/隐私保护 Windows
解锁安全之门,Windows Server 2019密码修改攻略大揭秘
解锁安全之门,Windows Server 2019密码修改攻略大揭秘