Fiori Horizon Object Cards¶
Design¶
Fiori Horizon Object Cards, based on the Material Card View, may consist of as little as a single Title, or as much as a section comprised of a Card Header, Content Description, and two Action Buttons. Object Cards support both Light mode and Dark mode, as determined by the device's settings.
![]() |
![]() |
---|---|
![]() |
![]() |
---|---|
Development¶
Fiori Horizon Object Cards are implemented entirely using Jetpack Compose, including a Composable
FioriHorizonTheme
. To understand the fundamentals of Jetpack Compose, refer to the Android Developer's documentation.
The fiori-horizon-cards
module provides four UI components for application developers to use out of the box:
ObjectCard
: a UI component to display a single Object Card.ObjectCardRow
: a UI component to display a series of Object Cards in a horizontally scrollable row.ObjectCardColumn
: a UI component to display a series of Object Cards in a vertically scrollable column.ObjectCardGrid
: a UI component to display a series of Object Cards in a vertically scrollable grid.
The latter three components are all responsive to different screen sizes.
Usage in an Application¶
This section describes how to use the Object Card components in an application. A sample Android application is bundled in this repository to demonstrate the usage of the above components on various screens.
![]() |
![]() |
---|---|
1. How to Create an ObjectCard
¶
ObjectCard
is a Composable
function that takes four parameters:
- An
ObjectCardData
that specifies the data to be displayed on the card - A
Modifier
that is used to define the height and width of the card and/or aTestTag
needed for Unit Tests. A defaultobjectCardModifier
is predefined to set the minimum height and width requirements. - An
ObjectCardTextColors
to customize the default text colors used. - An
ObjectCardTextStyles
to customize the default text styles used.
@Composable
fun ObjectCard(
data: ObjectCardData,
modifier: Modifier = objectCardModifier,
textColors: ObjectCardTextColors = ObjectCardDefaults.textColors(),
textStyles: ObjectCardTextStyles = ObjectCardDefaults.textStyles()
) {
val uiState = rememberObjectCardUiState(data = data)
ObjectCardContent(
darkTheme = isSystemInDarkTheme(),
uiState = uiState,
modifier = modifier,
textColors = textColors,
textStyles = textStyles
)
}
An object of the ObjectCardData
has to be passed in from the application in order to create an ObjectCard
.
@Parcelize
data class ObjectCardData(
val title: String,
val subtitle: String? = null,
val footnote: String? = null,
val status: Pair<String, ObjectCardStatusCode>? = null,
val imageThumbnail: @RawValue ImageThumbnail? = null,
val contentDesc: String? = null,
val showContentDescBelowHeader: Boolean = true,
val primaryActionButton: @RawValue ActionButton? = null,
val secondaryActionButton: @RawValue ActionButton? = null,
val menuItems: List<Pair<String, () -> Unit>>? = listOf(),
val menuItemsWithIcon: @RawValue List<MenuItem>? = listOf(),
val cardClickable: Boolean = true,
val onCardClick: (() -> Unit)? = null,
val cardStyles: @RawValue CardStyles? = null
) : Parcelable
Creating an ObjectCard
can be as simple as the following (if there is no need to provide a custom Modifier
):
FioriHorizonTheme(darkTheme = false) {
val objectCardData = remember { generateObjectCardData()}
ObjectCard (
data = objectCardData
)
}
You can customize the ObjectCard
's style, including Action Button styles and card background color, by supplying new values in the ObjectCardData
. For example, to change the background color of the ObjectCard
to light gray:
val objectCardFullHeaderOnlyCustomColor = ObjectCardData(
title = TITLE,
subtitle = SUBTITLE,
footnote = FOOTNOTE,
statusButton = STATUSBUTTON,
imageThumbnail = IMAGETHUMBNAIL,
menuItems = MENUITEMS,
cardStyles = CardStyles(
0, 300, Color.LightGray, Color.DarkGray,
null, null, null, 4.dp
)
)
When this card is drawn on the screen, it will always have a light gray background.
2. How to Create an ObjectCardRow
, ObjectCardColumn
, or ObjectCardGrid
¶
In a lot of use cases, an application won't display a single ObjectCard
. Instead, it will display a series of Object Cards in a certain layout that could be a horizontal row, a vertical column, or a grid.
All three layouts are defined similarly as a Composable
function in the fiorinextcard
module, even though the implementation details differ from one another. In order to create one of these ObjectCard
series layouts, an application only needs to know the signatures of these functions.
@Composable
fun ObjectCardRow(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
@Composable
fun ObjectCardColumn(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
@Composable
fun ObjectCardGrid(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
Then, to create one of the Object Card layouts, an application should pass in a list of ObjectCardData
objects as well as an Enum
value for ScreenType
. For example:
FioriHorizonTheme {
var objectCardData = remember { generateObjectCardData()}
ObjectCardRow(
cardList = listOf(objectCardData, objectCardData, objectCardData),
screenType = ScreenType.SMALL
)
}
Within the fiori-horizon-cards
module, a utility Composable
function is provided for any application to detect the ScreenType
based on the Android Developers' recommendation documented in Support different screen sizes.
@Composable
fun getScreenType(): ScreenType {
val screenWidth =
LocalContext.current.resources.displayMetrics.widthPixels.dp / LocalDensity.current.density
return when {
screenWidth < 600.dp -> ScreenType.SMALL
screenWidth >= 600.dp && screenWidth < 840.dp -> ScreenType.MEDIUM
screenWidth > 840.dp -> ScreenType.LARGE
else -> ScreenType.SMALL
}
}
In order to create the layouts with custom text color or styles, use the following Composable
functions:
@Composable
fun ObjectCardRow(
cardList: List<ObjectCardDataCustomStyleForCollection>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
) {
...
}
@Composable
fun ObjectCardColumn(
cardList: List<ObjectCardDataCustomStyleForCollection>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
) {
...
}
@Composable
fun ObjectCardGrid(
cardList: List<ObjectCardDataCustomStyleForCollection>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
) {
...
}
Where the ObjectCardDataCustomStyleForCollection
comprises of:
data class ObjectCardDataCustomStyleForCollection(
val data: ObjectCardData,
val textColors: @RawValue ObjectCardTextColors? = null,
val textStyles: @RawValue ObjectCardTextStyles? = null
) : Parcelable
3. Using These Composable
Components in a Non-Compose, View-Based Application¶
Jetpack Compose is still fairly new. Many applications are still built on the traditional view-based UI. Using a Composable
component in a view-based application consists of two steps:
-
In the XML layout, define the
Composable
component as aComposeView
. For example in the Fiori Next Card Demo app, in the fragment_view_based_object_card.xml, aComposeView
is defined for displaying anObjectCardRow
.<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view_alerts" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/alert_title"/>
-
In the
Fragment
class, inside theonCreateView
function, the actual Compose view can be added to the view tree programmatically while the rest of theFragment
class remains intact.override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentViewBasedObjectCardBinding.inflate(inflater, container, false) val view = binding.root.rootView val screenType = getScreenType(requireContext()) view.compose_view_alerts.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // In Compose world FioriHorizonTheme(darkTheme = isSystemInDarkTheme()) { ObjectCardRow(objectCardList = objectCardList6, screenType = screenType) } } } ... return view }
4. Customizing the ObjectCard
Text Color and Style¶
The default colors and styles of the text in an Object Card adhere to the Fiori Horizon design. However, users can also customize them with their own colors and styles. In order to change the text color and text style, use the ObjectCardDefaults.textColors()
and ObjectCardDefaults.textStyles()
Composable
functions respectively and pass on the required colors and styles for the different texts seen within the Object Card.
val customTitleColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorSemanticCritical
val customSubtitleColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorAccent4
val customFootnoteColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorSemanticNegative
val customContentDescriptionColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorAccent12
val customTitleStyle = FioriTextStyleH6
val customSubtitleStyle = FioriTextStyleSubtitle2
val customFootnoteStyle = FioriTextStyleSubtitle3
val customContentDescriptionStyle = FioriTextStyleBody1
val customStatusStyle = FioriTextStyleBody1
FioriHorizonTheme {
ObjectCard(
data = objectCardData,
textColors = ObjectCardDefaults.textColors(
titleColor = customTitleColor,
subtitleColor = customSubtitleColor,
footnoteColor = customFootnoteColor,
contentDescriptionColor = customContentDescriptionColor
),
textStyles = ObjectCardDefaults.textStyles(
titleStyle = customTitleStyle,
subtitleStyle = customSubtitleStyle,
footnoteStyle = customFootnoteStyle,
contentDescriptionStyle = customContentDescriptionStyle,
statusStyle = customStatusStyle
)
)
}