해당 글은 안드로이드 개발자 공식 미디엄의 Fixing Font Padding in Compose Text 포스팅을 번역한 것으로, 의역 및 오역이 다수 존재할 수 있습니다. 열람에 유의하세요!
Compose
버전 1.2.0의 PlatformTextStyle
API를 사용하여 includeFontPadding
을 제어할 수 있습니다.Compose
에서는 includeFontPadding
의 기본값이 true
지만, 차기 릴리즈에서는 false
로 변경될 것이고, 결과적으로 PlatformTextStyle
API는 제거될 예정입니다.includeFontPadding
을 false
로 설정된 상태에서 UI를 테스트하고, 주어진 디자인에 맞게끔 조정할 수 있습니다.LineHeightStyle
API를 사용하면, 주어진 디자인에 맞도록 쉽게 조정할 수 있습니다.Jetpack Compose
라는 새로운 UI 툴킷을 처음부터 개발하면서, 과거에 내린 선택을 다시 고려해볼 수 있는 기회를 맞이할 수 있었습니다. 해당 게시물에서는, 기존의 View
시스템은 물론, 현재의 Compose
시스템까지 어떻게 텍스트를 정렬하는지에 대해 알아보도록 할 것입니다.
그리고, 텍스트 렌더링 시 오류 방지 기능을 강화하고, 피그마나 스케치와 같은 디자인 툴에서 제공한 디자인을 더 쉽게 구현하는 데 도움이 될 수 있는 font padding
에 대해 자세히 알아보도록 합시다.
새로운 API로 마이그레이션 하는 과정은 많은 이로움을 부여하지만, 약간의 디자인이나 스크린샷 테스트를 진행하는 데 있어 불일치를 야기할 수 있는 과정이기도 합니다. 하지만 걱정하지 마세요! API를 활용해 텍스트를 어떻게 구현하는지부터 시작하여 애플리케이션에 가장 빠르게 마이그레이션 하는 방법을 보여주도록 할테니까요.
includeFontPadding
은 텍스트의 첫 번째 줄과 마지막 줄의 Line height
를 효과적으로 제어할 수 있는 TextView
의 파라미터입니다. 보통 이 기능을 키면 세로(Vertical) 패딩이 추가되면서 높이가 높아집니다.
기본값은 true
이며, 다음과 같이 오버라이딩할 수 있습니다.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
<TextView
android:id="@+id/hola_mundo"
android:text="@string/hola_mundo"
. . .
android:includeFontPadding="false"
/>
다음 예제 이미지를 보면 이해가 쉬울겁니다.
includeFontPadding
속성이 부여된 것이 오른쪽, 부여되지 않은 것이 왼쪽입니다.
includeFontPadding
이 false
인 경우, 첫 줄과 마지막 줄의 줄 높이는 (desecnt - ascent)
와 같습니다.
includeFontPadding
이 true
일 때는 흥미롭게도 다음과 같이 동작합니다.
Line height
는 (descent - top)
과 같습니다.Line height
는 (bottom - ascent)
와 같습니다.Line height
는 (bottom - top)
입니다.inner line
의 경우 Line height
는 항상 (descent - ascent)
입니다.top
, ascent
, descent
는 font metrics
의 예로, 이러한 metrics
는 시스템이 균등하게 간격을 유지할 수 있도록 폰트에 대한 정보를 제공하고, 텍스트 줄을 균등하게 정렬하는 역할을 맡습니다. 이는 보통 폰트마다 고유하게 지정되어 있으며, FontMetrics API나 ttx 와 같은 툴을 사용해 검색할 수 있습니다.
includeFontPadding
을 true
혹은 false
로 설정함으로써 부모 컨테이너에서 텍스트가 정렬되는 방식에 영향을 미치고, 이로 인해 정렬에 미묘한 차이가 발생할 수 있습니다.
안드로이드 API 30의 LinearLayout
에 나란히 정렬된 두 TextView
의 기본 Baseline
값을 볼 수 있습니다. includeFontPadding
속성이 부여된 것이 오른쪽, 부여되지 않은 것이 왼쪽입니다.
마찬가지로 안드로이드 API 30의 LinearLayout
에서, layout_gravity
속성을 center_vertical
로 부여하였습니다. 텍스트의 Baseline
대신 뷰의 높이를 기준으로 정렬됩니다. 왼쪽 케이스의 경우 가운데에 미묘하게 공간이 발생한 것을 확인할 수 있습니다. includeFontPadding
속성이 부여된 것이 오른쪽, 부여되지 않은 것이 왼쪽입니다.
includeFontPadding
을 true
로 구성한 것으로 인해 원하는 결과를 얻지 못하는 상황이 발생하고 말았습니다. 텍스트에 패딩을 추가하는 이 과정은 특정 디자인 가이드라인을 준수하기 어려워지게 만들 수 있습니다. 하지만 패딩을 포함하게 된 이유를 알아보려면 과거를 돌아보아야 합니다.
Roboto 는 안드로이드 플랫폼의 표준 타입페이스이자 기본 폰트입니다.
폰트를 그리는 데 사용할 수 있는 최대 공간을 계산하기 위해 안드로이드 27까지 Roboto 의 Font metrics
를 사용해왔습니다. 사용 가능한 공간의 값을 계산하고, 해당 공간을 텍스트를 “그리기 안전한 공간” 으로 취급해왔습니다.
이 솔루션은 일반적으로 라틴어 텍스트보다 긴 디자인이나 스트립트가 있는 글꼴이 클리핑되었기 때문에 모든 폰트에 대해 동작하지 않았습니다. 여기서 클리핑은, 문자의 일부가 사용 가능한 공간에 완전히 표시되지 않음을 의미합니다. 이러한 예로 버마어를 사용하도록 하겠습니다. 샘플 텍스트는 ၇ဤဩဦနိ. 입니다.
위의 버마어와 같은 문자가 잘리는 것을 방지하기 위해, includeFontPadding
을 도입해 텍스트 상단과 하단 패딩을 추가했습니다. 어떻게 동작하는지 예제를 볼까요?
왼쪽이 includeFontPadding
이 false
로 설정된 상태, 오른쪽이 true
로 설정된 상태입니다. 왼쪽의 경우 문자가 잘린 것을 확인할 수 있습니다.
따라서, includeFontPadding
을 false
로 설정한 상태에서 버마어를 지원하는 경우 안드로이드 버전에 따라 폰트가 잘리는 현상이 발생할 수 있습니다. 왜 includeFontPadding
의 기본값이 true
가 됐는지 알겠죠?
세로 정렬 문제를 해결하거나 특별한 디자인 가이드라인을 준수하기 위해 includeFontPadding
을 false
로 설정해야 하는 경우가 있습니다. 아래 두 예제를 알아보도록 합시다.
버튼과 같은 부모 위젯에서 텍스트를 중앙에 배치할 때 세로 기준 중앙으로 배치될 것을 예상할 수 있습니다. 아래 이미지와 같이요.
H를 기준으로 수평선을 그어보면 세로 기준 중앙에 그려질 것이라고 예상되지만, includeFontPadding
이 true
로 설정된 경우 Font metrics
의 기준에 따라 정렬이 달라지게 됩니다. 아래보다 위쪽에 패딩이 더 많은 폰트에서 어떤 일이 일어나는지 볼까요?
includeFontPadding
이 false
인 것이 왼쪽, 오른쪽이 true
인 상태입니다. 기본적으로 부여된 패딩으로 인해 오른쪽의 정렬이 어긋난 것을 확인할 수 있습니다. 사용된 폰트와 텍스트에 따라 이 차이는 더 심해질 수 있습니다.
또 다른 예시로 이미지와 같은 뷰에서 상단이나 하단에 텍스트를 정렬해야 하는 경우입니다.
includeFontPadding
이 true
인 경우 텍스트와 이미지가 정렬되지 않습니다. 아래 원하는 디자인을 살펴보도록 합시다.
위와 같은 결과를 얻으려면, includeFontPadding
을 false
로 설정하고, 일부 패딩을 하드 코딩해야 합니다.
TextView
위젯의 경우 StaticLayout
, BoringLayout
위에 구축되며, 화면에 표시할 텍스트의 특성에 따라 둘 중 하나로 위임됩니다.
안드로이드 28 버전부터 우리는 StaticLayout
에 useLineSpacingFromFallbacks
기능을 추가하였습니다. 따라서, Roboto 의 Font metrics
대신 실제 사용된 폰트를 기반으로 텍스트의 ascent
, descent
가 조정됩니다. 이로 인해 includeFontPadding
이 false
로 지정되어도 폰트가 잘리는 문제를 해결할 수 있었습니다.
또한 includeFontPadding
에서 해결하지 못했던 연속적 라인의 충돌 문제도 해결할 수 있습니다. 하지만 이 기능은 BoringLayout
에 추가되지 않았으므로, 여전히 클리핑이 발생할 수 있습니다. 이에 대한 서포트가 최근에 추가되어 Android T와 함께 제공될 예정입니다.
(BoringLayout)
→ 한 줄로 이루어졌으며, 왼쪽에서 오른쪽으로 문자열이 이어지는 텍스트를 담는 레이아웃
API 33부터 클리핑 문제가 자동적으로 처리되기 때문에,
includeFontPadding
은 더 이상 요구되지 않습니다. 하지만minSdk
가 33이 될 때까지includeFontPadding
을true
로 설정하여, 꼬리형 문자의 클리핑을 방지해야 합니다.
안드로이드 API 33버전에 적용된 왼쪽이 includeFontPadding
이 false
, 오른쪽이 true
인 상태입니다. 버마어 문자지만 클리핑이 일어나지 않는 것을 확인할 수 있습니다.
includeFontPadding
은 fallbackLineSpacing
뿐 아니라 elegantTextHeight
와도 사용됩니다. 이 두 파라미터의 조합으로 복잡한 동작이 발생합니다.
유연성을 더하기 위해 firstBaselineToTopHeight
및 lastBaselineToBottomHeight
를 구성할 수 있고, 각각 top line
과 bottom line
에 추가 패딩을 정의하는 데 사용됩니다. 두 속성은 API 레벨 28 이상에서만 적용할 수 있습니다.
여러 파라미터를 사용하면
TextView
에서 폰트 패딩을 수정할 수 있고, 복잡한 방식으로 상호 작용할 수 있지만 API 수준에 따라 동작이 달라질 수 있습니다. 항상 다양한 수준의 API에서 UI 테스트를 진행하도록 합시다.
Compose
역시 초기 Text
를 구현할 때 TextView
와 마찬가지로 기본적인 패딩이 추가되었습니다. 하지만, 해당 패딩에 대해 제어할 수 있는 파라미터의 사용을 제한하였죠. 커뮤니티는 이 문제를 매우 빠르게 지적했고, Text
컴포저블을 사용할 때 includeFontPadding
의 조절을 가능하게 해달라고 요청했습니다.
Compose
에서 이 기능을 구현하며 includeFontPadding
이 사용되는 방식과 이를 해결하는 주요 사례를 분석하였습니다. 우리는 개발자가 기대했던 대로 텍스트가 렌더링되어야 한다는 사실을 인식했습니다. 또한, includeFontPadding
이 매우 안드로이드 중심적인 구성이며, 이는 기본 제공되는 API에서 제외되어야 한다는 결론에 도달했습니다. 이 파라미터는 전체적으로 혼란을 초래할 수 있으며 사용 시 오류가 발생하기 쉽다는 특징을 가지고 있었습니다.
고로 우리는 아래 두 가지 목표를 세웠습니다.
그래서 다음과 같은 변경사항을 발표했습니다.
includeFontPadding
을 키고 끌 수 있는 새로운 API인 PlatformTextStyle
을 만들었습니다.includeFontPadding
을 비활성화했을 때 클리핑 문제가 발생하지 않도록 하기 위해, 필요할 경우 first line
과 last line
에만 추가적 패딩을 적용하고, 최대 line height
는 모든 텍스트의 폰트를 기준으로 계산하도록 구현하였습니다.Compose 1.2.0-alpha07
버전에서 TextStyle/PlatformTextStyle
에 includeFontPadding
을 제어할 수 있는 API를 제공합니다. 기본값은 true
입니다.
API는 exprerimental
, deprecated
된 것으로 표시되며, 호환성 목적으로만 사용됩니다. 향후 릴리즈에서는 includeFontPadding
의 기본값을 false
로 설정할 것이고, 궁극적으로는 호환성 API를 제거할 예정입니다.
무엇보다 각 Text
컴포저블별로 구성할 수 있으므로 마이그레이션을 점진적으로 진행할 수 있다는 장점이 있습니다.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@OptIn(ExperimentalTextApi::class)
/* ... */
Text(
text = myText,
style = TextStyle(
lineHeight = 2.5.em,
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
/* ... */
)
)
이렇게 타이포그래피 자체에도 적용할 수 있습니다.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@OptIn(ExperimentalTextApi::class)
val Typography = Typography(
body1 = TextStyle(
fontFamily = /* ... */,
fontSize = /* ... */,
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
/* ... */
)
MaterialTheme(
typography = Typography,
/* ... */
)
다만 이 API는 실험적이기 때문에 사용할 곳에 ExperimentalTextApi
어노테이션을 설정해주어야 합니다. 어노테이션을 달아주는 것이 싫다면 Gradle
의 KotlinOptions
의 별도의 설정을 추가할 수 있겠습니다.
kotlinOptions {
...
freeCompilerArgs += ["-Xuse-experimental=androidx.compose.ui.text.ExperimentalTextApi"]
}
우리는 변경 사항의 영향을 지속적으로 평가하고, 커뮤니티의 피드백을 받아들이면서 Compose
텍스트가 더 쉽게 디자인을 일치시킬 수 있는 API를 개발하였습니다.
LineHeight
의 정의는 텍스트 baseline
사이의 거리입니다.
텍스트의 첫 줄과 마지막 줄은 각각 위나 아래에 baseline
이 존재하지 않습니다. 따라서, 각 라인이 동일한 Line height
를 가지려면 주어진 기준에 따라 각 라인에 대한 공간을 추가하고 분산해주어야 합니다. 이 기준은 LineHeightStyle
을 통해 구성할 수 있습니다.
LineHeightStyle
은 텍스트의 각 줄에서 Line Height
를 분배하는 방법과, Line Height
를 첫 줄과 마지막 줄에 적용할지에 대한 여부를 제어합니다. TextStyle(lineHeight)
에서 제공하는 공간의 줄 정렬을 정의하고, 사용 가능한 공간 내에서 텍스트의 동작을 수정할 수 있는 여러 옵션을 제공합니다.
다음 예제를 볼까요?
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@OptIn(ExperimentalTextApi::class)
/* ... */
Text(
text = "Hi",
style = LocalTextStyle.current.merge(
TextStyle(
lineHeight = 2.5.em,
platformStyle = PlatformTextStyle(
includeFontPadding = false
),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
)
)
)
)
}
참고 :
LocalTextStyle
의merge
를 사용해TextStyle
을 정의하는 것이 더 좋습니다. 해당 텍스트를 버튼의 컨텐츠로 추가하는 경우, 기본 매터리얼 테마의 스타일이 손상될 수 있기 때문입니다. 자세히 알고 싶다면 이 링크를 확인해보세요.
LineHeightStyle.Alignment
는 제공된 공간 속에서 텍스트를 정렬하는 방법을 정의합니다.
LineHeightStyle.Alignment.Proportional
은 기본값이며, Font metrics
에 정의된 대로 줄 높이를 분배합니다.
LineHeightStyle.Alignment.Center
은 각 텍스트 줄의 위, 아래에 같은 양의 공간을 추가하고, 텍스트를 중앙에 배치시킵니다.
LineHeightStyle.Alignment.Top
은 각 텍스트 줄의 아래쪽에 공간을 추가하고, 텍스트를 위쪽으로 밀어냅니다.
LineHeightStyle.Alignment.Bottom
은 각 텍스트 줄의 맨 위에 공간을 추가하고, 텍스트를 아래로 밀어냅니다.
LineHeightStyle.Trim
옵션을 사용하면, 첫 줄의 상단과 마지막 줄 하단에 추가 패딩을 제어할 수 있습니다.
LineHeightStyle.Trim.FirstLineTop
은 첫 줄의 위에 추가되는 공간을 잘라냅니다.
LineHeightStyle.Trim.LastLineBottom
은 마지막 줄의 밑에 추가되는 공간을 잘라냅니다.
LineHeightStyle.Trim.None
은 공간을 제거하지 않습니다.
LineHeightStyle.Trim.Both
은 첫 줄의 위, 마지막 줄의 아래에 추가되는 공간을 잘라냅니다.
참고 : 해당 기능은 텍스트에
Line height
가 정의된 경우에만 적용되고,trim
의 경우PlatformParagraphStyle.includeFontPadding
이false
인 경우에만 사용할 수 있습니다.
lineHeight
와LineHeightStyle
과 함께includeFontPadding
은 주어진 폰트에서 텍스트를 효과적으로 정렬하고, 스타일을 지정할 수 있는 여러 조합을 생성해냅니다. 위에서 소개한 관련 클래스에 대한 문서를 확인하고, 디자인에 가장 잘 맞는 옵션을 선택해보세요.
Compose 1.2.0 베타 버전에서 includeFontPadding
옵션을 자유롭게 사용할 수 있으며, 당장 Text
를 조정하고 테스트를 시작할 수 있습니다. PlatformTextStyle
호환성 API는 향후 릴리즈에서 계속 사용할 수 있긴 하지만, 기본 옵션으로includeFontPadding
을 해제하도록 변경할 예정입니다. 또한 디자인을 더 쉽게 일치시키는 데 도움이 되는 LineHeightStyle
API를 추가하였습니다.
includeFontPadding
을 끄는 데 문제나 버그가 발생한다면, 이슈 트래커에 이를 신고하고 알려주세요.
즐거운 컴포즈 되세요!
좋은 글 감사합니다~