Androidアプリ開発初級編(ConstraintLayout)

今回はConstraintLayoutを使ってレイアウトを平面的に組むことにより複雑になりやすいView階層を出来るだけなくしてレイアウトを組めるようにするための学習になっていました。

■事前準備

Android StudioからNew ProjectでEmpty Activity ・ Kotlin を選択してプロジェクトを作成してください。

アプリモジュールのbuild.gradleで以下のコードを追加します。

android {
    ....
    dataBinding {
        enabled = true
    }
    ....
}

■サンプルアプリの動作

■ConstraintLayout

res -> layout -> activity_main.xml を下記のように修正します。

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <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/constraint_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/box_one_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/box_one"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio=""
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0" />

        <TextView
            android:id="@+id/box_two_text"
            style="@style/WhiteBox"
            android:layout_width="130dp"
            android:layout_height="130dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:text="@string/box_two"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/box_one_text" />

        <TextView
            android:id="@+id/box_three_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:text="@string/box_three"
            app:layout_constraintBottom_toTopOf="@+id/box_four_text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/box_two_text"
            app:layout_constraintTop_toTopOf="@+id/box_two_text" />

        <TextView
            android:id="@+id/box_four_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/box_four"
            app:layout_constraintBottom_toTopOf="@+id/box_five_text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/box_two_text"
            app:layout_constraintTop_toBottomOf="@+id/box_three_text" />

        <TextView
            android:id="@+id/box_five_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:text="@string/box_five"
            app:layout_constraintBottom_toBottomOf="@+id/box_two_text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/box_two_text"
            app:layout_constraintTop_toBottomOf="@+id/box_four_text" />

        <TextView
            android:id="@+id/label_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:fontFamily="@font/roboto"
            android:text="@string/how_to_play"
            android:textSize="24sp"
            app:layout_constraintBaseline_toBaselineOf="@+id/info_text"
            app:layout_constraintStart_toStartOf="parent" />

        <TextView
            android:id="@+id/info_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/tap_the_boxes_and_buttons"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toEndOf="@+id/label_text"
            app:layout_constraintTop_toBottomOf="@+id/box_five_text"
            app:layout_constraintVertical_bias="0.018" />

        <Button
            android:id="@+id/red_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="@string/box_red"
            app:layout_constraintBaseline_toBaselineOf="@+id/yellow_button"
            app:layout_constraintEnd_toStartOf="@+id/yellow_button"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/yellow_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/box_yellow"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/green_button"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/red_button"
            app:layout_constraintTop_toBottomOf="@+id/info_text"
            app:layout_constraintVertical_bias="1.0" />

        <Button
            android:id="@+id/green_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:text="@string/box_green"
            app:layout_constraintBaseline_toBaselineOf="@+id/yellow_button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/yellow_button" />

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

それでは一つ一つ追っていきます。

<layout>は前回もデータバインディング編で使いましたね。これはデータバインディングに必要なものです。今回はビューバインディングにしか使っていませんのでデータ定義はありません。<ConstraintLayout>は特に変わったところはありませんね、LinearLayoutと違い、orientationの指定がないぐらいです。

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <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/constraint_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

layout_marginXXX は各方向に対しViewの外側にマージンを設けています。

app:layout_constraintBottom_toBottomOf=”parent”

これがConstraintLayout特有のパラメータになります。Constraint = 制約です。このTextViewのBottomは親View(ConstraintLayout)のBottomに合わせるという意味になります。

app:layout_constraintLeft_toLeftOf=”parent”

app:layout_constraintRight_toRightOf=”parent”

app:layout_constraintTop_toTopOf=”parent”

同じようにTextViewの左側、右側、Topも親Viewに合わせるという意味です。

ここで注意が必要なのが、レイアウトの幅になります。

android:layout_width=”0dp”
android:layout_height=”wrap_content”

ここで幅の指定が0dpとなっていますが、これは”制約内で幅を最大とする”という意味になります(match_constraint)。

このViewには幅が無い?と誤解しないように注意してください。

wrap_contentの意味合いは他のレイアウトと変わりません。

app:layout_constraintHorizontal_bias=”0.0″

app:layout_constraintVertical_bias=”0.0″

このバイアスの指定がないと、制約内で均等に配置されることになります。

今回は0となっていますので、制約の上側、制約の左側に最大限寄る表示となります。

指定値は0.0~1.0の間が基本ですが、範囲外を指定すると画面外に表示されることになります。

Viewの幅指定が0dpで最大となっているので、horizontal_biasの指定は無くても特にレイアウトに変化はありません。

        <TextView
            android:id="@+id/box_one_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/box_one"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0" />

こちらのTextViewは幅と高さを130dp固定としているため、正方形となります。

左側は親View、上側はbox_one_textのViewに制約されています。それぞれ制約方向に対しマージンも設定されています。

でもこちらにはバイアスが設定されていません。

上下に制約が設定されている、もしくは左右に制約が設定されていないとそもそもバイアスが存在せず設定のしようがないからです。

そのため、このViewの配置はparentの左側に最大限まで寄っていて、box_one_text の下側に最大限まで寄っています。

        <TextView
            android:id="@+id/box_two_text"
            style="@style/WhiteBox"
            android:layout_width="130dp"
            android:layout_height="130dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:text="@string/box_two"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/box_one_text" />

3つ目のViewは、幅が最大で、左と右に16dpのマージンが設定されています。

このViewの下側は4つ目のViewの上側へ

このViewの右側は親Viewの右側へ

このViewの上側は2つ目のViewの上側へ

このViewの左側は2つ目のViewの右側へ

それぞれ制約が行われています。

biasがありませんが、2つ以上のViewが縦方向もしくは横方向に連結している場合、その方向に対してのbiasが効きません。

そのため、どちらかにViewを寄せたい場合などは適宜制約を外して寄せるようにするか、marginを設定する必要があります。

もしくはspaceというwigetを配置可能ですので均等にスペースを取りつつ間隔を開けたい場合などは利用できると思います。

このあたりがConstraintLayoutで一番難解な部分だと個人的には感じていますので、いろいろ公式リファレンスを参照しながら試してみてください。

        <TextView
            android:id="@+id/box_three_text"
            style="@style/WhiteBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:text="@string/box_three"
            app:layout_constraintBottom_toTopOf="@+id/box_four_text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/box_two_text"
            app:layout_constraintTop_toTopOf="@+id/box_two_text" />

同じようにbox_for_text、box_five_textが配置されているのでこちらは省略します。

label_textではbaselineという制約を使用しています。こちらはテキストの文字サイズが違う場合に役に立つ制約となっており、ベースラインを合わせて表示してくれるものになります。

一方で制約位置はinfo_text側で制御しています。

        <TextView
            android:id="@+id/label_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:fontFamily="@font/roboto"
            android:text="@string/how_to_play"
            android:textSize="24sp"
            app:layout_constraintBaseline_toBaselineOf="@+id/info_text"
            app:layout_constraintStart_toStartOf="parent" />

        <TextView
            android:id="@+id/info_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/tap_the_boxes_and_buttons"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toEndOf="@+id/label_text"
            app:layout_constraintTop_toBottomOf="@+id/box_five_text"
            app:layout_constraintVertical_bias="0.018" />

次にred_button、yellow_button、green_buttonの3つは横には均等に配置しており、縦方向の制約は中央のyellow_buttonがバイアス指定を行っています。

一見すると2つ以上のViewが縦に配置されているのでバイアスが効かないように感じますが、それぞれの制約が縦方向に相互に連結していないためバイアスは設定可能です。

試しにinfo_textのconstraintBottom_toTopOf=”@+id/yellow_button”とするとバイアス指定が出来なくなることが分かると思います。

        <Button
            android:id="@+id/red_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="@string/box_red"
            app:layout_constraintBaseline_toBaselineOf="@+id/yellow_button"
            app:layout_constraintEnd_toStartOf="@+id/yellow_button"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/yellow_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/box_yellow"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/green_button"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/red_button"
            app:layout_constraintTop_toBottomOf="@+id/info_text"
            app:layout_constraintVertical_bias="1.0" />

        <Button
            android:id="@+id/green_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:text="@string/box_green"
            app:layout_constraintBaseline_toBaselineOf="@+id/yellow_button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/yellow_button" />

■色変更処理の実装

こちらについては以前に行ったボタン処理の実装と本質的に何も変わりません。各Viewが押されたタイミングで単に色を変えているだけです。

MainActivity.kt

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        setListeners()
    }

    private fun setListeners() {
        binding.apply {
            val clickableViews: List<View> = listOf(
                boxOneText,
                boxTwoText,
                boxThreeText,
                boxFourText,
                boxFiveText,
                constraintLayout,
                redButton,
                greenButton,
                yellowButton
            )

            for (item in clickableViews) {
                item.setOnClickListener { makeColored(it) }
            }
        }
    }

    private fun makeColored(view: View) {
        when (view.id) {
            R.id.box_one_text -> view.setBackgroundColor(Color.DKGRAY)
            R.id.box_two_text -> view.setBackgroundColor(Color.GRAY)

            R.id.box_three_text -> view.setBackgroundResource(android.R.color.holo_green_light)
            R.id.box_four_text -> view.setBackgroundResource(android.R.color.holo_green_dark)
            R.id.box_five_text -> view.setBackgroundResource(android.R.color.holo_green_light)
            
            R.id.red_button -> binding.boxThreeText.setBackgroundResource(R.color.my_red)
            R.id.yellow_button -> binding.boxFourText.setBackgroundResource(R.color.my_yellow)
            R.id.green_button -> binding.boxFiveText.setBackgroundResource(R.color.my_green)
            else -> view.setBackgroundColor(Color.LTGRAY)
        }
    }
}

メインはレイアウトの使い方になりますので、いろいろ試してConstraintLayoutについての理解を深めてみましょう。

■まとめ

・ConstraintLayoutは制約によってViewの配置を変える

・幅、高さの0dpは最大値指定

・2つ以上のViewが縦方向・横方向に相互に制約が行われた場合はバイアス指定が出来ない

このあたりを理解しておけば、混乱することは無くなるのかなと個人的に思っています。特に私はバイアスに関しては最初、理解出来ずに少し苦しみました。他にもたくさんの制約方法がありますので是非試してみてください。

それでは。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です