Androidアプリ開発の覚書き

Androidアプリ開発を、Kotlin, Jetpack Composeで行っています。気になったことをChatGPTに聞いて記事にしています。

比較的複雑なUIの実装例

Jetpack ComposeのColumnとRowの使い方を勘違いしていました。

あと、RadioButtonやCheckBox, LazyColumn, LazyRowといった複数の値や状態を持つコンポーネントの状態管理も、コード上の変数を通して管理するようで、従来のXMLベースのUIとは違った感じです。

テキストボックス、チェックボックス、ラジオボタン、LazyRow, Tabを含む、比較的複雑なUIの実装例をお願いしたいのですが、よろしいですか?

User
AndroidアプリをJetpack Composeで開発しています。

テキストボックス、チェックボックス、ラジオボタン、LazyRow, Tabを含む、比較的複雑なUIの実装例をお願いしたいのですが、よろしいですか?

ChatGPT
もちろんです。Jetpack Composeを使用した複雑なUIの実装例を以下に示します。この例では、テキストボックス(TextField)、チェックボックス(Checkbox)、ラジオボタン(RadioButton)、LazyRow、そしてTabを組み合わせたUIを構築します。これらのコンポーネントを使って、ユーザー入力を取得し、リスト項目を表示し、異なるタブ間でのナビゲーションを実現します。

基本セットアップ
まず、Jetpack Composeを使用するための基本的なセットアップを行ってください。build.gradleファイルに必要な依存関係を追加し、最新のComposeバージョンを使用していることを確認してください。

サンプルコード

import android.os.Bundle  
import androidx.activity.ComponentActivity  
import androidx.activity.compose.setContent  
import androidx.compose.foundation.layout.Column  
import androidx.compose.foundation.layout.PaddingValues  
import androidx.compose.foundation.layout.padding  
import androidx.compose.foundation.lazy.LazyRow  
import androidx.compose.foundation.lazy.items  
import androidx.compose.material.*  
import androidx.compose.runtime.*  
import androidx.compose.ui.Modifier  
import androidx.compose.ui.tooling.preview.Preview  
import androidx.compose.ui.unit.dp  
  
class MainActivity : ComponentActivity() {  
  override fun onCreate(savedInstanceState: Bundle?) {  
    super.onCreate(savedInstanceState)  
    setContent {  
      AppContent()  
    }  
  }  
}  
  
@Composable  
fun AppContent() {  
  // タブの状態を保持  
  var selectedTab by remember { mutableStateOf(0) }  
  val tabTitles = listOf("Tab 1", "Tab 2")  
  
  Scaffold(topBar = {  
    TopAppBar(title = { Text("Jetpack Compose UI") })  
  }) { paddingValues ->  
    Column(modifier = Modifier.padding(paddingValues)) {  
      // テキスト入力  
      var text by remember { mutableStateOf("") }  
      TextField(value = text, onValueChange = { text = it }, label = { Text("テキスト入力") })  
  
      // チェックボックス  
      var checked by remember { mutableStateOf(false) }  
      Checkbox(checked = checked, onCheckedChange = { checked = it })  
  
      // ラジオボタン  
      val radioOptions = listOf("オプション1", "オプション2")  
      var selectedOption by remember { mutableStateOf(radioOptions[0]) }  
      Column {  
        radioOptions.forEach { text ->  
          Row {  
            RadioButton(selected = text == selectedOption, onClick = { selectedOption = text })  
            Text(text = text, modifier = Modifier.padding(start = 8.dp))  
          }  
        }  
      }  
  
      // LazyRow  
      LazyRow(contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)) {  
        items(items = listOf("Item 1", "Item 2", "Item 3")) { item ->  
          Card(modifier = Modifier.padding(8.dp), elevation = 4.dp) {  
            Text(text = item, modifier = Modifier.padding(16.dp))  
          }  
        }  
      }  
  
      // タブ  
      TabRow(selectedTabIndex = selectedTab) {  
        tabTitles.forEachIndexed { index, title ->  
          Tab(selected = selectedTab == index, onClick = { selectedTab = index }) {  
            Text(title)  
          }  
        }  
      }  
    }  
  }  
}  
  
@Preview(showBackground = true)  
@Composable  
fun DefaultPreview() {  
  AppContent()  
}  

このコードは、上記のUIコンポーネントを組み合わせて、一つのアクティビティ内で動作するサンプルです。各コンポーネントの状態管理にはrememberとmutableStateOfを使用しています。この例を基に、さらに複雑なUIやビジネスロジックを組み込むことができます。

含めていただいた要素は、画面上、どのように配置されているのですか?

User
含めていただいた要素は、画面上、どのように配置されているのですか?

ChatGPT
このコード例では、UIコンポーネントは縦方向に配置されています。Columnコンポーザブルを使用して各要素を縦に並べているため、画面上の配置は上から下へと順になります。具体的には以下のような配置になります:

TopAppBar: 画面の最上部に位置し、アプリケーションのタイトル「Jetpack Compose UI」を表示します。

TextField (テキストボックス): ユーザーがテキストを入力できるフィールドで、「テキスト入力」というラベルがついています。TopAppBarの直下に位置します。

Checkbox: チェックボックスが続き、ユーザーが選択/非選択を切り替えることができます。テキストボックスの下にあります。

RadioButton: 2つのラジオボタンが続き、「オプション1」と「オプション2」から選択できます。これらは縦に並んでおり、チェックボックスの下に位置します。

LazyRow: 横スクロール可能なリストで、「Item 1」、「Item 2」、「Item 3」というテキストが含まれるカードが表示されます。ラジオボタンの下に配置されています。

TabRow: 2つのタブ、「Tab 1」と「Tab 2」が含まれており、ユーザーはこれらのタブ間を切り替えることができます。LazyRowの下、画面の下部近くに配置されています。

このような配置は、Columnコンテナを使用することで実現されており、各コンポーネントはModifier.paddingなどのモディファイアを使って適切な間隔を持たせて配置されています。この配置は、基本的なアプリケーションのレイアウトを構築する際の一例であり、さらに複雑なレイアウトやカスタマイズが必要な場合は、追加のコンポーザブル関数やモディファイアを組み合わせて使用することができます。

全体としては、1列で、要素は縦に配置されるのですね。一方、タブは、全体として1行で、横に配置されるのですね。イマイチ直観的でなくて、勘違いしていました。

User
ありがとうございます。

全体としては、1列で、要素は縦に配置されるのですね。一方、タブは、全体として1行で、横に配置されるのですね。イマイチ直観的でなくて、勘違いしていました。

ChatGPT
はい、その通りです。全体のレイアウトはColumnコンポーザブルを使用しているため、要素が縦に一列で配置されます。それぞれのコンポーネント(テキストフィールド、チェックボックス、ラジオボタン、LazyRow)は、この縦の列の中で順に配置されています。

一方で、TabRowコンポーザブルは内部的にタブを横に並べて表示します。タブ自体はTabRowの中で横に一列で配置され、ユーザーはこれらのタブ間を切り替えることができます。タブ内のコンテンツは選択されたタブに応じて変わりますが、このサンプルコードではタブの切り替えに伴うコンテンツの変化は実装されていません。

確かに、特に初めてJetpack Composeを使用する場合、UIのコンポーネントがどのように画面上に配置されるかは直感的に理解しにくいかもしれません。しかし、実際にコードを書き、プレビューを見ながら試してみることで、徐々に理解が深まっていくと思います。また、ColumnやRowなどのレイアウトコンポーザブルを使って、様々な配置を試すことが可能です。これらの基本的なコンポーザブルを組み合わせることで、非常に柔軟なUIを作成できます。

そういえば、CheckBoxとRadioButtonはグループ化しなくて良いのですか?

User
そういえば、CheckBoxとRadioButtonはグループ化しなくて良いのですか?

ChatGPT
Jetpack ComposeでCheckboxやRadioButtonを使用する際、これらのコンポーネントは自動的にグループ化されません。それぞれの状態管理は個別に行う必要があります。特にRadioButtonの場合、複数の選択肢の中から一つだけを選択可能にするためには、選択された項目を追跡する状態を持ち、各RadioButtonのselectedプロパティを適切に更新する必要があります。

RadioButtonのグループ化
RadioButtonをグループ化するためには、共通の状態(例えば、選択されたオプション)をRadioButtonコンポーネント群で共有し、各RadioButtonが選択されたときにこの状態を更新するようにします。以下は、RadioButtonをグループ化する方法の一例です。

val radioOptions = listOf("オプション1", "オプション2")  
var selectedOption by remember { mutableStateOf(radioOptions[0]) } // 初期選択状態  
  
Column {  
  radioOptions.forEach { text ->  
    Row(modifier = Modifier  
      .fillMaxWidth()  
      .clickable(onClick = { selectedOption = text })) {  
      RadioButton(  
        selected = text == selectedOption,  
        onClick = { selectedOption = text }  
      )  
      Text(text = text, modifier = Modifier.padding(start = 8.dp))  
    }  
  }  
}  

このコードでは、selectedOptionを使って現在選択されているオプションを追跡しています。各RadioButtonのonClickイベントがトリガーされたときにselectedOptionを更新し、これにより選択されたRadioButtonだけが選択状態になり、他は非選択状態になります。

Checkboxの状態管理
Checkboxは各項目が独立しており、通常は個別のチェック状態を持ちます。しかし、複数のCheckboxを論理的にグループ化して、特定のビジネスロジックに従ってチェック状態を制御することも可能です。例えば、ある条件下で特定のCheckbox群を全てチェックする、あるいは全て未チェックにするといった操作です。この場合も、各Checkboxの状態を追跡し、適切に更新するロジックを実装する必要があります。

Jetpack Composeでは、コンポーネント間で状態を共有することで、このような複雑なUIの振る舞いを実現できます。

グループ化は、コードで共有される変数で管理するのが通常なのですね。よく分かりました。

User
グループ化は、コードで共有される変数で管理するのが通常なのですね。よく分かりました。

ChatGPT
はい、正確にその通りです。Jetpack Composeでは、コンポーネント間で状態を共有することにより、グループ化やその他の複雑なUIパターンを管理します。状態管理はComposeの核心的な概念の一つで、rememberやmutableStateOfなどの機能を使って実現されます。これにより、UIの異なる部分が同一のデータソースに基づいて反応的に更新されるようになります。

特に、ラジオボタンのように複数の選択肢の中から一つだけを選択するUIコンポーネントでは、選択された値を追跡する共有状態を持つことで、その選択肢群を効果的にグループ化することができます。この共有状態は、ユーザーのインタラクションに応じて更新され、Composeの再構成システムが変更を検出してUIを適切に更新します。

このアプローチは、UIの一貫性を保ちつつ、コードの可読性と保守性を向上させるために重要です。Composeを使用する際には、このような状態管理のパターンを理解し、適切に利用することが、効率的で反応的なアプリケーションを構築する鍵となります。

LazyRowのitemsについてですが、RecyclerViewのようなAdapterを実装したり出来るのですか?

User
LazyRowのitemsについてですが、RecyclerViewのようなAdapterを実装したり出来るのですか?

ChatGPT
Jetpack Composeでは、RecyclerViewのAdapterパターンとは異なるアプローチを取ります。ComposeのLazyRowやLazyColumnは、データのリストを直接受け取り、それを表示するための宣言的なUI定義を提供します。各項目のレンダリングは、items関数(またはitemsIndexedなどの変種)を使用して実装されます。この関数内で、リスト内の各アイテムに対してComposable関数を呼び出すことにより、UIのレンダリングを定義します。

RecyclerViewのAdapterのように別のクラスを定義してリストアイテムのUIを管理する必要はありません。代わりに、LazyRowやLazyColumnのitems関数内で直接UIを定義します。

以下に、LazyRowでリストアイテムを表示する例を示します。この例では、単純なリストアイテムを表示していますが、実際には任意の複雑なUIをこの中で構築することが可能です。

LazyRow {  
  // 仮のデータリスト  
  val itemList = listOf("Item 1", "Item 2", "Item 3")  
  
  items(itemList) { item ->  
    // ここで各アイテムのUIを定義  
    Card(modifier = Modifier.padding(8.dp), elevation = 4.dp) {  
      Text(text = item, modifier = Modifier.padding(16.dp))  
    }  
  }  
}  

このitems関数は、指定されたリストの各アイテムに対してラムダ式を実行します。このラムダ式の中で、各アイテムのUIをComposable関数を使って定義します。これにより、RecyclerViewのAdapterと同様の柔軟性と再利用性を、より宣言的で簡潔なコードで実現できます。

User
ありがとうございました。おかげでかなり見通しが良くなりました。

ChatGPT
喜んでお手伝いできてうれしいです! 見通しが良くなったと聞いて安心しました。Jetpack Composeを使った開発は、宣言的UIの強力な機能を提供し、Androidアプリ開発をより簡潔で直感的にします。もし他に質問があれば、どんなことでもお気軽にお尋ねください。開発の成功をお祈りしています!