Vítejte v druhé části vývoje našeho prvního pull requestu do Uno Platform. V prvním článkuhe této série jsme se podívali na Uno jako celek a poprvé nahlédli do jeho kódu. Tentokrát se podíváme na existující implementaci třídy DisplayInformation
, přidáme do ukázkové aplikace novou stránku na které náš kód budeme testovat, seznámíme se s projektem UWPSyncGenerator
a implementujeme ScreenWidthInRawPixels
a ScreenHeightInRawPixels
.
Třída DisplayInformation
Již minule jsem zmínil, že issue kterou jsem se rozhodl řešit má číslo #385 - "Add support for DisplayInformation Dimensions". Nejprve se podívejme co UWP třída DisplayInformation
umí. Můžeme ji najít v namepsace Windows.Graphics.Display
a poskytuje informace o displeji na kterém je aplikace zobrazena. Navíc, tato třída zahrnuje události o změnách displeje jako je OrientationChanged
. Abychom API mohli implementovat, je nutné dodžovat co nejpřesněji chování platformy UWP. Naštěstí Microsoft Docs jsou velmi dobře napsané a každá vlastnost je důkladně popsána. Rozhodl jsem se implementovat všechny vlastnosti DisplayInformation
na Androidu a iOS a přenechat ostatní platformy a události na další pull requesty. Tím bude pull request výstižný a získám rychlejší zpětnou vazbu. Jakmile bude potvrzeno, že je můj přístup správný, můžu s větší jistotou pokračovat dál.
Existující Uno implementace
Když jsem nahlédl do složky Graphics/Display
v projektu Uno
, zjistil jsem, že soubory DisplayInformation.cs
, DisplayInformation.Android.cs
a DisplayInformation.iOS.cs
již existují a implementují AutoRotationPreferences
, CurrentOrientation
a OrientationChanged
na iOS a AutoRotationPreferences
na Androidu. Pro získání instance DisplayInformation
na UWP je třeba zavolat metodu GetForCurrentView
z UI vlákna. Na UWP je instance svázaná s vláknem ale Uno nyní používá jednodušší přísutp a vyvtáří singleton instanci nezávislou na vlýkně:
Bezparametrický konstruktor je označen jako private
takže nové instance není možné mimo třídu vytvořit. Konstruktor volá partial
metodu Initialize
.
Metoda Initialize
je implementována pro Android a iOS zvlášť v souborech DisplayInformation.Android.cs
a DisplayInformation.iOS.cs
. Například na iOS Initialize
volá InitializeCurrentOrientation
, která nastavuje výchozí hodnotu pro CurrentOrientation
:
Jak si můžete všimnout, pro určení orientace se používá orientace StatusBaru
, protože jde o nejspolehlivější způsob
Úprava ukázkové aplikace
Abych si usnadnil testování implementace, vytvořil jsem v SamplesApp jednoduchý user control:
Control hostuje ListView
, který zobrazí seznam vlastností DisplayInformation
, jejich hodnoty a tlačítko, které je aktualizuje. UserControl
označíme atributem SampleControlInfoAttribute
jež se používá pro nalezení a kategorizaci ukázkových příkladů.
Metoda RefreshDisplayInformation
získává aktuální hodnoty vlastností ze třídy DisplayInformation
. Můj původní plán byl použít reflexi:
To fungovalo poměrně dobře až do chvíle než jsem kód spustil na iOS kde většina vlastností v seznamu chyběla. Důvodem byl fakt, že linker pro zmenšení výsledné assembly odstraňuje kód, na který nic nepřístupuje a nové vlastnosti DisplayInformation
byly mezi nimi. Kód jsem opravil tak, že jsem vlastnosti zadal ručně:
Toto samozřejmě pozbývá jednoduchost a obecnost původního řešení, ale na druhou stranu to funguje :-) . Pro úplnost taky uvedu kód metody SafeGetValue
, která pomocí delegátu Func<T>
získá hodnotu vlastnosti a převede ji na string
je-li to možné:
Všimněme si, že kód odcbytává výjimku NotImplementedException
, kterou Uno používá pro API která zatím nemají implementaci. Na obrázku níže je ukázková aplikace běžící na Windows proti UWP API. Je před námi hodně práce!
Synchronizace s UWP API
Nyní je čas nahlédnout pod pokličku dalšího z UWP kouzel. Jak dokáže tým udržet API plně synchronizované s UWP? Aktualizace Windows 10 přichází pravidelně dvakrát do roka, takže procházet vždy celé API a kontrolovat změny by byl skutečně náročný (a nezáviděníhodný) úkol. Zde ale přichází na scénu projekt Uno.UWPSyncGenerator
. Musíme nejprve porozumět tomu, jak Uno pracuje s neimplementovaným API. Uvnitř projektů Uno
a Uno.UI
můžeme najít složky s názvem Generated
. Uvnitř jsou podsložky pro všechna UWP API a každý typ pak má svůj vlastní .cs
soubor. Na příklad DisplayInformation.cs
je uvnitř podsložky Generated/3.0.0.0/Windows.Graphics.Display
a obsahuje následující:
Toto vše je automaticky generovaný kód, který pro všechna neimplementovaná API přidává NotImplementedException
wrapper. Také si všimněme, že jsou všechny třídy partial
. Skutečné třídy Una pak používají partial
také a mohou tak například implementovat jen část dané třídy. Uno.UWPSyncGenerator
má na starosti generovat všechen tento kód a jde na to opravdu chytře. Používá balíček Microsoft.CodeAnalysis
a prochází projekty Uno
a Uno.UI
a kontroluje co je již pro které platformy implementované. Pro příklad přidám první tři vlastnosti DisplayInformation
do DisplayInformation.cs
v Uno
projektu:
Po spuštění UWPSyncGenerator
deklarované vlastnosti zmizí ze složky Generated
a jsou nahrazeny informativním komentářem:
Ale to není vše! Co kdybychom se rozhodli implementovat nějakou z vlastností pouze pro Android?
Nástroj tuto situace zvládne i tak bravurně:
Et voilà, direktiva #if
nyní obsahuje namísto symbolu __ANDROID__
neškodné false
.
Implementace ScreenWidthInRawPixels
a ScreenHeightInRawPixels
Nebudu ve článku do detailů rozebírat implementaci celé třídy DisplayInformation
, protože jde z většiny o platformně závislý kód a tato série článků je věnována procesu přispívání do Una jako takévho. Ale pro ukázku můžeme ukázat proces na vlastnostech ScreenWidthInRawPixels
a ScreenHeightInRawPixels
. Tyto vlastnosti by měly vrátit skutečné rozlišení displeje v pixlech. Ze všeho nejdřív přidám do souboru DisplayInformation.cs
nové vlastnosti:
Toto způsobí konflikt s NotImplemented
vlastnostmi ve složce Generated
, ale spuštěním UWPSyncGenerator
konflikt zmizí. Nyní vlastnostem nastavíme skutečnou hodnotu!
iOS
Přidal jsem novou metodu UpdateProperties
kterou volá metoda Initialize
v DisplayInformation.iOS.cs
:
Použiji UpdateProperties
pro nastavení hondnot vlastností ve chvíli, kdy se displej změní. Pro tuto chvíli ale události neimplementujeme, takže vlastnosti zatím nastavím při inicializaci. Aby byl kód jasnější, rozdělím metodu UpdateProperties
na logické skupiny.
Veĺikost obrazovky na iOS je možné zjistit pomocí vlastnosti UIScreen.MainScreen.Bounds.Size
. To však vrací pouze velikost v layout "bodech". Vše se potom renderuje ve zvětšení UIScreen.MainScreen.Scale
a pak zase změnší na nativní rozlišení displeje. Tato infografika je pro pochopení tohoto procesu na iOS velmi užitečná. Naštěstí máme i API UIScreen.MainScreen.NativeScale
které vrací skutečné "nativní" zvětšení:
A ukázková aplikace nyní ukalzuje:
Android
Na Androidu jsem postupoval poobně jako na iOS:
Je potřeba trochu kódu navíc pro získání instance DisplayMetrics
a IWindowManager
, protože tyto budou znovu použity pro implementaci dalších vlastností. Základní implementace UpdateRawProperties
vypadá takto:
DisplayMetrics
které jsme výše získali přimo obsahují vlastnosti, které vrací šířku a výšku displeje v pixelech, takže je jednoduše přetypujeme a jsme hotovi:
Shrnutí
Podívali jsme se na existující implementaci DisplayInformation
v Unu, pochopili, jak framework synchronizuje své API s UWP a implementovali první dvě vlastnosti. Příště se v krátkosti podíváme na to nejzajímavější ze zbýavjících vlastností a implementujeme událost OrientationChanged
na Androidu. Potom konečně vytvoříme svůj pull request a odešleme jej pro review!