見出し画像

【Android】カスタムビューで複数のビューをまとめる

こんにちは!
Androidエンジニアのたぼーんです。
前回はカスタムビューの基本的な使い方について紹介しました。
記事についてはこちらをご参照ください!

今回は、複数のビューをカスタムビューを用いてまとめる方法を紹介します。
これによってビューの再利用性が高まり、コードの可読性や保守性の向上に利用することが可能です。

今回作るもの

こちらの仕様の画面を作成します。

  • UserNameというラベル(TextView)と入力フォーム(EditTextView)がある

  • ユーザー名に何も入力せずに送信すると、エラーメッセージが表示される

  • 何かを入力して送信すると、エラーメッセージが消える

構成

  • LabeledInputField(カスタムビュークラス)

    • カスタムビューで、ラベル、入力フォーム、エラー文字列を定義

    • 要素の取得・挿入のための関数を持つ

  • MainFragment

    • ボタン押下時の表示制御を行う

    • 対応するlayoutファイルからは、ラベルを指定できるようにする

全体のレイアウトを作成

まずは全体像を掴みやすいようにMainFragmentに対応するLayoutを記載します。
注目するべきは以下となります。

  • レイアウトにカスタムビューを指定すると、ラベル、入力フォーム、エラーメッセージの定義が作成される

  • ラベル名はlayoutから自由に設定できるように、「app : labelText」を独自に定義

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.MainFragment">

    <!--    カスタムビュークラス  -->
    <com.example.enjoy_android.CustomView.LabeledInputField
        android:id="@+id/labelField"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:labelText="UserName"  <-----  ここでLabel名を指定できる
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"/>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="送信"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/labelField"
        android:layout_marginStart="12dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Fragmentから表示制御を行う

Fragmentで送信ボタン押下時の表示制御を行います。
カスタムビュークラスに定義したgetterとsetterを利用できます。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val userNameField = view.findViewById<LabeledInputField>(R.id.labelField)
    val button = view.findViewById<Button>(R.id.button)
    button.setOnClickListener {
        val userName = userNameField.getTextInput()
        if(userName.isEmpty()){
            userNameField.setInputError("1文字以上の文字列を入力してください")
        }else{
            userNameField.clearInputError()
        }
    }
}

カスタムビュークラスの作成

ラベル、入力フォーム、エラーメッセージの要素を持ったカスタムビュークラスを作成します。
ラベルの文字列は、layoutから「app:labelText」で指定した値をattrs.xmlの定義を元にして取得します。

class LabeledInputField (context : Context, attrs : AttributeSet) : ConstraintLayout(context , attrs){
    private var label : TextView
    private var errorMessage : TextView
    private var inputField : EditText
    init{
        val view = LayoutInflater.from(context).inflate(R.layout.labeled_input_field , this , true)
        label = view.findViewById(R.id.label)
        errorMessage = view.findViewById(R.id.error_message)
        inputField = view.findViewById(R.id.input_field)
        val typedArray = context.obtainStyledAttributes(attrs , R.styleable.LabeledInputField)
        label.text = typedArray.getString(R.styleable.LabeledInputField_labelText)
        typedArray.recycle()
    }

    fun getTextInput() : String{
        return inputField.text.toString()
    }

    fun setInputError(message : String){
        errorMessage.text = message
        errorMessage.visibility = View.VISIBLE
    }

    fun clearInputError(){
        errorMessage.text = ""
        errorMessage.visibility = View.GONE
    }
}

attrs.xmlでは以下のようにapp:labelTextを定義しています。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LabeledInputField">
        <attr name="labelText" format="string" />
    </declare-styleable>
</resources>

また、カスタムビューのlayoutは以下の通りです

<merge 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="12dp"
            tools:text = "UserName"/>

        <EditText
            android:id="@+id/input_field"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="入力してください ..."
            app:layout_constraintStart_toEndOf="@id/label"
            app:layout_constraintTop_toTopOf="@id/label"
            app:layout_constraintBottom_toBottomOf="@id/label"
            android:layout_marginStart="8dp"/>
        <TextView
            android:id="@+id/error_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            app:layout_constraintTop_toBottomOf="@id/input_field"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="4dp"
            tools:text="1文字以上の文字列を入力してください"
            tools:visibility="visible"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</merge>

まとめ

いかがでしたでしょうか。
カスタムビューを用いて複数のビューをまとめて一つのビューとして扱う方法を紹介いたしました。
これによって、コードの再利用性が高まるので、同じようなビューをあらゆる画面で簡単に作成できるようになります
また、今回のように特定の属性だけはユーザーから定義できるように(今回はラベルをlayoutから設定できるようにした)、カスタマイズすることもできます
ぜひ、試してみてください!

この記事が気に入ったらサポートをしてみませんか?