본문 바로가기

Android

텍스트 오른쪽에 붙어다니는 이미지가 있을 때, 텍스트가 길어진다면

안드로이드 레이아웃 중에는 되게 쉬워보이는데 하다보면 이상하게 잘 안되는 것들이 있습니다.

그 중 대표적인 것이 텍스트 줄바꿈과 관련있는 레이아웃입니다. 

 

아래와 같은 레이아웃이 있다고 가정합시다.

텍스트뷰가 하나 있고, 그 오른쪽에는 A라는 뷰가 텍스트 오른쪽에 붙어 있어서 텍스트 길이가 변경됨에 따라 위치가 이동됩니다.

단, A의 위치는 절대 B를 넘어서지 않습니다. 즉, 텍스트의 길이가 계속 길어져서 A가 오른쪽으로 계속 이동하다가, B를 만나면 거기서 멈춥니다. 텍스트는 2줄이 되거나, "..." 말줄임표로 처리되어야 합니다. 이렇게요.

 

되게 쉬울 것 같습니다. 간단하잖아요? 텍스트를 쭉 늘리기만 하면 되니까요. 한 번 해봅시다.

(Constraint layout 으로 구현합니다. androidx.constraintlayout:constraintlayout:2.0.4 기준입니다)

 

Fail 1:

텍스트는 길이가 늘어남에 따라 늘어나야 하니, width 를 wrap_content 로 주고, 음... 다른 것들도 굳이 뭔가를 설정할 필요 없으니, wrap_content로 다 동일하게 설정합니다.

 

<TextView
    android:id="@+id/Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

전체코드

더보기

 

<?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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/B"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/Text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#DDD"
            android:text="Hello world Hello world Hello world Hello world "
            android:textSize="20dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@id/A"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/A"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:background="#999"
            android:text="A"
            android:textSize="20dp"
            app:layout_constraintLeft_toRightOf="@id/Text"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>

    <TextView
        android:id="@+id/B"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:background="#AAA"
        android:text="B"
        android:textSize="20dp"
        app:layout_constraintLeft_toRightOf="@id/constraint"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

어? 화면을 뚫고 나갑니다. 실패.

 

게다가 글자길이를 줄여보니 화면 가운데에 자동으로 정렬이 되네요. 역시 실패.

 

Fail 2:

일단 글자를 왼쪽에 붙여야 하니, 체인스타일을 packed 로 줘서 두 레이아웃을 가까이 붙이고, bias를 0으로 주어 왼쪽에 붙여봅시다.

 

<TextView
    android:id="@+id/Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello world Hello world Hello world"
    app:layout_constraintHorizontal_bias="0"
    app:layout_constraintHorizontal_chainStyle
="packed"
    ...
    app:layout_constraintWidth_default="wrap" />

글자 길이가 짧을 때는 잘 표시 되네요. 글자 길이를 늘려봅시다.

아... 여전히 A가 화면을 뚫고 나가 버리네요. 왜 구글은 레이아웃을 이렇게 동작하도록 만들었을까요. 이해가 되지 않습니다.

 

Fail 3:

화면을 뚫고 나가면 안되니, 텍스트의 width 우선순위를 낮춰 봅시다. 

constraint layout에서 좌우 위치관계가 다 설정되어 있을 때 0dp 로 width 를 설정하면, 너비 계산 우선순위가 낮아짐과 동시에 남은 위치를 채우게 됩니다.

 

<TextView
    android:id="@+id/Text"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="Hello world Hello world Hello world"
    app:layout_constraintHorizontal_bias="0"
    app:layout_constraintHorizontal_chainStyle="packed"
    .../>

 

우와. 이제는 좀 괜찮아 보이네요. A가 화면 밖으로 밀려나지도 않고, 텍스트는 정의된 영역 내의 "남은 부분"을 잘 채워주고 있습니다. 

그러면 글자를 줄여볼까요?

 

음... A가 글자의 오른쪽에 있는 것은 맞는데.... 텍스트가 영역의 남은 부분을 채우기 때문에 A는 항상 화면 "제일 오른쪽" 에 있을 뿐, 우리가 원하는 "텍스트 길이에 따라 위치 이동"하지는 않습니다. 뭔가 방법이 없을까요?

 

있습니다.

 

Success ?

android:layout_width="0dp"

app:layout_constraintWidth_default="wrap"

 

되게 헷갈리는 이름입니다. "뭐? width 가 0dp 인데 또 wrap 이라고?" 맞습니다. 이렇게 설정해 주면 우리가 원하는 형태로 보여줄 수 있습니다.

 

 

여기서 주의할 점은, android:layout_width 에 들어갈 수 있는 값은

- "10dp" 등의 실제 수치,

- "wrap_content, match_parent" 등의 길이 "정책"

- 그리고 "0dp" 우선순위 낮추기 같은 "수치"이지만 "정책" 인 값

입니다. 즉, 여기 들어가는 값은 단순히 숫자가 아닌, 숨은의미를 가지고 있는 값들입니다.

 

만약 그렇다면 차라리 android:layout_width 에 들어갈 수 있는 값으로 "wrap_content_lowest_priority" (wrap_content 인데, 화면을 채울 때는 다른 레이아웃보다 우선순위가 낮게 하세요라는 의미)등이 들어가는게 의미상으로 더 맞지 않은가 라고 질문할 수 있지만, 아쉽게도 constrant layout은 일종의 외부 라이브러리 같은 것이라... 너저분합니다.

 

 

Success!

하지만 더 간결한 방식이 있습니다. Constraint layout 1.1 에서 아래 attribute가 추가되었습니다.

app:layout_constrainedWidth

 

developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout

"keep enforcing constraints to limit the resulting dimension"

"이 레이아웃이 채울 공간을 넘어서지 않도록 제한합니다"

 

<TextView
    android:id="@+id/Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello world Hello world Hello world"
    app:layout_constraintHorizontal_bias="0"
    app:layout_constraintHorizontal_chainStyle="packed"
    app:layout_constrainedWidth="true"
    .../>

훨씬 더 의미있고 가독성 높은 코드가 되었습니다. 이 텍스트뷰의 너비는 

android:layout_width="wrap_content": 내용에 따라 가변적입니다.

app:layout_constrainedWidth="true" : 그렇지만 화면을 뚫고 나가지는 않습니다.

 

최종 코드는 이렇습니다. (chain style에 영향받지 않도록, Barrier가 추가되었습니다)

처음 코드에 비해 Flat 하고, 가독성있는 코드가 되었습니다.

 

더보기
<?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">

    <TextView
        android:id="@+id/Text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#DDD"
        android:text="Hello world Hello world Hello world"
        android:textSize="20dp"
        app:layout_constrainedWidth="true"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/A"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:background="#999"
        android:text="A"
        android:textSize="20dp"
        app:layout_constraintLeft_toRightOf="@id/Text"
        app:layout_constraintRight_toLeftOf="@id/barrier"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="left"
        app:constraint_referenced_ids="B" />

    <TextView
        android:id="@+id/B"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:background="#AAA"
        android:text="B"
        android:textSize="20dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>