1 /*
<lambda>null2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.material3
18
19 import androidx.compose.animation.core.Animatable
20 import androidx.compose.animation.core.VectorConverter
21 import androidx.compose.foundation.BorderStroke
22 import androidx.compose.foundation.interaction.DragInteraction
23 import androidx.compose.foundation.interaction.FocusInteraction
24 import androidx.compose.foundation.interaction.HoverInteraction
25 import androidx.compose.foundation.interaction.Interaction
26 import androidx.compose.foundation.interaction.InteractionSource
27 import androidx.compose.foundation.interaction.MutableInteractionSource
28 import androidx.compose.foundation.interaction.PressInteraction
29 import androidx.compose.foundation.layout.Column
30 import androidx.compose.foundation.layout.ColumnScope
31 import androidx.compose.material3.internal.animateElevation
32 import androidx.compose.material3.tokens.ElevatedCardTokens
33 import androidx.compose.material3.tokens.FilledCardTokens
34 import androidx.compose.material3.tokens.OutlinedCardTokens
35 import androidx.compose.runtime.Composable
36 import androidx.compose.runtime.Immutable
37 import androidx.compose.runtime.LaunchedEffect
38 import androidx.compose.runtime.Stable
39 import androidx.compose.runtime.State
40 import androidx.compose.runtime.mutableStateListOf
41 import androidx.compose.runtime.mutableStateOf
42 import androidx.compose.runtime.remember
43 import androidx.compose.ui.Modifier
44 import androidx.compose.ui.geometry.Offset
45 import androidx.compose.ui.graphics.Color
46 import androidx.compose.ui.graphics.Shape
47 import androidx.compose.ui.graphics.compositeOver
48 import androidx.compose.ui.graphics.takeOrElse
49 import androidx.compose.ui.unit.Dp
50
51 /**
52 * [Material Design filled card](https://m3.material.io/components/cards/overview)
53 *
54 * Cards contain contain content and actions that relate information about a subject. Filled cards
55 * provide subtle separation from the background. This has less emphasis than elevated or outlined
56 * cards.
57 *
58 * This Card does not handle input events - see the other Card overloads if you want a clickable or
59 * selectable Card.
60 *
61 * 
63 *
64 * Card sample:
65 *
66 * @sample androidx.compose.material3.samples.CardSample
67 * @param modifier the [Modifier] to be applied to this card
68 * @param shape defines the shape of this card's container, border (when [border] is not null), and
69 * shadow (when using [elevation])
70 * @param colors [CardColors] that will be used to resolve the colors used for this card in
71 * different states. See [CardDefaults.cardColors].
72 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
73 * This controls the size of the shadow below the card. Additionally, when the container color is
74 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
75 * also: [Surface].
76 * @param border the border to draw around the container of this card
77 * @param content The content displayed on the card
78 */
79 @Composable
80 fun Card(
81 modifier: Modifier = Modifier,
82 shape: Shape = CardDefaults.shape,
83 colors: CardColors = CardDefaults.cardColors(),
84 elevation: CardElevation = CardDefaults.cardElevation(),
85 border: BorderStroke? = null,
86 content: @Composable ColumnScope.() -> Unit
87 ) {
88 Surface(
89 modifier = modifier,
90 shape = shape,
91 color = colors.containerColor(enabled = true),
92 contentColor = colors.contentColor(enabled = true),
93 shadowElevation = elevation.shadowElevation(enabled = true, interactionSource = null).value,
94 border = border,
95 ) {
96 Column(content = content)
97 }
98 }
99
100 /**
101 * [Material Design filled card](https://m3.material.io/components/cards/overview)
102 *
103 * Cards contain contain content and actions that relate information about a subject. Filled cards
104 * provide subtle separation from the background. This has less emphasis than elevated or outlined
105 * cards.
106 *
107 * This Card handles click events, calling its [onClick] lambda.
108 *
109 * 
111 *
112 * Clickable card sample:
113 *
114 * @sample androidx.compose.material3.samples.ClickableCardSample
115 * @param onClick called when this card is clicked
116 * @param modifier the [Modifier] to be applied to this card
117 * @param enabled controls the enabled state of this card. When `false`, this component will not
118 * respond to user input, and it will appear visually disabled and disabled to accessibility
119 * services.
120 * @param shape defines the shape of this card's container, border (when [border] is not null), and
121 * shadow (when using [elevation])
122 * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
123 * different states. See [CardDefaults.cardColors].
124 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
125 * This controls the size of the shadow below the card. Additionally, when the container color is
126 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
127 * also: [Surface].
128 * @param border the border to draw around the container of this card
129 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
130 * emitting [Interaction]s for this card. You can use this to change the card's appearance or
131 * preview the card in different states. Note that if `null` is provided, interactions will still
132 * happen internally.
133 * @param content The content displayed on the card
134 */
135 @Composable
Cardnull136 fun Card(
137 onClick: () -> Unit,
138 modifier: Modifier = Modifier,
139 enabled: Boolean = true,
140 shape: Shape = CardDefaults.shape,
141 colors: CardColors = CardDefaults.cardColors(),
142 elevation: CardElevation = CardDefaults.cardElevation(),
143 border: BorderStroke? = null,
144 interactionSource: MutableInteractionSource? = null,
145 content: @Composable ColumnScope.() -> Unit
146 ) {
147 @Suppress("NAME_SHADOWING")
148 val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
149 Surface(
150 onClick = onClick,
151 modifier = modifier,
152 enabled = enabled,
153 shape = shape,
154 color = colors.containerColor(enabled),
155 contentColor = colors.contentColor(enabled),
156 shadowElevation = elevation.shadowElevation(enabled, interactionSource).value,
157 border = border,
158 interactionSource = interactionSource,
159 ) {
160 Column(content = content)
161 }
162 }
163
164 /**
165 * [Material Design elevated card](https://m3.material.io/components/cards/overview)
166 *
167 * Elevated cards contain content and actions that relate information about a subject. They have a
168 * drop shadow, providing more separation from the background than filled cards, but less than
169 * outlined cards.
170 *
171 * This ElevatedCard does not handle input events - see the other ElevatedCard overloads if you want
172 * a clickable or selectable ElevatedCard.
173 *
174 * 
176 *
177 * Elevated card sample:
178 *
179 * @sample androidx.compose.material3.samples.ElevatedCardSample
180 * @param modifier the [Modifier] to be applied to this card
181 * @param shape defines the shape of this card's container and shadow (when using [elevation])
182 * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
183 * different states. See [CardDefaults.elevatedCardElevation].
184 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
185 * This controls the size of the shadow below the card. Additionally, when the container color is
186 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
187 * also: [Surface].
188 * @param content The content displayed on the card
189 */
190 @Composable
ElevatedCardnull191 fun ElevatedCard(
192 modifier: Modifier = Modifier,
193 shape: Shape = CardDefaults.elevatedShape,
194 colors: CardColors = CardDefaults.elevatedCardColors(),
195 elevation: CardElevation = CardDefaults.elevatedCardElevation(),
196 content: @Composable ColumnScope.() -> Unit
197 ) =
198 Card(
199 modifier = modifier,
200 shape = shape,
201 border = null,
202 elevation = elevation,
203 colors = colors,
204 content = content
205 )
206
207 /**
208 * [Material Design elevated card](https://m3.material.io/components/cards/overview)
209 *
210 * Elevated cards contain content and actions that relate information about a subject. They have a
211 * drop shadow, providing more separation from the background than filled cards, but less than
212 * outlined cards.
213 *
214 * This ElevatedCard handles click events, calling its [onClick] lambda.
215 *
216 * 
218 *
219 * Clickable elevated card sample:
220 *
221 * @sample androidx.compose.material3.samples.ClickableElevatedCardSample
222 * @param onClick called when this card is clicked
223 * @param modifier the [Modifier] to be applied to this card
224 * @param enabled controls the enabled state of this card. When `false`, this component will not
225 * respond to user input, and it will appear visually disabled and disabled to accessibility
226 * services.
227 * @param shape defines the shape of this card's container and shadow (when using [elevation])
228 * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
229 * different states. See [CardDefaults.elevatedCardElevation].
230 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
231 * This controls the size of the shadow below the card. Additionally, when the container color is
232 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
233 * also: [Surface].
234 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
235 * emitting [Interaction]s for this card. You can use this to change the card's appearance or
236 * preview the card in different states. Note that if `null` is provided, interactions will still
237 * happen internally.
238 * @param content The content displayed on the card
239 */
240 @Composable
241 fun ElevatedCard(
242 onClick: () -> Unit,
243 modifier: Modifier = Modifier,
244 enabled: Boolean = true,
245 shape: Shape = CardDefaults.elevatedShape,
246 colors: CardColors = CardDefaults.elevatedCardColors(),
247 elevation: CardElevation = CardDefaults.elevatedCardElevation(),
248 interactionSource: MutableInteractionSource? = null,
249 content: @Composable ColumnScope.() -> Unit
250 ) =
251 Card(
252 onClick = onClick,
253 modifier = modifier,
254 enabled = enabled,
255 shape = shape,
256 colors = colors,
257 elevation = elevation,
258 border = null,
259 interactionSource = interactionSource,
260 content = content
261 )
262
263 /**
264 * [Material Design outlined card](https://m3.material.io/components/cards/overview)
265 *
266 * Outlined cards contain content and actions that relate information about a subject. They have a
267 * visual boundary around the container. This can provide greater emphasis than the other types.
268 *
269 * This OutlinedCard does not handle input events - see the other OutlinedCard overloads if you want
270 * a clickable or selectable OutlinedCard.
271 *
272 * 
274 *
275 * Outlined card sample:
276 *
277 * @sample androidx.compose.material3.samples.OutlinedCardSample
278 * @param modifier the [Modifier] to be applied to this card
279 * @param shape defines the shape of this card's container, border (when [border] is not null), and
280 * shadow (when using [elevation])
281 * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
282 * different states. See [CardDefaults.outlinedCardColors].
283 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
284 * This controls the size of the shadow below the card. Additionally, when the container color is
285 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
286 * also: [Surface].
287 * @param border the border to draw around the container of this card
288 * @param content The content displayed on the card
289 */
290 @Composable
291 fun OutlinedCard(
292 modifier: Modifier = Modifier,
293 shape: Shape = CardDefaults.outlinedShape,
294 colors: CardColors = CardDefaults.outlinedCardColors(),
295 elevation: CardElevation = CardDefaults.outlinedCardElevation(),
296 border: BorderStroke = CardDefaults.outlinedCardBorder(),
297 content: @Composable ColumnScope.() -> Unit
298 ) =
299 Card(
300 modifier = modifier,
301 shape = shape,
302 colors = colors,
303 elevation = elevation,
304 border = border,
305 content = content
306 )
307
308 /**
309 * [Material Design outlined card](https://m3.material.io/components/cards/overview)
310 *
311 * Outlined cards contain content and actions that relate information about a subject. They have a
312 * visual boundary around the container. This can provide greater emphasis than the other types.
313 *
314 * This OutlinedCard handles click events, calling its [onClick] lambda.
315 *
316 * 
318 *
319 * Clickable outlined card sample:
320 *
321 * @sample androidx.compose.material3.samples.ClickableOutlinedCardSample
322 * @param onClick called when this card is clicked
323 * @param modifier the [Modifier] to be applied to this card
324 * @param enabled controls the enabled state of this card. When `false`, this component will not
325 * respond to user input, and it will appear visually disabled and disabled to accessibility
326 * services.
327 * @param shape defines the shape of this card's container, border (when [border] is not null), and
328 * shadow (when using [elevation])
329 * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
330 * different states. See [CardDefaults.outlinedCardColors].
331 * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
332 * This controls the size of the shadow below the card. Additionally, when the container color is
333 * [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
334 * also: [Surface].
335 * @param border the border to draw around the container of this card
336 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
337 * emitting [Interaction]s for this card. You can use this to change the card's appearance or
338 * preview the card in different states. Note that if `null` is provided, interactions will still
339 * happen internally.
340 * @param content The content displayed on the card
341 */
342 @Composable
343 fun OutlinedCard(
344 onClick: () -> Unit,
345 modifier: Modifier = Modifier,
346 enabled: Boolean = true,
347 shape: Shape = CardDefaults.outlinedShape,
348 colors: CardColors = CardDefaults.outlinedCardColors(),
349 elevation: CardElevation = CardDefaults.outlinedCardElevation(),
350 border: BorderStroke = CardDefaults.outlinedCardBorder(enabled),
351 interactionSource: MutableInteractionSource? = null,
352 content: @Composable ColumnScope.() -> Unit
353 ) =
354 Card(
355 onClick = onClick,
356 modifier = modifier,
357 enabled = enabled,
358 shape = shape,
359 colors = colors,
360 elevation = elevation,
361 border = border,
362 interactionSource = interactionSource,
363 content = content
364 )
365
366 /** Contains the default values used by all card types. */
367 object CardDefaults {
368 // shape Defaults
369 /** Default shape for a card. */
370 val shape: Shape
371 @Composable get() = FilledCardTokens.ContainerShape.value
372
373 /** Default shape for an elevated card. */
374 val elevatedShape: Shape
375 @Composable get() = ElevatedCardTokens.ContainerShape.value
376
377 /** Default shape for an outlined card. */
378 val outlinedShape: Shape
379 @Composable get() = OutlinedCardTokens.ContainerShape.value
380
381 /**
382 * Creates a [CardElevation] that will animate between the provided values according to the
383 * Material specification for a [Card].
384 *
385 * @param defaultElevation the elevation used when the [Card] is has no other [Interaction]s.
386 * @param pressedElevation the elevation used when the [Card] is pressed.
387 * @param focusedElevation the elevation used when the [Card] is focused.
388 * @param hoveredElevation the elevation used when the [Card] is hovered.
389 * @param draggedElevation the elevation used when the [Card] is dragged.
390 * @param disabledElevation the elevation used when the [Card] is disabled.
391 */
392 @Composable
393 fun cardElevation(
394 defaultElevation: Dp = FilledCardTokens.ContainerElevation,
395 pressedElevation: Dp = FilledCardTokens.PressedContainerElevation,
396 focusedElevation: Dp = FilledCardTokens.FocusContainerElevation,
397 hoveredElevation: Dp = FilledCardTokens.HoverContainerElevation,
398 draggedElevation: Dp = FilledCardTokens.DraggedContainerElevation,
399 disabledElevation: Dp = FilledCardTokens.DisabledContainerElevation
400 ): CardElevation =
401 CardElevation(
402 defaultElevation = defaultElevation,
403 pressedElevation = pressedElevation,
404 focusedElevation = focusedElevation,
405 hoveredElevation = hoveredElevation,
406 draggedElevation = draggedElevation,
407 disabledElevation = disabledElevation
408 )
409
410 /**
411 * Creates a [CardElevation] that will animate between the provided values according to the
412 * Material specification for an [ElevatedCard].
413 *
414 * @param defaultElevation the elevation used when the [ElevatedCard] is has no other
415 * [Interaction]s.
416 * @param pressedElevation the elevation used when the [ElevatedCard] is pressed.
417 * @param focusedElevation the elevation used when the [ElevatedCard] is focused.
418 * @param hoveredElevation the elevation used when the [ElevatedCard] is hovered.
419 * @param draggedElevation the elevation used when the [ElevatedCard] is dragged.
420 * @param disabledElevation the elevation used when the [Card] is disabled.
421 */
422 @Composable
423 fun elevatedCardElevation(
424 defaultElevation: Dp = ElevatedCardTokens.ContainerElevation,
425 pressedElevation: Dp = ElevatedCardTokens.PressedContainerElevation,
426 focusedElevation: Dp = ElevatedCardTokens.FocusContainerElevation,
427 hoveredElevation: Dp = ElevatedCardTokens.HoverContainerElevation,
428 draggedElevation: Dp = ElevatedCardTokens.DraggedContainerElevation,
429 disabledElevation: Dp = ElevatedCardTokens.DisabledContainerElevation
430 ): CardElevation =
431 CardElevation(
432 defaultElevation = defaultElevation,
433 pressedElevation = pressedElevation,
434 focusedElevation = focusedElevation,
435 hoveredElevation = hoveredElevation,
436 draggedElevation = draggedElevation,
437 disabledElevation = disabledElevation
438 )
439
440 /**
441 * Creates a [CardElevation] that will animate between the provided values according to the
442 * Material specification for an [OutlinedCard].
443 *
444 * @param defaultElevation the elevation used when the [OutlinedCard] is has no other
445 * [Interaction]s.
446 * @param pressedElevation the elevation used when the [OutlinedCard] is pressed.
447 * @param focusedElevation the elevation used when the [OutlinedCard] is focused.
448 * @param hoveredElevation the elevation used when the [OutlinedCard] is hovered.
449 * @param draggedElevation the elevation used when the [OutlinedCard] is dragged.
450 */
451 @Composable
452 fun outlinedCardElevation(
453 defaultElevation: Dp = OutlinedCardTokens.ContainerElevation,
454 pressedElevation: Dp = defaultElevation,
455 focusedElevation: Dp = defaultElevation,
456 hoveredElevation: Dp = defaultElevation,
457 draggedElevation: Dp = OutlinedCardTokens.DraggedContainerElevation,
458 disabledElevation: Dp = OutlinedCardTokens.DisabledContainerElevation
459 ): CardElevation =
460 CardElevation(
461 defaultElevation = defaultElevation,
462 pressedElevation = pressedElevation,
463 focusedElevation = focusedElevation,
464 hoveredElevation = hoveredElevation,
465 draggedElevation = draggedElevation,
466 disabledElevation = disabledElevation
467 )
468
469 /**
470 * Creates a [CardColors] that represents the default container and content colors used in a
471 * [Card].
472 */
473 @Composable fun cardColors() = MaterialTheme.colorScheme.defaultCardColors
474
475 /**
476 * Creates a [CardColors] that represents the default container and content colors used in a
477 * [Card].
478 *
479 * @param containerColor the container color of this [Card] when enabled.
480 * @param contentColor the content color of this [Card] when enabled.
481 * @param disabledContainerColor the container color of this [Card] when not enabled.
482 * @param disabledContentColor the content color of this [Card] when not enabled.
483 */
484 @Composable
485 fun cardColors(
486 containerColor: Color = Color.Unspecified,
487 contentColor: Color = contentColorFor(containerColor),
488 disabledContainerColor: Color = Color.Unspecified,
489 disabledContentColor: Color = contentColor.copy(DisabledAlpha),
490 ): CardColors =
491 MaterialTheme.colorScheme.defaultCardColors.copy(
492 containerColor = containerColor,
493 contentColor = contentColor,
494 disabledContainerColor = disabledContainerColor,
495 disabledContentColor = disabledContentColor
496 )
497
498 internal val ColorScheme.defaultCardColors: CardColors
499 get() {
500 return defaultCardColorsCached
501 ?: CardColors(
502 containerColor = fromToken(FilledCardTokens.ContainerColor),
503 contentColor = contentColorFor(fromToken(FilledCardTokens.ContainerColor)),
504 disabledContainerColor =
505 fromToken(FilledCardTokens.DisabledContainerColor)
506 .copy(alpha = FilledCardTokens.DisabledContainerOpacity)
507 .compositeOver(fromToken(FilledCardTokens.ContainerColor)),
508 disabledContentColor =
509 contentColorFor(fromToken(FilledCardTokens.ContainerColor))
510 .copy(DisabledAlpha),
511 )
512 .also { defaultCardColorsCached = it }
513 }
514
515 /**
516 * Creates a [CardColors] that represents the default container and content colors used in an
517 * [ElevatedCard].
518 */
519 @Composable fun elevatedCardColors() = MaterialTheme.colorScheme.defaultElevatedCardColors
520
521 /**
522 * Creates a [CardColors] that represents the default container and content colors used in an
523 * [ElevatedCard].
524 *
525 * @param containerColor the container color of this [ElevatedCard] when enabled.
526 * @param contentColor the content color of this [ElevatedCard] when enabled.
527 * @param disabledContainerColor the container color of this [ElevatedCard] when not enabled.
528 * @param disabledContentColor the content color of this [ElevatedCard] when not enabled.
529 */
530 @Composable
531 fun elevatedCardColors(
532 containerColor: Color = Color.Unspecified,
533 contentColor: Color = contentColorFor(containerColor),
534 disabledContainerColor: Color = Color.Unspecified,
535 disabledContentColor: Color = contentColor.copy(DisabledAlpha),
536 ): CardColors =
537 MaterialTheme.colorScheme.defaultElevatedCardColors.copy(
538 containerColor = containerColor,
539 contentColor = contentColor,
540 disabledContainerColor = disabledContainerColor,
541 disabledContentColor = disabledContentColor
542 )
543
544 internal val ColorScheme.defaultElevatedCardColors: CardColors
545 get() {
546 return defaultElevatedCardColorsCached
547 ?: CardColors(
548 containerColor = fromToken(ElevatedCardTokens.ContainerColor),
549 contentColor =
550 contentColorFor(fromToken(ElevatedCardTokens.ContainerColor)),
551 disabledContainerColor =
552 fromToken(ElevatedCardTokens.DisabledContainerColor)
553 .copy(alpha = ElevatedCardTokens.DisabledContainerOpacity)
554 .compositeOver(
555 fromToken(ElevatedCardTokens.DisabledContainerColor)
556 ),
557 disabledContentColor =
558 contentColorFor(fromToken(ElevatedCardTokens.ContainerColor))
559 .copy(DisabledAlpha),
560 )
561 .also { defaultElevatedCardColorsCached = it }
562 }
563
564 /**
565 * Creates a [CardColors] that represents the default container and content colors used in an
566 * [OutlinedCard].
567 */
568 @Composable fun outlinedCardColors() = MaterialTheme.colorScheme.defaultOutlinedCardColors
569
570 /**
571 * Creates a [CardColors] that represents the default container and content colors used in an
572 * [OutlinedCard].
573 *
574 * @param containerColor the container color of this [OutlinedCard] when enabled.
575 * @param contentColor the content color of this [OutlinedCard] when enabled.
576 * @param disabledContainerColor the container color of this [OutlinedCard] when not enabled.
577 * @param disabledContentColor the content color of this [OutlinedCard] when not enabled.
578 */
579 @Composable
580 fun outlinedCardColors(
581 containerColor: Color = Color.Unspecified,
582 contentColor: Color = contentColorFor(containerColor),
583 disabledContainerColor: Color = Color.Unspecified,
584 disabledContentColor: Color = contentColorFor(containerColor).copy(DisabledAlpha),
585 ): CardColors =
586 MaterialTheme.colorScheme.defaultOutlinedCardColors.copy(
587 containerColor = containerColor,
588 contentColor = contentColor,
589 disabledContainerColor = disabledContainerColor,
590 disabledContentColor = disabledContentColor
591 )
592
593 internal val ColorScheme.defaultOutlinedCardColors: CardColors
594 get() {
595 return defaultOutlinedCardColorsCached
596 ?: CardColors(
597 containerColor = fromToken(OutlinedCardTokens.ContainerColor),
598 contentColor =
599 contentColorFor(fromToken(OutlinedCardTokens.ContainerColor)),
600 disabledContainerColor = fromToken(OutlinedCardTokens.ContainerColor),
601 disabledContentColor =
602 contentColorFor(fromToken(OutlinedCardTokens.ContainerColor))
603 .copy(DisabledAlpha),
604 )
605 .also { defaultOutlinedCardColorsCached = it }
606 }
607
608 /**
609 * Creates a [BorderStroke] that represents the default border used in [OutlinedCard].
610 *
611 * @param enabled whether the card is enabled
612 */
613 @Composable
614 fun outlinedCardBorder(enabled: Boolean = true): BorderStroke {
615 val color =
616 if (enabled) {
617 OutlinedCardTokens.OutlineColor.value
618 } else {
619 OutlinedCardTokens.DisabledOutlineColor.value
620 .copy(alpha = OutlinedCardTokens.DisabledOutlineOpacity)
621 .compositeOver(ElevatedCardTokens.ContainerColor.value)
622 }
623 return remember(color) { BorderStroke(OutlinedCardTokens.OutlineWidth, color) }
624 }
625 }
626
627 /**
628 * Represents the elevation for a card in different states.
629 * - See [CardDefaults.cardElevation] for the default elevation used in a [Card].
630 * - See [CardDefaults.elevatedCardElevation] for the default elevation used in an [ElevatedCard].
631 * - See [CardDefaults.outlinedCardElevation] for the default elevation used in an [OutlinedCard].
632 */
633 @Immutable
634 class CardElevation
635 internal constructor(
636 private val defaultElevation: Dp,
637 private val pressedElevation: Dp,
638 private val focusedElevation: Dp,
639 private val hoveredElevation: Dp,
640 private val draggedElevation: Dp,
641 private val disabledElevation: Dp
642 ) {
643 /**
644 * Represents the shadow elevation used in a card, depending on its [enabled] state and
645 * [interactionSource].
646 *
647 * Shadow elevation is used to apply a shadow around the card to give it higher emphasis.
648 *
649 * @param enabled whether the card is enabled
650 * @param interactionSource the [InteractionSource] for this card
651 */
652 @Composable
shadowElevationnull653 internal fun shadowElevation(
654 enabled: Boolean,
655 interactionSource: InteractionSource?
656 ): State<Dp> {
657 if (interactionSource == null) {
658 return remember { mutableStateOf(defaultElevation) }
659 }
660 return animateElevation(enabled = enabled, interactionSource = interactionSource)
661 }
662
663 @Composable
animateElevationnull664 private fun animateElevation(
665 enabled: Boolean,
666 interactionSource: InteractionSource
667 ): State<Dp> {
668 val interactions = remember { mutableStateListOf<Interaction>() }
669 LaunchedEffect(interactionSource) {
670 interactionSource.interactions.collect { interaction ->
671 when (interaction) {
672 is HoverInteraction.Enter -> {
673 interactions.add(interaction)
674 }
675 is HoverInteraction.Exit -> {
676 interactions.remove(interaction.enter)
677 }
678 is FocusInteraction.Focus -> {
679 interactions.add(interaction)
680 }
681 is FocusInteraction.Unfocus -> {
682 interactions.remove(interaction.focus)
683 }
684 is PressInteraction.Press -> {
685 interactions.add(interaction)
686 }
687 is PressInteraction.Release -> {
688 interactions.remove(interaction.press)
689 }
690 is PressInteraction.Cancel -> {
691 interactions.remove(interaction.press)
692 }
693 is DragInteraction.Start -> {
694 interactions.add(interaction)
695 }
696 is DragInteraction.Stop -> {
697 interactions.remove(interaction.start)
698 }
699 is DragInteraction.Cancel -> {
700 interactions.remove(interaction.start)
701 }
702 }
703 }
704 }
705
706 val interaction = interactions.lastOrNull()
707
708 val target =
709 if (!enabled) {
710 disabledElevation
711 } else {
712 when (interaction) {
713 is PressInteraction.Press -> pressedElevation
714 is HoverInteraction.Enter -> hoveredElevation
715 is FocusInteraction.Focus -> focusedElevation
716 is DragInteraction.Start -> draggedElevation
717 else -> defaultElevation
718 }
719 }
720
721 val animatable = remember { Animatable(target, Dp.VectorConverter) }
722
723 LaunchedEffect(target) {
724 if (animatable.targetValue != target) {
725 if (!enabled) {
726 // No transition when moving to a disabled state.
727 animatable.snapTo(target)
728 } else {
729 val lastInteraction =
730 when (animatable.targetValue) {
731 pressedElevation -> PressInteraction.Press(Offset.Zero)
732 hoveredElevation -> HoverInteraction.Enter()
733 focusedElevation -> FocusInteraction.Focus()
734 draggedElevation -> DragInteraction.Start()
735 else -> null
736 }
737 animatable.animateElevation(
738 from = lastInteraction,
739 to = interaction,
740 target = target
741 )
742 }
743 }
744 }
745
746 return animatable.asState()
747 }
748
equalsnull749 override fun equals(other: Any?): Boolean {
750 if (this === other) return true
751 if (other == null || other !is CardElevation) return false
752
753 if (defaultElevation != other.defaultElevation) return false
754 if (pressedElevation != other.pressedElevation) return false
755 if (focusedElevation != other.focusedElevation) return false
756 if (hoveredElevation != other.hoveredElevation) return false
757 if (disabledElevation != other.disabledElevation) return false
758
759 return true
760 }
761
hashCodenull762 override fun hashCode(): Int {
763 var result = defaultElevation.hashCode()
764 result = 31 * result + pressedElevation.hashCode()
765 result = 31 * result + focusedElevation.hashCode()
766 result = 31 * result + hoveredElevation.hashCode()
767 result = 31 * result + disabledElevation.hashCode()
768 return result
769 }
770 }
771
772 /**
773 * Represents the container and content colors used in a card in different states.
774 *
775 * @param containerColor the container color of this [Card] when enabled.
776 * @param contentColor the content color of this [Card] when enabled.
777 * @param disabledContainerColor the container color of this [Card] when not enabled.
778 * @param disabledContentColor the content color of this [Card] when not enabled.
779 * @constructor create an instance with arbitrary colors.
780 * - See [CardDefaults.cardColors] for the default colors used in a [Card].
781 * - See [CardDefaults.elevatedCardColors] for the default colors used in a [ElevatedCard].
782 * - See [CardDefaults.outlinedCardColors] for the default colors used in a [OutlinedCard].
783 */
784 @Immutable
785 class CardColors
786 constructor(
787 val containerColor: Color,
788 val contentColor: Color,
789 val disabledContainerColor: Color,
790 val disabledContentColor: Color,
791 ) {
792 /**
793 * Returns a copy of this CardColors, optionally overriding some of the values. This uses the
794 * Color.Unspecified to mean “use the value from the source”
795 */
copynull796 fun copy(
797 containerColor: Color = this.containerColor,
798 contentColor: Color = this.contentColor,
799 disabledContainerColor: Color = this.disabledContainerColor,
800 disabledContentColor: Color = this.disabledContentColor
801 ) =
802 CardColors(
803 containerColor.takeOrElse { this.containerColor },
<lambda>null804 contentColor.takeOrElse { this.contentColor },
<lambda>null805 disabledContainerColor.takeOrElse { this.disabledContainerColor },
<lambda>null806 disabledContentColor.takeOrElse { this.disabledContentColor },
807 )
808
809 /**
810 * Represents the container color for this card, depending on [enabled].
811 *
812 * @param enabled whether the card is enabled
813 */
814 @Stable
containerColornull815 internal fun containerColor(enabled: Boolean): Color =
816 if (enabled) containerColor else disabledContainerColor
817
818 /**
819 * Represents the content color for this card, depending on [enabled].
820 *
821 * @param enabled whether the card is enabled
822 */
823 @Stable
824 internal fun contentColor(enabled: Boolean) =
825 if (enabled) contentColor else disabledContentColor
826
827 override fun equals(other: Any?): Boolean {
828 if (this === other) return true
829 if (other == null || other !is CardColors) return false
830
831 if (containerColor != other.containerColor) return false
832 if (contentColor != other.contentColor) return false
833 if (disabledContainerColor != other.disabledContainerColor) return false
834 if (disabledContentColor != other.disabledContentColor) return false
835
836 return true
837 }
838
hashCodenull839 override fun hashCode(): Int {
840 var result = containerColor.hashCode()
841 result = 31 * result + contentColor.hashCode()
842 result = 31 * result + disabledContainerColor.hashCode()
843 result = 31 * result + disabledContentColor.hashCode()
844 return result
845 }
846 }
847