ConstraintLayout은 크고 복잡한 레이아웃을 중첩된 뷰 그룹이 없는 플랫한 뷰 계층구조로 만들 수 있게 함, 모든 뷰가 부모 레이아웃과 형제 뷰와의 관계로 레이아웃을 정하는 점은 RelativeLayout 과 비슷하지만 더 유연하고, 안드로이드 스튜디오의 레이아웃 에디터를 사용하기 쉬움
레이아웃 API와 레이아웃 에디터가 서로를 위해 만들어졌기 때문에 ConstraintLayout 은 레이아웃 에디터의 시각적 도구에서 모든 기능을 사용할 수 있음, XML을 직접 작성하지 않고 마우스 드래그만 사용해 ConstraintLayout 으로 레이아웃을 만들 수 있음
ConstraintLayout 에서 뷰의 위치를 정의하려면 각각 하나 이상의 수직 제약 조건과 수평 제약 조건을 추가해야 함, 각 제약 조건은 다른 뷰, 부모 레이아웃 또는 보이지 않는 가이드라인과의 연결 또는 정렬을 나타냄, 각 제약 조건은 수직, 수평 축을 따라 뷰의 위치를 정의함, 뷰마다 각 축마다 최소한 하나의 제약 조건만 있으면 되지만 주로 더 많이 사용함
뷰를 레이아웃 에디터에 놓을 때 레이아웃을 만들기 쉽게 하도록 아무 제약 조건이 없더라도 그 자리에 남게 만들어놨음, 만약 어떤 제약 조건도 없는 뷰가 있는 레이아웃을 기기에서 실행하면 [0,0] (왼쪽 위 구석) 에 그려짐
아래의 그림 1에서 에디터에서는 레이아웃이 괜찮아 보이지만 뷰 C에 수직 제약 조건이 없음, 이 레이아웃이 기기에서 그려질 때 뷰 C는 뷰 A의 왼쪽과 오른쪽 가장자리에 맞게 수평적으로 정렬되지만, 수직 제약 조건이 없기 때문에 화면의 상단에 나타나게 됨
![]() | ![]() |
|---|---|
| 그림 1. 에디터에는 뷰 C가 뷰 A의 밑에 나오지만 수직 제약 조건이 없음 | 그림 2. 뷰 C가 뷰 A의 하단에 있도록 수직 제약 조건을 추가한 모습 |
를 클릭하면 됨, 제약 조건을 놓치는 상황을 피하기 위해 레이아웃 에디터의 자동 연결과 제약 조건 추론기능은 제약 조건을 자동적으로 추가해줌프로젝트에서 ConstraintLayout 을 사용하려면 다음 단계를 따라야 함
settings.gradle 파일에 maven.google.com 저장소가 선언되어있는지 확인 dependencyResolutionManagement {
...
repositories {
google()
}
}
build.gradle 파일에 다음 예제와 같이 종속 항목으로 라이브러리 추가, 예제와 최신 버전은 다를 수 있음dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
// compose에서 constraintlayout을 사용하려면 추가
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
}
그림 3. ConstaintLayout 으로 변환하기 위한 메뉴
이미 존재하는 레이아웃을 ConstraintLayout 으로 변환하려면 다음 단계를 거쳐야 함
안드로이드 스튜디오에서 레이아웃을 열고 에디터 창 하단에 있는 Design 탭 클릭
Component Tree 창에서 레이아웃을 오른쪽 클릭하고 Convert LinearLayout to ConstraintLayout 을 클릭
새 ConstraintLayout 파일을 만드려면 다음 단계를 따라야 함
Project 창에서 모듈 폴더를 클릭하고 File > New > XML > Layout XML 을 선택함
레이아웃 파일의 이름을 입력하고 Root Tag 에 androidx.constraintlayout.widget.ConstraintLayout 를 입력
Finish 클릭
다음과 같은 방법으로 제약 조건을 추가할 수 있음
Palette 창에서 뷰를 드래그해 에디터로 올려놓기
ConstraintLayout 에 뷰를 추가하면 각 모서리에 크기 조절 핸들과 각 면에 제약 조건 핸들이 있는 경계 상자로 표시됨
뷰를 클릭해 선택함
다음 중 하나의 행동을 함
제약 조건 핸들을 클릭하고 이용 가능한 앵커 포인트('anchor point')까지 드래그함, 이 점은 다른 뷰의 가장자리, 레이아웃의 가장자리 또는 가이드라인일 수 있음, 제약 조건 핸들을 드래그 하면 레이아웃 에디터가 잠재적 앵커와 파란색 오버레이를 보여줌
다음 그림에서 보이는 것처럼, Attributes 창의 Layout 부분에서 Create a connection
버튼 중 하나를 클릭함
그림 4. 연결을 만들 수 있는 Attribute 창의 Layout 부분
제약 조건이 생성될 때 에디터는 두 뷰에 기본 margin 을 추가해 구분함
제약 조건을 생성할 때 지켜야할 것들
모든 뷰는 최소한 두개(하나는 수직, 하나는 수평)의 제약 조건이 있어야 함
제약 조건 핸들과 연결점 사이에는 같은 평면에 있을때만 제약 조건을 만들 수 있음, 수평 평면(왼쪽과 오른쪽 면)은 다른 수평 평면에만 제약될 수 있고, 기준선(baseline)은 다른 기준선에만 제약될 수 있음
각 제약 조건 핸들은 한 제약 조건을 위해서만 사용될 수 있지만, 앵커 포인트는 여러 제약 조건에 사용될 수 있음
다음과 같이 제약 조건을 삭제할 수 있음
제약 조건을 클릭해 선택하고 키보드의 Delete 버튼을 누르기
제약 조건 앵커를 키보드의 Control(macOS 에서는 Command)을 누른채로 클릭하기, 아래 그림과 같이 Control 을 누르고 있는 상태에서 마우스로 제약 조건을 가리키면 클릭을 누르면 삭제할 수 있다고 나타내기 위해 빨갛게 변함
그림 5. 클릭해서 삭제할 수 있다는 것을 나타내는 빨간 제약 조건
다음 그림과 같이 Layout 부분의 Attributes 창에서 제약 조건 앵커(anchor) 클릭
그림 6. 제약 조건 앵커를 클릭해 삭제하기
영상 2. 에서 보이는 것처럼 뷰에 이미 존재하는 제약 조건의 반대 방향으로 제약 조건을 추가하면 제약 조건 선이 스프링처럼 표시되어 반대쪽 힘을 나타냄, 이런 효과는 대부분 뷰의 크기가 fixed 나 wrap content 일 때 나타나고 이 경우 뷰는 제약 조건 사이의 정가운데에 위치하게 됨, 뷰가 제약 조건에 맞게 크기가 늘어나게 하고 싶으면 크기를 match constraints 로 변경하면 됨, 현재 크기를 유지하면서 정중앙에서 벗어난 곳에 위치시키고 싶다면 constraint bias 를 조정하면 됨
다음에서 설명하는 것처럼 제약 조건을 사용해 다양한 레이아웃 동작을 만들 수 있음
그림 7. 부모에 수평 제약 조건
뷰의 측면을 레이아웃의 대응되는 면에 제약 조건을 걸 수 있음
그림 7. 에서 뷰의 왼쪽 측면이 부모 레이아웃의 왼쪽 측면에 연결되어 있음, 레이아웃의 측면에서 얼마나 거리를 줄 것인지를 여백(margin) 으로 설정할 수 있음
그림 8. 수평과 수직 제약 조건
그림 10. 수평 정렬 제약 조건의 오프셋
그림 9. 수평 정렬 제약 조건
그림 9. 에서 B의 왼쪽 측면을 A의 왼쪽 측면에 정렬시켰음, 만약 뷰의 가운데로 정렬하고 싶다면 제약 조건을 양쪽에 모두 걸면 됨
뷰를 제약 조건의 안쪽으로 드래그해 기준에서 띄어지도록(offset) 정렬할 수 있음, 예시로 그림 10. 에서 B가 24dp 만큼 떨어지도록 정렬되어 있음, 오프셋(offset)은 제약된 뷰의 여백(margin)으로 정의됨
정렬할 모든 뷰를 선택하고 툴바에 있는 Align
을 클릭해 정렬 유형을 선택할 수도 있음
baseline) 정렬
그림 11. 기준선 정렬 제약 조건
그림 11. 에서 B의 기준선이 A의 텍스트에 맞게 정렬되어 있음
기준선을 정렬하는 제약 조건을 만드려면 제약할 텍스트 뷰를 우클릭하고 Show Baseline 을 클릭해 기준선이 보이게 해야 함, 그 이후에 텍스트 기준선을 다른 기준선으로 드래그해 제약 조건을 만들 수 있음
그림 12. 가이드라인에 제약된 뷰
가이드라인을 생성하려면 툴바에서 Guidelines
를 클릭하고 Add Vertical Guideline 이나 Add Horizontal Guideline 을 클릭하면 됨
점선을 드래그해 위치를 변경할 수 있고, 가이드라인의 끝에 있는 원을 클릭해 측정 모드를 켤 수도 있음
그림 13. 배리어에 제약된 뷰 C, 배리어의 위치는 뷰 A와 뷰 B의 위치와 크기에 의해 결정됨
예시로 그림 13. 에서 뷰 C가 배리어의 오른쪽 면에 제약되어있는 것을 볼 수 있음, 배리어는 뷰 A와 뷰 B의 끝(또는 left-to-right 레이아웃에서 오른쪽 면)으로 지정되어 있음, 이 상황에서 배리어는 뷰 A와 뷰 B의 오른쪽 면이 더 먼쪽으로 이동하게 됨
다음과 같이 배리어를 만들 수 있음
툴바에서 Guidelines
를 클릭하고 Add Vertical Barrier 나 Add Horizontal Barrier 를 클릭함
Component Tree 창에서 배리어 안으로 넣을 뷰들을 선택해 배리어 컴포넌트 안으로 드래그함
Component Tree 에서 배리어를 선택하고, Attributes
창을 열어 barrierDirection 을 설정함
배리어 안에 들어있는 뷰에서 해당 뷰를 포함하는 배리어에 제약 조건을 걸 수 있음, 이렇게 하면 어떤 뷰가 가장 길거나 높은지 몰라도 배리어를 기준으로 뷰를 정렬할 수 있음
배리어에 가이드라인을 포함해 배리어의 최소 범위를 지정할 수 있음
뷰의 크기가 fixed 또는 wrap content 이고, 같은 방향의 양쪽에 제약 조건을 추가하면 뷰는 두 제약 조건의 정중앙에 위치하게 되고 기본적으로 두 제약조건의 편향이 50%로 지정됨, 영상 3. 에서 보이는 것처럼 Attributes 창에서 편향 슬라이더를 드래그해 편향을 조정할 수 있음
이런 편향이 생기는 대신 뷰가 제약 조건에 맞게 크기가 변경되기를 원하면 크기를 match constraints 로 변경하면 됨
영상 3. 제약 조건 편향 조정
그림 14. 뷰를 선택할 때 Attributes 창은 1 크기 비율, 2 제약 조건 삭제, 3 높이 또는 너비 모드, 4 여백, 5 제약 조건 편향 제어를 포함함, 또한 레이아웃 에디터의 6 제약 조건 리스트에서 개별 제약 조건을 클릭해 강조 표시할 수 있음
뷰의 크기를 조절하기 위해 코너 핸들을 사용할 수 있지만 이는 크기를 하드코딩함, 이는 뷰가 내용이 달라지거나 화면 크기에 맞춰 크기가 변하지 않게 됨, 다른 크기 모드를 선택하려면 뷰를 클릭하고 에디터의 오른편에서 Attributes
창을 열어야 함
그림 14. 에서 보이는 것처럼 Attributes 창의 위쪽에 위치한 것은 몇가지의 레이아웃 속성을 제어할 수 있는 뷰 인스펙터임, 이는 뷰가 ConstraintLayout 일때만 사용 가능함
그림 14. 의 번호 3 으로 표시되어 있는 기호를 클릭해 높이와 너비가 계산되는 방법을 바꿀 수 있음, 이 기호는 다음과 같이 크기 모드를 나타내고 기호를 클릭해 이 설정들로 바꿀 수 있음
Fixed : 텍스트 박스에 특정 값을 입력해 지정하거나 에디터에서 뷰의 크기를 조절해 특정 크기로 맞춤
Wrap Content : 뷰의 내용이 필요한 만큼만 크기가 확장됨
layout_constrainedWidth : 이 값을 true 로 설정하면 가로 길이의 변화가 제약 조건에 맞게 일어남, 기본적으로 WRAP_CONTENT 로 설정되어 있는 위젯(뷰)은 제약 조건에 의해 제한되지 않음
Match Constraints : 뷰의 여백(margin)을 고려한 후, 양 면의 제약 조건을 만날때까지 뷰를 확장시킴, 다음 속성들과 값을 이용해 이 행동을 수정할 수 있음, 이 속성들은 너비를 match constraints 로 설정했을때만 효과가 있음
layout_constraintWidth_min : 뷰의 최소 너비를 dp 로 설정함layout_constraintWidth_max : 뷰의 최대 너비를 dp 로 설정함만약 한 방향에 하나의 제약 조건만 가지고 있다면 뷰는 그 방향의 크기(높이 또는 너비)를 내용에 맞게 크기를 조절함, 이 크기 모드를 높이 또는 너비에 사용하면 크기를 비율로 설정할 수 있음
그림 15. 높이를 기준으로 너비가 변하는 16:9 비율로 설정된 뷰
너비와 높이가 모두 match constraints 로 설정되어 있을때, Toggle Aspect Ratio Constraint 를 클릭해 어떤 방향이 비율의 기준이 될지 정할 수 있음, 뷰 인스펙터에서 비율의 기준이 되는 방향의 면이 굵은 선으로 표시됨
예시로 모든 방향에 match constraints 를 설정하고 Toggle Aspect Ratio Constraint 를 두 번 클릭해 너비가 높이의 비율이 되도록 설정할 수 있음, 이렇게 되면 뷰의 전체 크기가 높이에 의해 정해지게 됨
뷰의 간격을 일정하게 설정하려면 툴바에서 Margin
을 클릭해 레이아웃에 추가하는 각 뷰에 대한 기본 여백을 설정해야 함, 이렇게 기본 여백을 설정하고 나면 그 이후에 추가하는 뷰에 적용이 됨
Attributes 창에서 각 제약 조건을 나타내는 라인에 있는 숫자를 클릭해 각 뷰의 여백을 조정할 수 있음, 그림 14. 의 4 번에서 아래 여백이 16dp로 설정되어 있는 것을 나타냄
그림 16. 툴바의 **Margin** 버튼
체인은 각자와 양방향으로 위치 제약 조건이 걸려있는 뷰의 그룹을 의미함, 체인에 묶여있는 뷰는 수직 또는 수평 방향으로 분산될 수 있음
체인 스타일은 다음과 같이 있음
1 Spread : 여백이 계산된 이후에 뷰가 균등하게 분산됨, 기본 사항임
2 Spread inside : 첫 뷰와 마지막 뷰가 체인의 양쪽 끝에 고정되고 나머지 뷰가 균등하게 분산됨
3 Weighted : 체인이 spread 또는 spread inside 로 설정되어 있을 때, 하나 이상의 뷰를 match constraints(0dp) 로 설정해 남는 공간을 채울 수 있음, 기본으로 match constraints 로 설정된 뷰에 공간이 균등하게 분산되지만 layout_constraintHorizontal_weight 와 layout_constraintVertical_weight 속성을 사용해 각 뷰에 가중치(weight)를 할당할 수 있음, 이는 linear layout 의 layout_weight 처럼 높은 가중치를 가지는 뷰가 더 많은 공간을 차지하고 같은 가중치의 뷰들이 같은 양의 공간을 차지하도록 함
4 Packed : 뷰가 여백이 계산된 뒤에 가운데로 뭉침, 체인의 head 뷰의 편향을 변경해 체인 전체의 편향을 조절할 수 있음
체인의 head 뷰는 수평 체인에서 가장 왼쪽 뷰(left-to-right 레이아웃에서)이고 수직 체인에서 가장 위의 뷰를 뜻하고 이 뷰가 체인의 스타일을 XML에서 정의함, 하지만 체인의 아무 뷰 하나를 선택해 뷰 아래에 뜨는 체인 버튼
을 클릭해 스타일을 spread, spread inside, packed 사이에서 바꿀 수 있음
영상 4에서 보이는 것처럼 다음과 같이 체인을 생성할 수 있음
체인에 포함될 모든 뷰를 선택함
뷰 하나를 오른쪽 클릭함
Chains 를 선택함
Center Horizontally 나 Center Vertically 를 선택함
영상 4. 수평 체인 생성
레이아웃에서 뷰를 원하는 위치에 놓기 위해 제약 조건을 모든 뷰에 대해 추가하는 대신, 레이아웃 에디터에서 뷰를 원하는 위치에 놓고 Infer Constraints
를 클릭해 자동으로 제약 조건을 생성할 수 있음
Infer Constraints 는 모든 뷰를 위한 가장 효율적인 제약 조건을 결정하기 위해 레이아웃을 스캔함, 뷰를 현재 위치에 있도록 제약 조건을 만들며 그와 동시에 유연성을 제공함, 이후에는 레이아웃을 다른 화면 크기와 방향에 의도대로 반응하도록 조절해야 할 수 있음
Autoconnect to Parent 는 개별적으로 사용할 수 있는 기능임, 이를 켜놓으면 자식 뷰를 레이아웃에 추가할 때 2개 이상의 제약조건을 자동적으로 생성함, 하지만 이는 부모 레이아웃에 뷰를 제약시키는게 적절할 때만 일어남, 자동연결은 레이아웃의 다른 뷰에 제약조건을 생성하지 않음
자동연결은 기본적으로 꺼져있음, 레이아웃 에디터 툴바의 Enable Autoconnection to Parent
를 클릭해 킬 수 있음
ConstraintLayout 을 사용하면 ConstraintSet 과 TransitionManager 를 사용해 요소의 크기가 위치가 변하는 애니메이션을 만들 수 있음
ConstraintSet 은 ConstraintLayout 의 모든 자식 요소들의 제약 조건, 여백, 패딩(padding) 을 나타내는 가벼운 객체임, 표시되는 ConstraintLayout 에 ConstraintSet 을 적용하면 레이아웃은 모든 자식의 제약 조건을 업데이트함
ConstraintSet 을 사용한 애니메이션을 만드려면 애니메이션의 시작과 끝 키프레임으로 동작할 두 레이아웃 파일을 지정해야 함, 그 이후에 두번째 키프레임 파일에서 ConstraintSet 을 불러와 표시되는 ConstraintLayout 에 적용할 수 있음
노트:
ConstraintSet애니메이션은 오직 자식 요소의 크기와 위치만 애니메이션으로 만듭니다. 색상과 같은 다른 요소는 애니메이션으로 만들지 않습니다.
버튼이 화면 하단으로 움직이는 애니메이션을 만드는 예제 코드
// MainActivity.kt
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.keyframe_one)
constraintLayout = findViewById(R.id.constraint_layout) // 멤버 변수
}
fun animateToKeyframeTwo() {
val constraintSet = ConstraintSet()
constraintSet.load(this, R.layout.keyframe_two)
TransitionManager.beginDelayedTransition()
constraintSet.applyTo(constraintLayout)
}
// layout/keyframe1.xml
// 키프레임 1은 애니메이션의 모든 요소의 시작 위치와
// 최종 색상 및 텍스트 크기를 포함합니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// layout/keyframe2.xml
// 키프레임 2는 최종 위치를 가지는 다른 ConstraintLayout을 포함합니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
노트: 안드로이드 스튜디오 3.6 이상에서는 뷰 바인딩 기능이
findViewByID()호출을 대체할 수 있습니다. 이는 또한 뷰와 상호작용하는 코드를 위해 컴파일 타임에 타입 세이프티(type safety)를 제공합니다.findViewById()대신 뷰 바인딩를 사용하는 것을 고려해보세요.
원문: https://developer.android.com/develop/ui/views/layout/constraint-layout