안드로이드 젯팩 컴포즈 (ch29~31) - ConstraintLayout, IntrinsicSize
# ConstraintLayout에 대한 간단 요약
ConstraintLayout 은 안드로이드 7에 도입되었고, 다양한 화면 크기를 가진 안드로이드 기기에 앱을 유연하게 배포하기 위해 필요한 기능이다. 이를 위해 고정적으로 컴포넌트 위치를 정하는 것이 아니라, '제약'을 설정하고 컴포넌트 간 상대적인 위치, 비율을 통해 컴포넌트를 배치한다.
# IntrinsicSize에 대한 간단 요약
안드로이드 앱 화면을 구성하기 위해 젯팩 컴포즈는 '단한번' 재구성(Recomposition)을 수행한다. 때때로 부모 컴포넌트는 자식 컴포넌트가 렌더링 되기전에 미리 크기를 알아야 하는 경우가 있는데, 이때 IntrinsicSize 를 사용하면 된다. (재구성 작업을 2번 하지 않고도 자식의 크기를 미리 알수 있다)

- 지난 스터디 내용
- 이번 스터디 내용
- Ch29~31 ConstraintLayout, IntrinsicSize
렛츠 스타트.

Ch29 ConstraintLayout
ConstraintLayout 이란
젯팩 컴포즈는 다양한 레이아웃 컴포넌트를 제공하는데, 이번에는 ConstraintLayout에 대해서 알아본다. ConstraintLayout을 사용해서 안드로이드 앱 화면에 원하는 대로 UI 컴포넌트를 배치할 수 있다. ConstraintLayout 이 제공하는 기능에 대해 알아보고 이를 실전에서 활용해 보자.
ConstratintLayout 동작원리
ConstraintLayout 은 앞에서 배운 다른 레이아웃 컴포넌트(Box, Column)와 동일하게 자식 컴포넌트 위치, 크기를 관리하는 컴포넌트이다. 자식 컴포넌트에 제약 커넥션(Constraint Connection)을 설정하여 위치, 크기를 지정한다. ConstraintLayout 이 자식컴포넌트를 어떻게 배치하고 관리하는지 이해하기 위해서 아래의 개념들을 하나씩 살펴보자.
- 1) 제약 (Constraint)
- 2) 마진
- 3) 반대제약
- 4) 제약편향
- 5) 체인
- 6) 체인 스타일
- 7) 가이드라인
- 8) 베리어
1) 제약 (Constraint)
ConstraintLayout에서 제약(Constraint)이란 무엇일까? 왠지 영어를 한국어로 번역하니 어려운 느낌이다. 그냥 쉽게 UI 컴포넌트를 배치하기 위한 상대적인 규칙이라고 생각하면 될 것 같다. 예를 들면, 부모 레이아웃을 기준으로 얼마큼 떨어진다. 다른 컴포넌트를 기준으로 오른쪽 또는 왼쪽에 위치한다. 이런 느낌으로 생각하면 될것 같다. 이렇게 제약을 적용하기 위해서는 기준이 필요한데, x축, y축 각각 2개의 기준이 있다고 보면 된다. (x축 좌우, y축 위아래, 총 4개의 제약) 그리고 각각의 기준과 컴포넌트 간 '제약을 적용하기 위한 연결부'가 제약 커넥션이라고 보면 된다.
이러한 제약은 우리가 안드로이드 폰의 기기 방향(Orientation)을 변경하는 경우, 서로 다른 화면크기를 가진 기기에 표시될 때 반응하는 방법을 설명한다.
2) 마진
마진은 "고정된 거리"를 지정하기 위한 제약(Constraint)의 한 가지이다. 책에서는 Button 컴포넌트를 예시로 설명하고 있다. 안드로이드 앱 화면의 좌측 상단 근처에 Button을 배치하고 싶을 때 제약을 설정하면 된다. 아래 그림에서 빨간색 네모칸 부분이 제약 커넥션을 설정한 것이다. 이와 같이 제약 커넥션을 통해 앱 화면에서의 Button 위치를 지정할 수 있다.
아래와 같이 고정값(150dp, 200dp)을 통해 Button의 위치를 설정할 수 있지만, 다양한 화면크기를 가지는 안드로이드 기기에 맞춰서 적절하게 표시하지는 못하다 (이를 유연하지 못하다..라고 한다) 이런 부분을 개선하려면 다음에 나오는 반대제약을 사용하면 된다.

3) 반대 제약
컴포저블이 가진 각각의 축(x축, y축)에 존재하는 2개의 제약을 "반대 제약"이라고 한다. 아래 그림을 보면 바로 이해가 된다.
이번에는 Button에 마진 제약 커넥션을 적용하여 고정길이 만큼 떨어진 위치에 Button을 배치한 것이 아니라, 화면의 x축, y축 기준으로 특정 비율만큼 떨어진 위치에 Button 을 위치한 것이다. Button의 x축에 비율을 적용하기 위해서는 Button 의 왼쪽과 오른쪽 가장자리에 "제약"을 설정해야 한다. 이것을 "수평 반대 제약"을 가졌다라고 한다.
이렇게 반대제약을 설정하고 나면, Button 위치를 "비율"로서 지정할 수 있다. 이 결과 서로 다른 화면크기를 가진 안드로이드 기기에서도 일관성 있게 Button 위치를 지정할 수 있다 (보다 유연한 방법)

당연한 얘기지만, 만약 "반대제약"에 동일한 비율이 설정될 경우 아래 그림과 같이 컴포넌트는 해당 축의 "중앙"에 배치된다.

4) 제약 편향
반대제약이 설정된 상태에서 컴포넌트 위치 조정을 허용하려면, ConstraintLayout의 제약편향 기능을 구현해야 한다. 제약편향을 이용하면, 제약 조건에 대해 설정한 비율만큼 컴포넌트를 배치할 수 있다. 아래는 반대제약을 설정한 상태에서 수평편향 75%, 수직편향 10% 라는 "제약 편향"을 적용한 결과이다.

5) 체인
2개 이상의 컴포저블을 포함하는 레이아웃의 동작방법을 제공하는 것이 바로 "체인"이다. 체인을 통해 수직, 수평축 기준으로 체인에 포함된 컴포저블의 간격, 크기를 설정할 수 있다. 예를 들면 3개의 Button을 그룹핑한 레이아웃이 있을 때, 3개의 버튼을 보기 좋게 정렬하고 싶다면 이 기능을 사용하면 된다.
체인을 되려면, 그룹에 속한 컴포넌트들 간에는 양항향 제약이 설정된다. 그리고 그룹의 첫 번째 요소가 체인 헤드가 되는데, x축에서는 가장 왼쪽, y축에서는 가장 위 컴포넌트가 체인 헤드가 된다.

6) 체인 스타일
체인의 레이아웃 동작은 체인헤드에 적용된 체인 스타일에 따라 정의된다.
- Spread Chain
- Spread Inside Chain
- Weight Chain
- Packed Chain
각 스타일이 어떻게 동작하는지에 대해서는 별다른 설명이 없이, 아래 그림만 보면 직관적으로 이해할 수 있다.

7) 가이드라인
ConstraintLayout에서 이용할 수 있는 특별한 요소로, "추가적으로 연결될 수 있는 제약"을 제공한다. 앞에서 쪽 봤지만, 제약은 x축, y축 양쪽에 가상의 어떤 선이 있다고 생각하면 되는데, 가이드라인은 기존 제약 외에 추가적으로 제약을 적용하기 위한 선을 제공하는 것이다.
아래 그림에서 빨강 점선이 가이드라인, 초록 점선이 새로운 제약 커넥션이다. ConstraintLayout 에는 여러 개의 가이드라인을 추가할 수 있다. 이렇게 가이드라인을 추가하면, 여러개의 컴포저블을 축에 맞춰 정렬할 때 유용하다. (아래는 수직 가이드라인을 사용)

8) 베리어
베리어는 가이드라인과 유사하다. 가상의 뷰로서 컴포저블을 레이아웃 안에 표시되도록 제한할 때 사용된다. 이 역시 글보다는 예시를 통해 이해하는 것이 좋다.
아래와 같이 Item3 개가 배치된 ConstraintLayout을 생각해 보자. Item3는 Item1 의해 제약을 받는다.

Item1 이 우측방향으로 길어지면, Item3 는 영향을 받아 길이가 줄어든다. 하지만 Item3는 Item2에 대해서는 제약을 받지 않기 때문에, Item2 가 우측으로 길어지면 Item3의 폭 변화가 없어서 2개의 컴포저블이 겹치는 문제가 발생한다.


베리어는 이런 문제를 해결하기 위해 사용된다. 아래와 같이 수직 베리어를 추가하고, Item1, Item2를 베리어의 참조 컴포넌트로 할당한다. 그리고 Item3는 수직 베리어에 의해 제약을 받도록 하면 해결된다.
Item1 또는 Item2의 폭이 커지면, 수직 베리어는 2개의 컴포넌트 중 넓은 폭의 컴포넌트에 맞춰서 조정되고, 이에 따라 Item3의 폭이 변경된다.


정리
ConstraintLayout 은 다양한 화면크기를 가지는 안드로이드 기기에 대해 유연하게 적응형 앱을 개발할 수 있도록 추가된 기능이다. ConstraintLayout 은 "제약"을 이용하여 컴포저블을 배치한다. 또한 가이드라인, 베리어를 사용하면 여러 컴포넌트에 대해 정렬 및 위치를 배치할 수 있다.
Ch30 ConstraintLayout 다루기
여기서는 ConstraintLayout을 코드 수준에서 다루는 방법을 설명한다. 본 내용은 Ch29에서 다룬 ConstraintLayout의 여러 기능들을 샘플코드와 결과화면으로 설명하고 있다. 각각의 코드를 본 포스팅에서 다루는 것은 크게 의미가 없을 것 같고, 도서를 구입하여 참고하면 좋을 것 같다.

절대 귀찮아서 생략한 게 아니아.....
Ch31 IntrinsicSize 다루기
젯팩 컴포즈는 재구성(Recomposition) 작업을 할 때, 각 컴포넌트를 '한 번만' 측정하도록 제한하여, 빠르고 효율적으로 안드로이드 앱 화면을 렌더링한다. 이때, 부모 컴포저블은 재구성을 수행할때, 자식의 크기를 사전에 알아야 하는 경우가 존재한다. 예를 들면, 폭이 가장 넓은 자식 컴포저블과 폭이 일치하도록 부모의 Column 폭을 조절하려는 경우다. 근데 자식을 렌더링 하지 않은 상태에서 어떻게 자식의 크기를 알 수 있을까? 이때 사용할수 있는 것이 내재적 측정값(Intrinsic measurement) 이다. 이를 통해 재구성시 '한번만' 측정한다는 규칙을 어기지 않고도 자식의 크기를 알수 있다.
내재적 측정값이란
부모 컴포저블은 Intrinsic 열거형의 min, max 값을 통해 자식의 크기를 알수 있다. IntrinsicSize는 가장 큰 자식 컴포저블이 가질 수 있는 최댓값, 최솟값 정보를 부모에게 제공한다.
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
// ...
}
내재적 최대 및 최소 크기 측정값
아래 Text 컴포저블을 예시로 IntrinsicSize.Max, IntrinsicSize.Min에 대해 좀 더 설명해 보자.
IntrinsicSize.Max는 는 Text 컴포저블이 표시하는 최대 텍스트 길이와 같다.

근데 Text 컴포저블은 멀티라인으로 텍스트를 표시할 수 있다. 아래와 같이 높이제한이 없을 경우, 텍스트의 최소폭은 텍스트 문자열중 가장 긴 단어 길이와 같고, 이 값이 IntrinsicSize.Min 이 된다. 만약 높이제한이 있다면, 해당 높이에서의 Text 컴포저블의 폭 값이 IntrinsicSize.Min 이 된다.

정리
젯팩 컴포즈는 안드로이드 앱 화면을 빠르게 구성(렌더링) 하기 위해 재구성 과정에서 컴포저블 크기를 '단 한번'만 측정한다. 근데, 부모가 자식 컴포저블이 렌더링 되기 전에 미리 크기를 알아야 하는 경우에는 문제가 된다. 이때 IntrinsicSize를 이용하면 부모는 자식의 폭과 높이의 최소/최댓값을 알 수 있어서, 이정보를 사용하여 부모의 크기를 결정할 수 있다.
오늘은 여기까지!
Spark. Then. Move!