-
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
FloatingActionButton 구현 #12
base: main
Are you sure you want to change the base?
Changes from all commits
d357199
08eaa50
ca724c9
1d14bd1
4951925
e41f364
521a1be
1f73d62
847a6b4
41beaff
cbf5371
8a63ab7
30f7294
22cc9da
495d3c7
ebb6f67
e38ff0b
3969695
deb1a82
2e5bfb6
8c2f943
1f5792b
e3601cf
340d1d1
6ac9a43
a5562de
5f3bcc6
ad3a43e
6ed94de
69a5ca7
fa8eae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package com.yourssu.handy.demo | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.wrapContentSize | ||
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.FloatingActionButton | ||
import com.yourssu.handy.compose.FloatingActionButtonSize | ||
import com.yourssu.handy.compose.FloatingActionButtonType | ||
import com.yourssu.handy.compose.HandyTheme | ||
import com.yourssu.handy.compose.icons.HandyIcons | ||
import com.yourssu.handy.compose.icons.line.ArrowsChevronUp | ||
import com.yourssu.handy.compose.icons.line.ExternalLink | ||
|
||
@Composable | ||
@Preview | ||
fun FloatingActionButtonPreview() { | ||
HandyTheme { | ||
Column( | ||
modifier = Modifier | ||
.background(Color.White) | ||
.wrapContentSize() | ||
.padding(20.dp), | ||
verticalArrangement = Arrangement.spacedBy(10.dp) | ||
) { | ||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(20.dp) | ||
) { | ||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(10.dp) | ||
) { | ||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ArrowsChevronUp, | ||
sizeType = FloatingActionButtonSize.S, | ||
floatingActionButtonType = FloatingActionButtonType.Primary | ||
) | ||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.S, | ||
floatingActionButtonType = FloatingActionButtonType.Primary, | ||
enabled = false | ||
) | ||
} | ||
|
||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(10.dp) | ||
) { | ||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.S, | ||
floatingActionButtonType = FloatingActionButtonType.Secondary, | ||
) | ||
|
||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.S, | ||
floatingActionButtonType = FloatingActionButtonType.Secondary, | ||
enabled = false | ||
) | ||
} | ||
} | ||
|
||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(20.dp) | ||
) { | ||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(10.dp) | ||
) { | ||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.L, | ||
floatingActionButtonType = FloatingActionButtonType.Primary | ||
) | ||
|
||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.L, | ||
floatingActionButtonType = FloatingActionButtonType.Primary, | ||
enabled = false | ||
) | ||
} | ||
|
||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(10.dp) | ||
) { | ||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.L, | ||
floatingActionButtonType = FloatingActionButtonType.Secondary, | ||
) | ||
|
||
FloatingActionButton( | ||
onClick = {}, | ||
icon = HandyIcons.Line.ExternalLink, | ||
sizeType = FloatingActionButtonSize.L, | ||
floatingActionButtonType = FloatingActionButtonType.Secondary, | ||
enabled = false | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package com.yourssu.handy.compose | ||
|
||
import androidx.compose.foundation.border | ||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.foundation.interaction.collectIsPressedAsState | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.shape.CircleShape | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.Immutable | ||
import androidx.compose.runtime.State | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.rememberUpdatedState | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.Shape | ||
import androidx.compose.ui.graphics.vector.ImageVector | ||
import androidx.compose.ui.semantics.Role | ||
import androidx.compose.ui.semantics.role | ||
import androidx.compose.ui.semantics.semantics | ||
import androidx.compose.ui.unit.Dp | ||
import androidx.compose.ui.unit.dp | ||
import com.yourssu.handy.compose.foundation.ColorFabPrimaryShadow | ||
import com.yourssu.handy.compose.foundation.ColorFabSecondaryShadow | ||
import com.yourssu.handy.compose.icons.HandyIcons | ||
import com.yourssu.handy.compose.icons.line.ExternalLink | ||
|
||
enum class FloatingActionButtonSize(internal val value: Dp) { | ||
S(40.dp), | ||
L(56.dp), | ||
} | ||
|
||
enum class FloatingActionButtonType { | ||
Primary, | ||
Secondary; | ||
} | ||
|
||
@Composable | ||
fun FloatingActionButton( | ||
onClick: () -> Unit, | ||
modifier: Modifier = Modifier, | ||
enabled: Boolean = true, | ||
shape: Shape = CircleShape, | ||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | ||
icon: ImageVector = HandyIcons.Line.ExternalLink, | ||
sizeType: FloatingActionButtonSize = FloatingActionButtonSize.S, | ||
floatingActionButtonType: FloatingActionButtonType = FloatingActionButtonType.Primary, | ||
) { | ||
val colors = floatingActionButtonColorByType(type = floatingActionButtonType) | ||
val localPressed by interactionSource.collectIsPressedAsState() | ||
val containerColor = colors.apply { pressed = localPressed } | ||
val contentColor by containerColor.contentColor(enabled) | ||
val borderColor = containerColor.borderColor() | ||
|
||
Comment on lines
+56
to
+58
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. 위 변수에는 by 넣어주고 아래에는 안 넣어준 이유가 있나용..? |
||
val containerSize = boxButtonSizeStateBySize(size = sizeType) | ||
|
||
Surface( | ||
onClick = onClick, | ||
modifier = modifier.semantics { role = Role.Button }, | ||
Comment on lines
+61
to
+63
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.
|
||
shape = shape, | ||
backgroundColor = containerColor.backgroundColor(enabled).value, | ||
contentColor = contentColor, | ||
interactionSource = interactionSource, | ||
shadowColor = containerColor.shadowColor(enabled).value, | ||
shadowElevation = FloatingActionButtonDefaults.FloatingActionButtonShadowElevation, | ||
) { | ||
Box( | ||
modifier = Modifier | ||
.size(containerSize.containerSize.value) | ||
.border( | ||
width = FloatingActionButtonDefaults.FloatingActionButtonBorderSize, | ||
color = borderColor.value, | ||
shape = shape | ||
), | ||
contentAlignment = Alignment.Center, | ||
) { | ||
Icon( | ||
imageVector = icon, | ||
iconSize = FloatingActionButtonDefaults.iconSize, | ||
) | ||
} | ||
} | ||
} | ||
|
||
|
||
@Immutable | ||
data class FloatingActionButtonSizeState( | ||
val containerSize: FloatingActionButtonSize = FloatingActionButtonSize.S, | ||
) | ||
|
||
|
||
private fun boxButtonSizeStateBySize( | ||
size: FloatingActionButtonSize, | ||
): FloatingActionButtonSizeState = when (size) { | ||
FloatingActionButtonSize.S -> FloatingActionButtonSizeState( | ||
containerSize = FloatingActionButtonSize.S, | ||
) | ||
|
||
FloatingActionButtonSize.L -> FloatingActionButtonSizeState( | ||
containerSize = FloatingActionButtonSize.L, | ||
) | ||
} | ||
Comment on lines
+96
to
+106
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. 이렇게 따로 함수로 빼신 이유가 있을까요? 아마 YDS를 참고해서 만드신 것 같은데, YDS에서는 이 구조가 최선이었어서.. 여기서는 딱히 이렇게 해야 할 이유가 없어보여요. 더 간단하게, 직관적으로 상태를 관리/표현할 방법은 없는지 고민해주시면 좋을 것 같습니다! 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. 저도 구현하면서 똑같은 생각이 들긴했었는데요 .. 사실 YDS, Mateiral 에서의 디자인 시스템 보다 핸디가 속성과 옵션이 적어서 대부분 함수화를 하지 않는 것이 더 직관적이긴 했어요 상태가 적지만 확장가능성이 있는.. Handy상태 관리에 대해서 좀 더 고민해보겠습니당 |
||
|
||
@Composable | ||
private fun floatingActionButtonColorByType( | ||
type: FloatingActionButtonType, | ||
): FloatingActionButtonColorState = when (type) { | ||
FloatingActionButtonType.Primary -> FloatingActionButtonColorState( | ||
contentColor = HandyTheme.colors.iconBasicWhite, | ||
disabledContentColor = HandyTheme.colors.iconBasicWhite, | ||
bgColor = HandyTheme.colors.buttonFabPrimaryEnabled, | ||
disabledBgColor = HandyTheme.colors.buttonFabPrimaryDisabled, | ||
shadowColor = ColorFabPrimaryShadow, | ||
borderColor = Color.Transparent | ||
) | ||
|
||
FloatingActionButtonType.Secondary -> FloatingActionButtonColorState( | ||
contentColor = HandyTheme.colors.iconBasicTertiary, | ||
disabledContentColor = HandyTheme.colors.iconBasicDisabled, | ||
bgColor = HandyTheme.colors.buttonFabSecondaryEnabled, | ||
disabledBgColor = HandyTheme.colors.buttonFabSecondaryDisabled, | ||
shadowColor = ColorFabSecondaryShadow, | ||
borderColor = HandyTheme.colors.lineBasicLight | ||
) | ||
} | ||
|
||
@Composable | ||
private fun pressedFloatingActionButtonColorFor(color: Color): Color { | ||
return when (color) { | ||
HandyTheme.colors.buttonFabPrimaryEnabled -> HandyTheme.colors.buttonFabPrimaryPressed | ||
HandyTheme.colors.buttonFabSecondaryEnabled -> HandyTheme.colors.buttonFabSecondaryPressed | ||
else -> color | ||
} | ||
} | ||
|
||
@Immutable | ||
class FloatingActionButtonColorState( | ||
val contentColor: Color = Color.Unspecified, | ||
val disabledContentColor: Color = Color.Unspecified, | ||
val bgColor: Color = Color.Transparent, | ||
val disabledBgColor: Color = Color.Transparent, | ||
val shadowColor: Color = Color.Transparent, | ||
val borderColor: Color = Color.Transparent, | ||
pressed: Boolean = false, | ||
) { | ||
var pressed by mutableStateOf(pressed) | ||
internal set | ||
|
||
@Composable | ||
fun contentColor(enabled: Boolean): State<Color> = | ||
rememberUpdatedState( | ||
when { | ||
!enabled -> disabledContentColor | ||
pressed -> pressedFloatingActionButtonColorFor(contentColor) | ||
else -> contentColor | ||
} | ||
) | ||
|
||
@Composable | ||
fun backgroundColor(enabled: Boolean): State<Color> = | ||
rememberUpdatedState( | ||
when { | ||
!enabled -> disabledBgColor | ||
pressed -> pressedFloatingActionButtonColorFor(bgColor) | ||
else -> bgColor | ||
} | ||
) | ||
|
||
@Composable | ||
fun shadowColor(enabled: Boolean): State<Color> = | ||
rememberUpdatedState( | ||
when { | ||
!enabled -> Color.Transparent | ||
else -> shadowColor | ||
} | ||
) | ||
|
||
@Composable | ||
fun borderColor(): State<Color> = | ||
rememberUpdatedState(borderColor) | ||
} | ||
|
||
object FloatingActionButtonDefaults { | ||
val iconSize: IconSize = IconSize.M // 24.dp | ||
val FloatingActionButtonBorderSize = 1.dp | ||
val FloatingActionButtonShadowElevation = 6.dp | ||
} |
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.
엇 그리구 컴포넌트에 대한 설명이 빠진 것 같아요..!