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

141 changes: 141 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,141 @@
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 ScaffoldAllExistPreview() {
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)
)
}
)
}
}

@Composable
@Preview
fun ScaffoldNonExistFabPreview() {
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)
)
},
content = {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
)
}
)
}
}

@Composable
@Preview
fun ScaffoldNonExistBottomBarPreview() {
HandyTheme {
Scaffold(
topBar = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.Blue)
)
},
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)
)
}
)
}
}
172 changes: 172 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,172 @@
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.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) {
Box(
modifier = Modifier.semantics {
isTraversalGroup = true
traversalIndex = 0f
}
) {
topBar()
}
}.first().measure(looseConstraints)

val snackBarPlaceable = subcompose(ScaffoldLayoutContent.Snackbar) {
Box(modifier = Modifier
.padding()
Copy link
Member

Choose a reason for hiding this comment

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

이거 padding(horizontal = 0.dp, vertical = 0.dp)랑 같은거 맞을까요? 그렇다면 없어도 될 것 같습니다!

.semantics {
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 mainContentPlaceable = subcompose(ScaffoldLayoutContent.MainContent) {
Box(
modifier = Modifier.semantics {
isTraversalGroup = true
traversalIndex = 2f
}
) {
content(
PaddingValues(
top = with(LocalDensity.current) { topBarPlaceable.height.toDp() },
bottom = with(LocalDensity.current) { bottomBarPlaceable.height.toDp() }
)
)
}
}.first().measure(looseConstraints)

val bottomBarVerticalOffset = layoutHeight - bottomBarPlaceable.height

val snackBarVerticalOffset = bottomBarVerticalOffset - snackBarPlaceable.height - snackBarPxValue

val fabVerticalOffset = bottomBarVerticalOffset - fabPlaceable.height - fabBottomMarginPxValue
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
}
Comment on lines +160 to +164
Copy link
Member

Choose a reason for hiding this comment

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

ScaffoldLayoutContent 이넘클래스는 private인데 ScaffoldSpacingValues는 아닌 이유가 궁금해졌어요...!


private enum class ScaffoldLayoutContent {
TopBar,
MainContent,
Snackbar,
Fab,
BottomBar
}