Android 앱 테마와 디자인 시스템 구축 완벽 가이드 — Material 3, DayNight, 동적 색상과 XML 컴포넌트
Android 앱을 개발하다 보면 처음에는 화면마다 필요한 색상과 크기를 직접 지정하는 경우가 많습니다.
예를 들어 버튼에는 파란색을 사용하고, 카드 배경은 흰색으로 지정하고, TextView에는 검은색을 적용하는 방식입니다.
화면이 몇 개 없을 때는 큰 문제가 없습니다.
하지만 앱의 기능이 늘어나면 상황이 달라집니다.
화면마다 버튼 색상이 조금씩 다르다.
다크 모드에서 일부 글자가 보이지 않는다.
RecyclerView 카드 배경색이 테마와 맞지 않는다.
Android 버전에 따라 상태 표시줄 색상이 다르게 보인다.
BottomNavigationView의 선택 색상이 화면마다 다르다.
디자인을 변경할 때 여러 XML 파일을 수정해야 한다.
앱별 Product Flavor에 따라 브랜드 색상을 다르게 적용해야 한다.
이러한 문제를 줄이기 위해 필요한 것이 **디자인 시스템(Design System)**입니다.
디자인 시스템은 단순히 예쁜 화면을 만드는 작업이 아닙니다.
색상, 타이포그래피, 간격, 모서리, 컴포넌트 상태 등을 일관된 규칙으로 정의하고 재사용하는 구조입니다.
이번 글에서는 Jetpack Compose가 아닌 기존 XML 기반 Android 앱을 기준으로 Material Components, Material 3 스타일, DayNight, 동적 색상, 공통 UI 컴포넌트 구현 방법을 살펴보겠습니다.
개발 환경: Kotlin, XML ViewBinding, AppCompat, Material Components for Android, RecyclerView, ConstraintLayout, BottomNavigationView, minSdk 24 이상. Material 3의 XML 지원 범위와 라이브러리 버전은 사용하는 Material Components 릴리스에 따라 확인해야 합니다.
1. Android 디자인 시스템이 필요한 이유
다음과 같이 색상을 직접 지정한 XML이 있다고 가정하겠습니다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="조류 정보"
android:textColor="#222222"
android:textSize="18sp" />이 코드는 밝은 배경에서는 정상적으로 보일 수 있습니다.
하지만 다크 모드에서 배경이 어두워지면 텍스트와 배경의 대비가 충분하지 않을 수 있습니다.
또한 앱 전체의 텍스트 색상을 변경하려면 여러 레이아웃 파일을 수정해야 합니다.
디자인 시스템을 적용하면 다음과 같이 변경할 수 있습니다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="조류 정보"
android:textAppearance="?attr/textAppearanceTitleLarge"
android:textColor="?attr/colorOnSurface" />이 방식에서는 화면이 특정 색상에 직접 의존하지 않습니다.
대신 현재 테마에서 정의한 의미 있는 색상과 텍스트 스타일을 사용합니다.
디자인 시스템의 핵심은 색상 값을 재사용하는 것보다 색상의 역할을 재사용하는 데 있습니다.
Material Design과 Material 3
2. Material 3란?
Material 3는 Google의 Material Design 시스템을 발전시킨 디자인 체계입니다.
대표적인 특징은 다음과 같습니다.
역할 기반 색상 체계
동적 색상
일관된 타이포그래피
컴포넌트 상태별 스타일
다양한 화면 크기에 대응하는 디자인
접근성을 고려한 UI 구성
Android에서는 Jetpack Compose Material 3와 XML View 기반 Material Components for Android를 구분해야 합니다.
Compose에서는 androidx.compose.material3를 사용합니다.
반면 XML 기반 앱에서는 com.google.android.material 라이브러리와 테마, 스타일, View 컴포넌트를 사용합니다.
두 환경은 동일한 디자인 원칙을 공유하지만 API와 구현 방식은 다릅니다.
3. Material Components 의존성 추가
앱 모듈의 build.gradle.kts에 Material Components 라이브러리를 추가합니다.
dependencies {
implementation(
"com.google.android.material:material:<version>"
)
}<version>에는 공식 릴리스에서 확인한 호환 버전을 사용해야 합니다.
Material Components 라이브러리는 다음과 같은 XML 기반 컴포넌트를 제공합니다.
MaterialButton
MaterialCardView
TextInputLayout
TextInputEditText
BottomNavigationView
NavigationRailView
MaterialToolbar
MaterialAlertDialogBuilder
MaterialSwitch
이 글에서는 기존 XML 앱에 적용하기 쉬운 구성부터 살펴보겠습니다.
앱 테마 구성
4. Theme.Material3.DayNight 사용하기
Material Components for Android에서 제공하는 Material 3 테마 계열을 사용할 수 있습니다.
예를 들어 res/values/themes.xml에 다음과 같이 작성합니다.
<resources>
<style
name="Theme.BirdApp"
parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">
@color/bird_primary
</item>
<item name="colorOnPrimary">
@color/bird_on_primary
</item>
<item name="colorSecondary">
@color/bird_secondary
</item>
<item name="colorSurface">
@color/bird_surface
</item>
<item name="colorOnSurface">
@color/bird_on_surface
</item>
</style>
</resources>이 예제에서는 앱의 기본 색상 역할을 테마에서 정의합니다.
실제 프로젝트에서는 사용하는 Material Components 버전의 테마 속성을 확인해야 합니다.
5. AndroidManifest에 테마 적용
<application
android:theme="@style/Theme.BirdApp">
<!-- Activity 설정 -->
</application>앱 전체에 공통 테마를 적용하면 각 화면에서 동일한 디자인 속성을 사용할 수 있습니다.
특정 Activity에 다른 테마를 적용해야 하는 경우에는 Activity 수준에서 별도로 지정할 수도 있습니다.
다만 화면마다 테마를 과도하게 분리하면 디자인 일관성을 유지하기 어려워질 수 있습니다.
색상 시스템 구축
6. 색상은 역할 중심으로 정의하기
디자인 시스템에서 중요한 것은 색상을 단순히 이름으로 구분하지 않는 것입니다.
예를 들어 다음과 같은 색상 이름은 의미가 제한적입니다.
blue
dark_blue
light_gray
white대신 다음과 같은 역할을 기준으로 구분하는 것이 좋습니다.
| 색상 역할 | 사용 예 |
|---|---|
| Primary | 주요 버튼, 강조 요소 |
| On Primary | Primary 위의 텍스트 |
| Secondary | 보조 강조 |
| Surface | 카드 및 화면 표면 |
| On Surface | 표면 위의 텍스트 |
| Error | 오류 상태 |
| Outline | 테두리 및 구분선 |
이렇게 하면 나중에 브랜드 색상을 변경하더라도 컴포넌트의 역할을 유지할 수 있습니다.
7. 밝은 테마 색상 정의
res/values/colors.xml 예제입니다.
<resources>
<color name="bird_primary">
#356A4A
</color>
<color name="bird_on_primary">
#FFFFFF
</color>
<color name="bird_secondary">
#52665A
</color>
<color name="bird_surface">
#FAFDF8
</color>
<color name="bird_on_surface">
#191C19
</color>
</resources>위 색상은 설명을 위한 예시입니다.
실제 앱에서는 브랜드 이미지와 명암 대비를 고려해 색상을 결정해야 합니다.
다크 모드 구현
8. DayNight 테마란?
DayNight 테마는 시스템의 밝은 모드와 어두운 모드에 대응할 수 있도록 지원합니다.
Android에서는 리소스 한정자를 이용해 테마별 색상을 분리할 수 있습니다.
대표적으로 다음 디렉터리를 사용합니다.
res/
├── values/
│ ├── colors.xml
│ └── themes.xml
│
└── values-night/
└── colors.xmlvalues-night는 야간 모드에서 사용할 리소스를 정의하는 디렉터리입니다.
9. 다크 모드 색상 정의
res/values-night/colors.xml에 다음과 같이 작성합니다.
<resources>
<color name="bird_primary">
#9FD5B1
</color>
<color name="bird_on_primary">
#00391F
</color>
<color name="bird_secondary">
#B7CCBD
</color>
<color name="bird_surface">
#111411
</color>
<color name="bird_on_surface">
#E1E4DE
</color>
</resources>이렇게 하면 같은 색상 리소스 이름을 사용하면서도 시스템 테마에 따라 다른 색상 값을 적용할 수 있습니다.
중요한 것은 모든 화면에서 #FFFFFF, #000000 같은 값을 직접 사용하지 않는 것입니다.
10. 다크 모드에서 흔히 발생하는 문제
다크 모드를 처음 적용하면 다음과 같은 문제가 발생할 수 있습니다.
카드 배경이 흰색으로 유지됨
텍스트 색상이 검은색으로 고정됨
아이콘이 배경과 구분되지 않음
이미지 Placeholder가 지나치게 밝음
Dialog 배경과 글자색이 맞지 않음
상태 표시줄 아이콘이 보이지 않음
대부분은 XML에서 색상을 직접 지정했거나 테마 속성을 사용하지 않았을 때 발생합니다.
예를 들어 다음 코드는 주의해야 합니다.
android:textColor="#000000"대신 다음과 같이 작성할 수 있습니다.
android:textColor="?attr/colorOnSurface"앱에서 테마 변경하기
11. AppCompatDelegate 활용
사용자가 앱 내부에서 밝은 모드와 어두운 모드를 선택하도록 만들 수 있습니다.
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES
)밝은 모드는 다음과 같습니다.
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_NO
)시스템 설정을 따르도록 하려면 다음과 같이 작성합니다.
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)테마 변경 시 Activity가 재생성될 수 있으므로 화면 상태를 올바르게 복원해야 합니다.
특히 검색어, RecyclerView 스크롤 위치, 입력 중인 메모 등을 확인해야 합니다.
12. DataStore에 테마 설정 저장
테마 선택값을 DataStore에 저장할 수 있습니다.
val THEME_MODE =
stringPreferencesKey("theme_mode")예를 들어 다음 값을 사용할 수 있습니다.
system
light
dark저장 예제입니다.
suspend fun saveThemeMode(
mode: String
) {
dataStore.edit { preferences ->
preferences[THEME_MODE] = mode
}
}실제 프로젝트에서는 문자열 대신 enum class 또는 명확한 모델을 사용하는 것이 좋습니다.
또한 저장된 값을 앱 시작 시 읽어 테마에 반영해야 합니다.
Android 동적 색상
13. Dynamic Color란?
Android 12(API 31)부터 시스템이 제공하는 동적 색상 기능을 활용할 수 있습니다.
동적 색상은 기기의 배경화면과 시스템 색상 설정 등을 바탕으로 생성된 색상 팔레트를 앱에 적용하는 방식입니다.
이를 통해 앱이 시스템 디자인과 자연스럽게 어울리도록 만들 수 있습니다.
다만 동적 색상은 Android 12 이상에서 사용할 수 있으며, 기기와 시스템 설정에 따라 결과가 달라질 수 있습니다.
14. DynamicColors 적용
Material Components for Android에서는 다음과 같은 API를 사용할 수 있습니다.
class BirdApplication : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(
this
)
}
}이렇게 하면 지원되는 환경에서 Activity에 동적 색상을 적용할 수 있습니다.
다만 동적 색상을 사용하면 앱에서 직접 정의한 브랜드 색상과 다른 결과가 나타날 수 있습니다.
따라서 동적 색상을 무조건 활성화하기보다 앱의 디자인 정책에 맞춰 선택하는 것이 좋습니다.
15. 동적 색상을 선택적으로 적용하기
예를 들어 사용자가 동적 색상 사용 여부를 설정하도록 만들 수 있습니다.
앱 설정
├── 시스템 색상 사용
├── 앱 기본 색상 사용
└── 다크 모드 설정이 경우 설정값을 저장하고 Activity가 생성되기 전에 적절한 테마 정책을 적용해야 합니다.
동적 색상 적용 여부를 변경할 때는 Activity 재생성이나 앱 재시작이 필요할 수 있습니다.
설정 화면에서는 사용자에게 변경 적용 시점을 명확하게 안내하는 것이 좋습니다.
MaterialButton 스타일
16. 공통 버튼 스타일 만들기
화면마다 버튼 속성을 반복 작성하면 디자인을 변경하기 어렵습니다.
예를 들어 다음과 같은 버튼이 있다고 가정하겠습니다.
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="저장" />공통 스타일을 만들면 버튼의 모서리, 높이, 색상 등을 일관되게 관리할 수 있습니다.
17. styles.xml 작성
res/values/styles.xml에 다음과 같이 작성합니다.
<resources>
<style
name="Widget.BirdApp.Button.Primary"
parent="Widget.Material3.Button">
<item name="cornerRadius">
12dp
</item>
<item name="android:minHeight">
48dp
</item>
<item name="backgroundTint">
?attr/colorPrimary
</item>
</style>
</resources>실제 스타일 속성은 컴포넌트와 라이브러리 버전에 따라 지원 여부를 확인해야 합니다.
특히 MaterialButton의 배경과 모서리 설정은 일반 Button과 다르게 처리될 수 있습니다.
18. XML에서 스타일 사용
<com.google.android.material.button.MaterialButton
style="@style/Widget.BirdApp.Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="저장" />이제 앱 전체의 주요 버튼 스타일을 변경할 때 공통 스타일만 수정하면 됩니다.
다만 버튼의 역할에 따라 Primary, Secondary, Text Button 등으로 스타일을 구분하는 것이 좋습니다.
ColorStateList 활용
19. ColorStateList란?
ColorStateList는 View의 상태에 따라 서로 다른 색상을 적용할 수 있도록 하는 리소스입니다.
대표적인 상태는 다음과 같습니다.
선택됨
선택되지 않음
활성화됨
비활성화됨
눌림
체크됨
예를 들어 BottomNavigationView의 선택 상태에 따라 아이콘 색상을 다르게 표시할 수 있습니다.
20. 선택 상태별 색상 정의
res/color/navigation_item_color.xml을 작성합니다.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:color="?attr/colorPrimary" />
<item
android:color="?attr/colorOnSurface" />
</selector>이 파일은 선택된 항목과 선택되지 않은 항목의 색상을 구분합니다.
테마 속성을 사용하므로 밝은 모드와 다크 모드에 대응하기 쉽습니다.
21. BottomNavigationView에 적용
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@color/navigation_item_color"
app:itemTextColor="@color/navigation_item_color"
app:menu="@menu/bottom_navigation_menu" />이렇게 하면 BottomNavigationView의 아이콘과 텍스트 색상을 공통으로 관리할 수 있습니다.
Material 3 테마에서는 기본 선택 표시 및 활성 인디케이터 스타일도 함께 확인하는 것이 좋습니다.
MaterialCardView
22. 카드 디자인 통일하기
조류 도감 앱에서는 RecyclerView 항목을 카드 형태로 표시할 수 있습니다.
이때 카드마다 모서리와 그림자 설정이 다르면 화면이 일관되지 않게 보일 수 있습니다.
공통 스타일을 만들면 이러한 문제를 줄일 수 있습니다.
<style
name="Widget.BirdApp.Card"
parent="Widget.Material3.CardView.Elevated">
<item name="cardCornerRadius">
16dp
</item>
<item name="cardElevation">
2dp
</item>
</style>실제 카드 스타일의 부모 이름과 지원 속성은 Material Components 버전에 따라 확인해야 합니다.
23. RecyclerView 카드에 적용
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.BirdApp.Card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="참새"
android:textAppearance="?attr/textAppearanceTitleMedium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>이 예제는 공통 카드 스타일을 적용하는 기본 형태입니다.
실제 RecyclerView에서는 이미지, 좋아요 버튼, 학명, 분류 정보 등을 추가할 수 있습니다.
타이포그래피 시스템
24. TextAppearance 활용
앱 전체에서 글자 크기를 직접 지정하면 나중에 변경하기 어렵습니다.
예를 들어 다음과 같은 코드가 여러 화면에 반복될 수 있습니다.
android:textSize="18sp"
android:textStyle="bold"대신 TextAppearance를 사용하면 텍스트 스타일을 일관되게 관리할 수 있습니다.
android:textAppearance="?attr/textAppearanceTitleLarge"Material 3에서는 제목, 본문, 라벨 등의 역할을 기준으로 타이포그래피를 구분합니다.
25. 공통 텍스트 스타일 만들기
<style
name="TextAppearance.BirdApp.BirdName"
parent="TextAppearance.Material3.TitleMedium">
<item name="android:textStyle">
bold
</item>
</style>XML에서는 다음과 같이 사용합니다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="참새"
android:textAppearance="@style/TextAppearance.BirdApp.BirdName" />이렇게 하면 조류 이름에 사용하는 글꼴 스타일을 한곳에서 관리할 수 있습니다.
간격과 크기 관리
26. dimens.xml 활용
디자인 시스템에는 색상과 글꼴뿐 아니라 간격도 포함됩니다.
예를 들어 화면마다 다음과 같은 값이 반복된다고 가정하겠습니다.
8dp
12dp
16dp
24dp
32dp이러한 값을 dimens.xml로 관리할 수 있습니다.
<resources>
<dimen name="spacing_xs">
4dp
</dimen>
<dimen name="spacing_sm">
8dp
</dimen>
<dimen name="spacing_md">
16dp
</dimen>
<dimen name="spacing_lg">
24dp
</dimen>
<dimen name="spacing_xl">
32dp
</dimen>
</resources>27. XML에서 간격 리소스 사용
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/spacing_md">
</LinearLayout>이 방식은 화면 간 간격을 일관되게 유지하는 데 도움이 됩니다.
다만 모든 크기를 무조건 리소스로 분리할 필요는 없습니다.
반복적으로 사용되거나 디자인 규칙으로 관리할 값부터 분리하는 것이 좋습니다.
TextInputLayout
28. 검색 입력창 디자인
검색 기능이 있는 앱에서는 TextInputLayout을 활용할 수 있습니다.
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="새 이름 검색">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:imeOptions="actionSearch" />
</com.google.android.material.textfield.TextInputLayout>TextInputLayout은 입력창의 힌트, 오류 메시지, 보조 텍스트 등을 일관된 방식으로 표시하는 데 도움이 됩니다.
검색 화면에서는 키보드의 검색 버튼 처리와 입력 상태 복원도 함께 구현하는 것이 좋습니다.
공통 Dialog
29. MaterialAlertDialogBuilder 사용
기존 AlertDialog.Builder 대신 Material 스타일을 적용한 Dialog를 사용할 수 있습니다.
MaterialAlertDialogBuilder(requireContext())
.setTitle("메모 삭제")
.setMessage("저장된 메모를 삭제하시겠습니까?")
.setNegativeButton("취소", null)
.setPositiveButton("삭제") { _, _ ->
viewModel.deleteMemo()
}
.show()MaterialAlertDialogBuilder는 현재 테마의 스타일을 활용해 Dialog를 구성할 수 있습니다.
다크 모드에서도 적절한 색상과 컴포넌트 스타일이 적용되는지 확인해야 합니다.
공통 UI 컴포넌트 만들기
30. XML include 활용
앱에서 로딩 화면이나 빈 목록 화면을 여러 곳에서 사용한다면 공통 XML 레이아웃을 만들 수 있습니다.
예를 들어 view_empty_state.xml을 작성합니다.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="@dimen/spacing_lg">
<TextView
android:id="@+id/textEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="검색 결과가 없습니다."
android:textAppearance="?attr/textAppearanceBodyLarge" />
</LinearLayout>다른 화면에서는 다음과 같이 사용할 수 있습니다.
<include
android:id="@+id/emptyState"
layout="@layout/view_empty_state" />이렇게 하면 공통 UI를 여러 화면에서 재사용할 수 있습니다.
상태 표시줄과 내비게이션 바
31. Edge-to-edge와 테마
Android 15 이상을 대상으로 하는 앱에서는 Edge-to-edge 동작을 고려해야 합니다.
상태 표시줄과 내비게이션 바 주변의 색상 및 아이콘 대비도 디자인 시스템의 일부입니다.
단순히 상태 표시줄에 고정 색상을 지정하는 방식보다는 콘텐츠가 시스템 바 영역까지 확장되는 구조를 고려해야 합니다.
또한 WindowInsets를 올바르게 처리해야 화면이 시스템 바와 겹치지 않습니다.
32. WindowInsets 적용 예제
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view, insets ->
val systemBars =
insets.getInsets(
WindowInsetsCompat.Type.systemBars()
)
view.updatePadding(
top = systemBars.top,
bottom = systemBars.bottom
)
insets
}이 코드는 기본적인 Insets 처리 예제입니다.
실제 앱에서는 기존 Padding을 보존해야 할 수 있습니다.
또한 BottomNavigationView, Toolbar, RecyclerView의 구조에 따라 Insets를 적용할 View를 구분해야 합니다.
화면 전체에 동일한 Insets를 적용하면 불필요한 여백이 발생할 수 있습니다.
Product Flavor별 테마
33. 앱별 브랜드 색상 적용
하나의 프로젝트에서 조류 앱과 어류 앱을 Product Flavor로 관리한다고 가정하겠습니다.
src/
├── main/
│ └── res/
│ └── values/
│ └── themes.xml
│
├── bird/
│ └── res/
│ └── values/
│ └── colors.xml
│
└── fish/
└── res/
└── values/
└── colors.xml공통 테마는 main에 두고, 앱별 색상 리소스는 Flavor에서 정의할 수 있습니다.
예를 들어 조류 앱은 녹색 계열, 어류 앱은 청색 계열을 사용할 수 있습니다.
이렇게 하면 공통 XML 화면을 유지하면서도 앱별 브랜드 색상을 구분할 수 있습니다.
34. 공통 테마와 Flavor 리소스 분리
공통 테마에서는 다음과 같이 역할 기반 색상을 참조합니다.
<item name="colorPrimary">
@color/app_primary
</item>bird Flavor에서는 다음과 같이 정의합니다.
<color name="app_primary">
#356A4A
</color>fish Flavor에서는 다음과 같이 정의합니다.
<color name="app_primary">
#245F91
</color>단, 모든 빌드 변형에서 필요한 리소스가 제공되는지 확인해야 합니다.
Flavor별 리소스가 누락되면 빌드 오류가 발생할 수 있습니다.
접근성과 디자인 시스템
35. 색상 대비 확인
디자인 시스템에서는 시각적인 일관성뿐 아니라 접근성도 중요합니다.
특히 다음 요소를 확인해야 합니다.
텍스트와 배경의 대비
비활성화된 버튼의 가독성
오류 메시지의 구분 가능성
선택 상태의 명확성
다크 모드에서의 아이콘 가시성
색상만으로 상태를 구분하는 방식은 피하는 것이 좋습니다.
예를 들어 좋아요 버튼의 상태를 색상만으로 표현하기보다 아이콘 모양이나 접근성 설명도 함께 변경할 수 있습니다.
36. 터치 영역 확보
버튼과 아이콘은 사용자가 쉽게 누를 수 있도록 충분한 터치 영역을 제공해야 합니다.
Android에서는 일반적으로 최소 48dp 수준의 터치 대상 크기를 권장합니다.
예를 들어 다음과 같이 작성할 수 있습니다.
<ImageButton
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_favorite"
android:contentDescription="@string/favorite" />실제 아이콘의 시각적 크기는 더 작게 유지하면서 터치 영역을 충분히 확보할 수 있습니다.
디자인 시스템 테스트
37. 반드시 확인해야 할 화면 상태
디자인 시스템을 적용한 뒤에는 다음 상태를 확인해야 합니다.
| 테스트 항목 | 확인 내용 |
|---|---|
| 밝은 모드 | 색상과 텍스트 대비 |
| 다크 모드 | 배경·아이콘·텍스트 가시성 |
| 동적 색상 | 시스템 색상 적용 |
| 큰 글자 | 텍스트 잘림 및 레이아웃 |
| 화면 회전 | UI 상태 유지 |
| 태블릿 | 넓은 화면 대응 |
| 폴더블 | 화면 크기 변경 |
| 접근성 | TalkBack 및 터치 영역 |
특히 다크 모드는 앱 전체 화면을 확인해야 합니다.
메인 화면만 정상적으로 표시된다고 해서 모든 Dialog와 RecyclerView 항목이 올바르게 표시되는 것은 아닙니다.
기존 앱에 디자인 시스템 적용하기
38. 점진적 전환 방법
이미 운영 중인 Android 앱이라면 모든 XML을 한 번에 변경할 필요는 없습니다.
다음 순서로 적용하는 것이 좋습니다.
1단계: 현재 색상 조사
XML에 직접 작성된 색상 코드를 확인합니다.
2단계: 공통 색상 역할 정의
Primary, Surface, OnSurface 등 주요 색상을 정합니다.
3단계: DayNight 테마 적용
밝은 모드와 다크 모드 리소스를 분리합니다.
4단계: 공통 버튼·카드 스타일 작성
반복되는 컴포넌트부터 스타일을 통일합니다.
5단계: 타이포그래피 정리
TextAppearance를 활용해 제목과 본문 스타일을 정리합니다.
6단계: ColorStateList 적용
선택·비활성화 상태를 공통으로 관리합니다.
7단계: 동적 색상 검토
Android 12 이상에서 동적 색상 지원 여부를 결정합니다.
8단계: 접근성 및 화면 크기 테스트
큰 글자, 다크 모드, 태블릿, 폴더블 환경을 확인합니다.
39. 디자인 시스템 체크리스트
Material Components 의존성 확인
Material 3 XML 테마 적용
공통 색상 역할 정의
직접 지정한 색상 최소화
DayNight 테마 적용
values-night 리소스 작성
AppCompatDelegate 테마 변경 구현
DataStore 테마 설정 저장
Android 12 이상 동적 색상 검토
MaterialButton 공통 스타일 작성
MaterialCardView 공통 스타일 작성
TextAppearance 정의
dimens.xml 간격 관리
ColorStateList 적용
BottomNavigationView 상태 색상 확인
TextInputLayout 스타일 확인
MaterialAlertDialogBuilder 적용
공통 XML include 구성
Edge-to-edge 및 WindowInsets 확인
Product Flavor별 색상 분리
텍스트 대비 점검
터치 영역 확인
큰 글자 환경 테스트
다크 모드 전체 화면 테스트
태블릿 및 폴더블 화면 확인
40. 마무리
Android 앱의 디자인 시스템은 단순히 화면을 보기 좋게 만드는 작업이 아닙니다.
앱 전체에서 일관된 UI를 유지하고, 디자인 변경에 필요한 작업을 줄이며, 다크 모드와 접근성을 안정적으로 지원하기 위한 구조입니다.
특히 Kotlin + XML 기반 앱에서는 Theme, Style, ColorStateList, TextAppearance, dimens.xml 등을 활용해 디자인 시스템을 구축할 수 있습니다.
이번 글의 핵심을 정리하면 다음과 같습니다.
디자인 시스템은 색상·글꼴·간격·컴포넌트 규칙을 공통화하는 구조입니다.
Material 3는 역할 기반 색상과 일관된 컴포넌트 디자인을 제공합니다.
DayNight 테마로 밝은 모드와 다크 모드에 대응할 수 있습니다.
색상은 고정 값보다 테마 속성을 우선 사용하는 것이 좋습니다.
ColorStateList로 선택 상태와 비활성화 상태를 관리할 수 있습니다.
TextAppearance와 dimens.xml로 타이포그래피와 간격을 통일합니다.
MaterialButton과 MaterialCardView에 공통 스타일을 적용할 수 있습니다.
동적 색상은 Android 12 이상에서 선택적으로 활용할 수 있습니다.
Product Flavor별로 브랜드 색상을 분리할 수 있습니다.
다크 모드와 접근성 테스트는 디자인 시스템의 필수 검증 과정입니다.
기존 Android 앱을 개선하려면 먼저 XML에 직접 지정한 색상과 텍스트 크기를 조사하는 것부터 시작하는 것이 좋습니다.
그다음 공통 테마와 스타일을 정의하고, 화면별로 점진적으로 적용하면 됩니다.
좋은 디자인 시스템은 화면마다 새로운 디자인을 만드는 것이 아니라, 동일한 규칙으로 일관된 사용자 경험을 제공하는 구조입니다.
참고 자료
Material Design 3
https://m3.material.io/Material Components for Android
https://github.com/material-components/material-components-androidAndroid Developers — Styles and Themes
https://developer.android.com/develop/ui/views/theming/themesAndroid Developers — Dark Theme
https://developer.android.com/develop/ui/views/theming/darkthemeAndroid Developers — ColorStateList
https://developer.android.com/reference/android/content/res/ColorStateListAndroid Developers — Dynamic Colors
https://developer.android.com/develop/ui/views/theming/dynamic-colorsAndroid Developers — Support Different Screen Sizes
https://developer.android.com/develop/ui/views/layout/responsive-adaptive-design-with-viewsAndroid Developers — Accessibility
https://developer.android.com/guide/topics/ui/accessibilityAndroid Developers — Edge-to-edge
https://developer.android.com/develop/ui/views/layout/edge-to-edge
태그: Android, Kotlin, XML, Material 3, Material Components, Design System, Android 테마, DayNight, 다크 모드, Dynamic Color, ColorStateList, MaterialButton, MaterialCardView, BottomNavigationView, TextAppearance, Android UI, Android 앱 개발