더욱 세련된 사용자 환경 만들기

이전 Codelab에서 배운 것처럼 머티리얼은 사용자 인터페이스 디자인 권장사항을 지원하는 가이드라인, 구성요소, 도구로 Google에서 만든 디자인 시스템입니다. 이 Codelab에서는 아래에 나온 최종 스크린샷과 같이 더 세련된 사용자 환경을 제공하도록 팁 계산기 앱(이전 Codelab)을 업데이트합니다. 또한 몇 가지 추가적인 시나리오에서 앱을 테스트하여 사용자 환경이 최대한 원활하게 작동하는지 확인합니다.

5743ac5ee2493d7.png

기본 요건

  • TextView, ImageView, Button, EditText, RadioButton, RadioGroup, Switch와 같은 일반적인 UI 위젯에 익숙함
  • ConstraintLayout에 익숙하며 제약 조건을 설정하여 하위 뷰의 위치를 지정할 수 있음
  • XML 레이아웃 수정에 익숙함
  • 비트맵 이미지와 벡터 드로어블의 차이를 잘 알고 있음
  • 테마에서 테마 속성을 설정할 수 있음
  • 기기의 어두운 테마를 사용 설정할 수 있음
  • 이전에 앱의 build.gradle 파일에서 프로젝트 종속 항목을 수정한 경험이 있음

학습할 내용

  • 앱에서 머티리얼 디자인 구성요소를 사용하는 방법
  • Image Asset Studio에서 머티리얼 아이콘을 가져와서 앱에 사용하는 방법
  • 새 스타일을 만들고 적용하는 방법
  • 색상 이외의 다른 테마 속성을 설정하는 방법

빌드할 항목

  • UI 권장사항을 따르는 세련된 팁 계산기 앱

필요한 항목

  • Android 스튜디오가 설치된 컴퓨터
  • 이전 Codelab을 완료하여 얻은 Tip Time 앱 코드

이전 Codelab을 통해 Tip Time 앱을 빌드했습니다. 팁을 맞춤설정하는 옵션이 포함된 팁 계산기 앱입니다. 현재 앱 UI는 아래 스크린샷과 같습니다. 기능이 제대로 작동하지만 프로토타입처럼 보입니다. 필드가 시각적으로 제대로 정렬되지 않았습니다. 보다 일관성 있는 스타일과 간격은 물론 머티리얼 디자인 구성요소 사용 측면에서도 개선의 여지가 있습니다.

6685eaafba30960a.png

머티리얼 구성요소는 앱에서 머티리얼 스타일을 더 쉽게 구현할 수 있는 일반적인 UI 위젯입니다. 머티리얼 디자인 구성요소를 사용하고 맞춤설정하는 방법에 관한 정보를 문서에서 확인할 수 있습니다. 각 구성요소의 일반적인 머티리얼 디자인 가이드라인과 Android에서 사용할 수 있는 구성요소에 관한 Android 플랫폼용 안내도 있습니다. 원하는 플랫폼에 구성요소가 존재하지 않는 경우 라벨이 지정된 다이어그램을 통해 구성요소를 다시 만드는 데 도움이 되는 충분한 정보를 확인할 수 있습니다.

c4a4db857bb36c3f.png

머티리얼 구성요소를 사용하면 앱이 사용자 기기에 있는 다른 앱과 함께 더 일관된 방식으로 작동합니다. 이렇게 하면 한 앱에서 학습된 UI 패턴이 다음 앱에 이전될 수 있습니다. 따라서 사용자는 앱을 사용하는 방법을 훨씬 더 빠르게 배울 수 있습니다. 가능하면 항상 (비 머티리얼 위젯이 아닌) 머티리얼 구성요소를 사용하는 것이 좋습니다. 또한 머티리얼 구성요소는 유연성과 맞춤설정 가능성이 더 높습니다. 이에 관해서는 다음 작업에서 알아봅니다.

머티리얼 디자인 구성요소(MDC) 라이브러리를 프로젝트의 종속 항목으로 포함해야 합니다. Android 스튜디오 4.1 이상을 사용하는 경우 다음 줄이 이미 프로젝트에 기본적으로 포함되어 있습니다. 앱의 build.gradle 파일에서 이 라이브러리 최신 버전에 종속 항목이 포함되어 있는지 확인하세요. 자세한 내용은 머티리얼 사이트의 시작하기 페이지를 참고하세요.

app/build.gradle

dependencies {
    ...
    implementation 'com.google.android.material:material:<version>'
}

텍스트 필드

현재 팁 계산기 앱의 레이아웃 상단에는 서비스 비용을 위한 EditText 필드가 있습니다. 이 EditText 필드는 작동하지만 텍스트 필드의 모양과 동작 방식에 관한 최근 머티리얼 디자인 가이드라인을 따르지는 않습니다.

사용하려는 새 구성요소가 있다면 먼저 머티리얼 사이트를 통해 학습하세요. 텍스트 필드 가이드에 따르면 다음 두 가지 유형의 텍스트 필드가 있습니다.

채워진 텍스트 필드

bea54a560820fe84.png

윤곽선 텍스트 필드

c37af7d70aad8aa6.png

위와 같은 텍스트 필드를 만들려면 MDC 라이브러리의 TextInputEditText가 포함된 TextInputLayout을 사용합니다. 머티리얼 텍스트 필드를 다음과 같이 쉽게 맞춤설정할 수 있습니다.

  • 항상 표시되는 입력 텍스트 또는 라벨 표시하기
  • 텍스트 필드에 아이콘 표시하기
  • 도우미 또는 오류 메시지 표시하기

이 Codelab의 첫 번째 작업에서는 서비스 비용 EditText를 머티리얼 텍스트 필드(TextInputLayoutTextInputEditText로 구성됨)로 바꿉니다.

  1. Android 스튜디오에서 Tip Time 앱을 열고 activity_main.xml 레이아웃 파일로 이동합니다. 팁 계산기 레이아웃과 함께 ConstraintLayout이 포함되어 있습니다.
  2. 머티리얼 텍스트 필드용 XML의 예를 보려면 Android 텍스트 필드 지침으로 돌아가세요. 다음과 같은 스니펫이 표시됩니다.
<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/textField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>
  1. 이 예를 확인한 후에 EditText 필드 앞에 ConstraintLayout의 첫 번째 하위 요소로 머티리얼 텍스트 필드를 삽입합니다. 이후 단계에서 EditText 필드를 제거합니다.

Android 스튜디오에 입력하고 자동 완성을 사용하면 더 쉽습니다. 또는 문서 페이지에서 예제 XML을 복사하여 다음과 같이 레이아웃에 붙여넣을 수 있습니다. TextInputLayout에 하위 뷰 TextInputEditText가 있는 것을 확인합니다. 생략 기호(...)는 실제로 변경된 XML 줄에 집중할 수 있도록 스니펫을 축약하는 데 사용됩니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    ...>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textField"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/label">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

    </com.google.android.material.textfield.TextInputLayout>

    <EditText
        android:id="@+id/cost_of_service" ... />

    ...

TextInputLayout 요소에서 오류가 표시될 것입니다. 아직 상위 ConstraintLayout에서 이 뷰를 적절히 제한하지 않았기 때문입니다. 문자열 리소스도 인식되지 않습니다. 이 오류는 이후 단계에서 수정합니다.

1cf3f25cc047f291.png

  1. 텍스트 필드에 가로 및 세로 제약 조건을 추가하여 상위 ConstraintLayout 내에 적절하게 필드를 배치합니다. 아직 EditText를 삭제하지 않았으므로 EditText에서 제약 조건, 리소스 ID cost_of_service, 레이아웃 너비 160dp, 레이아웃 높이 wrap_content, 힌트 텍스트 @string/cost_of_service 등의 속성을 잘라 붙여넣어 TextInputLayout에 배치합니다.
...

<com.google.android.material.textfield.TextInputLayout
   android:id="@+id/cost_of_service"
   android:layout_width="160dp"
   android:layout_height="wrap_content"
   android:hint="@string/cost_of_service"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent">

   <com.google.android.material.textfield.TextInputEditText
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</com.google.android.material.textfield.TextInputLayout>

...

cost_of_service ID가 EditText의 리소스 ID와 동일하다는 오류 메시지가 표시될 수도 있지만 지금은 이 오류를 무시해도 됩니다. EditText는 몇 단계를 거쳐 삭제됩니다.

  1. 그런 다음 TextInputEditText 요소에 적절한 속성이 모두 있는지 확인합니다. EditText에서 입력 유형을 잘라 TextInputEditText.에 붙여넣습니다. TextInputEditText 요소 리소스 ID를 cost_of_service_edit_text.로 변경합니다.
<com.google.android.material.textfield.TextInputLayout ... >

   <com.google.android.material.textfield.TextInputEditText
       android:id="@+id/cost_of_service_edit_text"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:inputType="numberDecimal" />

</com.google.android.material.textfield.TextInputLayout>

match_parent의 너비와 wrap_content의 높이는 현재 그대로 좋습니다. match_parent의 너비를 설정하는 경우 TextInputEditText는 상위 TextInputLayout160dp와 너비가 같습니다.

  1. 이제 모든 관련 정보를 EditText에서 복사했으므로 레이아웃에서 EditText를 삭제합니다.
  2. 레이아웃의 Design 뷰에 이 미리보기가 표시됩니다. 이제 Cost of Service 필드가 머티리얼 텍스트 필드와 같이 표시됩니다.

148df54f0deda630.png

  1. MainActivity.kt 파일의 calculateTip() 메서드에 오류가 있으므로 아직 앱을 실행할 수 없습니다. 이전 Codelab에서 알아봤듯이 프로젝트에 뷰 결합을 사용 설정한 경우 Android는 리소스 ID 이름을 기반으로 결합 객체에 속성을 생성합니다. 서비스 비용을 검색한 필드가 XML 레이아웃에서 변경되었으므로 그에 따라 Kotlin 코드를 업데이트해야 합니다.

이제 TextInputEditText 요소에서 리소스 ID가 cost_of_service_edit_text인 사용자 입력을 가져옵니다. MainActivity에서 binding.costOfServiceEditText를 사용하여 저장된 텍스트 문자열에 액세스합니다. calculateTip() 메서드의 나머지 부분은 동일하게 유지할 수 있습니다.

private fun calculateTip() {
    // Get the decimal value from the cost of service text field
    val stringInTextField = binding.costOfServiceEditText.text.toString()
    val cost = stringInTextField.toDoubleOrNull()

    ...
}
  1. 수고하셨습니다. 이제 앱을 실행하고 여전히 작동하는지 테스트합니다. 입력할 때 'Cost of Service' 라벨이 입력 내용 위에 표시되는 것을 볼 수 있습니다. 팁은 여전히 예상대로 계산되어야 합니다.

b4a27e58f63417b7.png

스위치

머티리얼 디자인 가이드라인에는 스위치 관련 지침도 있습니다. 스위치는 설정을 사용 또는 사용 중지로 전환할 수 있는 위젯입니다.

  1. 머티리얼 스위치에 관한 Android 지침을 확인하세요. 스위치의 머티리얼 스타일을 제공하는 SwitchMaterial 위젯(MDC 라이브러리에 있음)에 관해 알아보겠습니다. 가이드를 계속 스크롤하면 몇 가지 XML 예를 볼 수 있습니다.
  2. SwitchMaterial을 사용하려면 레이아웃에서 SwitchMaterial을 명시적으로 지정하고 정규화된 경로 이름을 사용해야 합니다.

activity_main.xml 레이아웃에서 XML 태그를 Switch에서 com.google.android.material.switchmaterial.SwitchMaterial.로 변경합니다.

...

<com.google.android.material.switchmaterial.SwitchMaterial
    android:id="@+id/round_up_switch"
    android:layout_width="0dp"
    android:layout_height="wrap_content" ... />

...
  1. 앱을 실행하여 여전히 앱이 컴파일되는지 확인합니다. 앱에 눈에 띄는 변화는 없습니다. 그러나 Android 플랫폼의 Switch 대신 MDC 라이브러리의 SwitchMaterial을 사용할 때 이점은 라이브러리의 SwitchMaterial 구현이 업데이트되면(예: 머티리얼 디자인 가이드라인 변경) 직접 변경하지 않고도 무료로 위젯이 업데이트된다는 점입니다. 덕분에 미래에 대비한 앱을 마련할 수 있습니다.

이 시점에서 바로 사용 가능한 머티리얼 디자인 구성요소를 활용해 UI가 이점을 얻을 수 있는 방법과 머티리얼 가이드라인을 준수하는 앱에 더 가까워질 수 있는 방법과 관련하여 두 가지 예를 확인했습니다. Android에서 제공하는 다른 머티리얼 디자인 구성요소는 언제든지 이 사이트에서 확인할 수 있습니다.

아이콘은 의도한 기능을 시각적으로 전달하여 사용자가 사용자 인터페이스를 이해하는 데 도움을 주는 기호입니다. 사용자가 경험했을 것으로 기대되는 실제 세상의 사물에서 아이콘의 아이디어를 얻는 경우가 많습니다. 아이콘 디자인은 종종 필요한 최소한의 수준으로 세부 표현을 줄여서 사용자에게 인식되도록 만듭니다. 예를 들어 실제 세상의 연필은 쓰기에 사용되므로 그 아이콘은 일반적으로 항목 생성, 추가, 수정 등을 나타냅니다.

사진: 안젤리나 리트빈(Unsplash 제공)

간혹 아이콘이 지금은 사용되지 않는 실제 세상의 사물과 연관이 있습니다. 예를 들어 플로피 디스크 아이콘처럼 말입니다. 이 아이콘은 파일 또는 데이터베이스 레코드 저장을 나타내는 것으로 널리 인식됩니다. 하지만 플로피 디스크는 1970년대에 널리 사용되었지만 2000년 이후에는 더 이상 일반적으로 사용되지 않습니다. 하지만 아이콘이 지금도 계속 사용되는 것을 보면 강력한 시각 요소가 실물 형태의 수명을 뛰어넘을 수 있음을 알 수 있습니다.

사진: 빈센트 보타(Unsplash 제공)

앱의 아이콘 표현하기

앱의 아이콘의 경우 다양한 화면 밀도에 맞는 여러 버전의 비트맵 이미지를 제공하는 대신 벡터 드로어블을 사용하는 것이 좋습니다. 벡터 드로어블은 이미지를 구성하는 실제 픽셀을 저장하는 대신 이미지를 만드는 방법에 관한 지침을 저장하는 XML 파일로 표현됩니다. 벡터 드로어블은 시각적 품질 손실이나 파일 크기 증가 없이 확장하거나 축소할 수 있습니다.

제공된 아이콘

머티리얼 디자인은 대부분의 요구에 부합하는 다수의 아이콘을 일반적인 카테고리로 정리하여 제공합니다. 아이콘 목록을 확인하세요.

76e9b6c4ec0cbbe6.png

이러한 아이콘은 5개 테마(채워짐, 윤곽선 있음, 둥근 모서리, 투톤, 각진 모서리) 중 하나를 사용하여 그릴 수 있으며 색상을 적용할 수도 있습니다.

채워짐

윤곽선 있음

둥근 모서리

투톤

각진 모서리

아이콘 추가하기

이 작업에서는 벡터 드로어블 아이콘 세 개를 앱에 추가합니다.

  1. Cost of Service 텍스트 필드 옆의 아이콘
  2. 서비스 질문 옆의 아이콘
  3. 팁을 반올림할지 묻는 메시지 옆의 아이콘

아래는 최종 버전 앱의 스크린샷입니다. 아이콘을 추가한 후에는 이 아이콘의 위치를 허용할 수 있도록 레이아웃을 조정합니다. 아이콘을 추가하면 필드와 Calculate 버튼이 오른쪽으로 약간 옮겨지는 것을 볼 수 있습니다.

8c4225390dd1fb20.png

벡터 드로어블 애셋 추가하기

Android 스튜디오의 Asset Studio에서 직접 이 아이콘을 벡터 드로어블로 만들 수 있습니다.

  1. 애플리케이션 창의 왼쪽에 있는 Resource Manager 탭을 엽니다.
  2. + 아이콘을 클릭하고 Vector Asset을 선택합니다.

6a692157a2ada3f6.png

  1. Asset Type의 경우 Clip Art라는 라디오 버튼이 선택되어 있는지 확인합니다.

698ab1c8dc2d1714.png

  1. Clip Art: 옆에 있는 버튼을 클릭하여 다른 클립 아트 이미지를 선택합니다. 메시지가 표시되면 나타나는 창에 'call made'를 입력합니다. 이 화살표 아이콘을 Round up tip 옵션에 사용하겠습니다. 아이콘을 선택하고 OK를 클릭합니다.

50b0008ed6ab8d6d.png

  1. 아이콘 이름을 ic_round_up으로 바꿉니다. 아이콘 파일의 이름을 지정할 때는 접두어 ic_을 사용하는 것이 좋습니다. **Size**를 24dp x 24dp로, **Color**를 black 000000으로 그대로 둡니다.
  2. Next를 클릭합니다.
  3. 기본 디렉터리 위치를 그대로 사용하고 Finish를 클릭합니다.

9f522a73be34ecf6.png

  1. 다른 두 아이콘에 2~7단계를 반복합니다.
  • 서비스 질문 아이콘: 'room service' 아이콘을 검색하여 ic_service로 저장합니다.
  • Cost of Service 아이콘: 'store' 아이콘을 검색하여 ic_store로 저장합니다.
  1. 완료하면 Resource Manager는 아래 스크린샷과 같으며 벡터 드로어블 세 개(ic_round_up, ic_service, ic_store)가 res/drawable 폴더에 나열됩니다.

3c895747fbfa3793.png

이전 Android 버전 지원하기

앱에 벡터 드로어블을 추가했지만, Android 플랫폼의 벡터 드로어블 지원은 Android 5.0(API 수준 21) 전에는 추가되지 않았습니다.

여기서 프로젝트를 설정한 방식에 따라 Tip Time 앱의 최소 SDK 버전은 API 19입니다. 다시 말해서 Android 플랫폼 버전 19 이상을 실행하는 Android 기기에서 앱을 실행할 수 있습니다.

앱이 이러한 이전 버전의 Android에서 작동(이전 버전과의 호환성)하게 하려면 vectorDrawables 요소를 앱의 build.gradle 파일에 추가하세요. 그러면 API 21 미만의 플랫폼 버전에서 벡터 드로어블을 사용할 수 있습니다(프로젝트 빌드 시 PNG로 변환과 비교). 자세한 내용은 여기에서 확인하세요.

app/build.gradle

android {
  defaultConfig {
    ...
    vectorDrawables.useSupportLibrary = true
   }
   ...
}

프로젝트를 적절히 구성했으므로 이제 레이아웃에 아이콘을 추가할 수 있습니다.

아이콘을 삽입하고 요소 배치하기

ImageViews를 사용하여 앱에 아이콘을 표시합니다. 최종 UI는 다음과 같이 표시됩니다.

5d970eb04c642544.png

  1. activity_main.xml 레이아웃을 엽니다.
  2. 먼저 Cost of Service 텍스트 필드 옆에 상점 아이콘을 배치합니다. 새 ImageViewConstraintLayout의 첫 번째 하위 요소로(TextInputLayout 앞에) 삽입합니다.
<androidx.constraintlayout.widget.ConstraintLayout
   ...>

   <ImageView
       android:layout_width=""
       android:layout_height=""

   <com.google.android.material.textfield.TextInputLayout
       android:id="@+id/cost_of_service"
       ...
  1. ic_store 아이콘을 보유하도록 ImageView에서 적절한 속성을 설정합니다. ID를 icon_cost_of_service로 설정합니다. app:srcCompat 속성을 드로어블 리소스 @drawable/ic_store로 설정하면 이 XML 줄 옆에 아이콘의 미리보기가 표시됩니다. 이 이미지는 장식용으로만 사용되므로 android:importantForAccessibility="no"도 설정합니다.
<ImageView
    android:id="@+id/icon_cost_of_service"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:importantForAccessibility="no"
    app:srcCompat="@drawable/ic_store" />

뷰에 아직 제약 조건이 적용되지 않았으므로 ImageView에서 오류가 발생할 것으로 예상됩니다. 이 오류는 다음에 수정합니다.

  1. 두 단계에 거쳐 icon_cost_of_service를 배치합니다. 먼저 ImageView에 제약 조건을 추가(이 단계)한 다음 그 옆의 TextInputLayout에서 제약 조건을 업데이트합니다(5단계). 다음 다이어그램은 이 제약 조건의 설정 방법을 보여줍니다.

e23287bdeca07a1e.png

ImageView에서 시작 가장자리를 상위 뷰의 시작 가장자리(app:layout_constraintStart_toStartOf="parent")로 제한하려고 합니다.

아이콘이 옆에 있는 텍스트 필드와 비교하여 세로로 가운데에 표시되므로 이 ImageView의 상단(layout_constraintTop_toTopOf)을 텍스트 필드 상단으로 제한합니다. 이 ImageView의 하단(layout_constraintBottom_toBottomOf)은 텍스트 필드 하단으로 제한합니다. 텍스트 필드를 참조하려면 리소스 ID @id/cost_of_service를 사용합니다. 기본 동작은 위젯에 동일한 치수로 두 제약 조건(예: 상단 및 하단 제약 조건)이 적용되면 제약 조건이 동일하게 적용되는 것입니다. 결과적으로 아이콘은 Cost of Service 필드와 비교하여 세로로 가운데에 배치됩니다.

<ImageView
    android:id="@+id/icon_cost_of_service"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:importantForAccessibility="no"
    app:srcCompat="@drawable/ic_store"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/cost_of_service"
    app:layout_constraintBottom_toBottomOf="@id/cost_of_service" />

아이콘과 텍스트 필드가 Design 뷰에서 여전히 중첩됩니다. 이 문제는 다음 단계에서 해결합니다.

  1. 아이콘을 추가하기 전에는 텍스트 필드가 상위 요소 시작 부분에 위치했습니다. 이제 텍스트 필드를 오른쪽으로 옮겨야 합니다. icon_cost_of_service와 비교하여 cost_of_service 텍스트 필드의 제약 조건을 업데이트합니다.

40c0c8f04f53a87d.png

TextInputLayout의 시작 가장자리는 ImageView의 끝 가장자리(@id/icon_cost_of_service)로 제한되어야 합니다. 두 뷰 사이에 간격을 추가하려면 TextInputLayout에 시작 여백 16dp를 추가합니다.

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/cost_of_service"
    ...
    android:layout_marginStart="16dp"
    app:layout_constraintStart_toEndOf="@id/icon_cost_of_service">

    <com.google.android.material.textfield.TextInputEditText ... />

</com.google.android.material.textfield.TextInputLayout>

모두 변경된 후에는 아이콘이 텍스트 필드 옆에 올바르게 배치됩니다.

6ca04c3c964d5acc.png

  1. 그런 다음 'How was the service?' TextView 옆에 종 모양 아이콘을 삽입합니다. ConstraintLayout 안의 모든 위치에서 ImageView를 선언할 수 있지만 XML 레이아웃에서 TextInputLayout 뒤, service_question TextView 앞에 새 ImageView를 삽입하면 XML 레이아웃을 읽기가 더 쉽습니다.

ImageView의 경우 리소스 ID @+id/icon_service_question을 할당합니다. ImageView 및 서비스 질문 TextView에 적절한 제약 조건을 설정합니다.

4487340b399e8105.png

또한 서비스 질문과 그 위의 Cost of Service 텍스트 필드 사이에 세로 공간이 더 많아지도록 service_question TextView에 상단 여백 16dp를 추가합니다.

...

   <ImageView
        android:id="@+id/icon_service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:importantForAccessibility="no"
        app:srcCompat="@drawable/ic_service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/service_question"
        app:layout_constraintBottom_toBottomOf="@id/service_question" />

    <TextView
        android:id="@+id/service_question"
        ...
        android:layout_marginTop="16dp"
        app:layout_constraintStart_toStartOf="@id/cost_of_service"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>

...
  1. 이제 Design 뷰는 다음과 같습니다. Cost of Service 필드와 서비스 질문(그리고 각 아이콘)이 제대로 표시되지만 이제 라디오 버튼의 위치가 부자연스럽습니다. 위의 콘텐츠와 세로로 정렬되지 않습니다.

cdfd16c1851c88eb.png

  1. 서비스 질문 아래에서 오른쪽으로 옮겨 라디오 버튼의 위치를 개선합니다. 즉, RadioGroup 제약 조건을 업데이트하면 됩니다. RadioGroup의 시작 가장자리를 service_question TextView의 시작 가장자리로 제한합니다. RadioGroup의 다른 모든 속성은 동일하게 유지될 수 있습니다.

58a62fae3d676fe4.png

...

<RadioGroup
    android:id="@+id/tip_options"
    ...
    app:layout_constraintStart_toStartOf="@id/service_question">

...
  1. 그런 다음 레이아웃에서 'Round up tip?' 스위치 옆에 ic_round_up 아이콘을 추가합니다. 직접 해보고 도중에 문제가 발생하면 아래의 XML을 참고할 수 있습니다. 새 ImageView에 리소스 ID icon_round_up을 할당할 수 있습니다.
  2. 레이아웃 XML에서 RadioGroup 뒤, SwitchMaterial 위젯 앞에 새 ImageView를 삽입합니다.
  3. ImageView에 리소스 ID icon_round_up을 할당하고 srcCompat@drawable/ic_round_up 아이콘의 드로어블로 설정합니다. ImageView의 시작 부분을 상위 요소의 시작 부분으로 제한하고 SwitchMaterial을 기준으로 아이콘을 세로로 중앙에 배치합니다.
  4. SwitchMaterial을 아이콘 옆에 위치하도록 업데이트하고 시작 여백 16dp를 설정합니다. icon_round_upround_up_switch의 결과 XML은 다음과 같습니다.
...

   <ImageView
        android:id="@+id/icon_round_up"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:importantForAccessibility="no"
        app:srcCompat="@drawable/ic_round_up"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/round_up_switch"
        app:layout_constraintBottom_toBottomOf="@id/round_up_switch" />

    <com.google.android.material.switchmaterial.SwitchMaterial
        android:id="@+id/round_up_switch"
        ...
        android:layout_marginStart="16dp"
        app:layout_constraintStart_toEndOf="@id/icon_round_up" />

...
  1. Design 뷰는 다음과 같습니다. 세 아이콘이 모두 올바르게 배치되어 있습니다.

f4b0e8d61d91986b.png

  1. 최종 앱 스크린샷과 비교하면 Calculate 버튼도 옮겨져서 Cost of Service 필드, 서비스 질문, 라디오 버튼 옵션, Round up tip 질문과 세로로 정렬된다는 것을 알 수 있습니다. Calculate 버튼의 시작 부분을 round_up_switch의 시작 부분으로 제한하여 이런 결과를 얻을 수 있습니다. 또한 Calculate 버튼과 그 위의 스위치 사이에 세로 여백 8dp를 추가합니다.

60d5a43819c2367d.png

...

<Button
   android:id="@+id/calculate_button"
   ...
   android:layout_marginTop="8dp"
   app:layout_constraintStart_toStartOf="@id/round_up_switch" />

...
  1. 마지막으로 TextView에 상단 여백 8dp를 추가하여 tip_result를 배치합니다.

beb333a518b51323.png

...

<TextView
   android:id="@+id/tip_result"
   ...
   android:layout_marginTop="8dp" />

...
  1. 여러 단계를 진행했습니다. 단계별로 잘 따라와 주셨습니다. 레이아웃에서 요소를 올바르게 정렬하려면 세부 표현에 많은 주의를 기울여야 하지만, 그러면 최종 결과가 훨씬 더 좋아집니다. 앱을 실행하면 아래 스크린샷과 같습니다. 세로로 정렬하고 요소 사이의 간격을 늘리면 요소들이 복잡하지 않게 배치됩니다.

1f2ef2c0c9a9bdc7.png

다음 단계도 수행해야 합니다. 서비스 질문과 팁 금액의 글꼴 크기와 색상이 라디오 버튼 및 스위치의 텍스트와 다르게 표시될 수도 있습니다. 다음 작업에서 스타일 및 테마를 사용하여 일관성 있게 맞추겠습니다.

스타일은 단일 위젯 유형의 뷰 속성 값 모음입니다. 예를 들어 TextView 스타일은 글꼴 색상, 글꼴 크기, 배경색 등을 지정할 수 있습니다. 이러한 속성을 스타일로 추출하면 레이아웃의 스타일을 여러 뷰에 쉽게 적용하고 단일 위치에 유지할 수 있습니다.

이 작업에서는 먼저 텍스트 뷰, 라디오 버튼, 스위치 위젯용 스타일을 만듭니다.

스타일 만들기

  1. 아직 없는 경우 res > values 디렉터리에 styles.xml이라는 새 파일을 만듭니다. values 디렉터리를 마우스 오른쪽 버튼으로 클릭한 후 New > Values Resource File을 선택하여 파일을 만듭니다. 이름을 styles.xml로 지정합니다. 새 파일의 내용은 다음과 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
  1. 앱 전체에서 텍스트를 일관되게 표시하도록 새 TextView 스타일을 만듭니다. styles.xml에서 스타일을 한 번 정의한 후에는 레이아웃의 모든 TextViews에 적용할 수 있습니다. 스타일을 처음부터 정의할 수도 있지만 MDC 라이브러리의 기존 TextView 스타일에서 확장할 수 있습니다.

구성요소 스타일을 지정할 때는 일반적으로 사용하는 위젯 유형의 상위 스타일에서 확장해야 합니다. 두 가지 이유로 인해 이와 같이 해야 합니다. 첫째, 중요한 모든 기본값을 구성요소에 설정합니다. 둘째, 스타일이 계속해서 상위 스타일의 향후 변경사항을 상속합니다.

스타일 이름을 원하는 대로 지정할 수 있지만 권장되는 규칙이 있습니다. 상위 머티리얼 스타일에서 상속하는 경우 유사한 방식으로 MaterialComponents를 앱 이름(TipTime)으로 바꿔 스타일 이름을 지정합니다. 그러면 변경사항이 고유한 네임스페이스로 이동되어 향후 머티리얼 구성요소에 새로운 스타일이 도입될 때 충돌이 발생할 가능성이 없어집니다. 예를 들면 다음과 같습니다.

스타일 이름 Widget.TipTime.TextView가 상위 스타일 Widget.MaterialComponents.TextView에서 상속됩니다.

styles.xml 파일에서 resources 열기 태그와 닫기 태그 사이에 다음을 추가합니다.

<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
</style>
  1. android:minHeight,android:gravity, 속성과 android:textAppearance. 속성을 재정의하도록 TextView 스타일을 설정합니다.

android:minHeightTextView에서 최소 높이 48dp를 설정합니다. 모든 행의 최소 높이는 머티리얼 디자인 가이드라인에 따라 48dp여야 합니다.

android:gravity 속성을 설정하여 TextView에서 세로로 중앙에 텍스트를 배치할 수 있습니다. 아래 스크린샷을 참고하세요. 중력은 뷰 안의 콘텐츠가 배치되는 방식을 제어합니다. 실제 텍스트 콘텐츠의 높이가 48dp를 차지하지는 않기 때문에 값 center_verticalTextView 내에서 세로로 중앙에 텍스트를 배치합니다(가로 위치는 변경하지 않음). 가능한 다른 중력 값에는 center, center_horizontal, top, bottom이 있습니다. 다른 중력 값을 사용하면서 텍스트에 미치는 영향을 확인해보세요.

bd89f5a76d67ada6.png

텍스트 모양 속성 값을 ?attr/textAppearanceBody1로 설정합니다. TextAppearance는 텍스트 크기, 글꼴, 기타 텍스트 속성과 관련된 일련의 사전 제작된 스타일입니다. 머티리얼에서 제공하는 가능한 다른 텍스트 모양은 이 유형 스케일 목록을 참고하세요.

<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
    <item name="android:minHeight">48dp</item>
    <item name="android:gravity">center_vertical</item>
    <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
  1. activity_main.xml에서 각 TextView에 스타일 속성을 추가하여 Widget.TipTime.TextView 스타일을 service_question TextView에 적용합니다.
<TextView
    android:id="@+id/service_question"
    style="@style/Widget.TipTime.TextView"
    ... />

스타일을 적용하기 전에는 TextView가 다음과 같이 작은 글꼴 크기와 회색 글꼴 색상으로 표시됩니다.

5cd99583da77efba.png

스타일을 추가한 후 TextView는 다음과 같습니다. 이제 이 TextView는 레이아웃의 나머지 부분과 더 일관성 있게 표시됩니다.

296a89a6015d9e15.png

  1. 동일한 Widget.TipTime.TextView 스타일을 tip_result TextView에 적용합니다.
<TextView
    android:id="@+id/tip_result"
    style="@style/Widget.TipTime.TextView"
    ... />

c45860bda6761be7.png

  1. 동일한 텍스트 스타일을 스위치의 텍스트 라벨에 적용해야 합니다. 하지만 SwitchMaterial 위젯에 TextView 스타일을 설정할 수 없습니다. TextView 스타일은 TextViews에만 적용할 수 있습니다. 따라서 스위치의 새로운 스타일을 만듭니다. 속성은 minHeight, gravity, textAppearance 측면에서 동일합니다. 이제 MDB 라이브러리의 Switch 스타일에서 상속받기 때문에, 여기서 차이점은 스타일 이름과 상위 요소입니다. 스타일의 이름은 상위 스타일의 이름도 반영해야 합니다.

스타일 이름 Widget.TipTime.CompoundButton.Switch가 상위 스타일 Widget.MaterialComponents.CompoundButton.Switch에서 상속됩니다.

<style name="Widget.TipTime.CompoundButton.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
   <item name="android:minHeight">48dp</item>
   <item name="android:gravity">center_vertical</item>
   <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>

또한 이 스타일에서 스위치와 관련된 추가 속성을 지정할 수도 있지만 이 앱에서는 그럴 필요가 없습니다.

  1. 라디오 버튼 텍스트는 텍스트의 시각적 표현이 일관적인지 확인할 마지막 위치입니다. RadioButton 위젯에 TextView 스타일이나 Switch 스타일을 적용할 수 없습니다. 대신 라디오 버튼의 새로운 스타일을 만들어야 합니다. MDC 라이브러리의 RadioButton 스타일에서 확장할 수 있습니다.

이 스타일을 만드는 동안 라디오 버튼 텍스트와 원 시각 요소 사이에 패딩을 약간 추가합니다. paddingStart는 아직 사용하지 않은 새로운 속성입니다. 패딩은 뷰의 콘텐츠와 뷰 경계 사이의 공간 크기입니다. paddingStart 속성은 구성요소의 시작 부분에만 패딩을 설정합니다. 라디오 버튼에 적용한 paddingStart 0dp와 8dp의 차이를 확인하세요.

e1cef41d95740600.png

25f75f5c36085e76.png

<style name="Widget.TipTime.CompoundButton.RadioButton"
parent="Widget.MaterialComponents.CompoundButton.RadioButton">
   <item name="android:paddingStart">8dp</item>
   <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
  1. (선택사항) 자주 사용되는 값의 관리 효율성을 높이려면 dimens.xml 파일을 만듭니다. 위 styles.xml 파일의 경우와 동일한 방법으로 파일을 만들 수 있습니다. 값 디렉터리를 선택하여 마우스 오른쪽 버튼을 클릭한 후 New > Values Resource File을 선택합니다.

이 작은 앱에서 최소 높이 설정을 두 번 반복했습니다. 지금은 관리 효율이 좋지만, 이 설정 값을 공유하는 구성요소가 4개, 6개, 10개 또는 그 이상인 경우 빠르게 통제 불가능해집니다. 모두 개별적으로 변경하는 것은 지루하고 오류가 발생하기 쉽습니다. 이름을 지정할 수 있는 공통적인 치수를 보유하는 dimens.xml이라는 또 다른 유용한 리소스 파일을 res > values에 만들 수 있습니다. 공통적인 값을 명명된 치수로 표준화하면 앱을 더 쉽게 관리할 수 있습니다. TipTime은 작은 앱이므로 이 선택적 단계 밖에서 이 기능을 사용하지는 않습니다. 그러나 프로덕션 환경에서 디자인팀과 함께 작업할 수도 있는 더 복잡한 앱의 경우 dimens.xml을 사용하면 이러한 값을 더 자주 변경하기가 쉽습니다.

dimens.xml

<resources>
   <dimen name="min_text_height">48dp</dimen>
</resources>

48dp 대신 @dimen/min_text_height를 사용하도록 styles.xml 파일을 업데이트합니다.

...
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
    <item name="android:minHeight">@dimen/min_text_height</item>
    <item name="android:gravity">center_vertical</item>
    <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
...

테마에 스타일 추가하기

아직 새 RadioButton 스타일과 Switch 스타일을 각 위젯에 적용하지 않았음을 확인할 수 있습니다. 이것은 앱 테마에서 테마 속성을 사용하여 radioButtonStyleswitchStyle을 설정하기 때문입니다. 테마가 무엇인지 다시 살펴보겠습니다.

테마는 나중에 스타일, 레이아웃 등에 참조할 수 있는 명명된 리소스(테마 속성이라고 함)의 모음입니다. 개별 View.뿐만 아니라 전체 앱, 활동, 뷰 계층 구조에도 테마를 지정할 수 있습니다. 이전에 themes.xml에서 앱과 구성요소 전체적으로 사용되는 colorPrimary, colorSecondary 같은 테마 속성을 설정하여 앱의 테마를 수정했습니다.

radioButtonStyleswitchStyle은 설정할 수 있는 다른 테마 속성입니다. 이 테마 속성에 제공하는 스타일 리소스는 테마가 적용되는 뷰 계층 구조의 모든 라디오 버튼과 모든 스위치에 적용됩니다.

textInputStyle용 테마 속성도 있는데, 지정된 스타일 리소스는 앱 내의 모든 텍스트 입력란에 적용됩니다. TextInputLayout을 머티리얼 디자인 가이드라인에 제시된 윤곽선 텍스트 필드처럼 만들 수 있도록 MDS 라이브러리에 Widget.MaterialComponents.TextInputLayout.OutlinedBox로 정의된 OutlinedBox 스타일이 있습니다. 여기서는 이 스타일을 사용합니다.

b00a91da56e6f6e2.png

  1. 원하는 스타일이 테마에서 참조되도록 themes.xml 파일을 수정합니다. 테마 속성을 설정하는 방법은 이전의 Codelab에서 colorPrimarycolorSecondary 테마 속성을 선언한 방법과 같습니다. 하지만 이번에는 관련 테마 속성이 textInputStyle, radioButtonStyle, switchStyle입니다. 이전에 RadioButtonSwitch용으로 만든 스타일을 머티리얼 OutlinedBox 텍스트 필드의 스타일과 함께 사용합니다.

다음을 res/values/themes.xml의 앱 테마 스타일 태그에 복사합니다.

<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
  1. res/values/themes.xml 파일은 다음과 같습니다. 원하는 경우 XML에 <!--->로 표시된 주석을 추가할 수 있습니다.
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Base application theme. -->
    <style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        ...
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Text input fields -->
        <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
        <!-- Radio buttons -->
        <item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
        <!-- Switches -->
        <item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
    </style>

</resources>
  1. themes.xml (night)에서 어두운 테마도 동일하게 변경해야 합니다. res/values-night/themes.xml 파일은 다음과 같습니다.
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Application theme for dark theme. -->
    <style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        ...
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Text input fields -->
        <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
        <!-- For radio buttons -->
        <item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
        <!-- For switches -->
        <item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
    </style>

</resources>
  1. 앱을 실행하고 변경사항을 확인합니다. 텍스트 필드의 OutlinedBox 스타일이 훨씬 더 보기 좋습니다. 이제 모든 텍스트가 일관되게 표시됩니다.

31ac15991713b031.png 3e861407146c9ed4.png

앱 완료에 가까워지면서 예상되는 워크플로뿐만 아니라 다른 사용자 시나리오에서도 앱을 테스트해야 합니다. 코드를 약간 변경하여 사용자 환경을 크게 개선할 수 있음을 발견할 수도 있습니다.

기기 회전하기

  1. 기기를 가로 모드로 회전합니다. 먼저 자동 회전을 사용 설정해야 할 수도 있습니다. 이 설정은 기기의 빠른 설정 또는 설정 > 디스플레이 > 고급 > 화면 자동 회전 옵션 아래에 있습니다.

f2edb1ae9926d5f1.png

에뮬레이터에서는 기기 오른쪽 상단에 있는 에뮬레이터 옵션을 사용하여 화면을 오른쪽이나 왼쪽으로 회전할 수 있습니다.

da8aee11166adf41.png

  1. Calculate 버튼을 포함한 일부 UI 구성요소가 잘리는 것을 확인할 수 있습니다. 이로 인해 앱을 사용하지 못합니다.

d73499f9c9d2b330.png

  1. 이 버그를 해결하려면 ConstraintLayout 주위에 ScrollView를 추가합니다. XML은 다음과 같습니다.
<ScrollView
   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_height="match_parent"
   android:layout_width="match_parent">

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

       ...
   </ConstraintLayout>

</ScrollView>
  1. 앱을 다시 실행하고 테스트합니다. 기기를 가로 모드로 회전하면 UI를 스크롤하여 Calculate 버튼에 액세스하고 팁 결과를 볼 수 있어야 합니다. 이 수정사항은 가로 모드에 적용할 때 외에도 다른 크기의 다른 Android 기기에서도 유용합니다. 이제 기기 화면 크기와 관계없이 사용자가 레이아웃을 스크롤할 수 있습니다.

Enter 키를 누르면 키보드 숨기기

서비스 비용을 입력한 후에도 키보드가 계속 표시되는 것을 확인할 수도 있습니다. Calculate 버튼에 보다 쉽게 액세스하기 위해 매번 키보드를 수동으로 숨기는 일은 약간 번거롭습니다. Enter 키를 누르면 키보드가 자동으로 숨겨지도록 해야 합니다.

e2c3a3dbc40218a2.png

텍스트 필드의 경우 특정 키를 탭할 때 이벤트에 응답하도록 키 리스너를 정의할 수 있습니다. 키보드에서 가능한 모든 입력 옵션에는 Enter 키를 포함한 키와 키 코드가 연결되어 있습니다. 터치 키보드는 소프트 키보드라고도 하며 실제 키보드가 아닙니다.

1c95d7406d3847fe.png

이 작업에서는 Enter 키 누름을 수신하도록 텍스트 필드에 키 리스너를 설정합니다. 이벤트가 감지되면 키보드를 숨깁니다.

  1. 다음 도우미 메서드를 복사하여 MainActivity 클래스에 붙여넣습니다. MainActivity 클래스의 닫는 중괄호 바로 앞에 삽입할 수 있습니다. handleKeyEvent()keyCode 입력 매개변수가 KeyEvent.KEYCODE_ENTER와 같은 경우 터치 키보드를 숨기는 비공개 도우미 함수입니다. InputMethodManager는 소프트 키보드를 표시할지 숨길지 제어하고 사용자가 어느 소프트 키보드를 표시할지 선택할 수 있도록 합니다. 이 메서드는 키 이벤트가 처리된 경우 true를 반환하고 처리되지 않은 경우 false를 반환합니다.

MainActivity.kt

private fun handleKeyEvent(view: View, keyCode: Int): Boolean {
   if (keyCode == KeyEvent.KEYCODE_ENTER) {
       // Hide the keyboard
       val inputMethodManager =
           getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
       inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
       return true
   }
   return false
}
  1. 이제 TextInputEditText 위젯에서 키 리스너를 연결합니다. 결합 객체 binding.costOfServiceEditText.를 통해 TextInputEditText 위젯에 액세스할 수 있습니다.

costOfServiceEditText에서 setOnKeyListener() 메서드를 호출하고 OnKeyListener를 전달합니다. binding.calculateButton.setOnClickListener { calculateTip() }.를 사용해 앱의 Calculate 버튼에 클릭 리스너를 설정하는 방법과 비슷합니다.

뷰에 키 리스너를 설정하는 코드는 조금 더 복잡합니다. 일반적으로 OnKeyListener에는 키 누름이 발생할 때 트리거되는 onKey() 메서드가 있습니다. onKey() 메서드는 입력 인수 세 개, 즉 뷰, 누른 키의 코드, 키 이벤트(이 경우에는 사용하지 않으므로 _을 지정함)를 사용합니다. onKey() 메서드가 호출되면 handleKeyEvent() 메서드를 호출하고 뷰 및 키 코드 인수를 전달해야 합니다. 작성하는 구문은 view, keyCode, _ -> handleKeyEvent(view, keyCode).입니다. 이러한 구문을 람다 표현식이라고 하지만 람다에 대해서는 이후 단원에서 자세히 알아봅니다.

텍스트 필드에 키 리스너를 설정하는 코드를 활동의 onCreate() 메서드 내에 추가합니다. 왜냐하면 레이아웃이 생성되면 곧바로, 그리고 사용자가 활동과 상호작용을 시작하기 전에 키 리스너를 연결해야 하기 때문입니다.

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
   ...

   setContentView(binding.root)

   binding.calculateButton.setOnClickListener { calculateTip() }

   binding.costOfServiceEditText.setOnKeyListener { view, keyCode, _ -> handleKeyEvent(view, keyCode)
   }
}
  1. 새 변경사항이 작동하는지 테스트합니다. 앱을 실행하고 서비스 비용을 입력합니다. 키보드의 Enter 키를 누르면 소프트 키보드가 숨겨져야 합니다.

음성 안내 지원을 사용하여 앱 테스트하기

이 과정을 전체적으로 진행하면서 가능한 한 많은 사용자가 액세스할 수 있는 앱을 빌드하려고 합니다. 일부 사용자는 음성 안내 지원을 사용하여 앱에 액세스하고 탐색할 수도 있습니다. 음성 안내 지원은 Android 기기에 포함된 Google 스크린 리더입니다. 음성 안내 지원에서 음성 피드백을 제공하므로 화면을 보지 않고 기기를 사용할 수 있습니다.

음성 안내 지원을 사용 설정한 상태에서 사용자가 앱에서 팁 계산 사용 사례를 완료할 수 있는지 확인합니다.

  1. 안내에 따라 기기에서 음성 안내 지원을 사용 설정합니다.
  2. Tip Time 앱으로 돌아갑니다.
  3. 안내에 따라 음성 안내 지원을 통해 앱을 탐색합니다. 오른쪽으로 스와이프하여 화면 요소를 순서대로 탐색하고 왼쪽으로 스와이프하여 반대 방향으로 이동합니다. 아무 곳이나 두 번 탭하여 선택합니다. 스와이프 동작으로 앱의 모든 요소에 도달할 수 있는지 확인합니다.
  4. 음성 안내 지원 사용자가 화면의 각 항목으로 이동하여 서비스 비용을 입력하고, 팁 옵션을 변경하고, 팁을 계산한 후에 음성으로 팁 금액을 들을 수 있는지 확인합니다. 아이콘에는 importantForAccessibility="no"가 표시되어 있으므로 음성 피드백이 제공되지 않습니다.

앱의 접근성을 높이는 방법을 자세히 알아보려면 이 원칙을 확인하세요.

(선택사항) 벡터 드로어블의 색조 조정하기

이 선택적 작업에서는 밝은 테마와 어두운 테마에서 아이콘이 다르게 보이도록 테마의 기본 색상에 따라 아이콘의 색조를 조정합니다(아래 참고). 이러한 변경은 아이콘이 앱 테마와 더 일관성 있도록 UI에 추가한 멋진 추가 기능입니다.

77092f702beb1cfb.png 80a390087905eb29.png

앞서 언급했듯이 비트맵 이미지와 비교하여 VectorDrawables 이미지의 이점 중 하나는 크기를 확장하고 색조를 조정하는 기능입니다. 다음은 종 모양 아이콘을 나타내는 XML입니다. 주목해야 할 두 색상 속성은 android:tintandroid:fillColor입니다.

ic_service.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
   android:width="24dp"
   android:height="24dp"
   android:viewportWidth="24"
   android:viewportHeight="24"
   android:tint="?attr/colorControlNormal">
 <path
     android:fillColor="@android:color/white"
     android:pathData="M2,17h20v2L2,19zM13.84,7.79c0.1,-0.24 0.16,-0.51 0.16,-0.79 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,0.28 0.06,0.55 0.16,0.79C6.25,8.6 3.27,11.93 3,16h18c-0.27,-4.07 -3.25,-7.4 -7.16,-8.21z"/>
</vector>

bdddc76d0ca06573.png

색조가 있으면 드로어블의 모든 fillColor 지시문이 재정의됩니다. 이 경우 흰색은 colorControlNormal 테마 속성으로 재정의됩니다. colorControlNormal은 위젯의 '정상'(선택 해제된/비활성 상태) 색상입니다. 현재는 회색으로 표현됩니다.

앱에 적용할 수 있는 한 가지 시각적 개선 사항은 앱 테마의 기본 색상을 기반으로 드로어블의 색조를 조정하는 것입니다. 밝은 테마에서는 아이콘이 @color/green으로 표시되고 어두운 테마에서는 아이콘이 ?attr/colorPrimary@color/green_light로 표시됩니다. 앱 테마의 기본 색상을 기반으로 드로어블의 색조를 조정하면 레이아웃에 있는 요소의 통합성과 일관성을 높일 수 있습니다. 또한 밝은 테마와 어두운 테마용 아이콘 세트를 복제할 필요가 없습니다. 벡터 드로어블 세트가 하나뿐이고, colorPrimary 테마 속성을 기반으로 색조가 변경됩니다.

  1. ic_service.xml에서 android:tint 속성 값을 변경합니다.
android:tint="?attr/colorPrimary"

이 아이콘은 이제 Android 스튜디오에서 적절한 색조로 표현됩니다.

148a05c44b515c25.png

colorPrimary 테마 속성이 가리키는 값은 밝은 테마와 어두운 테마에 따라 다릅니다.

  1. 다른 벡터 드로어블에서 색조를 변경하는 데 이 단계를 반복합니다.

ic_store.xml

<vector ...
   android:tint="?attr/colorPrimary">
   ...
</vector>

ic_round_up.xml

<vector ...
   android:tint="?attr/colorPrimary">
   ...
</vector>
  1. 앱을 실행합니다. 밝은 테마와 어두운 테마에서 아이콘이 다르게 표시되는지 확인합니다.
  2. 마지막 정리 단계로, 앱에서 모든 XML 코드 파일과 Kotlin 코드 파일의 형식을 다시 지정해야 합니다.

축하합니다. 팁 계산기 앱을 완료했습니다. 빌드한 결과물을 아주 자랑스럽게 여기세요. 이 경험이 훨씬 더 멋지고 기능적인 앱을 빌드하기 위한 디딤돌이 되기를 바랍니다.

이 Codelab의 솔루션 코드는 아래에 나온 GitHub 저장소에 있습니다.

5743ac5ee2493d7.png ab4acfeed8390465.png

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 구현된 방식을 확인합니다.
  • 가능하면 머티리얼 디자인 구성요소를 사용하여 머티리얼 디자인 가이드라인을 준수하고 더욱 다양하게 맞춤설정합니다.
  • 아이콘을 추가하여 앱의 각 부분이 어떻게 작동하는지 시각적 힌트를 통해 알립니다.
  • ConstraintLayout을 사용하여 레이아웃에 요소를 배치합니다.
  • 가장자리 사례(예: 가로 모드로 앱 회전하기)에서 앱을 테스트하고 해당되는 경우 개선합니다.
  • 코드에 주석을 달아 다른 개발자가 코드를 읽을 때 잘 이해할 수 있도록 합니다.
  • 코드 서식을 다시 지정하고 코드를 정리하여 최대한 간결하게 만듭니다.
  • 이전의 Codelab을 계속 진행하면서 여기서 배운 권장사항(예: 머티리얼 디자인 구성요소 사용 관련)을 사용해 머티리얼 가이드라인을 더 긴밀하게 준수하도록 요리용 단위 변환기 앱을 업데이트하세요.