Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 22 additions & 11 deletions .idea/other.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions app/src/main/kotlin/com/yourssu/handy/demo/ScaffoldPreview.kt
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)
)
}
)
}
}
168 changes: 168 additions & 0 deletions compose/src/main/kotlin/com/yourssu/handy/compose/Scaffold.kt
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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with(LocalDensity.current) { ... }
보통 이렇게 하는 게 일반적인데 취향차이인 것 같긴 해요

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) {
Copy link
Member

Choose a reason for hiding this comment

The 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))
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

머티리얼에서는 어떻게 되어있을까요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠.. 머테리얼 SnackBar를 사용해보았을땐 기본으로 화면 너비를 채우고 밖에서 마진을 넣어주게 되어있긴 하네요

지금은 제가 SnackBar 컴포넌트 자체에 마진을 주긴했는데 이 파일에서 넣어주는 걸로 할까요?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
}