My Note Pad

エンジニアリングや日々の雑感を書いていきます

【Android学習】FragmentをKotlinでやってみる

今更感がありますが先日、Udemyを眺めていたところ、以下のコースが目に留まったので、Android開発の学習を進めています。 ※セクション3:Java Deep Diveなど、自分には必用なさそうな部分は省いています。

www.udemy.com

レッスン自体はAndroid N向けの内容のため、最新のAndroid Studioなどではlayoutを作るときに若干勝手が違って苦労する部分もありますが、コース自体はよくできているなという印象です。

しかし、Fragmentを使いたくなったのですがコース内を選択してもヒットしなかったので、こちらの記事を参考に試してみました。

qiita.com

また、Javaでそのまま写経するのもな・・という感じだったので、Kotlinで書いてみました。

レイアウトファイルの作成

まずはFragmentを作成します。
HogeFragmentとかだと味気ないのでにQiitaの例にmakerbrandという項目を追加して、CarDetailFragmentという名前にしてみました。

Fragment作成はapp -> New -> Fragment -> Fragment(Blank)で空のFragmentを作成しました。

レイアウト fragment_car_detail.xml はこんな感じになります。 f:id:yuki10k:20180429213647p:plain f:id:yuki10k:20180429213654p:plain

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

    <!-- TODO: Update blank fragment layout -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/helloText"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="Hello Fragment"
            android:textAlignment="center"
            android:textSize="24sp" />
        <Button
            android:id="@+id/numberButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="1" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/makerText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="maker" />
            <TextView
                android:id="@+id/brandText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="brand" />
        </LinearLayout>
    </LinearLayout>
</FrameLayout>

Fragmentのコード作成

CarDetailFragment.ktになります。
デフォルトでいろいろメソッドが宣言されているのですが、一旦消して書き直しています。

Kotlinならではのイディオムがあったりしますが、難しいことはないと思います。
断片的に書いていって、最後に全体を書きます。

まずは変数宣言です。
lateinitで後で初期化するよと明示しています。

    private lateinit var mTextView: TextView
    private lateinit var maker: String
    private lateinit var brand: String

続いてcompanion object。
定数やJavaでいうところのstaticメソッドはここに定義しました。
createInstanceメソッドでは、インスタンス生成時にパラメーターを受け取って、Bundleに渡してFragmentのargumentsにセットしています。

    companion object {
        private const val KEY_MAKER = "maker_name"
        private const val KEY_BRAND = "brand_name"

        fun createInstance(maker:String, brand:String): CarDetailFragment {
            val carDetailFragment = CarDetailFragment()
            val args = Bundle()
            args.putString(KEY_MAKER, maker)
            args.putString(KEY_BRAND, brand)
            carDetailFragment.arguments = args
            return carDetailFragment
        }
    }

あとはFragmentのライフサイクルメソッドを実装していきます。
ライフサイクルは公式の図がわかりやすいと思います。

developer.android.com

今回は、

  • onCreate ・・・ パラメータを受け取ってメンバ変数にセット
  • onCreateView ・・・ Viewの生成
  • onViewCreated ・・・ View生成後の操作

を実装しています。
全体は↓こんな感じになりました。

// packageは省略

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView

class CarDetailFragment : Fragment() {

    private lateinit var mTextView: TextView
    private lateinit var maker: String
    private lateinit var brand: String

    companion object {
        private const val KEY_MAKER = "maker_name"
        private const val KEY_BRAND = "brand_name"

        fun createInstance(maker:String, brand:String): CarDetailFragment {
            val carDetailFragment = CarDetailFragment()
            val args = Bundle()
            args.putString(KEY_MAKER, maker)
            args.putString(KEY_BRAND, brand)
            carDetailFragment.arguments = args
            return carDetailFragment
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val args = arguments
        if (args == null) {
            maker = ""
            brand = ""
        } else {
            maker = args.getString(KEY_MAKER)
            brand = args.getString(KEY_BRAND)
        }
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        mTextView = view.findViewById(R.id.helloText)
        view.findViewById<Button>(R.id.numberButton).setOnClickListener {
            mTextView.text = "${mTextView.text}!"
        }

        val makerText = view.findViewById<TextView>(R.id.makerText)
        makerText.text = maker

        val brandView = view.findViewById<TextView>(R.id.brandText)
        brandView.text = brand
    }
}

Fragmentを呼び出すActivityの作成

こちらもFragmentと同じように app -> New -> Activity -> Blank Activityで作成します。

LinearLayoutだけ定義し、中身は空っぽにしておきます。

f:id:yuki10k:20180429221650p:plain

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CarDetailActivity">

    <LinearLayout
        android:id="@+id/carDetailContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"></LinearLayout>
</android.support.constraint.ConstraintLayout>

CarDetailActivity.ktは以下のような感じになりました。
パラメーターとして

という文字をFragmentに対して渡しています。

// packageは省略

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class CarDetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_car_detail)

        // コードからフラグメントを追加
        if (savedInstanceState == null) {
            val transaction = supportFragmentManager.beginTransaction()
            transaction.add(R.id.carDetailContainer, CarDetailFragment.createInstance("TOYOTA", "プリウス"))
            transaction.commit()
        }
    }
}

実際に実行して、CarDetailActivityを開くと、以下のようにFragmentの内容が表示され、パラメータの受け渡しが行われていることが確認できました。

f:id:yuki10k:20180429222155p:plain

最初はなかなかとっつきにくいなーと思っていましたが、実際に触ってコードを書いていくと、「あーなるほど」という感じで少しづつ理解ができてきました。

引き続き時間を見つけて色々実装を試してみたいと思います。