Styling controls
Theme is an ability to change the Color scheme of the app among some predefined set of Color schemes like Light, Dark, or any other set of Colors. We can achieve this by the use of Resources, ResourceDictionaries and Styles in Xamarin.Forms.
Resources: Resources are the common shared values across the app, which can highly scales the reusability of values, by reducing the hardcoded values in the app. Thus, when we change the Theme, we don’t have to change all the values but the defined set of Resources only.
ResourceDictionary: Resources are grouped together and stored in a ResourceDictionary. You can add multiple resources into one ResourceDictionary by using MergedDictionaries.
Styles: Styles are defined for the UI controls of your app to give it consistent look and feel throughout the app. If your app has a same look and feel for Textbox in all pages, you may not want to copy paste the UI definition of Textboxes on every pages, instead, you will define one style for that control and bind that style to that control in all pages.
Define Theme Colors as ResourceDictionary:
CustomTheme.xaml is the place where you can change the style of controls and define own colors and styles no need to do that in xaml pages. Keep your xaml clean, and be free to play with styles. Each style should have a unique key and target control/class type.
Color Definition
This is an override of base theme colors and will override color resouce in all xaml's
<Color x:Key="LightBlue">#75C041</Color>
<Color x:Key="EdgePurple">#E57D04</Color>
<Color x:Key="EdgeBlue">#368F54</Color>
Adding more Colors*
<Color x:Key="MyActiveButtonBorderColor">#75C041</Color>
<Color x:Key="MyActiveButtonTextColor">#E57D04</Color>
<Color x:Key="MyActiveButtonBackgroundColor">#368F54</Color>
Control color override
<!--Keyboard colors-->
<Color x:Key="PinKeyboardButtonBackgroundColor">#f5f5f5</Color>
<Color x:Key="PinKeyboardButtonTextColor">#368F54</Color>
This file contains Colors defined for different layouts and controls of your app. It’s up to you, how you define the names of Color Keys. But, better to use meaningful names. Once you define keys and values for one theme, simply copy paste them into other theme files and just change the values of Colors respective to that Theme file. Make sure that the number of Colors and their Key Names remain same in all theme files. When your app grows, you may introduce new Color keys and then copying this Key in all Theme files, with theme specific Color value.
DEM will switch among themes dynamically during Runtime, we have to bind Color resources as DynamicResource, so that while changing the theme dynamically, those bound Colors updates immediately. If you are Binding the Color at Page or Control level, make sure you bind it using DynamicResource as follows.
<Grid BackgroundColor=”{DynamicResource PrimaryColor}”/>
Define UI Styles as ResourceDictionary:
Here, you can define Styles for all your UI controls and Layouts as follows:
<!--Kyeboard button-->
<Style x:Key="KeyboardButton" TargetType="Button">
<Setter Property="FontSize" Value="25" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="VerticalOptions" Value="EndAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
<Setter Property="TextColor" Value="{StaticResource PinKeyboardButtonTextColor}"/>
<Setter Property="BackgroundColor" Value="{StaticResource PinKeyboardButtonBackgroundColor}"/>
</Style>
Style Binding: You can bind this Style to a Control on the page as follows:
<Button Text=”1” Style=”{StaticResource KeyboardButton}”/>
Font override
Add ttf file in Adroid and iOS projects and use it.
<OnPlatform x:TypeArguments="x:String" x:Key="NormalFont">
<On Platform="Android" Value="_DEM_Geomanist-Regular.ttf#" />
<On Platform="iOS" Value="Geomanist-Regular" />
</OnPlatform>
Use your Colors and Fonts in creating your own style
<!--Active Round Button-->
<Style TargetType="Button" x:Key="RoundButtonActive">
<Setter Property="FontFamily" Value="{StaticResource NormalFont}" />
<Setter Property="BorderRadius" Value="10" />
<Setter Property="BorderColor" Value="{StaticResource MyActiveButtonBorderColor}" />
<Setter Property="BorderWidth" Value="2" />
<Setter Property="BackgroundColor" Value="{StaticResource MyActiveButtonBackgroundColor}" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="MinimumHeightRequest" Value="48" />
<Setter Property="TextColor" Value="{StaticResource MyActiveButtonTextColor}" />
<Setter Property="VerticalOptions" Value="FillAndExpand" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
</Style>
In the end, we register visual styles above through the application
public override void ThemeLoading(ResourceDictionary resources)
{
ManuallyCopyThemes(new CustomTheme(), resources);
}
protected virtual void ManuallyCopyThemes(ResourceDictionary fromResource, ResourceDictionary toResource)
{
foreach (var item in fromResource.Keys)
{
toResource[item] = fromResource[item];
}
}
Control Templates
Xamarin.Forms 2.1.0 has introduced ControlTemplates and TemplatedPage. For the uninitiated or unaware, Control Templating is a mechanism that allows separation of the logical view hierarchy from the visual hierarchy. Another way to think of it is a template that produces the visual hierarchy for your control or page.
Control Templates are a great way to customize xamarin forms app’s view with themes and custom layouts. You can also use them to wrap your content and get a beautiful experience for your users. In this post, we will see how to use Control Templates to create custom layouts.
ControlTemplate is a property of TemplatedPage class. A ContentPage derives from TemplatedPage and can define its own control template to display its content. Similarly, ContentView drives from TemplatedView that has a ControlTemplate property. ContentPage and ContentView present the content using ContentPresenter.
The first step is to go to your App.xaml and add a ControlTemplate to your ResourceDictionary. The ControlTemplate allows you to provide the VisualElement’s you want displayed.
<ControlTemplate x:Key="LoginTemplate">
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20" Padding="20">
<Entry Text="{TemplateBinding BindingContext.Username}" Placeholder="Username" />
<Entry Text="{TemplateBinding BindingContext.Password}" Placeholder="Password" />
<Button Command="{TemplateBinding BindingContext.Command}" Text="Click Here To Log In" />
</StackLayout>
</ControlTemplate>
<Style TargetType="views:LoginPage" x:Key="LoginPageStyle">
<Style.Setters>
<Setter Property="ControlTemplate" Value="{StaticResource LoginTemplate}" />
</Style.Setters>
</Style>
The TemplatedPage has a ControlTemplate property which is being set via a Style named LoginPageStyle. Inside of the ControlTemplate there are items using a new syntax called TemplateBinding.
<Entry Text="{TemplateBinding BindingContext.Username}" Placeholder="Username" />
It should look similar because it works almost identically to a Binding, however the Source of the Template binding is always defined to be the Templated Parent, which in this case is the LoginPage.
Redefine a control’s UI
When a ControlTemplate is instantiated and assigned to the ControlTemplate property of a ContentView derived custom control, or a ContentPage derived page, the visual structure defined for the custom control or page is replaced with the visual structure defined in the ControlTemplate.
However, the controls that comprise this UI can be replaced by defining a new visual structure in a ControlTemplate, and assigning it to the ControlTemplate property of a page object.
DataTemplate
A view can be defined as a data template and associated with a view model type. Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. The content of the control is the view model instance, and the data template is used to visually represent it. This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.
Data templates are flexible and lightweight. The UI designer can use them to easily define the visual representation of a view model without requiring any complex code. Data templates are restricted to views that do not require any UI logic (code-behind).
The following example shows the AccountWidgetView custom component that is bound to a collection of Acounts. The view for each Account is defined by the ItemTemplate property.
<ctrl:ACarouselViewControl x:Name="carouselAccountWidgetView" Orientation="Horizontal" Position="{Binding Position}" InterPageSpacing="10" ItemsSource="{Binding CollectionData, Converter={StaticResource EnumerableNullConverter}}"
HeightRequest="170" AutomationId="AccountsCarouselTestUI" ItemTemplate="{StaticResource AccountWidgetItemTemplate}" ArrowsBackgroundColor="Transparent"
ShowIndicators="false" ShowArrows="True" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" >
</ctrl:ACarouselViewControl>
The AccountWidgetItemTemplate specifies that the view for each Account consists of a StackLayout containing multiple child elements, including several Labels.
<DataTemplate x:Key="AccountWidgetItemTemplate">
<StackLayout Padding="10,10,10,10" Margin="10,10,10,10">
<Label TextColor="Black" Text="Override AccountWidgetItemViewTemplate" FontAttributes="Bold"
HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
<Label TextColor="Black" Text="{Binding Value.Nickname}"
HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
<Label TextColor="Black" Text="{Binding Value.AccountNumber}"
HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
</StackLayout>
</DataTemplate>
Using CSS
The process for adding a style sheet to a solution is as follows:
- Add an empty CSS file to your .NET Standard library project.
- Set the build action of the CSS file to EmbeddedResource.
button {
background-color: yellow;
}
Loding CSS
In C#, a style sheet can be loaded from a Assembly and added to a ResourceDictionary:
resources.Add(StyleSheet.FromResource("Styles.css", GetType().Assembly));
Please update ThemeLoading
method in application with previous line adding to the end of the method
public override void ThemeLoading(ResourceDictionary resources)
{
ManuallyCopyThemes(new CustomTheme(), resources);
resources.Add(StyleSheet.FromResource("Styles.css", GetType().Assembly));
}
Custom Renderers
A Custom Renderer is an intermediary between the Xamarin Forms and the native level. The hooks (on each side) are called element and control properties. The element property provides a reference to the Xamarin Forms level control and the control property provides a hook to native level controls.
How to Implement a Custom Renderers
Custom Renderers are platform specific, hence you need to create a custom renderer for all platforms you want the control displayed on. You create a class that inherits from ViewRenderer and then you declare it with an assembly attribute ExportRenderer. Then create the Native Control in the OnElementChanged method that you override as demonstrated below.
[assembly: ExportRenderer(typeof(XamarinFormsControl), typeof(CustomRenderer))]
namespace Mobile.Droid.Renderers
{
public class CustomRenderer: ViewRenderer<XamarinFormsControl, NativeControl>
{
protected override void OnElementChanged(ElementChangedEventArgs<XamarinFormsControls> e)
{
base.OnElementChanged(e);
SetNativeControl(new NativeControl());
}
}
}
Export Renderer
DEM V2 all rederes from Device project declares in AssemblyInfo.cs
of the corespoding device project
iOS
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.AComboBox), typeof(AssecoSEE.DEM.Components.Controls.iOS.Renderers.AComboBoxRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.AEntry), typeof(AssecoSEE.DEM.Components.Controls.iOS.Renderers.AEntryRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.ContextMenuScrollView), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.ContextMenuScrollViewRenderer))]
[assembly: ExportRenderer(typeof(ContentPage), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomContentPageRenderer))]
[assembly: ExportRenderer(typeof(DatePicker), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomDatePickerRenderer))]
[assembly: ExportRenderer(typeof(Entry), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomEntryRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DialogMenuList), typeof(AssecoSEE.DEM.Devices.iOS.Renderers.CustomListViewRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.CustomMap), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomMapRenderer))]
[assembly: ExportRenderer(typeof(NavigationPage), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomNavigationRenderer))]
[assembly: ExportRenderer(typeof(Picker), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.CustomPickerRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DraggableListView), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.DragAndDropListViewRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DraggableViewCell), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.DragAndDropViewCellRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Components.GradientBoxView), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.GradientBoxViewRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Components.PinEntryField), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.PinEntryFieldRenderer))]
[assembly: ExportRenderer(typeof((AssecoSEE.DEM.Core.Pages.UsecaseTabedPage), typeof(AssecoSEE.DEM.Devices.iOS.Renderers.UsecaseTabbedPageRenderer))]
[assembly: ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.WebViewer), typeof(AssecoSEE.DEM.Framework.iOS.Renderers.WebViewRender))]
Droid
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.AComboBox), typeof(AssecoSEE.DEM.Components.Controls.Android.Renderers.AComboBoxRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.AEntry), typeof(AssecoSEE.DEM.Components.Controls.Android.Renderers.AEntryRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.AAutoSuggestBox), typeof(AssecoSEE.DEM.Components.Controls.Platform.Android.AutoSuggestBoxRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.ContextMenuScrollView), typeof(AssecoSEE.DEM.Framework.Android.Renderers.ContextMenuScrollViewRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(Xamarin.Forms.Button), typeof(AssecoSEE.DEM.Framework.Android.Renderers.CustomButtonRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DialogMenuList), typeof(AssecoSEE.DEM.Devices.Droid.Renderers.CustomListViewRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DraggableListView), typeof(AssecoSEE.DEM.Framework.Android.Renderers.DragAndDropListViewRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.DraggableViewCell), typeof(AssecoSEE.DEM.Framework.Android.Renderers.DragAndDropViewCellRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.CustomMap), typeof(AssecoSEE.DEM.Framework.Android.Renderers.CustomMapRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.FontAwesomeLabel), typeof(AssecoSEE.DEM.Framework.Android.Renderers.FontAwesomeLabelRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Components.GradientBoxView), typeof(AssecoSEE.DEM.Framework.Android.Renderers.GradientBoxViewRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.PancakeView), typeof(AssecoSEE.DEM.Framework.Android.Renderers.PancakeViewRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Components.PinEntryField), typeof(AssecoSEE.DEM.Framework.Android.Renderers.PinEntryFieldRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Core.Pages.UsecaseTabedPage), typeof(AssecoSEE.DEM.Devices.Droid.Renderers.UsecaseTabbedPageRenderer))]
[assembly: Xamarin.Forms.ExportRenderer(typeof(AssecoSEE.DEM.Components.Controls.WebViewer), typeof(AssecoSEE.DEM.Framework.Android.Renderers.WebViewerRender))]
Note
You need to add AssecoSEE.DEM.Components package to the device specific projectes.
You can add now one or you can replace above declaration in a solution.
Modifying a Custom Renderer
DEM V2 has plenty of renderers for all of its controls and it is always best to use these if possible.
Sometimes the DEM V2 renderers don’t provide a way to customize a specific property and you need to do this yourself via a Custom Renderer.
You can inherit from existing renderers, call base.OnElementChanged(e) to ensure the existing rendering completes, then add your customization declaration in above AssemblyInfo.cs
.
Note
Be sure to change tick for type configuration cache in Application
More info