前の Codelab で学んだように、マテリアルは Google が作成したデザイン システムであり、ユーザー インターフェース設計のベスト プラクティスをサポートするガイドライン、コンポーネント、ツールで構成されています。この Codelab では、チップ計算アプリ(前の Codelabで作成)を更新して、次の最終スクリーンショットに示されている洗練されたユーザー エクスペリエンスを実現します。また、ユーザー エクスペリエンスができるだけスムーズなものになるように、いくつかの追加のシナリオでアプリをテストします。
前提条件
- 一般的な UI ウィジェット(
TextView
、ImageView
、Button
、EditText
、RadioButton
、RadioGroup
、Switch
など)に精通している ConstraintLayout
と、制約を設定して子ビューを配置する処理に精通している- XML レイアウトの変更に慣れている
- ビットマップ画像とベクター型ドローアブルの違いを理解している
- テーマにテーマ属性を設定できる
- デバイスでダークテーマを有効にできる
- アプリの
build.gradle
ファイルでプロジェクトの依存関係を変更した経験がある
学習内容
- アプリでマテリアル デザイン コンポーネントを使用する方法
- Image Asset Studio からマテリアル アイコンをインポートしてアプリで使用する方法
- 新しいスタイルを作成し、適用する方法
- 色以外のテーマ属性を設定する方法
作成するアプリの概要
- 推奨される UI のベスト プラクティスに基づく洗練されたチップ計算アプリ
必要なもの
- Android Studio がインストールされているパソコン
- 前の Codelab で作成した Tip Time アプリのコード
前の Codelab では、Tip Time アプリを作成しました。このアプリは、チップをカスタマイズするオプションを含むチップ計算アプリです。現在、アプリの UI は以下のスクリーンショットのように表示されます。機能は問題ありませんが、プロトタイプのように見えます。フィールドが見やすく配置されていません。スタイルと間隔の一貫性、およびマテリアル デザイン コンポーネントの使用という観点から見ると、明らかに改善の余地があります。
マテリアル コンポーネントは、アプリにマテリアル スタイル設定を簡単に実装できる一般的な UI ウィジェットです。このドキュメントでは、マテリアル デザイン コンポーネントの使用方法とカスタマイズ方法について説明します。コンポーネントにはそれぞれ一般的なマテリアル デザイン ガイドラインがあり、Android で利用可能なコンポーネントについては Android プラットフォーム固有のガイダンスがあります。選択しているプラットフォームにコンポーネントが存在しない場合は、ラベル付きの図を参照して、コンポーネントを再作成するための十分な情報を得ることができます。
マテリアル コンポーネントを使用すると、作成するアプリの動作とユーザーのデバイス上の他のアプリの動作の一貫性が向上します。つまり、あるアプリで習得した UI パターンを別のアプリでも使用できます。そのため、ユーザーは新しいアプリの使い方をより短時間で習得できます。可能な場合は、非マテリアル ウィジェットではなく、マテリアル コンポーネントを使用することをおすすめします。また、次のタスクで学習するように、マテリアル コンポーネントはより柔軟なカスタマイズが可能です。
マテリアル デザイン コンポーネント(MDC)ライブラリをプロジェクトの依存関係として含める必要があります。Android Studio 4.1 以降を使用している場合は、デフォルトで次の行がプロジェクトに存在するはずです。アプリの build.gradle
ファイルで、この依存関係が最新バージョンのライブラリに含まれていることを確認します。詳しくは、マテリアルのサイトのスタートガイド ページをご覧ください。
app/build.gradle
dependencies {
...
implementation 'com.google.android.material:material:<version>'
}
テキスト フィールド
現在、チップ計算アプリでは、レイアウトの上部に、サービス料金を表す EditText
フィールドが表示されます。この EditText
フィールドは機能に問題はありませんが、テキスト フィールドの外観と動作に関する最近のマテリアル デザイン ガイドラインに従っていません。
新しいコンポーネントを使用する際は、まずマテリアルのサイトでそのコンポーネントについて確認してください。テキスト フィールドに関するガイドによると、テキスト フィールドには次の 2 つのタイプがあります。
塗りつぶしテキスト フィールド
枠線付きテキスト フィールド
上記のテキスト フィールドを作成するには、MDC ライブラリの TextInputEditText
を包含する TextInputLayout
を使用します。マテリアル テキスト フィールドは、次のように簡単にカスタマイズできます。
- 入力テキストまたは常時表示されるラベルを表示する
- テキスト フィールド内にアイコンを表示する
- ヘルパーまたはエラー メッセージを表示する
この Codelab の最初のタスクでは、サービス料金の EditText
を、TextInputLayout
と TextInputEditText
で構成されるマテリアル テキスト フィールドに置き換えます。
- Android Studio で Tip Time アプリを開いたまま、
activity_main.xml
レイアウト ファイルに移動します。このファイルにはチップ計算レイアウトのConstraintLayout
が含まれている必要があります。 - マテリアル テキスト フィールドに対応する XML のサンプルについては、Android のガイダンスに戻ってテキスト フィールドをご覧ください。次のようなスニペットを確認できます。
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
- このサンプルを確認した後、マテリアル テキスト フィールドを
ConstraintLayout
の最初の子として(EditText
フィールドの前に)挿入します。EditText
フィールドは、この後のステップで削除します。
Android Studio でこれを入力する場合は、オートコンプリートを使用して簡単に入力できます。または、ドキュメントのページからサンプル XML をコピーして、次のようにレイアウトに貼り付けることもできます。TextInputLayout
に子ビュー TextInputEditText
があることに注目してください。実際に変更された XML の行に注意を向けられるように、省略記号(...)を使用してスニペットの表示を簡潔にしています。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<EditText
android:id="@+id/cost_of_service" ... />
...
TextInputLayout
要素に誤りがあることにお気づきでしょうか。このビューは、まだ親 ConstraintLayout
内で適切に制限されていません。また、文字列リソースが認識されていません。これらの誤りはこの後のステップで修正します。
- 親
ConstraintLayout
内にテキスト フィールドを適切に配置するには、テキスト フィールドに縦方向と横方向の制約を追加します。まだEditText
を削除していないため、次の属性をEditText
から切り取って貼り付け、TextInputLayout
に配置します: 制約、リソース ID(cost_of_service
)、レイアウトの幅(160dp
)、レイアウトの高さ(wrap_content
)、チップのテキスト(@string/cost_of_service
)。
...
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:hint="@string/cost_of_service"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
...
cost_of_service
ID が EditText
のリソース ID と同じであるというエラーが表示される場合がありますが、このエラーは今のところ無視してかまいません(EditText
はこの後のステップで削除します)。
- 次に、
TextInputEditText
要素に適切な属性がすべて設定されていることを確認します。EditText
からの入力タイプを切り取ってTextInputEditText.
に貼り付け、TextInputEditText
要素のリソース ID をcost_of_service_edit_text.
に変更します。
<com.google.android.material.textfield.TextInputLayout ... >
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/cost_of_service_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
match_parent
の幅と wrap_content
の高さはそのままでかまいません。match_parent
の幅を設定すると、TextInputEditText
の幅はその親である TextInputLayout
と同じ(160dp
)になります。
- 以上で
EditText
からすべての関連情報をコピーしたので、次はレイアウトからEditText
を削除します。 - レイアウトのデザインビューに、次のようなプレビューが表示されます。サービス料金フィールドがマテリアル テキスト フィールドらしく表示されています。
MainActivity.kt
ファイルのcalculateTip()
メソッドに誤りがあるため、まだアプリを実行することはできません。以前の Codelab で、プロジェクトでビュー バインディングが有効になっていると、リソース ID 名に基づいてバインディング オブジェクト内にプロパティが作成されたことを思い出してください。サービス料金の取得元フィールドが XML レイアウトで変更されたため、それに応じて Kotlin コードを更新する必要があります。
リソース ID が cost_of_service_edit_text
の TextInputEditText
要素から、ユーザー入力を取得します。MainActivity
内で、binding.costOfServiceEditText
を使用して、その中に格納されているテキスト文字列にアクセスします。calculateTip()
メソッド残りの部分は同じままでかまいません。
private fun calculateTip() {
// Get the decimal value from the cost of service text field
val stringInTextField = binding.costOfServiceEditText.text.toString()
val cost = stringInTextField.toDoubleOrNull()
...
}
- おつかれさまでした。アプリを実行して、前と同様に動作することを確認してください。入力すると、[Cost of Servive] ラベルが入力の上に表示されます。以前と同様、想定どおりにチップが計算されるはずです。
スイッチ
マテリアル デザイン ガイドラインには、スイッチに関するガイダンスもあります。スイッチとは、設定のオン / オフを切り替えられるウィジェットです。
- マテリアル スイッチについては、Android のガイダンスをご確認ください。スイッチ用のマテリアル スタイル設定を提供する
SwitchMaterial
ウィジェット(MDC ライブラリにあります)について学習できます。引き続きガイドをスクロールすると、サンプル XML が表示されます。 SwitchMaterial
を使用するには、レイアウト内でSwitchMaterial
を明示的に指定し、完全修飾パス名を使用する必要があります。
activity_main.xml
レイアウト内で、XML タグを Switch
から com.google.android.material.switchmaterial.SwitchMaterial.
に変更します。
...
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/round_up_switch"
android:layout_width="0dp"
android:layout_height="wrap_content" ... />
...
- アプリを実行して、前と同様に動作することを確認します。アプリに目に見える変化はありません。ただし、Android プラットフォームの
Switch
の代わりに MDC ライブラリのSwitchMaterial
を使用するメリットは、SwitchMaterial
用のライブラリの実装が更新されたとき(例: マテリアル デザイン ガイドラインが変更されたとき)に、アプリを一切変更せずに、更新されたウィジェットを無料で利用できることです。これは、アプリを将来も有効に使い続けるために役立ちます。
ここまでは、すぐに使えるマテリアル デザイン コンポーネントの使用が UI にもたらすメリットと、アプリをマテリアル ガイドラインに近づける方法に関する 2 つの例を見てきました。Android で提供されるその他のマテリアル デザイン コンポーネントについては、こちらのサイトでいつでも確認できます。
アイコンとは、目的の機能を視覚的に伝達することで、ユーザーがユーザー インターフェースを理解することを助けるシンボルです。多くの場合、アイコンはユーザーがよく知っていると思われる現実の物体から着想を得ています。ほとんどのアイコンのデザインは、ユーザーが即座に理解できる最小限のレベルまで簡略化されています。たとえば、現実の鉛筆は字を書くために使用されるので、通常、鉛筆のアイコンはアイテムの作成、追加、編集を表します。
写真撮影: Angelina Litvin(出典: Unsplash) |
フロッピー ディスク アイコンのように、かつて実際にあった物体に関連するアイコンもあります。このアイコンは、ファイルやデータベース レコードの保存を表すためによく使われます。しかし、フロッピー ディスクは 1970 年代は一般的でしたが、2000 年以降はほとんど使われなくなりました。それにもかかわらずこのアイコンが今でも使われているということは、強力な視覚的デザインが物質的形態としての寿命を越えていかに長く生き続けるかを物語っています。
写真撮影: Vincent Botta(出典: Unsplash) |
アプリ内のアイコンの表現
アプリ内のアイコンでは、画面密度ごとに異なるバージョンのビットマップ画像を使用する代わりに、ベクター型ドローアブルを使用することをおすすめします。ベクター型ドローアブルは、画像を構成する実際のピクセルを保存するのではなく、画像の作成方法を指示する XML ファイルとして表現されます。ベクター型ドローアブルは、画質の低下とファイルサイズの増大を招くことなく、拡大または縮小できます。
提供されるアイコン
マテリアル デザインでは、多くのニーズに対応した多数のアイコンが一般的なカテゴリ別に用意されています。アイコンの一覧をご覧ください。
また、アイコンは 5 つのテーマ(塗りつぶし、枠線付き、角丸、ツートン、鋭角)のいずれかを使用して描画できます。色を使用して色合いを調整することもできます。
塗りつぶし | 枠線付き | 角丸 | ツートン | 鋭角 |
アイコンを追加する
このタスクでは、次の 3 つのベクター型ドローアブル アイコンをアプリに追加します。
- サービス料金テキスト フィールドの横のアイコン
- サービスに関する質問の横のアイコン
- チップの端数切り上げプロンプトの横のアイコン
アプリの最終バージョンのスクリーンショットは以下のとおりです。アイコンを追加した後、アイコンの配置に合わせてレイアウトを調整します。フィールドと計算ボタンが右にずらされ、アイコンが追加されていることに注目してください。
ベクター型ドローアブル アセットを追加する
これらのアイコンは、Android Studio の Asset Studio から直接、ベクター型ドローアブルとして作成できます。
- アプリ ウィンドウの左側にある [Resource Manager] タブを開きます。
- プラス(+)アイコンをクリックして [Vector Asset] を選択します。
- [Asset Type] で [Clip Art] ラジオボタンがオンになっていることを確認します。
- [Clip Art] の横のボタンをクリックして、別のクリップアート画像を選択します。表示されたプロンプトで、ウィンドウに「call made」と入力します。この矢印アイコンは、チップの端数切り上げオプションに使用します。このアイコンを選択して [OK] をクリックします。
- アイコンの名前を
ic_round_up
に変更します(アイコン ファイルの名前には、接頭辞 ic_ を使用することをおすすめします)。[**Size*] は 24 dp x 24 dp、[**Color**] は黒(000000)のままでかまいません。 - [Next] をクリックします。
- デフォルトのディレクトリの場所を受け入れて [Finish] をクリックします。
- 他の 2 つのアイコンについて、ステップ 2~7 を繰り返します。
- サービスに関する質問のアイコン:「room service」アイコンを検索し、
ic_service
として保存します。 - サービス料金のアイコン:「store」アイコンを検索し、
ic_store
として保存します。
- 完了すると、Resource Manager が以下のスクリーンショットのように表示されます。また、これら 3 つのベクター型ドローアブル(
ic_round_up
、ic_service
、ic_store
)がres/drawable
フォルダ内に表示されます。
古い Android バージョンをサポートする
たった今ベクター型ドローアブルをアプリに追加しましたが、Android プラットフォームでのベクター型ドローアブルのサポートは、Android 5.0(API レベル 21)未満では追加されていないことに注意する必要があります。
プロジェクトを設定した方法に基づき、Tip Time アプリの最小 SDK バージョンは API 19 です。つまり、このアプリを実行できるのは、Android プラットフォーム バージョン 19 以上を搭載した Android デバイスです。
それより古いバージョンの Android でアプリを動作させる(いわゆる下位互換性を維持する)には、アプリの build.gradle
ファイルに vectorDrawables
要素を追加します。そうすれば、API 21 未満のバージョンのプラットフォームでベクター型ドローアブルを使用できます。プロジェクトのビルド時に PNG に変換する必要はありません。詳しくは、こちらをご覧ください。
app/build.gradle
android {
defaultConfig {
...
vectorDrawables.useSupportLibrary = true
}
...
}
プロジェクトが適切に構成されていれば、これでアイコンをレイアウトに追加できるようになります。
アイコンと位置要素を挿入する
アプリ内にアイコンを表示するには、ImageViews
を使用します。最終的な UI は次のように表示されます。
activity_main.xml
レイアウトを開きます。- まず、サービス料金テキスト フィールドの横にストアのアイコンを配置します。
ConstraintLayout
の最初の子として、TextInputLayout
の前に新しいImageView
を挿入します。
<androidx.constraintlayout.widget.ConstraintLayout
...>
<ImageView
android:layout_width=""
android:layout_height=""
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
...
ic_store
アイコンを保持するImageView
に適切な属性を設定します。ID をicon_cost_of_service
に設定します。app:srcCompat
属性をドローアブル リソース@drawable/ic_store
に設定します。これにより、XML の該当行の横にアイコンのプレビューが表示されます。この画像は装飾目的でのみ使用するので、android:importantForAccessibility="no"
も設定します。
<ImageView
android:id="@+id/icon_cost_of_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_store" />
ビューがまだ制限されていないため、ImageView
にエラーがあるはずです。これは後で修正します。
- 2 つのステップで
icon_cost_of_service
の位置を調整します。最初に(このステップで)ImageView
に制約を追加し、次に(ステップ 5 で)その横にあるTextInputLayout
の制約を更新します。次の図は、制約の設定方法を示しています。
ImageView
で、その始端を親ビューの始端に制限します(app:layout_constraintStart_toStartOf="parent"
)。
アイコンは横にあるテキスト フィールドに対して縦方向の中央に表示されるため、この ImageView
の最上部(layout_constraintTop_toTopOf
)をテキスト フィールドの最上部に制限します。この ImageView
の最下部(layout_constraintBottom_toBottomOf
)をテキスト フィールドの最下部に制限します。テキスト フィールドを参照するには、リソース ID @id/cost_of_service
を使用します。デフォルトの動作では、2 つの制約が同じディメンション(最上部と最下部の制約など)でウィジェットに適用される場合、制約は均等に適用されます。その結果、アイコンはサービス料金フィールドに対して縦方向の中央に配置されます。
<ImageView
android:id="@+id/icon_cost_of_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_store"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/cost_of_service"
app:layout_constraintBottom_toBottomOf="@id/cost_of_service" />
デザインビューでは、アイコンとテキスト フィールドが依然として重なり合っています。これは次のステップで修正します。
- アイコンを追加する前、テキスト フィールドは親の開始位置に配置されていました。これを右にずらす必要があります。
icon_cost_of_service
に関するcost_of_service
テキスト フィールドの制約を更新します。
TextInputLayout
の始端を ImageView
(@id/icon_cost_of_service
)の終端に制限する必要があります。2 つのビューの間隔を広げるには、TextInputLayout
に開始余白 16dp
を追加します。
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
...
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/icon_cost_of_service">
<com.google.android.material.textfield.TextInputEditText ... />
</com.google.android.material.textfield.TextInputLayout>
以上の変更が完了したら、アイコンをテキスト フィールドの真横に配置する必要があります。
- 次に、[How was the service?] の
TextView
の横に、サービスを表すベルのアイコンを挿入します。ImageView
はConstraintLayout
内の任意の場所で宣言できますが、XML レイアウトで新しいImageView
をTextInputLayout
の後、service_question
TextView
の前に挿入すると、XML レイアウトが読みやすくなります。
新しい ImageView
には、@+id/icon_service_question
のリソース ID を割り当てます。ImageView
とサービスに関する質問の TextView
に適切な制約を設定します。
また、service_question TextView
に 16dp
の上余白を追加し、サービスに関する質問とその上にあるサービス料金テキスト フィールドの間にある縦の間隔を広げます。
...
<ImageView
android:id="@+id/icon_service_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_service"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/service_question"
app:layout_constraintBottom_toBottomOf="@id/service_question" />
<TextView
android:id="@+id/service_question"
...
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="@id/cost_of_service"
app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>
...
- この時点で、デザインビューは次のように表示されます。サービス料金フィールドとサービスに関する質問(およびそれぞれのアイコン)の配置は改善されましたが、今度はラジオボタンの位置がずれています。上にあるコンテンツと縦の位置が揃っていません。
- サービスに関する質問の下にあるラジオボタンを右にずらして、位置を調整します。つまり、
RadioGroup
制約を更新します。RadioGroup
の始端をservice_question
TextView
の始端に制限します。RadioGroup
の残りの属性はすべて同じままでかまいません。
...
<RadioGroup
android:id="@+id/tip_options"
...
app:layout_constraintStart_toStartOf="@id/service_question">
...
- 次に、[Round up tip?] スイッチの横にあるレイアウトに
ic_round_up
アイコンを追加します。これはご自分でトライしてください。行き詰ったときは、下記の XML が参考になります。新しいImageView
には、リソース IDicon_round_up
を割り当てることができます。 - レイアウト XML で、
RadioGroup
の後、SwitchMaterial
ウィジェットの前に、新しいImageView
を挿入します。 ImageView
にリソース IDicon_round_up
を割り当てて、srcCompat
をアイコン@drawable/ic_round_up
のドローアブルに設定します。ImageView
の開始位置を親の開始位置に制限し、また、SwitchMaterial
を基準としてアイコンを縦方向の中央に配置します。- アイコンの横に配置されるように
SwitchMaterial
を更新し、16dp
の開始余白を設定します。結果として生成される XML は、icon_round_up
およびround_up_switch
のようになります。
...
<ImageView
android:id="@+id/icon_round_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_round_up"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/round_up_switch"
app:layout_constraintBottom_toBottomOf="@id/round_up_switch" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/round_up_switch"
...
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/icon_round_up" />
...
- デザイン ビューは次のようになります。3 つのアイコンがすべて正しく配置されています。
- これをアプリの最終スクリーンショットと比べると、計算ボタンも、サービス料金フィールド、サービスに関する質問、ラジオボタンのオプション、チップの端数切り上げプロンプトと縦に揃うようにずらされていることがわかります。そのような配置を実現するには、計算ボタンの開始位置を
round_up_switch
の開始位置に制限します。また、計算ボタンとその上のスイッチの間に、8dp
の縦方向の余白を追加します。
...
<Button
android:id="@+id/calculate_button"
...
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="@id/round_up_switch" />
...
- 最後に重要な作業として、
TextView
に8dp
の上余白を追加し、tip_result
の位置を調整します。
...
<TextView
android:id="@+id/tip_result"
...
android:layout_marginTop="8dp" />
...
- たくさんのステップがありましたが、順を追って実施すれば大きな成果が得られます。レイアウト内に要素を正しく配置するには細部に十分な注意を払う必要がありますが、その結果として外観が大幅に改善されます。アプリを実行すると、以下のスクリーンショットのようになります。要素を縦に揃えて要素間の間隔を広げたことで、要素が密集していないすっきりしたレイアウトになりました。
作業はこれで終わりではありません。サービスに関する質問とチップ金額のフォントサイズと色が、ラジオボタンとスイッチのテキストと異なることにお気づきでしょうか。次のタスクでは、スタイルとテーマを使用して、この点についても一貫性を実現します。
スタイルとは、ウィジェットの単一のタイプに対応するビュー属性値のコレクションです。たとえば、TextView
スタイルで、フォントの色、フォントサイズ、背景色などを指定できます。これらの属性をスタイルに抽出すると、レイアウト内の複数のビューにスタイルを適用し、一元的に管理することが容易になります。
このタスクでは、最初にテキストビュー、ラジオボタン、スイッチ ウィジェットのスタイルを作成します。
スタイルを作成する
- res > values ディレクトリに
styles.xml
という名前のファイルがない場合は、新たに作成します。作成するには、values ディレクトリを右クリックして、[New] > [Values Resource File] を選択します。styles.xml
という名前を付けます。新しいファイルには次の行が含まれています。
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
- 新しい
TextView
スタイルを作成して、アプリ全体でテキストの表示に一貫性を持たせます。styles.xml
で一度スタイルを定義すると、レイアウト内のすべてのTextViews
にそれを適用できます。スタイルは、ゼロから定義することも、MDC ライブラリの既存のTextView
スタイルから拡張することもできます。
通常、コンポーネントのスタイルを設定する際は、使用するウィジェット タイプの親スタイルから拡張する必要があります。これは 2 つの理由で重要です。第 1 に、すべての重要なデフォルト値が確実にコンポーネントに設定されます。第 2 に、親スタイルに対する今後のすべての変更が引き続きスタイルに継承されます。
スタイルには任意の名前を付けることができますが、推奨される命名規則があります。親マテリアル スタイルから継承する場合は、MaterialComponents
をアプリの名前(TipTime
)に置き換えて、類似したスタイル名を付けます。そうすると、変更が固有の名前空間に移動されるため、今後マテリアル コンポーネントに新しいスタイルが導入されたときに、競合する可能性を回避できます。例:
スタイル名: Widget.TipTime.TextView
親スタイルからの継承: Widget.MaterialComponents.TextView
styles.xml
ファイルで、これを resources
の開始タグと終了タグの間に追加します。
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
</style>
TextView
スタイルを設定して、属性android:minHeight,android:gravity,
およびandroid:textAppearance.
をオーバーライドします。
android:minHeight
は、TextView
の高さの最小値を 48 dp に設定します。すべての行の高さの最小値は、マテリアル デザイン ガイドラインに従って 48 dp にする必要があります。
android:gravity
属性を設定すると、TextView
のテキストを縦方向の中央に配置できます(以下のスクリーンショットをご覧ください)。gravity は、ビュー内のコンテンツの位置を制御します。実際のテキスト コンテンツの高さは 48 dp を超えないので、値 center_vertical
により TextView
内のテキストは縦方向の中央に配置されます(横方向の位置は変更されません)。gravity には、これ以外に center
、center_horizontal
、top
、bottom
などの値を指定できます。よろしければ、他の gravity の値を試して、テキストに及ぼす効果を確認してみてください。
テキスト表示属性の値を ?attr/textAppearanceBody1
に設定します。TextAppearance は、テキストサイズ、フォント、およびテキストのその他プロパティに関する事前作成済みのスタイルのセットです。マテリアルが提供するその他の可能なテキスト表示については、こちらの型スケールのリストをご覧ください。
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
<item name="android:minHeight">48dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
activity_main.xml
の各TextView
にスタイル属性を追加することにより、Widget.TipTime.TextView
スタイルをservice_question
TextView
に適用します。
<TextView
android:id="@+id/service_question"
style="@style/Widget.TipTime.TextView"
... />
スタイルを追加する前、TextView
は、小さいフォントサイズとグレーのフォントカラーで次のように表示されていました。
スタイルを追加した後、TextView
は次のようになります。これで、この TextView
とレイアウトの他の部分との一貫性が向上しました。
- 同じ
Widget.TipTime.TextView
スタイルをtip_result
TextView
に適用します。
<TextView
android:id="@+id/tip_result"
style="@style/Widget.TipTime.TextView"
... />
- 同じテキスト スタイルをスイッチのテキストラベルに適用する必要があります。ただし、
TextView
スタイルをSwitchMaterial
ウィジェットに設定することはできません。TextView
スタイルはTextViews
にのみ適用できます。したがって、スイッチ用の新しいスタイルを作成します。属性は、minHeight
、gravity
、textAppearance
については同じです。異なるのはスタイル名と親です。それらは MDC ライブラリのSwitch
スタイルから継承するからです。この場合も、スタイルの名前に、親スタイルの名前を反映させる必要があります。
スタイル名: Widget.TipTime.CompoundButton.Switch
親スタイルからの継承: Widget.MaterialComponents.CompoundButton.Switch
<style name="Widget.TipTime.CompoundButton.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
<item name="android:minHeight">48dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
このスタイルのスイッチに固有の追加属性を指定することもできますが、このアプリではその必要はありません。
- テキストの表示に一貫性を持たせる必要がある最後の箇所は、ラジオボタンのテキストです。
TextView
スタイルまたはSwitch
スタイルをRadioButton
ウィジェットに適用することはできません。代わりに、ラジオボタン用の新しいスタイルを作成する必要があります。これは、MDC ライブラリのRadioButton
スタイルから拡張できます。
このスタイルを作成する際に、ラジオボタン テキストと円形のシンボルの間にパディングを追加します。paddingStart
は、まだ使用したことがない新しい属性です。パディングとは、ビューのコンテンツとビューの境界の間のスペースの量です。paddingStart
属性は、コンポーネントの開始位置にのみパディングを設定します。ラジオボタンの paddingStart
を 0 dp にした場合と 8 dp にした場合の違いを次に示します。
<style name="Widget.TipTime.CompoundButton.RadioButton"
parent="Widget.MaterialComponents.CompoundButton.RadioButton">
<item name="android:paddingStart">8dp</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
- (省略可)
dimens.xml
ファイルを作成すると、頻繁に使用する値の管理が容易になります。このファイルは、前述のstyles.xml
ファイルと同じ方法で作成できます。values ディレクトリを選択し、右クリックして [New] > [Values Resource File] を選択します。
これまでに、この小さなアプリで高さの最小値の設定を 2 回繰り返しました。この程度であれば簡単に管理できますが、4 個、6 個、10 個、またはそれ以上のコンポーネントがこの値を共有するとなると、たちまち管理が困難になります。それらすべてを個別に変更する作業は退屈で、間違いが発生しやすくなります。res > values ディレクトリに dimens.xml
という名前の便利なリソース ファイルを作成し、そのファイルで共通のディメンションを保持できます。共通の値を名前付きディメンションとして標準化することにより、アプリの管理が容易になります。ただし、TipTime は小さなアプリなので、この省略可能なステップ以外ではこのファイルを使用しません。デザインチームと協力して作業する本番環境で複雑なアプリを作成する場合は、dimens.xml
を使用すると、共通のディメンションの値を頻繁に変更することが容易になります。
dimens.xml
<resources>
<dimen name="min_text_height">48dp</dimen>
</resources>
48dp
を直接設定する代わりに、@dimen/min_text_height
を使用するように styles.xml
ファイルを更新します。
...
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
<item name="android:minHeight">@dimen/min_text_height</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
...
スタイルをテーマに追加する
お気づきのとおり、新しい RadioButton
および Switch
スタイルをそれぞれのウィジェットにまだ適用していません。これは、テーマ属性を使用してアプリテーマに radioButtonStyle
と switchStyle
を設定するためです。テーマについてもう一度おさらいしましょう。
テーマとは、スタイルやレイアウトなどで後から参照できる名前付きリソース(テーマ属性と呼ばれます)のコレクションです。テーマは、個別の View.
だけでなく、アプリ全体、アクティビティ、ビュー階層に対して指定できます。以前は colorPrimary
や colorSecondary
のようなテーマ属性を設定することにより、themes.xml
でアプリテーマを変更していました。そして、テーマ属性はアプリとアプリのコンポーネント全体で使用されていました。
radioButtonStyle
と switchStyle
も設定可能なテーマ属性です。これらのテーマ属性に対して指定するスタイル リソースは、そのテーマが適用されるビュー階層内のすべてのラジオボタンとすべてのスイッチに適用されます。
指定されたスタイル リソースがアプリ内のすべてのテキスト入力フィールドに適用される、textInputStyle
用のテーマ属性もあります。TextInputLayout
を(マテリアル デザイン ガイドラインで示されているとおり)枠線付きテキスト フィールドのように表示するには、MDC ライブラリで Widget.MaterialComponents.TextInputLayout.OutlinedBox
として定義されている OutlinedBox
スタイルを利用できます。ここでは、このスタイルを使用します。
- テーマが目的のスタイルを参照するように、
themes.xml
ファイルを変更します。テーマ属性を設定する方法は、前の Codelab でcolorPrimary
およびcolorSecondary
テーマ属性を宣言した方法と同じです。ただし、今回設定するテーマ属性は、textInputStyle
、radioButtonStyle
、switchStyle
です。前にRadioButton
およびSwitch
用に作成したスタイルとともに、マテリアルのOutlinedBox
テキスト フィールド用のスタイルを使用します。
次の行をコピーして、res/values/themes.xml
のアプリテーマのスタイルタグに貼り付けます。
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
res/values/themes.xml
ファイルは次のようになります。お望みであれば、XML にコメントを追加できます(コメントは<!-
と-->
で示します)。
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Text input fields -->
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<!-- Radio buttons -->
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<!-- Switches -->
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
</style>
</resources>
- themes.xml(night)のダークテーマにも必ず同じ変更を加えてください。
res/values-night/themes.xml
ファイルは次のようになります。
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Application theme for dark theme. -->
<style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Text input fields -->
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<!-- For radio buttons -->
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<!-- For switches -->
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
</style>
</resources>
- アプリを実行して、変更を確認します。
OutlinedBox
スタイルによってテキスト フィールドの外観が改善され、すべてのテキストの表示に一貫性がもたらされました。
アプリが完成に近づくにつれて、想定しているワークフローだけでなく、それ以外のユーザー シナリオでもアプリをテストする必要があります。わずかなコード変更で、ユーザー エクスペリエンスが大幅に改善されることがあります。
デバイスを回転させる
- デバイスを回転させて横表示にします。まず、[自動回転] 設定を有効にします(この設定は、デバイスの [クイック設定] か、[設定] > [ディスプレイ] > [詳細設定] > [画面の自動回転] オプションで行います)。
エミュレータでは、エミュレータ オプション(デバイスのすぐ隣の右上にあります)を使用して、画面を左右に回転させることができます。
- ご覧のとおり、[Calculate] ボタンなど、UI コンポーネントの一部の表示が切れています。これでは明らかにアプリを使用できません。
- この不具合を解決するには、
ConstraintLayout
を囲むScrollView
を追加します。XML は次のようになります。
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:context=".MainActivity">
...
</ConstraintLayout>
</ScrollView>
- もう一度アプリを実行してテストします。デバイスを回転させて横表示にした場合、UI をスクロールすると計算ボタンが使用可能になり、チップの計算結果が表示されます。この修正は、横表示だけでなく、サイズが異なる他の Android デバイスでも有用です。これで、デバイスの画面サイズにかかわらず、ユーザーはレイアウトをスクロールできるようになります。
Enter キーでキーボードを非表示にする
サービス料金を入力した後もキーボードが表示されたままになることにお気づきでしょうか。計算ボタンを使用しやすくするために、毎回キーボードを手動で非表示にするのはいささか面倒です。代わりに、Enter キーが押されたらキーボードが自動的に非表示になるようにしましょう。
テキスト フィールドでは、特定のキーがタップされたときにイベントに応答するキーリスナーを定義できます。キーボードのすべての可能な入力オプションには、Enter
キーを含むキーコードが関連付けられています。画面キーボードは、物理キーボードに対してソフト キーボードと呼ばれることもあります。
このタスクでは、テキスト フィールドにキーリスナーを設定して、Enter
キーが押されたときにリッスンするようにします。このイベントが検出されたら、キーボードを非表示にします。
- 下記のヘルパー メソッドをコピーして
MainActivity
クラスに貼り付けます。MainActivity
クラスの右中かっこの直前に挿入できます。handleKeyEvent()
は、keyCode
入力パラメータがKeyEvent.
KEYCODE_ENTER
と等しい場合に画面キーボードを非表示にする非公開のヘルパー関数です。InputMethodManager は、ソフト キーボードの表示と非表示を制御し、表示されるソフト キーボードをユーザーが選択できるようにします。このメソッドは、キーイベントが処理された場合は true を返し、処理されなかった場合は false を返します。
MainActivity.kt
private fun handleKeyEvent(view: View, keyCode: Int): Boolean {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// Hide the keyboard
val inputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
return true
}
return false
}
- 次に、キーリスナーを
TextInputEditText
ウィジェットにアタッチします。バインディング オブジェクトを介して、binding.costOfServiceEditText.
としてTextInputEditText
ウィジェットにアクセスできることを思い出してください。
costOfServiceEditText
で setOnKeyListener()
メソッドを呼び出し、OnKeyListener
を渡します。これは、binding.calculateButton.setOnClickListener { calculateTip() }.
を使用して、アプリの計算ボタンにクリック リスナーを設定する方法と似ています。
ビューにキーリスナーを設定するコードはやや複雑ですが、キーの押下が発生したときにトリガーされる onKey()
メソッドを OnKeyListener
に設定するのが一般的な方法です。onKey()
メソッドは 3 つの入力引数を受け取ります。それは、ビュー、押されたキーを表すコード、キーイベント(ここでは使用しないので、"_
" を指定します)です。onKey()
メソッドが呼び出されたら、handleKeyEvent()
メソッドを呼び出して、ビューとキーコードの引数を渡す必要があります。これを記述する構文は、view, keyCode, _ -> handleKeyEvent(view, keyCode).
です。これは実際にはラムダ式と呼ばれますが、ラムダの詳細については今後の学習ユニットで詳しく説明します。
アクティビティの onCreate()
メソッド内のテキスト フィールドにキーリスナーを設定するコードを追加します。これは、レイアウトが作成された直後、ユーザーがアクティビティの操作を開始する前に、キーリスナーをアタッチする必要があるからです。
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(binding.root)
binding.calculateButton.setOnClickListener { calculateTip() }
binding.costOfServiceEditText.setOnKeyListener { view, keyCode, _ -> handleKeyEvent(view, keyCode)
}
}
- 新たに加えた変更が機能するかどうかをテストします。アプリを実行して、サービス料金を入力します。キーボードの Enter キーを押すと、ソフト キーボードが非表示になるはずです。
TalkBack を有効にしてアプリをテストする
このコースで学習してきたとおり、できるだけ多くのユーザーが利用できるアプリを作成する必要があります。一部のユーザーは、TalkBack を使用してアプリを操作する場合があります。TalkBack は、Android デバイスに組み込まれている Google スクリーン リーダーです。TalkBack から音声フィードバックが出力されるので、画面を見ずにデバイスを使用できます。
TalkBack を有効にして、ユーザーがこのアプリでチップの計算を完了できるかどうかを確認しましょう。
- デバイスで TalkBack を有効にするには、こちらの手順を実施します。
- Tip Time アプリに戻ります。
- こちらの手順に沿って、TalkBack でアプリを探します。右にスワイプして画面要素間を順番に移動し、左にスワイプして逆に移動します。選択するには、任意の場所をダブルタップします。スワイプ操作でアプリのすべての要素にアクセスできることを確認します。
- TalkBack ユーザーが画面上の各アイテムにナビゲートできることを確認した後、サービス料金を入力し、チップ オプションを変更して、チップを計算します。そうすると、チップ金額が読み上げられるはずです。アイコンは
importantForAccessibility="no"
としてマークしたため、アイコンの音声フィードバックは出力されないことにご注意ください。
アプリのユーザー補助機能を強化する方法について詳しくは、こちらの原則をご確認ください。
(省略可)ベクター型ドローアブルの色合いを調整する
この省略可能なタスクでは、テーマのメインカラーに基づいてアイコンの色合いを調整し、ライトテーマとダークテーマでアイコンの外観が変化するようにします(下記を参照)。この変更を行うと、アイコンとアプリテーマの調和が増し、UI がよりいっそう改善されます。
前述のように、ビットマップ画像と比較した場合の VectorDrawables
の利点のひとつは、拡大縮小と色合い調整の機能です。ベルのアイコンを表す XML を下記に示します。特に注目すべきは、android:tint
と android:fillColor
の 2 つの色属性です。
ic_service.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M2,17h20v2L2,19zM13.84,7.79c0.1,-0.24 0.16,-0.51 0.16,-0.79 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,0.28 0.06,0.55 0.16,0.79C6.25,8.6 3.27,11.93 3,16h18c-0.27,-4.07 -3.25,-7.4 -7.16,-8.21z"/>
</vector>
色合いが存在する場合、それによってドローアブルの fillColor
ディレクティブがすべてオーバーライドされます。この場合は、白い色が colorControlNormal
テーマ属性でオーバーライドされます。colorControlNormal
は、ウィジェットの「標準」(選択されていない非アクティブの状態)の色です。現在、それはグレーです。
アプリの外観を改善する方法のひとつとして、アプリテーマのメインカラーに基づいてドローアブルの色合いを調整できます。アイコンは、ライトテーマでは @color/green
として表示されますが、ダークテーマでは @color/green_light
(これは ?attr/colorPrimary
です)として表示されます。アプリテーマのメインカラーに基づいてドローアブルの色合いを調整すると、レイアウト内の要素の外観が統一され、調和が増します。また、ライトテーマとダークテーマ用に重複するアイコンのセットを保持せずに済みます。ベクター型ドローアブルのセットを 1 つだけ保持すれば、colorPrimary
テーマ属性に基づいて色合いが変更されます。
ic_service.xml
のandroid:tint
属性の値を変更します。
android:tint="?attr/colorPrimary"
Android Studio では、このアイコンに適切な色合いが設定されています。
colorPrimary
テーマ属性が指す値は、ライトテーマかダークテーマかによって異なります。
- 他のベクター型ドローアブルで色合いを変更するには、同じ手順を繰り返します。
ic_store.xml
<vector ...
android:tint="?attr/colorPrimary">
...
</vector>
ic_round_up.xml
<vector ...
android:tint="?attr/colorPrimary">
...
</vector>
- アプリを実行し、ライトテーマとダークテーマでアイコンの外観が異なることを確認します。
- 最終クリーンアップとして、アプリ内のすべての XML ファイルと Kotlin コードファイルを再フォーマットする手順を忘れずに実施してください。
おつかれさまでした。これでチップ計算アプリは完成です。成し遂げた成果を誇りに思ってください。ここで学習したことが、もっと優れた外観と機能を持つアプリを作成するための足掛かりとなることを願っています。
この Codelab の解答コードは、下記の GitHub リポジトリにあります。
この Codelab のコードを取得して Android Studio で開くには、以下の手順に沿って操作します。
コードを取得する
- 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ダイアログが表示されます。
- ダイアログで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ってください。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで [Open an existing Android Studio project] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [New] > [Import Project] を選択します。
- [Import Project] ダイアログで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン をクリックして、アプリをビルドし、実行します。期待どおりに動作することを確認します。
- [Project] ツール ウィンドウでプロジェクト ファイルを表示して、アプリがどのように実装されているかを確認します。
- マテリアル デザイン ガイドラインに沿って可能な限りマテリアル デザイン コンポーネントを使用し、カスタマイズを検討します。
- アイコンを追加することにより、アプリの各要素がどのように機能するかについてユーザーに視覚的な手掛かりを与えます。
ConstraintLayout
を使用してレイアウト内に要素を配置します。- エッジケース(例: デバイスを回転させて横向きの画面にアプリを表示する)でアプリをテストし、必要に応じて改善します。
- コードにコメントを挿入して、他の人がコードを読んだときに作成者の意図を簡単に理解できるようにします。
- コードを再フォーマットしてクリーンアップを行い、コードをできるだけ簡潔にします。
- 前の Codelab からの続きとして、ここで学習したベスト プラクティス(マテリアル デザイン コンポーネントの使用方法など)を使い、マテリアル ガイドラインにもっと近づけるように単位変換クッキング アプリを更新してください。