-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scaffold 구현 #15
base: main
Are you sure you want to change the base?
Scaffold 구현 #15
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package com.yourssu.handy.demo | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import com.yourssu.handy.compose.HandyTheme | ||
import com.yourssu.handy.compose.Scaffold | ||
|
||
@Composable | ||
@Preview | ||
fun ScaffoldPreview() { | ||
HandyTheme { | ||
Scaffold( | ||
topBar = { | ||
Box( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.height(56.dp) | ||
.background(Color.Blue) | ||
) | ||
}, | ||
bottomBar = { | ||
Box( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.height(56.dp) | ||
.background(Color.Green) | ||
) | ||
}, | ||
snackbarHost = { | ||
Box( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.height(56.dp) | ||
.background(Color.Red) | ||
) | ||
}, | ||
floatingActionButton = { | ||
Box( | ||
modifier = Modifier | ||
.size(56.dp) | ||
.background(Color.Yellow) | ||
) | ||
}, | ||
content = { | ||
Box( | ||
modifier = Modifier | ||
.fillMaxSize() | ||
.background(Color.Gray) | ||
) | ||
} | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package com.yourssu.handy.compose | ||
|
||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.PaddingValues | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.layout.SubcomposeLayout | ||
import androidx.compose.ui.platform.LocalDensity | ||
import androidx.compose.ui.semantics.isTraversalGroup | ||
import androidx.compose.ui.semantics.semantics | ||
import androidx.compose.ui.semantics.traversalIndex | ||
import androidx.compose.ui.unit.dp | ||
import com.yourssu.handy.compose.ScaffoldSpacingValues.FabBottomSpacing | ||
import com.yourssu.handy.compose.ScaffoldSpacingValues.FabEndSpacing | ||
import com.yourssu.handy.compose.ScaffoldSpacingValues.SnackBarBottomSpacing | ||
import com.yourssu.handy.compose.ScaffoldSpacingValues.SnackBarHorizontalSpacing | ||
import com.yourssu.handy.compose.foundation.LocalContentColor | ||
|
||
/** | ||
* layout을 구성하기 위한 Scaffold입니다. | ||
* | ||
* @param modifier : Modifier | ||
* @param topBar : 상단 바 | ||
* @param snackbarHost : Snackbar | ||
* @param floatingActionButton : Floating Action Button | ||
* @param bottomBar : 하단 네비게이션 바 | ||
* @param containerColor : Scaffold의 배경색 | ||
* @param contentColor : Scaffold의 content 색상 | ||
* @param content : Scaffold의 content | ||
*/ | ||
@Composable | ||
fun Scaffold( | ||
modifier: Modifier = Modifier, | ||
topBar: @Composable () -> Unit = {}, | ||
snackbarHost: @Composable () -> Unit = {}, | ||
floatingActionButton: @Composable () -> Unit = {}, | ||
bottomBar: @Composable () -> Unit = {}, | ||
containerColor: Color = Color.Unspecified, | ||
contentColor: Color = LocalContentColor.current, | ||
content: @Composable (PaddingValues) -> Unit | ||
) { | ||
Surface( | ||
modifier = modifier, | ||
backgroundColor = containerColor, | ||
contentColor = contentColor | ||
) { | ||
ScaffoldLayout( | ||
topBar = topBar, | ||
content = content, | ||
snackbar = snackbarHost, | ||
fab = floatingActionButton, | ||
bottomBar = bottomBar | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* ScaffoldLayout을 구성하는 함수입니다. | ||
* @param topBar : 상단 바 | ||
* @param content : Scaffold의 content | ||
* @param snackbar : Snackbar | ||
* @param fab : Floating Action Button | ||
* @param bottomBar : 하단 네비게이션 바 | ||
*/ | ||
@Composable | ||
private fun ScaffoldLayout( | ||
topBar: @Composable () -> Unit, | ||
content: @Composable (PaddingValues) -> Unit, | ||
snackbar: @Composable () -> Unit, | ||
fab: @Composable () -> Unit, | ||
bottomBar: @Composable () -> Unit | ||
) { | ||
val snackBarPxValue = LocalDensity.current.run { SnackBarBottomSpacing.toPx() }.toInt() | ||
val fabEndMarginPxValue = LocalDensity.current.run { FabEndSpacing.toPx() }.toInt() | ||
val fabBottomMarginPxValue = LocalDensity.current.run { FabBottomSpacing.toPx() }.toInt() | ||
|
||
SubcomposeLayout( | ||
modifier = Modifier.semantics { isTraversalGroup = true } | ||
) { constraints -> | ||
val layoutWidth = constraints.maxWidth | ||
val layoutHeight = constraints.maxHeight | ||
|
||
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) | ||
|
||
val topBarPlaceable = subcompose(ScaffoldLayoutContent.TopBar) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. subcompose 괄호 안에 넣어주는 게 무엇을 의미하는건가요? |
||
Box( | ||
modifier = Modifier.semantics { | ||
isTraversalGroup = true | ||
traversalIndex = 0f | ||
} | ||
) { | ||
topBar() | ||
} | ||
}.first().measure(looseConstraints) | ||
|
||
val mainContentPlaceable = subcompose(ScaffoldLayoutContent.MainContent) { | ||
Box( | ||
modifier = Modifier.semantics { | ||
isTraversalGroup = true | ||
traversalIndex = 2f | ||
} | ||
) { | ||
content(PaddingValues(0.dp)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. content padding 값은 항상 0인건가요? |
||
} | ||
}.first().measure(looseConstraints) | ||
|
||
val snackBarPlaceable = subcompose(ScaffoldLayoutContent.Snackbar) { | ||
Box(modifier = Modifier | ||
.padding(horizontal = SnackBarHorizontalSpacing) // TODO : SnackBar 컴포넌트 자체에서 margin을 줄지 여기서 주어야 할지? | ||
.semantics { | ||
Comment on lines
+109
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 머티리얼에서는 어떻게 되어있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 흠.. 머테리얼 SnackBar를 사용해보았을땐 기본으로 화면 너비를 채우고 밖에서 마진을 넣어주게 되어있긴 하네요 지금은 제가 SnackBar 컴포넌트 자체에 마진을 주긴했는데 이 파일에서 넣어주는 걸로 할까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 그러면 snackbar에 이미 되어있다면 여기서 마진 없도록 하겠습니다~! |
||
isTraversalGroup = true | ||
traversalIndex = 4f | ||
} | ||
) { | ||
snackbar() | ||
} | ||
}.first().measure(looseConstraints) | ||
|
||
val fabPlaceable = subcompose(ScaffoldLayoutContent.Fab) { | ||
Box(modifier = Modifier.semantics { | ||
isTraversalGroup = true | ||
traversalIndex = 3f | ||
}) { | ||
fab() | ||
} | ||
}.first().measure(looseConstraints) | ||
|
||
val bottomBarPlaceable = subcompose(ScaffoldLayoutContent.BottomBar) { | ||
Box(modifier = Modifier.semantics { | ||
isTraversalGroup = true | ||
traversalIndex = 1f | ||
}) { | ||
bottomBar() | ||
} | ||
}.first().measure(looseConstraints) | ||
|
||
val bottomBarVerticalOffset = layoutHeight - bottomBarPlaceable.height | ||
val fabVerticalOffset = | ||
bottomBarVerticalOffset - fabPlaceable.height - fabBottomMarginPxValue | ||
val snackBarVerticalOffset = fabVerticalOffset - snackBarPlaceable.height - snackBarPxValue | ||
Comment on lines
+139
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fab이 없을 땐 fabVerticalOffset이 bottomBarVerticalOffset과 같아야 할 것 같아요. 쉽게 설명하자면 지금 상황에서 fab이 없을 때 snackBar가 어디에 배치될지 생각해보심 됩니다. 아마 생각보다 높은 곳에 배치될거예요. (fab 마진 + snackBar 마진 만큼) |
||
val fabHorizontalOffset = layoutWidth - fabPlaceable.width - fabEndMarginPxValue | ||
|
||
layout(layoutWidth, layoutHeight) { | ||
topBarPlaceable.placeRelative(0, 0) | ||
mainContentPlaceable.placeRelative(0, topBarPlaceable.height) | ||
snackBarPlaceable.placeRelative(0, snackBarVerticalOffset) | ||
fabPlaceable.placeRelative(fabHorizontalOffset, fabVerticalOffset) | ||
bottomBarPlaceable.placeRelative(0, bottomBarVerticalOffset) | ||
} | ||
} | ||
} | ||
|
||
object ScaffoldSpacingValues { | ||
val FabBottomSpacing = 32.dp | ||
val FabEndSpacing = 16.dp | ||
val SnackBarBottomSpacing = 16.dp | ||
val SnackBarHorizontalSpacing = 16.dp | ||
} | ||
|
||
private enum class ScaffoldLayoutContent { | ||
TopBar, | ||
MainContent, | ||
Snackbar, | ||
Fab, | ||
BottomBar | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with(LocalDensity.current) { ... }
보통 이렇게 하는 게 일반적인데 취향차이인 것 같긴 해요