Sitemap

How to Create Fading Edge Modifier in Jetpack Compose

Kappdev
4 min readDec 18, 2023

Welcome 👋

In this article, we’ll create a highly customizable Fading Edge modifier in Jetpack Compose.

Let’s get started 🚀

Created by

Preparation

Before we step up, to the implementation, we need to define an enum class FadeSide that represents all sides of the view, and which we will use to specify the side we want to draw the gradient.

enum class FadeSide {
LEFT, RIGHT, BOTTOM, TOP
}

Also, to draw the gradient by a specified side we need an extension function getFadeOffsets for Size class that returns a pair of start and end gradient offsets.

fun Size.getFadeOffsets(side: FadeSide): Pair<Offset, Offset> {
return when (side) {
FadeSide.LEFT -> Offset.Zero to Offset(width, 0f)
FadeSide.RIGHT -> Offset(width, 0f) to Offset.Zero
FadeSide.BOTTOM -> Offset(0f, height) to Offset.Zero
FadeSide.TOP -> Offset.Zero to Offset(0f, height)
}
}

Defining the Function and Parameters

Alright, let’s define an extension function fadingEdge for the Modifier and quickly go through its parameters.

fun Modifier.fadingEdge(
vararg sides: FadeSide,
color: Color,
width: Dp,
isVisible: Boolean,
spec: AnimationSpec<Dp>?
)
  • Sides: Accepts values from the FadeSide enum, allowing multiple sides to be defined using vararg.
  • Color: Represents the color of the gradient.
  • Width: Specifies the width of the gradient edge, measured in Dp.
  • IsVisible: Allows to control the visibility of the fade effect.
  • Spec: This parameter is nullable, and gives a possibility to provide a specification for an animation. If null, no animation will be applied.

Implementation

For the final touch, let’s write the implementation.

First, we utilize a composed modifier to be able to create a state for the animation.

fun Modifier.fadingEdge(
// Parameters
) = composed {
// Implementation...
}

Now, we use the require function to validate the width parameter:

require(width > 0.dp) { "Invalid fade width: Width must be greater than 0" }

Let’s write the animation logic. In this scenario, we first check whether the spec parameter isn't null. Then, based on the isVisible condition and the provided spec, we animate the width parameter from 0 to a specified width.

val animatedWidth = spec?.let {
animateDpAsState(
targetValue = if (isVisible) width else 0.dp,
animationSpec = spec,
label = "Fade width"
).value
}

Great, now we’re set to draw the content and apply the fade effect on top using the drawWithContent function.

drawWithContent {
// Draw the content
this@drawWithContent.drawContent()

// Go through all provided sides
sides.forEach { side ->
// Get start and end gradient offsets
val (start, end) = this.size.getFadeOffsets(side)

// Define the static width
val staticWidth = if (isVisible) width.toPx() else 0f
// Define the final width
val widthPx = animatedWidth?.toPx() ?: staticWidth

// Calculate fraction based on view size
val fraction = when(side) {
FadeSide.LEFT, FadeSide.RIGHT -> widthPx / this.size.width
FadeSide.BOTTOM, FadeSide.TOP -> widthPx / this.size.height
}

// Draw the gradient
drawRect(
brush = Brush.linearGradient(
0f to color,
fraction to Color.Transparent,
start = start,
end = end
),
size = this.size
)
}
}

Additionally, for the call simplicity, we can create separate functions for each side. For instance, we can implement a function for the left side like this:

fun Modifier.leftFadingEdge(
color: Color,
isVisible: Boolean = true,
width: Dp = 16.dp,
spec: AnimationSpec<Dp>? = null
) = fadingEdge(FadeSide.LEFT, color = color, width = width, isVisible = isVisible, spec = spec)

The implementation for the remaining sides will be similar. By the way, you can access these functions and all the other source code on Gist.

Example

Let’s craft a simple LazyRow with a gradient on its left and right sides that will be visible only in case the content is overlapped.

val listState = rememberLazyListState()

LazyRow(
state = listState,
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.leftFadingEdge(
color = Color.White,
width = 32.dp,
spec = tween(500),
isVisible = listState.canScrollBackward,
)
.rightFadingEdge(
color = Color.White,
width = 32.dp,
spec = tween(500),
isVisible = listState.canScrollForward
)
) {
items(20) { item ->
Text(
text = "item $item",
modifier = Modifier
.background(Color.Gray, RoundedCornerShape(8.dp))
.padding(8.dp)
)
}
}

The output:

Demo

Thank you for reading this article! ❤️ If you found it enjoyable and valuable, show your appreciation by clapping 👏 and following

for more exciting articles 😊

--

--

Kappdev
Kappdev

Written by Kappdev

💡 Curious Explorer 🧭 Kotlin and Compose enthusiast 👨‍💻 Passionate about self-development and growth ❤️‍🔥 Push your boundaries 🚀

No responses yet