내가 또 까먹을 까봐 쓰는 compose UI 가이드
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(서체)도 옵션별로 잘 정의해주면 디자인 시스템 적용에 유용한데
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.
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 관련 객체나 오브젝트는 prefix는 Local 을 쓰는게 IDE에서 힌팅하거나 할 때도 좋음.
CompositionLocal을 오남용하지 않도록 주의.
2번과 이어지는 내용인데 대안이 있다면 대안을 사용할것!(불필요한 의존성을 만들지 말자)
@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")
}
}