Showing posts with label Xamarin. Show all posts
Showing posts with label Xamarin. Show all posts

Wednesday, 14 November 2018

Droid Themes

You can think of your Droid theme as a global CSS file for you android project, its the perfect place to modify the global styling of your application. To check which theme is applied by default to you android project open "MainActivity.cs" file in your project root.


now when you open your "MainActivity.cs" file you can identify which default theme you have assigned to your android application.

using Android.App;
using Android.Content.PM;
using Android.OS;

namespace pav.formEffects.Droid
{
    [Activity(Label = "pav.formEffects",
              Icon = "@mipmap/icon",
              Theme = "@style/MainTheme",
              MainLauncher = true,
              ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
        }
    }
}

We can see from the Activity attribute on your Main Activity class that we are using our MainTheme; to see our main theme you can check under the resources/values/styles.xml file. Here you'll see something like the following:

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <style name="MainTheme" parent="MainTheme.Base">
  </style>
  <!-- Base theme applied no matter what API -->
  <style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
    <!--If you are using revision 22.1 please use just windowNoTitle. Without android:-->
    <item name="windowNoTitle">true</item>
    <!--We will be using the toolbar so no need to show ActionBar-->
    <item name="windowActionBar">false</item>
    <!-- Set theme colors from http://www.google.com/design/spec/style/color.html#color-color-palette -->
    <!-- colorPrimary is used for the default action bar background -->
    <item name="colorPrimary">#2196F3</item>
    <!-- colorPrimaryDark is used for the status bar -->
    <item name="colorPrimaryDark">#1976D2</item>
    <!-- colorAccent is used as the default value for colorControlActivated
         which is used to tint widgets -->
    <item name="colorAccent">#FF4081</item>
    <!-- You can also set colorControlNormal, colorControlActivated
         colorControlHighlight and colorSwitchThumbNormal. -->
    <item name="windowActionModeOverlay">true</item>
    <item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
  </style>

  <style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">#FF4081</item>
  </style>
</resources>

now go ahead and add the following to your MainTheme.

<style name="MainTheme" parent="MainTheme.Base">
  <item name="colorPrimary">#FF0000</item> <!--Red-->
  <item name="colorPrimaryDark">#00FF00</item> <!--Green-->
  <item name="colorAccent">#00FFFF</item> <!--Aqua-->
  <item name="android:textColor">#FFFF00</item> <!--yellow-->
  <item name="android:textColorPrimary">#FF00FF</item> <!--fuchsia-->
  <item name="android:windowBackground">@color/launcher_background</item>
  <item name="colorButtonNormal">#FF0000</item>
  <item name="colorControlHighlight">@color/colorAccent</item>
</style>

with just some simple changes like the ones above we can transform our normal look & feel.


Tuesday, 3 July 2018

Xamarin Master-Detail page

Thanks to the Xamarin team this is very simple to accomplish, however I really don't like the way the pages are dumped into the same folder, i don't like the way they are named and i don't like the model and view models are added to the project, so let's get started. First create a Xamarin application and in your shared code wheather that's a shared project or a .net Standard library create folder for Models, Views and ViewModels.


now that you have those laid out add a new "Master Detail Page" to your views folder.



Now this will create four files, in your views folder.


Now this is what i don't like:
  • Firstly the PageMenuItem.cs is a model, not view and I don't like it's naming convention, 
    • I rename it to BurgerMenuItem.cs 
    • Move it into my Models folder
    • Update its namespace to Models
    • Include the missing using statement
  • Next You'd think that PageMaster is the master page, well it's not; it actually represents the Burger menu, 
    • I  rename it to MenuPage.
      • rename the file
      • rename the code-behind
      • rename the xaml markup
    • In its code-behind you'll see that the view-model is included
      • i create a separate file in my view-models folder rename it to MenuPageViewModel and include all of the missing using statements. 
      • I also extract the INotifyPropertyChanged implementation into an abstract base class called BaseViewModel
      • I Inherit from BaseViewModel in my MenuPageViewModel
      • I also make some modifications to the notification code
  • Next let's look at the Page.xaml file, this is in fact the masterpage
    • I Rename it to MasterPage.xaml
    • I change the markup to MasterPage
    • In the markup i change the name of the MenuPage to MenuPage
    • I change the codebehind to MasterPage
    • in the codebehind i change the reference to the MenuPage to the name i gave it.
  • Now the PageDetail is actually the contentPage that loads, so i like to give it a more meaning full name, in this case i'll call it GamePage.
  • Finally i open the App.xaml page and set my apps MainPage to MasterPage
When all is said and done my project looks like so.

So let's look at the BurgerMenuItem.cs class

using System;
using pav.tictactoe.Views;

namespace pav.tictactoe.Models
{
    public class BurgerMenuItem
    {
        public BurgerMenuItem() => TargetType = typeof(GamePage);
       
        public int Id { get; set; }
        public string Title { get; set; }
        public Type TargetType { get; set; }
    }
}

No great leaps of faith here, it's basically the same as before, just changed the constructor a bit for the sake of brevity.

Next let's look at our BaseViewModel.cs

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace pav.tictactoe.ViewModels
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Implementation

        bool SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(backingField,value))
                return false;
            backingField = value;

            NotifyPropertyChanged(propertyName);
            return true;
        }

        void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (!String.IsNullOrEmpty(propertyName))
                  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }
}

also nothing special here, this code has been around since wpf, just creates a generic setter for bindable fields and returns a Boolean letting you now if the backing field was updated or not and notifies any bound controls via the Observable Pattern.

next let's take a look at the MenuPageViewModel.cs file 

using pav.tictactoe.Models;
using pav.tictactoe.Views;
using System.Collections.ObjectModel;

namespace pav.tictactoe.ViewModels
{
    public class MenuPageViewModel : BaseViewModel 
    {
        public ObservableCollection<BurgerMenuItem> MenuItems { get; set; }

        public MenuPageViewModel() 
        {
            MenuItems = new ObservableCollection<BurgerMenuItem>(new[] {
                new BurgerMenuItem { Id = 0, Title = "Game", TargetType= typeof(GamePage) },
                new BurgerMenuItem { Id = 1, Title = "Settings" },
                new BurgerMenuItem { Id = 2, Title = "Stats" }
            });
        }
    }
}

also no rocket science here, i did include the TargetType for the first Menu item to illustrate how you'd set up the navigation to a page; later we'll add a settings and stats page.

Next we'll take a look at the game page only because we have to update the class name in two places and because we're just going to leave it as a place holder it'll be easiest to show where, you'll have to do the same for the MasterPage and the MenuPage.

firstly in the xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="pav.tictactoe.Views.GamePage"
             Title="Tic Tac Toe">
    <StackLayout>
        <Label Text="This is test data" />
    </StackLayout>

</ContentPage>

In the above snippet make sure to update the x:class path to your new page name

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace pav.tictactoe.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class GamePage : ContentPage
    {
        public GamePage() => InitializeComponent();
    }

}

and make sure that it's aligned with the Class name in the codebehind

Now the Menu page, this is

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="pav.tictactoe.Views.MenuPage"
             Title="Master">
  <StackLayout>
    <ListView x:Name="MenuItemsListView"
              SeparatorVisibility="None"
              HasUnevenRows="true"
              ItemsSource="{Binding MenuItems}">
      <ListView.Header>
        <Grid BackgroundColor="#03A9F4">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="10"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="80"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
          </Grid.RowDefinitions>
          <Label
              Grid.Column="1"
              Grid.Row="2"
              Text="AppName"
              Style="{DynamicResource SubtitleStyle}"/>
        </Grid>
      </ListView.Header>
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout Padding="15,10" HorizontalOptions="FillAndExpand">
              <Label VerticalOptions="FillAndExpand"
                    VerticalTextAlignment="Center"
                    Text="{Binding Title}"
                    FontSize="24"/>
            </StackLayout>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>

</ContentPage>

This page represents the Slider for the Hamburger Menu


and again make sure to update it in the codebehind

using pav.tictactoe.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace pav.tictactoe.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MenuPage : ContentPage
    {
        public ListView ListView;

        public MenuPage()
        {
            InitializeComponent();
            BindingContext = new MenuPageViewModel();
            ListView = MenuItemsListView;
        }
    }

}

And finally let's take a look at the MasterPage

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="pav.tictactoe.Views.MasterPage"
             xmlns:pages="clr-namespace:pav.tictactoe.Views">
    <MasterDetailPage.Master>
        <pages:MenuPage x:Name="MenuPage" />
    </MasterDetailPage.Master>
    <MasterDetailPage.Detail>
        <NavigationPage>
            <x:Arguments>
                <pages:GamePage />
            </x:Arguments>
        </NavigationPage>
    </MasterDetailPage.Detail>

</MasterDetailPage>

here we have a couple things to update:

  • Updated that the class name to MasterPage
  • Update the MenuPage and give it a more descriptive name 
  • Update the detailpage to GamePage

next take a look into the code behind

using pav.tictactoe.Models;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace pav.tictactoe.Views 
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MasterPage : MasterDetailPage 
    {
        public MasterPage() 
        {
            InitializeComponent();
            MenuPage.ListView.ItemSelected += ListView_ItemSelected;
        }

        private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e) 
        {
            var item = e.SelectedItem as BurgerMenuItem;
            if (item == null)
                return;

            var page = (Page)Activator.CreateInstance(item.TargetType);
            page.Title = item.Title;

            base.Detail = new NavigationPage(page);
            base.IsPresented = false;
           
            MenuPage.ListView.SelectedItem = null;
        }
    }

}

In the codebehind again you have to update the class name but also the MenuItem cast to whatever we renamed our Model to, in this case BurgerMenuItem.

And finally let's open our App.Xaml.cs class

using pav.tictactoe.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace pav.tictactoe 
{
    public partial class App : Application 
    {
              public App () 
              {
                 InitializeComponent();
                 MainPage = new MasterPage();
              }

              protected override void OnStart () { /* Handle when your app starts */ }
              protected override void OnSleep () { /* Handle when your app sleeps */ }
              protected override void OnResume () { /* Handle when your app resumes */ }
       }
}


Here in our constructor we simply set the page we want our application to set as it's MainPage.

this is involved and easy to screw up; however working though this exercise gave me a much better understanding how the burger layout works, at least at the superficial level of Xamarin forms.