Android Kaki

Build beautiful, usable products using required Components for Android.

Customized ModalBottomSheet for Doc 2 in Compose | by Roman Kamyshnikov | Might 2023


Roman Kamyshnikov

On this article, we’ll have a look at implementing a Customized Methodology backside web page in Compose for Doc 2. However first, let’s begin with a not-so-short introduction to why you wish to do that. there, as a result of there’s already a backside sheet of the sheet aspect technique in the usual library.

Why use customized implementation?

Even composing has been round for some time, and with every launch we see increasingly elements being added to the usual library, typically their APIs aren’t perfect anymore. In my view, the modal sheet is a type of elements. In Doc 2, you may show a modal sheet utilizing ModalBottomSheetLayout:

@Composable
enjoyable Material2SheetExample() {
val coroutineScope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
ModalBottomSheetLayout(
sheetContent = { /* sheet content material... */ },
sheetState = sheetState,
) {
Button(
onClick = { coroutineScope.launch { sheetState.present() } }
) { Textual content("Present sheet") }
}
}

And whereas this definitely works in lots of instances, it has some issues:

  1. You will need to use ModalBottomSheetLayout as the unique composable of the display. This generally is a drawback for advanced screens the place the Composable root acts as a container containing a number of nested Composable features, every of which might have its personal ViewModel. Pressured to make use of a selected composability as a result of the basis might not all the time be an choice and makes the composable root conscious of the logic related to its kids.
  2. To show the sheet, you will need to name sheetState.present(). This makes it more durable to regulate the visibility of the display from the ViewModel as it’s important to depend on LaunchedEffect or comparable strategies to vary the visibility of the sheet based mostly on the ViewModel’s state, including boilerplate code and including pointless complexity.

Higher API in Docs 3

It might be significantly better to have an API, much like AlertDialogthe place you may simply use a boolean within the state to regulate the visibility of the dialog or, in our case, a backside sheet:

@Composable
enjoyable AlertDialogExample() {
var showDialog by bear in mind { mutableStateOf(false) }
Button(
onClick = { showDialog = true }
) { Textual content("Present sheet") }
if (showDialog) {
AlertDialog(...)
}
}

The AlertDialog the composable perform may be known as from anyplace, it renders appropriately by displaying a bit of code that takes up the whole display and it is also very straightforward to vary its visibility based mostly on the state straightforward. In reality, that is the precise strategy that was used for the Materials 3 model of ModalBottomSheet:

@Composable
enjoyable Material3SheetExample() {
var showSheet by bear in mind { mutableStateOf(false) }
Button(
onClick = { showSheet = true }
) { Textual content("Present sheet") }
if (showSheet) {
ModalBottomSheet( // androidx.compose.material3.ModalBottomSheet
onDismissRequest = { showSheet = false },
) {
// sheet content material...
}
}
}

The unhappy information is that, as talked about earlier, it is a Materials 3 part and that might not be an choice in case your app makes use of Materials 2. Additionally, on the time of this writing. ModalBottomSheet is just not out there within the steady model of the library (however it’s included in 1.1.0-rc01 model).

That mentioned, Jetpack Compose provides a number of flexibility and a wealthy public API that makes customized creation ModalBottomSheet It isn’t that difficult, so let’s get straight to the implementation particulars of our customized sheet!

We would like our sheet to have an identical API to the usual API from Docs 3 to permit us simpler migration sooner or later, and we’ll begin by defining 3 doable states that the web page Our calculator might have:

enum class SheetPosition { HIDDEN, PARTIALLY_EXPANDED, EXPANDED }

To manage the gestures used to maneuver and take away the sheet, we are going to use Modifier.swipeable, however earlier than going into any particulars on the way it works, let’s first create a state for our sheet. Moreover storing the SwipeableStatewhich we are going to use for Modifier.swipablewe additionally must retailer a boolean worth to regulate if we’re displaying {a partially} expanded state:

class SheetState(
val skipPartiallyExpanded: Boolean,
initialPosition: SheetPosition = SheetPosition.HIDDEN,
confirmPositionChange: (SheetPosition) -> Boolean = { true },
) {
val swipeableState: SwipeableState = SwipeableState(
initialValue = initialPosition,
confirmStateChange = confirmPositionChange,
)
}

Notice that the preliminary place is HIDDENthe explanation for that is that we wish to change the state manually to both PARTIALLY_EXPANDED or EXPANDED after including the sheet to the desktop in order that the sheet seems with the animation.

Since we would like the sheet’s state to persist throughout configuration adjustments, let’s additionally create a perform to initialize and bear in mind the state. For this we are going to use rememberSaveable. Notice that based on Compose API pointerswe are going to add bear in mind prefix for perform title:

@Composable
enjoyable rememberSheetState(
skipPartiallyExpanded: Boolean = false,
confirmPositionChange: (SheetPosition) -> Boolean = { true },
): SheetState {
return rememberSaveable(
skipPartiallyExpanded, confirmPositionChange,
saver = ...
) {
SheetState(
skipPartiallyExpanded = skipPartiallyExpanded,
initialPosition = SheetPosition.HIDDEN,
confirmPositionChange = confirmPositionChange,
)
}
}

From SheetState is a customized class, we might want to present a Saver instance to have the ability to save and restore state throughout reorders:

class SheetState(...) {
companion object {
enjoyable Saver(
skipPartiallyExpanded: Boolean,
confirmPositionChange: (SheetPosition) -> Boolean,
) = Saver(
save = { ... },
restore = { savedValue -> ... }
)
}
}

To carry out Saverwe have to specify Authentic object that we’ll save and restore utilizing save And restore perform (in our case SheetState) and the precise information we are going to save – by default this may be something that may be saved within the Bundle and we are going to use the present place of the sheet as that is the one parameter of the state that may be modified by the person.

Give save perform, we are going to simply get the present worth from SwipeableState:

save = { sheetState: SheetState -> sheetState.swipeableState.currentValue }

IN restorewe are going to get save SheetPosition and create a brand new occasion of SheetState:

restore = { savedValue: SheetPosition ->
SheetState(
skipPartiallyExpanded = skipPartiallyExpanded,
initialPosition = savedValue,
confirmPositionChange = confirmPositionChange
)
}

Make the sheet swipeable

If we wish to all the time present the sheet expanded and take up full display, add swipeable The modifier could be a quite simple process and an instance implementation was offered in an article by Angelo Marchesin together with an awesome rationalization of the way it works.

In brief, we have to present an anchor map to swipeablethat may decide the peak of the sheet for any given state and use Modifier.offset to vary the place of the content material based mostly on the phrase worth SwipeableState. The primary drawback is that to calculate the anchor worth we have to know the entire out there top and the peak of the sheet itself.

For the entire top, BoxWithConstraints can be utilized to get top with BoxWithContsraintsScope.constraints.maxHeightand for the peak of the sheet itself we are able to use onPlaced Modifier to seek out out the peak of the sheet content material after it’s set. We’ll use a helper state variable sheetHeightPxinitially set to UNKNOWN (a relentless with worth -1) and simply add swipeable And offset modified after we discover out the precise measurement:

BoxWithConstraints(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier.fillMaxSize()
) {
var sheetHeightPx by bear in mind(sheetState) { mutableStateOf(UNKNOWN) }
val anchors: Map by bear in mind(...) { ... }
Floor(
modifier = Modifier
.fillMaxWidth()
.onPlaced { sheetHeightPx = it.measurement.top }
.let {
if (sheetHeightPx == UNKNOWN) {
it
} else {
it.swipeable(
state = sheetState.swipeableState,
anchors = anchors,
orientation = Orientation.Vertical
)
.offset {
IntOffset(
x = 0,
y = sheetState.swipeableState.offset.worth
.toInt()
.coerceIn(0, sheetHeightPx)
)
}
}
}
) {
// sheet content material goes right here
}
}

The anchors for swipeableState shall be calculated based mostly on whole top constaints.maxHeight, sheetHeightPx and whether or not we would like (and may) present the partially expanded state:

val anchors: Map by bear in mind(
sheetHeightPx,
constraints.maxHeight,
sheetState.skipPartiallyExpanded,
) {
mutableStateOf(
if (sheetHeightPx == UNKNOWN) {
emptyMap()
} else {
buildMap {
put(0f, SheetPosition.EXPANDED)
if (constraints.maxHeight / 2 < sheetHeightPx && sheetState.skipPartiallyExpanded.not()) {
put((sheetHeightPx - constraints.maxHeight / 2).toFloat(), SheetPosition.PARTIALLY_EXPANDED)
}
put(sheetHeightPx.toFloat(), SheetPosition.HIDDEN)
}
}
)
}

To set off the animation we are able to use LaunchedEffect and animate both PARTIALLY_EXPANDED or come EXPANDED state once we know the peak of the plate. We’ll animate provided that present place is HIDDEN to keep away from altering the place of the sheet after altering the configuration.

// Present the sheet with animation as soon as we all know it is measurement
LaunchedEffect(sheetHeightPx) {
if (sheetHeightPx != UNKNOWN && sheetState.swipeableState.currentValue == SheetPosition.HIDDEN) {
val goal = if (anchors.containsValue(SheetPosition.PARTIALLY_EXPANDED)) {
SheetPosition.PARTIALLY_EXPANDED
} else {
SheetPosition.EXPANDED
}
sheetState.swipeableState.animateTo(goal)
}
}

The final a part of work with swipeable is to permit the person to dismiss the sheet when swiping down. As a result of the preliminary state of our sheet is HIDDENwe are going to ignore that state the primary time, however name onDismissRequest() then in order that the dad or mum can react to it accordingly.

@Composable
enjoyable ModalBottomSheet(
onDismissRequest: () -> Unit,
...
) {
...
// Forestall the sheet from being dismissed in the course of the preliminary animation when the state is HIDDEN
var wasSheetShown by bear in mind(sheetState) { mutableStateOf(false) }
LaunchedEffect(sheetState.swipeableState.currentValue) {
when (sheetState.swipeableState.currentValue) {
SheetPosition.PARTIALLY_EXPANDED -> wasSheetShown = true
SheetPosition.EXPANDED -> wasSheetShown = true
SheetPosition.HIDDEN -> if (wasSheetShown) onDismissRequest()
}
}
}

John Wick: Chapter 4 (FREE) FULLMOVIE The Super Mario Bros Movie avatar 2 Where To Watch Creed 3 Free At Home Knock at the Cabin (2023) FullMovie Where To Watch Ant-Man 3 and the Wasp: Quantumania Cocaine Bear 2023 (FullMovie) Scream 6 Full Movie
Updated: May 9, 2023 — 7:11 pm

Leave a Reply

Your email address will not be published. Required fields are marked *

androidkaki.com © 2023 Android kaki