普段はAndroidエンジニアをやってる@sadaです。
先月、Jetpack Compose for Desktopが登場しました!
このプロジェクトはAndroid向けUIフレームワークのJetpack Composeを元にしていて、macOS/Linux/Windows上で動作するアプリケーションをFlutterやReactのような宣言的UIで作ることができます。
Androidエンジニアとしては全部Kotlinでかけるのは楽しいですね!
公式の関連ブログはこちら
試しに動かしたい場合はGetting Startedにチュートリアルがあるので、そちらを参考にすると良いと思います。
それではここからは実際のサンプルコードを紹介したいと思います。
動作確認環境
- macOS Catalina 10.15.7
- IntelliJ IDEA 2020.3 (Community Edition)
- AdoptOpenJDK 14.0.2
- Kotlin 1.4.20
- Desktop Compose 0.2.0-build132
画像表示
以下のようにImage
を使って画像を表示します。
fun main() {
Window {
Image(
bitmap = TODO("ImageBitmapを生成する"),
modifier = Modifier.fillMaxSize()
)
}
}
ImageBitmap
の作り方は以下のような方法があります。
リソースから読み込む
imageResource
という関数を利用してリソース内の画像を表示します。
bitmap = imageResource("sample.png"),
こちらは@Composable
関数なので、同様に@Composable
関数内でしか呼べない点は注意しましょう。
また、xmlのvector imageも利用することができます。その場合はbitmap
ではなくimageVector
にvectorXmlResource
関数で取得したVectorImage
を設定しましょう。
imageVector = vectorXmlResource("images/compose-logo.xml"),
デバイスストレージから読み込む
表示したい画像のパスを指定してFile
からImageBitmap
を生成します。
bitmap = Image.makeFromEncoded(File("sample.png").readBytes()).asImageBitmap(),
その他の方法
Canvas
を利用してrowデータを描画する方法もありますが、こちらは長くなるので割愛します。
アプリケーションアイコン
Window生成時にBufferedImage
を生成してicon
に指定するだけで表示されます。
fun main() {
Window(icon = ImageIO.read(File("sample.png"))) {
}
}
トレイアイコン
onActive
でTray
を生成して、icon
に設定するだけです。
menu
も設定しないと表示されないのでご注意ください。
fun main() {
Window {
onActive {
val tray = Tray().apply {
icon(ImageIO.read(File("sample.png")))
menu(
MenuItem(
name = "Quit App",
onClick = { AppManager.exit() }
)
)
}
onDispose {
tray.remove()
}
}
}
}
スクロールバー
スクロールバーを表示するなら必要最低限のコードだとこんな感じです。
fun main() {
Window {
Box {
val stateVertical = rememberScrollState(0f)
val stateHorizontal = rememberScrollState(0f)
ScrollableColumn(scrollState = stateVertical) {
ScrollableRow(scrollState = stateHorizontal) {
Column {
for (item in 0..100) {
Box { Text("Item in ScrollableColumn #$item") }
}
}
}
}
VerticalScrollbar(adapter = rememberScrollbarAdapter(stateVertical))
HorizontalScrollbar(adapter = rememberScrollbarAdapter(stateHorizontal))
}
}
}
するべきこととしては以下の4つです。
-
rememberScrollState()
で各スクロールバーの状態を保持するScrollState
を生成する -
ScrollableColumn
やScrollableRow
の引数scrollState
に生成したScrollState
を渡す -
rememberScrollbarAdapter(scrollState: ScrollState)
でScrollbarAdapter
を生成する -
Scrollbar
の引数adapter
に生成したScrollbarAdapter
を渡す
マウスイベント
クリック関連イベント
Modifier.clickable()
にリスナーを設定することで実装できます。
Box(
modifier = Modifier.clickable(
onClick = {
},
onDoubleClick = {
},
onLongClick = {
}
)
)
マウス移動イベント
Modifier.pointerMoveFilter()
にリスナーを設定することで実装できます。
-
onMove
: マウス座標の取得 -
onEnter
: 該当のViewのマウスが進入 -
onExit
: 該当のViewからマウスが退出
Box(
modifier = Modifier.pointerMoveFilter(
onMove = {
false
},
onEnter = {
false
},
onExit = {
false
}
)
)
キーイベント
以下のサンプルはテキストフィールドに入力した文字をCmd+Enterで「Appl
y text: 」に反映し、Cmd+Rで入力項目共々リセットするようにしています。
KeyInutに関してはまだExperimentalなので今後I/Fが大きく変わる可能性があることをご留意ください。
@OptIn(ExperimentalKeyInput::class)
fun main() = Window {
MaterialTheme {
var applyText by remember { mutableStateOf("") }
var inputText by remember { mutableStateOf("") }
Column {
Text("Apply text: $applyText")
TextField(
value = inputText,
onValueChange = { inputText = it },
modifier = Modifier.shortcuts {
on(Key.CtrlLeft + Key.Enter) {
applyText = inputText
inputText = ""
}
on(Key.CtrlLeft + Key.R) {
applyText = ""
inputText = ""
}
}
)
}
}
}
また、AppWindow
にはkeyboard
プロパティがあり、現在のウィンドウで常にアクティブになるキーボードショートカットを定義することができます。
以下は、ポップアップWindowにEscapeでクローズする処理を割り当てたサンプルです。
AppWindow().also {
it.keyboard.setShortcut(Key.Escape) {
it.close()
}
}.show {
}
Java Swing フレームワークを使う
Swingを利用する場合は、ComposePanel
を生成してSwingレイアウトに追加します。
最低限のコードサンプルはこんな感じになります。
fun main() {
val window = JFrame()
val composePanel = ComposePanel()
window.contentPane.add(composePanel, BorderLayout.CENTER)
composePanel.setContent {
Box {
Text("text")
}
}
window.setSize(100, 100)
window.isVisible = true
}
まとめ
いろいろなサンプルをとりあげてみましたがいかがでしょうか?
この記事で紹介したサンプルは以下のチュートリアルを参考にしています。
Androidエンジニアの方は、デスクトップアプリもちょっと作ってみようかな?ってなりませんか??(自分はなりましたw)
KotlinはAndroidだけでなくiOSやサーバーサイド、デスクトップ(Linux,macOS,Windows)など様々なプラットフォームに対応しています。
Kotlinを起点に色々なプラットフォームに手を出してみるのも面白いかもしれませんね。