Mobile devices usually include light and dark themes which can be set manually by the user or change automatically when some conditions are met. For example, your cell phone will probably switch to dark mode when it gets dark in the evening. This way the screen will be easier on the eyes.
If you want to implement this functionality in your app, you have to set the properties that are supposed to depend on the current theme to different values, so one value for the light theme and another for the dark theme. To do that, we use the AppThemeBinding
markup extension. Let’s first demonstrate it using the TestPage
.
The source code for this article is available on Github.
Light Theme, Dark Theme
Delete the code from the TestPage.xaml
file and implement the page like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Slugrace"
x:Class="Slugrace.Views.TestPage"
Title="TestPage"
Padding="20"
BackgroundColor="DarkGoldenrod">
<FlexLayout
Direction="Column"
JustifyContent="SpaceEvenly"
AlignItems="Center"
BackgroundColor="White">
<Label
Text="Hey, what a beautiful day!"
FontSize="26" />
<Image
Source="Speedster.png"
Scale=".9"/>
<Button
Text="Press"
BackgroundColor="Black"
TextColor="White" />
</FlexLayout>
</ContentPage>
Now run the app on Android:
Nothing special, just a label, an image and a button inside a FlexLayout
with a white background color and the background color of the ContentPage
set to a shade of golden.
Let’s modify the code and deliver distinct values for some of the properties. For example the FlexLayout
background color should be white in light theme and black in dark theme. The button should also change from black to white, and the text on it the other way around.
We’ll also change the label text. First, let’s temporarily define two static properties in the Helpers
class:
namespace Slugrace
{
public static class Helpers
{
public static string LightThemeText { get; set; } = "Hey, what a beautiful day!";
public static string DarkThemeText { get; set; } = "Hey, what a beautiful night!";
public static void ValidateNumericInputAndSetState(...)
...
We’re going to use them in the TestPage
. Here’s the modified code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
<FlexLayout
...
BackgroundColor="{AppThemeBinding Light=White, Dark=Black}">
<Label
Text="{AppThemeBinding
Light={x:Static local:Helpers.LightThemeText},
Dark={x:Static local:Helpers.DarkThemeText}}"
FontSize="26" />
...
<Button
Text="Press"
BackgroundColor="{AppThemeBinding Light=Black, Dark=White}"
TextColor="{AppThemeBinding Light=White, Dark=Black}" />
</FlexLayout>
</ContentPage>
To see the themes in action, we have to pick them manually in the emulator. How to exactly do that depends on the emulator, but you will probably have to go to the settings and find it there under Display or something like that. In my emulator it looks like so:
Switch to dark theme. Now our app uses the set of values defined for the dark theme:
You typically change colors in the dark theme to make the text more readable, but you can change any properties you like. Let’s now implement themes in the Slugrace app. But first, remove the two static properties you just added in the Helpers
class and set the label text in the TestPage
manually to what it was originally.
Themes in Our Application
We’re going to apply themes in our app globally and locally. Let’s start with the former. Here’s the App.xaml
file with the themes implemented:
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
...
<!--Colors-->
...
<Color x:Key="normalEntryColor">White</Color>
<Color x:Key="lightPrimaryColor">#FFFFCC</Color>
...
<!--Other values-->
<FontAttributes x:Key="emphasized">Bold</FontAttributes>
<Style TargetType="ContentPage"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{AppThemeBinding Light={StaticResource lightPrimaryColor}, Dark=Black}" />
</Style>
<Style TargetType="Border">
<Setter Property="Stroke"
Value="{AppThemeBinding
Light={StaticResource mainButtonColor},
Dark={StaticResource lightPrimaryColor}}"/>
<Setter Property="StrokeShape" Value="RoundRectangle 10, 10, 10, 10" />
...
<Style TargetType="Button">
<Setter Property="BackgroundColor"
Value="{AppThemeBinding
Light={StaticResource mainButtonColor},
Dark={StaticResource buttonTextColor}}"/>
<Setter Property="TextColor"
Value="{AppThemeBinding
Light={StaticResource buttonTextColor},
Dark={StaticResource mainButtonColor}}" />
<Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />
...
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{AppThemeBinding
Light={StaticResource pressedButtonColor},
Dark={StaticResource lightPrimaryColor}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale" Value=".98" />
<Setter Property="BackgroundColor"
Value="{AppThemeBinding
Light={StaticResource pressedButtonColor},
Dark={StaticResource lightPrimaryColor}}" />
</VisualState.Setters>
...
As you can see, I extracted the background color into a resource and used it to set the background color of ContentPage
in the light theme. In the dark theme the background color is set to black.
The colors of the BackgroundColor
and TextColor
properties of Button
are switched in the two themes.
I also used the new lightPrimaryColor
resource to set the Stroke
property of Border
and the background color of Button
in the PointerOver
and Pressed
visual states.
And now let’s have a look at some local changes.
In the RacePage
a different image is used in dark theme for the Sound button. Otherwise it would be hardly visible in this theme. You can grab the sound_on_dark.png
image and the other one, sound_off_dark.png
(which we’re going to need later) from my Github repository. Paste them to the Resources/Images
folder. Here’s how this is implemented in the RacePage.xaml
file:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
...
<!--the buttons-->
<VerticalStackLayout
...
<Button
Text="Instructions" />
<Button
ImageSource="{AppThemeBinding Light=sound_on.png, Dark=sound_on_dark.png}"
WidthRequest="{OnPlatform 125, Android=150}"
...
In the Bets content view I modified the colors of the slider:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>
<ContentView.Resources>
<Style TargetType="Label" BasedOn="{StaticResource labelBaseStyle}" />
<Style TargetType="Slider">
<Setter Property="ThumbColor"
Value="{AppThemeBinding
Light={StaticResource mainButtonColor},
Dark={StaticResource buttonTextColor}}" />
<Setter Property="MinimumTrackColor"
Value="{AppThemeBinding
Light={StaticResource mainButtonColor},
Dark={StaticResource buttonTextColor}}" />
</Style>
...
I also defined new color resources in the PlayerBet
and PlayerResult
content views. They’re used to set the background color of the Grid
and they look the same:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>
<ContentView.Resources>
<Color x:Key="backgroundColorLight">#FFF4E5</Color>
<Color x:Key="backgroundColorDark">Black</Color>
<Style TargetType="Label"
...
<Grid
BackgroundColor="{OnPlatform
Android={AppThemeBinding
Light={StaticResource backgroundColorLight},
Dark={StaticResource backgroundColorDark}}}"
...
And that’s it. Let’s now compare all the pages in light theme and dark theme: Here’s the SettingsPage
:
Light theme
Dark theme
Here’s the RacePage
with the Bets panel:
Light theme
Dark theme
Here’s the RacePage
with the Results panel:
Light theme
Dark theme
Here’s the GameOverPage
:
Light theme
Dark theme
If you’re wondering why the labels are white in the dark theme, even if we didn’t define their color for that theme, this is because default values for either theme are defined in the Resources/Styles/Style.xaml
file. Our application is now beautifully styled, it supports the light and dark themes on Android and the only thing it lacks is data. In the next part of the series we’ll be briefly talking about namespaces in XAML and then, in the following parts, we’ll see how to provide data and bind to it.