I have a Column component (SettingsGraphicSelectComposeView.kt) which needs to have a weight of 1f (modifier.weight(weight = 1f)) so that multiple of this component in a container, would be distributed evenly. Problem is that when upgrading Compose from alpha02 to alpha06, assigning the above modifier to a Column is no longer a possibility.
Here is the components code:
#Composable
fun SettingsGraphicSelectComposeView(
modifier: Modifier = Modifier,
textStyles: TextStyles = KoinJavaComponent.inject(TextStyles::class.java).value,
viewModel: ItemViewModel.GraphicSelect
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
modifier = modifier.weight(weight = 1f) // <-- in alpha06 this gives an error
) {
Image(
asset = vectorResource(id = viewModel.imageId)
)
Text(
modifier = modifier
.padding(
start = dimensionResource(id = R.dimen.spacingL),
top = dimensionResource(id = R.dimen.spacingL),
end = dimensionResource(id = R.dimen.spacingL),
bottom = dimensionResource(id = R.dimen.spacingS)
),
text = viewModel.caption,
style = textStyles.TitleSmall.merge(TextStyle(color = colorResource(id = R.color.tint_secondary)))
)
RadioButton(
selected = viewModel.selected,
onClick = viewModel.action,
colors = RadioButtonConstants.defaultColors(
selectedColor = colorResource(R.color.brand),
unselectedColor = colorResource(R.color.tint_secondary)
)
)
}
}
This component is placed in the following compose view:
Surface(color = colorResource(id = R.color.background)) {
Column(
modifier = Modifier
.fillMaxHeight()
) {
items.value?.let { items ->
val switchers = items.filter { it.viewModel is ItemViewModel.Switcher }
val graphicSelects = items.filter { it.viewModel is ItemViewModel.GraphicSelect }
if (switchers.isNotEmpty()) {
val autoSwitcher = switchers[0]
SettingsSwitcherComposeView(viewModel = autoSwitcher.viewModel as ItemViewModel.Switcher)
}
Row(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
) {
if (graphicSelects.size > 1) {
val lightSelector = graphicSelects[0]
val darkSelector = graphicSelects[1]
Row(
modifier = Modifier
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.padding(vertical = dimensionResource(id = R.dimen.spacing2XL))
) {
Row {
SettingsGraphicSelectComposeView(
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
}
}
}
}
}
}
}
Should look like this:
But without the weight it looks like this:
It seems like the weight modifier can only attributed to Column if this column is a child of a Row, so a team member suggested replacing the Row elements containing the SettingsGraphicSelectComposeView with Column and it made the weight attribute "legal" if you will, because we're in a RowScope, and that works.
Not sure what to think about that but it works and as Compose is still at alpha07 right now (new version since I made the post), things might change again in the future.
Here is the change:
Row(
modifier = modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.fillMaxWidth()
) {
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.light.viewModel as ItemViewModel.GraphicSelect
)
}
Spacer(
modifier = Modifier
.padding(top = dimensionResource(id = R.dimen.spacing2XL))
.preferredWidth(1.dp)
.preferredHeight(160.dp) // TODO: Find a way to make this max AUTO height, not fixed
.background(color = colorResource(R.color.highlight))
)
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.dark.viewModel as ItemViewModel.GraphicSelect
)
}
}
Try using Modifier.weight(1f) in your Row itself, so it takes Modifier.weight() from RowScope:
Row {
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
)
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
)
}
Here is the solution : Modifier --> "Arrangement.SpaceEvenly"
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
)
If you wants to give bigger space to any specific Row item, you can handle it dynamically by using your own condition :
For eg :
modifier=Modifier.then(Modifier.weight(if(myItem == index) 2f else 1f))
**'OR'**
modifier=Modifier.then(Modifier.weight(if(myItem .title=="GOLF") 1.5f else 1f))
Related
Would you like to help me, i have no idea what i am doing. I want to make a dashboard and there are banner inside it. so i decide to make banner and get data from api to fill it in banner. but i want to make it scroll automaticly
Dashboard screen
#OptIn(ExperimentalPagerApi::class, FlowPreview::class)
#Composable
fun DashboardScreen(
navController: NavHostController = rememberAnimatedNavController(),
datastore: DataStoreRepository,
dashboardViewModel: DashboardViewModel = hiltViewModel(),
) {
val state by dashboardViewModel.dashboardState.collectAsState()
val username = datastore.getUsername().collectAsState(initial = "")
val company = datastore.getUserCompany().collectAsState(initial = "")
val pagerState = rememberPagerState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp, vertical = 20.dp)
.padding(12.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
TopSectionDashboard(name = username.value, company = company.value)
Spacer(modifier = Modifier.height(20.dp))
Box {
state.dashboard?.let {
BannerSlider(
state = pagerState,
listBanner = it.banner
)
Indicators(
size = it.banner.size,
index = pagerState.currentPage,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
if (state.isLoading) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator()
}
}
}
}
BannerSlider
#OptIn(ExperimentalPagerApi::class)
#Composable
fun BannerSlider(
modifier: Modifier = Modifier,
state: PagerState,
listBanner: List<Banner>,
) {
HorizontalPager(
count = listBanner.size,
state = state
) {
BannerItem(
item = Banner(
count = listBanner[it].count,
text = listBanner[it].text,
icon = listBanner[it].icon
),
)
}
}
Banner Item
#Composable
fun BannerItem(item: Banner, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(200.dp)
) {
Image(
painter = painterResource(id = R.drawable.background_dashboard_slider1),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.align(Alignment.CenterStart)
.offset(y = (-20).dp)
) {
Column(horizontalAlignment = Alignment.Start) {
Text(
text = item.text,
fontFamily = Poppins,
fontSize = 20.sp,
color = Color.White,
modifier = Modifier.padding(horizontal = 24.dp)
)
Text(
text = "${item.count}",
fontFamily = Poppins,
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier
.padding(horizontal = 24.dp)
.offset(y = (-10).dp)
)
}
AsyncImage(
model = item.icon,
contentDescription = "$item.title",
modifier = Modifier.size(120.dp),
alignment = Alignment.CenterEnd
)
}
}
}
Please help me, i have no idea. i really appreciate it to someone who give a hand to me
in a simple way, You can make it possible by
LaunchedEffect(pagerState.currentPage) {
launch {
delay(2000L)
with(pagerState) {
val nextPage = if (currentPage < pageCount - 1) currentPage + 1 else 0
animateScrollToPage(nextPage)
}
}
}
Keep it in my mind to pass count to HorizontalPager as well
i´m new in Compose and i´m trying to manage the visibility of the TopBar when i´m scrolling a list ( LazyColumn ). I´m not pretend to use the Scaffold with Material 3 because I want to learn a bit more about Compose and Animation.
So, first of all, this is my code and it works just fine ->
#Composable
fun FavouriteCompose(stateUi: FavouriteStateUi.ShowMovies) {
Box(Modifier.fillMaxSize()) {
val state = rememberLazyListState()
val firstVisible = remember { derivedStateOf { state.firstVisibleItemIndex } }
Column(
Modifier
.fillMaxSize()
.background(color = GreenB)
) {
AnimatedVisibility(visible = firstVisible.value == 0) {
Spacer(modifier = Modifier.height(20.dp))
Paragraphs.Paragraph(
modifier = Modifier.padding(10.dp),
stringRes = R.string.favourite,
color = Color.White,
paragraphSize = Paragraphs.ParagraphSize.PARAGRAPH_24_SP
)
Spacer(modifier = Modifier.height(20.dp))
}
if (stateUi.movies?.isEmpty() == true) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = 10.dp)
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.White),
) {
Image(
alignment = Alignment.Center,
modifier = Modifier.size(200.dp),
painter = painterResource(
id = R.drawable.ic_baseline_local_movies_24
),
contentDescription = ""
)
Paragraphs.Paragraph(
modifier = Modifier
.padding(top = 10.dp)
.align(Alignment.CenterHorizontally),
stringRes = R.string.add_more_movies,
paragraphSize = Paragraphs.ParagraphSize.PARAGRAPH_24_SP
)
}
} else {
LazyColumn(
state = state,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.White)
.padding(10.dp)
) {
items(
items = stateUi.movies.orEmpty(),
key = { cardModel -> cardModel.id }
) { item -> MovieCard(item) }
}
}
}
val snackBarModel = stateUi.snackBarModel
if (snackBarModel.addOrRemoveMovie == true) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(bottom = 10.dp)
) {
SnackBar(
stringRes = R.string.pelicula_removida,
icon = R.drawable.ic_baseline_local_movies_24,
backgroundColor = Color.Black,
textColor = LightGrey,
iconColor = Blue,
snackBarModel = snackBarModel
)
}
}
}
}
Each Time i swipe the list the topApp hides and viceversa, but i´m not sure if this is the best way to do this ( without using Scaffold with Material 3 ).
I´m creating more recompositons with this kind of solution ?, should I save the lazy state in a viewModel instead?
Thanks!.
I am making a "ToggleGroup" with Jetpack-Compose, using essentially a Row into which I print Text. I manage to make it work if I tune the width manually (.width(70.dp) in the code below), but I would like it to automatically do that.
I essentially want this:
But without manually adding .width(70.dp), I get this:
My current (tuned) code is the following:
Row(
horizontalArrangement = Arrangement.End,
) {
options.forEach { option ->
val isSelected = option == selectedOption
val textColor = if (isSelected) Color.White else MaterialTheme.colors.primary
val backgroundColor = if (isSelected) Color.Gray else Color.White
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(
vertical = 6.dp, horizontal = 1.dp
)
.width(70.dp)
.background(backgroundColor)
.clickable { onSelectionChanged(option) }
) {
Text(
text = option,
color = textColor,
modifier = Modifier.padding(14.dp),
)
}
}
}
It feels similar to this question, but somehow it's different because I use Row and the question uses Column (or at least I did not manage to use Intrinsics correctly).
How could I do that?
Changes required.
1. On the parent Row, use Modifier.width(IntrinsicSize.Min)
(min|max)IntrinsicWidth: Given this height, what's the minimum/maximum width you can paint your content properly?
Source - Docs
2. Use Modifier.weight(1F) on all children.
Size the element's width proportional to its weight relative to other weighted sibling elements in the Row.
The parent will divide the horizontal space remaining after measuring unweighted child elements and distribute it according to this weight.
Source - Docs
3. Use Modifier.width(IntrinsicSize.Max) on all children.
This ensures the Text inside the children composables are not wrapped.
(You can verify this by removing the modifier and adding long text)
Screenshot
Sample code
#Composable
fun AutoWidthRow() {
val items = listOf("Item 1", "Item 2", "Item 300")
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.width(IntrinsicSize.Min),
) {
items.forEach { option ->
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(
vertical = 6.dp, horizontal = 1.dp
)
.width(IntrinsicSize.Max) // Removing this will wrap the text
.weight(1F)
.background(Color.Black)
) {
Text(
text = option,
color = Color.White,
modifier = Modifier
.padding(14.dp),
)
}
}
}
}
You should create a custom Layout
#Composable
fun EqualSizeTiles(
modifier: Modifier = Modifier,
content: #Composable () -> Unit,
) {
Layout(
content = content,
modifier = modifier,
) { measurables, constraints ->
layoutTiles(
measurables,
constraints
)
}
}
private fun MeasureScope.layoutTiles(
measurables: List<Measurable>,
constraints: Constraints,
): MeasureResult {
val tileHeight = constraints.maxHeight
val tileWidths = measurables.map { measurable ->
measurable.maxIntrinsicWidth(tileHeight)
}
val tileWidth = tileWidths.maxOrNull() ?: 0
val tileConstraints = Constraints(
minWidth = tileWidth,
minHeight = 0,
maxWidth = tileWidth,
maxHeight = constraints.maxHeight,
)
val placeables = measurables.map { measurable ->
measurable.measure(tileConstraints)
}
val width = (placeables.size * tileWidth).coerceAtMost(constraints.maxWidth)
return layout(width = width, height = tileHeight) {
placeables.forEachIndexed { index, placeable ->
placeable.place(tileWidth * index, 0)
}
}
}
#Preview(showBackground = true, widthDp = 512)
#Composable
private fun EqualSizeTilesPreview() {
WeatherSampleTheme {
Surface(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.Yellow)
) {
EqualSizeTiles(
modifier = Modifier
.height(64.dp)
.background(color = Color.Green)
.padding(all = 8.dp)
) {
Text(
text = "Left",
textAlign = TextAlign.Center,
modifier = Modifier
.background(color = Color.Red)
.padding(all = 8.dp)
.fillMaxHeight(),
)
Text(
text = "Center",
textAlign = TextAlign.Center,
modifier = Modifier
.background(color = Color.Yellow)
.padding(all = 8.dp)
.fillMaxHeight(),
)
Text(
text = "Right element",
textAlign = TextAlign.Center,
modifier = Modifier
.background(color = Color.Blue)
.padding(all = 8.dp)
.fillMaxHeight(),
)
}
}
}
}
I am new in jetpack compose, and I am try to learn it, so I have simple onboarding screen in jetpack compose, when I update project to jetpack compose 1.1.1 , "Next" button not working for onboarding screen to scroll horizontally. it was working when I use jetpack compose 1.0.0 version, I do not know what change in new version, any idea?
#ExperimentalPagerApi
#Composable
fun OnBoardScreen() {
val scaffoldState = rememberScaffoldState()
val onBoardViewModel : OnBoardViewModel = viewModel()
val context = LocalContext.current
val currentPage = onBoardViewModel.currentPage.collectAsState()
Toast.makeText(context, "${currentPage.value}", Toast.LENGTH_SHORT).show()
val pagerState = rememberPagerState(
pageCount = onBoardItem.size,
initialOffscreenLimit = 2,
initialPage = 0,
infiniteLoop = false
)
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Surface(
modifier = Modifier.fillMaxSize()
) {
LaunchedEffect(scaffoldState.snackbarHostState){
pagerState.animateScrollToPage(
page = currentPage.value
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(Gray200)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
HorizontalPager(
state = pagerState
) { page ->
Column(
modifier = Modifier
.padding(top = 65.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = onBoardItem[page].image),
contentDescription = "OnBoardImage",
modifier = Modifier
.size(250.dp)
)
Text(
text = onBoardItem[page].title,
modifier = Modifier
.padding(top = 50.dp),
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
Text(
text = onBoardItem[page].desc,
modifier = Modifier
.padding(30.dp),
color = Color.White,
fontSize = 18.sp,
textAlign = TextAlign.Center
)
}
}
PagerIndicator(onBoardItem.size, pagerState.currentPage)
}
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
) {
Row(
modifier = Modifier
.padding(bottom = 20.dp)
.fillMaxWidth(),
horizontalArrangement = if (pagerState.currentPage != 2 ) {
Arrangement.SpaceBetween
} else {
Arrangement.Center
}
) {
if (pagerState.currentPage == 2) {
OutlinedButton(
onClick = {
Toast.makeText(context, "Start the Screen", Toast.LENGTH_SHORT).show()
},
shape = RoundedCornerShape(45.dp)
) {
Text(
text = "Get Started",
modifier = Modifier.padding(
vertical = 8.dp,
horizontal = 40.dp
),
color = Color.Black
)
}
} else {
Text(
text = "Skip",
color = Color.White,
modifier = Modifier.padding(start = 20.dp),
fontSize = 18.sp,
fontWeight = FontWeight.Medium
)
Text(
text = "Next",
color = Color.White,
modifier = Modifier
.clickable {
onBoardViewModel.setCurrentPage(pagerState.currentPage + 1)
}
.padding(end = 20.dp),
fontSize = 18.sp,
fontWeight = FontWeight.Medium
)
}
}
}
}
}
}
}
It looks like you expect these lines to scroll the pager every time you change the value of currentPage:
LaunchedEffect(scaffoldState.snackbarHostState) {
pagerState.animateScrollToPage(
page = currentPage.value
)
}
But scaffoldState.snackbarHostState in this scope is a static value, which means LaunchedEffect is not gonna be relaunched.
One option is to pass currentPage instead, but also, as your currentPage is backed by flow, it's cleaner to collect it:
LaunchedEffect(Unit) {
onBoardViewModel.currentPage
.collect {
pagerState.animateScrollToPage(
page = currentPage.value
)
}
}
p.s.
Also, when you need to make a text button, instead of adding clickable to the Text you can use TextButton:
TextButton(
onClick = {
}
) {
Text(/*...*/)
}
How do I create a rounded checkbox in Jetpackcompose like this. I tried using a Shape composable on it but it doesn't work.
I was looking on how to do the same thing you were asking, your question helped me on my journey so it is only fair I share. Add some animations and you are set my friend.
Make a round looking icon by using a box and an icon
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
2.Place the newly made rounded icon and some text next to each other by using a Row
Row(
verticalAlignment = Alignment.CenterVertically,
){
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
3.Replace whatever you want with variables so you can customize
it
#Composable
fun RoundedCheckView(
) {
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
{
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
4.Finally add Modifier.toggleable to the row, basically making it a clickable item that toggles (between true and false) a variable in this case isChecked. Then just customize the variables according to what you need
#Composable
fun RoundedCheckView()
{
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.toggleable(value = isChecked.value,role = Role.Checkbox) {
isChecked.value = it
if (isChecked.value) {
checkedText.value = "Checked"
circleSize.value = 40.dp
circleThickness.value = 3.dp
color.value = Color.Black
} else {
checkedText.value = "unChecked"
circleSize.value = 20.dp
circleThickness.value = 2.dp
color.value = Color.Gray
}
}) {
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
if(isChecked.value){
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
This is how we can make a custom check box in jetpack compose
val isCheck = remember { mutableStateOf(false) }
Row {
Card(
modifier = Modifier.background(Color.White),
elevation = 0.dp,
shape = RoundedCornerShape(6.dp),
border = BorderStroke(1.5.dp, color = titleColor)
) {
Box(
modifier = Modifier
.size(25.dp)
.background(if (isCheck.value) titleColor else Color.White)
.clickable {
isCheck.value = !isCheck.value
},
contentAlignment = Center
) {
if(isCheck.value)
Icon(Icons.Default.Check, contentDescription = "", tint = Color.White)
}
}
Text(
modifier = Modifier
.align(CenterVertically)
.padding(start = 10.dp),
text = "I agree with the terms & condition",
)
}
You can try to make it use Box with modifier content alignment center. and put an icon on there.
#Preview
#Composable
fun Check() {
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.background(Color.Red)
.padding(5.dp)
.clip(CircleShape)
.background(Color.Blue),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
We can use animations to make it behave similar to the default Checkbox. Using Modifier.toggleable on the top-level Row, the entire thing is clickable, including the label. This also creates the proper semantics for screen reader users. You can change the shape of the card to get a circular checkbox.
#Composable
fun PrimaryCheckbox(
label: String,
modifier: Modifier = Modifier,
size: Float = 24f,
checkedColor: Color = DarkGray,
uncheckedColor: Color = White,
checkmarkColor: Color = White,
onValueChange: () -> Unit
) {
var isChecked by remember { mutableStateOf(false) }
val checkboxColor: Color by animateColorAsState(if (isChecked) checkedColor else uncheckedColor)
val density = LocalDensity.current
val duration = 200
Row(
modifier = modifier
.toggleable(
value = isChecked,
role = Role.Checkbox,
onValueChange = {
isChecked = !isChecked
onValueChange.invoke()
}
)
) {
Card(
elevation = 0.dp,
shape = RoundedCornerShape(4.dp),
border = BorderStroke(1.5.dp, color = checkedColor),
) {
Box(
modifier = Modifier
.size(size.dp)
.background(checkboxColor),
contentAlignment = Alignment.Center
) {
androidx.compose.animation.AnimatedVisibility(
visible = isChecked,
enter = slideInHorizontally(
animationSpec = tween(duration)
) {
with(density) { (size * -0.5).dp.roundToPx() }
} + expandHorizontally(
expandFrom = Alignment.Start,
animationSpec = tween(duration)
),
exit = fadeOut()
) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = checkmarkColor
)
}
}
}
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp),
text = label,
)
}
}