sobota, 21 lutego 2015

Kulisy Xamarina - odc. 6: Xamarin.Forms cz.2

Książka, o której wspominałem ostatnio, na obecny moment mogę uznać za przyczytaną (co tydzień pojawiają się nowe rozdziały w wersji preview). Widać kunszt autora, dbałość o szczegółowe wyjaśnienie zagadnień, a jednoczesne stopniowe dawkowanie wiedzy, co czasami może niecierpliwić.  Wynotowałem kolejne rzeczy, które chciałem jakoś utrwalić. Pasuje też niektóre z nich okrasić jakimiś komentarzami. Oto i one:

  • brak koncepcji marginesu - pierwsza poznana przeze mnie technologia UI,  w której twórcy udostępniają wyłącznie padding
  • obsługa przechowywania stanu aplikacji - bardzo prosta obsługa  i na wszystkie platformy
  • nie wszystko działa na wszystkich platformach np. zaokrąglenia na przyciskach w WP są ignorowane  - no cóż, czasami zdarzają się pewne ograniczenia, ale zasadniczo i tak wiele otrzymujemy
  • XAML - naprawdę kawał dobrej roboty, cross-platformowy XAML ma się lepiej niż myślałem, wspiera nawet częściowo nowszą specyfikację tę z 2009 (pamiętam jak była ogłaszana przy pokazach związanych z WF 4), dużo rzeczy jak we frameworkach XAML Microsoftu np. bindingi, markup extensions (w tym własne), słowniki… DynamicResource mieliśmy do tej pory jedynie w WPF, a tu proszę.  Ostatnio twórcy chwalą się już triggerami i behaviorami…
  • gesty dotykowe  - gesture recognizer kojarzy mi się z jakimś starym WP Toolkitem, tutaj tylko mamy proste zdarzenia dotykowe
  • obsługa różnic między platformami - mamy wszystko co trzeba:  proste zróżnicowanie deklaratywnie w XAML, w kodzie możemy natomiast dostarczać platformowe implementacje do zaproponowanego wcześniej przez siebie interfejsu (korzystamy z DependencyService używającego refleksji lub robimy własny mechanizm w oparciu o dependecy injection)

 

Rozmiary

Specyficzne dla platformy niezależne od urządzenia unity

  • iOS, Android - 160 unit / cal
  • WP - 240 unit / cal

Rozmiary na WP ok. 150% większe od rozmiarów na iPhone i Android

VisualElement

  • event SizeChanged (jedyny sposób na wykrycie zmiany orientacji ekranu bez używania API specyficznego dla danej platformy)
  • protected virtual OnSizeAllocated

Blokada zmiany orientacji

  • iOS:  Info.plist (iPhone Deployment Info –> Supported Device Orientations)
  • Android:  ScreenOrientation w atrybucie  Activity nad aktywnością 

Skalowanie tekstu w ustawieniach Androida

Wysokość linii

  • iOS:  1.2 * label.FontSize
  • Android:  1.2  * label.FontSize
  • WP:   1.3  * label.FontSize

Szerokość znaku:   0.5 * label.FontSize

Brak koncepcji marginesu

Device.StartTimer(TimeSpan.FromSeconds(1),  () =>

{

        …

        return true;

};

SizeRequest sizeRequest = label.GetSizeRequest(width, Double.PositiveInfinity);

Przyciski

Button

  • FontFamily
  • FontSize
  • FontAttributes
  • TextColor
  • BorderColor
  • BorderWidth
  • BorderRadius
  • Image
  • IsEnabled
  • StyleId

Nie wszystko działa na wszystkich platformach:

  • iPhone: BorderWidth musi mieć dodatnią wartość
  • Android: BackgroundColor musi być ustawiony, BorderColor - ustawiony na wartość inną niż domyślna, BorderWidth - ustawiony na wartość dodatnią
  • WP:  BorderRadius nie działa

var button = new Button { Text = “XXX” };

button.Clicked += OnButtonClicked;

void OnButtonClicked(object sender, EventArgs args)

{

}

Rodzic w drzewie wizualnym:

button.ParentView

Dziecko w drzewie wizualnym:

outerLayout.Children[1]

Persystencja stanu aplikacji:

Application

  • Properties: słownik klucz-wartość  (kombinacja ustawień i danych tymczasowych typowych dla danej strony)
  • OnStart, OnSleep, OnResume: metody protected virtual

Application.Current.Properties[“xxx”] = “yyy”;

OnSleep

  • trzeba brać pod uwagę, że może to zakończyć się zamknięciem
  • wyzwolenie:  przycisk Start

OnResume

  • wyzwolenie:  wybór aplikacji z menu start (iOS i Android),  przycisk Back (Android i WP)
  • system automatycznie przywraca stan aplikacji
  • czasami czyszczenie słownika
  • przywracanie połączenia sieciowego lub pobranie najnowszych danych

Przełączanie między aplikacjami:

  • iOS:  podwójne naciśnięcie przycisku Home
  • Android:  naciśnięcie przycisku Multitask (lub przytrzymanie Home na starszych urządzeniach)
  • WP: przytrzymanie klawiszu Back

Przechowywanie danych do zapisania w obiekcie aplikacji (słownik Properties dostępny już w konstruktorze, w którym tworzona jest też główna strona, w jej konstruktorze możemy odczytać dane, a zapisywać jako reakcję na zdarzenia)

Instancja aplikacji musi być pobierana za każdym razem (w Androidzie może się zmienić podczas wykonywania aplikacji)

 

XAML

Możliwe używanie bindingów

Nie ma narzędzi designerskich

<Label Text="Hello from XAML!" 

       IsVisible="True"

       Opacity="0.75"

       XAlign="Center"

       VerticalOptions="CenterAndExpand"

       TextColor="Blue"

       BackgroundColor="#FF8080"

       FontSize="Large"

       FontAttributes="Bold,Italic" />

Przy solution z portable library w tym projekcie dodajemy strony:

Add New Item –> Visual C# –> Code –> Forms Xaml Page

<ContentPage xmlns=http://xamarin.com/schemas/2014/forms

                     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 

                     x:Class="CodePlusXaml.CodePlusXamlPage">

</ContentPage>

public class App : Application

{

     public App()

    {

            MainPage = new CodePlusXamlPage();   //tak samo jak przy całej stronie w C#

     }

      …

}

Dzięki implementacji części specyfikacji XAML z 2009 możemy zdefiniować typ dla klasy generycznej i definiować wartości uzależnione od platform:

<OnPlatform x:TypeArguments=”Thickness”

              iOS = “0, 20, 0, 0”

              Android=”0”

              WinPhone = “0” />

Typy podstawowe ze specyfikacji XAML z 2009:

  • x:Object
  • x:Boolean
  • x:Byte
  • x:Int16
  • x:Int32
  • x:Int64
  • x:Single
  • x:Double
  • x:Decimal
  • x:Char
  • x:String
  • x:TimeSpan
  • x:Array

<Label VerticalOptions="CenterAndExpand">

       <Label.FormattedText>

              <FormattedString>

                     <Span Text="A single line with " />

                     <Span Text="bold" FontAttributes="Bold" />

               </FormattedString>

         </Label.FormattedText>

   </Label>

Konstruktory z parametrami (XAML 2009):

<Color>

        <x:Arguments>

                    <x:Double>1</x:Double>

                    <x:Double>0</x:Double>

                    <x:Double>0</x:Double>

         </x:Arguments>

</Color>  

Wywoływanie publicznych statycznych metod tworzących obiekt klasy, w której są zdefiniowane  [metody tworzące / fabryczne] (XAML 2009):

       <Color x:FactoryMethod="FromRgb">

                <x:Arguments>

                         <x:Double>0</x:Double>

                         <x:Double>1.0</x:Double>

                         <x:Double>0</x:Double>

                </x:Arguments>

        </Color>

OnPlatform:

<OnPlatform x:TypeArguments="View">

         <OnPlatform.iOS>

               <Label Text="Kocham iOS"

                   HorizontalOptions="Center"

                   VerticalOptions="Center" />

         </OnPlatform.iOS> 
       <OnPlatform.Android>

             <Label Text="Kocham Androida"

                    HorizontalOptions="Center"

                    VerticalOptions="Center" />

         </OnPlatform.Android> 
       <OnPlatform.WinPhone>

             <Label Text="Kocham Windows Phone"

                    HorizontalOptions="Center"

                    VerticalOptions="Center" />

         </OnPlatform.WinPhone>

   </OnPlatform>

<Label x:Name="deviceLabel"

            HorizontalOptions="Center"

           VerticalOptions="Center">

         <Label.Text>

             <OnPlatform x:TypeArguments="x:String"

                        iOS="Kocham iOS"

                         Android="Kocham Androida"

                         WinPhone="Kocham Windows Phone" />

         </Label.Text>

  </Label>

 

Własna kontrolka

Add –> New Item –> Visual C# > Code –> Forms Xaml Page i zmieniamy ręcznie ContentPage na ContentView

Własna przestrzeń nazw:

xmlns:local="clr-namespace:MyApp;assembly=MyApp"

 

Gesty dotykowe

  • View - nie ma zdarzeń, ale jest kolekcja GestureRecognizers

var tapGesture = new TapGestureRecognizer(); 

tapGesture.Tapped += OnViewTapped;

view.GestureRecognizers.Add(tapGesture);

void OnViewTapped(object sender, EventArgs args)

{

        …

}

 

Warunki View na generowanie zdarzeń:

  • IsEnabled = true
  • IsVisible = true
  • InputTransparent = false

TapGestureRecognizer:

  • NumberOfTapsRequired = 1 (domyślnie)
  • Command
  • CommandParameter

XAML:

<BoxView Color="Blue">

     <BoxView.GestureRecognizers>

         <TapGestureRecognizer Tapped="OnBoxViewTapped" />

     </BoxView.GestureRecognizers>

</BoxView>

 

API specyficzne dla platformy

Sharowanie plików

Portable Class Library odczytujące przez refleksję API typowe dla używanej w danym momencie platformy - gotowe rozwiązanie:  DependencyService

Interfejs w PCL

Klasy implementujące interfejs w projektach pod konkretne platformy.

[assembly:  Dependency(typeof(klasa_implementująca))]   /* w pliku klasy przed deklaracją namespace */

w PCL:

var platformInfo = DependencyService.Get<IPlatformInfo>();  //instancje obiektów pozyskiwanych przez Get są cachowane, stan w implementacjach na platformy jest przechowywany między wywołaniami Get

Kto chce może też za pomocą dependecy injection zrealizować podobną funkcjonalność.

Odtwarzanie audio (WAVE)

iOS:

       …

NSData data = NSData.FromStream(stream);

AVAudioPlayer audioPlayer = AVAudioPlayer.FromData(data);

audioPlayer.Play();  

Android:

if (previousAudioTrack != null)

{               

         previousAudioTrack.Stop();

         previousAudioTrack.Release(); 


 

AudioTrack audioTrack = new AudioTrack(Stream.Music,

                                                        samplingRate, 

                                                        ChannelOut.Mono,

                                                        Android.Media.Encoding.Pcm16bit,

                                                        pcmData.Length * sizeof(short),

                                                        AudioTrackMode.Static); 


audioTrack.Write(pcmData, 0, pcmData.Length);

audioTrack.Play();

 

previousAudioTrack = audioTrack;

 

XAML Markup Extensions

Ze specyfikacji XAML 2009:

  • x:Static  - każda statyczna propercja, pole lub wartość enum
  • x:Reference
  • x:Type
  • x:Null
  • x:Array

  <Label BackgroundColor=”{x: Static local:AppConstants.LightBackground}” />

<x:Array x:Key="array"  Type="{x:Type x:String}">

     <x:String>One</x:String>

     <x:String>Two</x:String>

</x:Array>

Markup extensions z frameworków XAML Microsoftu:

  • StaticResource
  • DynamicResource
  • Binding

Markup extension unikalny dla Xamarin.Forms

  • ConstraintExpression (powiązanie z RelativeLayout)

Własny markup extension:

public class HslColorExtension : IMarkupExtension

{        

        public HslColorExtension()

        {            

                A = 1; 

        }

 
        public double H { set; get; } 


        public double S { set; get; } 


        public double L { set; get; }

  
        public double A { set; get; }

 
        public object ProvideValue(IServiceProvider serviceProvider)

        {            

                  return Color.FromHsla(H, S, L, A);

        }

}

<BoxView Color="{local:HslColor H=0.67, S=1, L=0.5}" />

Biblioteka PCL z markup extension - aktualnie aplikacja oprócz referencji do biblioteki wymaga odwołania z kodu np. wywołania konstruktora markup extension w konstruktorze obiektu aplikacji

 

ResourceDictionary

VisualElement

  • Resources

Aplikacja także może mieć plik XAML, ale trzeba to przygotować ręcznie na podstawie strony XAML

Brak komentarzy: