ConstraintLayout
지난 UI 관련 포스팅에서는 LinaerLayout 을 다루었는데 이번에는 실제로 가장 많이 사용하는 ConstraintLayout 을 공부해봅시다. 바로 가시죠. ㅎ
ConstraintLayout
Constraint : 제약, 제한, 통제
어떤 Widget(ImageView
나 TextView
,Button
등)을 쉽게 "통제"할수 있다. 동시에 필수적으로 "제약"을 걸어주어야 한다. ConstraintLayout
을 사용하면
- 복잡한 레이아웃 계층구조를 단순히 구성하여 작성할 수 있다.
- 자식Veiw 간의 상호관계를 정의할 수 있다.예) 두 View를 위 아래 기준으로 중앙에 배치하기 등 (아래 읽어보면 알 수 있다.)
ConstraintLayout이 제공하는 "제약(Constraint)"들, 즉, 컨스트레인트레이아웃 속성의 이름은 기본적으로 "layout_constraint
"로 시작하며, 바로 뒤에 구체적인 제약 조건이 명시된다.
Layout_constraintXXXXXXXXX
(XXX: 구체적인 제약 조건)
app: Layout_constraintLeft_toRightof = "@+id/certainBtnId"
다양한 제약 유형
Relative positioning
요소 간 상대 위치 지정. (left, right, top, bottom, start, end, baseline)
Margins
요소 간 여백(Margin) 설정을 위한 제약.
margin 값은 항상 양수이거나 0이어야 한다.
layout_goneMarginStart
와 같이 GONE을 사용해서 다른 margin값을 줄 수 있다.
A와 B가 서로 제약이 걸려있는 아래와 같은 상황이라고 하자.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAAA"
android:id="@+id/a"
android:textSize="40sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/b"
app:layout_constraintBottom_toTopOf="@+id/runBtn"
android:visibility="visible"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BBBB"
android:textSize="40sp"
android:id="@+id/b"
app:layout_constraintStart_toEndOf="@+id/a"
app:layout_constraintBottom_toTopOf="@+id/runBtn"
android:layout_marginStart="30dp"
app:layout_goneMarginStart="200dp"
/>
이 때 B TextView 에 app:layout_goneMarginStart="200dp"
을 설정해서 만약 A 가 gone 이 되었을 때만 작동하는 마진을 설정하는 것이다.
그리고 A를 gone으로 설정하면 (visibility = "gone"
) 아래처럼 작동한다.
즉, A 가 visible 일 때는 B는 marginStart = "30dp"
으로 배치되고 A가 gone 일 때는 B는 goneMarginStart = "200dp"
로 배치되는 것이다.
Centering positioning
뷰를 부모 레이아웃 또는 제약 치수의 중앙에 배치.
제약이 불가능하도록 제약을 닿지않는 끝에 주면 가운데로 위치된다.
예를 들어 왼쪽 a 지점부터 오른쪽 b 지점까지 wdith 가 100dp 일 때 뷰의 constraintStart
와 constraintEnd
을 양쪽 끝에 주고 100보다 작은 width 을 설정해 준다면 위 사진처럼 작동된다.
Bias
위 경우처럼 제약을 두되 특정 비율을 정해주어서 가운데가 아닌 곳에 위치하도록 설정. (0부터 1사이의 값을 가진다.)
Circular positioning
대상 뷰를 기준으로 각도(angle)와 반지름(radius)으로 상대 위치 지정.
Visibility behavior
뷰의 Visibility
상태에 따른 최종 위치 결정 및 여백.
A가 GONE 상태가 되면 A의 크기와 마진값이 0 이되면서 B의 위치가 결정된다. 위에서 언급한 goneMargin 과 연관된 내용이다.
Dimension constraints
뷰에 적용된 치수 제약에 따른 뷰의 크기 결정.
android:minWidth
, android:maxHeight
레이아웃의 최소/최대 너비/높이 설정
(a) 는 wrap_content 에 Center Positioning, (b)는 0dp에 Center Positioning, (c)는 0dp에 margin 왼쪽만 설정
MATCH_PARENT 는 ConstraintLayout에서는 권장되지 않는다.
비슷한 작업이 left/right 혹은 top/bottom를 parent로 제약을 걸어주고 0dp 을 주는 것이 더 권장된다.
Percent Dimension
치수을 MATCH_CONSTRAINT(0dp)로 설정해야 함.
기본값은 app:layout_constraintWidth_default = "percent"
혹은 constraintHeight
로 설정해야 함.
그리고 layout_constraintWidth_percent
를 0과 1사이의 값으로 설정
Ratio
한 위젯의 치수을 다른 위젯 치수에 대한 비율로 설정 가능하다.
최소한 하나의 제한 치수를 0dp로 설정해야 한다. 그리고 laytout_constraintDimensionRatio
로 비율 설정한다..
아래 코드는 너비와 높이를 같은 비율로 설정한 것이다.. (실수형, 너비 : 높이)
<Button
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1" />
그리고 만약 너비와 높이 모두 MATCH_CONSTRAINT(0dp)일 때 W나 H를 먼저 적어서 하나를 제약에 맞춰 설정한 뒤 비율에 따라 높이를 결정할 수 있다.
아래 코드는 먼저 높이를 제약에 맞춰서 설정한 후 너비: 높이를 16 : 9 비율로 크기를 설정한다.
<Button
android:text="Button Ratio"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
Chains
수평 또는 수직 방향(Axis)으로 나열된 뷰에 대한 그룹화. 배치 스타일 지정. 아래 추가 설명
만약 BUTTON2의 parent의 start에 붙히고 BUTTON1의 end를 parent의 end에 붙힌 뒤티켓의 end를 설정의 start, 설정의 start를 티켓의 end에 붙히면 어떻게 될까?
이미지의 크기가 늘어나면서 수평으로 꽉차게 될까 혹은 오류가 뜰까?
이것은 아래 이미지처럼 된다. 마치 어떤 chain 이 서로를 연결하고 있는 모습이다.
- ConstraintLayout ChainConstraintLayout 의 Chain도 타입이 있다. 따로 아무런 타입을 지정해주지 않으면 위처럼 된다.이를 Spread Chain이라고 한다. 아래는 Chain의 종류이다.
- spread Chain : 기본 모드. 같은 간격(위에서는 16)으로 배치된다.
- spread instide chain : 제일 바깥쪽 위젯은 제일 바깥쪽으로 배치되고 사이에 위젯이 있다면 같은 간격으로 배치된다.
- Packed Chain : 바깥쪽의 끝을 기준으로 가운데에 모여서 배치된다. (물론 margin을 주면서 위치를 조금 조정할 수 있다.)
Virtual Helpers objects
레이아웃 내 효율적인 뷰 배치에 사용 가능한 몇 가지 Helper 객체들. Guideline, Barrier, GroupOptimizer 가 있다.
Guideline
- 가로 또는 세로 축 방향을 가진 가상의 뷰
- 부모 뷰의 특정 위치를 기준점으로 삼을 때 사용
- 축, 위치 값을 속성으로 가짐축 : android:orientation = "[vertical | horizontal]"위치
app : layout_constraintGuide_begin
: 시작 지점으로부터의 거리app : layout_constriantGuide_end
: 끝 지점으로부터의 거리app : layout_constraintGuide_percent
: 시작 지점으로부터의 % 위치
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="120dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="120dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3" />
Barrier
Barrier: 장벽. 말그대로 장벽을 만들어 그 이상 뷰들이 넘어 오지 못하도록 만든다.
Guideline은 정적으로 수치를 입력하여 고정된 벽을 만들었다면, Barrier는 어떤 뷰들을 기준으로 동적인 벽을 만들 수 있다..
barrierDirection
: barrier의 방향을 결정한다.constraint_referenced_ids
: 장벽의 기준점으로 참조할 뷰의 아이디를 여러개 참조barrierAllowsGoneWidgets
: 참조하고 있던 true 또는 false 값을 통해 참조하고 있던 뷰가 GONE 될 때의 동작을 지정.
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="동해물과 "
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/tv2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="마르고 닳도록"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv1" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:barrierDirection="end"
app:constraint_referenced_ids="tv1,tv2" />
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="barrier"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/barrier"
app:layout_constraintTop_toTopOf="parent" />
Tv2가 Tv1보다 길어서 tv2의 end 부분에 장벽이 생긴다.
Group
Group은 여러 뷰들을 참조하며, 참조된 뷰들을 쉽게 hide / show 할 수 있는 클래스.
여러 Group들이 동시에 같은 뷰를 참조하여 뷰의 상태를 변경하는 경우 xml에 선언된 순서를 따라 마지막에 적용된 Group의 state를 따른다.
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="btn1,btn2" />
Group에서 visibility를 gone으로 할 때 버튼 두개가 모두 gone 된다.
출처 :
https://www.charlezz.com/?p=691
https://medium.com/@futureofdev/android-constraintlayout-쉽게-알아가자-62d2ded79c17
https://recipes4dev.tistory.com/163
https://app-dev.tistory.com/98
https://seminzzang.tistory.com/21https://recipes4dev.tistory.com/158?category=658689