用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的阿里云开发者社区。

目录
相关文章
|
16天前
|
存储 负载均衡 Java
如何配置Windows主机MPIO多路径访问存储系统
Windows主机多路径(MPIO)是一种技术,用于在客户端计算机上配置多个路径到存储设备,以提高数据访问的可靠性和性能。本文以Windows2012 R2版本为例介绍如何在客户端主机和存储系统配置多路径访问。
59 13
如何配置Windows主机MPIO多路径访问存储系统
|
5天前
|
监控 安全 网络安全
Windows Server管理:配置与管理技巧
Windows Server管理:配置与管理技巧
24 3
|
1月前
|
弹性计算 关系型数据库 数据安全/隐私保护
阿里云国际版如何配置Windows服务器的虚拟内存
阿里云国际版如何配置Windows服务器的虚拟内存
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
77 8
|
2月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
62 4
|
3月前
|
Java 应用服务中间件 开发工具
[App Service for Windows]通过 KUDU 查看 Tomcat 配置信息
[App Service for Windows]通过 KUDU 查看 Tomcat 配置信息
|
3月前
|
网络安全 Windows
在Windows电脑上启动并配置SSH服务
在Windows电脑上启动并配置SSH服务
680 0
|
3月前
|
Ubuntu Linux 数据安全/隐私保护
在 Windows 中配置 WSL2 与 Debian 的全流程
【8月更文挑战第27天】本文详细介绍了在Windows环境中配置WSL2与Debian的全过程,包括确认Windows版本、启用相关功能、安装WSL并设置版本为WSL2、下载安装Debian、配置国内镜像源,以及设置Xserver实现GUI功能。通过这些步骤,用户能够顺利完成配置,并进行基本优化。
410 0
|
数据中心 Windows 数据安全/隐私保护