I'm trying to make a UI similar to the YouTube channel UI. I've got two lazy columns (I don't know if it's the correct way or not) in a column, in which the first lazy column consists of data regarding the second lazy column and a sticky header. I've added a horizontal pager from the accompanist library in which the second lazy column exists. As expected, the first lazy column doesn't scroll. Is there any way to scroll these two lazy columns simultaneously or as a single UI component?
PS:- I've checked the related question but it doesn't solve the problem
Code:-
Column(modifier = Modifier.background(md_theme_dark_surface)) {
LazyColumn{
item {
SmallTopAppBar(
title = {
Text(
text = headerText,
style = MaterialTheme.typography.titleMedium,
fontSize = 24.sp,
color = md_theme_dark_onSurface
)
},
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = md_theme_dark_surface)
)
AsyncImage(
model = ImageRequest.Builder(context).crossfade(true)
.data("")
.build(),
contentDescription = "null",
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
onError = painterResource(id = R.drawable.image),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.padding(top = 15.dp)
.fillMaxWidth()
.wrapContentHeight(),
contentAlignment = Alignment.Center
) {
Text(
text = "Cover-art stolen from xyz from abc",
style = MaterialTheme.typography.titleMedium,
fontSize = 16.sp,
color = md_theme_dark_onSurface
)
}
}
stickyHeader {
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
containerColor = md_theme_dark_surface
) {
tabsList.forEachIndexed { index, tabsData ->
Tab(
selected = pagerState.currentPage == index, onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}.start()
}, modifier = Modifier.padding(20.dp)
) {
Text(
text = tabsData.name,
style = MaterialTheme.typography.titleMedium,
color = md_theme_dark_onSurface,
fontSize = 17.sp
)
}
}
}
}
}
HorizontalPager(count = tabsList.size, state = pagerState) {
LazyColumn{
itemsIndexed(fetchedData) { index, dataItem ->
if (!dataItem.data.is_video && dataItem.data.url.contains(
regex = Regex(
"/i.redd.it"
)
)
) {
val screensList = subHomeScreensList
screensList[pagerState.currentPage].composable()
}
}
}
}
}
Screen recording of current code output.
How can I solve this problem, Thank you :)
Related
I have nested column, when I click add button the goal is add another text field and when I click delete button (which still hidden because first index) the goal is remove its text field. It seems doesn't recompose but the list size is changed.
I have tried using LazyColumn and foreach inside leads to force close, still no luck.
Any help appreciated, thank you!
My current code :
#Composable
fun ProblemScreen() {
val list = remember {
mutableStateListOf<MutableList<String>>()
}
LaunchedEffect(key1 = Unit, block = {
repeat(3) {
val listDesc = mutableListOf<String>()
repeat(1) {
listDesc.add("")
}
list.add(listDesc)
}
})
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
list.forEachIndexed { indexParent, parent ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Parent ${indexParent + 1}", fontSize = 18.sp)
Spacer(modifier = Modifier.weight(1f))
Button(onClick = {
parent.add("")
println("PARENT SIZE : ${parent.size}")
}) {
Icon(imageVector = Icons.Rounded.Add, contentDescription = "Add")
}
}
parent.forEachIndexed { indexChild, child ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = "",
onValueChange = {
},
colors = TextFieldDefaults.textFieldColors(),
maxLines = 1,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
Button(
onClick = {
parent.removeAt(indexChild)
},
modifier = Modifier.alpha(if (indexChild != 0) 1f else 0f)
) {
Icon(
imageVector = Icons.Rounded.Delete,
contentDescription = "Delete"
)
}
}
}
}
}
}
}
As said in docs, mutable objects that are not observable, such as mutableListOf(), are not observable by Compose and don't trigger a recomposition.
So instead of
val list = remember {
mutableStateListOf<MutableList<String>>()
}
Use:
val list = remember {
mutableStateListOf<List<String>>()
}
And when you need to update the List, create a new one:
//parent.add("")
list[indexParent] = parent + ""
I am making a project with Jetpack Compose. I want to display comments like there are in Instagram. There is an array that contains comments.
This is a code used to display comments:
val i : Int
for(i in 1..user.count) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image1(
painter = painterResource(id = user.pp),
contentDescription = "PP",
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
)
Spacer(modifier = Modifier.width(10.dp))
Column() {
Row() {
Text(text = user.name, color = Color.Black, fontSize = 20.sp)
Spacer(modifier = Modifier.width(10.dp))
}
Spacer(modifier = Modifier.width(10.dp))
Text(text = "Public", color = Color.DarkGray, fontSize = 13.sp)
}
}
IconButton(onClick = { /*TODO*/ }) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_more_vert_24),
contentDescription = "More"
)
}
}
Row() {
Spacer(modifier = Modifier.width(10.dp))
Text(
text = user.c[i-1],
color = Color.Black,
fontSize = 16.sp,
modifier = Modifier.padding(end = 10.dp)
)
}
Spacer(modifier = Modifier.height(10.dp))
Row() {
var isClicked by remember {
mutableStateOf(false)
}
Spacer(modifier = Modifier.width(10.dp))
Icon(
painter = painterResource(
id =
if (!isClicked) R.drawable.like_in_comments else R.drawable.like
),
contentDescription = "Like",
tint = Color.Blue,
modifier = Modifier
.size(25.dp)
.clickable { isClicked = !isClicked }
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "Like",
color = Color.DarkGray,
fontSize = 16.sp,
)
}
Spacer(modifier = Modifier.height(10.dp))
Divider()
}
I want to make it scrollable. I can use LazyRow. When I use it, I get some errors. How do I implement it? Please help.
You can make an ordinary Row scrollable by supplying its Modifier.horizontalScroll() with a scroll state like the following
val scrollState = rememberScrollState()
Row (modifier = Modifier.horizontalScroll(scrollState)) {
...
}
But for a simple LazyRow without having a unique key, you don't need to iterate each item via a loop construct (e.g a for-loop), you just have to simply call items and pass the list.
val itemList = listOf("Item1", "Item2")
LazyRow {
items(itemList) { item ->
// your composable here
}
}
For a LazyRow with a unique key (assuming you have a class with an "id" attribute)
val people = listOf(Person(1, "person1"), Person(2, "person2"))
LazyRow {
items(items = people, key = { item -> person.id }) { person->
// your composable here
}
}
I want to create this type of slid switch using jetpack compose but I don't know how to do it can anyone help me with it? it will toggle between this 2 option
You can use TabRow like this:
val items = (0..1)
var activeTabIndex by remember { mutableStateOf(0) }
TabRow(
selectedTabIndex = activeTabIndex, backgroundColor = Color.Transparent,
indicator = {
Box(
Modifier
.tabIndicatorOffset(it[activeTabIndex])
.fillMaxSize()
.background(color = Color.Cyan)
.zIndex(-1F)
)
},
) {
items.mapIndexed { i, item ->
Tab(selected = activeTabIndex == i, onClick = { activeTabIndex = i }) {
Icon(
painter = painterResource(id = someIcon),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.padding(vertical = 20.dp)
)
}
}
}
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.
I want to build a dropdown menu with items that contain not only text, but also an image. The image should be loaded from an url. I'm using Dropdown Menu and Accompanist to load the image.
But when I try to open the Dropdown Menu, I get java.lang.IllegalStateException: Intrinsic measurements are not currently supported by SubcomposeLayout.
I've tried to play around with Intrinsics in my Composables like here https://developer.android.com/codelabs/jetpack-compose-layouts#10, but it didn't work. If I don't use CoilImage, but get a painter from resources with Image, everything works fine.
Is there a way to solve it?
#Composable
fun DropdownChildren(
items: List<ChildUiModel>,
chosenChild: ChildUiModel?,
onChildChosen: (ChildUiModel) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(0) }
Box(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.TopStart)) {
Row(modifier = Modifier
.fillMaxSize()
.clickable(onClick = { expanded = true })) {
ChildrenDropdownMenuItem(
imageUrl = "https://oneyearwithjesus.files.wordpress.com/2014/09/shutterstock_20317516.jpg",
text = items[selectedIndex].name?: "No name",
chosen = false)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.fillMaxWidth()
.requiredSizeIn(maxHeight = 500.dp)
) {
items.forEachIndexed { index, child ->
DropdownMenuItem(
modifier = Modifier
.background(color = if (child.id == chosenChild?.id) appColors.secondary else appColors.primary),
onClick = {
expanded = false
selectedIndex = index
onChildChosen(items[selectedIndex])
}) {
ChildrenDropdownMenuItem(
imageUrl = "https://oneyearwithjesus.files.wordpress.com/2014/09/shutterstock_20317516.jpg",
text = child.name,
chosen = child.id == chosenChild?.id)
}
}
}
}
}
#Composable
fun ChildrenDropdownMenuItem(
imageUrl: String,
text: String,
chosen: Boolean
){
Row(){
Avatar(url = imageUrl)
Text(text = text,
style = AppTheme.typography.h4,
color = if (chosen) appColors.primary else appColors.secondary,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically))
}
}
#Composable
fun Avatar(
url: String
){
val contentPadding = PaddingValues(8.dp, 8.dp, 12.dp, 8.dp)
CoilImage(
data = url,
) { imageState ->
when (imageState) {
is ImageLoadState.Success -> {
MaterialLoadingImage(
result = imageState,
contentDescription = "avatar",
modifier = Modifier
.padding(contentPadding)
.clip(CircleShape)
.size(48.dp),
contentScale = ContentScale.Crop,
fadeInEnabled = true,
fadeInDurationMs = 600,
)
}
is ImageLoadState.Error -> CoilImage(
data = "https://www.padtinc.com/blog/wp-content/uploads/2020/09/plc-errors.jpg",
contentDescription = "error",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(contentPadding)
.clip(CircleShape)
.size(48.dp)
)
ImageLoadState.Loading -> CircularProgressIndicator()
ImageLoadState.Empty -> {}
}
}
}
CoilImage is now Deprecated,
use this
Image(
painter = rememberCoilPainter("https://picsum.photos/300/300"),
contentDescription = stringResource(R.string.image_content_desc),
previewPlaceholder = R.drawable.placeholder,
)
As you mentioned on the bug, this has already been fixed in v0.8.0 of Accompanist. You need to use the new rememberCoilPainter() though, and not the deprecated CoilImage().