Posts Harmonic Interfaces with MonettelliUIKIT in Xamarin.Forms Vol 1
Post
Cancel

Harmonic Interfaces with MonettelliUIKIT in Xamarin.Forms Vol 1

When creating interfaces in Xamarin.Forms, most developers (and myself included) use the physical device or the emulator to give the appearance of the design that is being replicated, and what about the rest of the devices that exist around the world ?

From this question this publication is born, in my beginnings as a developer of Xamarin Native and Xamarin.Forms I focused on the C # language, as I was learning, I realized that the appearance of interfaces are also a fundamental complement in applications, therefore, I continue studying various UI/UX courses, and therefore, my comfort zone I extend it more and more. To date, I love creating beautiful applications, harmonizing each device (phones, tablets, desktops, etc.).


¿What are Harmonic interfaces?

Harmonic Interfaces is the balance of a group of controls with the dimensions of the devices that exist today.

Xamarin.Forms and its powerful Hot Reload, allow you to visualize in real time the process of the interfaces in XAML and C #, with this great tool, I did my practices to perfect my interfaces, and to present my results in this publication.

Harmonic Interface in action.


The harmonic layout

Xamarin.Forms has different layouts, each with a purpose, of all of them, the one indicated for me is GridLayout, because with RowDefinitions and ColumnDefinitions I can cleanly manage my controls, however what stands out is proportional mode, and that is where we will focus on this post.

GridLayout Structure


Harmonic proportions

The proportional mode is in relation to the dimensions of the device and the content involved in the interface, that is, if a certain row and/or column proportion is added to a control, it is adapted to each device.

Harmonic Proportions in action


¿How to use proportions in GridLayout?

For this, it is necessary to use a design tool(in my case Adobe XD), adding semi-transparent blocks of different colors(both for rows and columns) to all the spaces of your design, in order to cover the positions that the controls, remember that each design has its difficulty.

Using Proportions in Adobe XD

The Ing. Juan Carlos Ricalde Poveda(UI/UX Expert), recommends that the artboard(Adobe XD or another tool) be done with the smallest screen size(preferably Android Phones), this helps extend and not reduce the space taken up by the controls.

Device Sizes use case

In the “Prototype” section, we create the flow of the designs(if there are two or more interfaces).

Adobe XD "Prototype" section

In the “Share” section, in the settings part, select the “Development” mode, once the link is created, open in the default browser.


Note: To have a harmonic ratio, divide the row and/or column distance of a control by the total distance of the design itself.


Adobe XD “Share” section

Calculation of proportions in the link made for Adobe XD

In the Xamarin.Forms XAML, add Grid to encapsulate the different controls that your interface will have, together with the harmonic proportions that have been calculated, then with Grid.Colum and Grid.Row, we position the controls, if it occupies more than one space you can use Grid.ColumSpan or Grid.RowSpan, AND WUALÁ !, you already have a harmonic interface.


Note: Some controls will necessarily use “Auto” since their dimensions do not have to be changed, ahem (Label, Entry, etc).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 <Grid>

            <Grid.RowDefinitions>
                <RowDefinition Height=".175*" />
                <RowDefinition Height=".175*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width=".075*" />
                <ColumnDefinition Width=".85*" />
                <ColumnDefinition Width=".075*" />
            </Grid.ColumnDefinitions>


             <Image
                Grid.Row="0"
                Grid.RowSpan="2"
                Grid.Column="0"
                Grid.ColumnSpan="3"
                Aspect="AspectFill"
                Source="ItalianFood.png" />

             <Label
                Grid.Row="2"
                Grid.Column="0"
                Grid.ColumnSpan="2"
                Text="Hello Monettelli UIKIT"
                Style="{StaticResource TxtHeadLine4_1}" />

        </Grid>

Margin and Padding you will rarely see involved, because it generates “Absolute Distances”, the following code is a clear example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    <Grid>

            <Grid.RowDefinitions>
                <RowDefinition Height=".35*" />
                <RowDefinition Height=".65*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width=".075*" />
                <ColumnDefinition Width=".85*" />
                <ColumnDefinition Width=".075*" />
            </Grid.ColumnDefinitions>


             <Image
                Grid.Row="0"
                Grid.Column="1"
                Aspect="AspectFill"
                Source="ItalianFood.png" />

        </Grid>

MonettelliUIKIT, Shell and MVVM on stage

MonettelliUIKIT implements the “Clean UI Style Architecture”, which reduces the time of developing beautiful interfaces and maintaining them, then I explain the process step by step.

MonettelliUIKIT


a) Use of embedded Fonts and SVG’s

In my publication Creating a clean Style Library for Xamarin.Forms I write about how to customize FontIcons and add fonts themselves, now with Embedded Fonts, it simplifies the use of such fonts, the next versions of MonettelliUIKIT will have this feature, considerably reducing the code and at the same time making us productive.


“Embedded Fonts” in AssemblyInfo.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]


#region FontIcons Embedded 
[assembly: ExportFont("monettelliuikitfonticons.ttf", Alias = "MonettelliFontIcons")]
#endregion

#region FontFamily Embedded 
[assembly: ExportFont("SourceSansPro-Bold.ttf", Alias = "SourceSansPro_Bold")]
[assembly: ExportFont("OpenSans-ExtraBold.ttf", Alias = "OpenSans_ExtraBold")]
[assembly: ExportFont("OpenSans-Bold.ttf", Alias = "OpenSans_Bold")]
[assembly: ExportFont("OpenSans-SemiBold.ttf", Alias = "OpenSans_SemiBold")]
[assembly: ExportFont("OpenSans-Regular.ttf", Alias = "OpenSans_Regular")]
#endregion

On the other hand, SVG’s are already embedded, thanks to the Nuget package called Xamarin.FFImageLoading.Svg.Forms.

1
2
3
4
 <ffSvg:SvgCachedImage
                Grid.Row="0"
                BackgroundColor="#F5CEB8"
                Source="resource://XF_Demo.SVGImages.svg_bg_food.svg" />

b) MVVM Pattern

In the following image we can see the flow of the MVVM Pattern.

MVVM Pattern by Gill Cleeren

1.- The “Models” folder is part of the business logic, the models are made up of Full Property and inherit INotifyPropertyChanged, the other class houses the data repository.


Model Structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Exercise : INotifyPropertyChanged
{

        private Guid _id_Exercise;
        private string _name_Exercise;

        public Guid Id_Exercise
        {
            get => _id_Exercise;
            set
            {
                _id_Exercise = value;
                RaisePropertyChanged(nameof(Id_Exercise));
            }
         }

        public string Name_Exercise
        {
            get => _name_Exercise;
            set
            {
                _name_Exercise = value;
                RaisePropertyChanged(nameof(Name_Exercise));
            }
         }


        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
 }

Structure of the Model Repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public static class ExerciseRepository
 {
        static ExerciseRepository()
        {
            if (Exercises == null)
            {
                Exercises = new List<Exercise>
                {
                    new Exercise
                    {
                        Id_Exercise = Guid.Parse("{70822596-265D-49E3-8CCC-CD996093E601}"),
                        Name_Exercise = "Diet Recommendation",
                        Image_Exercise = "resource://XF__EmbeddedSVG.SVGImages.svg_hamburger.svg",
                        Duration_Exercise = "15-55 MIN Course",
                        Description_Exercise = "Learn how to Create Your Clients(or your)perfect diet and nutrition plan based on their goals, preferences and lifestyle"
                    }
                };
            }
        }

        public static List<Exercise> Exercises { get; set; }
 }

2.- The “Services” folder has two services, a flexible navigation class, and a Data Services class, each inheriting its respective Interface.


Navigation Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class NavigationService : INavigationService
  {
        private Dictionary<string, Type> pages { get; } = new Dictionary<string, Type>();

        public Page MainPage => Application.Current.MainPage;

        public void Configure(string key, Type pageType) => pages[key] = pageType;

        public void GoBack() => MainPage.Navigation.PopAsync();

        public void NavigateTo(string pageKey, object parameter = null)
        {
            if (pages.TryGetValue(pageKey, out Type pageType))
            {
                var page = (Page)Activator.CreateInstance(pageType);
                page.SetNavigationArgs(parameter);

                MainPage.Navigation.PushAsync(page);

                (page.BindingContext as MyBaseViewModel).Initialize(parameter);
            }
            else
            {
                throw new ArgumentException($"This page doesn't exist: {pageKey}.", nameof(pageKey));
            }
        }
    }

    public static class NavigationExtensions
    {
        private static ConditionalWeakTable<Page, object> arguments = new ConditionalWeakTable<Page, object>();

        public static object GetNavigationArgs(this Page page)
        {
            object argument;
            arguments.TryGetValue(page, out argument);

            return argument;
        }

        public static void SetNavigationArgs(this Page page, object args)
            => arguments.Add(page, args);
    }

Data Services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ExerciseDataService : IModelDataService<Exercise>
 {
        public void Add(Exercise model)
        {
            throw new NotImplementedException();
        }

        public List<Exercise> GetAll()
        {
            return ExerciseRepository.Exercises;
        }

        public void Remove(Exercise model)
        {
            throw new NotImplementedException();
        }

        public void Update(Exercise model)
        {
            throw new NotImplementedException();
        }
 }

3.- As a Singleton, the Services are managed throughout the project(mainly in the ViewModels).


Singleton in App.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public partial class App : Application
 {
        public static ExerciseDataService ExerciseDataService { get; } = new ExerciseDataService();
        public static SessionDataService SessionDataService { get; } = new SessionDataService();
        public static NavigationService NavigationService { get; } = new NavigationService();
        public App()
        {
            InitializeComponent();

            NavigationService.Configure(ViewNames.ExercisePage, typeof(ExercisePage));
            NavigationService.Configure(ViewNames.ExerciseDetailPage, typeof(ExerciseDetailPage));

            MainPage = new AppShell();
            //MainPage = new NavigationPage(new ExercisePage());
        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
 }

4.- The “Helpers” folder, add an Enum Type called “ThemeType”, where you have the different Themes, the ThemeHelper class inherits said Enum, it should be noted that its use is through the method of a command that will be created in the ViewModel.


ThemeType.cs:

1
2
3
4
5
 public enum ThemeType
 {
        Light,
        Dark
 }

ThemeHelper.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class ThemeHelper
{
        public static void ChangeTheme(ThemeType themeType)
        {
            if (Application.Current.Resources != null)
                Application.Current.Resources.Clear();


            switch (themeType)
            {
                case ThemeType.Light:
                    Application.Current.Resources = new LightTheme();
                    break;
                case ThemeType.Dark:
                    Application.Current.Resources = new DarkTheme();
                    break;
            }
        }
}

5.- The “ViewModels” folder has a Base Class for each ViewModel.


MyBaseViewModel.cs:

1
2
3
4
5
6
7
8
 public class MyBaseViewModel : BaseViewModel
 {
        public virtual void Initialize(object parameter)
        {

        }
 }


ViewModel Structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class ExerciseViewModel : MyBaseViewModel
 {
        public ThemeType SelectedTheme;
        public ICommand ExerciseSelectedCommand { get; }
        public ICommand ThemeSelectedCommand { get; }

        private ObservableRangeCollection<Exercise> _exercises;

        public ObservableRangeCollection<Exercise> Exercises
        {
            get => _exercises;
            set
            {
                _exercises = value;
                OnPropertyChanged(nameof(Exercises));
            }
        }

        private Exercise _selectedExercise;

        public Exercise SelectedExercise
        {
            get { return _selectedExercise; }
            set { SetProperty(ref _selectedExercise, value); }
        }

        public ExerciseViewModel()
        {
            Exercises = new ObservableRangeCollection<Exercise>(App.ExerciseDataService.GetAll());

            ExerciseSelectedCommand = new Command(OnExerciseSelectedCommand);

            ThemeSelectedCommand = new Command(OnThemeSelectedCommand);
        }

        private void OnThemeSelectedCommand()
        {
            if(SelectedTheme == ThemeType.Light)
            {
                SelectedTheme = ThemeType.Dark;
            }
            else
            {
                SelectedTheme = ThemeType.Light;
            }

            ThemeHelper.ChangeTheme(SelectedTheme);
        }

        private void OnExerciseSelectedCommand()
        {
            if (SelectedExercise != null)
            {
                App.NavigationService.NavigateTo(ViewNames.ExerciseDetailPage, SelectedExercise);

                SelectedExercise = null;
            }
        }
 }

6.- The “Utilities” folder has two classes, a ViewModelLocator(useful for unit tests) that facilitates the replacement of the ViewModel in each View, and a ViewNames that names each View.


ViewModelLocator Structure:

1
2
3
4
5
6
7
8
 public static class ViewModelLocator
 {
        public static ExerciseViewModel ExerciseViewModel { get; set; } 
            = new ExerciseViewModel();

        public static ExerciseDetailViewModel ExerciseDetailViewModel { get; set; }
            = new ExerciseDetailViewModel();
 }

ViewNames.cs:

1
2
3
4
5
public class ViewNames
{
        public const string ExercisePage = "ExercisePage";
        public const string ExerciseDetailPage = "ExerciseDetailPage";
}

7.- In the C# code-behind file of the View, through the BindingContext property, we link the ViewModelLocator.


ViewModelLocator in the C# code-behind file:

1
2
3
4
5
6
7
8
9
public partial class ExercisePage : ContentPage
{
        public ExercisePage()
        {
            InitializeComponent();

            BindingContext = ViewModelLocator.ExerciseViewModel;
        }
}

c) XAML Harmonic

Now that you have a clear vision of what interface harmony is all about, let’s apply what we have learned in the following design.

Exercise App Concept by Shahidul Islam Shishir

Making a feedback, we analyze the design(s) in Adobe XD, we add multi-colored blocks to the rows and columns, we prototype and create the link that will help to see the distances of these blocks, and then calculate the proportions of each design.

"Harmonic Proportions" in the Concept of Exercise Application

Using the GridLayout, we complement these proportions in Row(Colum)Definitions, and with the help of the design, we position the controls, then through StaticResource/DynamicResource, we incorporate the styles assigned in Styles.xaml.


Complete Structure of a Style with OnIdiom:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <Style
        x:Key="TxtHeadLine4_1"
        TargetType="Label">
        <Setter Property="TextColor" Value="{DynamicResource colSec}" />
        <Setter Property="FontSize">
            <OnIdiom
                x:TypeArguments="x:Double"
                Watch="{StaticResource TxtSizeCap_12}"
                Phone="{StaticResource TxtSizeH4_34}"
                Desktop="{StaticResource TxtSizeH3_48}"
                Tablet="{StaticResource TxtSizeH3_48}"
                TV="{StaticResource TxtSizeH3_48}" />
        </Setter>
        <Setter Property="FontFamily" Value="OpenSans_ExtraBold" />
    </Style>

d) Shell in action

MonettelliUIKIT comes with Shell, and with AppShell.xaml you can add Views, easily modify colors, labels, images, etc. It practically makes life easier for the developer.


e) Final Score

If you have applied each step, the result will be similar to this one.

Harmony of interfaces made in Xamarin.Forms


Conclusions

The use of harmonic proportions is one of many methods to stylize interfaces, make them flexible, and above all, provide a good user experience on each device.

MonettelliUIKIT is a great ally of the UI/UX in Xamarin.Forms and I hope it is also in the new .NET MAUI.


Publication in Spanish

Interfaces Armónicas con MonettelliUIKIT en Xamarin.Forms Vol 1


Resources


Github Repository

danielmonettelli/XF_HarmonicInterfaces


Sketch App Sources

Exercise App Concept Sketch Resource

This post is licensed under MIT License by the author.