AndroidStudio フラグメント化による画面表示

#フラグメント
``Fragment`` は、``FragmentActivity`` でのユーザー インターフェースの挙動や部位を表すものです。

``フラグメント``を利用すると、同一のアプリでも、画面サイズによって画面構成を変えることが出来る。

例えば、スマホ画面では、２画面を画面遷移をしながら表示するのに対して、タブレットでは、２画面を画面の右側と、左側に分けて表示する。ということが出来る。
つまり、画面を1つの部品として扱うイメージ。![スクリーンショット 2021-06-17 14.34.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1098849/ff30fe27-dfe8-d4ab-021b-4c559c0fb68a.png)
[参照](https://developer.android.com/guide/fragments)



画面の一部を独立したブロックとして扱えるのが``フラグメント``であり、
アクティビティ同様に画面構成を担う.xmlファイルと、処理を行うKotlinクラスのセットで成り立ってる。
画面の構成に必要なファイルの種類などはアクティビティと同じ。

>ここで重要なことは
フラグメントはアクティビティと近しいが、あくまでも``部品``ということ

今回は、タブレットで左側にリスト表示の画面、右側に注文完了表示のブロックを配置していきます。
手順としては、大きく4つに分かれます。
**1,スマホサイズのメニューリスト画面のフラグメント化**
**2,スマホサイズの注文完了画面のフラグメント化**
**3,タブレットサイズ画面の作成**
**4,注文完了フラグメントのタブレット対応**

#1,スマホサイズのメニューリスト画面のフラグメント化
###(1)プロジェクト作成
###(2)strings.xmlに文字列情報を追加
###(3)メニューリスト用のフラグメントを追加する
　　　∟[new]>[Fragment]>Fragmentを選択
 　　　∟.xmlファイルと、Kotlinクラスのセットが出来る
###(4)作られたxmlファイルに画面構成を記述する
####サンプルコード

```fragment_menu_list.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/lvMenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

###(5)上記のxmlファイルに紐つけられるKotlinファイルに処理を記述する
クラス宣言では、MenuListFragment が、そのスーパークラスのコンストラクタである **Fragment** を呼び出す役割を担っています。
※コンストラクタ：コンストラクタとはオブジェクト生成時に自動的に呼ばれる処理です。

フラグメントは、画面の一部をブロック化したものなので、アクティビティ同様にライフサイクルを持っています。
さらに、フラグメントのライフサイクルはアクティビティと連動します。下図参照
![fragment_lifecycle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1098849/0b5e7c1a-d083-2f51-e038-17784ed269d6.png)
[参照](https://developer.android.com/guide/components/fragments?hl=ja)
上図の``onCreateView``は画面生成のメソッドなので、記述する必要がある。

####FragmentクラスのOnCreateViewメソッド
``onCreateView``メソッド：フラグメントに関連付けられたビュー階層を作成して返します。

戻り値型：View
パラメーター3つ：inflater、container、savedInstanceState
**公式サンプル**

```
public View onCreateView (LayoutInflater inflater, 
                ViewGroup container, 
                Bundle savedInstanceState)
```

``onCreateViewメソッド``で記述すること
**[１]レイアウトxmlファイルから画面部品を生成する**
∟パラメーターの1つ目を使用する。
∟[Android]LayoutInflaterについて(生成,方法比較,実装)は、[こちら](https://qiita.com/Bth0061/items/c4f66477979d064913e4)を参考に。
**[2]生成した画面部品にデータを格納する**
∟アダプターを設定する
※アダプターとは、リストビューとリストデータを結びつけるクラス
∟その為に、``findViewById``を使ってListViewを取得する。
**[3][1]で生成したviewを``onCreateView()メソッド``に戻り値として返す**


[Android]LayoutInflaterについて(生成,方法比較,実装)は、[こちら](https://qiita.com/Bth0061/items/c4f66477979d064913e4)を参考に。


####サンプルコード

```MenuListFragment.kt
package com.websarva.wings.android.fragmentsample

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import android.widget.SimpleAdapter
import androidx.fragment.app.Fragment


//フラグメントのKotlinクラスは、スーパークラスのコンストラクタであるFragmentクラスを継承している。
class MenuListFragment : Fragment() {

    //FragmentクラスのonCreateViewメソッドの上書きをしている。パラメータ3つ。
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        //パラメーターそれぞれの宣言をする
        // フラグメントで表示する画面を生成する為にxmlファイルからインフレートする
        val view = inflater.inflate(R.layout.fragment_menu_list, container, false)
        //アダプタオブジェクトを生成
        //画面部品ListViewを取得
        val lvMenu = view.findViewById<ListView>(R.id.lvMenu)

        //SimpleAdapterで使用するMutableListオブジェクトを用意
        val menuList: MutableList<MutableMap<String, String>> = mutableListOf()

        //「唐揚げ定食」のデータを格納するMapオブジェクトの用意とmenuListへのデータ登録
        var menu = mutableMapOf("name" to "唐揚げ定食", "price" to "800円")
        menuList.add(menu)
        //「~定食」のデータを格納するMapオブジェクトの用意とmenuListへのデータ登録
        //省略

        //SimpleAdapter第4引数from用データの用意
        val from = arrayOf("name", "price")
        //SimpleAdapter第5引数to用のデータの用意
        val to = intArrayOf(android.R.id.text1, android.R.id.text2)
        //SimpleAdapterを生成することで、リストビューとリストデータの橋渡し
        val adapter = SimpleAdapter(activity, menuList, android.R.layout.simple_list_item_2,from, to)
        //アダプタの登録
        lvMenu.adapter = adapter

        //インフレートされた画面を戻り値として返す
        return view
    }
}

```

→アクティビティへフラグメントを埋め込む
フラグメントは、あくまで画面の一部であり、実際にAndroidの画面は、アクティビティ単位です。
その為に、アクティビティにフラグメントを組み込む必要があります。

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragmentMenuList"
        android:name ="com.websarva.wings.android.fragmentsample.MenuListFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>
```

#2,スマホサイズの注文完了画面のフラグメント化
基本的な手順は、``1,スマホサイズのメニューリスト画面のフラグメント化``と同様になります。

####手順
>→``アクティビティ``と``フラグメントファイル``を作成する。
  ∟それぞれ、**画面構成ファイル(xml)**と**Kotlinファイル**で１セットとしている。
→``フラグメントのxmlファイル``に画面構成を記述
→``フラグメントのKotlinファイル``に処理を記述
→``アクティビティのxmlファイル``にフラグメントを埋め込む

参考

https://qiita.com/nipp/items/9f81154f2524fd614ce3

####サンプルコード

```MenuThanksFragment.kt
package com.websarva.wings.android.fragmentsample

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.Fragment

//Fragmentクラスを継承
class MenuThanksFragment : Fragment() {
    //FragmentクラスのonCreareViewメソッドをオーバーライド
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        //フラグメントで表示する画面をxmlファイルからインフレートする。
        val view = inflater.inflate(R.layout.fragment_menu_thanks,container,false)
        //所属アクティビティからインテントを取得（画面、アクティビティ起動のため）
        val intent = activity?.intent //①
        //インテントから引き継ぎデータをまとめたもの(Bundleオブジェクト)を取得
        val extras = intent?.extras
        //定食名と金額を取得
        val menuName = extras?.getString("menuName")
        val menuPrice = extras?.getString("menuPrice")
        //定食名と金額を表示させるTextViewを取得
        val tvMenuName = view.findViewById<TextView>(R.id.tvMenuName)
        val tvMenuPrice = view.findViewById<TextView>(R.id.tvMenuPrice)
        //TextViewに定食名と金額を表示
        tvMenuName.text = menuName
        tvMenuPrice.text = menuPrice

        //戻るボタンを取得
        val btBackButton = view.findViewById<Button>(R.id.btBackButton)
        //戻るボタンにリスナを登録
        btBackButton.setOnClickListener(ButtonClickListener())

        //インフレートされた画面を戻り値として返す
        return view
    }

    //ボタンが押された時の処理が記述されたメンバクラス
    private inner class ButtonClickListener(): View.OnClickListener {
        override fun onClick(view: View) {
            //自分が終了するアクティビティを終了
            activity?.finish()
        }
    }
}
```

###activityプロパティ
アクティビティで使えていたメソッドがフラグメントでは使えないということがよくあります。
例えば、``findViewById()メソッド``は、``Fragmentクラス``に定義されていないので、使うことはできません。
この解決策として使用するのが``activityプロパティ``です。

``activityプロパティは、Nullable型のメンバで、セーフコール演算子(?.)を記述する必要があります。``

画面遷移の中心となるintentクラスもフラグメントにはなく、Activityクラスのプロパティなので、フラグメントで宣言する場合は、上サンプルコードの①のように使用します。

###Tips フラグメントのリスナ登録
上記の``findViewById()``のようにアクティビティで支えていたメソッドが使えないことは説明しました。
同様に、``フラグメントのリスナ``についてもアクティビティと違う部分を説明していきます。

それが``ボタンの処理``です。

>``アクティビティでは、``xmlファイルで``android:onClick``属性を使用して、設定します。
 ∟activityのonCreate()メソッド内でxmlで設定したメソッドを宣言。
``フラグメントでは、``上記の方法が取れません。
→なぜなら、対象メソッドをアクティビティのxmlファイルに記述しなければならないからです。
→では、どうするか？
→従来通りにリスナクラスを設定します。


#3,タブレットサイズ画面の作成
Androidでは、``layoutフォルダ``に``修飾子``を付けることで、どの画面用のレイアウトファイルかを指定することができ、OS側で画面サイズに応じて自動的に切り替えてくれる仕組みが用意されています。
つまり、スマホ用のxmlファイルと同名（タブレット用は最後に修飾子をつける）のxmlファイルで作業出来るので、扱いやすい。

| 修飾子 | 画面表示 |
|:-|:-|
|layout-land   |横向き表示用   |
|layout-large   |7インチ画面用   |
|layout-xlarge   |10インチ画面用   |

####サンプルコード

```activity_main.xml(xlarge)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:baselineAligned="false">

    <fragment
        android:id="@+id/fragmentMenuList"
        android:name="com.websarva.wings.android.fragmentsample.MenuListFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="0.4" />

    <FrameLayout
        android:id="@+id/menuThanksFrame"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="50dp"
        android:layout_weight="0.6"
        android:background="?android:attr/detailsElementBackground" />

</LinearLayout>
```

![スクリーンショット 2021-06-22 11.46.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1098849/10cf6478-d414-2845-79de-c807b3f117c7.png)
[参照](https://codezine.jp/article/detail/9639)

それぞれの部品を並べるイメージはボタンなどの細かい部品も、ページも同じイメージを持つと理解しやすい。
なぜ右側の画面に``FrameLayout``を起用しているかというと、
画面部品を上に上にと重ねて表示できるからです。
Fragmentのメニューリストをタップした時に、右側に画面を表示させたいので、``FrameLayout``を起用することで、それが可能になります。

#4,注文完了フラグメントのタブレット対応
リストをタップすると、スマホ画面のように画面が遷移してしまうが、同じ画面で対応出来るようにする。
もう少し細かく説明すると、メニューリストをタップした時に、スマホ画面だったら、注文完了アクティビティを起動させる、
タブレット画面だったら、フラグメントを追加する処理を記述する。

####スマホ用画面と、タブレット用画面での条件分岐を使用する。流れ
>``MenuListFragment.kt``(最初に表示されている画面の方のフラグメントファイル)に画面サイズ判定フラグ、判定処理、リストタップ時のスマホ用、タブレット用の分岐処理を記述する。
→``MenuThanksFragment``(メニューリストをタップされた後に表示される画面)に画面サイズ判定フラグ、引き継ぎデータを取得（タブレットは画面遷移しないため、フラグメント間で引き継ぎデータを取得する必要があるから）、戻るボタンを押した時の処理

どうやって判定するかと言うと、

|  |  |
|:-|:-|
| 画面がタブレットの場合  |  必ずリストフラグメントが同一アクティビティ上に存在する。 つまり、同じ画面上にメニューリストと、注文決済画面が表示されている。 |
|  画面がスマホの場合 | MenuListFragmentは存在しない。つまり、画面が遷移されているから、メニューリストの画面と、注文決済画面は１画面上に表示されていない。  |