28
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KotlinAdvent Calendar 2020

Day 17

Jetpack Compose for Desktop チュートリアル紹介

Last updated at Posted at 2020-12-16

普段は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ではなくimageVectorvectorXmlResource関数で取得した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"))) {
    }
}

トレイアイコン

onActiveTrayを生成して、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を生成する
  • ScrollableColumnScrollableRowの引数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を起点に色々なプラットフォームに手を出してみるのも面白いかもしれませんね。

28
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?