[ComposeCamp] Week1.2 - Theming

David·2022년 12월 27일
0

[Android] Compose

목록 보기
5/5
post-thumbnail

Material Theming


제품 브랜드를 효과적으로 반영하기 위해
Material Design을 체계적으로 맞춤설정하는 
Material Theming을 기반으로 빌드됩니다.
Material 테마는 다음과 같은 속성으로 구성

  • 색상
  • 서체
  • 도형

색상


  • 기본 색상은 주요 브랜드 색상이며 보조 색상은 강조 표시에 사용
  • 예: '표면' 색상 컨테이너의 텍스트는 'on surface'에 색상이 지정되어야 함
  • 기본적으로 플로팅 작업 버튼은 secondary 색상이 지정되고 
    카드는 surface로 기본값이 설정

💁🏻 앱에서 색상 선택 시 참고 Material 색상 유저 인터페이스


서체


테마별로 서체 스타일을 변경할 수는 없지만 서체 스케일을 사용하면 애플리케이션 내에서 일관성이 높아집니다. 자체 글꼴 및 기타 유형 맞춤설정을 제공하면 앱에서 사용하는 Material 구성요소에 반영


도형


머티리얼은 도형을 체계적으로 사용하여
브랜드를 전달할 수 있도록 지원합니다.

소형, 중형, 대형 구성요소라는 3가지 카테고리를 정의합니다.
각각은 모서리 스타일(잘리거나 둥근 스타일)과 크기를
맞춤설정하여 사용할 도형을 정의할 수 있습니다

도형 테마를 맞춤설정하면 다양한 구성요소에 반영됩니다.
예를 들어 버튼과 텍스트 필드소형 도형 테마를 사용하고 

카드와 대화상자중형 도형 테마를 사용하며 

시트대형 도형 테마를 기본적으로 사용합니다.

여기에서 도형 테마에 관한 구성요소의
전체 매핑을 확인할 수 있습니다.
Material 도형 맞춤설정 도구
를 사용하면 도형 테마를 생성할 수 있습니다.


테마 정의


스타일을 중앙 집중화하려면 MaterialTheme을 래핑하고
구성하는 자체 컴포저블을 만드는 것이 좋습니다.

이렇게 하면 테마 맞춤설정을 한곳에서 지정하고
여러 화면 또는 @Preview등 여러 위치에서
쉽게 재사용할 수 있습니다.

색상

theme패키지에 새 파일 Color.kt

val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)

💡 참고:
색상을 정의할 때는 '의미론적'이 아닌
색상 값에 기반하여 '문자 그대로' 이름을 지정합니다(예: primary가 아닌 Red500).
이렇게 하면 여러 테마를 정의할 수 있습니다.
예를 들어 어두운 테마나 다른 스타일의 화면에서는
다른 색상이 primary로 간주될 수 있습니다.

여기서는 lightColors
함수를 사용하여 Colors를 빌드합니다.

이렇게 하면 적절한 기본값이 제공되므로
Material 색상 팔레트를 구성하는 모든 색상을 지정할 필요가 없습니다.

예를 들어 background
색상이나 많은 'on' 색상은 지정하지 않았으므로
기본값을 사용합니다.

서체

TextStyle
객체를 정의하여 일부 텍스트의 스타일을 지정하는 데 필요한 정보를 정의할 수 있습니다. 속성 샘플은 다음과 같습니다

theme 패키지에 Typography.kt
라는 새 파일을 만듭니다.

먼저 각 Font의 여러 가중치를 결합하는 
FontFamily를 정의해 보겠습니다.

val JetnewsTypography = Typography(
    h4 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 30.sp
    ),
    h5 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 24.sp
    ),
    h6 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 20.sp
    ),
    subtitle1 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    ),
    subtitle2 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    ),
    body1 = TextStyle(
        fontFamily = Domine,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    ),
    body2 = TextStyle(
        fontFamily = Montserrat,
        fontSize = 14.sp
    ),
    button = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    ),
    caption = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.Normal,
        fontSize = 12.sp
    ),
    overline = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 12.sp
    )
)
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(
    colors = LightColors,
+   typography = JetnewsTypography,
    content = content
  )
}

도형

theme 패키지에 새 파일 Shape.kt를 만들고 다음을 추가합니다.

val JetnewsShapes = Shapes(
    small = CutCornerShape(topStart = 8.dp),
    medium = CutCornerShape(topStart = 24.dp),
    large = RoundedCornerShape(8.dp)
)
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(
    colors = LightColors,
    typography = JetnewsTypography,
+   shapes = JetnewsShapes,
    content = content
  )
}

어두운 테마

private val DarkColors = darkColors(
    primary = Red300,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200
)
@Composable
fun JetnewsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
  content: @Composable () -> Unit
) {
  MaterialTheme(
+   colors = if (darkTheme) DarkColors else LightColors,
    typography = JetnewsTypography,
    shapes = JetnewsShapes,
    content = content
  )
}

색상 사용


표면 및 콘텐츠 색상

Surface(color = MaterialTheme.colors.primary) {
  Text(...) // default text color is 'onPrimary'
}
Surface(color = MaterialTheme.colors.error) {
  Icon(...) // default tint is 'onError'
}

📌 point
Modifier 에 의해 background을
하드코딩하기 보다 surface로 색상을 설정하여
다크모드 시 자동으로 전환되기 쉽게 만들기

+ Surface(
+   color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+   contentColor = MaterialTheme.colors.primary,
+   modifier = modifier
+ ) {
  Text(
    text = text,
    modifier = Modifier
      .fillMaxWidth()
-     .background(Color.LightGray)
      .padding(horizontal = 16.dp, vertical = 8.dp)
  )
+ }

콘텐츠 알파

+ CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
  Text(
    text = text,
    modifier = modifier
  )
+ }

어두운 테마

// 밝은 테마에서 실행 중인지 확인할 수 있습니다.
val isLightTheme = MaterialTheme.colors.isLight

Material Design은
어두운 테마에서 넓은 밝은 색상 영역을 피할 것을 권장합니다.
일반적인 패턴은
밝은 테마에서 컨테이너 primary 색상을 지정하고
어두운 테마에서 surface 색상을 지정하는 것입니다.

앱 바와 하단 탐색 메뉴와 같은 많은 구성요소에서
기본적으로 이 전략을 사용합니다.

이를 더 쉽게 구현할 수 있도록 
Colors는 정확하게 이 동작을 제공하는 
primarySurface 색상을 제공하고
이러한 구성요소에서 기본적으로 사용됩니다.

앱은 현재 앱 바를 primary 색상으로 설정하고 있지만 
primarySurface로 전환하거나 기본값이므로
이 매개변수를 삭제하여
이 안내를 따를 수 있습니다. 
AppBar 컴포저블에서 TopAppBar의 backgroundColor 매개변수를 변경합니다.

@Composable
private fun AppBar() {
  TopAppBar(
    ...
-   backgroundColor = MaterialTheme.colors.primary
+   backgroundColor = MaterialTheme.colors.primarySurface
  )
}


/**
 * primarySurface represents the background color of components that are [Colors.primary]
 * in light theme, and [Colors.surface] in dark theme, such as [androidx.compose.material.TabRow]
 * and [androidx.compose.material.TopAppBar]. This is to reduce brightness of large surfaces in dark
 * theme, aiding contrast and readability. See
 * [Dark Theme](https://material.io/design/color/dark-theme.html#custom-application).
 *
 * @return [Colors.primary] if in light theme, else [Colors.surface]
 */
val Colors.primarySurface: Color get() = if (isLight) primary else surface

텍스트


내부적으로는 
ProvideTextStyle 컴포저블 '현재' TextStyle
을 설정

  • 자체적으로 CompositionLocal사용)을 사용
@Composable
fun Button(
    // many other parameters
    content: @Composable RowScope.() -> Unit
) {
  ...
  ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
    ...
    content()
  }
}

@Composable
fun Text(
    // many, many parameters
    style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...

테마 텍스트 스타일

@Composable
fun Button(
    // many other parameters
    content: @Composable RowScope.() -> Unit
) {
  ...
  ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
    ...
    content()
  }
}

@Composable
fun Text(
    // many, many parameters
    style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...

여러 스타일

val text = buildAnnotatedString {
  append("This is some unstyled text\n")
  withStyle(SpanStyle(color = Color.Red)) {
    append("Red text\n")
  }
  withStyle(SpanStyle(fontSize = 24.sp)) {
    append("Large text")
  }
}
+ val tagStyle = MaterialTheme.typography.overline.toSpanStyle().copy(
+   background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
+ )
post.tags.forEachIndexed { index, tag ->
  ...
+ withStyle(tagStyle) {
    append(" ${tag.toUpperCase()} ")
+ }
}

도형사용


@Composable
fun FilledTextField(
  // other parameters
  shape: Shape = MaterialTheme.shapes.small.copy(
    bottomStart = ZeroCornerSize, // overrides small theme style
    bottomEnd = ZeroCornerSize // overrides small theme style
  )
) {
@Composable
fun PostItem(...) {
  ...
  Image(
    painter = painterResource(post.imageThumbId),
+   modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
  )

자체 커스텀 버튼 만들기

@Composable
fun LoginButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonConstants.defaultButtonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier
    ) {
        ProvideTextStyle(...) { // set our own text style
            content()
        }
    }
}
@Composable
fun AcmeButton(
  // expose Button params consumers should be able to change
) {
  val acmeButtonShape: Shape = ...
  Button(
    shape = acmeButtonShape,
    // other params
  )
}
profile
공부하는 개발자

0개의 댓글