用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天前
|
并行计算 Windows
23.10.02更新 Windows下CUDA和CUDNN的安装和配置(图多详细)
23.10.02更新 Windows下CUDA和CUDNN的安装和配置(图多详细)
11 1
|
5天前
|
存储 安全 搜索推荐
Windows之隐藏特殊文件夹(自定义快捷桌面程序)
Windows之隐藏特殊文件夹(自定义快捷桌面程序)
|
7天前
|
Oracle Java 关系型数据库
windows 下 win11 JDK17安装与环境变量的配置(配置简单详细,包含IJ中java文件如何使用命令运行)
本文介绍了Windows 11中安装JDK 17的步骤,包括从官方网站下载JDK、配置环境变量以及验证安装是否成功。首先,下载JDK 17的安装文件,如果没有Oracle账户,可以直接解压缩文件到指定目录。接着,配置系统环境变量,新建`JAVA_HOME`变量指向JDK安装路径,并在`Path`变量中添加。然后,通过命令行(cmd)验证安装,分别输入`java -version`和`javac -version`检查版本信息。最后,作者分享了如何在任意位置运行Java代码,包括在IntelliJ IDEA(IJ)中创建的Java文件,只需去掉包声明,就可以通过命令行直接运行。
|
10天前
|
C# Windows
一款.NET开源、简洁易用的Windows桌面小说阅读应用
一款.NET开源、简洁易用的Windows桌面小说阅读应用
|
11天前
|
Unix Shell 开发工具
windows下如何安装git以及IDEA如何配置git
该文指导安装Git 2.15.0版本。首先从Git官网下载最新安装包,双击安装,依次选择Next,同意默认配置,确保勾选添加到环境变量。在配置选项中,选择在cmd中使用Git(第2项),行结束转换选Windows(第1项),终端模拟器选MinTTY(第1项)。安装完成后,通过右键菜单或直接打开Git Bash验证安装成功。最后,配置全局用户名和邮箱,并在IDEA中设置Git路径以完成集成。
|
16天前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
19 0
|
22天前
|
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
41 0
|
1月前
|
XML 移动开发 Android开发
构建高效安卓应用:采用Jetpack Compose实现动态UI
【4月更文挑战第10天】 在现代移动开发中,用户界面的流畅性和响应性对于应用的成功至关重要。随着技术的不断进步,安卓开发者寻求更加高效和简洁的方式来构建动态且吸引人的UI。本文将深入探讨Jetpack Compose这一革新性技术,它通过声明式编程模型简化了UI构建过程,并提升了性能与跨平台开发的可行性。我们将从基本概念出发,逐步解析如何利用Jetpack Compose来创建具有数据动态绑定能力的安卓应用,同时确保应用的高性能和良好用户体验。
19 0
|
1月前
|
编译器 C语言 C++
VSCode上搭建C/C++开发环境(vscode配置c/c++环境)Windows系统---保姆级教程
VSCode上搭建C/C++开发环境(vscode配置c/c++环境)Windows系统---保姆级教程
|
4月前
|
Android开发 开发者
什么是Android Jetpack,它包括哪些组件?
什么是Android Jetpack,它包括哪些组件?
42 0