Things take time

[Android] Constraint Layout, 제약조건을 사용하는 레이아웃 본문

Android(기능)

[Android] Constraint Layout, 제약조건을 사용하는 레이아웃

겸손할 겸 2020. 5. 7. 15:09

[Constraint Layout]

이름 그대로 제약조건을 활용하는 레이아웃이다, 어떻게 보면 swift의 오토레이아웃과 같은 느낌으로 사용할 수 있겠다 싶어 정리한다. 실제해보니 많이 비슷하다. => 활용도가 높을 것이다.

 

기존 사용중인 프로그램은 다른 레이아웃으로도 충분하다 생각하여 사용했으나, 좀 더 유연한 처리를 하기 위해 알아보았다. 또한 참고한 블로그/티스토리는 하단에 있으므로 부족하다 생각하면 가서 참고할 것

 

[일단 써보자]

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="버튼1"
        />

    <Button
        android:id="@+id/btn2"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        android:text="버튼2"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

레이아웃 파일이다.

기본적으로 컨스트레인트 레이아웃을 넣었고, 버튼 두개를 배치했으며, 두 번째 버튼에는 id가 btn1인것의 오른쪽기준으로 왼쪽 정렬을 하라 했다.

layout_constraintLeft_toRightOf="@id/btn1"

속성을 해석할때, layout_옆에 있는 것은 현재 위젯을 의미하며, 마지막 _옆에있는 것은 = ""에 오는 위젯을 의미한다.

다시 설명하면

layout_constraintLeft : 현재 이 속성을 사용한 btn2 위젯의 좌측 정렬은

_toRightOf="@id/btn1" : btn1이란 id를 가진 위젯의 오른쪽 정렬에 맞춰라.

 

즉, btn2의 좌측은 btn1의 오른쪽에 맞춰서 정렬하란 의미이므로 결과는 아래와 같다.

스크린샷 1

그런데 저 Button이란 태그엔 빨간줄이 쳐져있고 메시지는 다음과 같이 나온다.

스크린샷 2

해석을 해보자면,

현재 이 뷰는 조건이 걸려있지 않다. 이것은 단지, 명시된 위치만 설정했기 때문에 당신이 제약조건을 추가하지 않는다면 미리보기화면에서 보이는 0,0 처럼 보이는 저 위치를 점프할 것이다.

 

스크린샷1을 자세히보자, 버튼1을 클릭했을 때 동서남북에 파란 버튼의 속이 비어있다. 만약 btn1의 아이디를 가진 버튼을 아래와 같이 수정하자.

    <Button
        android:id="@+id/btn1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="버튼1" />

parent는 현재 위젯이 가진 부모, ConstraintLayout을 의미할 것이고 첫번째 조건을 해석하면, 현재 버튼의 좌측 정렬은 부모의 좌측정렬과 맞춰라 가 된다.

 

다시 미리보기화면을 보면 

스크린샷 3 : 좌, 상단의 버튼이 파란색으로 채워짐

위와 같이 확인이되면서, 태그에 걸려있던 빨간 줄이 사라진다.

iOS의 오토레이아웃을 해봤다면, 이해되는 코드일 것이다. 당연히 위젯의 너비/ 높이만을 설정하는 것이 아니라 어디에 배치될 것인지를 정해야한다는 것이다.

 

일반 리니어레이아웃의 orientation속성처럼 위젯의 배치를 가로, 세로로 정할 수 없는 레이아웃이기 때문에 배치를 직접 작성해줘야한다는 것이다.

 

 

[중간정렬]

만약, 위젯의 정렬이 화면의 가운데였으면 할때는 아래와 같이 사용한다.

    <Button
        android:id="@+id/btn1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="버튼1" />

스크린샷 4

그럼 위처럼, 가운데 정렬된 것을 확인할 수 있다.

 

여기서, 버튼2를 보면 처음 버튼2에 layout_constraintLeft_toRightOf라는 옵션으로 btn1을 넣어 붙어있도록 했으나, 붙어있지 않다. 이유는 무엇일까.

 

앞서 설명한 것과 같다. 버튼2의 너비,높이 좌측 정렬은 알려줬지만, 다른 정렬은 알려주지 않았기때문이다. 그러므로 버튼2의 태그에는 분명, 좀전에 수행했던 버튼1처럼 빨간줄과 함께, 경고 메시지가 있을 것이다. 그래서 아래와 같이 수정했다.

    <Button
        android:id="@+id/btn2"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        app:layout_constraintTop_toTopOf="@id/btn1"
        android:text="버튼2" />

스크린샷 5

버튼1은 parent기준 중앙정렬, 버튼2는 버튼1의 상단과 맞춤정렬, 좌측정렬은 버튼1의 우측에 맞추라 했으므로 위와 같이 나오는 것이다.

 

[원형 조건]

    <Button
        android:id="@+id/btn3"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="버튼3"
        app:layout_constraintRight_toLeftOf="@id/btn1"
        app:layout_constraintTop_toBottomOf="@id/btn1"
        app:layout_constraintCircle="@id/btn1"
        app:layout_constraintCircleAngle="210"
        app:layout_constraintCircleRadius="150dp" />

버튼3을 추가했다. 우측정렬은 btn1의 왼쪽, 상단 정렬은 btn1의 밑쪽에 맞추라 했으므로 여기까지보면 버튼1의 바로 밑에 버튼3이 생기겠지만, 중요한 것은 그 아래 Circle 조건이다.

 

layout_constraintCircle="@id/btn1" : btn1의 위치를 기준으로 원을 만든다.

layout_constraintCircleAngle="210" : 그 원의 각은 210도를 기준으로 되어있다.

layout_constraintCircleRadius="150dp" : 원의 반지름은 150dp로 이루어져있다.

 

즉, 모든 조건을 합치자면, 버튼1을 기준으로 상단, 좌우의 정렬을 맞춰주고 원을 생성하여 해당 원의 중심에 버튼3을 생성한다. 이를 바탕으로 생성하면

스크린샷 6

[응용]

소스도 길고, 화면으로 보는게 편하므로 바로 코드를 보이겠다.

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="긴 장문의 글을 써보도록 하죠, 제 티스토리 주소는 g-y-e-o-m.tistory.com입니다."

        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constrainedWidth="true"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/btn2"
        />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"

        app:layout_constraintVertical_bias="0"
        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/btn1"
        />

    <!--
        bias를 사용하려면 사용하려는 방향에 맞게 제약조건이 걸려있어야 함
        layout_constraintVertical_bias를 사용하려면 Top, Bottom에 조건이 있어야 함
    -->
    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="여기는 짧은 텍스트 작성"


        app:layout_constraintVertical_bias="0.2"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constrainedWidth="true"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/btn4"
        />

    <Button
        android:id="@+id/btn4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"
        app:layout_constraintVertical_bias="0.2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/btn3"
        />

    <!--
        layout_constraintHorizontal_weight를 사용하기 위해선, width가 wrap_content, match_parent가 아닌
        0dp로 해야 함(weight_sum과 같음)
    -->
    <TextView
        android:id="@+id/text1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="text1"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_weight="1"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/text2"
        app:layout_constraintTop_toTopOf="parent"
        />


    <TextView
        android:id="@+id/text2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="text2"
        app:layout_constraintHorizontal_weight="2"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/text3"
        app:layout_constraintLeft_toRightOf="@+id/text1"
        app:layout_constraintTop_toTopOf="parent"
        />

    <TextView
        android:id="@+id/text3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="text3"
        app:layout_constraintHorizontal_weight="2"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/text2"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

스크린샷 7

일단 화면 맨 위에 있는 두 개의 버튼을 보자.

btn1이란 아이디를 가진 버튼은 긴 장문의 글로 써져있다. 그런데 btn2라는 버튼2가 자리를 차지함으로써, btn1이 적절하게 짤렸다.

 

이 옵션은 아래의 옵션과 관련이 있다.

layout_constrainedWidth="true"

width가 wrap_content로 되어있어 가변적인 데이터가 들어온다면, 너비는 시시각각 변하는데 여기서 나는 btn2는 항상 btn1의 오른쪽에 붙어있어야 하며 짤리지않았으면 하는 것이다.

 

스크린샷 8

만약 저 옵션을 빼버리면, wrap_content속성에 따라 길이가 길어 화면을 차지하게 되고, btn2는 btn1에 붙어있으라곤 했지만 btn1이 이미 자리차지를 했기 때문에, 스크린샷 8의 우측을 보면 알 수 있는 것처럼 화면 오른쪽에 붙어있어 사용자는 확인할 수 없게 된다. 그러므로 이 옵션은 중요하게 쓰일 것이다.

layout_constraintHorizontal_chainStyle

이 옵션의 경우, 제약조건에서 수평으로 배치된 위젯들의 chainStyle을 어떻게 할  것이냐 인데, 여기서 체인이란 스크린샷 7에서 보는 것처럼 각각의 위젯이 지그재그나 선으로 연결된 것을 확인할 수 있는데 이것을 의미한다. packed라는 것은 위젯들을 붙이라는 건데, 위의 소스에서 btn1, btn2의 width를 100dp로 각각 변경후 확인해보면 아래와 같다.

 

packed옵션을 주었을 때
spread 옵션을 주었을 때

layout_constraintHorizontal_bias

이 옵션은 수평으로된 제약조건의 bias, 방향, 배치편향을 어디로 둘 것이냐이며 0(좌 혹은 위)~1(우 혹은 아래)의 값을 지닌다.

 

위의 예제는 layout_constraintHorizontal_bias="0"로 주었기에 수평 편향이 좌측으로 되어 좌측 정렬처럼보이는 것이다. 1로 했다면 두개의 위젯 컴포넌트들은 우측으로 붙어있을 것이다.(해당 테스트를 위해서는 width를 wrap_content로 주지 말고 일정 값으로 둘 것)

 

그리고 다시 스크린샷 7의 상단과 중앙 사이에 있는 버튼 두 개를 보자. 그 곳에는 아래와 같은 속성이 들어 있다.

layout_constraintVertical_bias="0.2"

상하로 된 제약조건의 편향을 0.2, 즉 맨 위가 0이므로 화면 상단으로부터 20%떨어진 곳에 위치하란 것으로, 스크린샷을 확인해보면 알 수 있다.

 

다만 여기서 bias를 사용하려면, 위의 주석에서 적었듯이

bias를 사용하려면 사용하려는 방향에 맞게 제약조건이 걸려있어야 한다. 예로, layout_constraintVertical_bias를 사용하려면 Top, Bottom에 조건이 있어야 함

 

이것 때문에, 중간에 소스 하나로 넣었다. 

 

그리고 스크린샷7의 가장 중앙에 있는 텍스트 뷰는 하단에서 참고한 블로그에서 소스를 그냥 가져다 쓴 것인데 layout_constraintHorizontal_weight이란 속성을 통해 레이아웃의 weightSum과 같은 용도를 쓸 수 있다는 것을 알리기 위함이다. 해당 사용방은 layout_constraintHorizontal_weight 이란 이름에 맞게 width가 0dp로 수정해주어야 한다는 것이다.

 

[마치며]

실제 사용여부와 달리, 모르고 있는게 있어서 시작한 예제였는데 생각보다 도움이 많이 되었고, iOS를 해본사람이라면 오토레이아웃이 생각날 수 밖에 없는 레이아웃이었다.

 

그리고 초보자들이 할 수 있는 실수중 하나는 제약조건의 배치를 둘 때, Left와 Right가 짝인 것처럼 Start와 End는 그에 대응 하는 짝이다. 무슨 말이냐면, Left에 관련된 조건을 줘서 이제 Right를 줘야하는데 End를 사용해서 주지 말라는 뜻, Start를 썼으면 End로 조건을 마무리하고, Left로 썼으면 Right로 대응하도록 하란 의미이다.

 

여러 블로그들을 보면서 내가 이해하기 위해 썼는데, 참고한 곳은 다음과 같다.

 

https://www.charlezz.com/?p=669

 

Constraint Layout – Part1. 만능 레이아웃 | 찰스의 안드로이드

ConstraintLayout ConstraintLayout은 ViewGroup을 상속받아 확장시킨 라이브러리 입니다. 지난 2017년 2월에 1.0 버전이 출시되어 많은 개발자들이 이 라이브러리를 유용하게 써왔습니다. 출시때는 Android API9 �

www.charlezz.com

     

https://medium.com/@futureofdev/android-constraintlayout-%EC%89%BD%EA%B2%8C-%EC%95%8C%EC%95%84%EA%B0%80%EC%9E%90-62d2ded79c17                                                             

 

Android ConstraintLayout 쉽게 알아가자

LinearLayout이나 RelativeLayout을 쓰다보면 ConstraintLayout을 왜쓰면 좋을지 궁금증을 가지게 됩니다. 저도 그랬고, 새로 학습해야해서 잠깐 미뤄뒀는데, 직접 써보니 이게 왠걸, 정말 재미있는 레이아웃입니다.

medium.com

https://zoiworld.tistory.com/485

 

ConstraintLayout 6 - layout_constraintHorizontal_weight

layout_constraintHorizontal_weight zoiworld.tistory.com