LoginSignup
0
0

[チュートリアル6]UIを改善してみよう[compose multiplatform]

Last updated at Posted at 2023-11-30

はじめに

公式ドキュメントの和訳と要約+αです。
前回はこちらです。

デザインを改良する

今の見た目ってこんな感じだと思います。
現在の画像

これだと要素が詰まりすぎているので、少し余白を作りましょう。

App.ktのApp()ファンクションの中身を以下のように換えてください。

@Composable
fun App() {
    MaterialTheme {
        var location by remember { mutableStateOf("Europe/Paris") }
        var timeAtLocation by remember { mutableStateOf("No location selected") }

        // modifierを使って20dpのpaddingを追加
        Column(modifier = Modifier.padding(20.dp)) {
            Text(
                timeAtLocation,
                // style属性を使ってfontSizeを20spに変更
                style = TextStyle(fontSize = 20.sp),
                // textAlign属性をCenterに変更し、中央に文字がくるように
                textAlign = TextAlign.Center,
                // fillMaxWidthを使って、Columnの幅いっぱいに広げる
                modifier = Modifier.fillMaxWidth()
                    .align(Alignment.CenterHorizontally)
            )

            TextField(
                value = location,
                // modifierを使って10dpのpaddingを追加
                modifier = Modifier.padding(top = 10.dp),
                onValueChange = {
                    location = it
                }
            )

            Button(
                // modifierを使って10dpのpaddingを追加
                modifier = Modifier.padding(top = 10.dp),
                onClick = {
                    timeAtLocation = currentTimeAt(location) ?: "Invalid Location"
                }
            ) {
                Text("Show Time")
            }
        }
    }
}

細かい解説は面倒なので、コメントアウトの形でコードの中に入れておきました。
気になったら呼んでください。

またimportも同時に追加します。

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

この修正によって、時間を表示する部分は画面の中央にくるようになり、それぞれの要素の間に余白が生まれたはずです。

ユーザーに優しく

現状のアプリだとユーザーがスペルミスをしたら正しく動かないので、使いやすいとは言えませんね。
リストの中から国を選んだら、その国が所属しているタイムゾーンを判別して、そのタイムゾーンの時刻を表示してくれるように修正しましょう。

細かい解説はコメントアウトで書いてあります。
App.kt

// `Country` というデータクラスを作りました。国のデータを保持するクラスです。名前とタイムゾーンをプロパティに持ちます。
data class Country(val name: String, val zone: TimeZone)

fun currentTimeAt(location: String, zone: TimeZone): String {
    fun LocalTime.formatted() = "$hour:$minute:$second"

    val time = Clock.System.now()
    // 取得した時刻を指定されたタイムゾーンに変換します。
    val localTime = time.toLocalDateTime(zone).time

    // 整形した時刻と場所の名前を含む文字列を返します。
    return "The time in $location is ${localTime.formatted()}"
}

// `countries` 関数は、いくつかの国のリストを生成して返します。
fun countries() = listOf(
    Country("Japan", TimeZone.of("Asia/Tokyo")),
    Country("France", TimeZone.of("Europe/Paris")),
    Country("Mexico", TimeZone.of("America/Mexico_City")),
    Country("Indonesia", TimeZone.of("Asia/Jakarta")),
    Country("Egypt", TimeZone.of("Africa/Cairo")),
)

@Composable
fun App(countries: List<Country> = countries()) {
    MaterialTheme {
        // `showCountries` はドロップダウンメニューの表示状態を保持する変数です。
        var showCountries by remember { mutableStateOf(false) }
        // `timeAtLocation` は選択された場所の時刻を表示するための変数です。
        var timeAtLocation by remember { mutableStateOf("No location selected") }

        Column(modifier = Modifier.padding(20.dp)) {
            Text(
                timeAtLocation,
                style = TextStyle(fontSize = 20.sp),
                textAlign = TextAlign.Center,
                // `fillMaxWidth` は可能な限りの幅を埋めるようにします。
                // `align` は水平方向の位置を調整します。
                modifier = Modifier.fillMaxWidth()
                    .align(Alignment.CenterHorizontally)
            )
            // `Row` は横にコンテンツを並べるためのコンテナです。
            Row(modifier = Modifier.padding(start = 20.dp, top = 10.dp)) {
                // `DropdownMenu` はドロップダウンメニューを表示するためのコンポーザブルです。
                DropdownMenu(
                    expanded = showCountries,
                    onDismissRequest = { showCountries = false }
                ) {
                    // 国のリストをループし、各国に対してメニューアイテムを作成します。
                    countries.forEach { (name, zone) ->
                        DropdownMenuItem(
                            onClick = {
                                // メニューアイテムをクリックすると、時刻を更新してメニューを閉じます。
                                timeAtLocation = currentTimeAt(name, zone)
                                showCountries = false
                            }
                        ) {
                            Text(name)
                        }
                    }
                }
            }

            Button(
                modifier = Modifier.padding(start = 20.dp, top = 10.dp),
                onClick = { showCountries = !showCountries }
            ) {
                Text("Select Location")
            }
        }
    }
}

それに伴ってimportに以下を追加します。

import androidx.compose.foundation.layout.Row
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem

これで、国を選択したら、その国が属するtime zoneとそのタイムゾーンでの現在時刻を表示するようになったはずです。

画像を導入する

文字だけだと味気ないですよね、画像を入れてみましょう。
今回は国旗の画像を入れてみます。
これで最後なので頑張っていきましょう〜

リソースを配置する

今回はアプリ内部に画像のデータを保存するようにします。保存するパスは

src/commonMain/resources

保存する画像はflagcdnさんから拝借しましょう。
今回は、JapanFranceMexicoIndonesiaEgyptですね。

です。実はそれぞれのプラットフォームごとに別々の画像を保存しておけば、違う画像を表示してくれるのですが、今回はやりません。

リソースを表示してみる

次に先ほどの画像を表示するように、コードを修正してみましょう。
詳細な解説はコメントアウトに書いてあります。

// Countryというデータクラスで、画像を持つように修正します。
data class Country(val name: String, val zone: TimeZone, val image: String)

fun currentTimeAt(location: String, zone: TimeZone): String {
    fun LocalTime.formatted() = "$hour:$minute:$second"

    val time = Clock.System.now()
    val localTime = time.toLocalDateTime(zone).time

    return "The time in $location is ${localTime.formatted()}"
}

// `countries` 関数の中で、先ほど追加した画像を含むようにします。
fun countries() = listOf(
    Country("Japan", TimeZone.of("Asia/Tokyo"), "jp.png"),
    Country("France", TimeZone.of("Europe/Paris"), "fr.png"),
    Country("Mexico", TimeZone.of("America/Mexico_City"), "mx.png"),
    Country("Indonesia", TimeZone.of("Asia/Jakarta"), "id.png"),
    Country("Egypt", TimeZone.of("Africa/Cairo"), "eg.png")
)

@OptIn(ExperimentalResourceApi::class)
@Composable
fun App(countries: List<Country> = countries()) {
    MaterialTheme {
        var showCountries by remember { mutableStateOf(false) }
        var timeAtLocation by remember { mutableStateOf("No location selected") }

        Column(modifier = Modifier.padding(20.dp)) {
            Text(
                timeAtLocation,
                style = TextStyle(fontSize = 20.sp),
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
                    .align(Alignment.CenterHorizontally)
            )
            Row(modifier = Modifier.padding(start = 20.dp, top = 10.dp)) {
                DropdownMenu(
                    expanded = showCountries,
                    onDismissRequest = { showCountries = false }
                ) {
                    // imageも使うように修正します。
                    countries.forEach { (name, zone, image) ->
                        DropdownMenuItem(
                            onClick = {
                                timeAtLocation = currentTimeAt(name, zone)
                                showCountries = false
                            }
                        ) {
                            Image(
                                painterResource(image),
                                modifier = Modifier.size(50.dp).padding(end = 10.dp),
                                contentDescription = "$name flag"
                            )
                            Text(name)
                        }
                    }
                }
            }

            Button(
                modifier = Modifier.padding(start = 20.dp, top = 10.dp),
                onClick = { showCountries = !showCountries }
            ) {
                Text("Select Location")
            }
        }
    }
}

それに伴いimport文を追加します。

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource

ここまでで、割とまともなアプリになっていると思います。
以上でチュートリアル終了です!
お疲れ様でした。

0
0
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
0
0