To recap, I’m writing a shopping cart app for Windows Phone, Android, and iOS. The purpose of the app is primarily to let me use Forms. Each post will build on top of the previous one.
Last time I added an dependency injection framework. This week I want to make the app look a little less blah so I’ll be adding themes and styling.
Recap and Code
This is the seventh post in the series, you can find the rest here:
- Day 0: Getting Started (blog / code)
- Day 1: Binding and Navigation (blog / code)
- Day 2: Frames, Event Handlers, and Binding Bugs (blog / code)
- Day 3: Images in Lists (blog / code)
- Day 4: Search and Barcode Scanner (blog / code)
- Day 5: Dependency Injection (blog / code)
-
Day 6: Styling (blog / code)
- Day 7: Attached Behaviors (blog / code)
- Day 8: Writing to Disk (blog / code)
- Day 9: App and Action Bars (blog / code)
- Day 10: Native Views (blog / code)
For a full index of posts, including future posts, go to the GitHub project
page.
Styling Options in XF
The guidance in Xamarin’s documentation is to create custom renders for each
control you want to style for each platform. This seemed like an unintuitive
and complicated way to go about solving the problem so I avoided trying it as
long as possible.
@TheRealJoeRall suggested an interesting idea to try: use the native theming
options from each platform. On WindowsPhone add styles with a TargetType but
no key to the App.xaml. On Android create a theme in xml and apply it to the
top level activity. On iOS use the UIAppearance class. Like the content
render solution this approach requires writing a different theme for each
platform, but it uses the native approach meaning that there will be more
documentation and tooling around it. Now, let’s just hope it works.
Quick Reminder about iOS
Before I go any further, I want to put out a reminder that since I don’t have
access to iOS these articles won’t cover it. Which means that I was unable to
determine if the UIAppearance class would be able to help in styling a XF
application.
Styling Windows Phone
There’s lots of documentation on how to style and theme a native Windows Phone
app. Most of it written by people who know a lot more about the topic than I
do. I’m going to keep mine to the basics.
The first thing to know is that any styles you create in the App.xaml file will
be accessible from all of your content pages by default. But how do you create
a style in the first place?
<Application>
<Application.Resources>
<ResourceDictionary>
<Style TargetType="TextBlock" >
<Setter Property="Foreground" Value="Black"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
Above is a very simple style that sets the Foreground of all TextBlocks to
black. Again, this style can get much more complex, but I’ll leave it here for
now.
You could make this an explicit style by giving it a key,
x:key=”BlackTextBlock”
for example. This way it would only apply to TextBlocks
that specifically reference the style. But since all of our pages are
currently defined in the common Xamarin Forms layer, we don’t have TextBlocks
at all, and can’t reference explicit styles. So while explicit styles are
great when writing native Windows Phone pages, they are not very interesting in
XF.
Again, I could rant on this a lot longer, but I’m not the best source. My
style for WP is quite long but not much more than a basic example, so I won’t
bother with the snippet here. If you’re interested, you can view my full
App.xaml on GitHub.
Limitations With Using Windows Phone Styling
My goal with the styling was to create a white background. I could not figure
out a way to set the background color from within the Windows Phone project. I
tried several techniques that did not work.
First, I tried explicitly setting the color in the MainPage.xaml (the following
code is stripped down for size and clarity):
<phone:PhoneApplicationPage
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Background="White" />
This had no effect so I tried setting it programmatically in MainPage.xaml.cs.
I tried setting both the MainPage background as well as the navigation page
returned from the common layer. Neither worked, either separately or together.
I’ll show them all at once.
public MainPage()
{
this.Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.White);
var startupPage = ShoppingCart.App.StartupPage;
startupPage.BackgroundColor = Xamarin.Forms.Color.White;
Content = startupPage.ConvertPageToUIElement(this);
}
Digging down into the child page of the navigation page did work, but that had
two drawbacks. First it only worked for that one page, so I’d have to change
the color of all the pages. Second, it pushed me up into the common layer.
Since I was in the common layer anyway it would just be easier to set the
background page color directly in the XAML definitions of the pages. This is
ultimately what I did. Here’s an example from the Welcome page.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ShoppingCart.Views.WelcomePage"
BackgroundColor="White" >
<!-- Layout Removed -->
</ContentPage>
Note that I could have used Xamarin Form’s OnPlatform mechanism to selectively
set the property only for Windows Phone. I elected not to since I did want the
background to be white on all platforms anyway, even if I did not need to set
the property explicitly for Android to work.
Styling Android
Android also has a very simple way to set the overall style for all Activities.
They also have much better documentation than I could mimic. The key take away
here is to define your android theme in an xml file in the values folder of
your project. Then reference it in your AndroidManifest.xml file. The
quickest way I’ve found to create a theme is to use the online Holo Colors
Generator.
You just enter the color you want to use in your theme and tick off the
checkboxes of the controls you want this theme to apply to. It generates a zip
that you can extract directly into your resources directory. Just include all
of the files and set your theme in the AndroidManifest like this where AppTheme
is the the name of the theme in the themes_apptheme.xml file:
<application android:theme="@style/AppTheme" />
That’s it for styling droid. It’s pretty straight forward and took me about 15
minutes. The color I picked was a nice light green: #afcca6
.
Problems with This Approach
One major limitation with this approach is that it treats all things equally
All text boxes will look the same. If for example you wanted the text box at
the top of the product page to be a color based on the theme, you can’t easily
achieve that. You’d have to fallback to manually setting the various
properties correctly on each page.
Solution 1: Theme Class
For this specific case, I created an interface IThemer with a default
implementation that could be overriden for specific platforms. For now, the
only property exposed is AccentColor, but it could be expanded to provide other
style related properties.
public interface IThemer
{
Color AccentColor { get; }
}
public class DefaultThemer : IThemer
{
public Color AccentColor { get { return Color.Accent; } }
}
public class AppSetup
{
protected virtual void RegisterDepenencies(ContainerBuilder cb)
{
// ... Code removed for clarity
cb.RegisterType<DefaultThemer>().As<IThemer>().SingleInstance();
}
}
Originally, the DefaultThemer I created just returned a randomly generated dark
color and the Windows Phone implementation overrode it with the AccentColor
pulled from the app’s resource dictionary. Then while playing around I found
the Xamarin.Forms.Color.Accent property. On Windows Phone this returns the
phone’s accent color. On Droid it returns black. This is great in that it
gets me half way there, but I want to use my nice light green color I defined
in my droid styles: #afcca6
. So in the droid project I created an
implementation of IThemer and registered it with my dependency injection
container from last week:
public class DroidThemer : IThemer
{
public DroidThemer()
{
var resources = Forms.Context.Resources;
int colorId = Resource.Color.apptheme_color;
var color = resources.GetColor(colorId);
AccentColor = Color.FromRgba(color.R, color.G, color.B, color.A);
}
public Color AccentColor { get; private set; }
}
public class DroidSetup : AppSetup
{
protected override void RegisterDepenencies(ContainerBuilder cb)
{
base.RegisterDepenencies(cb);
cb.RegisterType<DroidLogger>().As<ILogger>().SingleInstance();
cb.RegisterType<DroidScanner>().As<IScanner>().SingleInstance();
cb.RegisterType<DroidThemer>().As<IThemer>().SingleInstance();
}
}
The code grabs the color as it is defined in my app’s theme and sets the
AccentColor property. Now if the color is ever changed in the theme, it will
propagate to my special theming class. Again, I simply override the existing
registration for the IThemer with the DroidThemer. AutoFac is smart enough to
remove the prior registration made in AppSetup.
Now the question becomes, where do I put this class? I could make the
BaseViewModel take an instance of it so that all of the views can easily know
where to access it. I don’t like this for a number of reasons. First and
foremost this is not view model information. It has nothing to do with the
state of the app or data, it’s purely view. Secondly, adding constructor
parameters on a base class is a headache to mange long term, especially when
you wind up having a lot of inheriting classes. You need to touch each child
class whenever those parameters change. This is definitely not the solution
for me.
My second idea was to expose the AccentColor property on the App class. This
really is a system wide app setting so the App class does make a certain amount
of sense. Besides, all of my views are already aware of it since that’s where
they are getting their view models. Also, it’s really quick to add it in this
one place. It’s not an ideal solution since I’m trying to NOT have App turn
into a dumping ground for properties (hence my clean up last week) but it’s so
quick and easy now, that I’ll go with it.
public static class App
{
private static IContainer _container;
public static void Init(AppSetup appSetup)
{
_container = appSetup.CreateContainer();
AccentColor = _container.Resolve<IThemer>().AccentColor;
}
public static Color AccentColor { get; private set; }
}
The init method pulls out the IThemer implementation and grabs the AccentColor
from it. After this I bind to the property from within the WelcomePage.
<Label Text="Welcome to The Store"
Font="Bold, Large"
HorizontalOptions="Center"
TextColor="{x:Static local:App.AccentColor}" />
I only used this solution in one place: WelcomePage. While it is good for
keeping all of the style definitions in one place I really only had one style
that I wanted to use: accent color. As it turned out Xamarin already did most
of the heavy lifting for me. Isn’t that always the case?
After finding out that I could just use the Color.Accent static color (I really
did find that very late in the game), I realized that there was a much simpler
way to solve the problem. On platforms that support the Color.Accent, use it,
on other platforms use my alternate color. This is easy to accomplish with the
OnPlatform
class. Here’s an example from my ProductsPage:
<Label Text="{Binding Product.Name}"
HorizontalOptions="Center"
Font="Bold,Large">
<Label.TextColor>
<OnPlatform x:TypeArguments="Color"
iOS="Accent"
WinPhone="Accent"
Android="#afcca6" />
</Label.TextColor>
</Label>
On WinPhone and iOS, the Accent color is used. On Droid, I fallback to my
custom light green color.
Pics or It Didn’t Happen
I was mostly concerned with the how-to aspect of this write up and avoided
screen shots throughout the post. But, now that the post is over, here’s what
the app looks like after my styling efforts.
Welcome Screen
Categories Screen
It’s starting to look just a little more polished.
Happy Coding.
this post was originally on the MasterDevs Blog