【Kotlin研修3日目】SimpleAdapterを用いたアクティビティ間のデータ共有と画面遷移、レイアウトファイルでのリスナ定義

# SimpleAdapter
[参考1: 研修2日目](https://qiita.com/b150005/items/7059179037a56df42c7c)
[参考2: SimpleAdapter](https://developer.android.com/reference/android/widget/SimpleAdapter)
`MutableList<MutableMap<String, *>>`型のデータ構造をとる`Adapter`クラス。

## MutableList
「`キー`と`値`をもち、要素数が`可変`」な`MutableMap<String, *>`型のデータを、各`インデックス`に格納した`リスト`。

## SimpleAdapterの生成

### 定義

```kotlin
SimpleAdapter(
    context: Context!,
    data: MutableList<out MutableMap<String!, *>!>!,
    resource: Int
    from: Array<String>!
    to: IntArray!
)
// パラメータ
// context: コンテキストとなるアクティビティオブジェクト
// data: リスト形式ビューに反映するリストデータ
// resource: リスト形式ビューの各Itemのレイアウトを表現するR値
// from: MutableMapのキーのみを格納した配列
// to: Item内の複数のビュー(=TextView)を識別するR値(=ID)を格納した配列
// -> toで指定したビュー(=TextView)に対して、
//    fromで指定したキーに対応する値を格納
```

## 値を加工して表示するListView

```kotlin:MainActivity.kt
// 発展: 値を加工したListViewの生成

class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val nameList = createNameList()
        val from = arrayOf("name", "sex")
        val to = intArrayOf(R.id.tvName, R.id.imSex)
        // Adapterオブジェクトの生成
        val adapter = SimpleAdapter(
            applicationContext,
            nameList,
            R.layout.row,
            from,
            to
        )

        // AdapterにViewBinderオブジェクトをセット
        adapter.viewBinder = CustomViewBinder()
        // リストデータを紐づけるListViewの定義
        val lvPhones = findViewById<ListView>(R.id.lvNameList)
        // ListViewにAdapterオブジェクトをセット
        lvPhones.adapter = adapter
    }

    // 加工するリストデータを生成するメソッド
    private fun createNameList(): MutableList<MutableMap<String, Any>> {
        // 加工するリストデータを記述
        ...  // 最終的に"return <リストデータ>"を記述
    }

    // MutableMapの値を基にデータを加工するクラス
    private inner class CustomViewBinder: SimpleAdapter.ViewBinder {
        // MutableMapの値を基にデータを加工
        // -> ListView生成時に自動的に実行される
        override fun setViewValue(
            view: View,
            data: Any,
            textRepresentation: String
        ): Boolean {
            // データの加工処理
            ...  // 最終的に"return true/false"を記述
                 // true: データのバインドが実行された場合
                 // false: データのバインドが実行されなかった場合
        }
    }
}
```

***

# 画面の追加
[参考: 研修1日目](https://qiita.com/b150005/items/46ffc9a4d079cccdc807)
アプリに表示される`文字列`を除き、アプリの`1画面`を構成しているのは、以下の3つ。

> 1. **アプリ内処理**を担う、`app/java`フォルダの`アクティビティクラス`(=`.kt`ファイル)
> 2. **画面構成**を担う、`app/res/layout`フォルダの`レイアウトファイル`(=`.xml`ファイル)
> 3. 1.および2.を**認識**する、`app/manifests`フォルダの`AndroidManifest.xml`ファイル

画面を追加する場合は、`AndroidManifest.xml`ファイルに`activity`タグを記述する必要がある。
※`Android Studio`は、`New Activity`の作成時に、`アクティビティ`の追加にあたって必要な処理を自動的に行う。

# Androidにおける画面遷移
`遷移先`の画面は、`遷移元`の画面の**前面**に表示される。
遷移先から戻る場合は、`遷移先`の画面が消滅し、背面に隠れていた`遷移元`の画面が表示される挙動をとる。

## インテント(Intent)
[参考: インテント](https://developer.android.com/guide/components/intents-filters?hl=ja)
[参考2: インテントのクラスメソッド](https://developer.android.com/reference/kotlin/android/content/Intent?hl=ja#Intent(android.content.Context,%20java.lang.Class))
`画面遷移`にあたり、`画面`(=`アクティビティ`)を起動するクラス。
`遷移元`アクティビティと`遷移先`アクティビティの橋渡し役を担う。

### Intentクラスのインスタンス化(実体化)

#### 定義

```kotlin
Intent(packageContext: Context!, cls: Class<*>!)
// パラメータ
// packageContext: 遷移元のアクティビティオブジェクト(=コンテキスト)
// cls: Javaクラス化した遷移先アクティビティ
// -> KotlinのクラスをJavaに変換する場合は、"<Kotlinクラス名>::class.java"と記述
```

### Intentオブジェクトへのデータの格納
`インテント`を通じてデータをやり取りする場合、`Extraデータ`(=追加情報)を`Intent`オブジェクトに追加する。

#### 定義

```kotlin
putExtra(name: String!, value: String?): Intent
// パラメータ
// name: 格納するデータの名称
// value: 格納するデータ
```

### 遷移先アクティビティの起動(画面遷移)

#### 定義

```kotlin
Activity.startActivity(intent: Intent!): Unit
// パラメータ
// intent: 画面遷移を実現するIntentオブジェクト
```

### サンプルコード

```kotlin:MainActivity.kt
class MainActivity: AppCompatActivity() {
    ...
    // リスナクラス
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            // "タップ"されたItemの定義
            // parent: AdapterView
            // getItemAtPosition(): 指定したIndex(position)のtext値を取得(返り値はAny型)
            val item = parent?.getItemAtPosition(position) as MutableMap<String, String>

            // 指定したキーの値
            val menuName = item["name"]
            val menuPrice = item["price"]

            // 画面遷移を実現するIntentオブジェクトの生成
            // Intent(packageContext:cls:): Intentオブジェクトの生成
            // packageContext: 遷移元のアクティビティオブジェクト(=コンテキスト)
            // cls: Javaクラス化した遷移先アクティビティ
            val intent2MenuThanks = Intent(
                this@MainActivity,
                MenuThanksActivity::class.java
            )

            // Intentオブジェクトへのデータの格納
            // putExtra(name:value:): Intentオブジェクトにデータを格納
            // name: 格納するデータの名称
            // value: 格納するデータ
            intent2MenuThanks.putExtra("menuName", menuName)
            intent2MenuThanks.putExtra("menuPrice", menuPrice)

            // 遷移先アクティビティの起動
            // Context.startActivity(intent:): Intentオブジェクトによる画面遷移の実行
            // intent: Intentオブジェクト
            startActivity(intent2MenuThanks)
        }
    }
}
```

#### apply関数を用いたIntentオブジェクトの生成・定義

```kotlin:MainActivity.kt
// 発展: apply関数の使用

// 本来はこっち
// 画面遷移を実現するIntentオブジェクトの生成
val intent2MenuThanks = Intent(
    this@MainActivity,
    MenuTahnksActivity::class.java
)
// Intentオブジェクトへのデータの格納
intent2MenuThanks.putExtra("menuName", menuName)
intent2MenuThanks.putExtra("menuPrice", menuPrice)

// apply関数(スコープ関数)を用いる場合
// -> applyブロック{...}内では、インスタンス変数(intent2MenuThanks)の記述が不要
val intent2MenuThanks = Intent(
    this@MainActivity,
    MenuTahnksActivity::class.java
).apply {
    putExtra("menuName", menuName)
    putExtra("menuPrice", menuPrice)
}
```

### 遷移先アクティビティの終了

#### 定義

```kotlin
finish(): Unit
```

## 遷移先による遷移元の値取得
`遷移元`のデータは`Intent`オブジェクトが保持しているため、`遷移先アクティビティ`で`intent`プロパティを用いてデータを取得する。

### 文字列(String)

#### 定義

```kotlin
Intent.getStringExtra(name: String!): String?
// パラメータ
// name: Intentオブジェクト(=intentプロパティ)に格納されたデータの名称
```

### 数値(Int)

#### 定義

```kotlin
Intent.getIntExtra(name: String!, defaultValue: Int): Int
// パラメータ
// name: Intentオブジェクト(=Activity.intent)に格納されたデータの名称
// defaultValue: 取得するデータの初期値
// <- 指定したnameのデータが存在しない場合のnullを避ける
```

### サンプルコード

```kotlin:MenuThanksActivity.kt
class MenuThanksActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Intentオブジェクトが保持するデータの取得
        val menuName = intent.getStringExtra("menuName")
        val menuPrice = intent.getStringExtra("menuPrice")

        // 値を反映する遷移先ビューの定義
        val tvMenuName = findViewById<TextView>(R.id.tvMenuName)
        val tvMenuPrice = findViewById<TextView>(R.id.tvMenuPrice)

        // 遷移先ビューの値(=TextView.text)の変更
        tvMenuName.text = menuName
        tvMenuPrice.text = menuPrice
    }
}
```

***

# レイアウトファイルによるリスナ定義
[参考: タップイベントのイベント処理](https://developer.android.com/guide/topics/ui/controls/button)
`イベント`を検知する`リスナ`を定義する方法は以下の2つ。

> - `Activity`ファイルで`リスナクラス`を定義
> - `レイアウトファイル`に`android:onClick`属性を追加し、その`属性値`と一致する`イベントハンドラ`を<br>`Activity`ファイルに記述<br>※[`フラグメント`](https://qiita.com/b150005/items/cbc437ae0025d04f83bd)では実装不可

## サンプルコード

```xml:activity_menu_thanks.xml
<Button
    ...
    android:onClick="onBackButtonClick"  <!-- リスナ定義 -->
/>
```

```kotlin:MenuThanksActivity.kt
class MenuThanksActivity: AppCompatActivity() {
    ...
    // "タップ"イベント検知時の処理(イベントハンドラ)
    // ※実装にあたって制約が存在(後述)
    fun onBackButtonClick(view: View) {
        ...
    }
}
```

## 制約
`レイアウトファイル`で`リスナ`の定義を行う場合、`イベントハンドラ`は以下の要件を満たす必要がある。

> - `public`メソッドであること(`Kotlin`では`public`が規定値)
> - `返り値`がない(=Unit型である)こと
> - `View`型**1つ**のみの`引数`をとること