I'm still getting the hang of Android's Jetpack Compose declarative UI library and would appreciate some help. The column (containing an image "icon" and text) isn't rendering at all on smaller screens. Here is the relevant code -->
#Composable
fun ComposableExample() {
...
ScrollableColumn(modifier = Modifier.fillMaxWidth()) {
// Scrollable column should have one child.
Column {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = "This is the Title of the Video")
//--------- This column is rendering on wider (tablet+) screens but not phone sized screens.
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()) {
Image(imageResource(id = R.drawable.icon_download_arrow),
modifier = Modifier.preferredSize(20.dp)
.clickable(onClick = { }))
Text(text = "Download")
}
// ---------
}
...
}
}
}
I've tried a ton of different modifiers/permutations and can't get the column to render on smaller screens. I'd really appreciate some help, thanks!
Simply swapping the column and the text is working, i.e. rendering the the clickable column of icon image and text BEFORE the video title. Not entirely sure why this is working, but it is!
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 was trying to create a sample Tab View in Jetpack compose, so the structure will be like
Inside a Parent TabRow we are iterating the tab title and create Tab composable.
More precise code will be like this.
#OptIn(ExperimentalPagerApi::class)
#Composable
private fun MainApp() {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
backgroundColor = MaterialTheme.colors.surface
)
},
modifier = Modifier.fillMaxSize()
) { padding ->
Column(Modifier.fillMaxSize().padding(padding)) {
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
val tabContents = listOf(
"Home" to Icons.Filled.Home,
"Search" to Icons.Filled.Search,
"Settings" to Icons.Filled.Settings
)
HorizontalPager(
count = tabContents.size,
state = pagerState,
contentPadding = PaddingValues(horizontal = 32.dp),
modifier = Modifier
.weight(1f)
.fillMaxWidth()
) { page ->
PagerSampleItem(
page = page
)
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier
.pagerTabIndicatorOffset(pagerState, tabPositions)
.height(4.dp)
.background(
color = Color.Green,
shape = RectangleShape
)
)
}
) {
tabContents.forEachIndexed { index, pair: Pair<String, ImageVector> ->
Tab(
selected = pagerState.currentPage == index,
selectedContentColor = Color.Green,
unselectedContentColor = Color.Gray,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(text = pair.first) },
icon = { Icon(imageVector = pair.second, contentDescription = null) }
)
}
}
}
}
}
#Composable
internal fun PagerSampleItem(
page: Int
) {
// Displays the page index
Text(
text = page.toString(),
modifier = Modifier
.padding(16.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(4.dp))
.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
.padding(8.dp)
.wrapContentSize(Alignment.Center)
)
}
And coming to my question is whenever we click on the tab item, the inner content get recompose so weirdly. Im not able to understand why it is happens.
Am attaching an image of the recomposition counts below, please take a look that too, it would be good if you guys can help me more for understand this, also for future developers.
There are two question we have to resolve in this stage
Whether it will create any performance issue, when the view getting more complex
How to resolve this recompostion issue
Thanks alot.
… whenever we click on the tab item, the
inner content get recompose so weirdly. Im not able to understand why
it is happens...
It's hard to determine what this "weirdness" is, there could be something inside the composable your'e mentioning here.
You also didn't specify what the API is, so I copied and pasted your code and integrated accompanist view pager, then I was able to run it though not on an Android Studio with a re-composition count feature.
And since your'e only concerned about the Text and the Icon parameter of the API, I think that's something out of your control. I suspect the reason why your'e getting those number of re-composition count is because your'e animating the page switching.
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
Though 'm not able to try this on another Android Studio version with the re-composition feature, I think (though I'm not sure) scrolling to another page without animation will yield less re-composition count.
coroutineScope.launch {
pagerState.scrollToPage(index)
}
If it still bothers you, the best course of action is to ask them directly, though personally I wouldn't concerned much about this as they are part of an accepted API and its just Text and Icon being re-composed many times by an animation which is also fine IMO.
Now if you have some concerns about your PagerSampleItem stability(which you have a full control), based on the provided code and screenshot, I think your'e fine.
There's actually a feature suggested from this article to check the stability of a composable, I run it and I got this report.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun PagerSampleItem(
stable page: Int
)
Everything about this report is within the article I linked.
Also, your Text and Icon are using String and ImageVector which is stable and immutable (marked by #Immutable) respectively.
So TLDR, IMO your code is fine, your PagerSampleItem is not re-composing in the screenshot.
I am trying to make the ImageComposable wrap its height and width according to its content, along with the two Text composable, align to the bottom of Assemble composable. Following is the code for that:
#Composable
fun ImageComposable(url:String){
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current).data(url).apply{
placeholder(drawableResId = R.drawable.ic_broken_pic)
}.build()
)
Image(painter = painter, contentDescription = null, Modifier.padding(2.dp).border(width = 2.dp, shape = CircleShape, color = MaterialTheme.colors.onPrimary)
}
#Composable
fun Assemble(url:String){
Column (modifier = Modifier.fillMaxWidth().height(400.dp).background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
but the ImageComposable ends up taking all the height and width of the Assemble composable and I am not able to see the two Text composables that I added in the column. So I am confused as to what is the exact problem here. I thought at least it should show the ImageComposable along with the two Text composable but it is not happening.
I am using coil image loading library here for parsing the image from url. For now in testing, I am passing url as an Empty String. Hence I am calling the composable as:
Assemble("")
I didn't find any document that would help me understand this behavior. So I wanted to know the reason to this problem and possible solutions to overcome it.
You can explicitly specify the height of each component:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.height(200.dp)//...
Text(modifier = Modifier.height(50.dp)//...
Text(modifier = Modifier.height(150.dp)//...
}
Or you can specify a fraction of the maximum height it will take up:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.fillMaxHeight(0.75f)//...
Text(modifier = Modifier.fillMaxHeight(0.1f)//...
Text(modifier = Modifier.fillMaxHeight(0.15f)//...
}
You can also try playing with the weight modifier:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.weight(1f)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
}
It would be easier to solve your problem if there would be a sketch of what you want to achieve.
Nevertheless, I hope I can help:
It looks like the issue you are facing can be handled by Intrinsic measurements in Compose layouts.
The column measures each child individually without the dimension of your text constraining the image size. For this Intrinsics can be used.
Intrinsics lets you query children before they're actually measured.
For example, if you ask the minIntrinsicHeight of a Text with infinite width, it'll return the height of the Text as if the text was drawn in a single line.
By using IntrinsicSize.Max for the width of the Assemble composable like this:
#Composable
fun Assemble(url: String) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom
) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
you can can create a layout like this:
(Please note that I am using a local drawable here)
You can now see the 2 texts and the width of the image is adjusted to the width of the texts.
Using Intrinsics to measure children in dependance to each other should help you to achieve what you wanted.
Please let me know if this layout does not meet your expectations.
I would like to align my components based on the Material responsive layout grid, as visualized by from https://material.io/design/layout/responsive-layout-grid.html#columns-gutters-and-margins.
I don't necessarily want my content to be included in a column but rather put baselines on either a start or an end of a column.
A simple example:
// FIXME: What to do?
#Composable
fun example() {
Button(onClick = {
}, text = "Stretch me between start of column 2 and end of column 4"
)
}
My best guess is that I need to use this library somehow: https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary
But I'm still clueless...
How can I achieve what I want to do in Jetpack Compose?
The requirement is not clear. Please comment if this is not what you are looking for.
To have a button with a width of 2 columns in a 4 column grid system. You can use weight and Spacer to achieve the requirement.
This starts at starting of Column 2 and ends at the ending of Column 3.
Row(
modifier = Modifier.fillMaxWidth(),
) {
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.fillMaxWidth()
.weight(2f),
) {
Text(text = "Center Button")
}
Spacer(modifier = Modifier.weight(1f))
}
Using weight and padding modifiers, along with Spacer we can achieve most of the grid requirements.
I have some text.
I want to centre it on the screen.
I am using Jetpack Compose.
How do I do this?
I know that there are three types of layouts in Jetpack Compose.
Box
Column
Horizontal
Which one should I use?
I don't know how layouts work.
Are they full screen by default like in XML?
If so, how do I position elements like ConstraintLayout?
How do I set padding and margin from only one side and how do I link elements?
I guess all your questions can be clarified if you follow the Compose Pathway. But I'll try to summarize for you...
You can organize your components using one of the following "layout managers" (which in Compose are just called layouts):
Column (similar to LinearLayout with vertical orientation)
Row (similar to LinearLayout with horizontal orientation)
Box (similar to FrameLayout)
and ConstraintLayout.
If you need something different of these, you can create a custom layout using the Layout composable.
"Which one should I use?"
You can use any of these, depending of the case... To simply display a text in the center of the screen, you can achieve with all of them.
Using Column:
Column(
Modifier.fillMaxSize(), // to fill the whole screen
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Hello")
}
Using Box
Box(
Modifier.fillMaxSize()
) {
Text(text = "Hello",
modifier = Modifier.align(Alignment.Center))
}
"Are they full screen by default like in XML?"
No, they are "wrap_content" by default.
"how do I position elements like ConstraintLayout? How do I set padding and margin from only one side and how do I link elements?"
You need to declare the references to the components and then positioning them accordingly.
Here is a simple example...
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Creating refs...
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Name",
// Linking the reference to this component
modifier = Modifier.constrainAs(text1Ref) {
// linking the top of this component to the parent top
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(
value = "",
onValueChange = {},
label = { Text("Name") },
modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
// linking this component with the previous component
top.linkTo(text1Ref.bottom)
})
Button(onClick = {},
content = { Text("OK") },
modifier = Modifier.padding(top = 8.dp).constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {},
content = { Text("Cancel") },
modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}