컨스트레인트레이아웃 - 상대 위치 지정 (ConstraintLayout - Relative Positioning)

2019. 4. 19. 15:28


1. 상대적(Relative) 위치 관계를 고려하여 뷰 배치하기.

레이아웃에 뷰를 배치할 때 뷰 위젯들의 상대적 위치 관계에 따라 뷰가 표시될 영역을 지정하는 방법, 즉, 상대 위치 지정(Relative Positioning) 방법은 레이아웃을 구성함에 있어 매우 큰 이점을 가져다 줍니다. 레이아웃 중첩으로 인한 성능 이슈 고민을 줄여주고 화면 구성의 복잡성을 낮춰주기 때문에, UI 화면 구성에 소요되는 시간 비용을 아낄 수 있는 장점이 있죠.


Relative Positioning

물론 리니어레이아웃 또는 테이블레이아웃처럼, 사용 방법이 간단하고 결과 화면이 쉽게 예측되는 레이아웃에 비해 사용하기가 까다로운 것은 사실입니다. 배치하고자 하는 뷰의 상/하/좌/우에 인접한 뷰의 위치 관계와 뷰가 차지하는 영역의 크기를 고려해야 하고, 때로는 부모 레이아웃과의 위치 관계도 따져야 하기 때문에 단순히 속성 몇 개를 외우고 있다고 해서 쉽게 사용할 수 있는 레이아웃은 아닙니다.


하지만 대부분의 "개발"이 그러하듯, 원리 이해를 위한 노력과 여러 번의 시행착오를 거치다 보면, 그리 어렵지 않게 레이아웃을 구성할 수 있습니다. 특히, 상대 위치 지정(Relative Positioning) 방법은, [안드로이드 렐러티브레이아웃 (Android RelativeLayout)]을 통해 이미 확인했듯이, 매우 간결하고 효율적인 레이아웃 구성 방법입니다. 초기 진입장벽(?)만 잘 극복하면, 아주 활용성이 뛰어난 레이아웃이죠.


그렇다고 지금 다시 [안드로이드 렐러티브레이아웃 (Android RelativeLayout)]를 설명하고자 하는 것은 아닙니다.


여기서는, 렐러티브레이아웃보다 조금 더 유연하고 확장성 있게 레이아웃을 구성할 수 있는 컨스트레인트레이아웃(ConstraintLayout)에 대해 살펴볼텐데요, 특히 컨스트레인트레이아웃 여러 제약들 중, 상대 위치 지정(Relative Positioning)과 관련된 속성들에 대해 설명하겠습니다.


만약 컨스트레인트레이아웃에 대한 내용을 이 글에서 처음 접했다면, [안드로이드 컨스트레인트레이아웃 (Android ConstraintLayout)]과 [컨스트레인트레이아웃 - 레이아웃 공통사항 (ConstraintLayout Common)]을 먼저 읽어보시길 바랍니다.

2. 컨스트레인트레이아웃의 상대 위치 지정. (ConstraintLayout Relative Positioning)

컨스트레인트레이아웃의 상대 위치 지정(Relative Positioning)은 렐러티브레이아웃의 사용법과 매우 비슷합니다. 컨스트레인트레이아웃에 포함된 "자식 뷰 위젯 간 상대 위치" 또는 "자식 뷰와 레이아웃 간 상대 위치"를 수평(left, right, start, end sides) 및 수직(top, bottom sides and text baseline) 방향으로 지정하여 뷰의 위치를 결정하는 것이죠.


하지만 지정한 상대 위치가 인접한 사이드(Side)를 기준으로 배치되고, [자식 뷰 간 상대 위치], [자식 뷰와 부모 간 상대 위치], [맞춤 정렬(Alignment)] 등에 대한 속성이 각각 따로 존재했던 렐러티브레이아웃과 달리, 컨스트레인트레이아웃은 "자식 뷰 간 상대 위치"와 "자식 뷰와 부모 간 상대 위치"를 동일한 속성을 통해 지정 가능하고, 맞춤 정렬(Alignment)을 위한 속성이 별도로 존재하지 않는다는 차이가 있습니다. 이는 컨스트레인트레이아웃이 "Text Baseline"을 포함한 모든 사이드(Side)에 대해 상대적 위치를 사용할 대상(뷰 또는 부모 레이아웃)을 지정할 수 있게끔 만들어져 있기 때문입니다.


설명만으로는 무슨 말인지 쉽게 이해되지 않죠? 그럼, 좀 더 자세한 설명을 통해, 컨스트레인트레이아웃에서 제공하는 상대 위치 지정(Relative Positioning)을 위한 속성에 대해 확인해보겠습니다.

2.1 상대 위치 지정(Relative Positioning)을 위한 속성.

먼저, 컨스트레인트레이아웃의 상대 위치 지정 속성들이 아래와 같은 이름 규칙을 가진다는 것을 알아두면, 아래 속성들의 동작 결과를 이해하는데 도움이 될 것입니다.

ConstraintLayout 속성 이름 규칙



컨스트레인트레이아웃에서 제공하는 상대 위치 지정을 위한 속성에는 아래와 같은 것들이 있습니다.

  * layout_constraintLeft_toLeftOf - 뷰의 왼쪽 사이드(Side)를 대상 뷰의 왼쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 수직 방향 왼쪽 맞춤 정렬 또는 부모의 왼쪽에 맞춤. * layout_constraintLeft_toRightOf - 뷰의 왼쪽 사이드(Side)를 대상 뷰의 오른쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 주로 왼쪽에서 오른쪽으로, 뷰를 일렬로 나열할 때 사용. * layout_constraintRight_toLeftOf - 뷰의 오른쪽 사이드(Side)를 대상 뷰의 왼쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 주로 오른쪽에서 왼쪽으로, 뷰를 일렬로 나열할 때 사용. * layout_constraintRight_toRightOf - 뷰의 오른쪽 사이드(Side)를 대상 뷰의 오른쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 수직 방향 오른쪽 맞춤 정렬 또는 부모의 오른쪽에 맞춤. * layout_constraintTop_toTopOf - 뷰의 위쪽 사이드(Side)를 대상 뷰의 위쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수직 방향(vertical) 상대 위치 지정. 수평 방향 위쪽 맞춤 정렬 또는 부모의 위쪽에 맞춤. * layout_constraintTop_toBottomOf - 뷰의 위쪽 사이드(Side)를 대상 뷰의 아래쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수직 방향(vertical) 상대 위치 지정. 주로 위쪽에서 아래쪽으로, 뷰를 일렬로 나열할 때 사용. * layout_constraintBottom_toTopOf - 뷰의 아래쪽 사이드(Side)를 대상 뷰의 위쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수직 방향(vertical) 상대 위치 지정. 주로 아래쪽에서 위쪽으로, 뷰를 일렬로 나열할 때 사용. * layout_constraintBottom_toBottomOf - 뷰의 아래쪽 사이드(Side)를 대상 뷰의 아래쪽 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수직 방향(vertical) 상대 위치 지정. 수평 방향 아래쪽 맞춤 정렬 또는 부모의 아래쪽에 맞춤. * layout_constraintBaseline_toBaselineOf - 뷰의 텍스트 Baseline을 대상 뷰의 텍스트 Baseline에 맞춤. > 속성 값에는 대상 뷰의 ID 지정. > 수직 방향(vertical) 상대 위치 지정. 대상 뷰의 텍스트 Baseline에 맞춤 정렬. * layout_constraintStart_toEndOf - 뷰의 시작 사이드(Side)를 대상 뷰의 끝 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 주로 수평 방향으로 뷰를 일렬로 나열할 때 사용. > LTR(LeftToRight) 환경에서는 layout_constraintLeft_toRightOf와 동일. > RTL(RightToLeft) 환경에서는 layout_constraintRight_toLeftOf와 동일. * layout_constraintStart_toStartOf - 뷰의 시작 사이드(Side)를 대상 뷰의 시작 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 수직 방향 시작 위치 맞춤 정렬 또는 부모의 시작 위치에 맞춤. > LTR(LeftToRight) 환경에서는 layout_constraintLeft_toLeftOf와 동일. > RTL(RightToLeft) 환경에서는 layout_constraintRight_toRightOf와 동일. * layout_constraintEnd_toStartOf - 뷰의 끝 사이드(Side)를 대상 뷰의 시작 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 주로 수평 방향으로 뷰를 일렬로 나열할 때 사용. > LTR(LeftToRight) 환경에서는 layout_constraintRight_toLeftOf와 동일. > RTL(RightToLeft) 환경에서는 layout_constraintLeft_toRightOf와 동일. * layout_constraintEnd_toEndOf - 뷰의 끝 사이드(Side)를 대상 뷰의 끝 사이드(Side)에 맞춤.
> 속성 값에는 대상 뷰의 ID, 또는 "parent" 사용 가능. ("parent"=ConstraintLayout) > 수평 방향(horizontal) 상대 위치 지정. 수직 방향 끝 위치 맞춤 정렬 또는 부모의 끝 위치에 맞춤. > LTR(LeftToRight) 환경에서는 layout_constraintRight_toRightOf와 동일. > RTL(RightToLeft) 환경에서는 layout_constraintLeft_toLeftOf와 동일.

형식을 갖춰 속성에 대한 설명을 적다보니, 내용이 필요 이상으로 길어진 것 같은데요. 아래 그림을 보면, 컨스트레인트레이아웃의 상대 위치 지정(Relative Positioning) 관련 속성이 어떻게 동작하는지 좀 더 쉽게 이해되실 것 같습니다.

ConstraintLayout 자식 뷰 위젯 간 Relative Positioning


ConstraintLayout 부모 레이아웃과 자식 뷰 위젯 간 Relative Positioning


3. 컨스트레인트레이아웃의 상대 위치 지정에 대한 몇 가지 예제.

그럼 이제 몇 가지 예제를 통해, 컨스트레인트레이아웃의 제약들을 사용하여 레이아웃을 구성하는 방법을 알아보겠습니다.

3.1 자식(Child) 뷰를 부모 레이아웃의 가운데 위치시키기.

첫 번째 예제는 자식(Child) 뷰를 부모 레이아웃의 한 가운데 위치시키는 예제입니다.

ConstraintLayout 자식(Child) 뷰를 부모 레이아웃의 가운데 위치시키기


여기서 눈여겨 볼 부분은 크게 두 가지인데요, 첫 번째는 각 사이드(Left, Top, Right, Bottom)를 부모의 각 사이드(Left, Top, Right, Bottom)에 맞춘 것입니다. 그런데 이것만 보면, 텍스트뷰가 부모 레이아웃의 크기에 맞춰져야 할 것 같은데, 그렇지 않고 자신의 영역 크기를 그대로 가지죠? 이 점이 바로 두 번째 눈여겨 볼 부분입니다. 텍스트뷰의 layout_width와 layout_height를 "wrap_content"로 지정함으로써, 자신의 크기는 텍스트 길이에 맞추도록 만든 것이죠.


그럼, 텍스트뷰 크기를 부모 레이아웃의 영역에 맞추려면 어떻게 해야 할까요?

3.2 자식(Child) 뷰를 부모 레이아웃에 가득차게 만들기.

자식(Child) 뷰를 부모 레이아웃에 맞추려면, 앞서 작성한 제약은 그대로 두고, 대신 layout_width와 layout_height를 "0dp"로 지정하여 텍스트뷰의 크기가 지정된 제약에 따르도록 만들면 됩니다.

ConstraintLayout 자식 뷰를 부모 레이아웃에 가득차게 만들기


참고로 텍스트뷰의 "TEXT"가 왼쪽,위에 표시된 건 텍스트뷰의 gravity를 따로 지정하지 않았기 때문입니다. 텍스트를 가운데로 위치시키려면 gravity 속성에 "center" 값을 지정하면 됩니다.

3.3 수평(Horizontal) 또는 수직(Vertical) 방향으로 뷰 나열하기.

앞의 예제들은 부모 레이아웃을 기준으로 뷰를 배치하는 방법에 대한 내용인데요, 이번에는 자식 레이아웃 간 상대 위치를 지정하는 방법, 그 중에서 수평(Horizontal) 또는 수직(Vertical) 방향으로 뷰를 나열하는 방법에 대해 알아보겠습니다.


아래 코드와 결과를 먼저 보시죠.

ConstraintLayout 수평(Horizontal) 또는 수직(Vertical) 방향으로 뷰 나열하기


예제에서는 가장 먼저 TEXT1을 부모의 왼쪽,위에 위치시켰습니다. 그리고 TEXT2의 왼쪽 사이드를 TEXT1의 오른쪽 사이드에(Left_toRightOf="@+id/text1"), TEXT3의 왼쪽 사이드를 TEXT2의 오른쪽 사이드에(Left_toRightOf="@+id/text2") 맞춤으로써 자식(Children) 뷰들을 수평(Horizontal) 방향으로 배치하였습니다.


만약 수직(Vertical) 방향으로 자식 뷰들을 나열하려면, "Left_toRightOf" 대신 "Top_toBottomOf" 속성을 사용하면 됩니다.

3.4 수평(Horizontal) 또는 수직(Vertical) 기준으로 뷰 정렬(Alignment)하기.

컨스트레인트레이아웃에는 렐러티브레이아웃에서 소개했던 맞춤 정렬(Alignment) 관련 속성이 별도로 제공되지 않습니다. 앞서 소개한 상대 위치(Relative Positioning) 속성들로 뷰들을 정렬(Alignment)할 수 있기 때문입니다.

수평(Horizontal) 또는 수직(Vertical) 기준으로 뷰 정렬하기


예제의 코드를 보면, 크기가 다른 세 개의 텍스트뷰를 수평(Horizontal) 방향으로 배치한 다음, TEXT2, TEXT3에 "layout_constraintBottom_toBottomOf="@id/text1"" 속성을 사용하여 각 뷰의 bottom을 TEXT1의 bottom에 맞춘 것을 확인할 수 있습니다.

3.5 텍스트 Baseline 기준으로 뷰 정렬(Alignment)하기.

자식 뷰를 배치할 때 상/하/좌/우 사이드(Side) 외에 텍스트의 Baseline을 기준으로 정렬하는 방법이 있습니다.

텍스트 Baseline 기준으로 뷰 정렬(Alignment)하기


텍스트 Baseline에 대해서는 [안드로이드 렐러티브레이아웃 - 5.2 layout_alignBaseline]에서 설명했었는데요, 아래 그림을 보면 그 의미를 쉽게 이해할 수 있을 것입니다.

텍스트 Baseline


아래 예제는 텍스트 Baseline 기준으로 뷰를 정렬(Alignment)하는 코드와 결과입니다.


3.6 뷰를 나머지 영역에 가득 채우기.

레이아웃에 뷰를 배치할 때, 뷰를 항상 자신의 컨텐츠 크기로만(wrap_content) 표시하는 경우는 거의 없습니다. 상황에 따라, 자식 뷰 중 하나를 다른 뷰의 사이드 또는 부모 레이아웃에 가득차게 만들어야 하죠.


컨스트레인트레이아웃에서 뷰를 다른 뷰의 사이드 또는 부모 레이아웃에 가득 채우기 위해서는, 뷰의 크기가 결정될 때 자신에게 지정된 제약에 따르도록 만들어야 합니다. 뷰의 크기가 제약(Constraints)에 따르게 만드려면, layout_width 또는 layout_height 속성에 "0dp" 값을 지정하면 됩니다. 관련된 내용은 "3.2 자식(Child) 뷰를 부모 레이아웃에 가득차게 만들기."에서 설명했었죠.


아래 예제는 TEXT1을 컨텐츠 크기대로 부모의 왼쪽에 배치하고 TEXT3를 컨텐츠 크기대로 부모의 오른쪽에 배치한 다음, TEXT2를 TEXT1와 TEXT3 사이에 가득차도록 만든 예제입니다. 코드의 핵심은 TEXT2의 layout_width 속성에 "0dp"를 지정한 것이죠.

뷰를 나머지 영역에 가득 채우기


4. Start와 End.

컨스트레인트레이아웃이 가지는 속성을 보면 수평(Horizontal) 방향 상대 위치 지정 속성에 Left와 Right에 더해 Start와 End가 있는 것을 볼 수 있죠? 이는 시스템 언어 설정에 따라 바뀔 수 있는 텍스트 시작와 끝 위치 기준에 대한 대응을 위해 제공되는 속성입니다.


설정된 언어가 LTR(Left To Right)을 따른다면, Start는 Left를, End는 Right를 의미합니다. 하지만 아랍 지역 몇몇 언어처럼 RTL(Right To Left)를 따른다면, 위와는 반대로 Start는 Right를, End는 Left를 가리키게 됩니다.


좀 더 자세한 설명은 [안드로이드 렐러티브레이아웃 (Android Relative Layout) - Start와 End]와 [안드로이드 텍스트뷰 속성 4 - drawableLeft와 drawableStart, drawableRight와 drawableEnd의 차이]의 내용을 참고하시기 바랍니다.

5. 참고.

.END.


ANDROID 프로그래밍/LAYOUT , , , , , , , , , , , , , , , , ,

  1. Blog Icon
    초보 개발자 1호

    3.5번 예제 텍스트 Baseline 기준 정렬에서
    정렬 속성값이 'ConstraintBottom_toBottomOf' 가 아니라
    'ConstraintBaseLine_toBaseLineOf'여야 하는 것 아닌가요?
    약간 잘못된 것 같아 의견 올립니다.

  2. 네. 맞습니다. 지적해주신대로 본문의 내용이 잘못 작성되어 있었네요.
    잘못된 부분은 고쳐서 본문에 반영했습니다.

    잘못된 내용 찾아주셔서 감사합니다.

  3. Blog Icon
    체프

    예를 들어 text2의 왼쪽과 오른쪽의 제약을 부모 레이아웃에 둔다면 중간에 위치하게 되잖아요? 그런 상황에서 layout_width를 0dp로 지정하니까 이 경우에는
    제약에 맞게 크기가 늘어나지 않습니다. match_parent를 하면 원하는 상태가 되는데 혹시 이건 어떤 문제 인가요?

  4. 음, 딱히 문제가 없어야 할 내용같은데요, 정확한 원인을 파악하려면 프로젝트 설정과 작성하신 코드를 봐야할 것 같습니다.

    먼저 현재 프로젝트에서 사용중인 ConstraintLayout 버전을 체크해 보세요. 1.1 이상의 최신 버전을 사용하고 있는지 확인해보시고, 만약 이전 버전을 사용하고 있다면 1.1 이상 버전으로 업그레이드하여 사용하시기 바랍니다.

    그리고 괜찮으시다면 질문글에 적어주신 부분에 대한 코드만 복사해서 다시 질문글에 올려주세요. 그러면 도움 드리기가 더 쉬울 것 같습니다.

    감사합니다.