Back to blog

How to build overlapping layout in Jetpack Compose

Share
How to build overlapping layout in Jetpack Compose

In this article, we're going to build a UI where contents of one composable will overlap on another (as you would usually find in social profiles where profile image overlap on cover/background image).

We're going to build 2 boxes that overlap on each other. The first box is a large inner box (as cover image) while the second box is a small outer box (as profile image).

To do so, first we'll need to build a custom layout that calculates the size of overlapping boxes and place them accordingly.

@Composable fun OverlappingBoxes( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { Layout( modifier = modifier, content = content, ) { measurables, constraints -> val largeBox = measurables[0] val smallBox = measurables[1] val looseConstraints = constraints.copy( minWidth = 0, minHeight = 0, ) val largePlaceable = largeBox.measure(looseConstraints) val smallPlaceable = smallBox.measure(looseConstraints) layout( width = constraints.maxWidth, height = largePlaceable.height + smallPlaceable.height / 2, ) { largePlaceable.placeRelative( x = 0, y = 0, ) smallPlaceable.placeRelative( x = (constraints.maxWidth - smallPlaceable.width) / 2, y = largePlaceable.height - smallPlaceable.height / 2 ) } } }

Now, we'll build a composable which contains actual content of our UI and pass it to OverlappingBoxes

@Composable fun CoverAndProfileImage( modifier: Modifier = Modifier, coverImage: Any?, profileImage: Any?, onCoverClick: () -> Unit = {}, onProfileClick: () -> Unit = {}, ) { OverlappingBoxes(modifier = modifier.fillMaxWidth()) { Box( modifier = Modifier .fillMaxWidth() .height(150.dp) .clickable { onCoverClick() } ) { ImageItem( modifier = Modifier.fillMaxSize(), data = coverImage ?: R.drawable.default_cover_image, contentScale = ContentScale.FillWidth ) } Box( modifier = Modifier .size(100.dp) .clip(RoundedCornerShape(16.dp)) .clickable{ onProfileClick() } ) { ImageItem( modifier = Modifier.fillMaxSize(), data = profileImage ?: R.drawable.default_profile_image, contentScale = ContentScale.Crop ) } } } @Composable fun ImageItem( modifier: Modifier, data: Any?, crossfadeValue: Int = 300, contentDescription: String? = null, contentScale: ContentScale = ContentScale.Crop ) { coil.compose.AsyncImage( modifier = modifier, model = ImageRequest.Builder(LocalContext.current) .data(data) .crossfade(crossfadeValue) .build(), contentDescription = contentDescription, placeholder = painterResource(id = R.drawable.placeholder), contentScale = contentScale ) }

You can see the UI in action by using the below preview configuration.

@Preview @Composable fun CoverAndProfileImagePreview() { CoverAndProfileImage( coverImage= "https://9to5google.com/wp-content/uploads/sites/4/2021/02/android-jetpack-header.png", profileImage= "https://3.bp.blogspot.com/-VVp3WvJvl84/X0Vu6EjYqDI/AAAAAAAAPjU/ZOMKiUlgfg8ok8DY8Hc-ocOvGdB0z86AgCLcBGAsYHQ/s1600/jetpack%2Bcompose%2Bicon_RGB.png" ) }

How to build overlapping layout in Jetpack Compose