Compose UI Theme 가이드

코랑·2023년 10월 3일
0

android

목록 보기
16/16

내가 또 까먹을 까봐 쓰는 compose UI 가이드

MaterialTheme 설정만 잘 해도 반은 먹고 간다

Activity에서 setContent로 해당 activity의 UI를 그릴 때, MaterialTheme로 정의한 커스텀 테마로 감싸서 UI 를 바인딩 하도록 가이드 되고있음 근데 이거 잘 쓰고있나? 점검해볼 필요가 있다!!

일반적으로 디자인 시스템을 사용한다면 카드나, 다이얼로그에 동일하게 적용되는
border radius 값이 정해져 있을것이다. => Shape 잘 정의하면 됨.

  • small: Button or Snackbar, TextField
  • medium: Card or AlertDialog
  • large: ModalDrawer or ModalBottomSheetLayout

++ 그리고 여기서 커스텀 컴포넌트를 만들 때도 구글 가이드와 동일하게 맞춰주는게 좋다!
예를 들어, CustomBottomSheet의 shape(border radius값)을 지정할 때에는MaterialTheme.shapes.large 값을 넣어주자!

이 외에 color나 typography는 말 그대로 색깔이나 서체에 관한 설정임
typography(서체)도 옵션별로 잘 정의해주면 디자인 시스템 적용에 유용한데

typography 옵션(글꼴 설정)

defaultFontFamily - TextStyle에 FontFamily가 null이면 기본 값으로 들어가는 폰트 스타일
h1, h2, h3, h4, h5, h6 - largest headline, reserved for short, important text or numerals. 숫자가 작을 수록 더 강조되는 글자에 사용하면 됨.
subtitle1, subtitle2 - subtitle1 is the largest subtitle, and is typically reserved for medium-emphasis text that is shorter in length.
body1, body2 - body1 is the largest body, and is typically used for long-form writing as it works well for small text sizes.
button - 모든 타입의 버튼 텍스트 스타일(카드, 다이얼로그, 탭, OutlinedButton 등 모두 포함)
caption - caption is one of the smallest font sizes. It is used sparingly to annotate imagery or to introduce a headline.
overline - overline is one of the smallest font sizes. It is used sparingly to annotate imagery or to introduce a headline.

CompositionLocalProvider

CompositionLocal 이라는게 뭘까? 아래 예시를 확인해보자

// 예시 코드
@Composable
fun CompositionLocalExample() {
	MaterialTheme { // MaterialTheme sets ContentAlpha.high as default 
		Column {
			Text("Uses MaterialTheme's provided alpha")
			CompositionLocalProvider(LocalContentAlpha
									 provides
									 ContentAlpha.medium) {
				Text("Medium value provided for LocalContentAlpha")
				Text("This Text also uses the medium value")
				CompositionLocalProvider(LocalContentAlpha
										 provides
										 ContentAlpha.disabled) {
                    // 하위 컴포즈에 동일한 알파값을 적용하기 때문에
                    // 요 하위 컴포즈에도 disabled 옵션이 적용됨.
					DescendantExample()
				}
			}
		}
	}
}

@Composable
fun DescendantExample() {
	Text("This Text uses the disabled alpha now")
}

<결과값>
결과값
웹에서는 상위(부모)컴포넌트에서 스타일 지정해주면 하위(자식)UI에 스타일이 propagation 되는데 Compose에서는 CompositionLocal 로 이 효과를 동일하게 적용할 수 있음.

그래서 CompositionLocal이 뭐냐고?
UI 트리를 통해 암시적으로 데이터(색상, 폰트, 뷰모델 등등)가 흐르도록 할 수 있는 객체를 제공하려고 만든거

이 문서에서는 UI 데이터에 대해서만 다루겠지만, 뷰모델(근데 뷰모델은 추천 안함)이나 여러가지를 전달해서 쓸 수 있음.

루트에서 사용하는 MaterialTheme가 이 CompositionLocal로 구현되어있음.

  • 위에서 설명했듯이 Color, Shape, Typography 세가지에 대해 Local scope을 지정하고
  • 속성들이 암시적으로 모든 컨텐츠의 기본 값으로 전달되도록 함.

위 예시는 MaterialTheme에서 지정해 주지 않는 Alpha 값에 대한 데이터를 지정해 주는 예시.

CompositionLocal 사용 시 주의할 점

  1. CompositionLocal 관련 객체나 오브젝트는 prefix는 Local 을 쓰는게 IDE에서 힌팅하거나 할 때도 좋음.

  2. CompositionLocal을 오남용하지 않도록 주의.

    • 어쨌든 compose 함수 간 dependency를 만드는거고 이게 많아지면 나중에 UI 문제가 생겨도 디버깅이나 추적이 힘들기 때문에 고생할 수 있음.
    • 소스코드에서 직관적으로 원인을 찾을 수 없음 => 뭐 이때는 IDE에서 Find나 Layout Inspector 사용하면 된다고는 하는뎅,,
  3. 2번과 이어지는 내용인데 대안이 있다면 대안을 사용할것!(불필요한 의존성을 만들지 말자)

    • 자식 컴포즈가 단순히 데이터만 필요한 경우 명시적으로 파라미터로 넘길것
    • 뷰모델은 CompositionLocal로 넘기지 않는것을 추천함
    @Composable
    fun MyComposable(myViewModel: MyViewModel = viewModel()) {
        // ...
        MyDescendant(myViewModel.data)
    }
    
    // 전체 객체 넘기지 마라! 특히 뷰모델!
    @Composable
    fun MyDescendant(myViewModel: MyViewModel) { /* ... */ }
    
    // 자식에게 필요한거만
    @Composable
    fun MyDescendant(data: DataToDisplay) {
        // Display data
    }
    • 제어 반전: 불필요한 의존성을 피하기위해 자식이 제어권을 가져감 => 부모가 불필요하게 로직이 복잡해지는걸 막을 수 있음.
    @Composable
    fun MyComposable(myViewModel: MyViewModel = viewModel()) {
        // ...
        MyDescendant(myViewModel)
    }
    
    @Composable
    fun MyDescendant(myViewModel: MyViewModel) {
    	// 근데 이런식으로 제어 반전을 해버리면 MyDescendant컴포즈 함수는 너무 많은 책임을 가져가 버림. MyComposable함수랑 완전히 묶여서 재사용성도 떨어지게됨,,
        Button(onClick = { myViewModel.loadData() }) {
            Text("Load data")
        }
    }
    
    // 제어 반전은 해줘야 되는데 그럼 어떡하냐,,? 대안으로 실행할 함수를 넘기는거다!
    @Composable
    fun MyComposable(myViewModel: MyViewModel = viewModel()) {
        // ...
        ReusableLoadDataButton(
            onLoadClick = {
                myViewModel.loadData()
            }
        )
    }
    
    @Composable
    fun ReusableLoadDataButton(onLoadClick: () -> Unit) {
        Button(onClick = onLoadClick) {
            Text("Load data")
        }
    }
    
    // content로 @Composable 함수를 넘기는 방식들이 위와 같은 원리~
    @Composable
    fun MyComposable(myViewModel: MyViewModel = viewModel()) {
        // ...
        ReusableLoadDataButton(
            onLoadClick = {
                myViewModel.loadData()
            }
        )
    }
    
    @Composable
    fun ReusableLoadDataButton(onLoadClick: () -> Unit) {
        Button(onClick = onLoadClick) {
            Text("Load data")
        }
    }

0개의 댓글