Android용 XML 레이아웃 만들기

1. 시작하기 전에

이 Codelab에서는 기본적인 팁 계산기 앱의 레이아웃을 빌드합니다. Codelab을 마치면 앱 UI는 작동하지만 실제로 앱에서 아직 팁을 계산하지는 않습니다. 앱이 작동하고 좀 더 전문적으로 보이도록 하는 내용은 다음 Codelab에서 다룹니다.

기본 요건

  • Android 스튜디오의 템플릿에서 Android 앱을 만들고 실행할 수 있어야 합니다.

학습할 내용

  • Android에서 XML 레이아웃을 읽고 쓰는 방법
  • 간단한 양식의 레이아웃을 빌드하여 사용자 텍스트 입력과 선택을 포함하는 방법

빌드할 프로그램

  • 팁 계산기 Android 앱의 UI

필요한 항목

  • Android 스튜디오의 최신 안정화 버전이 설치된 컴퓨터
  • Android 개발자 문서에 액세스하기 위한 인터넷 연결

2. 프로젝트 시작

Google에서 팁 계산기를 확인하세요. https://www.google.com/search?q=tip+calculator

18da3c120daa0759.png

이 과정에서는 간단한 버전의 팁 계산기를 Android 앱으로 빌드합니다.

개발자들은 일단 부분적으로 작동하는 간단한 버전의 앱을 만든 후 완전히 작동하고 시각적으로도 세련되도록 앱을 발전시켜 나갈 때가 많습니다.

이 Codelab을 마치면 팁 계산기 앱은 다음과 같이 표시됩니다.

bcc5260318477c14.png

Android에서 제공하는 다음 UI 요소를 사용할 예정입니다.

  • EditText: 텍스트를 입력하고 수정합니다.
  • TextView: 서비스 질문, 팁 금액과 같은 텍스트를 표시합니다.
  • RadioButton: 각 팁 옵션의 선택 가능한 라디오 버튼입니다.
  • RadioGroup: 라디오 버튼 옵션을 그룹화합니다.
  • Switch: 팁을 반올림할지 여부를 선택하는 켜기/끄기 전환 버튼입니다.

Empty Activity 프로젝트 만들기

  1. 먼저 Android 스튜디오에서 Empty Activity 템플릿을 사용하여 새 Kotlin 프로젝트를 만듭니다.
  2. 최소 API 수준 19(KitKat)로 'Tip Time' 앱을 호출합니다. 패키지 이름은 com.example.tiptime입니다.

4f7619e9faff20e9.png

  1. Finish를 클릭하여 앱을 만듭니다.

3. XML 읽기 및 이해하기

이미 익숙한 Layout Editor를 사용하는 대신 UI를 설명하는 XML을 수정하여 애플리케이션의 레이아웃을 빌드합니다. XML을 사용하여 UI 레이아웃을 파악하고 수정하는 방법을 아는 것은 Android 개발자에게 중요합니다.

이 앱의 UI 레이아웃을 정의하는 XML 파일을 살펴보고 수정합니다. XML은 확장성 마크업 언어(eXtensible Markup Language)를 의미하며 텍스트 기반 문서를 사용하여 데이터를 설명하는 방법입니다. XML은 확장 가능하고 매우 유연하므로 Android 앱의 UI 레이아웃 정의를 비롯하여 다양한 용도로 사용됩니다. 앱의 문자열과 같은 다른 리소스도 strings.xml이라는 XML 파일에서 정의된다는 점을 이전 Codelab에서 살펴본 바 있습니다.

Android 앱의 UI는 구성요소(위젯)의 포함 계층 구조와 이러한 구성요소의 화면 레이아웃으로 빌드됩니다. 이러한 레이아웃은 UI 구성요소 자체입니다.

화면에서 UI 요소의 뷰 계층 구조를 설명합니다. 예를 들어 ConstraintLayout(상위 요소)에는 ButtonsTextViews, ImageViews, 기타 뷰(하위 요소)가 포함될 수 있습니다. ConstraintLayoutViewGroup의 서브클래스입니다. 이를 통해 유연한 방식으로 하위 뷰를 배치하거나 크기를 조절할 수 있습니다.

74c7c563d18fffd4.png

Android 앱의 포함 계층 구조

32df120272b2331d.png

각 UI 요소는 XML 파일의 XML 요소로 표현됩니다. 각 요소는 태그로 시작하고 끝나며 각 태그는 <로 시작하고 >로 끝납니다. Layout Editor(디자인)를 사용하여 UI 요소에서 속성을 설정할 수 있는 것처럼 XML 요소에도 속성이 있을 수 있습니다. 간단히 말해서 위 UI 요소의 XML은 다음과 같을 수 있습니다.

<ConstraintLayout>
    <TextView
        text="Hello World!">
    </TextView>
</ConstraintLayout>

8dea708333aebabe.png

실제 예를 살펴보겠습니다.

  1. activity_main.xml(res > layout > activity_main.xml)을 엽니다.
  2. 이 템플릿에서 만든 이전 프로젝트에서 확인한 것처럼 앱이 ConstraintLayout 내에 'Hello World!'가 포함된 TextView를 표시합니다.

4fbdb64c02d62e73.png

  1. Layout Editor의 오른쪽 상단에서 Code, Split, Design 뷰 옵션을 찾습니다.
  2. Code 뷰를 선택합니다.

6203bec920791bcc.png

activity_main.xml의 XML은 다음과 같습니다.

<?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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

단순화된 예보다 훨씬 많은 일이 발생하지만 Android 스튜디오는 Kotlin 코드에서 한 것처럼 XML을 더 쉽게 읽을 수 있도록 몇 가지 작업을 합니다.

  1. 들여쓰기를 확인합니다. Android 스튜디오는 이 작업을 자동으로 실행하여 요소의 계층 구조를 표시합니다. TextViewConstraintLayout에 포함되어 있으므로 들여쓰기 됩니다. ConstraintLayout은 상위 요소이고 TextView는 하위 요소입니다. 각 요소의 속성은 들여쓰기 되어 요소의 일부임을 나타냅니다.
  2. 색 구분을 확인합니다. 어떤 항목은 파란색, 어떤 항목은 녹색 등입니다. 파일의 비슷한 부분은 더 잘 일치시킬 수 있도록 같은 색상으로 그려집니다. 특히 Android 스튜디오는 요소 태그의 시작과 끝을 같은 색상으로 그립니다. 참고: Codelab에서 사용된 색상은 Android 스튜디오에 표시되는 색상과 일치하지 않을 수 있습니다.

XML 태그, 요소, 속성

다음은 단순화된 버전의 TextView 요소로, 중요한 부분을 확인할 수 있습니다.

<TextView
    android:text="Hello World!"
/>

<TextView가 있는 줄은 태그의 시작 부분이고 />가 있는 줄은 태그의 끝부분입니다. android:text="Hello World!"가 있는 줄은 태그의 속성으로, TextView에서 표시할 텍스트를 나타냅니다. 이 세 줄이 빈 요소 태그라고 하는 흔히 사용되는 약식 표현입니다. 다음과 같이 별도의 시작 태그종료 태그로 작성해도 같은 의미를 나타냅니다.

<TextView
    android:text="Hello World!"
></TextView>

빈 요소 태그를 사용하여 가능한 한 적은 줄에 코드를 작성하고 태그의 끝부분을 그 앞줄과 결합하는 것도 일반적입니다. 따라서 두 줄(또는 속성이 없는 경우 한 줄)에 빈 요소 태그가 표시될 수도 있습니다.

<!-- with attributes, two lines -->
<TextView
    android:text="Hello World!" />

ConstraintLayout 요소는 별도의 시작 태그와 종료 태그로 작성됩니다. 내부에 다른 요소를 보유할 수 있어야 하기 때문입니다. 다음은 내부에 TextView 요소가 있는 단순화된 버전의 ConstraintLayout 요소입니다.

<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:text="Hello World!" />
</androidx.constraintlayout.widget.ConstraintLayout>

다른 ViewConstraintLayout의 하위 요소로 추가하려면(예: TextView 아래에 Button) 다음과 같이 TextView 태그 /> 끝의 뒤, ConstraintLayout 종료 태그 앞에 옵니다.

<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:text="Hello World!" />
    <Button
        android:text="Calculate" />
</androidx.constraintlayout.widget.ConstraintLayout>

레이아웃의 XML에 관한 추가 정보

  1. ConstraintLayout 태그를 보면 TextView와 같이 ConstraintLayout만이 아닌 androidx.constraintlayout.widget.ConstraintLayout이라고 표시됩니다. 이는 ConstraintLayout이 핵심 Android 플랫폼 외에도 추가 기능을 제공하는 코드 라이브러리가 포함된 Android Jetpack의 일부이기 때문입니다. Jetpack에는 앱을 더 쉽게 빌드하는 데 활용할 수 있는 유용한 기능이 있습니다. 이 UI 구성요소는 'androidx'로 시작하므로 Jetpack의 일부인 것을 알 수 있습니다.
  2. xmlns:로 시작하여 android, app, tools로 이어지는 줄이 있을 것입니다.
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"

xmlns는 XML 네임스페이스를 나타내고 각 줄은 스키마나 이러한 단어와 관련된 속성의 어휘를 정의합니다. 예를 들어 android: 네임스페이스는 Android 시스템에서 정의한 속성을 표시합니다. 레이아웃 XML의 속성은 모두 이러한 네임스페이스 중 하나로 시작합니다.

  1. XML 요소 사이의 공백은 컴퓨터에는 의미를 변경하지 않으면서 사용자가 XML을 더 쉽게 읽는 데 도움이 될 수 있습니다.

Android 스튜디오는 가독성을 높이기 위해 공백과 들여쓰기를 자동으로 추가합니다. Android 스튜디오를 통해 XML이 코딩 스타일 규칙을 준수하는지 확인하는 방법을 나중에 알아봅니다.

  1. Kotlin 코드에서와 마찬가지로 XML에 주석을 추가할 수 있습니다. <!--로 시작하고 -->로 끝납니다.
<!-- this is a comment in XML -->

<!-- this is a
multi-line
Comment.
And another
Multi-line comment -->
  1. 파일의 첫 번째 줄을 확인합니다.
<?xml version="1.0" encoding="utf-8"?>

파일이 XML 파일이지만 모든 XML 파일에 이 내용이 포함되는 것은 아님을 나타냅니다.

4. XML로 레이아웃 빌드

  1. 계속 activity_main.xml에서 Split 화면 뷰로 전환하여 Design Editor 옆의 XML을 확인합니다. Design Editor를 사용하여 UI 레이아웃을 미리 볼 수 있습니다.

a03bcf5beacb4b45.png

  1. 사용하는 뷰는 개인 선호도에 따라 다르지만 이 Codelab에서는 Split 뷰를 사용하여 개발자가 수정하는 XML과 이러한 수정으로 인한 Design Editor의 변경사항을 모두 확인할 수 있습니다.
  2. 다른 줄, 즉 ConstraintLayout 아래 줄 다음에 TextView 아래 줄을 클릭해보면 상응하는 뷰가 Design Editor에서 선택됩니다. 반대의 경우도 마찬가지입니다. 예를 들어 Design Editor에서 TextView를 클릭하면 상응하는 XML이 강조표시됩니다.

1abc54a646c39f66.png

TextView 삭제

  1. 지금은 TextView가 필요하지 않으므로 삭제합니다. <TextView부터 닫는 />까지 모든 항목을 삭제해야 합니다.
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

파일에는 ConstraintLayout만 남습니다.

<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>
  1. 16dp 패딩을 ConstraintLayout에 추가하여 UI가 화면 가장자리에서 비좁아지지 않도록 합니다.

패딩은 여백과 비슷하지만 외부에 공간을 추가하는 대신 ConstraintLayout 내부에 공간을 추가합니다.

<androidx.constraintlayout.widget.ConstraintLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

서비스 비용 텍스트 입력란 추가

이 단계에서는 사용자가 서비스 비용을 앱에 입력할 수 있는 UI 요소를 추가합니다. 사용자가 앱에서 텍스트를 입력하거나 수정할 수 있는 EditText 요소를 사용합니다.

7746dedb0d79923f.png

  1. EditText 문서를 살펴보고 샘플 XML을 확인합니다.
  2. ConstraintLayout의 여는 태그와 닫는 태그 사이에 빈 공간을 찾습니다.
  3. 문서에서 XML을 복사하여 Android 스튜디오의 레이아웃에 있는 빈 공간에 붙여넣습니다.

레이아웃 파일은 다음과 같이 표시됩니다.

<?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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/plain_text_input"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:inputType="text"/>

</androidx.constraintlayout.widget.ConstraintLayout>

아직 이 내용을 모두 이해하지 못할 수 있지만 다음 단계에서 설명합니다.

  1. EditText에는 빨간색으로 밑줄이 그어져 있습니다.
  2. 그 위로 포인터를 가져가면 '뷰가 제한되지 않음' 오류가 표시됩니다. 이전 Codelab에서 알아본 내용입니다. ConstraintLayout의 하위 요소에는 레이아웃이 정렬 방법을 알 수 있도록 제약 조건이 필요합니다.

40c17058bd6786f.png

  1. 이러한 제약 조건을 EditText에 추가하여 상위 요소의 왼쪽 상단에 고정합니다.
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

왼쪽에서 오른쪽(LTR)으로 작성되는 영어나 다른 언어로 작성하는 경우 시작 가장자리는 왼쪽입니다. 그러나 아랍어와 같은 일부 언어는 오른쪽에서 왼쪽(RTL)으로 작성되므로 시작 가장자리가 오른쪽입니다. 이러한 이유로 제약 조건은 'start'를 사용하여 LTR 언어나 RTL 언어에서 모두 작동할 수 있도록 합니다. 마찬가지로 제약 조건은 오른쪽 대신 'end'를 사용합니다.

새 제약 조건이 추가되면 EditText 요소는 다음과 같이 표시됩니다.

<EditText
    android:id="@+id/plain_text_input"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:inputType="text"/>

EditText 속성 검토

붙여넣은 모든 EditText 속성을 다시 확인하여 앱에서 효과적으로 사용되도록 합니다.

  1. @+id/plain_text_input으로 설정된 id 속성을 찾습니다.
  2. id 속성을 더 적절한 이름인 @+id/cost_of_service로 변경합니다.
  1. layout_height 속성을 확인합니다. wrap_content로 설정되어 있고 이는 높이가 내부 콘텐츠의 높이와 같다는 의미입니다. 텍스트 한 줄만 있을 것이므로 괜찮습니다.
  2. layout_width 속성을 확인합니다. match_parent로 설정되어 있지만 ConstraintLayout의 하위 요소에서는 match_parent를 설정할 수 없습니다. 또한 텍스트 입력란이 너무 넓을 필요도 없습니다. 고정 너비 160dp로 설정합니다. 이 정도 너비면 사용자가 서비스 비용을 입력하기에 충분합니다.

1f82a5e86ae94fd2.png

  1. 새로운 기능인 inputType 속성을 확인하세요. 속성값은 "text"입니다. 즉, 사용자가 화면의 입력란에 모든 텍스트 문자(알파벳 문자, 기호 등)를 입력할 수 있습니다.
android:inputType="text"

그러나 개발자는 EditText에 숫자만 입력하길 바랍니다. 입력란이 금전적 가치를 나타내기 때문입니다.

  1. text 단어는 삭제하고 따옴표는 유지합니다.
  2. 그 자리에 number를 입력합니다. 'n'을 입력하면 Android 스튜디오에서 'n'이 포함된 가능한 완성 목록을 표시합니다.

99b04cbd21e74693.gif

  1. numberDecimal을 선택하여 소수점이 있는 숫자로 제한합니다.
android:inputType="numberDecimal"

다른 입력 유형 옵션을 확인하려면 개발자 문서에서 입력 방법 유형 지정을 참고하세요.

변경할 사항이 하나 더 있습니다. 사용자가 이 입력란에 입력해야 하는 내용에 관한 힌트를 표시하면 유용하기 때문입니다.

  1. 사용자가 입력란에 입력해야 하는 내용을 설명하는 hint 속성을 EditText에 추가합니다.
android:hint="Cost of Service"

Design Editor 업데이트도 표시됩니다.

824454d2a316efb1.png

  1. 에뮬레이터에서 앱을 실행합니다. 다음과 같이 나타납니다.

c9d413de53b0853d.png

좋습니다. 아직 별 내용은 없지만 시작이 좋고 일부 XML을 수정했습니다. 레이아웃의 XML은 다음과 같이 표시됩니다.

<?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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:hint="Cost of Service"
        android:inputType="numberDecimal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

서비스 질문 추가

이 단계에서는 "서비스가 어땠나요?"라는 질문 프롬프트에 TextView를 추가합니다. 복사하여 붙여넣기 없이 입력해보세요. Android 스튜디오의 제안사항이 도움이 될 수 있습니다.

  1. EditText 태그를 닫은(/>) 다음 새 줄을 추가하고 <TextView를 입력합니다.
  2. 제안사항에서 TextView를 선택하면 Android 스튜디오에서 자동으로 TextViewlayout_widthlayout_height 속성을 추가합니다.
  3. 두 속성에서 모두 wrap_content를 선택합니다. TextView가 내부의 텍스트 콘텐츠만큼만 크면 되기 때문입니다. fee18cc43df85294.png
  4. "How was the service?"text 속성을 추가합니다.
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="How was the service?"
  1. />를 사용하여 태그를 닫습니다.
  2. Design Editor에서 TextViewEditText와 겹치는 것을 확인할 수 있습니다.

ac09d5cae6ae2455.png

바르게 보이지 않으므로 다음에는 TextView에 제약 조건을 추가합니다. 어떤 제약 조건이 필요한지 생각해보세요. TextView를 가로 및 세로로 어디에 배치해야 하나요? 도움이 필요하면 앱 스크린샷을 참고하세요.

bcc5260318477c14.png

세로 방향으로는 TextView를 서비스 비용 텍스트 입력란 아래에 두려고 합니다. 가로 뱡향으로는 TextView를 상위 요소의 시작 가장자리에 맞게 정렬하려고 합니다.

  1. 가로 제약 조건을 TextView에 추가하여 시작 가장자리를 상위 요소의 시작 가장자리로 제한합니다.
app:layout_constraintStart_toStartOf="parent"
  1. 세로 제약 조건을 TextView에 추가하여 TextView의 상단 가장자리를 서비스 비용 View의 하단 가장자리로 제한합니다.
app:layout_constraintTop_toBottomOf="@id/cost_of_service"

ID가 이미 정의되어 있으므로 @id/cost_of_service에는 더하기 기호가 없습니다.

3822136f7ed815f2.png

좀 부족해 보이긴 하지만 현재로서는 괜찮습니다. 필요한 모든 부분이 화면에 표시되고 기능이 작동하는지만 확인하면 됩니다. 다음 Codelab에서 표시되는 방식을 수정합니다.

  1. TextView에서 리소스 ID를 추가합니다. 나중에 뷰를 더 추가하여 서로에게 맞게 제한할 때 이 뷰를 참조해야 합니다.
android:id="@+id/service_question"

이 시점에서 XML은 다음과 같이 표시됩니다.

<?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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:hint="Cost of Service"
        android:layout_height="wrap_content"
        android:layout_width="160dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:inputType="numberDecimal"/>

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="How was the service?"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>

</androidx.constraintlayout.widget.ConstraintLayout>

5. 팁 옵션 추가

이제 사용자가 선택할 수 있는 다양한 팁 옵션에 사용할 라디오 버튼을 추가합니다.

옵션은 세 가지가 있어야 합니다.

  • 훌륭함(20%)
  • 좋음(18%)
  • 괜찮음(15%)

방법을 잘 모르겠다면 Google 검색을 사용하면 됩니다. 문제가 발생할 때 개발자가 사용하기 좋은 도구입니다.

  1. Google에서 radio button android를 검색합니다. 라디오 버튼 사용 방법에 관한 Android 개발자 사이트의 가이드가 상위 검색결과로 나옵니다. 좋습니다.

f5f1c6778ae7a5d.png

  1. 라디오 버튼 가이드를 훑어봅니다.

설명을 읽을 때 레이아웃에서 필요한 각 라디오 버튼에 RadioButton UI 요소를 사용할 수 있는지 확인할 수 있습니다. 또한 RadioGroup 내에서도 라디오 버튼을 그룹화해야 합니다. 한 번에 옵션 한 개만 선택할 수 있기 때문입니다.

요구사항에 적합해 보이는 XML이 몇 가지 있습니다. 잘 살펴보며 RadioGroup이 어떻게 상위 뷰이고 RadioButtons는 그 안에 있는 하위 뷰인지 확인합니다.

  1. Android 스튜디오의 레이아웃으로 돌아가서 RadioGroupRadioButton을 앱에 추가합니다.
  2. TextView 요소 뒤, 그러나 여전히 ConstraintLayout 내에서 <RadioGroup을 입력하기 시작합니다. Android 스튜디오는 XML을 완성할 수 있도록 유용한 제안사항을 제공합니다. aee75ba409dc51aa.png
  3. RadioGrouplayout_widthlayout_heightwrap_content로 설정합니다.
  4. @+id/tip_options로 설정된 리소스 ID를 추가합니다.
  5. >로 시작 태그를 닫습니다.
  6. Android 스튜디오에서 </RadioGroup>을 추가합니다. ConstraintLayout과 마찬가지로 RadioGroup 요소에는 내부에 다른 요소가 있으므로 자체 줄로 이동하는 것이 좋습니다. 콘텐츠를 래핑하도록 레이아웃 너비 및 레이아웃 높이 설정
  7. RadioGroup을 서비스 질문 아래(세로 방향)와 상위 요소의 시작 부분(가로 방향)으로 제한합니다.
  8. android:orientation 속성을 vertical로 설정합니다. 연속된 RadioButtons를 원한다면 방향을 horizontal로 설정합니다.

RadioGroup의 XML은 다음과 같습니다.

<RadioGroup
    android:id="@+id/tip_options"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/service_question">

</RadioGroup>

RadioButtons 추가

  1. RadioGroup의 마지막 속성 뒤, 그러나 </RadioGroup> 종료 태그 앞에 RadioButton을 추가합니다.
<RadioGroup
    android:id="@+id/tip_options"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/service_question">

    <!-- add RadioButtons here -->

</RadioGroup>
  1. layout_widthlayout_heightwrap_content로 설정합니다.
  2. @+id/option_twenty_percent의 리소스 ID를 RadioButton에 할당합니다.
  3. 텍스트를 Amazing (20%)로 설정합니다.
  4. />를 사용하여 태그를 닫습니다.
<RadioGroup
   android:id="@+id/tip_options"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:layout_constraintTop_toBottomOf="@id/service_question"
   app:layout_constraintStart_toStartOf="parent"
   android:orientation="vertical">

   <RadioButton
       android:id="@+id/option_twenty_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Amazing (20%)" />

</RadioGroup>

53cb416b368e9612.png

이제 RadioButton 하나를 추가했으므로 XML을 수정하여 Good (18%)Okay (15%) 옵션에 라디오 버튼을 두 개 더 추가할 수 있나요?

RadioGroupRadioButtons의 XML은 다음과 같습니다.

<RadioGroup
   android:id="@+id/tip_options"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:layout_constraintTop_toBottomOf="@id/service_question"
   app:layout_constraintStart_toStartOf="parent"
   android:orientation="vertical">

   <RadioButton
       android:id="@+id/option_twenty_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Amazing (20%)" />

   <RadioButton
       android:id="@+id/option_eighteen_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Good (18%)" />

   <RadioButton
       android:id="@+id/option_fifteen_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Okay (15%)" />

</RadioGroup>

bab25b6a35d4ce52.png

기본 선택 추가

현재 선택된 팁 옵션은 없습니다. 기본적으로 라디오 버튼 옵션 중 하나를 선택하는 것이 좋습니다.

RadioGroup에는 처음에 선택해야 하는 버튼을 지정할 수 있는 속성이 있습니다. checkedButton이라고 하며 선택하길 바라는 라디오 버튼의 리소스 ID로 설정합니다.

  1. RadioGroup에서 android:checkedButton 속성을 @id/option_twenty_percent로 설정합니다.
<RadioGroup
   android:id="@+id/tip_options"
   android:checkedButton="@id/option_twenty_percent"
   ...

Design Editor에서 레이아웃이 업데이트되었습니다. 20% 팁 옵션이 기본적으로 선택되어 있습니다. 훌륭합니다. 이제 점점 팁 계산기 같아 보입니다.

c412e7f16590cd33.png

지금까지의 XML은 다음과 같습니다.

<?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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:hint="Cost of Service"
        android:layout_height="wrap_content"
        android:layout_width="160dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:inputType="numberDecimal"/>

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="How was the service?"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"
        app:layout_constraintStart_toStartOf="parent" />

    <RadioGroup
        android:id="@+id/tip_options"
        android:checkedButton="@id/option_twenty_percent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/service_question"
        app:layout_constraintStart_toStartOf="parent"
        android:orientation="vertical">

        <RadioButton
            android:id="@+id/option_twenty_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Amazing (20%)" />

        <RadioButton
            android:id="@+id/option_eighteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Good (18%)" />

        <RadioButton
            android:id="@+id/option_fifteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Okay (15%)" />
    </RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

6. 나머지 레이아웃 완료

이제 레이아웃의 마지막 부분입니다. Switch, Button, TextView를 추가하여 팁 금액을 표시합니다.

bcc5260318477c14.png

팁을 반올림하기 위한 Switch 추가

이제 Switch 위젯을 사용하여 사용자가 팁 반올림 여부에 예나 아니요를 선택할 수 있도록 합니다.

Switch가 상위 요소의 너비와 같기를 바라므로 너비를 match_parent로 설정해야 한다고 생각할 수 있습니다. 앞서 언급했듯이 ConstraintLayout의 UI 요소에는 match_parent를 설정할 수 없습니다. 대신 뷰의 시작 및 끝 가장자리를 제한하고 너비를 0dp로 설정해야 합니다. 너비를 0dp로 설정하면 시스템에서 너비를 계산하지 않고 뷰에 적용된 제약 조건을 일치시키려고만 합니다.

  1. RadioGroup의 XML 뒤에 Switch 요소를 추가합니다.
  2. 위에서 설명한 것처럼 layout_width0dp로 설정합니다.
  3. layout_heightwrap_content로 설정합니다. 이렇게 하면 Switch 뷰의 높이가 내부 콘텐츠의 높이와 같아집니다.
  4. id 속성을 @+id/round_up_switch로 설정합니다.
  5. text 속성을 Round up tip?으로 설정합니다. 이는 Switch의 라벨로 사용됩니다.
  6. Switch의 시작 가장자리를 tip_options의 시작 가장자리로 제한하고 끝 가장자리를 상위 요소의 끝 가장자리로 제한합니다.
  7. Switch 상단을 tip_options 하단으로 제한합니다.
  8. />를 사용하여 태그를 닫습니다.

스위치가 기본적으로 사용 설정되어 있고 가능한 값이 true(사용 설정)나 false(사용 중지)인 속성 android:checked가 있으면 좋습니다.

  1. android:checked 속성을 true로 설정합니다.

종합해보면 Switch 요소의 XML은 다음과 같습니다.

<Switch
    android:id="@+id/round_up_switch"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:checked="true"
    android:text="Round up tip?"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="@id/tip_options"
    app:layout_constraintTop_toBottomOf="@id/tip_options" />

d374fab984650296.png

Calculate 버튼 추가

이제 사용자가 팁 계산을 요청할 수 있도록 Button을 추가합니다. 버튼의 너비가 상위 요소와 같기를 바라므로 가로 제약 조건과 너비가 Switch의 경우와 동일합니다.

  1. Switch 뒤에 Button을 추가합니다.
  2. Switch와 마찬가지로 너비를 0dp로 설정합니다.
  3. 높이를 wrap_content로 설정합니다.
  4. "Calculate" 텍스트와 함께 리소스 ID @+id/calculate_button을 제공합니다.
  5. Button의 상단 가장자리를 Round up tip? Switch의 하단 가장자리로 제한합니다.
  6. 시작 가장자리를 상위 요소의 시작 가장자리로 제한하고 끝 가장자리를 상위 요소의 끝 가장자리로 제한합니다.
  7. />를 사용하여 태그를 닫습니다.

Calculate Button의 XML은 다음과 같습니다.

<Button
   android:id="@+id/calculate_button"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="Calculate"
   app:layout_constraintTop_toBottomOf="@id/round_up_switch"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent" />

5338cf87c61d15c9.png

팁 결과 추가

레이아웃이 거의 완성되었습니다. 이 단계에서는 팁 결과에 TextView를 추가하여 Calculate 버튼 아래에 배치하고 다른 UI 요소와 같이 시작 부분이 아닌 끝부분에 맞춥니다.

  1. 이름이 tip_result인 리소스 ID와 텍스트 Tip Amount가 있는 TextView를 추가합니다.
  2. TextView의 끝 가장자리를 상위 요소의 끝 가장자리로 제한합니다.
  3. 상단 가장자리를 Calculate 버튼의 하단 가장자리로 제한합니다.
<TextView
    android:id="@+id/tip_result"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toBottomOf="@id/calculate_button"
    android:text="Tip Amount" />

9644bcdabbd8d7d1.png

  1. 앱을 실행합니다. 다음과 같은 스크린샷이 표시됩니다.

e4ed552fa9fbe4ce.png

훌륭합니다. 특히 처음 XML을 사용하는 개발자라면 더욱 그렇습니다.

앱의 모양이 스크린샷과 정확하게 일치하지 않을 수도 있습니다. 이후 버전의 Android 스튜디오에서 템플릿이 변경되었을 수 있기 때문입니다. Calculate 버튼으로 실행되는 작업은 아직 아무것도 없지만 비용을 입력하고 팁 비율을 선택한 후 옵션을 전환하여 팁 반올림 여부를 선택할 수 있습니다. 다음 Codelab에서 Calculate 버튼을 작동하게 만들므로 꼭 확인하세요.

7. 적절한 코딩 사례 채택

문자열 추출

하드 코딩 문자열에 관한 경고를 확인했을 수 있습니다. 리소스 파일로 문자열을 추출하면 앱을 다른 언어로 번역하고 문자열을 재사용하기가 더 쉬워진다고 이전 Codelab에서 알아봤습니다. activity_main.xml을 살펴보고 모든 문자열 리소스를 추출합니다.

  1. 문자열을 클릭합니다. 표시되는 노란색 전구 아이콘 위로 마우스를 가져간 다음 옆에 있는 삼각형을 클릭합니다. Extract String Resource를 선택합니다. 문자열 리소스의 기본 이름도 괜찮습니다. 원한다면 팁 옵션에 amazing_service, good_service, ok_service를 사용하여 이름을 좀 더 구체적으로 지정할 수 있습니다.

이제 방금 추가한 문자열 리소스를 확인합니다.

  1. Project 창이 표시되지 않으면 창 왼쪽에 있는 Project 탭을 클릭합니다.
  2. app > res > values > strings.xml을 열어 UI 문자열 리소스를 모두 확인합니다.
<resources>
    <string name="app_name">Tip Time</string>
    <string name="cost_of_service">Cost of Service</string>
    <string name="how_was_the_service">How was the service?</string>
    <string name="amazing_service">Amazing (20%)</string>
    <string name="good_service">Good (18%)</string>
    <string name="ok_service">Okay (15%)</string>
    <string name="round_up_tip">Round up tip?</string>
    <string name="calculate">Calculate</string>
    <string name="tip_amount">Tip Amount</string>
</resources>

XML의 형식 다시 지정

Android 스튜디오는 코드를 깔끔하게 정리하고 권장 코딩 규칙을 따르는지 확인하는 다양한 도구를 제공합니다.

  1. activity_main.xml에서 Edit > Select All을 선택합니다.
  2. Code > Reformat Code를 선택합니다.

이렇게 하면 들여쓰기가 일관되고 UI 요소의 일부 XML이 재정렬되어 항목을 그룹화할 수 있습니다. 예를 들면 한 요소의 모든 android: 속성을 함께 배치하는 것입니다.

8. 솔루션 코드

bcc5260318477c14.png

res/layout/activity_main.xml

<?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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:hint="@string/cost_of_service"
        android:inputType="numberDecimal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/how_was_the_service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service" />

    <RadioGroup
        android:id="@+id/tip_options"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@id/option_twenty_percent"
        android:orientation="vertical"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/service_question">

        <RadioButton
            android:id="@+id/option_twenty_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/amazing_service" />

        <RadioButton
            android:id="@+id/option_eighteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/good_service" />

        <RadioButton
            android:id="@+id/option_fifteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/ok_service" />
    </RadioGroup>

    <Switch
        android:id="@+id/round_up_switch"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="@string/round_up_tip"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/tip_options"
        app:layout_constraintTop_toBottomOf="@id/tip_options" />

    <Button
        android:id="@+id/calculate_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/calculate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/round_up_switch" />

    <TextView
        android:id="@+id/tip_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tip_amount"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/calculate_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

res/values/strings.xml

<resources>
   <string name="app_name">Tip Time</string>
   <string name="cost_of_service">Cost of Service</string>
   <string name="how_was_the_service">How was the service?</string>
   <string name="amazing_service">Amazing (20%)</string>
   <string name="good_service">Good (18%)</string>
   <string name="ok_service">Okay (15%)</string>
   <string name="round_up_tip">Round up tip?</string>
   <string name="calculate">Calculate</string>
   <string name="tip_amount">Tip Amount</string>
</resources>

9. 요약

  • XML(확장성 마크업 언어)은 텍스트를 구성하는 방법이며 태그, 요소, 속성으로 구성됩니다.
  • XML을 사용하여 Android 앱의 레이아웃을 정의합니다.
  • EditText를 사용하여 사용자가 텍스트를 입력하거나 수정하도록 합니다.
  • EditText에는 사용자에게 입력란에 예상되는 내용을 알려주는 힌트가 있을 수 있습니다.
  • android:inputType 속성을 지정하여 사용자가 EditText 입력란에 입력할 수 있는 텍스트 유형을 제한합니다.
  • RadioGroup으로 그룹화된 RadioButtons를 사용하여 배타적인 옵션 목록을 만듭니다.
  • RadioGroup은 세로 또는 가로로 될 수 있고 개발자는 처음에 선택해야 하는 RadioButton을 지정할 수 있습니다.
  • Switch를 사용하여 사용자가 두 옵션 간에 전환할 수 있도록 합니다.
  • 별도의 TextView를 사용하지 않고 Switch에 라벨을 추가할 수 있습니다.
  • ConstraintLayout의 각 하위 요소에는 세로 및 가로 제약 조건이 있어야 합니다.
  • 'start' 및 'end' 제약 조건을 사용하여 왼쪽에서 오른쪽(LTR) 및 오른쪽에서 왼쪽(RTL) 방향 언어를 모두 처리합니다.
  • 제약 조건 속성의 이름은 layout_constraint<Source>_to<Target>Of 형식을 따릅니다.
  • View의 너비를 포함되는 ConstraintLayout의 너비와 같게 하려면 시작과 끝을 상위 요소의 시작과 끝으로 제한하고 너비를 0dp로 설정합니다.

10. 자세히 알아보기

다루는 주제에 관한 추가 문서 링크를 아래에서 확인하세요. developer.android.com에서 모든 Android 개발 관련 문서를 확인할 수 있습니다. 문제가 발생하면 Google 검색을 사용하면 됩니다.

11. 연습하기

다음을 실행합니다.

  • 요리용 단위 변환기와 같은 다른 계산기 앱을 만들어 밀리미터를 액량 온스로 또는 그 반대로, 그램을 컵으로 또는 그 반대로 변환합니다. 어떤 입력란이 필요한가요?