Android Kotlin基礎講座 10.3:利用者に向けたデザイン
目次
タスク:リージョンをフィルタリングするためにチップを使う
チップとは属性、テキスト、エンティティ、アクションなどを表すコンパクトな要素です。これによって、ユーザーは情報の入力、選択肢の選択、コンテンツのフィルタリング、何かしらのアクションを起こしたりできます。
Chipウィジェットは全てのレイアウトおよび描写ロジックを含むChipDrawableを囲むビューラッパーです。タッチ、マウス、キーボード、アクセシビリティナビゲーションをサポートするための別のロジックも存在しています。主要なチップと閉じるアイコンはロジック用のサブビューに分けられるべきというのが一般的で、それらには画面遷移の挙動や各々のビューの状態などが含まれています。
チップはドローアブルを使用します。Androidドローアブルを使うことで、画像、シェイプ、アニメーション等を画面に描写することができます。またドローアブルには固定サイズも動的に変わるサイズもあります。GDGアプリ内の画像のような画像はドローアブルとして使うことができます。また、ヴェクター画像を使うことで、想像できるものはすべて描写することができます。
この講座では解説しませんが、 9-patch drawableというリサイズ可能なドローアブルもあります。drawable/ic_gdg.xml内のGDGロゴも別のドローアブルです。
ドローアブルはビューではないので、ConstraintLayout内に直接ドローアブルを置くことはできません。ImageViewの中に置く必要があります。またドローアブルを使ってテキストビューやボタン用の背景を表現することもできます。
ステップ1:GDGsのリストにチップを追加する
下の画像のチェックが入ったチップは三つのドローアブルを使用しています。背景とチェックマークはそれぞれドローアブルです。チップをタッチすることで、エフェクトが表示されますが、それもRippleDrawableというドローアブルを使用しています。
このタスクでは、チップをGDGsのリストに追加し、選択されたときに状態が変わるようにします。今回の場合は、検索画面のトップにchipsというボタンの列を追加します。それぞれのボタンはGDGリストをフィルタリングし、ユーザーが選択した地域からのみ結果を取得できるようにします。ボタンが選択されると、ボタンの背景が変わり、チェックマークが付きます。
- fragment_gdg_list.xmlを開いてください。
- HorizontalScrollView内にcom.google.android.material.chip.ChipGroupを作成してください。
そのsingleLineプロパティをtureにしl、全てのチップが水平にスクロールできるライン上に並ぶようにします。
singleSelectionプロパティをtureにし、グループ内のチップから一つのみ選択できるようにします。以下がコードになります。
<com.google.android.material.chip.ChipGroup
android:id="@+id/region_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleSelection="true"
android:padding="@dimen/spacing_normal"/>
- layoutフォルダー内にregion.xmlという新規レイアウトリソースファイルを作成してください。これはチップ用のレイアウトを定義するためのものです。
- region.xml内の全てのコードを以下のコードに置き換えてください。Chipがマテリアルコンポーネントであることを確認してください。また、app:checkedIconVisibleプロパティを設定することによってチェックマークを取得していることも確認してください。
selected_highlightカラーがないことによるエラーが表示されます。
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Chip.Choice"
app:chipBackgroundColor="@color/selected_highlight"
app:checkedIconVisible="true"
tools:checked="true"/>
- slected_highlightカラーを作成するために、selected_highligtの上にカーソルをおいて、インテンションメニューを開いてください。create color resource for selected highlightを選択してください。設定は全てデフォルトのままで大丈夫ですので、OKをクリックしてください。ファイルがres/colorフォルダに作成されます。
- res/color/selected_highlight.xmlを開いてください。<selector>としてエンコードされているカラー状態リスト内では、異なる状態に応じて別のカラーを提供することができます。それぞれの状態とそれに紐づいたカラーは<item>としてエンコードされます。
- <selector>内にデフォルトカラーであるcolorOnSurfaceを追加してください。この状態リスト内で、常に全ての状態に対応することが重要です。その方法の一つとして、デフォルトカラーを設定しておくことが挙げられます。
<item android:alpha="0.18" android:color="?attr/colorOnSurface"/>
- デフォルトカラーの上に、colorPrimaryVariantという色用のitemを追加し、trueという状態の時にこれが使われるように設定します。
状態リストは条件分岐文のように、上から下に読み取られていきます。もしどの状態にもマッチしなかった場合に、デフォルトカラーが適用される仕組みです。
<item android:color="?attr/colorPrimaryVariant"
android:state_selected="true" />
ステップ2:チップの列を表示する
GDG FinderはGDGのある地域を表示するチップのリストを作成します。チップが選択されると、アプリはその地域用のGDGのみを表示するようにフィルタリングします。
Note: どのようにリストが生成されるか、結果がフィルタリングされるかはこの記事の範囲外です。もしGDGがどのように取得されているか、フィルタリングが実装されているかを知りたい場合は、search、networkパッケージ内のファイルを調べてみてください。特にGdgListViewModelを調べてみるといいです。
- searchパッケージ内、GdgListFragment.ktを開いてください。
- onCreateView()内、return文の直前にviewModel.regionListにオブザーバーを追加し、onChanged()をオーバーライドします。ビューモデルによって提供されている地域のリストが変更されるとき、チップは再生成される必要があります。dataがnullの場合は即座にreturnするための文を追加してください。
viewModel.regionList.observe(viewLifecycleOwner, object: Observer<List<String>> {
override fun onChanged(data: List<String>?) {
data ?: return
}
})
- onChanged()内、nullチェックの下でbinding.regionListをchipGroupという新規変数に代入し、regionListをキャッシュしてください。
val chipGroup = binding.regionList
- さらにその下に、chipGroup.contextからのチップをインフレートするための新規変数layoutInflatorを作成してください。
val inflator = LayoutInflater.from(chipGroup.context)
- クリーン、リビルドしてデータバインディングエラーを取り除いてください。
これでインフレーターの下に実際のチップを作成することができます。
- 全てのチップを保持するための変数childrenを作成してください。それを渡されたdataのマッピング関数に代入し、それぞれのチップを作成して返します。
val children = data.map {}
- mapのラムダ式の中で、それぞれのregionName用にチップを作成、インフレートします。完成コードは以下になります。
Inside the map lambda, for eachregionName
, create and inflate a chip. The completed code is below.
val children = data.map { regionName ->
val chip = inflator.inflate(R.layout.region, chipGroup, false) as Chip
chip.text = regionName
chip.tag = regionName
// TODO: Click listener goes here.
chip
}
- ラムダ式の中のchipを返している直前にクリックリスナーを追加します。chipがクリックされたら、状態をcheckedに設定します。viewModel内のonFilterChanged()を呼び出し、このフィルター用の結果を取得する一連のイベントを起動します。
chip.setOnCheckedChangeListener { button, isChecked ->
viewModel.onFilterChanged(button.tag as String, isChecked)
}
- ラムダ式の最後で、chipGroupからの現在のビューすべてを削除し、childrenからの全てのビューをchipGroupに追加します。(このチップは更新することができないので、削除してから再生成する必要があります)
chipGroup.removeAllViews()
for (chip in children) {
chipGroup.addView(chip)
}
最終的なオブザーバーは以下のようになります。
override fun onChanged(data: List<String>?) {
data ?: return
val chipGroup = binding.regionList
val inflator = LayoutInflater.from(chipGroup.context)
val children = data.map { regionName ->
val chip = inflator.inflate(R.layout.region, chipGroup, false) as Chip
chip.text = regionName
chip.tag = regionName
chip.setOnCheckedChangeListener { button, isChecked ->
viewModel.onFilterChanged(button.tag as String, isChecked)
}
chip
}
chipGroup.removeAllViews()
for (chip in children) {
chipGroup.addView(chip)
}
}
})
- アプリを起動して、検索画面を開くためにGDGを検索してください。チップを使ってみてください。チップをクリックすると、フィルタリングされた結果が表示されます。