CSS 글꼴(Font) 이야기

CSS 글꼴

우리는 하루에도 수많은 글을 봅니다. 스마트폰으로 웹서핑을 하거나 책을 볼 때, 우연히 길에서 스쳐간 간판까지, 눈을 뜨고 있다면 의식적으로든, 무의식적으로든 글을 읽는 걸 피할 수 없죠.

글은 기본적으로 정보를 전달하지만 같은 글이라도 다양한 서체로 표현될 수 있습니다. 사람마다 글씨체가 모두 다른 것처럼 말이죠. 이에 따라 세상에는 굉장히 많은 글꼴이 개발되어 사용되고 있습니다. 애플의 스티브 잡스가 글꼴을 굉장히 중요하게 다루었다는건 굉장히 유명한 이야기입니다.

프로그래머인 저에게도 글꼴을 다룰일은 굉장히 많습니다. 개발하는 서비스에 따라 디자이너가 원하는 글꼴이 다르기 때문에 디자인 팀에서 선정한 글꼴을 적용해야합니다. 또한 개발환경을 세팅할 때 좋아하는 글꼴을 설치하고 이를 IDE에 적용하는 것은 마치 신성한 의식과도 같은 일입니다.

이번 글에서는 프로그래머로서 글꼴을 다룰 때 자주 마주치는 몇 가지 개념들에 대해 다루도록 하겠습니다.

세리프(Serif)와 산 세리프(Sans-Serif)

출처 https://www.w3schools.com

세리프(Serif)

세리프는 '장식’을 의미합니다. 위에 이미지에서 세리프 글꼴을 보면 선의 끝에 굴림 처리가 되어있고, 선의 굵기도 다른 특징을 가집니다. 한글로 치면 궁서체가, 영어로는 타임즈 뉴 로만(Times New Roman)이 대표적이죠. 보통 인쇄물에 많이 사용됩니다.

산 세리프(Sans-Serif)

Sans는 '없음’을 나타냅니다. 즉 세리프가 없다라는 의미가 되니 장식이 없다라고 받아들이면 되겠죠? 위의 이미지에서 산 쉐리프 서체를 보면 선의 굵기가 동일하고 장식이 없습니다. 한글 글꼴에서는 고딕 서체들을 떠올리면 됩니다. 주로 모니터 화면에서 보는 서체에 많이 사용됩니다.

참고로 디지털 세상에서 산 세리프 글꼴이 자주 쓰이게 된건 과거에는 디스플레이 기능의 제약상 곡선을 매끄럽게 표현하기 어려워 세리프 글꼴을 표현하기 어려웠던 이유가 있었다고 하네요.

세리프와 산 세리프의 차이

둘의 차이를 위에서 간단히 살펴 보았습니다. 표면적으로는 글꼴에 '장식’이 있는지 없는지가 핵심이지만 조금만 더 이 둘의 차이를 알아보도록 하겠습니다.

먼저 세리프와 산 세리프의 차이는 가독성(readability)과 판독성(legibility)을 가지고 볼 수 있습니다.

가독성은 많은 양의 글을 볼 때의 읽기 쉬운 정도를 나타내는데 일반적으로 신문과 같은 인쇄물에서는 쉐리프가, 모니터 화면 상에서는 산세리프가 가독성이 높다고 봅니다. 다만 이건 사람에 따라, 익숙한 정도에 따라 달라지는 주관적인 부분입니다.

판독성은 각각의 글자가 정확히 어떤 글자를 나타내는지를 인식할 수 있는 정도를 나타냅니다. 예를 들어 i와 l은 글꼴에 따라 매우 비슷하게 보일 수 있습니다. 프로그래머가 코딩을 할 때 가장 먼저 프로그래밍 하기 좋은 글꼴을 설정하는데 그 이유가 여기에 있습니다.

프로그래머가 사용하는 글꼴들은 대부분 이 판독성이 좋은 것들이 많습니다. 아래의 예는 네이버에서 만든 D2Coding 이라는 글꼴입니다. 이미지를 보시면 헷갈리기 쉬운 글자들이 구분되기 쉽도록 신경을 많이 쓴 것이 느껴 지시나요? 그리고 이런 노력들이 앞에서 이야기한 판독성을 높여줍니다.

d2coding

모노스케이프(Monoscape)

앞에서 세리프와 산 세리프에 대해 알아보았습니다. 그럼 두 번째. 글꼴을 이야기 할 때 모노스케이프는 무엇을 의미하는 걸까요?

답은 간단합니다. 모노스케이프 글꼴을 모든 글자의 가로길이가 같은 글꼴을 이야기합니다. 모노스케이프란 단어 자체가 고정너비라는 뜻이기도 합니다. 그럼 모노스케이프 글꼴은 언제 주로 사용할까요? 바로 프로그래밍을 할 때 가장 많이 선택 합니다. 글꼴이 고정너비여야만 코드가 동일한 간격으로 정렬되어 편집도 쉽고 보기도 좋기 때문이죠. 엄밀히 말하면 보통 모노스케이프이면서 산 세리프인 글꼴을 많이 사용합니다.

참고로 저는 코딩을 할 때 D2Coding이나 나눔고딕코딩 글꼴을 주로 사용하는데요. 이 두 글꼴은 한국에서 개발된 글꼴이라 한글도 잘 지원이 되고 무료이기까지 합니다.

아무래도 우린 한국 사람이니 코드를 짜다보면 주석이나, 코드 자체에도 한글이 들어갈 일이 생깁니다. 이럴 때 외국에서 만든 글꼴을 쓰면 한글이 예쁘게 나오지 않는 경우가 많은데(폰트에 한글 자체가 아예 안들어가 있기 때문에) D2Coding이나 나눔고딕코딩을 쓰면 이런 문제가 깨끗이 해결됩니다. 참고로 이 두 글꼴은 네이버에서 만든 글꼴입니다. 한때 제가 몸담았던 회사라 괜히 더 애정이 가네요. :)

d2coding

폰트 패밀리(Font Families)

글꼴은 우리가 글을 사용하며 함께 발전해 왔습니다. 어떤 운영체제를 사용하든 글꼴 설정에 들어가서 내가 사용할 수 있는 글꼴을 확인해 보면 그 숫자에 아마 놀라실겁니다.

이렇게 많은 글꼴중에 원하는 글꼴을 좀 더 쉽게 적용하려면 어떻게 해야할까요? 그리고 내가 사용하려고 하는 글꼴이 시스템에 설치가 되어있지 않다면 어떻게 해야할까요? 폰트 패밀리는 이런 문제를 해결해 줍니다.

아래는 CSS에서 폰트 패밀리를 설정하는 예제입니다. 아래 코드는 이렇게 읽을 수 있습니다.

시스템에 Verdana 글꼴이 있으면 Verdana를, 없으면 Arial을, Arial도 없으면 산 세리프 글꼴 중에 하나를 사용해주세요.

1
font-family: Verdana, Arial, sans-serif;

즉 글꼴이 없는 경우를 대비해서 좌측에서부터 순서대로 예비 글꼴을 지정할 수 있는 것이죠. 이때 특정 글꼴을 지정하거나, 앞서 우리가 배웠던 글꼴의 형태를 지정해 줄 수 있습니다. 위의 예에서는 산 세리프(sans-serif) 부분이 이에 해당합니다.

마무리

이번 글에서는 프로그래머로서 글꼴을 다룰 때 알아야할 아래의 세 가지 주요 개념에 대해 알아보았습니다.

  1. 세리프와 산 세리프
  2. 모노스케이프
  3. 폰트 패밀리

세리프와 산 세리프, 모노스케이프는 글꼴에 대한 전반적인 개념이고 폰트 패밀리는 CSS에서 글꼴을 지정하는 방식입니다. 폰트 패밀리는 CSS에서 대표적으로 사용되지만 다른 언어나, 툴 등에서도 자주 사용됩니다. 다만 사용되는 곳에 따라 구체적인 사용법은 조금 다를 수 있습니다. 하지만 우리는 개념을 알고 있으니 문서를 보면 금방 이해하고 적용할 수 있겠죠?

테마(Theme)를 이용해서 다양한 모습의 안드로이드 앱 만들기

멋쟁이들은 계절이나 분위기에 따라 다양한 색의 옷으로 스스로를 표현합니다. 아름다운 자연도 계절에 따라 완전 다른 모습으로 우리의 눈을 사로잡습니다.

최근 Android, iOS에 많은 화제가 되고 있는 다크 모드(Dark Mode)도 비슷합니다. 모드에 따라 하나의 앱이 밝고, 어두운 두 가지 모습을 자유자재로 오갑니다.

안드로이드에서는 테마(Theme)를 이용해서 이를 구현할 수 있습니다. 계절에 따라 모습이 바뀌는 자연을 생각하면 테마는 계절이라고 생각할 수도 있고, 기분에 따라 옷의 컬러를 선택하는 사람에겐 기분이 테마가 될 수 있습니다.

안드로이드의 다크 모드라는 것도 결국은 시스템이 '다크 모드(Dark mode)'일 때 이에 맞는 미리 만들어둔 테마를 적용시켜주는 것에 불과합니다.

아래 이미지는 안드로이드 공식 페이지에서 테마에 대해 설명하는 이미지입니다. 동일한 액티비티에 서로 다른 테마를 적용한 모습으로, 테마에 따라 액티비티가 완전히 다른 느낌으로 변하는 모습을 볼 수 있죠.

Two themes applied to the same activity: Theme.AppCompat (left) and Theme.AppCompat.Light (right)

그럼 이번 포스팅에서는 테마에 따라 다양한 모습으로 변신하는 앱을 어떻게 만들 수 있는지에 대해 이야기하도록 하겠습니다.

테마의 선언과 구성

앞에서 테마에 대해 개념적으로 설명을 드렸지만 우리는 개발자이니 좀 개발자의 관점에서 테마를 살펴보도록 하겠습니다. 테마는 아래와 같이 res/values/style.xml에 선언합니다. 여기서 파일 명은 원하는 대로 작성하셔도 됩니다.

1
2
3
4
5
6
7
8
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorImagePlaceHolder">@color/GR80</item>
</style>
</resources>

일단 기본적으로 테마는 키, 값의 쌍으로 이루어진 맵과 같이 생각할 수 있습니다.

더 위에 style 태그를 보면 마치 클래스 처럼 parent를 통해 다른 테마를 상속 받을 수도 있습니다. 위의 예에서는 AppTheme이라는 테마가 Theme.MaterialComponents.DayNight.NoActionBar라는 테마를 상속받아 선언되고 있습니다. 다른 테마를 상속 받으면 새로 정의한 테마는 parent 테마의 모든 속성을 동일하게 갖게되고 기존의 속성을 오버라이드할 수 있습니다.

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">

이제 가장 중요한 내용입니다.

테마에 있는 속성들은 컬러와 같은 디자인 리소스에 의미있는 이름을 붙인 것 입니다. 예를 들어 위에 colorAccent은 안드로이드의 뷰가 가진 속성이 아닙니다. 그리고 colorAccent는 이름에서 보이듯이 이름에서 어떤 특정 색을 표현하지 않고 '강조를 나타내는 색’이라는 의미를 더 강조하고 있습니다.

즉 테마에 따라 colorAccent를 다르게 적용할 수 있음을 표현하는 것입니다. 꼭 인터페이스와 구현체의 관계와 비슷합니다. colorAccent라는 속성(인터페이스)은 서로 다른 테마(구현체)에서 다른 컬러로 표현될 수 있는 것입니다.

머티리얼 디자인에 포함된 주요 테마 속성

테마는 내가 직접 정의해서 사용할 수도 있고 안드로이드 플랫폼에 정의되어 바로 사용할 수 있는 것들도 있습니다. 아래 이미지는 롤리팝과 함께 머티리얼 디자인이 소개되면서 많이 쓰인 이미지입니다.

Material theme attributes

위에 보이는 colorPrimary, textColorPrimary, colorPrimaryDark, windowBackground, navigationBarColor와 같은 속성들은 머티리얼 테마(Material Theme)에 미리 정의된 것으로 보시는 것처럼 시스템 UI에 미리 사용되고 있고 앱에서 얼마든지 이를 재정의(Override)해서 사용이 가능합니다. 즉 앱에서 사용하는 테마에서 colorPrimaryDark를 노란색으로 적용하면 위에 위에 그림에 있는 상태바(Status bar)의 색은 노란색으로 변경될 것입니다.

그렇다면 이게 어떻게 가능할까요? 바로 안드로이드도 플랫폼에서 상태바의 색상에 특정 컬러를 하드코딩 한게 아니라 colorPrimaryDark라는 테마의 속성을 참조하도록 해두었기 때문입니다.

아래는 머티리얼 테마를 상속받아서 앱에서 직접 사용할 테마를 AppTheme라는 이름으로 선언해서 사용하는 예입니다. 아래 예에서 android:windowBackground나 android:windowActivityTransitions와 같은 속성은 안드로이드 프레임워크에 있는 테마이고, colorPrimary, colorPrimaryDark, colorAccent는 머티리얼 테마에 선언된 속성입니다.

그리고 마지막에 있는 colorImagePlaceHolder는 제가 직접 만든 속성입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
// Attributes from material theme
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>

// Attributes from android framework
<item name="android:windowBackground">#ffffff</item>
<item name="android:windowActivityTransitions">true</item>

// Attributes that I've defined
<item name="colorImagePlaceHolder">@color/GR80</item>
</style>
</resources>

colorImagePlaceHolder와 같이 기존에 없는 속성을 선언하고 싶으면 res/values/values.xml 파일에 아래와 같은 방식으로 선언하면됩니다. 보시면 좌측에 속성이 가질 리소스 포맷을 적고 name에 사용할 속성 명을 적어주면 됩니다.

1
2
3
4
5
6
<resources>
<attr format="color" name="colorImagePlaceHolder"/>
<attr format="color" name="colorDivider"/>
<attr format="color" name="mildBackgroundColor"/>
...
</resources>

테마에 적용된 값을 사용하기

자 이제 테마의 개념부터, 종류, 선언하는 방법까지 알아봤으니 어떻게 사용하는지를 알아봅시다. 테마를 사용하는 가장 기본적인 방법은 ?attr/[attributeName] 을 사용하는겁니다. 구체적으로 아래와 같이 사용할 수 있습니다.

1
2
3
4
5
6
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar"
app:titleTextColor="?attr/titleTextColor"
app:titleTextAppearance="@style/ToolbarTitle"
tools:title="Toolbar" />

툴바의 텍스트 컬러를 지정하는 부분을 보시면 아래처럼 ?attr을 사용하고 있죠.

?attr/titleTextColor

?attr은 테마에서 attr 뒤에 있는 속성 이름으로 값을 찾아오라는 의미입니다. 예를 들어 위의 예에서는 현재 나에게 적용된 테마에서 titleTextColor라는 이름으로 정되된 컬러값을 가져오라는 뜻입니다.

앱에 현재 LightTheme과 DarkTheme가 있다고 합시다. 현재 이 툴바가 LightTheme를 적용받고 있고 LightTheme에 titleTextColor가 흰색이라면 툴바의 텍스트는 흰색이 될 것이고, DarkTheme를 적용받고 있고 여기에 titleTextColor가 검정이라면 툴바의 텍스트는 검정이 됩니다.

결국 테마를 통해 화면의 모습을 다이나믹하게 변경하는 것은 내가 변경하고자 하는 UI 요소의 컬러를 최대한 ?attr을 이용해서 테마의 속성을 간접 참조(indirect reference)로 설정하고, 사용자의 선택이나, 환경에 따라 테마를 변경하는 것입니다. (이 포스팅 전체에서 가장 중요한 내용인거 같네요.)

테마의 적용

그렇다면 위의 예에서 저 툴바에 적용되는 테마는 어떻게 결정 될까요? 테마는 컨텍스트에 적용이 됩니다. 자 그럼 안드로이드 플랫폼에서 컨텍스트를 갖는게 어떤게 있을까요? View, ViewGroup, Activity, Application 등이 있겠죠. 테마는 컨텍스트에 적용이 되기 때문에 여기에 모두 적용이 됩니다.

또한 테마는 범위를 갖는데 상위레벨에 적용한 테마는 하위 레벨까지 모두 적용이 되고 하위레벨에 적용된 테마가 상위레벨에 적용된 테마에 우선해서 적용됩니다.

  1. 어플리케이션 레벨로 적용하기
    androidManifest.xml
1
2
3
4
5
6
7
8
<manifest
...>
<application
...
android:theme="@style/AppTheme">
...
</application>
</manifest>
  1. 액티비티에 적용하기
    androidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
<manifest
...>
<application
...>
<activity
...
android:theme="@style/SplashTheme">
</activity>
...
</application>
</manifest>
  1. 뷰그룹 및 뷰에 적용하기
1
2
3
4
5
6
7
8
9
10
11
<LinearLayout
...
android:theme="@style/Theme.MaterialComponents.Light">
<TextView
...
/>
<TextView
...
android:theme="@style/ThemeOverlay.MaterialComponents.Dark"
/>
</LinearLayout>

xml에서 각 테마는 android:theme를 통해 적용이 가능하고 적용하고자 하는 범위에 따라 androidManifest.xml의 application 태그나, activity 태그 또는 레이아웃 파일에서 각 뷰그룹 또는 뷰에 적용이 가능합니다.

코드에서 테마에 정의된 속성 값 읽어오기

앞서 테마는 컨테스트에 적용이 된다고 이야기했습니다. 그럼 테마에 엮여있는 특정 속성의 값을 코드에서 가져오려면 어떻게 할까요?
(xml이였다면 ?attr/[attributeName]을 썼겠죠)

아래는 컨텍스트에 엮여있는 컬러값을 가져오는 예제입니다.

1
2
3
4
5
6
7
8
@ColorInt
fun Context.getThemeColor(@AttrRes themeAttrId: Int): Int {
return obtainStyledAttributes(
intArrayOf(themeAttrId)
).use {
it.getColor(0, Color.TRANSPARENT)
}
}

현재 액티비티의 테마에 있는 colorPrimary를 가져오려면 액티비티 내에서 getThemeColor(R.attr.colorPrimary)와 같이 할 수 있을 것이고 특정 뷰그룹에 적용된 테마에 대한 컬러를 가져오려면 targetViewGroup.getContext().getThemeColor(R.attr.colorPrimary)와 같이 사용할 수 있겠죠.

테마의 사용

자 그럼 위에서 알아본 테마의 개념이 어떤식으로 사용될 수 있을까요? 아래의 UI를 예로 들어 봅시다. 이 앱의 처음 디자인은 좌측의 밝은 색 위주의 디자인이였는데 어떤 과정을 거쳐서 우측과 같이 어두운 테마를 적용할 수 있었을까요?

Android Theming

이를 위해서는 아래와 같은 단계를 밟아가면 됩니다.

  1. 테마에 따라 색상이 변하는 부분과 변치 않을 부분을 나눠봅니다.
  2. UI에서 컬러가 하드코딩 되어있는 부분을 찾고 테마의 어떤 속성을 사용할지 결정합니다. 이때는 이미 시스템에 정의된 테마의 속성을 사용할지, 새로운 속성을 만들지 결정합니다.
  3. 새로운 테마를 만들고 이 테마에서 변화를 줄 속성을 오버라이드합니다.
  4. UI에서 변경이 필요한 부분의 컬러를 테마의 속성을 참조하도록 ?attr/을 이용하여 변경합니다.

테마에 따라 색상이 변하는 부분과 변치 않을 부분을 나눠보기

위의 예제에서 변하는 부분과 변치 않는 부분을 나눠봤습니다.

변하는 것

  • 화면의 배경
  • 툴바의 배경
  • 쿨바의 텍스트
  • 리스트 내의 카드 배경
  • 리스트 내의 카드 텍스트
  • 바텀네비게이션 배경
  • 바컴네비게이션 아이콘
  • 탭레이아웃 배경

변하지 않는 것

  • 탭레이아웃 인디케이터 및 선택된 탭의 텍스트
  • 리스트 내 카드의 재생시간 뱃지

변하는 부분과 변치 않는 부분을 나눴으면 각각에 어떤 테마의 속성을 사용할 수 있을까요? 모두 새로 선언해도 되지만 이미 선언돼있는 다양한 속성들이 있으니 이것들을 먼저 확인해봅니다.

아래 링크에는 머티리얼 디자인 시스템에 이미 선언돼있는 다양한 속성들에 대한 설명이 있습니다.

Android Styling: Common Theme Attributes

이 글에서는 컬러에 대한 내용을 주로 다루고 있으니 몇 가지 컬러 관련 속성을 보겠습니다.

  • ?attr/colorPrimary : 앱의 메인 브랜딩 컬러
  • ?attr/colorSecondary : 앱의 세컨 브랜딩 컬러로 주로 메인 브랜딩 컬러를 보완하는 좀 더 밝은 색상으로 사용
  • ?attr/colorOn[Primary, Secondary, Surface etc] : 각 컬러에 대한 대비를 이루는 색입니다. 예를 들어 배경에 ?attr/colorPrimary를 쓰고, 여기위에 다른 UI 컴포넌트를 올릴 때 colorOnPrimary를 사용하면 이 컴포넌트를 좀 더 눈에 띄게 강조 할 수 있겠죠.
  • ?attr/colorSurface : 카드(cards), 시트(sheets), 메뉴(menues)와 같은 컴포넌트의 표면 색을 나타냅니다.
  • ?attr/colorBackground : 스크린의 백그라운드 색
  • ?attr/colorError : 에러를 표시할 때 사용할 색

아래 이미지는 material.io 페이지에 있는 The baseline Material color theme로, 위의 컬러를 보통 어떤식으로 선택하는지에 대한 예시를 보여주고 있습니다. 참고로 material.io 페이지에 가면 대비가 되는 색을 선택하거나, 위에 있는 머티리얼 테마에 사용되는 속성값들을 어떤 식으로 결정하면 좋을지에 대한 가이드와 툴이 잘 나와있습니다.

The baseline Material color theme

다시 아래 목록으로 돌아와서 위에 있는 속성들을 참고하면 저는 아래와 같이 테마의 속성을 정의할 수 있을거 같습니다. 이걸 바탕으로 각 UI 컴포넌트에 하드코딩 되어있던 컬러를 ?attr를 이용해서 바꿔줍니다.

Choosing theme attributes

이제 위의 속성들을 각 테마에 따라 정의합니다.

밝은 테마

1
2
3
4
5
6
7
<style name="LightTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorSurface">@android:color/white</item>
<item name="backgroundColor">@android:color/white</item>
<item name="android:windowBackground">@color/gray_bg</item>
<item name="titleTextColor">@android:color/black</item>
...
</style>

어두운 테마

1
2
3
4
5
6
7
<style name="DarkTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorSurface">@color/gray_900</item>
<item name="backgroundColor">@android:color/black</item>
<item name="android:windowBackground">@android:color/black</item>
<item name="titleTextColor">@android:color/white</item>
...
</style>

이제 감이 오시나요? 위의 속성들을 재정의 하는 테마를 추가하기만 하면 이제 얼마든지 분위기가 확 다른 앱을 만들 준비가 된 것이죠.

그럼 이제 이런 테마를 어떻게 적용하면 될까요? 아까 위에 기술 했던대로 activity 또는 application 단위로 androidManifest의 android:theme 태그를 이용해 정의하면 됩니다. 이걸 동적으로 하고 싶다구요? 그럼 간단히 테마를 선택할 수 있는 다이얼로그를 하나 만들고 테마를 선택하면 이걸 Preference 같은 곳에 저장하고, 액티비티가 시작 시 setContentView 이전에 테마를 설정해주면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userSelectedPref = getThemeFromPref()
when(userSelectedPref) {
"light" -> setTheme(R.style.LightTheme)
"pink" -> setTheme(R.style.PinkTheme)
"dark" -> setTheme(R.style.DarkTheme)
else -> setTheme(R.style.LightTheme)
}
setContentView(R.layout.activity_main)
...
}

테마는 뷰가 인플레이트 되기 전에 설정이 되고 인플레이트 후에는 변경해도 UI에 업데이트가 반영이 되지 않기때문에 setOntentView 이전에 해주어야합니다.

다크 모드

그럼 다크모드는 뭘까요? 다크모드는 안드로이드 9에서부터 지원되는 시스템 설정입니다. 즉 앱이 아니라 안드로이드 시스템에서 설정 가능한 모드입니다. 앱에서 다크모드를 지한한다는 것은, 사용자가 시스템의 다크모드를 적용하면 이에 따른 어두운 테마를 만들어 두었다가 이게 적용되도록 해주면 됩니다. 간단하죠?

이 부분은 이 글의 내용을 이해했다면 간단히 적용이 가능하지만 이 글에서 다루면 너무 길어질 것 같아서 다음 포스팅에서 따로 다루도록 하겠습니다.

마무리

저는 디자인과는 좀 거리가 있어보이는 개발자입니다. 노트북이 들어간 백팩과 체크무늬 셔츠가 가장 편안합니다. 하지만 그럼에도 제가 만든 앱은 어디서나 돋보였으면 좋겠고 보기에 좋았으면 좋겠습니다. 또한 저는 잘 다듬어진 디자인은 그 자체로 유저에게 큰 가치를 준다고 생각합니다. 이 글을 보신 분들도 테마를 이용해서 보다 다양한 느낌의 앱을 앱을 만드는데 도움이 되셨으면 합니다.

참고

테마에 관련된 글들은 주로 안드로이드 엔지니어인 Nick Butcher의 유튜브 영상과 미디엄 글을 많이 참고했습니다. 또한 material.io에 있는 페이지들도 개발자에게 디자인과 관련된 컴포넌트나 개념을 잡는데 도움이 많이 되었습니다.

마지막으로 안드로이드의 테마를 이야기하면 항상 스타일(Style)에 대한 이야기를 보통 함께 합니다. 이 둘은 상당히 비슷하면서 다른데 개인적으로는 두개를 같이 다루면 오히려 개념을 잡는데 헷갈리기 때문에 스타일에 대한 이야기는 최대한 배제하였습니다. 하지만 테마를 이해했다면 스타일에 대한 부분도 꼭 짚고 넘어가면 좋을 것 같습니다.