I am trying to show an Ad banner at the bottom which is globally displayed. That means that I want to stick there when I navigate throughout the app.
So far I've tried adding the NavHost and the AdView inside a Column, hoping that the navigation with the linked pages will just take the remaining space and the bottom view will stick there. However the NavHost takes the whole screen and the AdView has zero height. When I navigate the second view, the AdView is still inside the view hierarchy but still with zero height.
I've tried changing all kinds of setup, adding .weight(1f) to the Ad view, changing Column Arrangement, .fillMaxSize(), .fillMaxHeight(), .minHeight(), fixed height, etc.
I might need to look into different layout for achieving this result, but not sure in which direction to search. The Ad works well, I tried displaying without the column and is shown correctly in the center of the screen.
Another variant I've tried is to display it without column, add bottom spacing to the NavHost then position the AdView to the bottom somehow. With this variant I haven't been able to position it to bottom.
Any help would be appreciated!
Column() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = NavigationRoutes.Home
) {
composable(NavigationRoutes.Home) { HomeView(navController = navController) }
composable(
NavigationRoutes.Books,
arguments = listOf(navArgument("mode") { type = NavType.IntType })
) { backStackEntry ->
val rawMode = backStackEntry.arguments?.getInt("mode") ?: 0
var mode: ContentMode = if (rawMode == 0) {
ContentMode.READING
} else {
ContentMode.QUIZ
}
BooksView(navController = navController, mode = mode)
}
}
AndroidView(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Max)
.weight(1f),
factory = { context ->
AdView(context).apply {
setAdSize(AdSize.BANNER)
adUnitId = AdIdentifiers.GlobalBanner.adID
loadAd(AdRequest.Builder().build())
}
}
)
}
It might be the issue of any child Composable of NavHost having Modifier.fillmaxSize(). You can set a Modifier for NavHost which let's you define how much space it will cover
Column(modifier = Modifier.fillMaxSize()){
NavHost(modifier = Modifier.fillmaxWidth).weight(1f){}
AdView(modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Max)
)
}
You can put AndroidView in Box and give androidView height as bottom padding values in screens.
NavHost(
//
){
//
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
){
AndroidView()
}
Related
I'm currently trying to recreate a behavior, upon adding a new element to a LazyColumn the items start shifting to the right, in order to represent a Tree and make the elements easier to read.
The mockup in question:
Documentation
Reading through the documentation of Jetpack Compose in Lists and grids I found the following.
Keep in mind that cases where you’re nesting different direction layouts, for example, a scrollable parent Row and a child LazyColumn, are allowed:
Row(
modifier = Modifier.horizontalScroll(scrollState)
) {
LazyColumn {
// ...
}
}
My implementation
Box(Modifier.padding(start = 10.dp)) {
Row(
modifier = Modifier
.horizontalScroll(scrollState)
.border(border = BorderStroke(1.dp, Color.Black))
) {
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
for (i in 0..25) {
item {
OptionItem(Modifier.padding(start = (i*20).dp))
}
item {
TaskItem(Modifier.padding(start = (i*10).dp))
}
}
}
}
.
.
.
}
OptionItem represents the element with the dot at the beginning, and TaskItem the other one.
When testing the LazyColumn, it appears as if instead of having a fixed size, the size of the column starts growing just after the elements have gone outside the screen, this causes a strange effect.
As you can see in the GIF, the width of the column starts increasing after the elements no longer fit in the screen.
The Question
I want to prevent this effect from happening, so is there any way I could maintain the width of the column to the maximum all the time?
The reason that applying a simple fillMaxWidth will not work because you are telling a composable to stretch to max, but that is impossible because the view itself can stretch indefinitely since it can be horizontally scrollable. I'm not sure why do you want to prevent this behavior but perhaps maybe you want your views to have some initial width then apply the padding, while maintaining the same width. what you can do in such case is simply give your composables a specific width, or what you can do is to get the width of the box and apply them to your composables by width (i used a text in this case)
val localDensity = LocalDensity.current
var lazyRowWidthDp by remember { mutableStateOf(0.dp) }
Box(
Modifier
.padding(start = 10.dp)
.onGloballyPositioned { layoutCoordinates -> // This function will get called once the layout has been positioned
lazyRowWidthDp =
with(localDensity) { layoutCoordinates.size.width.toDp() } // with Density is required to convert to correct Dp
}
) {
val scrollState = rememberScrollState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(scrollState)
) {
items(25) { i ->
Text(
text = "Hello",
modifier = Modifier
.padding(start = (i * 20).dp)
.width(lazyRowWidthDp)
.border(1.dp, Color.Green)
)
}
items(25) { i ->
Text(
text = "World",
modifier = Modifier
.padding(start = (i * 10).dp)
.width(lazyRowWidthDp)
.border(1.dp, Color.Green)
)
}
}
}
Edit:
you can apply horizontal scroll to the lazy column itself and it will scroll in both directions
I have empty fragment with composable:
setContent {
Surface(
modifier = Modifier
.fillMaxWidth().fillMaxHeight().padding(bottom = 48.dp, top = 16.dp),
color = colorResource(id = R.color.usaa_white)
) {
val itemsList = (0..50).toList()
val itemsIndexedList = listOf("A", "B", "C")
LazyColumn(
) {
items(itemsList.size) {
Text("Item is $it")
}
item {
Text("Single item")
}
itemsIndexed(itemsIndexedList) { index, item ->
Text("Item at index $index is $item")
}
}
}
}
the problem is: I can only scroll the content until "Single Item" row and the rest of content is hidden. I added some padding to make sure that it wasn't bottomNavBar covering the list but it's still cropped.
Looks like the issue is caused by bottomNavBar. What's interesting is that it happens only with LazyColumn and works fine when I use Column
The fix I found is to add contentPadding to the bottom. (But hope to find better solution)
LazyColumn(contentPadding = PaddingValues(bottom = 70.dp)) { }
Use Scaffold (check documentation).
Scaffold has a generic content trailing lambda slot. The lambda receives an instance of PaddingValues that should be applied to the content root — for example, via Modifier.padding — to offset the top and bottom bars, if they exist.
setContent {
Scaffold { contentPadding ->
Box(
modifier = Modifier.padding(contentPadding)
) {
// Your code
}
}
}
Hope it helps !
Using Jetpack Compose and the accompanist pager, I'm trying to create a HorizontalPager where:
Edges of items to the left and right of current item are shown
There is a max width to the Pager items
As an example, I wrote the following code (for simplicities sake, I made Text items, but in reality, I'm actually making more complex Card items):
#Composable
fun MyText(modifier: Modifier) {
Text(
text = LOREM_IPSUM_TEXT,
modifier = modifier
.wrapContentHeight()
.border(BorderStroke(1.dp, Color.Red))
)
}
#ExperimentalPagerApi
#Composable
fun MyPager(pagerItem: #Composable () -> Unit = {}) {
Scaffold {
Column(
modifier = Modifier
.fillMaxSize()
// In case items in the VP are taller than the screen -> scrollable
.verticalScroll(rememberScrollState())
) {
HorizontalPager(
contentPadding = PaddingValues(32.dp),
itemSpacing = 16.dp,
count = 3,
) {
pagerItem()
}
}
}
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_200dpWidth() {
MyPager { MyText(modifier = Modifier.widthIn(max = 200.dp)) }
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_500dpWidth() {
MyPager { MyText(modifier = Modifier.widthIn(max = 500.dp)) }
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_FillMaxWidth() {
MyPager { MyText(modifier = Modifier.fillMaxWidth()) }
}
The issue I'm having is that when I make the item have a max width that seems to be smaller than the screen width (see MyPager_200dpWidth), I no longer see the items on the side anymore. On the other hand, using items with larger max widths (See MyPager_500dpWidth) or fillMaxWidth (See MyPager_FillMaxWidth) allows me to see the items on the side.
It seems weird to me that items with the smaller max width are actually taking up more horizontal space than the items with the larger max width. This can be shown in the Preview section...
See how the images on the left (MyPager_500dpWidth) and middle (MyPager_FillMaxWidth) show the next item, while the image on the right (MyPager_200dpWidth`) takes up the whole screen? Here's another comparison when hovering my mouse over the items to see the skeleton boxes...
Just wondering if someone could help me figure out how I can solve this use case. Is it possible that there's a bug in Compose?
The page size is controlled by the HorizontalPager.contentPadding parameter.
Applying widthIn(max = 200.dp) to your text only reduces the size of your element inside the page, while the page size remains unchanged.
Applying more padding should solve your problem, for example, contentPadding = PaddingValues(100.dp) looks like this:
You can fix this by adding your contentPadding like this
val horizontalPadding = 16.dp
val itemWidth = 340.dp
val screenWidth = LocalConfiguration.current.screenWidthDp
val contentPadding = PaddingValues(start = horizontalPadding, end = (screenWidth - itemWidth + horizontalPadding).dp)
I'm trying to achieve modalBottomSheet by BottomSheetScaffold for some custom implementation.
here is my BottomSheetScaffold
BottomSheetScaffold(
sheetPeekHeight = 0.dp,
scaffoldState = bottomSheetState,
sheetBackgroundColor = Color.Transparent,
backgroundColor = Color.Transparent,
sheetElevation = 0.dp,
sheetShape = RoundedCornerShape(topStart = 36.dp, topEnd = 36.dp),
snackbarHost = snackBarHost,
sheetContent = { bottomSheet() }) {
Box(Modifier.fillMaxSize()) {
val coroutineScope = rememberCoroutineScope()
sheetContent()
Scrim(
color = Primary,
alpha = bottomSheetState.currentFraction * 0.5f,
onDismiss = {
coroutineScope.launch { bottomSheetState.bottomSheetState.collapse() }
},
visible = bottomSheetState.bottomSheetState.targetValue != BottomSheetValue.Collapsed && bottomSheetState.bottomSheetState.currentValue != BottomSheetValue.Collapsed
)
}
}
When this scaffold is called by some screen, the sheetContent() will be replaced as screen content. My problem here is when bottomSheet() is empty on that screen and thus there is no height, bottom sheet state think it is expanded while I just not put composable inside bottomSheet() and it just fill based on some condition with no default composable. Because of that the Scrim() function will be visible and when I click on it this exception will throw
java.lang.IllegalArgumentException: The target value must have an associated anchor.
It seems while sheetContent is necessary for BottomSheetScaffold there is no way to deal with empty value because BottomSheetState class that handle's swiping need anchor to get height and empty value cause's unexpected result
This is a bug introduced in latest release of compose 1.2.x. To stop showing bottom sheet at start, I only add sheetContent if data is not null(this was not possible in previous release1.1.x). Now I have another issue. The click event to expanding bottomSheet won't trigger at first try. I always need to click it twice.
In Compose, how do we get the position or size of a Composable in a screen ? For example, I'm trying to focus the map camera between specific bounds and adding padding. Here I need to get the padding corresponding to the pager top position and TopBar bottom position.
Currently the code of this screen is the following:
BoxWithConstraints {
MapViewWithMarkers(...)
TopAppBar(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding(),
backgroundColor = Color.Transparent,
elevation = 0.dp,
)
HorizontalPager(
state = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.navigationBarsPadding()
.padding(bottom = 32.dp),
itemSpacing = 8.dp,
) { page ->
val hikeOnMapCard = hikeMarkerList[page]
HikeOnMapCard(hikeOnMapCard) {
viewModel.hikeOnMapCardClicked(hikeOnMapCard.id)
}
}
}
I would like to forward to the MapViewWithMarkers Composable the padding corresponding to the TopAppBar size and Pager size on this screen
Thanks !
To get the position and the size of a composable you can use the onGloballyPositioned modifier.
Something like:
var sizeTopBar by remember { mutableStateOf(IntSize.Zero) }
var positionInRootTopBar by remember { mutableStateOf(Offset.Zero) }
TopAppBar(
modifier = Modifier
.onGloballyPositioned { coordinates ->
// size
sizeTopBar = coordinates.size
// global position (local also available)
positionInRootTopBar = coordinates.positionInRoot()
}
//...
)
With complex layout to measure and layout multiple composables, use the Layout composable instead. This composable allows you to measure and lay out children manually.