본문 바로가기
개발/안드로이드

Android ConstraintLayout 분석 - 2

by darksilber 2019. 6. 13.
반응형

출처 - https://m.blog.naver.com/pistolcaffe/221290283458

 

ConstraintLayout 분석 두번째 포스팅 입니다.
지난번 포스팅에서 ConstraintLayout 의 기본 특성에 대해 알아보았습니다.
v1.1 이 release 되면서 가이드 문서에 추가 된 내용들에 대해 정리해보려고 합니다

1. WRAP_CONTENT : enforcing constraints
2. MATCH_CONSTRAINT dimension
3. Margins and chains
4. Optimizer
5. Group
6. Barrier
7. Placeholder
8. Circular Positioning

ConstraintLayout 첫번째 분석 글은 아래 링크를 참고하세요.

Android ConstraintLayout 분석 - 1

이번 글은 ConstraintLayout 에 대한 분석 내용입니다. ConstraintLayout 은 지난 2016 년 개최 되었던...

blog.naver.com


 

 

1. WRAP_CONTENT : enforcing constraints

ConstraintLayout 에서 특정 constraint 를 적용 하기 위해서는 위젯의 width 혹은 height 를 MATH_CONSTRAINT 로 설정 해야 했습니다.
반대로 말하면 WRAP_CONTENT 로 설정 시 layout 시점에서 결과 dimension 에 대해 대부분의 constraint 이 영향을 받지 않았습니다.
(여기서 결과 dimension 이란, layout process 중 measure 단계에서 view 가 갖게 될 최종 width or height 값을 의미합니다)

그러나 1.1 버전에서 부터는 WRAP_CONTENT 로 설정하였을 때 결과 dimension 에 constraint 가 영향을 줄수 있는 속성이 추가 되었습니다.

layout_constrainedWidth="true|false" layout_constrainedHeight="true|false"

default 값은 false 이며, 원하는 상황에 true 로 설정하여 사용 합니다.
그러면 어떠한 상황에서 사용 될 수 있는 속성인지 특수한 예를 통해 알아보겠습니다.

<예제1>

3개의 TextView 가 있는 단순한 구조 입니다.
width, height 를 모두 wrap content 로 설정 하였고 Text1 를 Head 로 하여 packed chain 을 설정 하였습니다.
packed chain 설정 시 기본 bias 가 0.5 이므로 중앙에 정렬 된 모습입니다.

여기서 위젯 배치에 조건을 걸어보겠습니다.
1. Text1 는 text 가 변할 수 있는 상황. (maxLines="1", ellipsize="end" 로 설정)
2. 나머지 Text2, Text3 들은 Text1 의 text 가 변경 되어 width 가 변하여도 화면에서 벗어나지 않고 표시 되어야 함.

이 상태에서 Text1 을 길이가 긴 text 로 변경해보겠습니다.

<예제2>

첫번째 TextView 의 앞부분이 잘려서 보이고, text3 은 조건에 맞지 않게 화면 밖에 벗어난 상황입니다.
그러면 이 조건을 충족시키기 위해 layout_constrainedWidth 속성을 적용하여 수정해보겠습니다.

<TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/holo_orange_dark" android:ellipsize="end" android:maxLines="1" android:text="@string/str_test" android:textSize="24sp" app:layout_constrainedWidth="true" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/text2" />

밑에서 4번째 줄에 layout_constrainedWidth 속성이 추가 된 것을 볼 수 있습니다.
이제 결과 화면을 보겠습니다.

<예제3>

조건대로 세개의 TextView 가 배치 되었습니다.
text1 은 chain style 적용을 위해 이미 양 side 에 constraint 를 설정한 상태이므로, 길이가 긴 text 가 설정 되었을 때 결과 dimension 에 대해 constraint 이 적용 되어 parent 의 left 부터 text2 의 left 까지의 영역으로 설정 된 것입니다.
이렇게 특수한 상황에서 WRAP_CONTENT 일 때도 constraint 가 적용 되도록 layout_constrainedWidth(Height) 속성을 사용 할 수 있겠습니다.

2. MATCH_CONSTRAINT dimension

이번에는 MATCH_CONSTRAINT 상태 에서 사용 할 수 있는 추가 된 속성을 알아보겠습니다.

layout_constraintWidth_min layout_constraintWidth_max layout_constraintHeight_min layout_constraintHeight_max

먼저 min, max 에 대한 속성입니다.
dp 값 혹은 wrap 속성을 사용 할 수 있습니다 (dp 외 다른 단위는 생략 하였습니다)
minWidth, minHeight 속성과 유사하게 dp 값을 넣으면 min,max 가 해당 값으로 설정 되고,
wrap 으로 설정 시 min, max 값이 WRAP_CONTENT 를 적용 한 것처럼 설정 됩니다.

layout_constraintWidth_percent layout_constraintHeight_percent

다음은 percent 속성입니다. width 혹은 height 를 parent 의 percent 만큼 적용하는 속성입니다. 0~1 사이 값을 사용합니다.
(1.1-beta1, 1.1-beta2 버전 에서는 layout_constraintWidth(Height)_default="percent" 로 설정 하여 함께 사용해야 합니다.)


 

3. Margins and chains

이 내용은 추가 된 기능이나 특성은 아니고, 1.1 버전이 Release 되면서 가이드 페이지에 추가로 명시 된 내용 입니다.
따라서 간단히 알아보고 넘어가겠습니다.

spread or spread_insde chain style 이 적용 된 상황을 예시로 들어보겠습니다.

위젯 3개를 spread chain 으로 적용 해 놓은 상태 입니다.
Text1 과 Text2 위젯의 간격이 조금더 벌어져있습니다.
Text1 에는 layout_marginRight="10dp" 를 적용 하였고 Text2 에는 layout_marginLeft="5dp" 를 적용 하였습니다.

이때 두 위젯의 간격은 spread chain 동작으로 퍼뜨려 배치 됨으로써 생기는 두 위젯간 기본 빈공간은 margin에 포함 되지 않고 명시적으로 지정한 margin 값만 계산 되어 15dp 가 되는 것 임을 설명 하고 있습니다.


4. Optimizer

Optimizer 또한 1.1 이 release 되면서 가이드 문서에 추가 된 내용입니다.
자료가 부족하여 디테일한 내용을 파악하진 못하였으나, ConstraintLayout 에는 퍼포먼스를 위해 자체적인 Optimizer 가 존재 합니다.
가이드 문서에 따르면 하기 속성을 통해 optimize level 을 설정하여 사용 할 수 있다고 합니다.

layout_optimizationLevel="none|standard|chains|barrier|dimensions|direct"

● none : optimize 를 해제
● standard : 기본값. direct 와 barrier constraint 에 대한 optimize
● chians : chain constraint 에 대한 optimize
● barrier : barrier constraint 에 대한 optimize
● dimensions : dimensions constraint 에 대한 optimize
● direct : direct constraint 에 대한 optimize

Optimizer 는 계속해서 개선 및 리디자인이 진행 중인것 같습니다.


5. Group

여러개의 위젯이 배치 되어 있는 상황에서, 특정 위젯들을 묶어서 특정 상황에서 visible 상태를 전환해야 하는 경우를 가정해봅시다.

fun setVisibleState(visible: Boolean){ val state = if(visible) View.VISIBLE else View.GONE widget1.visibility = state widget2.visibility = state } etc....

위의 예제 코드 처럼 visible 상태를 전환 할 위젯들을 대상으로 특정 함수를 만들거나,
xml 에서 해당 위젯들만 따로 ViewGroup 으로 묶어서 배치 시킨 뒤 해당 viewgroup 의 visibility 속성을 변경 하는 방법 등이 있으나
대상 위젯들이 많아지면 코드가 길어지고 불필요하게 view 계층이 많아지는 문제가 있습니다.

1.1 이 release 되면서 visible 상태를 관리 할 수 있도록 Group 이라는 Helper widget 가 도입 되었습니다.
예제코드를 보겠습니다.

<android.support.constraint.ConstraintLayout .....> <TextView android:id="@+id/text1" .../> <TextView android:id="@+id/text2" .../> <TextView android:id="@+id/text3" .../> <android.support.constraint.Group android:id="@+id/group" android:layout_width="wrap_content" android:layout_height="wrap_content" app:constraint_referenced_ids="text1,text2,text3" /> </android.support.constraint.ConstraintLayout>

Group 위젯을 추가 한 뒤 constraint_referenced_ids 속성에 Group 을 만들 widget 의 id 를 (,) 기준으로 구분해서 정의하여 사용 합니다.
이후 사용할 클래스에서 해당 group 의 visibility 속성을 변경 해줌으로써 그룹화된 widget 들의 visibility 속성을 쉽게 관리 할 수 있습니다.

xml 상에서 Group 을 넣는 위치는 중요하지 않으나, Group 을 여러개 사용할 때 중복 된 위젯이 있을 경우에는 순서상으로 마지막으로 정의 된 Group 의 visibility 상태로 적용 되는 점을 주의하시기 바랍니다. 예제코드를 보겠습니다

<android.support.constraint.ConstraintLayout .....> <TextView android:id="@+id/text1" .../> <TextView android:id="@+id/text2" .../> <TextView android:id="@+id/text3" .../> <android.support.constraint.Group android:id="@+id/group1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:constraint_referenced_ids="text1,text2" /> <android.support.constraint.Group android:id="@+id/group2" android:layout_width="wrap_content" android:visibility="visible" android:layout_height="wrap_content" app:constraint_referenced_ids="text2,text3" /> </android.support.constraint.ConstraintLayout>

group1, group2 가 있고 text2 위젯이 중복되어 각각 그룹화 되어 있습니다.
group1 의 visibility 는 gone, group2 의 visibility 는 visible 로 설정 하였습니다.
이 상황에서 앱을 실행하면 text2 위젯은 xml 순서상으로 마지막으로 정의 된 group2 의 visibility 속성이 적용되어 visible 상태가 됩니다

참고로 Group 은 Guideline 과 동일하게 크기를 갖지 않고 레이아웃 과정에 참여하지 않는 특징이 있습니다.


6. Barrier

barrier 는 참조하고 있는 위젯들 중에 원하는 direction 에 가장 큰 dimension 의 position 에 guideline 을 생성합니다.
가이드 문서에 있는 참고 이미지를 먼저 보겠습니다.

두개의 버튼이 있습니다. end direction 에 barrier 를 설정해보겠습니다.

<android.support.constraint.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="end" app:constraint_referenced_ids="button1,button2" />

group 과 동일하게 constraint_referenced_ids 속성에 barrier 를 적용할 위젯 id 정보를 추가 합니다.
그러면 end direction 기준으로 봤을때 두번째 버튼이 더 큰 dimension 을 갖고 있으므로 두번째 버튼 end position 기준으로 guideline 이 생성 됩니다.

여기서 두 버튼의 크기가 가변적 이라고 가정하였을 때, 만약 첫번째 버튼이 두번째 버튼보다 width 가 커지면 barrier 는 자동으로 첫번째 버튼의 end position 기준으로 guideline position 이 변경 됩니다.

위의 예시 이미지를 기준으로, 두 버튼 오른쪽에 특정 위젯을 배치 하고자 할때 barrier 가 유용하게 사용 될 수 있습니다.
만약 두 버튼의 width 가 항상 고정적 이라고 할 경우에는 width 가 큰 위젯 쪽에 side constraint 을 적용하여 배치 하면 되겠지만,
반대로 width 가 설정에서 언어변경 등을 통해 가변적일 가능성이 있을 경우에는 barrier 를 세워두고 barrier 를 기준으로 side constraint 를 적용하여 배치 해볼 수 있겠습니다. 하단에 barrier 를 적용한 간단한 예제 코드와 결과화면을 첨부합니다.

<android.support.constraint.ConstraintLayout ... android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" .../> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" .../> <Button android:id="@+id/rightbtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="barrier is good" app:layout_constraintLeft_toLeftOf="@+id/barrier" app:layout_constraintTop_toTopOf="@id/button1" app:layout_constraintBottom_toBottomOf="@id/button2"/> <android.support.constraint.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="end" app:constraint_referenced_ids="button1,button2" /> </android.support.constraint.ConstraintLayout>

 

그렇다면 만약 barrier 가 visibility 가 gone 상태인 widget 을 참조 중이면 어떻게 동작 할까요?
기본 동작은 gone 처리가 된 후의 해당 direction 기준으로 barrier 가 생성 됩니다만, 아래 속성을 통해 gone 을 허용하지 않을 수도 있습니다.

barrierAllowsGoneWidgets="true|false" // default 는 true 입니다

아래 예제 코드를 통해 barrier 의 gone 위젯 참조여부에 따른 차이를 살펴 보겠습니다.

<android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" ...> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button1" app:layout_constraintRight_toRightOf="parent" app:layout_constraintHorizontal_bias="0.8" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" android:text="ABCDEFGHIJKLMNOP" app:layout_constraintRight_toRightOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/button1" /> <Button android:id="@+id/leftbtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="barrier is good" app:layout_constraintRight_toLeftOf="@+id/barrier" app:layout_constraintTop_toTopOf="@id/button1" app:layout_constraintBottom_toBottomOf="@id/button2"/> <android.support.constraint.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="true" app:barrierDirection="left" app:constraint_referenced_ids="button1,button2" /> </android.support.constraint.ConstraintLayout>

버튼 두개의 bias 를 각각 0.8, 0.5 로 배치 하였고 left direction 의 barrier 를 생성 하였습니다.
button2 의 visibility 는 gone 으로 설정 하였으며 오른쪽의 버튼을 해당 barrier 의 side constraint 를 적용 하였습니다.

 

 




그러면 barrier 는 기본적으로 gone widget 를 참조 중일때 gone 처리가 된 이후의 위치에 guideline 을 생성하기 떄문에 아래와 같은 배치를 볼 수 있습니다.

 

그러나 barrierAllowsGoneWidgets 속성을 false 로 설정 할 경우 참조 중인 gone widget 은 생략하고 위치를 선정하기 떄문에 아래와 같이 배치가 됨을 확인 할 수 있습니다.

 


7. Placeholder

Placeholder 는 이미 layout 된 상태의 view 를 다른 곳에 배치 할때 효과적으로 사용 될 수 있습니다.
예시를 들어보겠습니다.

특정 버튼이 있습니다. A 와 같은 상태로 최초에 배치되었다가 B 와 같은 형태로 재배치 하려고 합니다.
B 는 parent bottom side 에 constraint 하고 height percent 를 0.6 설정한 상태라고 가정해보겠습니다.
동적인 처리가 필요하기 때문에 프로그래밍 방식으로 layout param 을 변경 하면 될것입니다.

ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, ConstraintLayout.LayoutParams.MATCH_CONSTRAINT).apply { leftToLeft = contentparent.id rightToRight = contentparent.id bottomToBottom = contentparent.id matchConstraintPercentHeight = 0.6f button1.layoutParams = this }

placeholder 를 사용한다면 xml 에서 placeholder 를 정의해두고, 원하는 시점에 setContent(id) 를 호출해주기만 하면 됩니다.

<android.support.constraint.Placeholder android:id="@+id/placeholder" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHeight_percent=".60" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" />

placeholder.setContentId(button.id)

setContentId() 호출 시점에 내부적으로 해당 content view 를 placeholder 에 지정 된 속성에 따라 view 를 재배치 하게 됩니다.
몇가지 참고사항을 정리 하였습니다.

● placeholder 는 동적으로 새로운 view 를 추가하기 위한 helper class 가 아닙니다. 이미 같은 계층 상으로 layout 되어있는 view 를 재배치 할때 사용 됩니다
● placeholder 와 재배치하려는 view 는 같은 계층에 속해 있어야 합니다
● 만약 재배치 하려는 widget 이 chain 상태에서 재배치가 이뤄질경우 chain 관계에 있던 나머지 widget 들간의 chain 관계는 유지 됩니다.
이는 재배치 하려는 widget 을 gone 처리 하였을때 chain 관계가 유지 되는 것과 같습니다


 

8. Circular Positioning

widget 의 중심을 기준으로 을 특정 widget 의 중심에 대해 각도와 거리를 통해 constraint 할 수 있는 특성입니다.

출처: https://developer.android.com/reference/android/support/constraint/ConstraintLayout

이를 위해 아래와 같은 속성들이 추가 되었습니다.

layout_constraintCircle //circular positioning 하려는 참조 widget id layout_constraintCircleRadius //해당 widget 과의 중심 거리 layout_constraintCircleAngle //각도 (0~360)

바로 예제코드를 살펴보겠습니다.

//width, height 생략하여 첨부하였습니다 <Button android:id="@+id/button1" android:text="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:text="A" app:layout_constraintCircle="@id/button1" app:layout_constraintCircleAngle="0" app:layout_constraintCircleRadius="100dp" /> <Button android:id="@+id/button3" android:text="B" app:layout_constraintCircle="@id/button1" app:layout_constraintCircleAngle="90" app:layout_constraintCircleRadius="100dp" /> <Button android:id="@+id/button4" android:text="C" app:layout_constraintCircle="@id/button1" app:layout_constraintCircleAngle="180" app:layout_constraintCircleRadius="100dp" /> <Button android:id="@+id/button5" android:text="D" app:layout_constraintCircle="@id/button1" app:layout_constraintCircleAngle="270" app:layout_constraintCircleRadius="100dp" />

화면 중앙에 button1 을 배치시키고 0, 90, 180, 270 각도에 4개에 위젯을 circular 형태로 배치해 보았습니다.
아주 간단하게 circular positioning 을 할 수 있는것 같습니다.


이렇게 해서 v1.1 에 추가 된 내용에 대해 알아보았습니다.
추가 할 내용이나 새 버전이 릴리즈 되면 ConstraintLayout 에 대해 지속적으로 포스팅 할 계획입니다.
피드백은 언제나 환영입니다. 잘못된 내용이나 궁금하신점 있으시면 댓글 달아주세요.

도움이 되셨다면 좋아요도 꾹 눌러주시구요.
그럼 즐거운 개발 되세요 :)

반응형

댓글