#1 ๊ฐ์
#1-1 ๊ฐ๋ฐ ์ด์
๋ง๋ก ์ค๋ช ํ๊ธฐ ํ๋ค์ง๋ง, BottomSheetScaffold์ ๋ด๋ถ ์ฝ๋๋ฅผ ์ด์ง๋ง ๋ฐ๊พธ๋ฉด ๋ด๊ฐ ์ํ๋ ๋์์ ๊ตฌํํ ์ ์์๋ค. ๊ทธ๋ ๋ค๊ณ internal์ด๋ private ์ ๊ทผ ์ง์ ์๊ฐ ๋ถ์ ๋ด๋ถ ์ฝ๋๋ฅผ ๋ด๊ฐ ์ปค์คํ ํ ์๋ ์๋ ๋ ธ๋ฆ์ด์๋ค. publicํ ํจ์๋ค๋ก ๊ตฌํ๋ ํ ์ ์๋ ๋ชจ๋ ๊ฑธ ํด๋ดค๋ค. ๊ฒฐ๊ตญ ๊น์ํ ํ๊ณ ๋ค์ด๋ณธ ๊ฒฐ๊ณผ, ๋ฐ๋์ ๋ด๋ถ ์ฝ๋๋ฅผ ๋ฐ๊ฟ์ผ๋ง ๋ด๊ฐ ์ํ๋ ๋์์ ๊ตฌํํ ์ ์๋ค๋ ๊ฑธ ๊นจ๋ฌ์๋ค.
์ ํํ๋, NestedScrollConnection์ ๊ธฐ์ ๋ฅผ ์๊ฒ๋๊ณ ๋์ ๊นจ๋ฌ์๋ค. ์์ BottomSheetScaffold์ ๊ฐํด์ง๋ ์ฌ์ฉ์์ ํฐ์น ์ ๋ ฅ(Pointer input)์ ์ต์์ ๋ถ๋ชจ(์ธ ๋ด๋ถ ์ฝ๋)๊น์ง ์ฌ๋ผ๊ฐ๋๋ฐ, ์ด๋ฅผ ๋ง์ ๋ฐฉ๋ฒ์ด ์๋ค๋ ๊ฑธ ์๊ฒ๋ ๊ฒ์ด๋ค. ๊ทธ๋์ ๋ช ์ฃผ๊ฐ ์์ BottomSheetScaffold๋ฅผ ๋ชจ๋ฐฉํ BottomSheetScaffold๋ฅผ ๋ง๋ค๋ ค ํ๋ค. ์ปค์คํ BottomSheetScaffold๋ฅผ ๋ง๋ค๊ธฐ ์ํด์ ์์ BottomSheetScaffold ๋ด๋ถ ์ฝ๋๋ค์ ์ ๋ถ publicํ๊ฒ ๋ง๋ค๋ ค๊ณ ํ ๊ฒ์ด๋ค.
#1-2 ์ ์ ์ด์
๊ทธ๋ฌ๋, ์์ BottomSheetScaffold์ ์ฝ๋๋ ๋ด๊ฐ ์ดํดํ๊ธฐ ๋ฒ ์ฐผ๋ค. ๋จผ์ ์ผ๋จ์ (์์ BottomSheetScaffold์) ์ฝ๋๋ฅผ ์ฝ์ ์ ์์ด์ผ ํ๋ค๊ณ ์๊ฐํ๋ค. ๊ฐ๋ น NestedScroll์ ๊ธฐ์ ์ ๋ํ ํ์ ์, BottomSheet์ ์คํฌ๋กค ๋์์ ์ดํดํ๋๋ฐ ํ์์ ์ด๋ค. ๋, NestedScroll์ ๊ธฐ์ ๋ฅผ ์๋ ค๋ฉด ๋ ๊น์ํ ๋ค์ด๊ฐ Pointer input๊น์ง ์๊ณ ์์ด์ผ ํ๋ค. ๊ทธ๋์ ๋ ๊ณต์ ๋ฌธ์์ Pointer input ๋ถ๋ถ์ ์๋์ ๊ฐ์ด ๋๋ง์ ์ธ์ด๋ก ์ ๋ฆฌํ๋ฉฐ ๊ณต๋ถํ๋ค.
[Android] Pointer input - PointerInputChange, PointerEvent
#1 ๊ฐ์ ๋์ ์ดํดํ๊ธฐ | Jetpack Compose | Android Developers์ด ํ์ด์ง๋ Cloud Translation API๋ฅผ ํตํด ๋ฒ์ญ๋์์ต๋๋ค. ๋์ ์ดํดํ๊ธฐ ์ปฌ๋ ์ ์ ์ฌ์ฉํด ์ ๋ฆฌํ๊ธฐ ๋ด ํ๊ฒฝ์ค์ ์ ๊ธฐ์ค์ผ๋ก ์ฝํ ์ธ ๋ฅผ ์
kenel.tistory.com
์คํฌ๋กค ๋์์ ์ดํดํ๊ณ ๋๋, ํจ์ฌ ๋ BottomSheetScaffold์ ๋ด๋ถ ์ฝ๋๋ฅผ ์ดํดํ๊ธฐ ์ฌ์ ๋ค. ๊ทธ๋ฌ๋, ๊ทธ ์ดํ์๋ ๋์ด์ผ ํ ์ฐ(๋๋๊ทธ ๋์ ๋ฐ ์ํคํ ์ฒ์ ๋ํ ์ดํด)์ ๋ง์๋ค. ํ๋ํ๋ ์ ๋ นํ๋ฉด ๊ทธ๋ง์ด์๋ค. ๊ทธ๋ฌ๋, ๋ด๊ฐ ์ด๋ฏธ ์๋ชจํ ์๊ฐ ๊ทธ๋ฆฌ๊ณ ์์ผ๋ก ์๋ชจํด์ผํ ์๊ฐ์ด ์๋ฅธ๊ฑฐ๋ ธ๋ค.
#1-3 ์คํจ๊ฐ ์๋ ์ ์
์ ๋ง ์คํจ์๋ค๋ฉด, ๊ตณ์ด ๊ฒ์๊ธ๋ก ๋จ๊ธธ ์ด์ ๊ฐ ์๋ค. ์คํจ๊ฐ ์๋ ์ ์๋ค. ์ด์ง๋ง ๋ฐ๊ฟ์ ํจ์ฌ ๋ ์ข์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ป์ด๋ผ ์ ์๋ ๋์์ด๋ผ๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ ์์ ํฌ๊ธฐํ๊ธฐ๋ ๋๋ฌด๋๋ ์์ฝ๋ค. ๊ทธ๋์ ๋ด๋ถ ์ฝ๋๋ฅผ ๊ทธ๋๋ก ๋ณต์ฌํ ์ฝ๋๋ฅผ ์ฌ๊ธฐ์ ๋จ๊ฒจ๋๊ณ ํ๋ ๋๋ง๋ค ๊ณต๋ถํด๋ณด๋ ค๊ณ ํ๋ค.
๋, ๋ณธ ๊ฒ์๊ธ์ ์ง์์ ์ผ๋ก ์์ ํ ๊ฒ์ด๋ค. ๋น๋ถ๊ฐ์ ์ฃผ์์ ๊ณ์ ์ถ๊ฐํด์ ์ฝ๋๋ฅผ ํด์ํ๊ฑฐ๋, ๋ด๊ฐ ๊ณต๋ถํด์ผํ ๊ฐ๋ ๋ค์ ์ ์ด๋๊ฐ๋ฏ ํ๋ค.
#2 ์ฝ๋ - ReplicatedBottomSheetScaffold.kt
#2-1 ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ฐ์ ธ์จ ์ฝ๋
package com.example.replicatedbottomsheetscaffold
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.DraggableAnchors // error: Cannot access 'DraggableAnchors': it is internal in 'androidx. compose. material3'
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetValue
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplicatedBottomSheetScaffold(
sheetContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
sheetPeekHeight: Dp = BottomSheetDefaults.SheetPeekHeight,
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
sheetShape: Shape = BottomSheetDefaults.ExpandedShape,
sheetContainerColor: Color = BottomSheetDefaults.ContainerColor,
sheetContentColor: Color = contentColorFor(sheetContainerColor),
sheetTonalElevation: Dp = BottomSheetDefaults.Elevation,
sheetShadowElevation: Dp = BottomSheetDefaults.Elevation,
sheetDragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
sheetSwipeEnabled: Boolean = true,
topBar: @Composable (() -> Unit)? = null,
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
containerColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(containerColor),
content: @Composable (PaddingValues) -> Unit
) {
val peekHeightPx = with(LocalDensity.current) {
sheetPeekHeight.roundToPx()
}
ReplicatedBottomSheetScaffoldLayout(
modifier = modifier,
topBar = topBar,
body = content,
snackbarHost = {
snackbarHost(scaffoldState.snackbarHostState)
},
sheetPeekHeight = sheetPeekHeight,
sheetOffset = { scaffoldState.bottomSheetState.requireOffset() },
sheetState = scaffoldState.bottomSheetState,
containerColor = containerColor,
contentColor = contentColor,
bottomSheet = { layoutHeight ->
ReplicatedStandardBottomSheet(
state = scaffoldState.bottomSheetState,
peekHeight = sheetPeekHeight,
sheetMaxWidth = sheetMaxWidth,
sheetSwipeEnabled = sheetSwipeEnabled,
calculateAnchors = { sheetSize ->
val sheetHeight = sheetSize.height
DraggableAnchors { // error: Cannot access 'DraggableAnchors': it is internal in 'androidx. compose. material3'
if (!scaffoldState.bottomSheetState.skipPartiallyExpanded) { // error: Cannot access 'skipPartiallyExpanded': it is internal in 'SheetState'
SheetValue.PartiallyExpanded at (layoutHeight - peekHeightPx).toFloat() // error: Cannot access 'DraggableAnchorsConfig': it is internal in 'androidx. compose. material3'
}
if (sheetHeight != peekHeightPx) {
SheetValue.Expanded at maxOf(layoutHeight - sheetHeight, 0).toFloat() // error: Cannot access 'DraggableAnchorsConfig': it is internal in 'androidx. compose. material3'
}
if (!scaffoldState.bottomSheetState.skipHiddenState) { // error: Cannot access 'skipHiddenState': it is internal in 'SheetState'
SheetValue.Hidden at layoutHeight.toFloat() // error: Cannot access 'DraggableAnchorsConfig': it is internal in 'androidx. compose. material3'
}
}
},
shape = sheetShape,
containerColor = sheetContainerColor,
contentColor = sheetContentColor,
tonalElevation = sheetTonalElevation,
shadowElevation = sheetShadowElevation,
dragHandle = sheetDragHandle,
content = sheetContent
)
}
)
}
๋ด์ฉ ์ถ๊ฐ ์์ .
#3 ReplicatedBottomSheetScaffoldLayout.kt
#3-1 ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ฐ์ ธ์จ ์ฝ๋
package com.example.replicatedbottomsheetscaffold
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
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.unit.Dp
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplicatedBottomSheetScaffoldLayout(
modifier: Modifier,
topBar: @Composable (() -> Unit)?,
body: @Composable (innerPadding: PaddingValues) -> Unit,
bottomSheet: @Composable (layoutHeight: Int) -> Unit,
snackbarHost: @Composable () -> Unit,
sheetPeekHeight: Dp,
sheetOffset: () -> Float,
sheetState: SheetState,
containerColor: Color,
contentColor: Color,
) {
// b/291735717 Remove this once deprecated methods without density are removed
val density = LocalDensity.current
SideEffect {
sheetState.density = density // error: Cannot access 'density': it is internal in 'SheetState'
}
SubcomposeLayout { constraints ->
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val sheetPlaceable = subcompose(BottomSheetScaffoldLayoutSlot.Sheet) {
bottomSheet(layoutHeight)
}[0].measure(looseConstraints)
val topBarPlaceable = topBar?.let {
subcompose(BottomSheetScaffoldLayoutSlot.TopBar) { topBar() }[0]
.measure(looseConstraints)
}
val topBarHeight = topBarPlaceable?.height ?: 0
val bodyConstraints = looseConstraints.copy(maxHeight = layoutHeight - topBarHeight)
val bodyPlaceable = subcompose(BottomSheetScaffoldLayoutSlot.Body) {
Surface(
modifier = modifier,
color = containerColor,
contentColor = contentColor,
) { body(PaddingValues(bottom = sheetPeekHeight)) }
}[0].measure(bodyConstraints)
val snackbarPlaceable = subcompose(BottomSheetScaffoldLayoutSlot.Snackbar, snackbarHost)[0]
.measure(looseConstraints)
layout(layoutWidth, layoutHeight) {
val sheetOffsetY = sheetOffset().roundToInt()
val sheetOffsetX = Integer.max(0, (layoutWidth - sheetPlaceable.width) / 2)
val snackbarOffsetX = (layoutWidth - snackbarPlaceable.width) / 2
val snackbarOffsetY = when (sheetState.currentValue) {
SheetValue.PartiallyExpanded -> sheetOffsetY - snackbarPlaceable.height
SheetValue.Expanded, SheetValue.Hidden -> layoutHeight - snackbarPlaceable.height
}
// Placement order is important for elevation
bodyPlaceable.placeRelative(0, topBarHeight)
topBarPlaceable?.placeRelative(0, 0)
sheetPlaceable.placeRelative(sheetOffsetX, sheetOffsetY)
snackbarPlaceable.placeRelative(snackbarOffsetX, snackbarOffsetY)
}
}
}
private enum class BottomSheetScaffoldLayoutSlot { TopBar, Body, Sheet, Snackbar }
๋ด์ฉ ์ถ๊ฐ ์์ .
#4 ReplicatedStandardBottomSheet.kt
#4-1 ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ฐ์ ธ์จ ์ฝ๋
package com.example.replicatedbottomsheetscaffold
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection // error: Cannot access 'ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection': it is internal in 'androidx. compose. material3'
import androidx.compose.material3.DraggableAnchors // error: Cannot access 'DraggableAnchors': it is internal in 'androidx. compose. material3'
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.SheetValue.Expanded
import androidx.compose.material3.SheetValue.Hidden
import androidx.compose.material3.SheetValue.PartiallyExpanded
import androidx.compose.material3.Strings // error: Cannot access 'Strings': it is internal in 'androidx. compose. material3'
import androidx.compose.material3.Surface
import androidx.compose.material3.anchoredDraggable // error: Cannot access 'anchoredDraggable': it is internal in 'androidx. compose. material3'
import androidx.compose.material3.getString // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.semantics.collapse
import androidx.compose.ui.semantics.dismiss
import androidx.compose.ui.semantics.expand
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplicatedStandardBottomSheet(
state: SheetState,
calculateAnchors: (sheetSize: IntSize) -> DraggableAnchors<SheetValue>, // error: Cannot access 'DraggableAnchors': it is internal in 'androidx. compose. material3'
peekHeight: Dp,
sheetMaxWidth: Dp,
sheetSwipeEnabled: Boolean,
shape: Shape,
containerColor: Color,
contentColor: Color,
tonalElevation: Dp,
shadowElevation: Dp,
dragHandle: @Composable (() -> Unit)?,
content: @Composable ColumnScope.() -> Unit
) {
val scope = rememberCoroutineScope()
val orientation = Orientation.Vertical
Surface(
modifier = Modifier
.widthIn(max = sheetMaxWidth)
.fillMaxWidth()
.requiredHeightIn(min = peekHeight)
.nestedScroll(
remember(state.anchoredDraggableState) { // error: Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( // error: Cannot access 'ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection': it is internal in 'androidx. compose. material3
sheetState = state,
orientation = orientation,
onFling = { scope.launch { state.settle(it) } } // error: Cannot access 'settle': it is internal in 'SheetState'
)
}
)
.anchoredDraggable( // error: Cannot access 'anchoredDraggable': it is internal in 'androidx. compose. material3'
state = state.anchoredDraggableState,
orientation = orientation,
enabled = sheetSwipeEnabled
)
.onSizeChanged { layoutSize ->
val newAnchors = calculateAnchors(layoutSize)
val newTarget = when (state.anchoredDraggableState.targetValue) { // Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
Hidden, PartiallyExpanded -> PartiallyExpanded
Expanded -> {
if (newAnchors.hasAnchorFor(Expanded)) Expanded else PartiallyExpanded // Cannot access 'DraggableAnchors': it is internal in 'androidx. compose. material3'
}
}
state.anchoredDraggableState.updateAnchors(newAnchors, newTarget) // Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
},
shape = shape,
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
) {
Column(Modifier.fillMaxWidth()) {
if (dragHandle != null) {
val partialExpandActionLabel =
getString(Strings.BottomSheetPartialExpandDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
val dismissActionLabel = getString(Strings.BottomSheetDismissDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
val expandActionLabel = getString(Strings.BottomSheetExpandDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
Box(
Modifier
.align(CenterHorizontally)
.semantics(mergeDescendants = true) {
with(state) {
// Provides semantics to interact with the bottomsheet if there is more
// than one anchor to swipe to and swiping is enabled.
if (anchoredDraggableState.anchors.size > 1 && sheetSwipeEnabled) { // error: Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
if (currentValue == PartiallyExpanded) {
if (anchoredDraggableState.confirmValueChange(Expanded)) { // error: Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
expand(expandActionLabel) {
scope.launch { expand() }; true
}
}
} else {
if (anchoredDraggableState.confirmValueChange( // error: Cannot access 'anchoredDraggableState': it is internal in 'SheetState'
PartiallyExpanded
)
) {
collapse(partialExpandActionLabel) {
scope.launch { partialExpand() }; true
}
}
}
if (!state.skipHiddenState) { // error: Cannot access 'skipHiddenState': it is internal in 'SheetState'
dismiss(dismissActionLabel) {
scope.launch { hide() }
true
}
}
}
}
},
) {
dragHandle()
}
}
content()
}
}
}
๋ด์ฉ ์ถ๊ฐ ์์ .
#4-2 ์๋ฉํฑ ์ฝ๋
#4-1์ ์๋น ๋ถ๋ถ์ด Semantics(์๋ฉํฑ) ์ฝ๋์ ๋ ์๋ฌ๋ค. ์๋ฉํฑ ์ ๋ณด์ internal ์ ๊ทผ์ ์ด์๊ฐ ๋ถ์ด์๊ธฐ ๋๋ฌธ์ด๋ฆฌ๋ผ. ์๋ฉํฑ์ ํ ๋ง๋๋ก, ์ค๋งํธํฐ'์' ์ํ UI๋ค. ์ค๋งํธํฐ์ด ํ๋ฉด ์ ์์์ ์ค๋ช ๊ณผ ์ํ ๋ฑ์ ์๊ฒํ๋ ๊ฒ์ด๋ค. ์ด๊ฒ ๋ฌด์จ ์๋ฏธ๊ฐ ์์๊น? ๋ถ๋ช ์ผ๋ฐ์ ์ธ ์ํฉ์์๋ ์๋ฏธ๊ฐ ์๋ค. ํ์ง๋ง, ์ฌ์ฉ์์๊ฒ ์๊ฐ ์ฅ์ ๊ฐ ์๋ค๋ฉด ์ด๋ฐ Sematics๊ฐ ๋์์ด ๋๋ค. ์์ฑ ๋ช ๋ น ๋ฑ์ผ๋ก UI๋ฅผ ์กฐ์ํ๋ค๊ณ ์๊ฐํด๋ณด์. ์ฌ์ฉ์๊ฐ "์ ํ ๋ฒํผ์ ๋๋ฌ์ค"๋ผ๊ณ ๋งํ๋ค. ์ค๋งํธํฐ์ "์ ํ ๋ฒํผ"์ด ๋ฌด์์ธ์ง ์์์ผ ํ๋ค. ์๋ฉํฑ์ "์ ํ ๋ฒํผ"๊ณผ ๊ฐ์ ์ ๋ณด๋ฅผ ํ๋ก๊ทธ๋๋ฐ ๋จ๊ณ์์ ๋ฏธ๋ฆฌ ์ ๋ ฅํ๋ ๊ฒ์ด๋ค. ์ค๋งํธํฐ์ด ์ธ ์ ์๊ฒ ๋ง์ด๋ค. ๋ฐ๋ผ์ ์๋ฉํฑ์ ์ค๋งํธํฐ'์' ์ํ UI๋ค.
val partialExpandActionLabel = "TODO: internal ์๋ฉํฑ ๋ผ๋ฒจ ๋์ , ๋ด๊ฐ ์ง์ ์จ๋ฃ์ ๊ฒ 1" // ์๋ ์ฝ๋: getString(Strings.BottomSheetPartialExpandDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
val dismissActionLabel = "TODO: internal ์๋ฉํฑ ๋ผ๋ฒจ ๋์ , ๋ด๊ฐ ์ง์ ์จ๋ฃ์ ๊ฒ 2" // ์๋ ์ฝ๋: getString(Strings.BottomSheetDismissDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
val expandActionLabel = "TODO: internal ์๋ฉํฑ ๋ผ๋ฒจ ๋์ , ๋ด๊ฐ ์ง์ ์จ๋ฃ์ ๊ฒ 3" // ์๋ ์ฝ๋: getString(Strings.BottomSheetExpandDescription) // error: Cannot access 'getString': it is internal in 'androidx. compose. material3'
partialExpandActionLabel, dismissActionLabel, expandActionLabel๋ internal ์๋ฉํฑ ๋ผ๋ฒจ ์คํธ๋ง์ด ํ ๋น๋๋ค. ๋ฐ๋ผ์ (๋นจ๊ฐ ์ค์ ์์ ๊ธฐ ์ํด์) ์์๋ก ์๋ฌด ๋ผ๋ฒจ์ ๋ถ์ฌํ๋ค.
#4-3 AnchoredDraggable
๋ฑ ๋ด๋, Drag ๋์์ ์์์ผ ์ดํด ๊ฐ๋ฅํ ์ฝ๋๋ค. ๊ทธ๋ฌ๋ ๋ Darg ์ฝ๋์ ๋์ ๊ธฐ์ ๋ฅผ ์ ๋ชจ๋ฅธ๋ค. ์ ๋ชจ๋ฅด๋ ์ฝ๋๋ฅผ ๊ฒํฅ๊ธฐ์์ผ๋ก ์ดํดํ๋ '์ฒ'ํ๋ ๊ฑด ์ฃ์ ์ด๋ค. ๋ฐ๋์ ๊ทธ ์ ๋ณด๋ฅผ ์น๋ฃจ๊ฒ ๋๋ค. ๊ทธ๋์ ์์ Drag ๋์์ ๋ํด ๊ณต๋ถํ๋ ๊ฒ์๊ธ์ ๋ง๋ค์๋ค.
[Android] Pointer input - Drag
#1 ๊ฐ์#1-1 ๊ณต์ ๋ฌธ์ Swipeable์์ AnchoredDraggable๋ก ์ด์ | Jetpack Compose | Android Developers์ด ํ์ด์ง๋ Cloud Translation API๋ฅผ ํตํด ๋ฒ์ญ๋์์ต๋๋ค. Swipeable์์ AnchoredDraggable๋ก ์ด์ ์ปฌ๋ ์ ์ ์ฌ์ฉ
kenel.tistory.com
๋ด์ฉ ์ถ๊ฐ ์์ .
#3 ํ์ผ๋ด
์ถ๊ฐ ์์
'๊ฐ๋ฐ ์ผ์ง ๐ป > Nutri Capture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Nutri Capture ํ๋ก ํธ์๋ - 'ํผ์' ์์ด์ฝ ์์ ์ ์ฉ (0) | 2025.03.25 |
---|---|
Nutri Capture ํ๋ก ํธ์๋ - 'ํผ์' ์์ด์ฝ ๊ตฌํ (0) | 2025.03.20 |
Nutri Capture ๋ฐฑ์๋ - Hilt ๋์ (0) | 2025.02.01 |
Nutri Capture ํ๋ก ํธ์๋ - windowInsetsPadding() (0) | 2025.01.29 |
Nutri Capture - ์ฝ๋ ์ ๋ฆฌ (0) | 2025.01.29 |