컨스트레인트레이아웃 - 레이아웃 공통사항. (ConstraintLayout - Layout Common)

2019. 4. 17. 11:28


1. 컨스트레인트레이아웃과 레이아웃 공통 사항.

[안드로이드 레이아웃 공통사항 (Android Layout Common)]에서, 안드로이드 레이아웃에 사용할 수 있는 공통 사항에 대해 설명했습니다. 레이아웃에 포함된 뷰의 크기를 결정하는 layout_width와 layout_height, 뷰의 여백을 위한 layout_margin과 padding, 그리고 레이아웃 내 뷰 위젯의 위치 정렬을 위한 layout_gravity 등에 대해 언급했었죠.


컨스트레인트레이아웃 사용법에 대해 본격적인 설명을 하기에 앞서, 이러한 레이아웃의 공통속성들이 컨스트레인트레이아웃에서는 어떻게 다루어지는지 알아둘 필요가 있습니다. [안드로이드 레이아웃 공통사항 (Android Layout Common)]에서 살펴본 내용 정도로만 충분하다면 관련 내용을 따로 정리할 필요가 없겠지만, 컨스트레인트레이아웃에서는 레이아웃 공통사항에 대해서 미리 알아둬야 할 내용이 몇 가지 존재하기 때문입니다.

2. 컨스트레인트레이아웃에서 뷰(Child View)의 기본 위치.

컨스트레인트레이아웃에 포함된 자식(Children) 뷰에 아무런 제약도 지정하지 않으면, 자식(Children) 뷰는 컨스트레인트레이아웃 영역 내 왼쪽(Left), 위(Top)를 기준으로 배치됩니다. 이는 뷰의 갯수와 관계없이 적용되는 기본 사항이므로, 만약 모든 자식(Children) 뷰가 어떠한 제약도 가지지 않는다면, 컨스트레인트레이아웃에 포함된 뷰들은 모두 레이아웃 영역의 왼쪽 위를 기준으로 배치됩니다. 그래서 나중에 추가된 뷰가 앞서 작성된 뷰를 덮어버리게 되죠. 컨스트레인트레이아웃의 자식(Children) 뷰 배치는 뷰에 지정된 제약(Constraint)에 의해 결정되기 때문입니다.

<?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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="48sp"
        android:background="#FF0000"
        android:id="@+id/text1"
        android:text="TEXT1" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:background="#00FF00"
        android:id="@+id/text2"
        android:text="TEXT2" />

</android.support.constraint.ConstraintLayout>

ConstraintLayout 자식 뷰의 기본 위치


하지만 위의 코드는 기본 표시 위치만 나타낸 것일 뿐, 실제로 컨스트레인트레이아웃을 사용할 때는 자식(Children) 뷰에 반드시 하나 이상의 제약(또는 수평, 수직 방향에 대한 양 방향 위치 제약)을 적용해야 한다는 것을 알아두시기 바랍니다.

3. 컨스트레인트레이아웃의 자식 뷰(Child View) 크기.

레이아웃 내 자식(Child) 뷰의 크기를 지정할 때 사용하는 속성은 "layout_width"와 "layout_height" 속성입니다. 이는 모든 레이아읏이 가지는 공통 속성이죠. [안드로이드 레이아웃 공통사항 (Android Layout Common) - Layout 또는 View 위젯의 크기. (layout_width, layout_height)]에서 설명했듯이, "layout_width"와 "layout_height" 속성 값에는 wrap_content", "match_parent" 또는 치수 단위의 고정 값(e.g. 100dp, 200px)을 지정할 수 있습니다.


컨스트레인트레이아웃에 뷰를 추가할 때 아무런 제약을 사용하지 않거나, 뷰 위젯의 크기에 영향을 주는 제약이 적용되지 않은 경우는 뷰의 크기가 온전히 "layout_width"와 "layout_height"에 지정된 값에 따라 결정됩니다.

ConstraintLayout layout_width, layout_height 1


ConstraintLayout layout_width, layout_height 2


그런데 컨스트레인트레이아웃에 뷰를 추가할 때 레이아웃 또는 다른 뷰 위젯과의 위치 관계를 고려하여 뷰 크기를 결정해야 한다면, "layout_width"와 "layout_height" 속성만으로 뷰 위젯의 크기를 조절하기 힘든 상황이 발생합니다. 이런 경우 개발자가 선택할 수 있는 방법은, 뷰의 크기를 제약(Constraint)을 사용해 결정하고, "layout_width"와 "layout_height" 속성에는 "제약에 맞춤(MATCH_CONSTRAINT)"이라는 의미의 값을 지정하는 것입니다. 바로 "0dp" 값을 사용하는 것이죠.


설명만으로는 잘 와닿지 않으니, 이런 상황을 설명할 수 있는 예를 하나 들어볼까요? 아래 그림과 같이, 텍스트뷰 두 개를 가로 방향으로 나란히 배치하는 경우를 가정해 보겠습니다.

ConstraintLayout wrap_content example 1


일단 두 개의 텍스트뷰를 나란히 배치하는 코드는 아래와 같이 작성할 수 있습니다. 그리고 그 결과는, "layout_width" 속성 값을 "wrap_content"로 지정하였으므로 두 개의 텍스트뷰는 모두 텍스트 길이만큼의 너비를 가집니다.

ConstraintLayout wrap_content example 2


자, 이제 두 번째 텍스트뷰가 첫 번째 텍스트뷰의 너비를 제외한 영역에 가득차게 만들어보겠습니다.

ConstraintLayout wrap_content example 3


나중에 설명하겠지만, 컨스트레인트레이아웃에서 자식(Child) 뷰의 오른쪽 사이드(Side)를 부모 레이아웃의 오른쪽에 맞추는 방법은 "layout_constraintRight_toRightOf" 속성 값에 "parent"를 지정하면 됩니다.

ConstraintLayout wrap_content example 4


하지만 원하는 결과가 나오지 않네요. 두 번째 텍스트뷰의 왼쪽과 오른쪽 사이드를 지정한 것 까진 좋았는데, 텍스트뷰의 너비가 텍스트 길이에 맞춰져 버렸습니다. 아마도 "layout_width" 속성에 "wrap_content"를 지정했기 때문인 것 같습니다. 그렇다면 "match_parent"를 사용하면 문제가 해결될 것 같네요. 한번 해보죠.

ConstraintLayout match_parent example 1


이런, "match_parent"를 지정했더니, 값의 의미 그대로 두 번째 텍스트뷰가 레이아웃을 가득 채워버렸습니다.


음, 뷰의 너비(width)를, 특정 뷰를 제외한 나머지 영역에 가득채우려고 한 것 뿐인데, "wrap_content"도 안되고 "match_parent"도 안되네요. 분명 사용한 제약들(layout_constraintLeft_toRightOf="@id/text1", layout_constraintRight_toRightOf="parent")은 문제가 없는 것 같은데, 도대체 왜 안되는 걸까요? 그럼 이제 어떤 방법을 사용해야 하는 걸까요? 아예 무식하게(?) dp 단위의 고정 값을 사용해야 하는 걸까요?


자, 그럼 여기서, 앞에서 언급했던 내용을 다시 확인해볼까요?


"layout_width"와 "layout_height" 속성만으로 뷰 위젯의 크기를 조절하기 힘든 상황.

그리고 그 다음, 바로 해결 방법을 제시했었죠.


뷰의 크기를 제약(Constraint)을 사용해 결정하고, "layout_width"와 "layout_height" 속성에는 "제약에 맞춤(MATCH_CONSTRAINT)"이라는 의미의 값을 지정하는 것입니다. 바로 "0dp" 값을 사용하는 것이죠.

아래와 같이 코드를 수정해보겠습니다.

ConstraintLayout match_constraint example 1


이제, 만들고자 하는 화면이 표시되었습니다.


컨스트레인트레이아웃에서 뷰의 크기와 관련된 내용은 아래와 같이 정리될 수 있겠네요.


  • 뷰를 자신의 컨텐츠 크기에 맞추려면, layout_width 또는 layout_height 속성에 "wrap_content"를 지정.
  • 뷰의 크기를 제약(Constraint)에 맞추려면, layout_width 또는 layout_height 속성에 "0dp"(=MATCH_CONSTRINT)를 지정.
  • "match_parent" 값은 사용 가능하나, 사용하지 않는 것을 권고.


컨스트레인트레이아웃에서 뷰의 크기(Dimension)과 관련된 내용은 추후 조금 더 자세히 다루도록 하겠습니다.

4. 컨스트레인트레이아웃 뷰 위젯 간 여백.

레이아웃 내 자식(Child) 뷰의 여백를 지정할 때 사용하는 속성은 "layout_margin" 입니다. 이 또한 모든 레이아읏이 가지는 공통 속성입니다. [안드로이드 레이아웃 공통사항 (Android Layout Common) - 2.2 Layout 또는 자식(Children) View 위젯 요소 간 여백. (layout_margin, padding)]에서 설명했었죠.


기본적으로 [안드로이드 레이아웃 공통사항 (Android Layout Common) - 2.2 Layout 또는 자식(Children) View 위젯 요소 간 여백. (layout_margin, padding)]에서 설명한 "layout_margin" 속성들은 컨스트레인트레이아웃에 동일하게 사용될 수 있습니다. 하지만 주의해야 할 사항이 하나 있는데요, "layout_margin" 속성이 적용되어 여백이 표시되려면, 반드시 해당 방향에 대한 제약(Constraint) 또한 지정되어 있어야 한다는 것입니다.


아래 예제를 통해 "layout_margin"이 적용되는 조건을 확인할 수 있습니다. 첫 번째 코드는 "layout_marginLeft" 속성에 "100dp"를 지정하였으나, 왼쪽 사이드에 어떠한 제약(Constraint)도 지정되어 있지 않아 여백이 적용되지 않습니다. 두 번째 예제는, "layout_constraintLeft_toLeftOf" 속성으로 왼쪽 사이드 제약을 지정함으로써, 왼쪽 여백 "100dp"가 적용된 것을 볼 수 있습니다.

ConstraintLayout layout_margin 1


그런데 컨스트레인트레이아웃은 기본 여백 관련 속성에 더하여, 화면에서 보여지지 않는 뷰 위젯에 대한 여백을 별도로 지정할 수 있는 속성을 제공합니다. 즉, visibility가 "View.GONE"으로 지정된 대상에 대한 여백을 따로 지정할 수 있는 것이죠.


아시다시피, 안드로이드의 뷰 위젯은 visibility와 관련하여 세 가지 값을 가질 수 있습니다. VISIBLE, INVISIBLE 그리고 GONE이 바로 그것이죠. VISIBLE은 화면에 표시되는 상태이고, INVISIBLE과 GONE은 화면에 보여지지 않는 상태입니다. 단, INVISIBLE은 자신이 속한 레이아웃 내에서 자신의 영역을 그대로 유지하며 보여지지 않는 상태이고, GONE은 화면에 보여지지 않음과 동시에 자신의 영역 마저도 없앤다는 차이가 있습니다.


컨스트레인트레이아웃에는, 이렇게 상대 위치 지정 대상이 GONE으로 변경됨에 따라 영역이 없어지는 경우에 대한 여백까지 지정 가능하게 만들어둔 것이죠.


아래 그림은 두 개의 텍스트뷰에 각각 "30dp"의 왼쪽 사이드 여백을 지정했을 때 표시되는 결과입니다.

ConstraintLayout layout_margin 2


이 상태에서 TEXT1이 GONE 상태로 변경되면, TEXT1이 차지했던 모든 영역(여백 포함)이 0(zero)으로 취급되면서 아래 그림처럼 TEXT2가 왼쪽으로 이동되어 표시됩니다.

ConstraintLayout layout_margin 3


TEXT2의 왼쪽 여백이 지정된 값에 따라 표시된 것을 확인할 수 있는데요. 이 때, "layout_goneMarginLeft" 속성을 사용하면, 대상 뷰가 GONE 상태로 변경될 때 적용될 여백을 별도로 지정해 줄 수 있습니다.

ConstraintLayout layout_margin 4


정리하자면, 컨스트레인트레이아웃에서 여백(Margin)과 관련하여 알아두어야 할 내용은 아래와 같습니다.


  • 여백과 관련된 "layout_marginXXX" 속성은 그대로 사용 가능.
  • 단, 여백이 적용되기 위해서는 해당 방향에 대한 제약(Constraint)이 반드시 지정.
  • 뷰의 visibility가 GONE으로 변경되면 크기가 0(zero)으로 취급.
  • 대상 뷰의 visibility가 GONE 상태일 때의 여백을 "layout_goneMarginXXX"를 사용하여 별도로 지정 가능.


컨스트레인트레이아웃에서 뷰의 여백(Margin)에 관련 내용은 나중에 다시 한번 설명하겠습니다.

5. 컨스트레인트레이아웃 자식 뷰의 위치 정렬.

[안드로이드 레이아웃 공통사항 (Android Layout Common) - 2.3 Layout 내의 View 위젯의 위치 정렬. (layout_gravity)]에서 레이아웃 내 자식(Child) 뷰의 위치 정렬에 대해 살펴보았습니다. "gravity", 즉, 뷰 위젯에 어느 방향으로의 중력이 작용할 것인가를 지정하는 속성이었죠.


하지만, 컨스트레인트레이아웃에서는 "layout_gravity" 속성이 적용되지 않습니다. 컨스트레인트레이아웃에서는 기본적으로 제약(Constraint)을 통해 뷰의 위치가 결정되기 때문에, "gravity"와 같은 개념은 무의미하기 때문입니다.


컨스트레인트레이아웃의 상대 위치 지정(Relative Positioning)에 대해서는 다른 글을 통해 자세히 설명하겠습니다.

6. 참고.

.END.


ANDROID 프로그래밍/LAYOUT