I tried to make a fullscreen dialog using Jetpack Compose using this code:
Dialog(onDismissRequest = { /*TODO*/ }) {
NewPostDialog()
}
It ended up looking something like this. How can I remove the margin at the side (marked red)?
UPDATE: As #Nestor Perez mentioned, since compose 1.0.0-rc01 you can set usePlatformDefaultWidthin DialogProperties to make a dialog fill the whole screenwidth:
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest...
){
Surface(modifier = Modifier.fillMaxSize()) {
DialogContent()
}
}
Compose Dialog uses ContextThemeWrapper so you should be able to theme your dialog with a custom style.
themes.xml:
<style name="Theme.YourApp" parent="Theme.MaterialComponents.Light.NoActionBar">
//theme content...
<item name="android:dialogTheme">#style/Theme.DialogFullScreen</item>
</style>
<style name="Theme.DialogFullScreen" parent="#style/ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="android:windowMinWidthMajor">100%</item>
<item name="android:windowMinWidthMinor">100%</item>
</style>
And in code:
#Composable
fun FullScreenDialog(showDialog:Boolean, onClose:()->Unit) {
if (showDialog) {
Dialog(onDismissRequest = onClose ) {
Surface(
modifier = Modifier.fillMaxSize(),
shape = RoundedCornerShape(16.dp),
color = Color.LightGray
) {
Box(
contentAlignment = Alignment.Center
) {
Text(modifier = Modifier.align(Alignment.TopCenter),
text = "top")
Text("center")
Text(
modifier = Modifier.align(Alignment.BottomCenter),
text = "bottom")
}
}
}
}
}
Solution from jns didnt work too well form me, I leave another solution here if anyone is still looking:
Implement the theme as jns answer:
<style name="Theme.Outlay" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<!-- Customize your theme here. -->
<item name="android:dialogTheme">#style/Theme.DialogFullScreen</item >
</style>
<style name="Theme.DialogFullScreen" parent="#style/ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="android:windowMinWidthMajor">100%</item>
<item name="android:windowMinWidthMinor">100%</item>
</style>
For the dialog create an scaffold and add the experimental property "usePlatformDefaultWidth = false" on dialog properties:
Dialog(
onDismissRequest = onBackPressed,
properties = DialogProperties(
usePlatformDefaultWidth = false
)
) {
Scaffold(topBar = { TopBar(onBackPressed = onBackPressed) }) {
Content()
}
}
If you want to avoid having an xml theme entirely and also avoid doing this for all dialogs, you can set a requiredWidth modifier to be equal to LocalConfiguration.current.screenWidthDp.dp (multiplied by some fraction as you please).
An example that takes up 0.96f of the screen width:
#Composable
fun LargerDialog(
dialogOpen: MutableState<Boolean>
) {
Dialog(onDismissRequest = { dialogOpen.value = false }) {
Card( // or Surface
elevation = 8.dp,
modifier = Modifier
.requiredWidth(LocalConfiguration.current.screenWidthDp.dp * 0.96f)
.padding(4.dp)
) {
// content
}
}
}
To make a full-screen dialog using Jetpack Compose using this code:
EG1:
Full screen dialog screenshot
package compose.material.theme
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import compose.material.theme.ui.theme.Material3ComposeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Material3ComposeTheme {
val openFullDialogCustom = remember { mutableStateOf(false) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.padding(20.dp)
.verticalScroll(rememberScrollState())
) {
//...................................................................
// * full screen custom dialog
Button(
onClick = {
openFullDialogCustom.value = true
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "No internet",style = MaterialTheme.typography.labelLarge)
}
}
}
//...............................................................................
//Full screen Custom Dialog Sample
NoInternetScreen(openFullDialogCustom)
}
}
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
private fun NoInternetScreen(openFullDialogCustom: MutableState<Boolean>) {
if (openFullDialogCustom.value) {
Dialog(
onDismissRequest = {
openFullDialogCustom.value = false
},
properties = DialogProperties(
usePlatformDefaultWidth = false // experimental
)
) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.no_intrenet),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(20.dp))
//.........................Text: title
Text(
text = "Whoops!!",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 20.dp)
.fillMaxWidth(),
letterSpacing = 2.sp,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.height(8.dp))
//.........................Text : description
Text(
text = "No Internet connection was found. Check your connection or try again.",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, start = 25.dp, end = 25.dp)
.fillMaxWidth(),
letterSpacing = 1.sp,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
)
//.........................Spacer
Spacer(modifier = Modifier.height(24.dp))
val cornerRadius = 16.dp
val gradientColor = listOf(Color(0xFFff669f), Color(0xFFff8961))
GradientButton(
gradientColors = gradientColor,
cornerRadius = cornerRadius,
nameButton = "Try again",
roundedCornerShape = RoundedCornerShape(topStart = 30.dp,bottomEnd = 30.dp)
)
}
}
}
}
}
}
//...........................................................................
#Composable
fun GradientButton(
gradientColors: List<Color>,
cornerRadius: Dp,
nameButton: String,
roundedCornerShape: RoundedCornerShape
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 32.dp, end = 32.dp),
onClick = {
//your code
},
contentPadding = PaddingValues(),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
shape = RoundedCornerShape(cornerRadius)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
brush = Brush.horizontalGradient(colors = gradientColors),
shape = roundedCornerShape
)
.clip(roundedCornerShape)
/*.background(
brush = Brush.linearGradient(colors = gradientColors),
shape = RoundedCornerShape(cornerRadius)
)*/
.padding(horizontal = 16.dp, vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Text(
text = nameButton,
fontSize = 20.sp,
color = Color.White
)
}
}
}
Related
I have a screen that includes only a LazyRow horizontal list includes 2 Cards, so you can slide cards. You can see my code below:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.jetpackdeneme.R
#Composable
fun TestScreen1() {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
LazyRow(
Modifier.height(160.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
item {
PromotionItem1(
imagePainter = painterResource(id = R.drawable.statue_of_liberty),
title = "AMERICA",
header = "USA",
backgroundColor = Color.White
)
}
item {
PromotionItem1(
imagePainter = painterResource(id = R.drawable.statue_of_liberty),
title = "AMERICA",
header = "USA",
backgroundColor = Color.White
)
}
}
}
}
}
}
#Composable
fun PromotionItem1(
title: String = "",
header: String = "",
backgroundColor: Color = Color.Transparent,
imagePainter: Painter
) {
Card(
Modifier.width(300.dp),
shape = RoundedCornerShape(8.dp),
backgroundColor = backgroundColor,
elevation = 0.dp
) {
Row {
Image(
painter = imagePainter, contentDescription = "",
modifier = Modifier
.fillMaxHeight()
.weight(1f),
alignment = Alignment.CenterEnd,
contentScale = ContentScale.Crop
)
Column(
Modifier
.padding(horizontal = 16.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center
) {
Text(text = title, fontSize = 14.sp, color = Color.Black, fontWeight = FontWeight.Bold)
Text(text = header, fontSize = 14.sp, color = Color.Black)
}
}
}
}
The output is like this (it is not fitting, and I want a custom style):
I wanted to set the image's bottom edge to bottom of card, and overflow the top a little bit like that output:
You can get the image here
How can I do that? Can you help me please?
First, replace Card with Box which doesn't clip layout that prevents overflow.
Second, set a graphicsLayer to increase scale of Image but by default tranform origin is center since we want to push image upwards we need to set bottom left as transform origin which is TransformOrigin(0f,1f).
Third, increase scale as you wish and have expected output.
#Composable
fun PromotionItem1(
title: String = "",
header: String = "",
backgroundColor: Color = Color.Transparent,
imagePainter: Painter
) {
Box(
Modifier
.width(300.dp)
.background(backgroundColor, RoundedCornerShape(8.dp)),
) {
Row {
Image(
painter = imagePainter,
contentDescription = "",
modifier = Modifier
.graphicsLayer {
scaleY = 1.2f
scaleX = 1.2f
this.transformOrigin = TransformOrigin(0f, 1f)
}
.fillMaxHeight(),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.weight(1f))
Column(
Modifier
.padding(horizontal = 16.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center
) {
Text(
text = title,
fontSize = 14.sp,
color = Color.Black,
fontWeight = FontWeight.Bold
)
Text(text = header, fontSize = 14.sp, color = Color.Black)
}
}
}
}
Also i increased LazyRow height to Modifier.height(200.dp)
Result
I am creating a screen with a color picker. Here's the code for the whole screen, including the color picker code. The reason why I'm including the whole screen's code instead of just the color picker's is because I want to give more context.
package com.gitlab.djsushi123.kingenscoreboard.presentation.screen.newplayer
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.gitlab.djsushi123.kingenscoreboard.domain.model.Player
import com.google.accompanist.systemuicontroller.SystemUiController
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
#Composable
fun NewPlayerScreen(
viewModel: NewPlayerScreenViewModel = getViewModel(),
appNavController: NavHostController,
systemUiController: SystemUiController,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
val state = viewModel.state
val animatedSelectedColor by animateColorAsState(
targetValue = state.playerColor,
animationSpec = tween(durationMillis = 600)
)
val primaryVariantColor = MaterialTheme.colors.primaryVariant
val darkIconsDisposable = MaterialTheme.colors.isLight
val useDarkIcons = with(animatedSelectedColor) {
(red + green + blue) / 3f > 0.5f
}
DisposableEffect(key1 = animatedSelectedColor) {
systemUiController.setStatusBarColor(
color = animatedSelectedColor,
darkIcons = useDarkIcons
)
onDispose {
systemUiController.setStatusBarColor(
color = primaryVariantColor,
darkIcons = darkIconsDisposable
)
}
}
Scaffold(
topBar = {
TopBar(
color = animatedSelectedColor,
onColor = if (useDarkIcons) Color.Black else Color.White,
saveButtonEnabled = state.saveButtonEnabled,
onBackButtonClicked = { appNavController.popBackStack() },
onSave = {
val player = Player(
name = state.playerName,
abbreviation = state.playerAbbreviation,
color = state.playerColor
)
coroutineScope.launch {
viewModel.savePlayer(player)
appNavController.popBackStack()
}
}
)
},
modifier = modifier
) { paddingValues ->
Surface(color = MaterialTheme.colors.surface, modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()) // so that we can scroll with the keyboard open
.padding(paddingValues)
.padding(start = 8.dp, top = 8.dp, end = 8.dp)
) {
// Player name field with error
OutlinedTextField(
value = state.playerName,
onValueChange = {
viewModel.onEvent(
NewPlayerFormEvent.PlayerNameChanged(name = it)
)
},
isError = state.playerNameError != null,
label = { Text(text = "Name") },
placeholder = { Text(text = "John Cena") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words
),
modifier = Modifier.fillMaxWidth()
)
Text(
text = state.playerNameError ?: "",
color = MaterialTheme.colors.error,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
// Player abbreviation field
OutlinedTextField(
value = state.playerAbbreviation,
onValueChange = {
viewModel.onEvent(
NewPlayerFormEvent.PlayerAbbreviationChanged(abbreviation = it)
)
},
isError = state.playerAbbreviationError != null,
label = { Text(text = "Abbreviation") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
autoCorrect = false
),
modifier = Modifier.fillMaxWidth()
)
Text(
text = state.playerAbbreviationError ?: "",
color = MaterialTheme.colors.error,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
ColorPicker(
selected = state.playerColor,
onSelect = { color ->
viewModel.onEvent(
NewPlayerFormEvent.PlayerColorChanged(color = color)
)
},
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
#Composable
private fun TopBar(
color: Color,
onColor: Color,
saveButtonEnabled: Boolean,
onBackButtonClicked: () -> Unit,
onSave: () -> Unit
) {
val saveButtonAlpha by animateFloatAsState(
if (saveButtonEnabled) ContentAlpha.high else ContentAlpha.disabled
)
TopAppBar(
title = { Text(text = "New player", color = onColor) },
navigationIcon = {
IconButton(onClick = onBackButtonClicked) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
tint = onColor
)
}
},
actions = {
IconButton(onClick = onSave, enabled = saveButtonEnabled) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Save",
tint = onColor.copy(alpha = saveButtonAlpha)
)
}
},
backgroundColor = color
)
}
#Composable
private fun ColorPicker(
selected: Color,
onSelect: (color: Color) -> Unit,
modifier: Modifier = Modifier
) {
val colors: List<Color> = remember {
(0..340 step 8).map { hue ->
Color.hsl(hue = hue.toFloat(), saturation = 0.5f, lightness = 0.5f)
}.dropLast(3)
}
Column(modifier = modifier) {
for (row in 0..(colors.size - 1) / 4) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
for (tile in 0 until 4) {
val color = colors[row * 4 + tile]
ColorPickerTile(
color = color,
selected = color == selected,
onClick = { onSelect(color) },
modifier = Modifier
.width(70.dp)
.height(70.dp)
)
}
}
}
}
}
#Composable
private fun ColorPickerTile(
color: Color,
selected: Boolean,
onClick: () -> Unit,
shape: Shape = MaterialTheme.shapes.small,
modifier: Modifier = Modifier
) {
val scale = remember { Animatable(initialValue = 1f) }
val borderWidth by animateDpAsState(
targetValue = if (selected) 6.dp else 0.dp,
animationSpec = tween(durationMillis = 350)
)
LaunchedEffect(key1 = selected) {
if (selected) {
launch {
scale.animateTo(
targetValue = 0.90f,
animationSpec = tween(
durationMillis = 50
)
)
scale.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
}
} else {
launch { scale.animateTo(1f) }
}
}
Box(
modifier = modifier
.scale(scale.value)
.border(
width = borderWidth,
color = if (borderWidth.value == 0f) Color.Transparent else MaterialTheme.colors.onSurface,
shape = shape
)
.clip(shape)
.background(color = color)
.clickable(onClick = onClick)
)
}
My color picker is composed of ColorPickerTiles, which are stacked in a grid that is 4 tiles wide. This grid is a made-up grid from Rows inside a big Column. My problem is related to performance. When we check the "layout inspector" tab in Android Studio, we can see that when the user selects a different color tile in the app, every single ColorPickerTile gets recomposed MULTIPLE TIMES because of an animation I've added. I don't understand why this is necessary and why is this even possible, since the ColorPickerTile composable is restartable and also skippable and none of its parameters changed AFAIK. Here's a screenshot of what I mean:
The fact that these recompositions happen for every single ColorPickerTile many times during an animation means that the whole animation lags, especially on low-end devices. And the lag increases exponentially, with more ColorPickerTiles. My question is, what is causing these useless recompositions? And how would I go about solving the issue?
I am searching how create custom dialog in Jetpack Compose. In XML or Material Design we can create easily custom Dialog in which we can take user input, radio button etc. but i am not finding such thing in Jetpack Compose.
Starting from M3 1.1.0-alpha04 there is an AlertDialog composable function with a slot for content.
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
androidx.compose.material3.AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onDismissRequest.
openDialog.value = false
}
) {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large
) {
Column(modifier = Modifier.padding(16.dp)) {
//... AlertDialog content
}
}
}
}
Before M3 1.1.0-alpha04 or with M2, you can use the standard AlertDialog.
The text,title and buttons parameters support #Composable functions and in this way you can customize the dialog as you prefer.
For example:
val openDialog = remember { mutableStateOf(true) }
var text by remember { mutableStateOf("") }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Column() {
TextField(
value = text,
onValueChange = { text = it }
)
Text("Custom Text")
Checkbox(checked = false, onCheckedChange = {})
}
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { openDialog.value = false }
) {
Text("Dismiss")
}
}
}
)
}
This example demonstrates how to make custom dialog in android jet compose.
Read more
https://www.boltuix.com/2022/01/ice-cream-app-ui-ux.html
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.compose.example.ui.theme.Pink80
import com.compose.example.ui.theme.Purple40
import com.compose.example.ui.theme.Purple80
import com.compose.example.ui.theme.PurpleGrey40
#Composable
fun CustomDialog(openDialogCustom: MutableState<Boolean>) {
Dialog(onDismissRequest = { openDialogCustom.value = false}) {
CustomDialogUI(openDialogCustom = openDialogCustom)
}
}
//Layout
#Composable
fun CustomDialogUI(modifier: Modifier = Modifier, openDialogCustom: MutableState<Boolean>){
Card(
//shape = MaterialTheme.shapes.medium,
shape = RoundedCornerShape(10.dp),
// modifier = modifier.size(280.dp, 240.dp)
modifier = Modifier.padding(10.dp,5.dp,10.dp,10.dp),
elevation = 8.dp
) {
Column(
modifier
.background(Color.White)) {
//.......................................................................
Image(
painter = painterResource(id = R.drawable.notification),
contentDescription = null, // decorative
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(
color = Purple40
),
modifier = Modifier
.padding(top = 35.dp)
.height(70.dp)
.fillMaxWidth(),
)
Column(modifier = Modifier.padding(16.dp)) {
androidx.compose.material3.Text(
text = "Get Updates",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 5.dp)
.fillMaxWidth(),
style = MaterialTheme.typography.labelLarge,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
androidx.compose.material3.Text(
text = "Allow Permission to send you notifications when new art styles added.",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, start = 25.dp, end = 25.dp)
.fillMaxWidth(),
style = MaterialTheme.typography.bodyMedium
)
}
//.......................................................................
Row(
Modifier
.fillMaxWidth()
.padding(top = 10.dp)
.background(Purple80),
horizontalArrangement = Arrangement.SpaceAround) {
androidx.compose.material3.TextButton(onClick = {
openDialogCustom.value = false
}) {
androidx.compose.material3.Text(
"Not Now",
fontWeight = FontWeight.Bold,
color = PurpleGrey40,
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
)
}
androidx.compose.material3.TextButton(onClick = {
openDialogCustom.value = false
}) {
androidx.compose.material3.Text(
"Allow",
fontWeight = FontWeight.ExtraBold,
color = Color.Black,
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
)
}
}
}
}
}
#SuppressLint("UnrememberedMutableState")
#Preview (name="Custom Dialog")
#Composable
fun MyDialogUIPreview(){
CustomDialogUI(openDialogCustom = mutableStateOf(false))
}
Also adding a lambda to return value from dialog back to any other composable is possible with
#Composable
private fun CustomDialogWithResultExample(
onDismiss: () -> Unit,
onNegativeClick: () -> Unit,
onPositiveClick: (Color) -> Unit
) {
var red by remember { mutableStateOf(0f) }
var green by remember { mutableStateOf(0f) }
var blue by remember { mutableStateOf(0f) }
val color = Color(
red = red.toInt(),
green = green.toInt(),
blue = blue.toInt(),
alpha = 255
)
Dialog(onDismissRequest = onDismiss) {
Card(
elevation = 8.dp,
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(8.dp)) {
Text(
text = "Select Color",
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
modifier = Modifier.padding(8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
// Color Selection
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Column {
Text(text = "Red ${red.toInt()}")
Slider(
value = red,
onValueChange = { red = it },
valueRange = 0f..255f,
onValueChangeFinished = {}
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Green ${green.toInt()}")
Slider(
value = green,
onValueChange = { green = it },
valueRange = 0f..255f,
onValueChangeFinished = {}
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Blue ${blue.toInt()}")
Slider(
value = blue,
onValueChange = { blue = it },
valueRange = 0f..255f,
onValueChangeFinished = {}
)
Spacer(modifier = Modifier.height(8.dp))
Surface(
border = BorderStroke(1.dp, Color.DarkGray),
color = color,
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
) {}
}
}
// Buttons
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
TextButton(onClick = onNegativeClick) {
Text(text = "CANCEL")
}
Spacer(modifier = Modifier.width(4.dp))
TextButton(onClick = {
onPositiveClick(color)
}) {
Text(text = "OK")
}
}
}
}
}
}
And show it with
var showCustomDialogWithResult by remember { mutableStateOf(false) }
if (showCustomDialogWithResult) {
CustomDialogWithResultExample(
onDismiss = {
showCustomDialogWithResult = !showCustomDialogWithResult
Toast.makeText(context, "Dialog dismissed!", Toast.LENGTH_SHORT)
.show()
},
onNegativeClick = {
showCustomDialogWithResult = !showCustomDialogWithResult
Toast.makeText(context, "Negative Button Clicked!", Toast.LENGTH_SHORT)
.show()
},
onPositiveClick = { color ->
showCustomDialogWithResult = !showCustomDialogWithResult
Toast.makeText(context, "Selected color: $color", Toast.LENGTH_SHORT)
.show()
}
)
}
And result is
I had to achieve something like this:
Putting image in "title" slot and text in "text" slot of compose AlertDialog, ended with this:
Because "title" and "text" are wrapped with AlertDialogBaselineLayout that is adding padding, and I did not have a clue how to change it.
However, "buttons" slot was not wrapped, and my solution was like in following code ("title" and "text" slot must be set to null and all of dialog content goes into "buttons" slot):
#Composable
fun AppDialog(
modifier: Modifier = Modifier,
dialogState: Boolean = false,
onDialogPositiveButtonClicked: (() -> Unit)? = null,
onDialogStateChange: ((Boolean) -> Unit)? = null,
onDismissRequest: (() -> Unit)? = null,
) {
val textPaddingAll = 24.dp
val buttonPaddingAll = 8.dp
val dialogShape = RoundedCornerShape(16.dp)
if (dialogState) {
AlertDialog(
onDismissRequest = {
onDialogStateChange?.invoke(false)
onDismissRequest?.invoke()
},
title = null,
text = null,
buttons = {
Column{
Image(
painter = painterResource(R.drawable.dialog_top_image),
contentDescription = "",
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
Row(Modifier.padding(all = textPaddingAll)){
TextWithHTMLSupport(
text = stringResource(R.string.gdprText)
)
}
Divider(color = MaterialTheme.colors.onSurface, thickness = 1.dp)
Row(
modifier = Modifier.padding(all = buttonPaddingAll),
horizontalArrangement = Arrangement.Center
) {
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = {
onDialogStateChange?.invoke(false)
onDialogPositiveButtonClicked?.invoke()
}
) {
Text(text = stringResource(R.string.dialog_ok), color = MaterialTheme.colors.onSurface)
}
}
}
},
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false),
modifier = modifier,
shape = dialogShape
)
}
}
If the content of your custom alert dialog need to scroll (eg: landscape mode or the content get longer). You can do like
#Composable
fun CustomDialogScrollable(
onConfirmClicked: () -> Unit,
onDismiss: () -> Unit,
) {
Dialog(
onDismissRequest = onDismiss,
) {
Surface(
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colors.surface,
) {
Column(modifier = Modifier.padding(16.dp)) {
// TITLE
Text(text = "Title", style = MaterialTheme.typography.subtitle1)
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.weight(weight = 1f, fill = false)
.padding(vertical = 16.dp)
) {
Text(
text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
style = MaterialTheme.typography.body2
)
OutlinedTextField(value = "", onValueChange = {
}, Modifier.padding(top = 8.dp), label = { Text(text = "Email") })
// other content can go here
}
// BUTTONS
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
TextButton(onClick = onDismiss) {
Text(text = "Cancel")
}
TextButton(onClick = onConfirmClicked) {
Text(text = "OK")
}
}
}
}
}
}
Using
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
CustomDialog({
// confirm clicked
}, {
openDialog.value = false
})
}
Custom dialog with Image
To create a custom dialog like this.
First, create Dialog composable and set dismissRequest and dialog properties.
Inside the dialog create your own view.
finally, set up the dismiss dialog on the desired button.
#Composable
fun CustomAlertDialog(onDismiss: () -> Unit, onExit: () -> Unit) {
Dialog(onDismissRequest = { onDismiss() }, properties = DialogProperties(
dismissOnBackPress = false,dismissOnClickOutside = false
)) {
Card(
//shape = MaterialTheme.shapes.medium,
shape = RoundedCornerShape(10.dp),
// modifier = modifier.size(280.dp, 240.dp)
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = 8.dp
) {
Column(
Modifier
.fillMaxWidth()
.background(Color.White)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.Red.copy(alpha = 0.8F)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Image(
painter = painterResource(id = R.drawable.background_image),
contentDescription = "Exit app",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillWidth
)
}
Text(
text = "Lorem Ipsum is simply dummy text",
modifier = Modifier.padding(8.dp), fontSize = 20.sp
)
Text(
text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard",
modifier = Modifier.padding(8.dp)
)
Row(Modifier.padding(top = 10.dp)) {
OutlinedButton(
onClick = { onDismiss() },
Modifier
.fillMaxWidth()
.padding(8.dp)
.weight(1F)
) {
Text(text = "Cancel")
}
Button(
onClick = { onExit() },
Modifier
.fillMaxWidth()
.padding(8.dp)
.weight(1F)
) {
Text(text = "Exit")
}
}
}
}
}
}
Now we have created the Custom dialog, to show the dialog on button click, you need to create a mutableStateOf() variable to maintain the dialog show and dismiss state.
Also, create conditions like if the variable is true call the dialog, otherwise don’t call the dialog function.
#Composable
fun Content() {
val context = LocalContext.current
var showCustomDialog by remember {
mutableStateOf(false)
}
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { showCustomDialog = !showCustomDialog }, Modifier.wrapContentSize()) {
Text(text = "Show Alert Dialog")
}
}
if (showCustomDialog) {
CustomAlertDialog({
showCustomDialog = !showCustomDialog
}, {
val activity = (context as? Activity)
activity?.finish()
})
}
}
when clicking on the dismiss button on the dialog, need to update a variable to false to hide the dialog.
The final output of the code is below,
Alert dialog with Input field
As mentioned above, we need to create the dialog with a Dialog() composable function. But for the input field, we need to create a mutableStateOf() variable to hold the values of the input field.
#Composable
fun InputDialogView(onDismiss:() -> Unit) {
val context = LocalContext.current
var searchedFood by remember {
mutableStateOf("")
}
Dialog(onDismissRequest = { onDismiss() }) {
Card(
//shape = MaterialTheme.shapes.medium,
shape = RoundedCornerShape(10.dp),
// modifier = modifier.size(280.dp, 240.dp)
modifier = Modifier.padding(8.dp),
elevation = 8.dp
) {
Column(
Modifier
.background(Color.White)
) {
Text(
text = "Search your favorite food",
modifier = Modifier.padding(8.dp),
fontSize = 20.sp
)
OutlinedTextField(
value = searchedFood,
onValueChange = { searchedFood = it }, modifier = Modifier.padding(8.dp),
label = { Text("Favorite Food") }
)
Row {
OutlinedButton(
onClick = { onDismiss() },
Modifier
.fillMaxWidth()
.padding(8.dp)
.weight(1F)
) {
Text(text = "Cancel")
}
Button(
onClick = {
Toast.makeText(context, searchedFood, Toast.LENGTH_SHORT).show()
onDismiss() },
Modifier
.fillMaxWidth()
.padding(8.dp)
.weight(1F)
) {
Text(text = "Search")
}
}
}
}
}
}
To display the dialog you need to follow the same way above. by creating the mutableStateOf() variable and making it true or false.
The output of the above code is,
Loading Dialog
For the loading dialog, we need to use CircularProgressIndicator() composable function for loading animation. Apart from that everything is the same as other custom dialogs.
#Composable
fun LoadingView(onDismiss:() -> Unit) {
Dialog(onDismissRequest = { onDismiss() }) {
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier,
elevation = 8.dp
) {
Column(
Modifier
.background(Color.White)
.padding(12.dp)
) {
Text(
text = "Loading.. Please wait..",
Modifier
.padding(8.dp), textAlign = TextAlign.Center
)
CircularProgressIndicator(
strokeWidth = 4.dp,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(8.dp)
)
}
}
}
}
The output of the about loading dialog code,
It's easy to create a custom dialog in Jetpack Compose.
Here is a dialog box that asks to enalbe 2 Step verification. I have also added click events.
Output:
Code:
For more designs with source code, see Jetpack Compose Samples
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
/*
For more designs with source code,
visit: https://semicolonspace.com/jetpack-compose-samples/
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.background),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
var openDialog by remember {
mutableStateOf(false) // Initially dialog is closed
}
ButtonClick(buttonText = "Open Dialog") {
openDialog = true
}
if (openDialog) {
DialogBox2FA {
openDialog = false
}
}
}
}
}
}
}
}
#Composable
fun DialogBox2FA(onDismiss: () -> Unit) {
val contextForToast = LocalContext.current.applicationContext
Dialog(
onDismissRequest = {
onDismiss()
}
) {
Surface(
modifier = Modifier
.fillMaxWidth(),
elevation = 4.dp
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.background(color = Color(0xFF35898f)),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier
.padding(top = 16.dp, bottom = 16.dp),
painter = painterResource(id = R.drawable.image_security),
contentDescription = "2-Step Verification",
alignment = Alignment.Center
)
}
Text(
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
text = "2-Step Verification",
textAlign = TextAlign.Center,
style = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_bold, FontWeight.Bold)),
fontSize = 20.sp
)
)
Text(
modifier = Modifier.padding(start = 12.dp, end = 12.dp),
text = "Setup 2-Step Verification to add additional layer of security to your account.",
textAlign = TextAlign.Center,
style = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_regular, FontWeight.Normal)),
fontSize = 14.sp
)
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(top = 36.dp, start = 36.dp, end = 36.dp, bottom = 8.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF35898f)),
onClick = {
onDismiss()
Toast.makeText(
contextForToast,
"Click: Setup Now",
Toast.LENGTH_SHORT
).show()
}) {
Text(
text = "Setup Now",
color = Color.White,
style = TextStyle(
fontFamily = FontFamily(
Font(
R.font.roboto_medium,
FontWeight.Medium
)
),
fontSize = 16.sp
)
)
}
TextButton(
onClick = {
onDismiss()
Toast.makeText(
contextForToast,
"Click: I'll Do It Later",
Toast.LENGTH_SHORT
).show()
}) {
Text(
text = "I'll Do It Later",
color = Color(0xFF35898f),
style = TextStyle(
fontFamily = FontFamily(
Font(
R.font.roboto_regular,
FontWeight.Normal
)
),
fontSize = 14.sp
)
)
}
}
}
}
}
#Composable
fun ButtonClick(
buttonText: String,
onButtonClick: () -> Unit
) {
Button(
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary),
onClick = {
onButtonClick()
}) {
Text(
text = buttonText,
fontSize = 16.sp,
color = Color.White
)
}
}
I wanted a layout similar to
`<Scrollview>
<Relativelayout>
<Recyclerview/>(Horizontal)
<Recyclerview/>(Vertical)
</Relativelayout>
</Scrollview>`
this is my compose code related to the issue
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.composeappdemo.R
import com.example.composeappdemo.model.Feature
import com.example.composeappdemo.ui.theme.*
#ExperimentalFoundationApi
#Composable
fun HomeScreen(list: List<String>) {
var features = listOf(
Feature(
title = "Summer Times",
R.drawable.ic__search,
BlueViolet1,
BlueViolet1,
BlueViolet1
),
Feature(
title = "Winter Vibes",
R.drawable.ic__search,
LightGreen1,
BlueViolet1,
BlueViolet1
),
Feature(
title = "Spring Times",
R.drawable.ic__search,
LightRed,
BlueViolet1,
BlueViolet1
)
)
Box(
modifier = Modifier
.fillMaxWidth()
.background(DeepBlue)
) {
LazyColumn() {
item {
GreetingScreen("Android")
}
item {
ChipSection(list)
}
item {
CurrentMeditation()
}
item {
Text(
text = "Features",
color = Color.White,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(15.dp)
)
}
items(features.windowed(2, 2, true)) { list ->
Row(Modifier.fillMaxWidth()) {
list.forEach {
FeatureItem(it)
}
}
}
}
}
}
#Composable
fun GreetingScreen(name: String = "Android") {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Column(
verticalArrangement = Arrangement.Center
) {
Text(
text = "Good Morning , $name",
style = MaterialTheme.typography.h6,
color = Color.White
)
Text(
text = "We wish you a good day today",
style = MaterialTheme.typography.body1,
color = Color.White
)
}
Icon(
painter = painterResource(id = R.drawable.ic__search),
contentDescription = "Search",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
}
#Composable
fun ChipSection(
chips: List<String>
) {
var selectedChipIndex by remember {
mutableStateOf(0)
}
LazyRow {
items(chips.size) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(start = 15.dp, top = 15.dp, bottom = 15.dp)
.clickable {
selectedChipIndex = it
}
.clip(RoundedCornerShape(10.dp))
.background(
if (selectedChipIndex == it) ButtonBlue
else DarkerButtonBlue
)
.padding(15.dp)
) {
Text(text = chips[it], color = Color.White)
}
}
}
}
#Composable
fun CurrentMeditation(
color: Color = LightRed
) {
Box(
modifier = Modifier
.padding(15.dp)
.clip(RoundedCornerShape(15.dp))
.background(color)
.fillMaxWidth()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Column(verticalArrangement = Arrangement.Center) {
Text(
text = "Daily Thought",
color = TextWhite,
fontSize = 18.sp
)
Text(
text = "Meditation 3 - 10 mins",
color = TextWhite,
fontSize = 13.sp
)
}
Icon(
painter = painterResource(id = R.drawable.ic__play),
contentDescription = "Play",
tint = Color.White
)
}
}
}
#ExperimentalFoundationApi
#Composable
fun FeatureSection(features: List<Feature>) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Text(
text = "Features",
color = Color.White,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(15.dp)
)
}
items(features.windowed(2, 2, true)) { list ->
Row(Modifier.fillMaxWidth()) {
list.forEach {
FeatureItem(feature = it)
}
}
}
/*LazyVerticalGrid(
cells = GridCells.Fixed(2),
contentPadding = PaddingValues(start = 5.dp, top = 5.dp, bottom = 5.dp),
modifier = Modifier.fillMaxHeight()
) {
items(features.size) {
}
}*/
}
}
#Composable
fun FeatureItem(feature: Feature) {
Box(
modifier = Modifier
.padding(15.dp)
.fillMaxWidth(.3f)
.clip(RoundedCornerShape(15.dp))
.aspectRatio(1f)
.background(color = feature.lightColor)
) {
Text(
text = feature.title,
color = Color.White,
style = MaterialTheme.typography.h6,
lineHeight = 24.sp,
modifier = Modifier.align(Alignment.TopStart)
)
Icon(
painter = painterResource(id = feature.iconId),
contentDescription = feature.title,
tint = Color.White,
modifier = Modifier.align(Alignment.BottomStart)
)
Text(
text = "Start",
color = TextWhite,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.clickable {
}
.align(
Alignment.BottomEnd
)
.clip(RoundedCornerShape(10.dp))
.background(ButtonBlue)
.padding(vertical = 6.dp, horizontal = 15.dp)
)
}
}
When I try to achieve it in jetpack compose. The list scrolls under it's own layout but doesn't expand making the whole scrolling. Horizontal one is fine but the vertical one tends to have a small height not making it the sameheight as the list itself.
Needed a view something like this so that the view can expand as well as scroll with the layout.
#Composable
fun NewsList() {
LazyColumn {
items(rows) { item ->
Text(
modifier = Modifier
.height(80.dp),
text = item
)
}
}
something like this you can create fr lazycolum
for gride you can try this(Not sure for this) is it right or not
LazyVerticalGrid(
cells = GridCells.Fixed(cellState),
content = {
}
}
)
Don't forget to import this library explicitly
import androidx.compose.foundation.lazy.items
#Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}
Here is my component:
#Composable
fun Cover(
name: String,
imageRes: Int,
modifier: Modifier = Modifier.padding(16.dp, 8.dp)
) {
Box(modifier) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Stack {
Image(
imageResource(imageRes),
modifier = Modifier
.gravity(Alignment.Center)
.aspectRatio(2f),
contentScale = ContentScale.Crop,
)
Text(
text = name,
modifier = Modifier
.gravity(Alignment.BottomStart)
.padding(8.dp),
style = MaterialTheme.typography.h6
)
}
}
}
}
This is how it looks:
I want to display a dark gradient over the Image and behind the Text so that the text is easy to read. I guess I'll have to use LinearGradient or RadialGradient but due to the lack of documentation I'm not able to do it.
Edit: This is what I'm trying to do but with Jetpack Compose.
You can use something like:
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black),
startY = sizeImage.height.toFloat()/3, // 1/3
endY = sizeImage.height.toFloat()
)
Box(){
Image(painter = painterResource(id = R.drawable.banner),
contentDescription = "",
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
})
Box(modifier = Modifier.matchParentSize().background(gradient))
}
Original:
After:
You can also apply the gradient to the Image() using the .drawWithCache modifier and the onDrawWithContent that allows the developer to draw before or after the layout's contents.
Image(painter = painterResource(id = R.drawable.conero),
contentDescription = "",
modifier = Modifier.drawWithCache {
val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black),
startY = size.height/3,
endY = size.height
)
onDrawWithContent {
drawContent()
drawRect(gradient,blendMode = BlendMode.Multiply)
}
}
)
Wow, that one took a couple of hours ;)
You can use Modifier.background with a VerticalGradient. I used a Column to hold the modifiers and made a calculation to get the images size, but your solution might differ, you could calculate or store the size differently, and put the modifiers somewhere else. I left two TODOs in the code so you can tweak the gradient.
#Composable
fun Cover(
name: String,
imageRes: Int,
modifier: Modifier = Modifier.padding(16.dp, 8.dp)
) {
val density = DensityAmbient.current.density
val width = remember { mutableStateOf(0f) }
val height = remember { mutableStateOf(0f) }
Box(modifier) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Stack {
Image(
imageResource(imageRes),
modifier = Modifier
.gravity(Alignment.Center)
.aspectRatio(2f)
.onPositioned {
width.value = it.size.width / density
height.value = it.size.height / density
},
contentScale = ContentScale.Crop,
)
Column(
Modifier.size(width.value.dp, height.value.dp)
.background(
VerticalGradient(
listOf(Color.Transparent, Color.Black),
0f, // TODO: set start
500f, // TODO: set end
)
)
) {}
Text(
text = name,
modifier = Modifier.gravity(Alignment.BottomStart)
.padding(8.dp),
style = typography.h6,
)
}
}
}
}
This is how my sample looks like:
Compose version 1.2.1 as of 2022-08-22
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
#Composable
fun Cover(
name: String,
#DrawableRes imageRes: Int,
modifier: Modifier = Modifier
) {
Box(modifier.padding(16.dp, 8.dp)) {
Card(shape = RoundedCornerShape(4.dp)) {
Box {
Image(
painter = painterResource(imageRes),
contentDescription = "image: $name",
modifier = Modifier
.align(Alignment.Center),
contentScale = ContentScale.Crop
)
Text(
text = name,
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.background(
Brush.verticalGradient(
0F to Color.Transparent,
.5F to Color.Black.copy(alpha = 0.5F),
1F to Color.Black.copy(alpha = 0.8F)
)
)
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp, top = 24.dp),
color = Color.White
)
}
}
}
}
#Preview
#Composable
fun ComicsPreview() {
Cover(
"Comics",
R.drawable.comics
)
}
The updated answer of #vitor-ramos
1.0.0-alpha09
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.dp
import tech.abd3lraouf.learn.compose.kombose.ui.theme.typography
#Composable
fun Cover(
name: String,
#DrawableRes imageRes: Int,
modifier: Modifier = Modifier
) {
val image = imageResource(imageRes)
val density = AmbientDensity.current.density
val width = remember { mutableStateOf(0f) }
val height = remember { mutableStateOf(0f) }
Box(
modifier
.padding(16.dp, 8.dp)
) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Box {
Image(
image,
modifier = Modifier
.align(Alignment.Center)
.aspectRatio(2f)
.onGloballyPositioned {
width.value = it.size.width / density
height.value = it.size.height / density
},
contentScale = ContentScale.Crop,
)
Column(
Modifier
.size(width.value.dp, height.value.dp)
.background(
Brush.verticalGradient(
listOf(Color.Transparent, Color.Black),
image.height * 0.6F,
image.height * 1F
)
)
) {}
Text(
text = name,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(8.dp),
style = typography.body2,
color = Color.White
)
}
}
}
}
Also, notice the control of how the gradient draws its height.
The output
You can try this approach as well
Image(
painterResource(R.drawable.something),
null,
Modifier
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(Brush.verticalGradient(
0.5f to Color.White.copy(alpha=0F),
1F to Color.White
))
}
},
)
Straight forward:
Card(shape = RoundedCornerShape(8.dp)) {
Box {
Image(...)
Text(
text = "title",
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.background(Brush.verticalGradient(0F to Color.Transparent, .5F to Color.Red, 1F to Color.Red))
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp, top = 16.dp),
color = Color.White,
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Start
)
}
}