Create list with custom text jetpack compose - android

I'm trying to create a list with customized text views, like the image bellow.
I tried to do that with a lazy column, but it does not work properly.
The problem is that row does not grow with the content created, I don't know how compose redraw that view behind de hood but it isn't working to me.
#Composable
fun NumberList(viewModel: HomeViewModel) {
val gameList = viewModel.numberList //The list of numbers
LazyColumn(
modifier = Modifier.padding(top = 24.dp),
content = {
item {
gameList.forEach {
MegaCard(item = it)
}
}
})
}
#Composable
fun MegaCard(item: String) {
val itemList = item.split("-")
Card(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp, vertical = 4.dp)
) {
Row(modifier = Modifier.padding(3.dp)) {
itemList.forEach { number ->
GameCircle(number)
}
}
}
}
#Composable
fun GameCircle(number: String) {
val megaColor = colorResource(id = R.color.mega_green)
Text(
modifier = Modifier
.padding(16.dp)
.drawBehind {
drawCircle(
color = megaColor,
radius = this.size.maxDimension
)
},
text = number,
style = MaterialTheme.typography.body2,
color = Color.White,
fontSize = 18.sp
)
}
The final result is something like this bellow:

As more game numbers are added, I assume you want each row to grow vertically. If that's the case, the problem is that the total circle size exceeds the available space. You can use a FlowRow for this:
#Composable
fun MegaCard(item: String) {
val itemList = item.split("-")
Card(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp, vertical = 4.dp)
) {
FlowRow(modifier = Modifier.padding(3.dp)) {
itemList.forEach { number ->
GameCircle(number)
}
}
}
}
You'll need to add the flow layout Gradle dependency:
implementation 'com.google.accompanist:accompanist-flowlayout:0.25.1'
Also, unless you intend to treat them as one entity, I recommend not adding every row into a single item. The docs go into more detail on the pitfalls of doing so. Here's the final result using FlowRow.

Related

Jetpack compose LazyGridView not scroll on Column

When trying to put a LazyVerticalGrid inside a scrollable Column I get the following error:
java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like LazyColumn and Column(Modifier.verticalScroll()) is not allowed. If you want to add a header before the list of items please take a look on LazyColumn component which has a DSL api which allows to first add a header via item() function and then the list of items via items().
I am not making a traditional list, I just have alot of elements that are too big to fit on the screen. Therefore I want the column to scroll so I can see all the elements. Here is my code:
you need to use an item with a span attribute. Assume that your grid view has three items in every row, you can put your item wherever you want with the scrollable screen.
LazyVerticalGrid(cells = GridCells.Fixed(4)) {
item(span = { GridItemSpan(4) }) { Header() }
item(span = { GridItemSpan(4) }) { AnotherView() }
items(gridItems) { GridItemView(it) }
}
If you want to keep some UI elements sticky at the top or bottom, you can play with the Modifier. weight(), for example:
#Composable
fun SomeScreenContent() {
val gridItems = List(30){ it.toString() }
Column(modifier = Modifier.fillMaxSize()) {
SomeTopContent(modifier = Modifier.weight(0.2f))
LazyVerticalGrid(
modifier = Modifier.weight(0.6f),
columns = GridCells.Fixed(4),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(gridItems) { item ->
GridItem(item)
}
}
SomeBottomContent(modifier = Modifier.weight(0.2f))
}
}
#Composable
fun SomeTopContent(modifier:Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.background(Color(0xff5077FC)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(text = "Top")
}
}
#Composable
fun SomeBottomContent(modifier:Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.background(Color(0xff4EE180)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(text = "Bottom")
}
}
#Composable
fun GridItem(item: String) {
Card(modifier = Modifier.size(100.dp)) {
Text(text = item)
}
}
Or just use the Scaffold composable function...

When adding lazy column inside another lazy column in android jetpack compose it shows error

How to add a lazy column inside another lazy column in jetpack compose like below screenshot.(The inner list size maybe vary)
#Composable
fun SingleItem() {
LazyColumn(modifier = Modifier
.fillMaxWidth()
),
) {
item {
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_school),
contentScale = ContentScale.Crop,
contentDescription = null
)
Text(
text = "School",
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
Icon(
painter = painterResource(id = R.drawable.ic_down_arrow),
modifier = Modifier
.size(dimensionResource(id = R.dimen.margin_large))
.clip(CircleShape)
.clickable { isExpand = !isExpand },
contentDescription = stringResource(id = R.string.expand_list)
)
}
}
if (isExpand) {
item {
Divider(
modifier = Modifier
.padding(vertical = dimensionResource(id = R.dimen.margin_large))
.background(DividerColor.copy(alpha = 0.5F))
.fillMaxWidth(), thickness = 0.5.dp
)
}
items(3) {
Row(modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.margin_large))
.fillMaxWidth()) {
Text("School",
modifier = Modifier
.fillMaxWidth()
.weight(1f),
)
Text(text = "3 KM",
textAlign = TextAlign.End
)
}
}
}
}
}
//For Full list
#Composable
fun MainList()
{
LazyColumn() {
items(10) {
SingleItem()
}
}
}
But using this code it shows following error.
Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
It is recommended to avoid Nested Scrolling in the same direction. One possible hack if you already know the size of the component you can use that in modifier.
or try wrapping all of your composables inside one parent LazyColumn and using its DSL to pass in different types of content.
In your example
#Composable
fun MainList()
{
LazyColumn() {
items(10) { item ->
ListComposable(item)
}
}
}
#Composable
private fun ListComposable(item: List<String>){
//...
}
This item can also be a multiple list item.
Reference: https://developer.android.com/jetpack/compose/lists#avoid-nesting-scrollable

Screen dosnt show the entire view in Compose Android

I can't understand why the screen doesn't fill the entire view or even allow for scrolling even though I am using fillMaxSize() modifier.
This is how my view supposes to be shown and this is what I get when I build it. even though I also use scroll view it doesn't scroll as will, how can I actually make the view only fit whatever screen its built on it, as I am certain that the issue is me using fixed padding in Greeting2 compose function, can someone tell me how I can fix this issue pls.
#Composable
fun Greeting2() {
Column(modifier = Modifier.fillMaxWidth().
padding(top = 160.dp,
start = 16.dp,
end = 24.dp
)) {
androidx.compose.material.Text(text = "Welcome",
color = Color.White,
fontSize = 48.sp,
modifier = Modifier.padding(8.dp))
androidx.compose.material.Text(text = "Back...!",
color = Color.White,
fontSize = 48.sp,
modifier = Modifier.padding(8.dp))
}
}
#Composable
fun loginFullView(modifier: Modifier){
Column (modifier =
modifier.background(color = Red800).
fillMaxSize()){
Greeting2()
Card(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth().padding(top = 24.dp),
elevation = 4.dp,
shape = RoundedCornerShape(
topStart = 24.dp,
topEnd = 24.dp)
) {
Column (horizontalAlignment = Alignment.CenterHorizontally){
LoginText()
PasswordField()
LoginButton ()
OTPLogin()
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LoginAppComposeSmaatTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
loginFullView(modifier = Modifier.fillMaxWidth())
}
}
}
}

How to implement list multiSelect in Jetpack Compose?

I need to implement multiselect for LazyList, which will also change appBar content when list items are long clicked.
For ListView we could do that with just setting choiceMode to CHOICE_MODE_MULTIPLE_MODAL and setting MultiChoiceModeListener.
Is there a way to do this using Compose?
Since you're in control of the whole state in jetpack compose, you can easily create your own multi-select mode. This is an example.
First I created a ViewModel
val dirs: LiveData<DirViewState> = {
DirViewState.Content(dirRepository.targetFolders)}.asFlow().asLiveData()
val all: LiveData<DirViewState> = {
DirViewState.Content(dirRepository.allFolders)
}.asFlow().asLiveData()
val createFolder = mutableStateOf(false)
val refresh = mutableStateOf(false)
val enterSelectMode = mutableStateOf(false)
val selectedAll = mutableStateOf(false)
val selectedList = mutableStateOf(mutableListOf<String>())
fun updateList(path: String){
if(path in selectedList.value){
selectedList.value.remove(path)
}else{
selectedList.value.add(path)
}
}
}
Usage
Card(modifier = Modifier
.width(100.dp)
.height(120.dp)
.padding(8.dp)
.pointerInput(Unit) {
detectTapGestures(
onLongPress = { **viewModel.enterSelectMode.value = true** },
onTap = {
if (viewModel.enterSelectMode.value) {
viewModel.enterSelectMode.value = false
}
}
)
}
,
shape = MaterialTheme.shapes.medium
) {
Image(painter = if (dirModel.dirCover != "") painter
else painterResource(id = R.drawable.thumbnail),
contentDescription = "dirThumbnail",
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
contentScale = ContentScale.FillHeight)
**AnimatedVisibility(
visible = viewModel.enterSelectMode.value,
enter = expandIn(),
exit = shrinkOut()
){
Row(verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.Start) {
Checkbox(checked = isSelected.value, onCheckedChange = {
isSelected.value = !isSelected.value
viewModel.updateList(dirModel.dirPath)
})
}
}**
}
Add selected field to some class representing the item. Then just compose proper code based on that field. In compose you don't have to look for some LazyColumn flag or anything like that. You are in control of the whole state of the list.
The same can be said about the AppBar, you can do a simple if there, like if (items.any { it.selected }) // display button
You can simply handle this by the below code:
Fore example This is your list:
LazyColumn {
items(items = filters) { item ->
FilterItem(item)
}
}
and this is your list item View:
#Composable
fun FilterItem(filter: FilterModel) {
val (selected, onSelected) = remember { mutableStateOf(false) }
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.padding(horizontal = 8.dp)
.border(
width = 1.dp,
colorResource(
if (selected)
R.color.black
else
R.color.white
),
RoundedCornerShape(8.dp)
)) {
Text(
modifier = Modifier
.toggleable(
value =
selected,
onValueChange =
onSelected
)
.padding(8.dp),
text = filter.text)
}
}
and that's it use tooggleable on your click component modifier like here I did on text.

Jetpack Compose onClick ripple is not propagating with a circular motion?

As can be seen in gif
when Column that contains of Text, Spacer, and LazyRowForIndexed is touched ripple is not propagating with circular motion. And it gets touched effect even when horizontal list is touched.
#Composable
fun Chip(modifier: Modifier = Modifier, text: String) {
Card(
modifier = modifier,
border = BorderStroke(color = Color.Black, width = Dp.Hairline),
shape = RoundedCornerShape(8.dp)
) {
Row(
modifier = Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.preferredSize(16.dp, 16.dp)
.background(color = MaterialTheme.colors.secondary)
)
Spacer(Modifier.preferredWidth(4.dp))
Text(text = text)
}
}
}
#Composable
fun TutorialSectionCard(model: TutorialSectionModel) {
Column(
modifier = Modifier
.padding(top = 8.dp)
.clickable(onClick = { /* Ignoring onClick */ })
.padding(16.dp)
) {
Text(text = model.title, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.h6)
Spacer(Modifier.preferredHeight(8.dp))
Providers(AmbientContentAlpha provides ContentAlpha.medium) {
Text(model.description, style = MaterialTheme.typography.body2)
}
Spacer(Modifier.preferredHeight(16.dp))
LazyRowForIndexed(items = model.tags) { _: Int, item: String ->
Chip(text = item)
Spacer(Modifier.preferredWidth(4.dp))
}
}
}
#Preview
#Composable
fun TutorialSectionCardPreview() {
val model = TutorialSectionModel(
clazz = MainActivity::class.java,
title = "1-1 Column/Row Basics",
description = "Create Rows and Columns that adds elements in vertical order",
tags = listOf("Jetpack", "Compose", "Rows", "Columns", "Layouts", "Text", "Modifier")
)
Column {
TutorialSectionCard(model)
TutorialSectionCard(model)
TutorialSectionCard(model)
}
}
What should be done to have circular effect, but not when list itself or an item from list is touched, or scrolled?
You have to apply a Theme to your composable, which in turn provides a default ripple factory, or you have to set the ripple explicitly:
#Preview
#Composable
fun TutorialSectionCardPreview() {
MaterialTheme() {
Column {
TutorialSectionCard
...
}
}
}
or
Column(
modifier = Modifier
.padding(top = 8.dp)
.clickable(
onClick = { /* Ignoring onClick */ },
indication = rememberRipple(bounded = true)
)
.padding(16.dp)
) {
// content
}
(As of compose version 1.0.0-alpha09 there seems to be no way to prevent the ripple from showing when content is scrolled)
I'm using this approach:
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
color = Color.Black
),
onClick = {
}
)
I also figured out how to keep ripple only for the card not the scrollable list that contains tags. To prevent ripple only move through cards use a Box which places it's children as a stack, and add clickable to section that contains header and text.
#Composable
fun TutorialSectionCard(
model: TutorialSectionModel,
onClick: ((TutorialSectionModel) -> Unit)? = null
) {
Card(
modifier = Modifier.padding(vertical = 3.dp, horizontal = 8.dp),
elevation = 1.dp,
shape = RoundedCornerShape(8.dp)
) {
Box(
contentAlignment = Alignment.BottomStart
) {
TutorialContentComponent(onClick, model)
TutorialTagsComponent(model)
}
}
}
#Composable
private fun TutorialContentComponent(
onClick: ((TutorialSectionModel) -> Unit)?,
model: TutorialSectionModel
) {
Column(Modifier
.clickable(
onClick = { onClick?.invoke(model) }
)
.padding(16.dp)
) {
Text(
text = model.title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.h6
)
// Vertical spacing
Spacer(Modifier.height(8.dp))
// Description text
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(model.description, style = MaterialTheme.typography.body2)
}
// Vertical spacing
Spacer(Modifier.height(36.dp))
}
}
#Composable
private fun TutorialTagsComponent(model: TutorialSectionModel) {
Column(Modifier.padding(12.dp)) {
// Horizontal list for tags
LazyRow(content = {
items(model.tags) { tag ->
TutorialChip(text = tag)
Spacer(Modifier.width(8.dp))
}
})
}
}

Categories

Resources