PROBLEM:
I have a screen with one column full of (too many) text fields which could be packed into categories - to make it easier for the user to see what he is working with.
My Idea is that I could make a form of tabs that the text fields will be put into, then only the tab that the user is currently working on is open and the others are closed but visible.
I would like to make it something like a vertical View Pager but where you can see all the elements to scroll over.
To change the view you would either scroll or click the category to access.
Only one tab will always be open.
QUESTION:
However I don't know, what this is called or how to do this in Jetpack Compose - So I want to ask if someone can point me in the right direction.
CodePudding user response:
You can store selected item index with rememberSaveable
, and then you can use AnimatedVisibility
to animate opened view. To add swipes I've created Modifier.swipeableTopBottom
, see more details about how it works in this answer.
var selectedItem by rememberSaveable { mutableStateOf(0) }
val itemsCount = 10
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.padding(10.dp)
.swipeableTopBottom(
onTop = {
selectedItem = (selectedItem - 1).coerceIn(0, itemsCount)
},
onBottom = {
selectedItem = (selectedItem 1).coerceIn(0, itemsCount)
},
)
) {
repeat(itemsCount) { i ->
Column(
Modifier
.clickable {
selectedItem = i
}
.fillMaxWidth()
.background(Color.DarkGray)
.padding(10.dp)
) {
Text("Title $i")
AnimatedVisibility(visible = i == selectedItem) {
Column {
repeat(5) { j ->
Text("Subtitle $i $j")
}
}
}
}
}
}
Modifier.swipeableTopBottom
:
fun Modifier.swipeableTopBottom(onTop: () -> Unit, onBottom: () -> Unit): Modifier = composed {
var width by rememberSaveable { mutableStateOf(0f) }
val swipeableState = rememberSwipeableState(
SwipeDirection.Initial,
animationSpec = snap()
)
val anchorWidth = remember(width) {
if (width == 0f) {
1f
} else {
width
}
}
val scope = rememberCoroutineScope()
if (swipeableState.isAnimationRunning) {
DisposableEffect(Unit) {
onDispose {
when (swipeableState.currentValue) {
SwipeDirection.Top -> {
onTop()
}
SwipeDirection.Bottom -> {
onBottom()
}
else -> {
return@onDispose
}
}
scope.launch {
swipeableState.snapTo(SwipeDirection.Initial)
}
}
}
}
return@composed Modifier
.onSizeChanged { width = it.width.toFloat() }
.swipeable(
state = swipeableState,
anchors = mapOf(
0f to SwipeDirection.Top,
anchorWidth / 2 to SwipeDirection.Initial,
anchorWidth to SwipeDirection.Bottom,
),
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Vertical
)
}
private enum class SwipeDirection(val raw: Int) {
Top(0),
Initial(1),
Bottom(2),
}
Result: