Designing Modern Gradient Bars in Android Using XML and Compose

Oğuzhan Aslan
6 min readNov 3, 2024

--

Photo by Codioful (Formerly Gradienta) on Unsplash

In the dynamic world of Android development, creating beautiful and engaging user interfaces is essential. With Android 10, a major shift occurred with a focus on edge-to-edge design, allowing UI elements to seamlessly stretch across the screen. This change, driven by gesture navigation, moved developers away from the traditional navigation and status bars towards a more fluid and immersive experience. Now, with Android 15 and SDK 35, edge-to-edge design is the default. Even if your app doesn’t specifically require edge-to-edge support, it’s important to be prepared for it. In this blog, we’ll explore how to design eye-catching gradient status and navigation bars in Android.

To learn more about Edge to edge support you may examine the followings:

First things first

First, we need to manually make our application edge-to-edge for devices running Android 14 and below. This allows us to draw custom elements over the system bars.

System bars include the status bar, caption bar, and navigation bar.

This can be acheived by adding just one line of code to your host activity.

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity_layout)
}
}

With this single line of code, we now have an edge-to-edge activity. This means the window will extend across the full width and height of the display, drawing behind the system bars.

Before we use e2e function
after we use e2e function

Draw Gradient

One way to create a gradient background is by adding a dummy view to the activity layout and applying the gradient as its background. Alternatively, you could set the desired gradient directly as the root view’s background. Here, I’ll demonstrate the first approach.

First, create gradient views:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.25" />
<View
android:id="@+id/upper_gradient_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.75" />
<View
android:id="@+id/bottom_gradient_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/guideline2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />

<!-- other content -->
</androidx.constraintlayout.widget.ConstraintLayout>

Now we’ve created the necessary gradient views, but they don’t yet display any color. To add color, I use one of my dynamic shaping/coloring classes, which works as follows:

class GradientDrawableBuilder {
private var orientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
private var colors: IntArray = intArrayOf()
private var width = 0
private var height = 0

fun setOrientation(orientation: GradientDrawable.Orientation): GradientDrawableBuilder {
this.orientation = orientation
return this
}
fun setGradientColors(vararg colors: Int): GradientDrawableBuilder {
this.colors = colors
return this
}
fun setSize(width: Int, height: Int): GradientDrawableBuilder {
this.width = width
this.height = height
return this
}
fun build(): GradientDrawable {
val drawable = GradientDrawable(orientation, colors)
// Set size if provided
if (width > 0 && height > 0) {
drawable.setSize(width, height)
}

return drawable
}
}

As you can see the builder is simply helps us to create gradient drawable dynamically on runtime.

Now all we need is to create and set gradients to our views.

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.view.post {
binding.view.background = GradientDrawableBuilder()
.setGradientColors(
Color.parseColor("#66D5AEFF"),
Color.parseColor("#00D5AEFF")
)
.setSize(binding.view.width, binding.view.height)
.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM)
.build()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
binding.view2.post {
val navigationBarSize = windowManager.getNavigationBarSize()
binding.view2.updateLayoutParams {
height = navigationBarSize
}
binding.view2.background = GradientDrawableBuilder()
.setGradientColors(
Color.parseColor("#00D5AEFF"),
Color.parseColor("#66D5AEFF")
)
.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM)
.build()
}
}
}
}

After applying the code above should see something like the followings.

I intentionally provided two different approaches for setting the gradient view heights. As shown, you can either use the system bar heights or specify any custom height that suits your needs. The choice is entirely up to you and your use case.

However, we have two problems here.

  • Content views are placed far into the edges.
  • Navigation bar blocks the bottom gradient.

Solving Spacing Problem

All we need to do is apply window insets to our content views. Here’s how to apply the insets:

val windowManager = WindowManagerUtil.get(this)
with(windowManager) {
binding.button2.addSystemWindowInsetToMargin(bottom = true)
binding.button.addSystemWindowInsetToMargin(top = true)
}

Window manager is also one of my utility classes. It just abstracts “window” interactions for us. If you are curious, it is implemented here.

Solving Navigation Bar Problem

Solution of this problem is also one line code.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {                
window.isNavigationBarContrastEnforced = false
}

The solution is also explained here.

And that is it, we implemented gradient color for out system bars successfully.

Compose side

Now that we know about how to implement the gradient color for the bars, we can implement a compose version of it. As usual, Jetpack Compose makes the process easier. (You can read about the details from official documentation.)

@Composable
fun Screen(modifier: Modifier = Modifier) {
Surface(
modifier = modifier
.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
) {
VerticalGradient(
modifier = Modifier
.fillMaxWidth()
.height((maxHeight * 0.25f)) // Calculate height based on maxHeight
.align(Alignment.TopCenter),
colors = listOf(
Color(0x66D5AEFF),
Color(0x00D5AEFF)
)
)
VerticalGradient(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.windowInsetsBottomHeight(WindowInsets.systemBars),
colors = listOf(
Color(0x00D5AEFF),
Color(0x66D5AEFF)
)
)
}
Column(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemBars),
verticalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {}
) {
Text("Button")
}
Button(
onClick = {}
) {
Text("Button")
}
}
}
}

@Composable
fun VerticalGradient(
colors: List<Color>,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.background(Brush.verticalGradient(colors))
)
}

Conclusion

In this blog, we’ve explored how to enhance your Android app’s user interface with gradient status and navigation bars using both XML and Jetpack Compose. By embracing edge-to-edge design, you can create a more immersive experience for users. We covered enabling edge-to-edge support, crafting dynamic gradient views, and addressing common UI challenges like content spacing and navigation bar obstruction. With these techniques, you can ensure your app stands out with a sleek and modern look. Whether you’re using XML or Compose, these tools will help you deliver visually stunning and engaging interfaces.

--

--