[This is Part 2 of the 2-part series where we build a .Net Maui app as part of the amazing #MAUIUIJuly initiative by Matt Goldman]
The Design
As part of the .NET MAUI UI JULY initiative, I started implementing this wonderful design from Dribbble using .Net Maui. In Part 1, I completed the first page of the design and added some basic animations to spice it up. Today, I will complete the remaining two screen.
The Implementation
The full source code of this app is available on Github for you to play with.
The navigation on Android broke with MAUI Service Release 2 (since 13-July). It was working fine before that (with Service Release 1). I didn't get much time to investigate; not sure if it is an issue with my machine/emulator setup. But the iOS version is working perfectly fine (see a video of the final app running on iOS emulator at the bottom of this post).
So let's dive into it.
The Planets Page
Let's look at the second page of the design. There is a lot going on in this page. I have broken the page into 4 main sections: The Header, Search Bar, Planets Lists and Bottom Tab Menu.
I decided to go with a grid layout with 2 rows to implement this page, one housing the top header, and the second to host the ScrollView which will in turn host the other three sections of the page.
<Grid
ColumnDefinitions="*,Auto"
RowDefinitions="Auto,*">
</Grid>
The header part is straightforward, with a couple of labels laid out in a VerticalStackView and the profile pic enclosed in a border with image clipping to provide the rounded look. Here is the code for implementing the rounded profile pic with a thick border.
<!-- Profile Pic -->
<Border
WidthRequest="56"
HeightRequest="56"
VerticalOptions="Center"
HorizontalOptions="End"
Stroke="{StaticResource LightBorderColor}"
StrokeThickness="6"
Grid.Row="0"
Grid.Column="1">
<Border.StrokeShape>
<RoundRectangle CornerRadius="28"/>
</Border.StrokeShape>
<Image
VerticalOptions="Center"
HorizontalOptions="Center"
WidthRequest="50"
HeightRequest="50"
Source="profilepic.jpeg">
<Image.Clip>
<EllipseGeometry
Center="25,25"
RadiusX="25"
RadiusY="25"/>
</Image.Clip>
</Image>
</Border>
Next up is Search bar. This was bit of a dilemma for me. I started off with the built-in SearchBar control, but given my previous experience in Xamarin Forms (and same applies in Maui as well), it is very painstaking to customize the look and feel of SearchBar, so I decided to go another route. In Xamarin Forms I always ended up implementing custom renderers to make it look and feel the way I want, and even then I could never achieve a perfect result (especially on Android side). In most cases, I ended up creating my own custom control combining Xaml controls (such as entry, imagebutton, events etc.) to achieve the pixel-perfect result that I wanted for my apps. So, I decided to do the same and implement it in Xaml. This is not a functioning SearchBar at all, and only the design. Since this is just a UI design, the actual code implementation is outside the scope of this blog post. To make it work, you will need to replace the label with borderless entry and raise an event on pressing of search image or enter key press on the keyboard.
<!-- Search Box -->
<Border
Padding="12,4"
BackgroundColor="#33CCCCCC"
Stroke="{StaticResource BorderColor}"
HorizontalOptions="Fill"
Margin="0,0,24,0"
HeightRequest="48">
<Border.StrokeShape>
<RoundRectangle CornerRadius="6"/>
</Border.StrokeShape>
<Grid
HorizontalOptions="Fill"
VerticalOptions="Center"
ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<Image
WidthRequest="18"
HeightRequest="18"
Source="imgsearch.png"
Grid.Column="0"
VerticalOptions="Center" />
<Label
Text="Search for your favorite planet"
Grid.Column="1"
VerticalOptions="Center"
VerticalTextAlignment="Center"
Style="{StaticResource MenuLabelStyle}" />
</Grid>
</Border>
The Featured Planets and All Planets lists are implemented using Horizontal Layout CollectionViews.
<CollectionView.ItemsLayout>
<LinearItemsLayout
Orientation="Horizontal"
ItemSpacing="16" />
</CollectionView.ItemsLayout>
The specific design for cells is achieved using DataTemplate with Round-cornered rectangle (using Border control with shaded gradient) and placement of image/text within it.
<DataTemplate
x:DataType="models:Planet">
<Border
VerticalOptions="Fill"
WidthRequest="220"
StrokeThickness="0"
Background="{Binding Background}"
Padding="20">
<Border.StrokeShape>
<RoundRectangle CornerRadius="24"/>
</Border.StrokeShape>
<Grid
RowDefinitions="*,Auto,Auto"
RowSpacing="4">
<Image
Grid.Row="0"
Source="{Binding HeroImage}"
Rotation="-30"
Aspect="AspectFit"
VerticalOptions="Fill"
HorizontalOptions="Fill"/>
<Label
Grid.Row="1"
Style="{StaticResource FeaturedPlanetHeaderStyle}"
Text="{Binding Name}"
Margin="0,8,0,0"/>
<Label
Grid.Row="2"
Style="{StaticResource FeaturedPlanetHeaderSubtitleStyle}"
Text="{Binding Subtitle}" />
</Grid>
</Border>
</DataTemplate>
For the bottom tab menu bar, I have a confession to make. I am not a fan of Shell because of difficulty in customizing it's look and feel, as well as providing page specific menu items. So, I always implement my own custom bottom navigation bar in all my apps (with design, animations etc. as needed). This gives me much finer control at how I want the bottom menu bar to look and behave. The same is done over here with simple images and text laid out inside a grid, housed within a border control. But I am not advocating this route. If you are comfortable using Shell, please go ahead. Everyone has a different use case. For this implementation, this approach provides us with exact look and feel as per the original design.
<!-- Bottom Menu -->
<Border
Padding="16,0"
BackgroundColor="#393965"
Stroke="{StaticResource BorderColor}"
HorizontalOptions="Fill"
VerticalOptions="End"
Margin="0,0,0,20"
HeightRequest="90"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2">
<Border.StrokeShape>
<RoundRectangle CornerRadius="45"/>
</Border.StrokeShape>
<Grid
HorizontalOptions="Fill"
VerticalOptions="Center"
RowDefinitions="Auto,Auto"
ColumnDefinitions="*,*,*"
RowSpacing="6">
<Image
WidthRequest="30"
HeightRequest="30"
Source="imgexplore.png"
Grid.Row="0"
Grid.Column="0"
HorizontalOptions="Center" />
<Label
Text="Explore"
Grid.Row="1"
Grid.Column="0"
HorizontalOptions="Center"
Style="{StaticResource MenuLabelStyle}" />
<Image
WidthRequest="30"
HeightRequest="30"
Source="imgfavorite.png"
Grid.Row="0"
Grid.Column="1"
HorizontalOptions="Center" />
<Label
Text="Favorite"
Grid.Row="1"
Grid.Column="1"
HorizontalOptions="Center"
Style="{StaticResource MenuLabelStyle}" />
<Image
WidthRequest="30"
HeightRequest="30"
Source="imgprofile.png"
Grid.Row="0"
Grid.Column="2"
HorizontalOptions="Center" />
<Label
Text="Profile"
Grid.Row="1"
Grid.Column="2"
HorizontalOptions="Center"
Style="{StaticResource MenuLabelStyle}" />
</Grid>
</Border>
The Planet Details Page
Phew. We have almost made it. I must confess that this was the simplest of all pages to implement; no sweat at all. Not much to talk about here, except maybe a couple of things.
For divider lines, I decided to go with lighter-weight Rectangle control than the usual BoxView approach. I could have used Line shape itself, but I wanted to make the divider a bit thicker, so I went with the Rectangle shape.
<!-- Divider -->
<Rectangle
HorizontalOptions="Fill"
HeightRequest="2"
Stroke="Transparent"
Fill="{StaticResource LightBorderColor}" />
And for the list of images, I again used horizontally laid out CollectionView with custom DataTemplate. To keep the implementation simple, I used Image Clipping (using RoundRectangleGeometry) to provide rounded corners to the images. Else, I would have had to implement images inside a Grid or ContenView inside a Frame with ClippedToBounds set to true. This is a much simpler and cleaner implementation.
<DataTemplate>
<Image
Source="{Binding .}"
Aspect="AspectFill"
HorizontalOptions="Fill"
VerticalOptions="Fill"
WidthRequest="160">
<Image.Clip>
<RoundRectangleGeometry
Rect="0,0,160,160"
CornerRadius="16" />
</Image.Clip>
</Image>
</DataTemplate>
The Final Result
We have made it. Here is the final application in action. You can head over to Github to grab the source code and play with it yourself.
MauiPlanets.mp4 (1.02 MB) (in case it doesn't auto-play in the browser)