코틀린 6장

손현수·2022년 11월 12일

레이아웃 소개

  • 뷰들이 여기저기 산재해 있으면 사용자가 화면을 효율적으로 사용하지 못한다. 그래서 레이아웃이 필요한 것. 레이아웃의 종류에는 리니어 레이아웃, 상대적 레이아웃, 컨스트 레이아웃, 테이블 레이아웃, 프레임 레이아웃 등이 있다.

  1. 리니어 레이아웃: 수직 방향(위에서 아래로) 혹은 수평 방향(왼쪽에서 오른쪽으로 ) 차례로 주어진 뷰를 정렬한다.
  2. 상대적 레이아웃: 상대적 레이아웃을 사용하면 뷰들이 다른 뷰들로부터 위치를 지정하거나 자신이 속한 레이아웃을 기준으로 위치를 정한다. 예를 들면 'A 뷰의 오른쪽에 위치', '부모 레이아웃의 정중앙의 위치'와 같이 지정할 수 있다.
  3. 컨스트레인트 레이아웃: 뷰 사이에 수평, 수직 방향의 제약을 주어 뷰들을 위치시킨다.
  4. 테이블 레이아웃: 뷰를 행과 열로 구성하여 표(테이블)의 형태로 표현한다.
  5. 프레임 레이아웃: 뷰들을 액자처럼 쌓아놓는다. 여러 뷰들을 추가하더라도 가장 나중에 추가한 뷰가 가장 위에 위치하게 되는 것이다. 그러므로 레이아웃 내에 여러 뷰들을 배치시키는 데는 적합하지 않고 주로 화면에 표시될 하나의 뷰를 바꿔가며 표시하는 데 적합하다.

리니어 레이아웃

  • 리니어는 한국어로 하면 '직선 모양의'이라는 뜻이다. 이름에서 알 수 있다시피 이 레이아웃을 사용하면 뷰들이 세로 또는 가로 방향 직선 모양으로 정렬된다. 뷰가 쌓이는 방식은 우리가 책을 꽂을 때나, 접시를 쌓는 방식을 생각하면 된다. 뷰가 쌓이는 순서가 중요한 것이다.
  • linear_layout_1 레이아웃 리소스 파일을 만들고 다음과 같이 코드 작성
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>
  • orientation이 vertical로 설정되어 있다. 그러므로 버튼이 차례대로 수직 방향으로 정렬된다.
  • vertical을 hrozontal로 수정하면 버튼이 수평 방향으로 정렬된다.

독립적으로 위치를 지정하는 layout_gravity 속성 알아보기

  • orientation은 레이아웃 속성 자체에 값을 주어 종속된 모든 뷰가 영향을 받았다. 이번에는 리니어 레이아웃 안에 있는 뷰에 layout_gravity 속성을 주어 본다.

  • 리니어 레이아웃의 방향이 horizontal이라면 layout_gravity의 값에서 start 대신 top을, end 대신 bottom을 쓰면 된다.

비중을 지정하는 layout_weight 속성 알아보기

  • 버튼 3개를 1:2:1 비율로 부모 레이아웃의 가로에 꽉차게 배치하고 싶을 때 layout_weight를 사용하면 된다. 쉽게 생각해보면 각 크기에 가중치를 둔다고 생각하면 된다.

  • 가로 길이가 match_parent이고, 세로 길이가 match_parent인 리니어 레이아웃을 만들고, 그 태그 안에 weightSum 속성을 추가한다. weightSum은 weight의 총합을 지정해주는 것이다. 즉 꽉 찬 가로의 길이를 weightSum에 지정을 해주는 것이다. 비율이 1:2:1이라면 모두를 합한 4를 지정해주면 된다.

  • 각 버튼의 속성에 더 이상 가로 크기를 명시적으로 지정해주지 않으니 0dp라고 해준다. 레이아웃에서 뷰가 정해진 고정 값이 아닌 경우에는 0dp를 넣어준다. 그리고 layout_weight를 비율에 맞게 지정해주어야 한다.

  • 만약 weightSum의 값을 6으로 바꾸면 어떻게 될까? 6중의 4만큼만 채워지고, 나머지는 여백으로 비워둔다. 이런 방식으로 layout_weight와 weightSum을 이용하면 원하는 대로 여백까지 조정할 수 있다.

상대적 레이아웃

  • 상대적 레이아웃은 다른 뷰를 기준으로 상대적 위치를 지정하는 레이아웃이다. 리니어 레이아웃을 사용할 때보다 조금 더 복잡한 레이아웃 구성을 목표로 할 때 적합하다.

	<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:text="parent\nstart"/>
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:text="parent\nend"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="parent bottom"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:text="parent bottom\n + parent end"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="parent\ncenter"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="center\nhorizontal"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="center\nvertical"/>

  • \n은 줄바꿈을 하고 \t는 탭을 누른 것처럼 띄어쓰기를 해준다.

  • 다른 뷰들이 기준으로 활용할 뷰를 먼저 만들어준다. 기준 뷰가 되는 뷰들은 id값을 부여하여 다른 뷰들이 이용할 수 있게 한다.

  • 이제 앞서 만든 자식 뷰 중 하나를 기준으로 새로운 위치를 지정한다. 기준1 버튼의 오른쪽 아래에 BUTTON1을 위치시키는 코드를 작성한다. 기준2 버튼 오른쪽 위에 BUTTON2를 위치시킨다. 기준3 버튼 왼쪽 위에 BUTTON3을 위치시킨다.

	<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/standard_1"
        android:layout_toRightOf="@id/standard_1"
        android:text="button 1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/standard_2"
        android:layout_toRightOf="@id/standard_2"
        android:text="button 2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/standard_3"
        android:layout_toStartOf="@id/standard_3"
        android:text="button 3"/>

컨스트레인트 레이아웃: ConstraintLayout

  • 컨스트레인트는 한국어로 '제약'이다. 한 화면을 구성하는 뷰들에 서로 제약을 준다.
  • 컨스트레인트 레이아웃을 리니어 레이아웃이나 상대적 레이아웃보다 더 자주 쓰는 가장 큰 이유는 다양한 화면 크기에 대응하는 반응형 UI를 쉽게 구성할 수 있기 때문이다. 또 중첩된 레이아웃을 사용하지 않고도 크고 복잡한 레이아웃을 만들 수 있어 성능면에서도 유리하다. 리니어 레이아웃과 상대적 레이아웃은 잘 모르더라도 컨스트레인트 레이아웃은 꼭 잘 알아야 한다.

컨스트레인트 레이아웃 기본 속성

  • 컨스트레인트 레이아웃에서 자식 뷰의 위치를 정의하려면 자식 뷰의 수직/수평 방향에 제약 조건을 각각 하나 이상 추가해야 한다. 자식 뷰에 아무런 제약도 추가해주지 않으면 왼쪽 상단에 배치된다. 컨스트레인트 레이아웃에서 자주 쓰는 속성은 다음과 같은 모습이다.
app:layout_constraint[내 방향]_to[기준 뷰 방향]Of = "[기준 뷰 ID or parent]"
  • 내 방향을 기준 뷰 방향에 맞추고 그 기준 뷰가 무엇인지 알려준다. 이것만 잘 기억해도 컨스트레인트 레이아웃이 한결 수월해진다.
  • 새로운 layout 파일을 만들고 xml 코드를 다음과 같이 작성한다.
  • 수평 방향으로 제약을 추가했다. 이 버튼 뷰의 시작 지점을 부모 레이아웃의 시작점에 배치시키고 32dp를 간격으로 주었다.
  • 이렇게 수평 방향만 추가하면 IDE에서 버튼 뷰에 빨간 밑줄과 함께 에러가 뜬다. 수직 방향으로 제약이 설정되지 않았다는 에러이다. 수직 방향 제약으로 버튼 뷰의 위쪽을 부모 레이아웃의 위쪽에 위치시켜본다.
  • layout_constraintTop_toTopOf 속성을 추가해 뷰의 위쪽을 부모 레이아웃의 위쪽에 위치시켰다. 위쪽에 100dp만큼 마진을 주려고 layout_marginTop 속성을 추가했다. 그럼 버튼이 수직과 수평 방향 모두 제약이 생기고 에러 메시지도 사라진다.
  • 이번에는 부모 레이아웃을 기준으로 잡지 않고, 방금 만든 Button1을 기준 뷰로 새로운 텍스트뷰를 배치해본다. Button1을 기준으로 삼을 수 있게 ID를 추가한다.
  • app:layout_constraintTop_toTopOf, app:layout_constraintBottom_toBottomOf 속성을 사용해 수직 방향으로 두 제약을 추가했다. 제약은 각각의 방향으로 최소 하나, 혹은 그 이상을 지정해줄 수 있다. 이 경우에는 제약을 두 개 추가하여 Button1의 위쪽과 아래쪽 중간에 텍스트뷰가 위치한다. 제약을 두 개 추가하는 것을 마치 뷰에 줄을 걸어서 각 방향으로 같은 힘으로 당기고 있다고 생각하면 이해하기 쉽다. 위아래로 동시에 당기고 있으므로 텍스트뷰는 중간에 위치하게 되는 것이다.

컨스트레인트 레이아웃에서 마진을 줄 때 주의점

  • 자식 뷰 사이에 여백을 정해줄 때 레이아웃에서는 layout_margin 속성을 사용한다. 리니어 레이아웃과 상대적 레이아웃에서는 별다른 고려 없이 사용해도 되지만, 컨스트레인트 레이아웃을 사용할 때는 반드시 해당 방향으로 제약이 존재해야 마진값이 적용된다는 규칙이 있다. 예를 들면 위쪽 방향으로 여백을 100dp 주고 싶다고 해보자. 위쪽 방향에 제약이 없는 상태에서 android:layout_marginTop = "100dp"를 하면 제대로 적용이 안된다. 그러므로 반드시 android:layout_constraintTop_toTopOf="parent"와 같은 위쪽 방향에 제약을 추가할 수 있는 속성을 추가해야 한다.

match_constraint 속성

  • 컨스트레인트 레이아웃을 이용해 앱의 레이아웃을 구성하다 보면 layout_width와 layout_height 속성에서 빈번하게 0dp값을 보게 된다. 이때 그 뷰의 너비나 높이가 0dp라는 것은 아니다. 0dp는 match_constraint를 값으로 주는 것과 같다. match_parent가 부모 레이아웃 크기에 뷰 크기를 맞추는 것이라면 match_constraint는 제약에 뷰 크기를 맞추는 것이다.
  • Button2의 layout_width를 0dp로 설정하여 match_constraint 값을 주었다. 따라서 Button2의 너비가 제약에 맞춰졌다. 다양한 곳에서 빈번하게 사용되니 꼭 기억해두자.

반응형 UI 만들기: Guideline

  • 가이드 라인은 실제 화면에는 보이지 않으며, 레이아웃을 구성할 때만 사용되는 도구이다. 어떤 기기의 해상도에서도 일정한 비율로 레이아웃을 구성하고 싶을 때 굉장히 유용하게 사용된다.

가이드라인 만들어보기

  • 새로운 layout 파일을 만들고 가이드라인 추가 아이콘을 클릭하여 vertical guideline을 클릭한다.
	<androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="20dp" />
  • 자동 생성된 가이드라인 ID는 @+id/guideline[숫자] 형식이다. [숫자]는 생성할 때마다 1씩 올라간다.
  • 수직 방향 가이드라인을 추가했으므로 orientation값은 vertical이다.
  • 부모 레이아웃의 시작점에서 20dp 떨어진 곳에 가이드라인을 위치시켰다. 이 속성을 포함하여 가이드라인은 3가지 종류의 제약을 줄 수 있다.
  1. app:layout_constraintGuide_begin = "xdp": 부모 레이아웃의 시작점을 기준으로 xdp만큼 떨어진 가이드라인이다.
  2. app:layout_constraintGuide_end = "xdp": 부모 레이아웃의 끝점을 기준으로 xdp만큼 떨어진 가이드라인이다.
  3. app:layout_constraintGuide_percent = "0.x": 수평 방향 가이드라인이면 위쪽 , 수직 방향 가이드라인이면 왼쪽을 기준으로 몇 퍼센트 지점에 위치하는지를 정한다. 예를 들어 0.3이면 전체 길이의 30% 지점에 가이드라인이 위치한다.
  • 이 가이드라인은 수직 방향 가이드라인이므로 이해하기 쉽게 ID를 vertical로 바꿔준다.
  • 반응형으로 만들려면 고정된 dp값이 아니라 백분률로 위치를 정해주어야 한다. 그러니 이 수직 방향 가이드 라인이 전체 너비 중 40%에 해당하는 곳에 위치하도록 코드를 수정한다.
  • 똑같은 방법으로 이번에는 수평 가이드라인을 추가해본다.
  • 수평 가이드라인의 ID를 horizontal로 바꾸고 부모 레이아웃의 높이의 30%에 해당하는 곳에 가이드라인을 추가한다. 이 가이드라인까지 추가하면 화면은 다음과 같이 수직, 수평 방향의 가이드라인 두 개로 구성되어 있다.
  • 두 개의 가이드라인을 이용하여 텍스트뷰 3개를 만들어본다.
<?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">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.4" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.3" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#a0cac7"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="@id/horizontal"
        android:gravity="center"
        android:text="TOP"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@id/vertical"
        app:layout_constraintStart_toStartOf="parent"
        android:gravity="center"
        android:background="#ff7e67"
        android:text="LEFT"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#006a71"
        app:layout_constraintStart_toEndOf="@id/vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        android:gravity="center"
        android:text="RIGHT"/>

</androidx.constraintlayout.widget.ConstraintLayout>

profile
안녕하세요.

0개의 댓글