ビューで Android アプリを作成する

1. 始める前に

はじめに

ここまで、Compose で Android アプリを作成する方法を学んできました。それは良いことです。Compose は、開発プロセスを簡素化できる非常に強力なツールです。しかし、Android アプリは必ずしも宣言型 UI で作成されているわけではありません。Android アプリの歴史において、Compose はごく最近のツールです。もともと、Android UI はビューを使用して作成されていました。そのため、Android デベロッパーとしての歩みを進めていくと、ビューに遭遇する可能性が高くなります。この Codelab では、Compose 以前の Android アプリの作成方法の基礎(XML、ビュー、ビュー バインディング、Fragment)を学びます。

前提条件:

  • ユニット 7 で「Compose での Android の基礎」コースワークを完了していること。

必要なもの

  • Android Studio がインストールされた、インターネットに接続できるパソコン。
  • デバイスまたはエミュレータ。
  • Juice Tracker アプリのスターター コード。

作成するアプリの概要

この Codelab では、Juice Tracker アプリを完成させます。このアプリは、詳細なアイテムで構成されるリストを作成することで、注目のジュースを追跡できるというものです。Fragment と XML を追加して変更し、UI とスターター コードを完成させます。具体的には、UI と関連するロジックや Navigation を含む、新しいジュースを作るための入力フォームを作成します。その結果、独自のジュースを追加できる空のリストを持ったアプリができあがります。

d6dc43171ae62047.png 87b2ca7b49e814cb.png 2d630489477e216e.png

2. スターター コードを取得する

  1. Android Studio で basic-android-kotlin-compose-training-juice-tracker フォルダを開きます。
  2. Android Studio で Juice Tracker アプリコードを開きます。

3. レイアウトを作成する

Views でアプリを作成する場合は、Layout 内に UI を構築します。Layout は通常、XML を使用して宣言します。宣言した XML レイアウト ファイルは、リソース ディレクトリの [res] > [layout] に配置されます。レイアウトには、UI を構成するコンポーネントが含まれています。これらのコンポーネントが View と呼ばれるものです。XML 構文は、タグ、要素、属性で構成されています。XML 構文の詳細については、Android の XML レイアウトを作成するの Codelab をご覧ください。

このセクションでは [Type of juice] 入力ダイアログの XML レイアウトを作成します。

87b2ca7b49e814cb.png

  1. [main] > [res] > [layout] ディレクトリに、fragment_entry_dialog という名前の新しいレイアウト リソース ファイルを作成します。

Android Studio のプロジェクト ペインのコンテキスト ペインが開き、レイアウト リソース ファイルを作成するオプションが表示される。

6adb279d6e74ab13.png

fragment_entry_dialog.xml レイアウトには、アプリがユーザーに表示する UI コンポーネントが含まれています。

ルート要素ConstraintLayout であることに注目してください。このタイプのレイアウトは、制約を使用してビューの位置とサイズを柔軟に調整できる ViewGroup です。ViewGroup は、他の View(子または子 View)を含む View の一種です。次のステップでは、このトピックについて詳しく説明しますが、ConstraintLayout の詳細については、ConstraintLayout でレスポンシブ UI を作成するをご覧ください。

  1. ファイルを作成したら、ConstraintLayout でアプリの名前空間を定義します。

fragment_entry_dialog.xml

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
  1. ConstraintLayout に次の Guidelines を追加します。

fragment_entry_dialog.xml

<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_left"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="vertical"
   app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_middle"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="vertical"
   app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_top"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="horizontal"
   app:layout_constraintGuide_begin="16dp" />

これらの Guideline は、他のビューのパディングとして機能します。Guidelines では「Type of juice」というヘッダー テキストが制約されています。

  1. TextView 要素を作成します。この TextView は、詳細フラグメントのタイトルを表します。

110cad4ae809e600.png

  1. TextViewidheader_title に設定します。
  2. layout_width0dp に設定します。最終的には、レイアウト制約がこの TextView の幅を定義します。そのため、幅を定義しても、UI の描画中に不要な計算処理が発生するだけです。0dp の幅を定義すれば余分な計算処理がなくなります。
  3. TextView text 属性を @string/juice_type に設定します。
  4. textAppearance@style/TextAppearance.MaterialComponents.Headline5 に設定します。

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

最後に、制約を定義する必要があります。寸法を制約として使用する Guideline とは異なり、このガイドライン自体はこの TextView を制約します。そのために、ビューを制約する Guideline の ID を参照します。

  1. ヘッダーの上部を guideline_top の下部に制約します。

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
   app:layout_constraintTop_toBottomOf="@+id/guideline_top" />
  1. 終点を guideline_middle の始点に制約し、始点を guideline_left の始点に制約して、TextView プレースメントを完成させます。ビューの制約方法は、UI の表示方法によって異なります。

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
   app:layout_constraintTop_toBottomOf="@+id/guideline_top"
   app:layout_constraintEnd_toStartOf="@+id/guideline_middle"
   app:layout_constraintStart_toStartOf="@+id/guideline_left" />

スクリーンショットに基づいて、残りの UI を作成してください。完成した fragment_entry_dialog.xml ファイルは、解答にあります。

4. ビューで Fragment を作成する

Compose では、Kotlin または Java を使用してレイアウトを宣言的に作成します。別の「画面」にアクセスするには、通常は同じアクティビティ内にある、別のコンポーザブルに移動します。ビューでアプリを作成する場合は、XML レイアウトをホストする Fragment が、Composable の「画面」のコンセプトに代わるものとなります。

このセクションでは、fragment_entry_dialog レイアウトをホストして UI にデータを提供する Fragment を作成します。

  1. juicetracker パッケージで、EntryDialogFragment という名前の新しいクラスを作成します。
  2. EntryDialogFragmentBottomSheetDialogFragment を拡張するようにします。

EntryDialogFragment.kt

import com.google.android.material.bottomsheet.BottomSheetDialogFragment

class EntryDialogFragment : BottomSheetDialogFragment() {
}

DialogFragment は、フローティング ダイアログを表示する Fragment です。BottomSheetDialogFragmentDialogFragment クラスを継承していますが、画面下部に固定された画面幅のシートを表示します。この方法は、前述の設計に合わせたものです。

  1. プロジェクトを再ビルドします。これにより、fragment_entry_dialog レイアウトに基づくビュー バインディング ファイルが自動生成されます。ビュー バインディングを使用すると、XML で宣言された View にアクセスして操作できます。詳細については、ビュー バインディングのドキュメントをご覧ください。
  2. EntryDialogFragment クラスで、onCreateView() 関数を実装します。名前が示すように、この関数はこの FragmentView を作成します。

EntryDialogFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   return super.onCreateView(inflater, container, savedInstanceState)
}

onCreateView() 関数は View を返しますが、現時点では有用な View を返しません。

  1. super.onCreateView() を返す代わりに、FragmentEntryDialogViewBinding のインフレートで生成された View を返します。

EntryDialogFragment.kt

import com.example.juicetracker.databinding.FragmentEntryDialogBinding

override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
  1. onCreateView() 関数外、かつ EntryDialogFragment クラス内で、EntryViewModel のインスタンスを作成します。
  2. onViewCreated() 関数を実装します。

ビュー バインディングをインフレートすると、レイアウト内の View にアクセスして変更できるようになります。onViewCreated() メソッドが、このライフサイクルで onCreateView() の後に呼び出されます。onViewCreated() メソッドは、レイアウト内の View にアクセスして変更する場合におすすめの場所です。

  1. FragmentEntryDialogBindingbind() メソッドを呼び出して、ビュー バインディングのインスタンスを作成します。

この時点で、コードは次の例のようになります。

EntryDialogFragment.kt

import androidx.fragment.app.viewModels
import com.example.juicetracker.ui.AppViewModelProvider
import com.example.juicetracker.ui.EntryViewModel

class EntryDialogFragment : BottomSheetDialogFragment() {

   private val entryViewModel by viewModels<EntryViewModel> { AppViewModelProvider.Factory }

   override fun onCreateView(
       inflater: LayoutInflater,
       container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View {
       return FragmentEntryDialogBinding.inflate(inflater, container, false).root
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
    }
}

バインディングを使用してビューにアクセスし、設定できます。たとえば、setText() メソッドで TextView を設定できます。

binding.name.setText("Apple juice")

入力ダイアログの UI は、ユーザーが新しいアイテムを作成する場所ですが、既存のアイテムを変更するためにも使用できます。そのため、Fragment はクリックされたアイテムを取得する必要があります。Navigation コンポーネントにより、EntryDialogFragment への移動とクリックされたアイテムの取得が容易になります。

EntryDialogFragment は未完成ですが、心配無用です。次のセクションに進んで、View を使用するアプリで Navigation コンポーネントを使用する方法を学びましょう。

5. Navigation コンポーネントを変更する

このセクションでは、入力ダイアログを起動するためと、アイテムを取得するため(該当する場合)に、Navigation コンポーネントを使用します。

Compose では、呼び出すだけでさまざまなコンポーザブルをレンダリングする機会があります。しかし、Fragment の動作は異なります。Navigation コンポーネントは、Fragment の「デスティネーション」を調整し、Fragment 間とそれに含まれるビューの間を簡単に移動できるようにします。

Navigation コンポーネントを使用して、EntryDialogFragment へのナビゲーションを調整します。

  1. nav_graph.xml ファイルを開き、[Design] タブが選択されていることを確認します。783cb5d7ff0ba127.png
  2. 93401bf098936c15.png アイコンをクリックして、新しいデスティネーションを追加します。

d5410c90e408b973.png

  1. EntryDialogFragment デスティネーションを選択します。このアクションは、nav graph で entryDialogFragment を宣言し、ナビゲーション アクションからアクセスできるようにするものです。

418feed425072ea4.png

TrackerFragment から EntryDialogFragment を起動する必要があります。そのためには、このタスクをナビゲーション アクションで実行する必要があります。

  1. trackerFragment の上にカーソルを移動します。灰色の点を選択して、線を entryDialogFragment にドラッグします。85decb6fcddec713.png
  2. nav_graph デザインビューでは、デスティネーションを選択し、[Arguments] プルダウンの横にある a0d73140a20e4348.png アイコンをクリックして、デスティネーションの引数を宣言できます。この機能を使用して、Long 型の itemId 引数を entryDialogFragment に追加します。デフォルト値は 0L です。

555cf791f64f62b8.png

840105bd52f300f7.png

TrackerFragmentJuice アイテムのリストを保持します。これらのアイテムのいずれかをクリックすると、EntryDialogFragment が起動します。

  1. プロジェクトを再ビルドします。EntryDialogFragmentitemId 引数にアクセスできるようになりました。

6. Fragment を完成させる

ナビゲーション引数のデータを使用して、入力ダイアログを完成させます。

  1. EntryDialogFragmentonViewCreated() メソッドで navArgs() を取得します。
  2. navArgs() から itemId を取得します。
  3. ViewModel を使用して、新規または変更されたジュースを保存する saveButton を実装します。

入力ダイアログ UI で、デフォルトの色が赤であることを思い出してください。現時点では、これをプレースホルダとして渡します。

saveJuice() を呼び出すときに引数から取得したアイテム ID を渡します。

EntryDialogFragment.kt

import androidx.navigation.fragment.navArgs
import com.example.juicetracker.data.JuiceColor

class EntryDialogFragment : BottomSheetDialogFragment() {

   //...
   var selectedColor: JuiceColor = JuiceColor.Red

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
        val args: EntryDialogFragmentArgs by navArgs()
        val juiceId = args.itemId

        binding.saveButton.setOnClickListener {
           entryViewModel.saveJuice(
               juiceId,
               binding.name.text.toString(),
               binding.description.text.toString(),
               selectedColor.name,
               binding.ratingBar.rating.toInt()
           )
        }
    }
}
  1. データを保存したら、dismiss() メソッドでダイアログを閉じます。

EntryDialogFragment.kt

class EntryDialogFragment : BottomSheetDialogFragment() {

    //...
    var selectedColor: JuiceColor = JuiceColor.Red
    //...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
        val args: EntryDialogFragmentArgs by navArgs()
        binding.saveButton.setOnClickListener {
           entryViewModel.saveJuice(
               juiceId,
               binding.name.text.toString(),
               binding.description.text.toString(),
               selectedColor.name,
               binding.ratingBar.rating.toInt()
           )
           dismiss()
        }
    }
}

なお、上記のコードでは EntryDialogFragment は完成していません。まだ実装する必要のあることが多数あります。既存の Juice データをフィールドに入力する(該当する場合)、colorSpinner から色を選ぶ、cancelButton を実装などです。ただし、このコードは Fragment に固有なものではなく、自身で実装できるはずです。残りの機能を実装しましょう。最後の手段として、この Codelab の解答コードを参照することもできます。

7. 入力ダイアログの起動

最後のタスクでは、Navigation コンポーネントを使用して入力ダイアログを起動します。ユーザーがフローティング アクション ボタン(FAB)をクリックしたときに、入力ダイアログを起動する必要があります。また、ユーザーがアイテムをクリックしたときに起動して、対応する ID を渡す必要があります。

  1. FAB の onClickListener() で、nav コントローラの navigate() を呼び出します。

TrackerFragment.kt

import androidx.navigation.findNavController

//...

binding.fab.setOnClickListener { fabView ->
   fabView.findNavController().navigate(
   )
}

//...
  1. navigate 関数に、トラッカーから入力ダイアログに移動するアクションを渡します。

TrackerFragment.kt

//...

binding.fab.setOnClickListener { fabView ->
   fabView.findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment()
   )
}

//...
  1. このラムダ本体でのアクションを JuiceListAdapteronEdit() メソッドについても繰り返しますが、今回は Juiceid を渡します。

TrackerFragment.kt

//...

onEdit = { drink ->
   findNavController().navigate(
       TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment(drink.id)
   )
},

//...

8. 解答コードを取得する

この Codelab の完成したコードをダウンロードするには、以下の git コマンドを使用します。

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。

解答コードを確認する場合は、GitHub で表示します