今さら聞けないRelativeLayoutの話

  • 136
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

「俺はLinearLayoutしか使わない」
そんな人みなさんの周りにはいませんでしょうか?

例えばListView内の要素でこんなレイアウトを組むことになった場合、みなさんならどのような構成でレイアウトを組むでしょうか?

components_lists_keylines_two6 (1).png

まず考えられるのは水平方向のLinearLayoutの中に垂直方向のLinearLayoutを重ねるパターンだと思います。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:padding="16dp">

    <ImageView
        android:id="@+id/image_icon"
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:layout_marginRight="16dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text_primary"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/text_secondary"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

</LinearLayout>

もちろんこれでも十分機能します。でもこれ、RelativeLayoutでも書くことができるんですね。

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <ImageView
        android:id="@+id/image_icon"
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:layout_marginRight="16dp"/>

    <TextView
        android:id="@+id/text_primary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image_icon"/>

    <TextView
        android:id="@+id/text_secondary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_primary"
        android:layout_toRightOf="@id/image_icon"/>

</RelativeLayout>

ネストが一つ減ってスッキリしました。今回のような単純なレイアウトの場合はどちらでもそれほど大きな差はないと思うんですが、もっと複雑なレイアウトの場合にLinearLayoutでいくつもネストして配置するのはパフォーマンスにも影響しますし、何より見た目がよろしくないですよね。

RelativeLayoutとは

AndroidのViewGroupの中でLayoutと呼ばれる存在はいくつか存在しますが、主に使用される頻度が高いものはこちらの3つだと思っています。

  • FrameLayout(Viewを重ねあわせて配置)
  • LinearLayout(Viewを縦もしくは横の一方向に順番に配置)
  • RelativeLayout(View同士の位置関係から相対的に配置)

個人の感覚ですが、上から単純なレイアウトの時に使う順に並べています。
FrameLayoutは主にFragmentを配置するViewに指定したり、プログレスを重ねて出したい場合などに使うことが多いですね。
LinearLayoutは何も考えずに要素を順番に並べるだけで済むレイアウトで使用します。あとはどうしてもlayout_weightの力が必要なんだ頼むという時も活躍してくれます。LinearLayoutがよく使われるのはこれも大きな理由じゃないでしょうか。
RelativeLayoutはその他のレイアウトで使用します。その他と書きましたが、上記でFrameLayoutとLinearLayoutを使用したレイアウトもRelativeLayoutで置き換えることが可能な場合がほとんどです。これはRelativeLayoutが要素を相対的に配置できるためあらゆるレイアウトに対応できるということなんですね。

ではその相対的に配置するというのは具体的にどういうことなんでしょうか。先程のレイアウトを例に見てみましょう。

<!-- 実際のレイアウトではもう少しデザインに沿った調整が必要 -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <!-- まずはこのImageViewを基準に考える。 -->
    <ImageView
        android:id="@+id/image_icon"
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:layout_marginRight="16dp"/>

    <!-- RelativeLayoutで何も指定しない場合は左上に配置される。実際はこのような属性が指定されてると考えていい。
        <ImageView
            android:id="@+id/image_icon"
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:layout_marginRight="16dp"/>
    -->

    <!-- ImageViewの右側に配置。widthにmatch_parentを指定しても大丈夫。 -->
    <TextView
        android:id="@+id/text_primary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image_icon"/>

    <!-- ImageViewの右側に配置。かつ上記のTextViewの下に配置する。 -->
    <TextView
        android:id="@+id/text_secondary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_primary"
        android:layout_toRightOf="@id/image_icon"/>

</RelativeLayout>

まず初めにそのレイアウトの中で基準となるViewを一つ決めましょう。今回の場合はImageViewを基準にします。RelativeLayout内で何の属性も指定しない場合はFrameLayoutと同様でViewは左上に配置されます。これはImageViewにandroid:layout_alignParentTop="true"android:layout_alignParentLeft="true"を指定した場合と同様の配置です。なので例えば、いや俺はアイコンを右側に置きたいんだという場合はandroid:layout_alignParentLeft="true"の代わりにandroid:layout_alignParentRight="true"を指定してましょう。

layout_alignParent〜

さて、このlayout_alignParent〜は何なのかといいますと、字面からなんとなく想像できるとは思うんですが、この属性を指定したViewの親のView、つまりRelativeLayout上で上寄せにする、左寄せにする、右寄せにするなどといったことを指定することができます。
他にもlayout_centerInParentなど、親のViewに対して位置を指定する属性は以下のものがあります。

属性 効果
layout_alignParentTop trueを指定した場合、親のViewに対して上寄せで配置
layout_alignParentBottom trueを指定した場合、親のViewに対して下寄せで配置
layout_alignParentLeft trueを指定した場合、親のViewに対して左寄せで配置
layout_alignParentRight trueを指定した場合、親のViewに対して右寄せで配置
layout_alignParentStart trueを指定した場合、親のViewに対して左寄せで配置
layout_alignParentEnd trueを指定した場合、親のViewに対して右寄せで配置
layout_centerHorizontal trueを指定した場合、親のViewに対して水平方向中央寄せで配置
layout_centerVertical trueを指定した場合、親のViewに対して垂直方向中央寄せで配置
layout_centerInParent trueを指定した場合、親のViewに対して水平垂直方向共に中央寄せで配置

ちなみにlayout_alignParentStartlayout_alignParentEndについて左寄せ、右寄せと記載したんですが、これは言語によって左右が反転します。世界には昔の日本のように右から左に文字を読む言語がありまして、そういった言語に対応する場合にLeftやRightといった表現だけでは対応するのが大変だからと新たに用意されたのがStartやEndといった表現なんですね。もしそういった言語への対応が必要な場合は上の表の限りではないのでご注意ください。

layout_toLeftOf,layout_toRightOf

次に1つ目のTextViewを配置します。先程説明した通り、何も属性を指定しない場合はViewは左上に配置されるので、ImageViewの上に重なるように配置されてしまいます。これをImageViewの右側に配置したい。そこで指定するのがandroid:layout_toRightOf="@id/image_icon"です。こちらの属性は指定したidのViewに対して右側にViewを配置するという効果を持っています。

属性 効果
layout_toLeftOf 指定したidのViewに対して左側に配置
layout_toRightOf 指定したidのViewに対して右側に配置
layout_toStartOf 指定したidのViewに対して左側に配置
layout_toEndOf 指定したidのViewに対して右側に配置

layout_above,layout_below

さて最後に2つ目のTextViewを配置します。ImageViewの右側への配置に関しては先程配置した1つ目のTextViewと同じ属性を指定すれば問題なさそうですが、そのままだと1つ目に配置したTextViewに重なるように配置されてしまいます。これを1つ目のTextViewの下側に配置する場合に指定するのがandroid:layout_below="@id/text_primary"です。こちらは先ほど説明したlayout_toLeftOflayout_toRightOfと同じように指定したidのViewに対して下側にViewを配置するという効果を持っています。ちなみに上側や下側という表現はy軸上の話でz軸上の重なりを制御するものではありません。

属性 効果
layout_above 指定したidのViewに対して上側に配置
layout_below 指定したidのViewに対して下側に配置

以上で例のレイアウトは配置できました。以下に簡単な流れを記載しておきます。

  1. ImageViewを親のViewに対して上寄せ左寄せで配置
  2. 1つ目のTextViewをImageViewに対して右側に配置、かつ親のViewに対して上寄せで配置
  3. 2つ目のTextViewをImageViewに対して右側に配置、かつ1つ目のTextViewに対して下側に配置

相対的に配置するということがなんとなく理解できたでしょうか。ここまで記載した属性だけでもRelativeLayoutは十分活用できると思うんですが、他にも幾つかあるので以下に紹介していきます。

layout_align〜

属性 効果
layout_alignTop 指定したidのViewの上端の位置とViewの上端を揃えて配置
layout_alignBottom 指定したidのViewの下端の位置とViewの下端を揃えて配置
layout_alignLeft 指定したidのViewの左端の位置とViewの左端を揃えて配置
layout_alignRight 指定したidのViewの右端の位置とViewの右端を揃えて配置
layout_alignStart 指定したidのViewの左端の位置とViewの左端を揃えて配置
layout_alignEnd 指定したidのViewの右端の位置とViewの右端を揃えて配置
layout_alignBaseline 指定したidのViewのベースラインの位置とViewのベースラインを揃えて配置

初めに紹介したlayout_alignParent〜と似たような名前でなんとなく効果は想像できるかと思いますが、layout_align〜は指定したidのViewに対して上端の位置や右端の位置を揃えるように配置することができます。例えば先程のレイアウトはこのように1つ目のTextViewを基準として2つ目のTextViewを配置することもできます。

<!-- 省略 -->
    <TextView
        android:id="@+id/text_primary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image_icon"/>

    <!-- 上記のTextViewと左端右端を揃えて配置する -->
    <TextView
        android:id="@+id/text_secondary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_primary"
        android:layout_alignLeft="@id/text_primary"
        android:layout_alignRight="@id/text_primary"/>
<!-- 省略 -->

一つだけこの中で特殊なものがlayout_alignBaselineで、指定したidのViewのベースラインの位置とViewのベースラインを揃えて配置することができます。ここで言うベースラインとはViewの下端ではなくView内で表示されるテキストのベースラインになります。ImageViewなどでは指定しても効果が得られないので注意が必要です。

layout_alignWithParentIfMissing

属性 効果
layout_alignWithParentIfMissing trueを指定した場合、layout_toLeftOfやlayout_alignTopなどで指定したidのViewが存在しなかった場合に親のViewに対して効果を適用する

次にlayout_alignWithParentIfMissing="true"を指定すると、layout_toLeftOflayout_alignTopなどで指定したidのViewが存在しなかった場合に、代わりに親のViewに対して指定した属性の効果を適用することができます。残念ながら使ったことがないので便利ポイントがイマイチよくわかってない。

ignoreGravity

属性 効果
ignoreGravity 指定したidのViewはgravityで指定した効果が適用されない

最後にこちらは親レイアウトに指定する属性ですが、ignoreGravityを使うと、指定したidのViewはgravityで指定した効果が適用されなくなります。こちらも残念ながらあまり使ったことはありません。

最後に

ここまでRelativeLayout最高だよLinearLayout使ってネストするのやめようよみたいな話を書いてきたんですが、レイアウトでネストすることが必ずしも悪いわけではないと思っています。例えばレイアウト内で表示非表示を切り替える部分をまとめたり、レイアウトの構成上いくつかネストして小分けにしたほうが管理しやすくなる場合も多々あると思います。そのあたりは臨機応変に、ただいざという時に使えないのはダメだよねというお話でした。

おまけ

PercentRelativeLayoutってのもあるよ!