I'am new to jetpack compose and i really liked it. But ran into a problem : I to have a modal that take all my screen size, following multiples tutorial i was able to do it using ModalBottomSheetLayout, Here is my code :
#ExperimentalMaterialApi
#Composable
fun DetailsScreen() {
val coroutineScope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
ModalBottomSheetLayout(
sheetContent = {
Box(
modifier = Modifier
.navigationBarsWithImePadding()
.fillMaxSize()
.wrapContentHeight() //changed here to have full screen size, but it was only half screen, i had to slide with my finger to make the modal to full screen
//.height(200.dp) initial value i have a little modal on the bottom
//.fillMaxWidth()
.background(Color.White)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
Text(
text = "Bottom Sheet",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Item 1",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
Text(
text = "Item 2",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
Text(
text = "Item 3",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
}
}
},
sheetState = sheetState,
sheetBackgroundColor = Color.White
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = "Modal Bottom Sheet",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
)
}
) {
MainContent(
onClick = {
coroutineScope.launch {
//sheetState.show() // changed here make the modal full screen at the beginning
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
modifier = Modifier.padding(it)
)
}
}
}
#Composable
fun MainContent(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(10.dp)
) {
Text(
text = "Click Me!",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6.copy(color = Color.White)
)
}
}
}
Problem i encounter : at first the modal was just at the bottom so i made my box to fillMaxSize to make it full screen, but it only open to half screen and i slide to swipe up to make it full screen.
Then i found that i could change sheetState.show() to sheetState.animateTo(ModalBottomSheetValue.Expanded) to make my modal full screen from the moment it display. Perfect it's what i wanted. here is a screen of the modal :
The problem is that now when i slide down to dismiss the modal it's stop half scree as bellow:
From there i have to swipe down an other time to make it disappear. Also when it's on the middle i can swipe up to make it back to full screen. It's seem that there is 3 states: fullscreen, halfscreen, and gone. And the modal can be full screen or halfscreen. I would like to remove the half screen. So when i open the modal it's fullscreen and when i swipe down to remove it, it disappear without stoping in the middle. So i would have only one gesture to remove it.
I think you can get this behavior with skipHalfExpanded property:
val bottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Expanded, skipHalfExpanded = true)
Related
I am trying to overlap two different compose elements. I want to show a toast kind of message at the top whenever there is an error message. I don't want to use a third party lib for such an easy use case. I plan to use the toast in every other composable screen for displaying error message. Below is the layout which i want to achieve
So I want to achieve the toast message saying "Invalid PIN, please try again".
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
and my screen composable is as follows
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box(){
MyToast(
title = "Invalid pin, please try again"
)
Column() {
//my other screen components
}
}
}
I will add the AnimatedVisibility modifier later to MyToast composable. First I need to overlap MyToast over all the other elements and somehow MyToast is just not visible
If you want the child of a Box to overlay/overlap its siblings behind, you should put it at the last part in the code
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
}
So if I put the green box before the bigger red box like this
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
}
the green box will hide behind the red one
You have to solutions, you can either put the Toast in the bottom of your code because order matters in compose:
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box() {
Column() {
//my other screen components
}
MyToast(
title = "Invalid pin, please try again"
)
}
}
}
Or you can keep it as it is, but add zIndex to the Toast:
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.zIndex(10f) // add z index here
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
Note: elevation in Card composable is not the same as elevation in XML so it's not going to make the composable in the top, it will just add a shadow but if you want to give the composable a higher z order use Modifier.zIndex(10f)
I like to keep the current page indicator a specific shape like in the picture when I'm on the active page, but want to keep the previous tab indicator like a dot shape when I swipe from one screen to another. But, from what I can tell, it doesn't seem like I can change the indicatorShape below based on the currently active page state, and keep the previous shape different. Any ideas would be greatly appreciated. Thank You!
#OptIn(ExperimentalPagerApi::class)
#Composable
fun HorizontalPagerScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(30.dp)
) {
val items = createItems()
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
HorizontalPager(
count = items.size,
state = pagerState,
modifier = Modifier.weight(1f)
) { currentPage ->
Column(
modifier = Modifier.fillMaxSize()
) {
Text(
text = items[currentPage].title,
style = MaterialTheme.typography.h2
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = items[currentPage].subtitle,
style = MaterialTheme.typography.h4
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = items[currentPage].description,
style = MaterialTheme.typography.body1
)
}
}
HorizontalPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(16.dp),
indicatorHeight = 8.dp,
activeColor = Color.Blue,
indicatorWidth = ChangeSizeOfShape(currentPage = pagerState.currentPage),
pageCount = 2,
indicatorShape =RoundedCornerShape(corner = CornerSize(40.dp))
)
Button(
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(page = 2)
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "Scroll to the third page")
}
}
}
As of Oct 14, 2022, there isn't anything built-in that can allow us to change view pager indicator size/shape the way we want. But, there are few other ways of doing it for now that are available online.
Animated worm page Indicator. This is in compose.
With Jetpack Compose, I'm trying to implement a function to switch between items with flick actions, like the ViewPager in Android JetPack.
At first I tried to get it to work using a dialog as shown in the code below. However, I found that the dialog cannot be moved horizontally with flick actions.
#OptIn(ExperimentalPagerApi::class)
#Composable
fun ContentDisplayWindow(
openDialog: MutableState<Boolean>,
contentList: List<Content>,
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
modifier = Modifier.align(Alignment.End),
onClick = { openDialog.value = false }
) {
Icon(
painter = painterResource(R.drawable.ic_arrow_back),
contentDescription = ""
)
}
HorizontalPager(
count = contentList.size
) { index ->
Dialog(
onDismissRequest = { openDialog.value = false },
properties = DialogProperties(dismissOnClickOutside = false)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.border(2.dp, Color.Red)
.background(color = MaterialTheme.colors.surface)
) {
Text(
text = contentList[index].title,
modifier = Modifier.padding(
top = 10.dp,
bottom = 10.dp,
start = 20.dp
)
)
Spacer(modifier = Modifier.padding(vertical = 10.dp))
Text(
text = contentList[index].description,
modifier = Modifier.padding(
top = 10.dp,
bottom = 10.dp,
start = 20.dp
)
)
}
}
}
}
}
implementaton:
implementation "com.google.accompanist:accompanist-pager:0.25.1"
So I tried to find a way to perform the process of dimming the screen and disabling other buttons or input fields such as a dialog, but was not able to find it. Is there any other way to make it work like a dialog?
I have comment box and need to put icon on specific position ( bottom right ). I need to make something like position absolute where my icon button need to be bottom right inside comment box. Here is image what I am trying to achieve. Any help or idea?
You can do it by using Modifier.offset{} and putting your Icon inside a Box with Modifier.align(Alignment.BottomEnd)
#Composable
private fun Test() {
Column(modifier = Modifier.padding(10.dp)) {
Box(
modifier = Modifier
.width(200.dp)
.background(Color.LightGray.copy(alpha = .5f), RoundedCornerShape(8.dp))
.padding(4.dp)
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Text("Title", fontSize = 20.sp)
Text("Comment")
}
val offsetInPx = with(LocalDensity.current) {
16.dp.roundToPx()
}
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
modifier = Modifier
.offset {
IntOffset(-offsetInPx, offsetInPx)
}
.shadow(2.dp, RoundedCornerShape(40))
.background(Color.White)
.padding(horizontal = 10.dp, vertical = 4.dp)
.size(30.dp)
.align(Alignment.BottomEnd),
tint = Color.LightGray
)
}
Spacer(modifier = Modifier.height(4.dp))
Text("Reply", color = Color.Blue)
}
}
I'm trying to build a custom Drop down menu and I encountered some issues in animating its state. The animation is both laggy and sketchy, even on a real device and even on a release build (APK). The Compose version I'm using is 1.1.1.
Observe the flicker (and the lag?).
The code:
Column(modifier = Modifier.fillMaxWidth()) {
var visible by remember { mutableStateOf(false) }
//header
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { visible = !visible }
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Click me",
style = MaterialTheme.typography.h6
)
Icon(
modifier = Modifier.rotate(animateFloatAsState(if (visible) 180f else 0f).value),
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null
)
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = Color.Black.copy(ContentAlpha.disabled)
)
}
//the 4 items
Column {
(1..4).forEach {
AnimatedVisibility(
visible = visible,
enter = expandVertically(
spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = IntSize.VisibilityThreshold
),
),
exit = shrinkVertically(),
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Hello",
style = MaterialTheme.typography.h6
)
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = null
)
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = Color.Black
)
}
}
}
}
}
If I add some bottom padding to the bigger Column or if I make it occupy the whole screen's height, there's no more flicker, but I feel like that is a workaround and also I'm not sure whether the animation is lagging or not, so this wouldn't be a solution to all my problems. The parent Column wraps around its content and as the content size increases, it tries to "keep up" with the new size, but it doesn't do a perfect job. Am I using AnimatedVisibility improperly? How else could I create a custom Drop down menu?