I am trying to create multiple items to encapsulate the specific behavior of every component but I cannot specify the dimensions for every view.
I want a Textfield with an X icon on its right
setContent {
Surface(
modifier = Modifier
.fillMaxSize()
.background(color = white)
.padding(horizontal = 15.dp)
) {
Row(horizontalArrangement = Arrangement.spacedBy(15.dp)) {
Searcher(
modifier = Modifier.weight(1f),
onTextChanged = { },
onSearchAction = { }
)
Image(
painter = painterResource(id = R.drawable.ic_close),
contentDescription = null,
colorFilter = ColorFilter.tint(blue)
)
}
}
}
The component is the following
#Composable
fun Searcher(
modifier: Modifier = Modifier,
onTextChanged: (String) -> Unit,
onSearchAction: () -> Unit
) {
Row {
SearcherField(
onTextChanged = onTextChanged,
onSearchAction = onSearchAction,
modifier = Modifier.weight(1f)
)
CircularSearch(
modifier = Modifier
.padding(horizontal = 10.dp)
.align(CenterVertically)
)
}
}
and the SearcherField:
#Composable
fun SearcherField(
modifier: Modifier = Modifier,
onTextChanged: (String) -> Unit,
onSearchAction: () -> Unit
) {
var fieldText by remember { mutableStateOf(emptyText) }
TextField(
value = fieldText,
onValueChange = { value ->
fieldText = value
if (value.length > 2)
onTextChanged(value)
},
singleLine = true,
textStyle = Typography.h5.copy(color = White),
colors = TextFieldDefaults.textFieldColors(
cursorColor = White,
focusedIndicatorColor = Transparent,
unfocusedIndicatorColor = Transparent,
backgroundColor = Transparent
),
trailingIcon = {
if (fieldText.isNotEmpty()) {
IconButton(onClick = {
fieldText = emptyText
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = emptyText
)
}
}
},
placeholder = {
Text(
text = stringResource(id = R.string.dondebuscas),
style = Typography.h5.copy(color = White)
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
onSearchAction()
}
),
modifier = modifier.fillMaxWidth()
)
}
But I don´t know why, but the component Searcher with the placeholder is rendered in two lines.
It´s all about the placeholder that seems to be resized for not having enough space because if I remove the placeholder, the component looks perfect.
Everything is in one line, not having a placeholder of two lines. I m trying to modify the size of every item but I am not able to get the expected result and I don´t know if the problem is just about the placeholder.
How can I solve it? UPDATE -> I found the error
Thanks in advance!
Add maxlines = 1 to the placeholder Text's parameters.
Your field is single -lune but your text is multi-line. I think it creates conflict in implementation.
Okay... I find that the problem is about the trailing icon. Is not visible when there is no text in the TextField but is still occupying some space in the view, that´s why the placeholder cannot occupy the entire space. The solution is the following.
val trailingIconView = #Composable {
IconButton(onClick = {
fieldText = emptyText
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = emptyText
)
}
}
Create a variable with the icon and set it to the TextField only when is required
trailingIcon = if (fieldText.isNotEmpty()) trailingIconView else null,
With that, the trailing icon will be "gone" instead of "invisible" (the old way).
Still have a lot to learn.
Related
.
I want my code to remove elements from list of text fields properly.
Each element has an X button to remove it's text field.
If I start removing elements from the bottom it works but it doesn't work for removing random elements
I want to use forEachIndexed for displaing the list
Please help me with solving this problem. I've been trying to do this for some time but every trial is unsuccessful.
This is a piece of code that I've managed to write but removing elements doesn't work properly
val listOfWords = mutableStateListOf<String>()
#Composable
fun Main() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Words",
modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 4.dp),
style = MaterialTheme.typography.h6
)
listOfWords.forEachIndexed { index, word ->
Input(word, 30, "Word", 1,
{newWord ->
listOfWords[index] = newWord
Log.d("text ",word)
},
{
listOfWords.removeAt(index)
}
)
}
IconButton(
onClick = {
listOfWords.add("")
}
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Add"
)
}
}
}
#Composable
fun Input(
word: String,
maxChar: Int,
label: String,
maxLines: Int,
onEdit: (word: String) -> (Unit),
onRemove: () -> (Unit)
) {
var text by remember { mutableStateOf(word) }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp, 0.dp, 8.dp, 0.dp)
) {
OutlinedTextField(
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
onEdit(text)
},
modifier = Modifier.fillMaxWidth(),
label = { Text(label) },
leadingIcon = {
Icon(Icons.Default.Edit, null)
},
trailingIcon = {
IconButton(onClick = {
onRemove()
}) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = "Back"
)
}
},
maxLines = maxLines
)
Text(
text = "${text.length} / $maxChar",
textAlign = TextAlign.End,
style = MaterialTheme.typography.caption,
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp)
)
}
}
The problem is here.
var text by remember { mutableStateOf(word) }
Without supplying a key to Input's remember, compose will not be able refresh/update your remaining Input's states during Main's re-composition every time an Input is removed.
You can use the word parameter as key for remember to re-calculate every composition pass (i.e when you add/remove or typed a value in the TextField), and your code should probably work as you expected.
var text by remember(word) { mutableStateOf(word) }
Have you tried doing the following instead?
listOfWords.forEachIndexed { index, word ->
... // rest of code
{
listOfWords.removeAt(index)
}
I am using Android Compose to implement new UI in my app. Unfortunately the content of my view is hidden when the keyboard is opened.
Please find below some details :
#Composable
private fun OnBoardingAddressScreen() {
var address by remember { mutableStateOf("") }
val space = 35.dp
val horizontalPadding = resources.getDimension(R.dimen.horizontal_padding).dp
Column(modifier = Modifier.fillMaxSize()) {
OnBoardingTopContent()
Spacer(modifier = Modifier.size(space))
OnboardingAddressContent(
address = address,
onAddressChange = { address = it },
onBoardingButtonClick =
{
},
horizontalPadding
)
}
}
#Composable
private fun OnboardingAddressContent(
address: String,
onAddressChange: (String) -> Unit,
onBoardingButtonClick: () -> Unit,
horizontalPadding: Dp
) {
Column(modifier = Modifier
.fillMaxSize()) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding),
fontSize = 35.sp,
maxLines = 3,
text = "Welcome", fontWeight = FontWeight.Bold
)
Image(
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.size(330.dp, 330.dp),
painter = painterResource(R.drawable.onboarding_image),
contentDescription = null
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding),
value = address,
onValueChange = onAddressChange,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
label = { Text("Your content") }
)
Box(modifier = Modifier.fillMaxSize()){
Image(
modifier = Modifier.align(Alignment.BottomStart),
painter = painterResource(R.drawable.onboarding_bottom_background),
contentDescription = null
)
Button(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.align(Alignment.BottomCenter)
.padding(horizontal = horizontalPadding),
shape = RoundedCornerShape(10.dp),
content = { Text("Navigate") },
onClick = onBoardingButtonClick
)
}
}
}
And here the result :
Please notice :
I am using android:windowSoftInputMode="adjustPan" in my manifest and tried using android:windowSoftInputMode="adjustResize" but I still have the issue
Thank you in advance for your help
You can add an empty layout in the bottom of your screen when the keyboard will show then you will show the empty layout and when the keyboard is gone then you can hide the empty layout.
Can you check this link that provided from google : https://google.github.io/accompanist/insets/#animated-insets-support
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
// content
}
and
OutlinedTextField(
// other params,
modifier = Modifier.navigationBarsWithImePadding()
)
Our problems seems to be very similar. I created some kind of tutorial basing on my code. Hope it will help:
(Compose UI) - Keyboard (IME) overlaps content of app
I have the following code:
Box(
Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
OutlinedTextField(
value = text,
onValueChange = {
text = it
if (text.length >= 3) {
viewModel.getSuggestions(text)
}
},
label = { Text("Search") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = null,
modifier = Modifier.padding(16.dp, 0.dp, 8.dp, 0.dp),
tint = Color.Unspecified
)
},
shape = RoundedCornerShape(50)
)
DropdownMenu(expanded = suggestions.value.isNotEmpty(),
modifier = Modifier
.fillMaxWidth(0.92f),
onDismissRequest = { }) {
for (suggestion in suggestions.value) {
DropdownMenuItem(onClick = {
viewModel.searchWord(suggestion)
}) {
Text(suggestion)
}
}
}
}
It's a dictionary, on top of the screen there is this OutlinedTextField.
When I search for a word I get suggestions based on the input and show them in a DropdownMenu.
The problem I am facing is that when the DropdownMenu is shown, the keyboard disappears but the focus remains on the Text field. How can I solve this problem and most importantly, why is this happening? I know it's redrawing the UI based on the status change but why it's not keeping the keyboard opened.
DropdownMenu(properties = PopupProperties(focusable = false))
Check this
I want to make a simple jetpack compose layout, using weights. below is the code
Row() {
Column(
Modifier.weight(1f).background(Blue)){
Text(text = "Weight = 1", color = Color.White)
}
Column(
Modifier.weight(2f).background(Yellow)
) {
Text(text = "Weight = 2")
}
}
which will result in a layout like this
but what if my inner views are coming from some other Composable function, which is unaware that its going to be a child of a column or a row?
Row() {
// Calling Some composable function here
}
In that case i am not able to access the Modifier.weight(..) function, because it thinks that its not in the row or column scope, because it is an independent function.
You can add the modifier as parameter in your Composable.
Something like:
#Composable
fun myCard(modifier: Modifier = Modifier, name:String){
Box(modifier){
Text(name,textAlign = TextAlign.Center,)
}
}
In this way the myCard is unaware that its going to be a child of a Column or a Row.
Then in your implementation you can use:
Row(Modifier.padding(16.dp).height(50.dp)) {
myCard(Modifier.weight(1f).background(Color.Yellow),"Weight = 1")
myCard(Modifier.weight(2f).background(Color.Red),"Weight = 2")
}
You can prefix RowScope. or ColumnScope. to your function declaration
#Composable
fun RowScope.SomeComposable() {
// weights should work in here
Column(Modifier.weight(1f)){ /**/ }
}
Row(
modifier = mainRowModifier,
verticalAlignment = rowCenterVerticallyAlign
) {
Row(
verticalAlignment = rowCenterVerticallyAlign,
horizontalArrangement = rowContentArrangementCenter,
modifier = Modifier.weight(.5f, true).wrapContentHeight()
) {
Text(
text = "971",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
Icon(
painter = painterResource(id = resIdDropDownIcon),
contentDescription = null,
modifier = Modifier.padding(start = startPaddingDropdownIcon.dp)
)
}
Divider(
color = dividerColor,
modifier = dividerModifier
)
SimpleTextField(
modifier = Modifier.weight(2f),
value = "",
placeHolder = textFieldPlaceholder,
keyboardType = KeyboardType.Phone,
singleLine = true
)
Text(
text = optionalTextTxt,
modifier = Modifier.weight(.5f, true),
style = MaterialTheme.typography.bodySmall,
fontSize = optionalTextFontSize.sp
)
}
no weight is issue to main row, then i will assign subrow weight to 0.5 and for textField weight will be 2f and last textview weight is issue to 0.5f
then Output will be looks like
Hi i'm trying to implement a lazycolumn of a list of posts, I tested it on the emulator api 21 and 29 and it looks kinda smooth on the api 29 it's a little bit laggy, when I tested it on a physical device it was lagging, It looks like it's skipping frames or something..
I tried to remove some views that uses imageVector to see if that was the problem and still the same problem.
This is my composable view:
#Composable
fun HomePostView(
category: String,
imagesUrl: List<String> = listOf(imageHolder),
doctorProfileImage: String = imageUrl,
title: String,
subTitle: String
) {
Card(
shape = PostCardShape.large, modifier = Modifier
.padding(horizontal = 3.dp)
.fillMaxWidth()
) {
Column {
PostTopView(
category = category,
onOptionsClicked = { /*TODO option click*/ },
onBookmarkClicked = {/*TODO bookmark click*/ })
CoilImage(
data = imagesUrl[0],
fadeIn = true,
contentDescription = "post_image",
modifier = Modifier
.fillMaxWidth()
.requiredHeight(190.dp)
.padding(horizontal = contentPadding),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(10.dp))
PostDoctorContent(
doctorProfileImage = doctorProfileImage,
title = title,
subTitle = subTitle
)
Spacer(modifier = Modifier.height(contentPadding))
PostBottomView(likesCount = 293, commentsCount = 22)
Spacer(modifier = Modifier.height(contentPadding))
}
}
Spacer(modifier = Modifier.height(10.dp))
}
#Composable
private fun PostDoctorContent(doctorProfileImage: String, title: String, subTitle: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = contentPadding)
) {
CoilImage(data = doctorProfileImage,
contentScale = ContentScale.Crop,
contentDescription = null,
fadeIn = true,
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.clickable {
/*Todo on doctor profile clicked*/
})
Column {
Text(
text = title, fontSize = 14.sp, maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = contentPadding)
)
Text(
text = subTitle,
fontSize = 11.sp,
color = LightTextColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = contentPadding)
)
}
}
}
#Composable
private fun PostBottomView(likesCount: Long, commentsCount: Long) {
Row(
modifier = Modifier.padding(horizontal = contentPadding),
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.clip(RoundedCornerShape(50))
.clickable { /*Todo on like clicked*/ }
.padding(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_heart),
contentDescription = "Like"
)
Spacer(modifier = Modifier.width(5.dp))
Text(text = likesCount.toString(), fontSize = 9.sp)
}
Spacer(Modifier.width(20.dp))
Row(
Modifier
.clip(RoundedCornerShape(50))
.clickable { /*Todo on comment clicked*/ }
.padding(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_comment),
contentDescription = "Comment"
)
Spacer(modifier = Modifier.width(5.dp))
Text(text = commentsCount.toString(), fontSize = 9.sp)
}
}
}
#Composable
private fun PostTopView(
category: String,
onOptionsClicked: () -> Unit,
onBookmarkClicked: () -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = onOptionsClicked) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_threedots),
contentDescription = "Options",
tint = Color.Unspecified
)
}
Text(text = category, fontSize = 16.sp, color = LightTextColor)
}
IconButton(onClick = onBookmarkClicked) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_bookmark),
contentDescription = "Bookmark"
)
}
}
}
and the lazyColumn:
LazyColumn(contentPadding = paddingValues , state = state ) {
item {
Spacer(modifier = Modifier.height(10.dp))
DoctorsList(
viewModel.doctorListData.value,
onCardClicked = {})
}
items(30) { post ->
HomePostView(
category = "Public Health ",
title = "Food Importance",
subTitle = "you should eat every day it's healthy and important for you, and drink water every 2 hours and what you should do is you should run every day for an hour"
)
}
}
Note: I'm still not using a viewmodel i'm just testing the view with fake data
This may not work for anyone else but on an earlier version (1.0.0-beta01) I saw a very large improvement in performance when I switched
lazy(items) { item ->
...
}
to
items.forEach { item ->
lazy {
...
}
}
I have no idea why and I'm not sure if this is still the case in later versions but its worth checking. So for the example given in the question, that would mean changing
items(30) {
...
}
to
repeat(30) {
item {
...
}
}
TLDR: Make sure your new LazyColumn compose element is not within a RelativeLayout or LinearLayout.
After some investigation the solution to this issue for us was the view in which the LazyColumn was constrained.
Our project uses a combination of Jetpack Compose and the older XML layout files. Our new compose elements were embedded within a RelativeLayout of an existing XML file. This was where the problem was. The compose element would be given the entire screen and then the onMeasure function of the compose element was called to re-configure the view and add our bottom nav bar...this onMeasure was called over and over again, which also in the case of a LazyColumn the re-measuring was throwing out the cache as the height had changed.
The solution for us was to change the RelativeLayout that contained both the new compose element and the bottom nav bar and replace it with a ConstraintLayout. This prevented the onMeasure from being called more than twice and gave a massive performance increase.
Try to build release build with turn off debug logs. should be works fine.
Ok, So far I know that there is an issue with the API when it comes to performance ...But what I found is this
Actually, In my case, I was just loading the image with 2980*3750 pixels image. I just crunched my resources to shorter pixels by some other tools
Now the lag is not present...
In my case, after I set the height of ComposeView to a specific value, it make LazyColumn scroll smooth.
Therefore, I create a XML file like
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.compose.ui.platform.ComposeView
android:id="#+id/compose"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle ? ) {
super.onViewCreated(view, savedInstanceState)
view.doOnLayout {
compose.layoutParams.height = view.height
compose.requestLayout()
}
}
I know ComposeView height already match_parent so set the height for it again on Fragment seem useless. However, without setting the height, LazyColumn will lagging when scroll.
I am not sure if it will work in all device but work well on my Pixel and Xiomi.
If you are using JPGs or PNGs in your project then check for their size, images having larger size causes a lots of lagging issues on low end devices.
I was having the same issue with my simple LazyColumn list and turned out I was using a JPG having size larger than 2MBs.