Android Compose - Align child views inside Box by parent view - android-jetpack-compose

I have tried severals techniques including nested Box, Vertical/Horizontal alignment to the Child/Parent view without success, I'm trying to align the Row View in the top start Corner of the inner parent (Lottie view)
Important
the parent (column) need to be fillMaxSize and centered Vertically for other non-related elements
Column(modifier = Modifier.fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally) {
Header()
Box(contentAlignment = Alignment.CenterStart) {
LottieView()
Row(horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Top) {
Button(onClick = {
.....
}) {
Text(text = "Open")
}
}
}
}

Related

ScrollableTabRow Jetpack Compose

How can I display an Icon to the right of a ScrollableTabRow so the ScrollableTabRow scrolls using the space to the left of the icon?
By placing them in a Row the ScrollableTabRow appears to take up the entire horizontal space and the Icon is not displayed.
Would like something like this:
You could place your ScrollableTabRow and your Icon in a Row and give your ScrollableTabRow 'weight(1f)'. That way the icon stays in place and TabRow can be scrolled on the left of the icon.
Row {
ScrollableTabRow(
selectedTabIndex = selectedTabIndex,
contentColor = Color.White,
edgePadding = 0.dp,
modifier = Modifier.weight(1f)
) {
tabs.forEachIndexed { tabIndex, tab ->
Tab(
selected = selectedTabIndex == tabIndex,
onClick = { onTabClick(tabIndex) },
text = { Text(text = tab) }
)
}
}
Icon(...)
}

Custom TopAppBarDefaults ScrollBehavior in Compose

I'm currently working on a animated TopAppBar using Material3 LargeTopAppBar and Compose.
I'm using TopAppBarDefaults.exitUntilCollapsedScrollBehavior to make the AppBar collapse when scrolled.
When flung downwards it snaps to the expanded view, but when flung upwards it doesn't snap and more often than not just stays in between the two states(collapsed & expanded).
Here's some of the code:
val topappbarstate = rememberTopAppBarState()
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
decayAnimationSpec, topappbarstate)
Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = {
LargeHeader(
title = "Title",
subtitle = "Subtitle",
scrollBehavior
)
}, content = { innerPadding ->
LazyColumn(contentPadding = innerPadding,) {
items(count = 100) {
Box(Modifier.fillMaxWidth()) {
Text(text = "$it")
}
}
}
},
)
}
The LargeHeader is a function that contains a Motionlayout to animate the Title and Subtitle in the LargeTopAppBar to swing upwards when scrolled but has no impact on the flinging animation of the TopAppBar.
Is there a way to customize this flinging snap of the actual TopAppBar?
Sorry for my english and thanks in advance!

Scrolling LazyColumn items makes ScaffoldBottomSheet collapses

I am going to implement a screen with jetpack compose containing a ScaffoldBottomSheet and a LazyColumn inside the bottom sheet's content. I also want the bottom sheet's height to be fixed and users can't collapse it. In order to do that I disable the sheet gestures and give the height of 600.dp for the content of the bottom sheet. But when I scroll items of the lazy column the bottom sheet scrolls down and finally collapses.
Here are my codes:
#ExperimentalMaterialApi
#Composable
fun TestScreen(
testViewModel: TestViewModel = hiltViewModel()
) {
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(
BottomSheetValue.Expanded
)
)
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
Box(modifier = Modifier.fillMaxWidth().height(600.dp)) {
LazyColumn() {
items(20) {
Text(text = "this is for test", modifier = Modifier
.padding(start = 20.dp, top = 20.dp)
.fillMaxWidth()
.height(50.dp))
}
}
}
},
sheetPeekHeight = 0.dp,
sheetShape = RoundedCornerShape(topEnd = 52.dp, topStart = 52.dp),
backgroundColor = AppColor.ThemeColor.BACKGROUND,
sheetGesturesEnabled = false
) {
}
}

Avoid one frame delay in calculating child size

I have to create a reusable component that tries to achieve this goal: I have a column that can have content that's larger than the screen height. On the bottom of the screen we have panel with gradient background that can contain button or something else (it's basically a slot in the component). This bottom panel have to be always visible on the screen, and in case of the column being bigger than screen - bottom panel have to be on the top of this column. Gradient background does a nice UX effect so user knows what is going on. It looks like that:
I have that solved, but here's the challenge. The column content have to be scrollable to be on top of the bottom panel when scrolled to the end. Current solution I have is to add a spacer on the bottom of this column. This spacer have the calculated height of the bottom parent. And here's the issue - right now we have calculation done in onSizeChanged which basically results in additional frame needed for the spacer to have correct size.
We did not observe any negative impact of that performance or UX wise. The spacer height calculation never does anything that user can see, but I still want to solve that properly.
AFAIK this can be done using custom Layout, but that seems a little bit excessive for what I want to achieve. Is there another way to do this properly?
Current solution:
#Composable
fun FloatingPanelColumn(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
contentHorizontalAlignment: Alignment.Horizontal = Alignment.Start,
bottomPanelContent: #Composable ColumnScope.() -> Unit,
content: #Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
var contentSize by remember {
mutableStateOf(1)
}
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.then(contentModifier),
horizontalAlignment = contentHorizontalAlignment,
) {
content()
val contentSizeInDp = with(LocalDensity.current) { contentSize.toDp() }
Spacer(modifier = Modifier.height(contentSizeInDp))
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.onSizeChanged {
contentSize = it.height
}
.wrapContentHeight()
.align(Alignment.BottomStart)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0x00FAFCFF),
Color(0xFFF6F9FB),
)
)
),
content = bottomPanelContent
)
}
}
The best way to depend on an other view size during layout is using SubcomposeLayout:
SubcomposeLayout { constraints ->
// subcompose the view you need to measure first
val bottomPanel = subcompose("bottomPanel") {
Column(
// ...
)
}[0].measure(constraints)
// use calculated value in next views layout, like bottomPanel.height
val mainList = subcompose("mainList") {
LazyColumn(
contentPadding = PaddingValues(bottom = bottomPanel.height.toDp())
) {
// ...
}
}[0].measure(constraints)
layout(mainList.width, mainList.height) {
mainList.place(0, 0)
bottomPanel.place(
(mainList.width - bottomPanel.width) / 2,
mainList.height - bottomPanel.height
)
}
}

Is ScrollableTabRow lazy

If i create like 1000 Tabs within the ScrollableTabRow will it only compose the Tabs which are visible to the screen or`will compose everytime non-visible Tabs if something changes?
Like same as a LazyColumn composes only visible items.
I've written a minimal example in which i'm just "repeat"ing the same composable a lot of times - with no item{}-like DSL available like the lazy-components have.
#Composable
#Preview
fun MinimalTabExample() {
var selectedTabIndex by remember { mutableStateOf(0) }
ScrollableTabRow(selectedTabIndex = selectedTabIndex) {
repeat(60) { tabNumber ->
Tab(
selected = selectedTabIndex == tabNumber,
onClick = { selectedTabIndex = tabNumber },
text = { Text(text = "Tab #$tabNumber") }
)
}
}
}
Also, if you look into the ScrollableTabRow component you'll find that all the tabs are measured right at the beginning: (tabs are the compose-tabs created above)
val tabPlaceables = subcompose(TabSlots.Tabs, tabs)
.map { it.measure(tabConstraints) }
I guess this is implemented this way due to the height-calculation: "Make the height as high as the highest component"
tabPlaceables.forEach {
layoutWidth += it.width
layoutHeight = maxOf(layoutHeight, it.height)
}

Resources