Sie sind auf Seite 1von 304

Informações Importantes

A Azuris Mobile e Cloud Experts agradece seu interesse na


tradução do livro "Criando Aplicativos Mobile com Xamarin
Forms".

Gostaríamos de agradecer ao autor Charles Petzold que


nos ajudo com a Microsoft Press para a liberação da
tradução do livro "Criando Mobile Apps com Xamarin
Forms
Inicialmente iriamos enviar capitulo a capitulo por email,
mas agora recebemos a liberação para publicação da
tradução da versão do Livro Xamarin Forms e com isso
estamos liberando o link para o PDF do Livro com 14
capitulos. Estamos finalizando a Revisão dos demais
capítulos. Iremos liberar todos os capitulos no GitHub no
formato GitBook, pois estamos com alguns problemas na
conversão para o formato do GitBook . Assim todas a
contribuições e sugestões poderão ser enviadas
diretamente para esse repositório.

Não podemos esquecer que tudo isso so foi possível


porque a comunidade esta organizada a muitos anos com o
projeto Mono. No Brasil temos várias pessoas notáveis que
tem atuado no suporte da comunidade de software livre.
Interessado em contribuir mais como o mundo do Mono e
.NET OpenSouce?? Acesse: www.monobrasil.com.br
canal no Gitter https://gitter.im/MonoBrasil

Canal no Slack : monobrasil.slack.com

Nosso site :
www.xamarin.com.br

Canal do Slack :
xamarin-com-br.slack.com

Repositório onde está disponível o fonte do livro para


contribuições:
github.com/xamarin-com-br

Fanpage
facebook.com/xamarin.com.br

Interessados em Treinamento Online ou presenciais de


Xamarin deve acessar nosso site www.azuris.com.br
Pessoas que contribuíram para a tradução deste livro:
Alessandro Binhara
Alexandre Marcondes
Rafael Teixeira – MonoMan
Alcides Cardozo
Amanda Costa Corrêa
Mauricio de Araujo Greboge
Andre de Siqueira Neu Junior
Renata de Souza Antonio
Rodrigo Drewniak Cocci
Edson Edgar Egger
João Fabio Lourenço dos Santos
Fabiano Filla
Gabriel Firmino de Farias
Cristiano Jose da Silva
Marcelo Jose Kohler
Ernane José Ribas Bacellar
Matheus Lorena Crema
Cezar Luiz
Thiago Luiz Ribeiro
Johnny M. Ferreira
Rodrigo Machado Santos
Cristiano Moreira da Costa
Rejane Myszka
Reedlei Nagornni Junior
Roberson Fleck
Luiz Rodrigo Schuitek
Wermes Rodrigues dos Santos
Matheus Rorato Baptista
Rafael Rosar
Rafael Silvério
Jhoni Stracke
Cassio Vinicius Camargo Schrega
Gustavo Yu Fabris Hasegawa
Geraldo Yukio Ishida

Quer ter seu nome nessa lista. Mande suas correções sugestões de melhorias no GitHub!!!
Sumário
Informações Importantes ........................................................................................................................................................................... 1
Pessoas que contribuíram para a tradução deste livro: .............................................................................................................................. 4
Capítulo 1 .................................................................................................................................................................................................. 9
Multi-plataforma de desenvolvimento móvel.......................................................................................................................................... 9
A Visão Móvel ....................................................................................................................................................................................... 9
Problema 1: Diferentes paradigmas de interface do usuário ................................................................................................................. 9
Problema 2: diferentes ambientes de desenvolvimento......................................................................................................................... 9
Problema 3: diferentes interfaces de programação ............................................................................................................................... 9
Problema 4: Diferentes linguagens de programação ............................................................................................................................. 9
O C # e .NET solução ......................................................................................................................................................................... 10
Uma linguagem única para todas as plataformas ................................................................................................................................ 10
Código Compartilhado ........................................................................................................................................................................ 10
Apresentando o Xamarin.Forms .............................................................................................................................................................. 11
A opção Xamarin.Forms ..................................................................................................................................................................... 12
Suporte XAML ................................................................................................................................................................................... 13
Especificidade Platform....................................................................................................................................................................... 14
A panacéia multi-plataforma?.............................................................................................................................................................. 15
Seu ambiente de desenvolvimento ..................................................................................................................................................... 15
Máquinas e IDEs ................................................................................................................................................................................ 15
Dispositivos e emuladores .................................................................................................................................................................. 16
Capítulo 2 ................................................................................................................................................................................................ 17
Anatomia de um aplicativo .................................................................................................................................................................. 17
Diga Olá.............................................................................................................................................................................................. 17
Dentro dos arquivos ............................................................................................................................................................................ 18
O Projeto IOS ..................................................................................................................................................................................... 20
O Projeto Android ............................................................................................................................................................................... 20
O Projeto Windows Phone .................................................................................................................................................................. 21
Nada especial! .................................................................................................................................................................................... 21
PCL ou SAP?...................................................................................................................................................................................... 21
Quais são esses símbolos? ................................................................................................................................................................ 22
Labels para o texto ............................................................................................................................................................................. 23
1. Incluir preenchimento na página ..................................................................................................................................................... 25
2. Incluir preenchimento apenas para IOS .......................................................................................................................................... 26
3. Incluir preenchimento apenas para IOS (PCL ou SAP) ................................................................................................................... 27
4. Centralizar o label com a página ..................................................................................................................................................... 28
5. Centralizar o texto com o label ........................................................................................................................................................ 29
Capítulo 3 ................................................................................................................................................................................................ 30
Mais a fundo no texto .......................................................................................................................................................................... 30
Quebra automática de texto nos parágrafos........................................................................................................................................ 30
Cores do texto e do fundo ................................................................................................................................................................... 31
A estrutura Color................................................................................................................................................................................. 32
Tamanho de Fontes e atributos .......................................................................................................................................................... 34
Texto Formatado ................................................................................................................................................................................ 35
Capítulo 4 ................................................................................................................................................................................................ 39
Rolando a pilha ................................................................................................................................................................................... 39
Pilhas de views ................................................................................................................................................................................... 39
Rolando conteúdo ............................................................................................................................................................................... 41
A opção Expands ................................................................................................................................................................................ 45
Frame e BoxView................................................................................................................................................................................ 47
Um ScrollView numa StackLayout? .................................................................................................................................................... 52
Capítulo 5 ................................................................................................................................................................................................ 54
Lidar com tamanhos ........................................................................................................................................................................... 54
Pixels, points, dps, DIPs, e DIUs ......................................................................................................................................................... 55
Tamanhos métricos ............................................................................................................................................................................ 58
Tamanos de fonte estimado ................................................................................................................................................................ 59
Textos apropriados para tamanho de dispositivos............................................................................................................................... 61
Um clock para ajustar tamanho ........................................................................................................................................................... 63
Montagem de texto ............................................................................................................................................................................. 64
Capítulo 6 ................................................................................................................................................................................................ 67
Cliques de Botão ................................................................................................................................................................................ 67
O Processamento do Clique ............................................................................................................................................................... 68
Compartilhando cliques de botão ........................................................................................................................................................ 69
Gerenciadores de Eventos Anônimos ................................................................................................................................................. 71
Visualizações Separadas por Identificadores ...................................................................................................................................... 72
Salvando dados temporários............................................................................................................................................................... 74
Capítulo 7 ................................................................................................................................................................................................ 79
XAML vs Código ................................................................................................................................................................................. 79
Propriedades e atributos ..................................................................................................................................................................... 80
Sintaxe Propriedade – Elemento ......................................................................................................................................................... 82
Adicionando uma página XAML para o seu projeto ............................................................................................................................. 85
Plataformas específicas do arquivo XAML .......................................................................................................................................... 88
O atributo de propriedade de conteúdo ............................................................................................................................................... 91
Formatação do Texto .......................................................................................................................................................................... 92
Capítulo 8 - Código e XAML em harmonia ............................................................................................................................................... 95
Passando Argumentos ........................................................................................................................................................................ 95
Construtor com argumentos ................................................................................................................................................................ 95
Posso chamar um método por XAML? ................................................................................................................................................ 97
O atributo x:Name ............................................................................................................................................................................... 98
Visualizações personalizadas baseadas em XAML ........................................................................................................................... 102
Eventos e Handlers........................................................................................................................................................................... 105
Gestos de Toque .............................................................................................................................................................................. 107
Capítulo 9 .............................................................................................................................................................................................. 113
Chamadas de API específicas da plataforma .................................................................................................................................... 113
Pré-processamento no compartilhamento do Projeto ........................................................................................................................ 113
As classes paralelas e o compartilhamento do projeto ...................................................................................................................... 115
DependencyService e Biblioteca de Classes Portátil. ....................................................................................................................... 117
Plataforma especifica de renderização de som. ................................................................................................................................ 120
Capítulo 10 ............................................................................................................................................................................................ 125
Extensões de marcação XAML ......................................................................................................................................................... 125
A infra-estrutura de código ................................................................................................................................................................ 125
Acessando membros estáticos ......................................................................................................................................................... 126
Dicionários de recursos..................................................................................................................................................................... 131
StaticResource para a maioria dos fins ............................................................................................................................................. 132
Uma árvore de dicionários ................................................................................................................................................................ 136
DynamicResource para fins especiais............................................................................................................................................... 139
Extensões de marcação Menos Divulgadas ...................................................................................................................................... 141
Uma extensão de marcação personalizada....................................................................................................................................... 142
Capítulo 11 ............................................................................................................................................................................................ 145
A infraestrutura Bindable .......................................................................................................................................................................... 145
A Hierarquia da Classe Xamarin.Forms ............................................................................................................................................ 146
Um olhar sobre Bindableobject e BindableProperty .......................................................................................................................... 152
Definindo Bindable Properties ........................................................................................................................................................... 156
Criando métodos genéricos ...................................................................................................................................................................... 161
The Read-Only Bindable Property ................................................................................................................................................................ 162
Capítulo 12 ............................................................................................................................................................................................ 167
Estilos ............................................................................................................................................................................................... 167
Estilo básico...................................................................................................................................................................................... 167
Estilos no código............................................................................................................................................................................... 171
Herança de estilo .............................................................................................................................................................................. 173
Estilos Implícitos ............................................................................................................................................................................... 176
Estilos dinâmicos .............................................................................................................................................................................. 179
Estilos de dispositivo ......................................................................................................................................................................... 184
Capítulo 14 - Absolute Layout ............................................................................................................................................................... 188
Absolute Layout no Código ............................................................................................................................................................... 188
Incluindo propriedades Bindable ....................................................................................................................................................... 191
Posicionamento e dimensionamento proporcional ............................................................................................................................ 194
Trabalhando com coordenadas proporcionais................................................................................................................................... 196
XML e Layout Absoluto ..................................................................................................................................................................... 199
Sobreposições .................................................................................................................................................................................. 201
Alguma Diversão .............................................................................................................................................................................. 204
Capítulo 13 - Bitmaps ............................................................................................................................................................................ 210
BITMAPS INDEPENDENTES DE PLATAFORMA ............................................................................................................................ 210
Fit e fill .............................................................................................................................................................................................. 213
Recursos Incorporados ..................................................................................................................................................................... 214
Mais Sobre Dimensionamento .......................................................................................................................................................... 218
Navegação e esperando ................................................................................................................................................................... 226
Bitmaps de streaming ....................................................................................................................................................................... 229
Acessando os fluxos ......................................................................................................................................................................... 229
Geração de bitmaps em tempo de execução .................................................................................................................................... 231
Bitmaps específicos da plataforma.................................................................................................................................................... 234
Resoluções de bitmap ...................................................................................................................................................................... 236
Bitmaps independentes de dispositivo para iOS ............................................................................................................................... 237
Bitmaps independentes de dispositivo para Android ......................................................................................................................... 237
Bitmaps independentes de dispositivo para plataformas Windows Runtime ...................................................................................... 237
Barras de ferramentas e seus ícones ................................................................................................................................................ 240
Ícones para Android. ......................................................................................................................................................................... 241
Imagens de botão ............................................................................................................................................................................. 244
Capítulo 18 ............................................................................................................................................................................................ 246
MVVM ............................................................................................................................................................................................... 246
Inter-relacionamentos MVVM ............................................................................................................................................................ 246
Data binding e ViewModels ............................................................................................................................................................... 247
Um relógio ViewModel ...................................................................................................................................................................... 247
Propriedades interativas em um ViewModel...................................................................................................................................... 252
ViewModel Colorido .......................................................................................................................................................................... 257
Melhorando o ViewModel .................................................................................................................................................................. 263
A Interface de Comando ................................................................................................................................................................... 266
Quase uma calculadora .................................................................................................................................................................... 270
ViewModels e o ciclo de vida do aplicativo ........................................................................................................................................ 277
Capítulo 27 ............................................................................................................................................................................................ 280
Renderizadores Customizados ......................................................................................................................................................... 280
A hierarquia de classe completa ....................................................................................................................................................... 280
Olá renderizadores customizados! .................................................................................................................................................... 282
Renderizadores e propriedades ........................................................................................................................................................ 285
Renderizadores e eventos ................................................................................................................................................................ 295
Capítulo 1
Multi-plataforma de desenvolvimento móvel
A indústria de computadores pessoais tem experimentado uma grande mudança nos últimos anos. Os computadores de mesa ainda
existem, é claro, e eles continuam sendo vitais para tarefas que exigem teclados e telas grandes: programação, escrita, rastreamento de
dados. Mas muito da computação pessoal agora ocorre em dispositivos menores, em particular para à informação rápida, o consumo de
mídia e redes sociais. Tablets e smartphones têm um paradigm de interação do usuário fundamentalmente diferente baseado
principalmente no toque, com um teclado que aparece somente quando necessário.

A Visão Móvel
Embora o mercado móvel tenha o potencial de mudança rápida, atualmente duas principais plataformas de telefones e tablets dominam:
• A família Apple iPhones e iPads, os quais executam o sistema operacional iOS.
• O sistema operacional Android, desenvolvido pelo Google baseado no kernel do Linux, que funciona em uma
variedade de telefones e tablets.
Como o mundo esta dividido entre esses dois gigantes, vai depender da forma que eles são medidos: existem mais dispositivos Android
atualmente em uso, mas os usuários de iPhone e iPad são mais dedicados e passam mais tempo com os seus dispositivos.
Há também uma Terceira plataforma de desenvolvimento móvel, o que não é tão popular como iOS e Android, mas envolve uma empresa
com uma forte história na indústria de computadores pessoais:
• Windows Phone, da Microsoft.
Nos últimos anos, o Windows Phone tornou-se uma alternativa mais atraente, como Microsoft foi a fusão das APIs de suas plataformas.
Uma única API chamada de tempo de execução do Windows (ou WinRT) já está disponível para aplicativos executados em computadores
desktop, laptops, tablets e telefones. Para desenvolvedores de software, a melhor estratégia é a meta, mais do que apenas uma dessas
plataformas. Mas isso não é fácil. Existem quatro grandes obstáculos:

Problema 1: Diferentes paradigmas de interface do usuário


Todas as três plataformas incorporam formas semelhantes de apresentar a interface gráfica do usuário (GUI) e a interação com o
dispositivo, através de multi touch, mas há muitas diferenças. Cada plataforma tem diferentes maneiras de navegar em torno de aplicações
e páginas, diferentes convenções para á apresentação de dados, diferentes maneiras de invocar e menus de exibição, e até mesmo
diferentes abordagens ao toque.
Os usuários se acostumaram a interagir com aplicativos em uma plataforma específica e esperam aproveitar esse conhecimento com
aplicações futuras. Cada plataforma adquire a sua própria cultura associada, e essas convenções culturais, em seguida, influenciam
desenvolvedores.

Problema 2: diferentes ambientes de desenvolvimento


Os programadores de hoje estão acostumados a trabalhar em um desenvolvimento integrado em ambiente sofisticado (IDE), existem tais
IDEs para as três plataformas, mas é claro que eles são diferentes:
• Para o desenvolvimento do iOS, Xcode no Mac.
• Para o desenvolvimento do Android, Estúdio Android em uma variedade de plataformas.
• Para o desenvolvimento do Windows Phone, Visual Studio no PC.

Problema 3: diferentes interfaces de programação


Todos os três tipos destas plataformas são baseadas em diferentes sistemas operacionais com diferentes APIs. Em muitos casos, as três
implementam tipos similares de objetos de interface do usuário, mas com nomes diferentes.
Por exemplo, todas as três plataformas têm algo que permite ao usuário alternar um valor booleano:
• No iPhone, é uma "visão" chamado UISwitch.
• Em dispositivos Android, é um "widget" chamado Switch.
• No Windows Phone, uma possibilidade é um "controle" chamado Toggle Switch Botão do pacote Windows Phone
Toolkit NuGet.
Claro, as diferenças vão muito além dos nomes para as próprias interfaces de programação.

Problema 4: Diferentes linguagens de programação


Os colaboradores têm alguma flexibilidade na escolha de uma linguagem de programação para cada uma destas três plataformas, mas,
em geral, cada plataforma está muito intimamente relacionada com uma linguagem de programação específica:
• Objective-C para o iPhone
• Java para dispositivos Android
• C # para Windows Phone
Estas três línguagens são primas, porque elas são todas descendentes de orientação a objetos de C, mas elas se tornaram primas
bastante distantes.
Por estas razões, uma empresa que quer atingir múltiplas plataformas poderia muito bem empregar três equipes de programadores
diferentes, cada equipe qualificada e especializada em uma linguagem e API particular.
Este problema da liguagem é particularmente desagradável, mas é o problema que é o maisdesafiador para resolver: Se você poderia
usar a mesma linguagem de programação para estas três plataformas, você poderia, pelo menos, compartilhar algum código entre as
plataformas. Este código compartilhado provavelmente não estaria envolvido com a interface de usuário porque cada plataforma tem
diferentes APIs, mas pode muito bem ser o código do aplicativo que não toca a interface do usuário em tudo.
Uma linguagem única para estas três plataformas certamente seria conveniente. Mas qual a linguagem que seria isso?

O C # e .NET solução
Uma sala cheia de programadores viria uma variedade de respostas para a pergunta simplesmente apresentadas, mas um bom argumento
pode ser feito em favor do C#. Revelado pela Microsoft no ano de 2000, C# é uma linguagem relativamente nova de programação, pelo
menos quando comparado com Objective-C e Java. Na primeira, C# parecia ser uma linguagem fortemente tipada, linguagem bastante
simples, imperativo orientada a objetos, certamente influenciada por C ++ (e Java também), mas com uma sintaxe muito mais limpa do C
++ e nenhuma da bagagem histórica. Além disso, a primeira versão do C # tinha o suporte a nível de linguagem para propriedades e
eventos, que acabam por ser tipos que são particularmente adequados para interfaces de usuário de programação gráfica.
Mas C # tem continuado a crescer e obter melhor resultado ao longo dos anos. O apoio dos genéricos, as funções lambda, LINQ e
operações assíncronas elevou com sucesso C # para ser classificada como uma linguagem de programação de multi paradigma. C #
código pode ser tradicionalmente imperativo ou aromatizadas com paradigmas de programação declarativas ou funcionais.
Também interessante é o futuro do C #: em 3 de Abril de 2014, criador Anders Hejlsberg estava em um estágio na Microsoft Conferência
2014 e clicou um botão que publicou uma versão de código aberto do compilador C #, chamado de Plataforma Compiler .NET
(anteriormente conhecido pelo seu nome de código "Roslyn").
Desde a sua criação, C# tem sido intimamente associado com o Microsoft .NET Framework. No nível mais baixo, .NET fornece uma infra-
estrutura para os tipos de dados básicos C # (int, double, corda, e assim por diante). Mas a extensa biblioteca de classes .NET Framework
fornece suporte para muitas tarefas comuns encontradas em muitos tipos diferentes de programação. Esses incluem:
• Math
• Debugging
• Reflection
• Collections
• Globalization
• File I/O
• Networking
• Security
• Threading
• Web services
• Data handling
• XML and JSON reading and writing
Aqui está outro grande motivo para C # e .NET para ser considerada como uma solução multi-plataforma convincente:
Não é apenas hipotética. É uma realidade. Logo após o anúncio da Microsoft de .NET caminho de volta em junho de 2000, a empresa
Ximian (fundada por Miguel de Icaza e Nat Friedman) deu início a um projeto de código aberto chamado Mono para criar uma
implementação alternativa do compilador C # e .NET Framework que pode rodar em Linux.
Uma década depois, em 2011, os fundadores da Ximian (que foi adquirida pela Novell) funda a Xamarin, que ainda contribui para a versão
de código aberto do Mono, mas que também se adaptou Mono para formar a base de soluções móveis multi-plataforma. Logo após a
versão de código aberto do C # foi publicado, Scott Guthrie de Microsoft anunciou a formação da Fundação .NET, que serve como um
mordomo para tecnologias open-source .NET, em que Xamarin desempenha um papel importante.

Uma linguagem única para todas as plataformas


Durante os três primeiros anos da sua existência, Xamarin focada principalmente em tecnologias de compilador e três conjuntos básicos
de bibliotecas .NET:
• Xamarin.Mac, que evoluiu a partir do projeto MonoMac.
• Xamarin.iOS, que evoluiu de MonoTouch.
• Xamarin.Android, que evoluíram a partir de Mono para Android ou (mais informal) MonoDroid.
Colectivamente, estas bibliotecas são conhecidas como a plataforma Xamarin. As bibliotecas consistem em versões .NET dos Mac, iOS
e APIs nativas do Android. Os programadores que usam essas bibliotecas podem escrever aplicações em C # para direcionar as APIs
nativas dessas três plataformas, mas também (como um bônus) com acesso à biblioteca de classes .NET Framework.
Xamarin também disponibiliza Xamarin Studio, um ambiente de desenvolvimento integrado que roda tanto em Mac e PC e permite
desenvolver aplicativos iPhone e Android sobre as aplicações Mac e Android no PC.
Também é possível usar o Visual Studio com as bibliotecas Xamarin para desenvolver Mac, iPhone e aplicações droid An-. (No entanto,
Mac e iPhone desenvolvimento também requer um Mac com o Xcode e Xamarin Studio instalado e conectado através de uma rede local
com o PC.)
Claro, se você estiver usando Visual Studio, você pode também alvo Windows Phone e outras plataformas da Microsoft.

Código Compartilhado
A vantagem da segmentação em várias plataformas com uma única linguagem de programação vem da capacidade de compartilhar
código entre as aplicações.
Antes de código pode ser compartilhado, um aplicativo deve ser estruturado para esse efeito. Particularmente desde o amplo uso de
interfaces gráficas de usuário, os programadores têm entendido a importância de separar o código do aplicativo em camadas funcionais.
Talvez a divisão mais útil é entre o código de interface do usuário e os modelos de dados subjacentes e algoritmos. O MVC popular (MVC)
arquitetura de aplicação apro- formaliza esta separação código em um modelo (os dados subjacentes), o Vista (a representação visual
dos dados), eo Controller (que lida com a entrada do usuário).
MVC originou na década de 1980. Mais recentemente, a arquitetura (Model-View-ViewModel) MVVM tem efetivamente modernizado MVC
com base em interfaces gráficas modernas. MVVM separa código no Modelo (a compreensão de dados mentir), a View (interface do
usuário, incluindo recursos visuais e de entrada), e o ViewModel (que dados idades-homem passando entre o modelo e a vista).
Quando um programador desenvolve uma aplicação que tem como alvo várias plataformas móveis, a arquitetura MVVM ajuda a orientar
o desenvolvedor para separar código no Ver-o código específico da plataforma que requer a interação com as APIs-e plataforma do
modelo independente de plataforma e Ver- Modelo.
Muitas vezes este código independente de plataforma precisa acessar arquivos ou a rede ou coleções de uso ou threading. Normalmente,
estes postos de trabalho seriam considerados parte de uma API do sistema operacional, mas eles também são postos de trabalho que
podem fazer uso da biblioteca de classes .NET Framework, e se a biblioteca de classes está disponível em cada plataforma, é
efetivamente independente de plataforma.
A parte do aplicativo que é independente de plataforma pode então ser isolado e-no contexto do Visual Studio ou Xamarin Studio-colocado
em um projeto separado. Isso pode ser um projeto de recurso compartilhado (SAP) -que consiste simplesmente em arquivos de código e
outros ativos acessível a partir de outros projetos, ou uma Biblioteca de Classes Portátil (PCL), que abrange todo o código comum em
uma biblioteca de vínculo dinâmico (DLL) que podem então ser referenciado a partir de outros projetos.
Independentemente do método utilizado, este código comum tem acesso à biblioteca de classes .NET Framework, para que ele possa
executar arquivo de I / O, lidar com a globalização, os serviços Web Access, decompor XML, e assim por diante.
Isso significa que você pode criar uma única solução Visual Studio que contém quatro projetos C # para alvo conseguir os três principais
plataformas móveis (todos com acesso a um SAP comum ou projeto PCL), ou você pode usar Xamarin Studio para direcionar dispositivos
iPhone e Android .
O diagrama a seguir ilustra as inter-relações entre os projetos do Visual Studio ou Xamarin Studio, as bibliotecas Xamarin, e as APIs da
plataforma:

As caixas da segunda fila são as aplicações reais específicos da plataforma. Esses aplicativos fazer chamadas para o projeto comum e
também (com o iPhone e Android) as bibliotecas Xamarin que implementam as APIs de plataforma nativa.
Mas o diagrama não é bastante completo: ele não mostra a SAP ou PCL fazer chamadas para a biblioteca de classes .NET Framework.
O PCL tem acesso à sua própria versão do .NET, enquanto a SAP utiliza a versão do .NET incorporados em cada plataforma particular.
Neste diagrama, as bibliotecas Xamarin.iOS e Xamarin.Android parecem ser substanciais, e enquanto eles são certamente importantes,
eles são na sua maioria ligações apenas de linguagem e não adicionar significativamente qualquer sobrecarga de chamadas de API.
Quando o iPhone app é construído, o # compilador Xamarin C gera C # Intermediate Language (IL), como de costume, mas, em seguida,
faz uso do compilador da Apple no Mac para gerar código nativo máquina de iPhone como o compilador Objective-C. As chamadas do
aplicativo às APIs do iPhone são o mesmo como se o aplicativo foi escrito em Objective-C.
Para o aplicativo para Android, o # compilador Xamarin C gera IL, que é executado em uma versão do Mono no dispositivo ao lado do
motor de Java, mas as chamadas de API do aplicativo são praticamente o mesmo como se o app foram escritas em Java.
Para aplicações móveis que têm necessidades muito específicas da plataforma, mas também um pedaço potencialmente compartilhável
de código independente de plataforma, Xamarin.iOS e Xamarin.Android fornecer excelentes soluções. Você tem acesso a toda a API da
plataforma, com todo o poder (e responsabilidade) isso implica.
Mas para aplicações que não pode precisar bastante tanta especificidade plataforma, existe agora uma alternativa que irá simplificar a
sua vida ainda mais.

Apresentando o Xamarin.Forms
Em 28 de maio de 2014, Xamarin introduzido Xamarin.Forms como parte de um conjunto de melhorias para a plataforma Xamarin
apelidado Xamarin 3. Xamarin.Forms lhe permite escrever o código de interface do usuário que pode ser compilado para o iPhone, Android
e Windows Phone .
A opção Xamarin.Forms
No caso geral, uma aplicação Xamarin.Forms é composta de três projetos separados para as três plataformas móveis, com um quarto
projeto que contém o código-muito comum muito parecido com o diagrama que apareceu na seção anterior. No entanto, os três projectos
de plataformas em um aplicativo Xamarin.Forms são tipicamente muito pequenos, muitas vezes consistindo de apenas stubs com um
pouco de código de inicialização do clichê. O Projeto de propriedade compartilhada, ou no portátil projeto Class Library, contém a maior
parte do aplicativo, incluindo o código da interface pelo usuário:

As bibliotecas Xamarin.Forms.Core e Xamarin.Forms.Xaml implementar a API Xamarin.Forms. Dependendo da plataforma,


Xamarin.Forms.Core seguida, faz uso de uma das bibliotecas Xamarin.Forms.Platform. Essas bibliotecas são na maior parte uma coleção
de classes chamados representantes que transformam os objetos de interface do usuário Xama- rin.Forms na interface do usuário
específico da plataforma.
O restante do diagrama é o mesmo que o mostrado anteriormente.
Por exemplo, suponha que você precisa do objeto de interface do usuário discutido anteriormente que permite ao usuário para alternar
um valor booleano. Ao programar para Xamarin.Forms, isso é chamado de Switch, e uma classe chamada Mudar é implementada na
biblioteca Xamarin.Forms.Core. Na os representantes individuais para as três plataformas, este switch é mapeado para um UISwitch no
iPhone, um interruptor no Android, e um telefone ToggleSwitchButton no Windows.
Xamarin.Forms.Core também contém uma classe chamada Slider para a exibição de uma barra horizontal que o usuário manipula para
escolher um valor numérico. Na os representantes nas bibliotecas específicas da plataforma, este é mapeado para um UISlider no iPhone,
a SeekBar no Android, e um telefone deslizante no Windows.
Isto significa que quando você escreve um programa Xamarin.Forms que tem um interruptor ou um Slider, o que está realmente exibido
é o objeto correspondente implementado em cada plataforma.
Aqui está um pequeno programa Xamarin.Forms contendo uma etiqueta dizendo "Olá, Xamarin.Forms!", Um botão que diz "Clique-me!",
Um interruptor e um Slider. O programa está sendo executado em (a partir da esquerda para a direita) o iPhone, Android e Windows
Phone:
Você verá os screenshots triplos como este ao longo deste livro. Elas estão sempre na mesma ordem-iPhone, Android e Windows Phone
e neles estão correndo sempre o mesmo programa.

Como você pode ver, o Button, switch e Slider todos têm aparências diferentes nas três telefones porque todos eles são renderizados
com o objeto específico para cada plataforma.
O que é ainda mais interessante é a inclusão neste programa de objetos Ponto Seis barra de ferramentas, três identificados como itens
primários com ícones, e três como itens secundários sem ícones. No iPhone estes são renderizados com objetos UIBarButtonItem como
os três ícones e três botões na parte superior da página. No Android, os três primeiros são processados como itens em um ActionBar,
também no topo da página. No Windows Phone, eles perceberam como itens no ApplicationBar na parte inferior da página.
O ActionBar Android tem umas reticências vertical eo Windows Phone ApplicationBar tem uma elipse horizontal. Tocar nestas reticências
faz com que os itens secundários para ser exibido em uma forma apropriada para estas duas plataformas:

Suporte XAML

Xamarin.Forms também suporta XAML (pronuncia-se "zammel" para rimar com "camelo"), a baseada em XML Extensible Application
Markup Language desenvolvido na Microsoft como uma linguagem de marcação de uso geral para instanciar e inicializar objetos. XAML
não se limita a definição de layouts iniciais de interfaces de usuário, mas historicamente, é assim que tem sido o mais utilizado, e é isso
que ele é usado no Xamarin.Forms.Here’s the XAML file for the program whose screenshots you’ve just seen:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatformVisuals.PlatformVisualsPage"
Title="Visuals">

<StackLayout Padding="10,0">
<Label Text="Hello, Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Button Text = "Click Me!"


VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Switch VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Slider VerticalOptions="CenterAndExpand" />


</StackLayout>

<ContentPage.ToolbarItems>
<ToolbarItem Text="edit" Order="Primary">
<ToolbarItem.Icon>
<OnPlatform x:TypeArguments="FileImageSource" iOS="edit.png"
Android="ic_action_edit.png"
WinPhone="Images/edit.png" />
</ToolbarItem.Icon>
</ToolbarItem>

<ToolbarItem Text="search" Order="Primary">


<ToolbarItem.Icon>
<OnPlatform x:TypeArguments="FileImageSource" iOS="search.png"
Android="ic_action_search.png"
WinPhone="Images/feature.search.png" />
</ToolbarItem.Icon>
</ToolbarItem>

<ToolbarItem Text="refresh" Order="Primary">


<ToolbarItem.Icon>
<OnPlatform x:TypeArguments="FileImageSource" iOS="reload.png"
Android="ic_action_refresh.png"
WinPhone="Images/refresh.png" />
</ToolbarItem.Icon>
</ToolbarItem>

<ToolbarItem Text="explore" Order="Secondary" />


<ToolbarItem Text="discover" Order="Secondary" />
<ToolbarItem Text="evolve" Order="Secondary" />
</ContentPage.ToolbarItems>
</ContentPage>

A menos que você tem experiência com XAML, alguns detalhes de sintaxe pode ser um pouco obscura. (Não se preocupe, você vai aprender
tudo sobre eles mais tarde neste livro.) Mas, mesmo assim, você pode ver as etiquetas etiqueta, botão, switch e Slider. Em um programa real,
o botão, o interruptor e Slider provavelmente teria evento manipuladores anexados que deveriam ser implementadas em um arquivo
de código C #. Aqui eles não. Os VerticalOptions e HorizontalOptions atributos ajudar no layout; eles são discutidos no próximo
capítulo.

Especificidade Platform
Na seção do arquivo XAML envolvendo a ToolbarItem, você também pode ver uma etiqueta com o nome OnPlat- formulário. Esta é uma das
várias técnicas em Xamarin.Forms que permitem a introdução de alguma plataforma espe- especifici- no código de outra forma independente
da plataforma ou marcação. Ele é usado aqui, porque cada uma das plataformas separadas tem requisitos um pouco diferentes de formato e
tamanho de imagem associados a esses ícones.
Uma instalação semelhante existe no código com a classe de dispositivos. É possível determinar qual plataforma o código está sendo
executado em e escolher valores ou objetos com base na plataforma. Por exemplo, você pode especificam os tamanhos de fonte diferentes
Ify para cada plataforma ou executar diferentes blocos de código com base na plataforma. Você pode querer deixar o usuário manipular um
controle deslizante para selecionar um valor em uma plataforma, mas escolher um número a partir de um conjunto de valores explícitos em
outra plataforma.
Em algumas aplicações, as especificidades de plataforma mais profundas pode ser desejado. Por exemplo, suponha que sua aplicaç~ao
requer as coordenadas GPS do telefone do usuário. Isso não é algo que a versão inicial de Xamarin.Forms fornece, de modo que você precisa
para escrever seu próprio código específico para cada plataforma para obter essa informação.
A classe DependencyService fornece uma maneira de fazer isso de uma forma estruturada. Você definir uma inter- face com os métodos que
você precisa (por exemplo, IGetCurrentLocation) e, em seguida, implementar essa interface com uma classe em cada um dos projectos de
plataforma. Você pode então chamar os métodos em que a interface do projeto Xamarin.Forms quase tão facilmente como se fosse parte da
API.
Como discutido anteriormente, cada um dos Xamarin.Forms padrão objetos, tais visuais como Etiqueta, Button, Switch, e Slider-são suportados
por uma classe de renderização nas três bibliotecas Xamarin.Forms.Platform. Cada classe de renderização implementa o objeto específico da
plataforma que mapeia para o objeto Xamarin.Forms.
Você pode criar seus próprios objetos visuais personalizados com seus próprios representantes personalizados. O objeto visual personalizado
vai no projecto de código comum, e os representantes de costume ir nos pro- jectos de plataforma individual. Para torná-lo um pouco mais
fácil, geralmente você vai querer derivar de uma classe existente. Dentro das bibliotecas da plataforma Xamarin.Forms individuais, todos os
representantes de correspondentes são classes públicas, e você pode deduzir a partir deles também.
Xamarin.Forms permite que você seja como plataforma independente ou como plataforma específica que você precisa para ser.
O Xamarin.Forms não substitui Xamarin.iOS e Xamarin.Android; em vez disso, ele se integra com eles.

A panacéia multi-plataforma?
Para a maior parte, Xamarin.Forms define suas abstrações com foco em áreas da interface do usuário móvel que são comuns a iOS, Android
e Windows Phone. Esses objetos visuais Xamarin.Forms são mapeados para objetos específicos da plataforma, mas Xamarin.Forms tende a
evitar a implementação de qualquer coisa que é exclusivo para uma plataforma particular.
Por esta razão, apesar da enorme ajuda que Xamarin.Forms pode oferecer na criação de plataforma aplicações independentes,
não é um substituto completo para a programação API nativa. Se a sua aplicação depende fortemente de recursos da API
nativas, como determinados tipos de controles ou widgets, então você pode querer ficar com Xamarin.iOS, Xamarin.Android,
eo nativo do Windows API Phone.
Provavelmente você também quiser ficar com as APIs nativas para aplicações que exigem gráficos vetoriais ou complexa
interação sensível ao toque. A versão atual do Xamarin.Forms não está completamente pronto para esses cenários.
Por outro lado, Xamarin.Forms é ótimo para prototipagem ou fazer uma rápida aplicação de prova de conceito. E depois de ter
feito isso, você pode simplesmente achar que você pode continuar usando Xamarin.Forms recursos para construir o aplicativo
inteiro. Xamarin.Forms é ideal para aplicações de linha de negócios.
Mesmo se você começar a construir uma aplicação com Xamarin.Forms e, em seguida, implementar grandes partes dele com
APIs da plataforma, você está fazendo isso dentro de uma estrutura que lhe permite compartilhar código e que oferece formas
estruturadas para fazer visuais específicos da plataforma.

Seu ambiente de desenvolvimento


Como você configurar seu hardware e software depende do que plataformas móvel que você está alvejando e quais ambientes
de computação são mais confortável para você.
Os requisitos para Xamarin.Forms não são diferentes dos requisitos para a utilização Xamarin.iOS ou Xamarin.Android ou para
programação para Windows Phone.
Isso significa que nada nesta secção (e no restante deste capítulo) é específico para Xamarin.Forms. Não existe muita
documentação no site do Xamarin sobre a criação de máquinas e software para Xamarin.iOS e programação Xamarin.Android,
e no site da Microsoft sobre o Windows Phone.

Máquinas e IDEs
Se você deseja alcançar o iPhone, você vai precisar de um Mac. A Apple exige que um Mac ser usado para a construção de
iPhone e outros iOS aplicações. Você precisa instalar o Xcode nesta máquina e, claro, a plataforma Xamarin que inclui as
bibliotecas necessárias e Xamarin Studio. Você pode então usar Xamarin Studio e Xamarin.Forms no Mac para o seu
desenvolvimento do iPhone.
Depois de ter um Mac com o Xcode ea plataforma Xamarin instalado, você também pode instalar a plataforma Xamarin em
um PC e um programa para o iPhone usando o Visual Studio. O PC e Mac devem ser ligados através de uma rede (tais como
Wi-Fi). Você corre o Xamarin.iOS Construir host no Mac para esta interconexão, e Visual Studio usa isso para criar e implantar
o executável no Mac.
Embora você possa executar Xamarin Studio no PC, você não pode usar Xamarin Estúdio no PC para fazer a programação
iPhone. Se você deseja direcionar os telefones Android, você tem muita flexibilidade. Você pode fazer isso usando Xamarin
Studio no Mac, Xamarin Studio no PC, ou Visual Studio no PC.
Se você deseja direcionar Windows Phone, você precisará usar o Visual Studio 2012 ou 2013, mas não uma edição Express.
Isto significa que se você quer atingir todas as três plataformas em um único IDE, você pode fazê-lo com Visual Studio rodando
em um PC conectado ao Mac através de uma rede. Outra opção é executar o Visual Studio em uma máquina virtual no Mac.

Dispositivos e emuladores
Você pode testar seus programas em telefones reais ligados à máquina através de um cabo USB, ou você pode testar seus programas com
emuladores na tela.
Há vantagens e desvantagens de cada abordagem. Um telefone real é essencial para testar complexa interação de toque ou quando começar
uma sensação para a inicialização ou tempo de resposta. No entanto, emuladores al-baixo que você veja como o aplicativo se adapta a uma
variedade de tamanhos e formatos.
Talvez o mais suave emulador em execução é que para o iPhone. No entanto, porque Mac máquinas de desktop não têm telas sensíveis ao
toque, você vai precisar usar o mouse ou trackpad para simular toque. Os gestos de toque no touchpad Mac não se traduzem para o emulador.
Você também pode conectar um iPhone real para o Mac, mas você vai precisar de disposição, como um dispositivo de desenvolvedor.
O emulador de Windows Phone é capaz de várias resoluções de tela diferentes e também tende a correr bastante bem, embora consumir
muita memória. Se você executar o emulador de Windows Phone em uma tela sensível ao toque, você pode usar o toque na tela do emulador.
Ligar um Windows Phone real para o PC exige desbloquear o telefone. Se você quiser desbloquear mais de um telefone, você vai precisar de
uma contagem desenvolvedor.
emuladores Android são os mais problemáticos. Eles tendem a ser lenta e irritada, apesar de serem muitas vezes, extremamente versátil na
emulando uma vasta gama de dispositivos Android reais. Do lado de cima, é muito fácil de conectar um telefone Android real para um Mac ou
PC para o teste. Tudo o que você realmente precisa fazer é permitir a depuração USB no dispositivo. Se você gosta da idéia de testes
em dispositivos reais, mas quero testar em mais dispositivos reais do que você pode gerenciar a si mesmo, olhar para o
Xamarin Teste Cloud.
Capítulo 2
Anatomia de um aplicativo
A interface de usuário moderna é construída a partir de objetos visuais de vários tipos. Dependendo do sistema operacional, esses objetos
virtuais podem ter diferentes nomes - controles, elementos, views, widgets - mas todos eles são dedicados aos trabalhos de apresentação ou
interação.
Em Xamarin.Forms, os objetos que aparecem na tela são chamados coletivamente de elementos visuais. Eles vêm em três categorias
principais:
• Page
• Layout
• View

Estes não são conceitos abstratos! A interface de programação de aplicativo Xamarin.Forms (API) define classes nomeadas VisualElement,
Page, Layout e View. Estas classes e seus descendentes a partir da espinha dorsal da interface do Xamarin.Forms. VisualElement é uma
classe extremamente importante em Xamarin.Forms. Um objeto VisualElement é algo que ocupa espaço na tela.
Uma aplicação Xamarin.Forms consiste de uma ou mais páginas. Uma página normalmente ocupa toda (ou pelo menos uma grande área) da
tela. Algumas aplicações são compostas por apenas uma única página, enquanto outros permitem navegar entre várias páginas. Em muitos
dos primeiros capítulos deste livro, você vai ver apenas um tipo de página, chamada de ContentPage.
Em cada página, os elementos visuais são organizados em uma hierarquia pai-filho. O filho de um ContentPage é geralmente um layout de
algum tipo para organizar os elementos visuais. Alguns layouts tem um único filho, mas muitos layouts têm vários filhos que o layout organiza
dentro de si. Estes filhos podem ser outros layouts ou views. Diferentes tipos de layouts de filhos providenciam uma pilha, num grid
bidimensional, ou de um modo mais livre.
O termo view em Xamarin.Forms indica tipos de objetos de apresentação e objetos interativos: texto, bitmaps, botões, campos de entrada de
texto, controles deslizantes, interruptores, barras de progresso, data e hora, e outros de sua própria invenção. Estes são muitas vezes
chamados de controles ou widgets em outros ambientes de programação. Este livro se refere a eles como views ou elements. Neste capítulo,
você encontrará o Label para mostrar no texto.

Diga Olá
Usando o Microsoft Visual Studio ou Xamarin Studio, vamos criar um novo aplicativo Xamarin.Forms usando um modelo padrão. Este processo
cria uma solução que contém até quatro projetos: três projetos de plataforma – iOS, Android e Windows Phone – e um projeto comum para a
maior parte do código do seu aplicativo.
No Visual Studio, selecione a opção do menu File > New > Project. À esquerda da caixa de diálogo New Project, selecione Visual C# e, em
seguida, Mobile Apps.
No Xamarin Studio, selecione File > New > Solution no menu, e no lado esquerdo da caixa de diálogo New Solution, selecione C# e, em
seguida, Mobile Apps.
Em ambos os casos, a seção central da caixa de diálogo lista três modelos de solução disponíveis:
• Blank App (Xamarin.Forms Portable)
• Blank App (Xamarin.Forms Shared)
• Class Library (Xamarin.Forms Portable)

E agora? Nós definitivamente queremos criar uma solução Blank App, ou seja, em branco, mas de que tipo?
O termo "Portable", neste contexto, refere-se a uma Biblioteca de Classes Portátil (PCL). Todo o código comum da aplicação torna-se uma
biblioteca de vínculo dinâmico (DLL) que é referenciado por todas as plataformas individuais do projeto.
O termo "Shared" neste contexto significa um projeto de recurso compartilhado (SAP) contendo arquivos de código solto (e talvez outros
arquivos) que são compartilhados entre os projetos de plataformas, essencialmente tornando-se parte de cada projeto de plataforma.
Por enquanto, escolha o primeiro: Blank App (Xamarin.Forms Portable). Selecione um local do disco para a solução, e dê um nome, por
exemplo, Hello.
Se você estiver executando o Visual Studio, quatro projetos são criados: um projeto comum (o projeto PCL) e três projetos de aplicação. Para
a solução chamada Hello, são eles:
• Um projeto Biblioteca de Classes Portátil chamado Hello que é referenciado pelos três aplicativos do projeto;
• Um projeto de aplicativo para o Android, chamado Hello.Droid;
• Um projeto de aplicativo para iOS, chamado Hello.iOS; e
• Um projeto de aplicativo para Windows Phone, chamado Hello.WinPhone.
Se você estiver executando Xamarin Studio no Mac, o projeto do Windows Phone não é criado, e se você estiver executando Xamarin Studio
no PC, nem o iOS nem o programa do Windows Phone é criado.
Antes de continuar, certifique-se de que as configurações do projeto estão corretas. No Visual Studio, selecione o item de menu Build >
Configuration Manager. Na caixa de diálogo Configuration Manager, você verá o projeto PCL e os três projetos de aplicação. Certifique-se
de que a caixa Build está verificada para todos os projetos e a caixa de Deploy está verificada para todos os projetos dos aplicativos (a não
ser que a caixa fica acinzentado). Selecinone na coluna Platform: Se o projeto Hello estiver na lista, ele deve ser sinalizado como Any CPU.
O projeto Hello.Droid também deve ser marcado como Any CPU. (Para os dois tipos de projetos, Any CPU é a única opção.) Para o projeto
Hello.iOS, escolha iPhone ou iPhoneSimulator dependendo de como você vai estar testando o programa. Para o projeto Hello.WinPhone,
você pode selecionar x86 se você estiver usando um emulador na tela, ARM se você vai estar instalando em um telefone real, ou Any CPU
para a implantação de um ou outro. Independentemente da sua escolha, Visual Studio gera o mesmo código.
Se um projeto não parecer na compilação ou implantação no Visual Studio, verifique novamente as configurações na caixa de diálogo
Configuration Manager. Às vezes, uma configuração diferente torna-se ativo e pode não incluir o projeto PCL.
No Xamarin Studio no Mac, você pode alternar entre instalar no iPhone e simulador iPhone através do menu Project > Active Configuration.
No Visual Studio, você provavelmente vai querer exibir as barras de ferramentas iOS e Android. Essas barras de ferramentas permitem que
você escolha entre os emuladores e dispositivos e permitem que você gerencie os emuladores. No menu principal, certifique-se que View >
Toolbars > iOS e Views > Toolbars > Android estão checados.
Como a solução contém em qualquer lugar de dois a quatro projetos, você deve designar que programa é iniciado quando você optar por
executar ou depurar um aplicativo.
No Solution Explorer do Visual Studio, clique com o botão direito em qualquer um dos três projetos de aplicação e selecione Set As StartUp
Project no item do menu. Você pode então selecionar a posicionar ou um emulador ou um dispositivo real. Para criar e executar o programa,
selecione o item de menu Debug> Start Debugging.
Na Solution no Xamarin Studio, clique no ícone de ferramenta pequena que aparece à direita de um projeto selecionado e selecione Set As
StartUp Project a partir do menu. Você pode então escolher Run > Start Debugging no menu principal.
Se tudo correr bem, o esqueleto do aplicativo criado pelo modelo será executado e você verá uma pequena mensagem:

Como você pode ver, estas plataformas têm diferentes esquemas de cores. Por padrão, o esquema de cores do Windows Phone é como o
esquema de cores Android em que ele exibe o texto claro em um fundo escuro, mas no Windows Phone este esquema de cores é mutável
pelo usuário. Mesmo em um emulador do Windows Phone, você pode alterar o esquema de cores na seção Themes do aplicativo Settings
e, em seguida, execute novamente o programa.
O aplicativo não só é executado no dispositivo ou emulador, mas instalado. Ele aparece com as outras aplicações no telefone ou emulador e
pode ser executado a partir de lá. Se você não gosta do ícone do aplicativo ou como o nome do aplicativo é exibido, você pode mudar isso
nos projetos de plataformas individuais.

Dentro dos arquivos


Claramente, o programa criado pelo modelo Xamarin.Forms é muito simples, por isso esta é uma excelente oportunidade para examinar os
arquivos de código gerado e descobrir suas inter-relações e como elas funcionam.
Vamos começar com o código que é responsável por definir o texto que você vê na tela. Esta é a classe App no projeto Olá. No projeto criado
pelo Visual Studio, a classe App é definido no arquivo App.cs, mas no Xamarin Studio, o arquivo é Hello.cs.
Se o modelo de projeto não mudou muito desde que este capítulo foi escrito, provavelmente é algo como isto:

Observe que o espaço para nome é o mesmo que o nome do projeto. Esta classe App é definida como pública e deriva da classe
Xamarin.Forms Application. O construtor realmente tem apenas uma responsabilidade: definir a propriedade MainPage da classe
Application para um objeto do tipo de página.
O código que o modelo Xamarin.Forms mostra uma abordagem muito simples para definir esse construtor: A classe ContentPage deriva da
página e é muito comum em uma única página Xamarin.Forms Application. (Você vai ver um monte de ContentPage ao longo deste livro.)
Ele ocupa a maior parte da tela do telefone com a exceção da barra de status no topo da tela Android, os botões na parte inferior da tela
Android, e a barra de status na parte superior da tela do Windows Phone. (Como você vai descobrir, a barra de status iOS é realmente parte
do ContentPage.)
A classe ContentPage define uma propriedade chamada Content que você definiu para o conteúdo da página. Geralmente este conteúdo é
um layout que por sua vez contém um monte de views, e, neste caso, ele é definido como um StackLayout, que organiza seus filhos em uma
pilha.
Este StackLayout tem apenas um filho, que é um Label. A classe Label deriva da View e é usada em aplicações Xamarin.Forms para exibir
até um parágrafo de texto. O VerticalOptions e a propriedade xalign são discutidos em mais detalhes mais adiante neste capítulo.
Para seus próprios aplicativos Xamarin.Forms uma única página, você geralmente irá definir sua própria classe que deriva de ContentPage.
O construtor da classe App, define uma instância da classe que você define a sua propriedade MainPage. Você vai ver como isso funciona
em breve.
Na solução Hello, você também verá um arquivo AssemblyInfo.cs para a criação do PCL e um arquivo de packages.config que contém os
pacotes NuGet exigidos pelo programa. Na seção de referências em Olá na lista de solução, você verá as três bibliotecas este PCL requer:
• .NET (displayed as .NET Portable Subset in Xamarin Studio)

• Xamarin.Forms.Core

• Xamarin.Forms.Xaml

Este é um projeto PCL que irá receber a maior parte de sua atenção como você está escrevendo uma aplicação Xamarin.Forms. Em algumas
circunstâncias, o código neste projeto pode exigir alguma adaptação para as três plataformas diferentes, e você verá em breve como fazer
isso. Você também pode incluir código específico da plataforma nos três projetos de aplicação.
Os três projetos de aplicação têm os seus próprios assets na forma de ícones e metadados, e você deve prestar atenção especial a esses
assets, se você pretende trazer a aplicação para o mercado. Mas durante o tempo que você está aprendendo a desenvolver aplicações usando
Xamarin.Forms, esses assets podem geralmente ser ignorado. Você provavelmente vai querer manter estes projetos de aplicação collapsed
na solução, porque você não precisa se preocupar muito com seus conteúdos.
Mas você realmente deve saber o que está nesses projetos de aplicativos, por isso vamos dar uma olhada mais de perto.
Na seção de referências de cada projeto de aplicativo, você verá referências comuns para o projeto PCL (Olá, neste caso), bem como vários
conjuntos .NET, os Xamarin.Forms assembles listados acima, e Xamarin.Forms assembles adicionais aplicáveis a cada plataforma:
• Xamarin.Forms.Platform.Android

• Xamarin.Forms.Platform.iOS

• Xamarin.Forms.Platform.WP8

Cada uma destas três bibliotecas define um método estático Forms.Init no namespace Xamarin.Forms que inicializa o sistema Xamarin.Forms
para essa plataforma particular. O código de inicialização em cada plataforma deve fazer uma chamada para esse método.
Você também acabou de ver que o projeto PCL deriva uma classe pública denominada App que deriva da Aplicação. O código de inicialização
em cada plataforma também deve instanciar essa classe App.
Se você estiver familiarizado com iOS, Android, ou o desenvolvimento do Windows Phone, você pode estar curioso para ver como o código
de inicialização da plataforma lida com essa forma de trabalho.

O Projeto IOS
Um projeto iOS normalmente contém uma classe que deriva de UIApplicationDelegate. No entanto, a biblioteca Xamarin.Forms.Platform.iOS
define uma classe base denominada FormsApplicationDelegate. No projeto Hello.iOS, você verá esse arquivo AppDelegate.cs, aqui usando
diretivas e comentários:

A substituição FinishedLaunching começa chamando o método Forms.Init definido no assemble Xamarin.Forms.Platform.iOS. Em seguida,
chama um método LoadApplication (definido pelo FormsApplicationDelegate), passando a ele uma nova instância da classe App definido no
PCL compartilhada. O objeto da página definido para a propriedade MainPage deste objeto App pode ser usado para criar um objeto do tipo
UIViewController, que é responsável por renderizar o conteúdo da página.

O Projeto Android
No aplicativo Android, a classe MainActivity típico deve ser derivado de uma classe chamada FormsApplicationActivity Xamarin.Forms definido
no Xamarin.Forms.Platform.Android assemble, e a chamada Forms.Init requer algumas informações adicionais:
A nova instância da classe App é então passada para um método LoadApplication definido por FormsApplicationActivity. O atributo definido
na classe MainActivity indica que a atividade não é recriada quando o telefone muda de orientação (de retrato para paisagem ou para trás) ou
o tamanho da tela muda.

O Projeto Windows Phone


No projeto do Windows Phone, veja o arquivo MainPage.xaml.cs escondido debaixo do arquivo Main-Page.xaml na lista de arquivos de projeto.
Esse arquivo define a classe MainPage habitual, mas perceba que ele deriva de uma classe Xamarin.Forms chamada FormsApplicationPage.
A classe App recém-instanciada é passada para o método LoadApplication definido por esta classe base:

A configuração da propriedade SupportedOrientations permite que o telefone responda às mudanças de orientação entre retrato e paisagem.
Os projetos iOS e Android são ativados para mudanças de orientação por padrão, bem, então você deve ser capaz de transformar o lado do
telefone ou o simulador para o outro e ver o texto realinhados no centro da tela.

Nada especial!
Se você criou uma solução Xamarin.Forms sob Visual Studio e não deseja direcionar uma ou mais plataformas, simplesmente apague esses
projetos.
Se mais tarde você mudar de ideia sobre esses projetos, ou você originalmente criou a solução no Xamarin Studio e quer movê-lo para o
Visual Studio para Windows Phone que você pode adicionar novos projetos de plataforma para a solução Xamarin.Forms. Na caixa de diálogo
Add New Project, você pode criar uma API Unified (não API Classic) Xamarin.iOS projeto, selecionando o tipo Universal projeto iOS e modelo
de Blank App. Criar um projeto Xamarin.Android com o modelo em Blank App Android ou um projeto Windows Phone 8.1 Silverlight,
selecionando Store Apps, em seguida, Windows Phone Apps, em seguida, Blank App (Windows Phone Silverlight) template.
Para estes novos projetos, você pode obter as referências corretas e código consultando os projetos gerados pelo modelo Xamarin.Forms
padrão.
Para resumir: não há realmente nada de tão especial em um aplicativo Xamarin.Forms comparando com projetos Xamarin ou Windows Phone,
exceto as bibliotecas Xamarin.Forms.

PCL ou SAP?
Quando criou a solução Hello, você tinha uma escolha de dois modelos de aplicação:
• Blank App (Xamarin.Forms Portable)

• Blank App (Xamarin.Forms Shared)

A primeira cria uma Biblioteca de Classes Portátil (PCL), enquanto o segundo cria um projeto recurso compartilhado (SAP), consistindo apenas
em arquivos de código compartilhado. A solução original Hello utiliza o modelo de PCL. Agora vamos criar uma segunda solução nomeado
HelloSap com o modelo de SAP.

Como você vai ver, tudo parece praticamente o mesmo, exceto que o próprio projeto HelloSap contém apenas um item: o arquivo App.cs.
Tanto as abordagens PCL e SAP, o código é compartilhado entre as três aplicações, mas de maneiras diferentes: Com a aproximação PCL,
todo o código comum é empacotado em uma bilbioteda de dynamic-link que cada projeto referência na aplicação e liga em tempo de execução.
Com a abordagem SAP, os arquivos de código comuns são efetivamente incluídos com os três projetos de aplicação em tempo de compilação.
Por padrão, a SAP tem apenas um único arquivo chamado App.cs, mas de forma eficaz, é como se não existisse esse projeto HelloSap e,
em vez havia três cópias diferentes deste arquivo nos três projetos da aplicação.
Alguns problemas sutis (e não tão sutis) podem manifestar-se com a Blank App (Xamarin.Forms Shared) template:
Os projetos iOS e Android têm acesso a praticamente a mesma versão do .NET, mas não é a mesma versão do .NET que um projeto do
Windows Phone usa. Isto significa que quaisquer classes .NET acessado pelo código partilhado pode ser um pouco diferente, dependendo da
plataforma. Como você vai descobrir mais tarde neste livro, este é o caso de algum arquivo I / O classes e o namespace System.IO.
Você pode compensar estas diferenças, usando diretivas de pré-processador C#, particularmente #if e #elif. Nos projetos gerados pelo modelo
Xamarin.Forms, os projetos Windows Phone e iPhone definem símbolos que você pode usar com estas diretivas.

Quais são esses símbolos?


No Visual Studio, clique com o botão direito do mouse no nome do projeto no Solution Explorer e selecione Properties. À esquerda da tela
de propriedades, selecione Build, e olhe para o campo Conditional compilation symbols.
No Xamarin Studio, selecione um projeto de aplicativo na lista Solution, chame o menu de ferramentas drop-down e selecione Options. Na
esquerda da caixa de diálogo Project Options, selecione Build > Compiler, e olhe para o campo Define Symbols.
Você descobre que o símbolo __IOS__ está definido para o projeto iOS (que é dois sublinhados antes e depois) e WINDOWS_PHONE está
definido para o projeto do Windows Phone. Você não vai ver nada para o Android, mas o __ANDROID__ identificador é definido de qualquer
maneira, assim como vários identificadores __ANDROID_nn__, onde nn é cada nível API Android suportado. Seu arquivo de código
compartilhado pode incluir blocos como este:

Isso permite que seus arquivos de código compartilhado possam executar as classes específicas da plataforma de código ou de acesso
específicos da plataforma, incluindo nos projetos de plataformas individuais. Você também pode definir os seus próprios símbolos condicionais,
que você gostaria.
Estas diretivas de pré-processador não fazem sentido em um projeto de Biblioteca de Classes Portátil. O PCL é totalmente independente das
três plataformas, e esses identificadores nos projetos da plataforma são ignoradas quando o PCL é compilado.
O conceito do PCL originalmente surgiu porque todas as plataformas que usam .NET, na verdade, usam um pouco diferente o subconjunto
de .NET. Se você deseja criar uma biblioteca que pode ser usada entre múltiplas plataformas .NET, você precisa usar apenas as partes
comuns desses subconjuntos .NET.
O PCL se destina a ajudar, contendo código que é utilizável em vários (mas específicos) formas da plataforma .NET. Consequentemente,
qualquer PCL particular contém algumas bandeiras embutidos que indicam quais plataformas que ele suporta. A PCL usado em um aplicativo
Xamarin.Forms deve suportar as seguintes plataformas:
• .NET Framework 4.5
• Windows 8
• Windows Phone Silverlight 8
• Xamarin.Android
• Xamarin.iOS
• Xamarin.iOS (Classic)

Se você precisar de comportamento um específico da plataforma no PCL, você não pode usar as diretivas de pré-processador C# são código
que são usandos apenas em tempo de compilação. Você precisa de algo que funcione em tempo de execução, tais como a classe de
dispositivos Xamarin.Forms. Você verá em breve um exemplo.
O Xamarin.Forms PCL pode acessar outras PCLs suportando as mesmas plataformas, mas não pode acessar diretamente as classes definidas
nos projetos de aplicativos individuais. No entanto, se isso é algo que você precisa fazer e você vai ver um exemplo no capítulo 9.
Xamarin.Forms fornece uma classe chamada Dependency Service que lhe permite acessar o código específico da plataforma do PCL de
maneira metódica.
A maioria dos programas neste livro usa a abordagem PCL. Esta é a abordagem recomendada para Xamarin.Forms e é preferido por muitos
programadores que trabalham com Xamarin.Forms. No entanto, a abordagem SAP também é suportada e definitivamente tem seus defensores
também.
Mas por que escolher? Pode ter ambos na mesma solução. Se você criou uma solução Xamarin.Forms solução com um projeto recurso
compartilhado, você pode adicionar um novo projeto PCL para a solução, selecionando a biblioteca de classes (Xamarin.Forms Portable)
modelo. Os projetos de aplicativos podem acessar tanto a SAP e PCL, ea SAP pode acessar o PCL também.

Labels para o texto


Vamos criar uma nova solução Xamarin.Forms PCL, Greetings, usando o mesmo processo descrito acima para criar a solução Hello. Esta
nova solução será estruturada mais como um programa Xamarin.Forms, o que significa que ele irá definir uma nova classe que deriva de
ContentPage. Na maioria das vezes neste livro, todas as classes e estruturas definidas por um programa terão seu próprio arquivo. Isto
significa que um novo arquivo deve ser adicionado ao projeto Greetings:
No Visual Studio, você pode clicar com o botão direito no projeto Greetings no Solution Explorer e selecione Add > New Item do menu. À
esquerda da caixa de diálogo Add New Item, selecione Visual C# e código, e na área central, selecione Forms ContentPage. (Cuidado: Há
também uma opção de formulários ContentView Não escolha esse!)
No Xamarin Studio, a partir do ícone da ferramenta no projeto Greetings, selecione Add > New File no menu. Na esquerda da caixa de diálogo
New File, selecione Forms, e na área central, selecione Forms ContentPage. (Cuidado: Há também Forms ContentView e formas
ContentPage Xaml. Não escolher esses!)
Em ambos os casos, de o nome do novo arquivo de GreetingsPage.cs.
O arquivo GreetingsPage.cs será inicializado com algum código para uma classe chamada GreetingsPage que deriva de ContentPage. Porque
ContentPage está no namespace Xamarin.Forms, usando uma diretiva que inclui namespace. A classe é definida como pública, mas ela não
precisa ser, porque não vai ser acessado diretamente de fora do projeto Greetings.
Vamos apagar todo o código no construtor GreetingsPage e a maioria das diretivas usadas, assim que o arquivo se parece com isto:

No construtor da classe GreetingsPage, deve-se instanciar um Label view, defina sua propriedade de texto, e definir essa instância Label para
a propriedade de conteúdo que GreetingsPage herda ContentPage:

Agora altere a classe App em App.cs para definir a propriedade MainPage a uma instância desta classe GreetingsPage:
É fácil esquecer este passo, e você vai ficar perplexo que seu programa parece ignorar completamente a sua classe de página e ainda diz
"Welcome to Xamarin Forms!"
É na classe GreetingsPage (e outros como ela), onde você estará gastando mais de seu tempo na programação do Xamarin.Form. Esta classe
pode conter o único código do aplicativo. Claro, você pode adicionar classes adicionais para o projeto, se você precisar.
A classe que deriva de ContentPage terá um nome que é o mesmo que o aplicativo, mas com Página anexada. Essa convenção de
nomenclatura deve ajudá-lo a identificar as listagens de código neste livro a partir de apenas o nome da classe ou construtor sem ver o arquivo
inteiro. Na maioria dos casos, os trechos de código nas páginas deste livro não incluem as diretivas using ou a definição namespace.
Muitos programadores Xamarin.Forms preferem usar o estilo de criação de objetos e inicialização de propriedade C# 3.0 em seus construtores
da página. Seguindo o construtor Label, um par de chaves delimitam uma ou mais definições de propriedades separadas por vírgula. Está
aqui uma alternativa (mas funcionalmente equivalente) definição GreetingsPage:

Este estilo permite a ocorrência de Label a ser definida para a propriedade de conteúdo diretamente, de modo que a etiqueta não exige um
nome, assim:
Para mais layouts de páginas complexos, este estilo de instanciação e inicialização fornece um visual melhor analógico da organização de
layouts e views na página. No entanto, nem sempre é tão simples como este exemplo pode indicar se você precisa chamar métodos nesses
objetos ou manipuladores de eventos definidos.
Independentemente da forma como você faz isso, se você pode compilar e executar o programa nas três formas de plataformas de cada
emulador ou um dispositivo com sucesso, aqui está o que você vai ver:

A versão deste programa Greetings é definitivamente o iPhone: A partir do iOS 7, um aplicativo uma única página partes da tela com a barra
de status no topo. Qualquer coisa exibida na parte superior de sua página vai ocupar o mesmo espaço que a barra de status, a menos que a
aplicação do compense para ele.
Este problema desaparece em aplicações multipage-navigation discutidos mais adiante neste livro, mas até esse momento, aqui estão quatro
maneiras (ou cinco maneiras se você estiver usando um SAP) para resolver este problema imediatamente.

1. Incluir preenchimento na página


A classe de página define uma propriedade chamada Padding que marca uma área em torno do perímetro interior da página em que o conteúdo
não pode intrometer. A propriedade Padding é do tipo de espessura, uma estrutura que define quatro propriedades nomeadas Esquerda,
Cima, Direita, Inferior. (Você pode querer memorizar essa ordem que você vai definir as propriedades no construtor de espessura, bem como
em XAML.). A propriedade Espessura também define construtores para definir a mesma quantidade de preenchimento em todos os quatro
lados ou para definir a mesma quantidade, à esquerda e à direita e na parte superior e na parte inferior.
Um pouco de pesquisa no seu motor de busca favorito irá revelar que a barra de status iOS tem uma altura de 20. (Vinte quê? Você pode
perguntar. Vinte pixels? Na verdade, não. Por agora, basta pensar neles como 20 "unidades". Para a maioria dos Xamarin.Forms você não
precisa se preocupar com tamanhos numéricos, mas o Capítulo 5, "Lidar com tamanhos", irá fornecer alguma orientação quando você precisa
chegar até o nível de pixel).
Você pode acomodar a barra de status da seguinte forma:

Agora o cumprimento aparece 20 unidades a partir do topo da página:


A definição da propriedade Padding no ContentPage resolve o problema do texto substituindo a barra de status iOS, mas também define o
mesmo preenchimento no Android e Windows Phone, onde não é necessária. Existe uma maneira de definir este preenchimento apenas no
iPhone?

2. Incluir preenchimento apenas para IOS


Uma das vantagens da abordagem de projeto recurso compartilhado (SAP) é que as classes no projeto são extensões dos projetos de
aplicativos, assim você pode usar diretivas de compilação condicional.
Vamos tentar fazer isso. Vamos precisar de uma nova solução chamada GreetingsSap com base no modelo SAP, e uma nova classe de
página no projeto GreetingsSap chamado GreetingsSapPage. Essa classe tem esta aparência:

A diretiva #if referência o símbolo de compilação condicional __IOS__, portanto, a propriedade Padding é definida somente para o projeto iOS.
Os resultados são parecidos com este:
No entanto, esses símbolos de compilação condicional afetam somente a compilação do programa, para que eles não têm nenhum efeito em
um PCL. Existe uma maneira para um projeto PCL para incluir estofamento diferente para as três plataformas?

3. Incluir preenchimento apenas para IOS (PCL ou SAP)


Sim! A classe de dispositivos estático inclui várias propriedades e métodos que permitem que seu código possa lidar com as diferenças de
dispositivos em tempo de execução de uma forma muito simples e direta:
• A propriedade Device.OS retorna um membro da enumeração TargetPlatform: iOS, Android, WinPhone, ou Outro.
• A propriedade Device.Idiom retorna um membro da enumeração TargetIdiom: Telefone, Tablet, Desktop ou não suportado. (Embora
Xamarin.Forms é principalmente destinado a telefones, certamente você pode experimentar com a implantação de tablets.)
Você pode usar essas duas propriedades em if e else para executar código específico para uma determinada plataforma.
Dois métodos chamados OnPlatform oferece soluções ainda mais elegante:
• O método estático genérico OnPlatform <T> tem três argumentos do tipo T-o primeiro para iOS, o segundo para o Android, ea
terceira para Windows Phone e retorna o argumento para a plataforma de execução.
• O método estático OnPlatform tem quatro argumentos do tipo de acção (a função de delegado .NET que não tem argumentos e
retorna void), também na ordem iOS, Android e Windows Phone, com um quarto para um padrão, e executa o argumento para a
plataforma de execução.

A classe de dispositivo tem outros fins também: Ela define um método estático para começar a correr um temporizador e uma outra para
executar algum código no thread principal. Este último método é útil quando você está trabalhando com métodos assíncronos e suplementares
porque o código que manipula a interface do usuário geralmente pode ser executado apenas no segmento principal da interface do usuário.
Em vez de definir a mesma propriedade Padding em todas as três plataformas, você pode restringir o esforço para apenas o iPhone usando
o método genérico Device.OnPlatform:

O primeiro argumento é Espessura para iOS, o segundo é para o Android, e a terceira é para o Windows Phone. Especificando explicitamente
o tipo dos argumentos Device.OnPlatform dentro dos suportes de ângulo não é necessária se o compilador pode descobrir a partir dos
argumentos, assim que isso funciona bem:

Ou, você pode ter apenas um construtor de espessura e usar Device.OnPlatform para o segundo argumento:

Isto é como o Preenchimento geralmente será definido nos programas que se seguem quando é exigido. Claro, você pode substituir alguns
outros números para 0s se você quer um pouco de preenchimento adicional na página. Às vezes, um pouco acima dos lados traz uma exibição
mais atraente.
No entanto, se você só precisa definir padding para iOS, você pode usar a versão de Device.OnPlatform com argumentos de ação. Estes
argumentos são nulos por padrão, então você pode apenas definir o primeiro de uma ação a ser realizada no iOS:
Agora, a instrução para definir o preenchimento é executada somente quando o programa está sendo executado no iOS. Claro que, com
apenas um argumento para Device.OnPlatform, poderia ser um pouco obscuro para as pessoas que precisam ler o seu código, de modo que
você pode querer incluir o nome do parâmetro anterior ao argumento para torná-lo explícito que esta instrução é executada apenas para iOS:

Nomeando o argumento como esse é um recurso introduzido no C# 4.0.


O método Device.OnPlatform é muito útil e tem a vantagem de trabalhar em ambos os projetos PCL e SAP. No entanto, não se pode aceder
APIs dentro das plataformas individuais. Para isso você vai precisar da DependencyService, que é discutido no Capítulo 9.

4. Centralizar o label com a página


O problema com o texto sobrepondo a barra de status iOS ocorre apenas porque o texto, por padrão, é exibido no canto superior esquerdo. É
possível centralizar o texto na página?
Xamarin.Forms suporta um número de instalações para facilitar o layout sem exigir que o programa de cálculos por forma envolvendo os
tamanhos e coordenadas. A classe View define duas propriedades, HorizontalOptions e VerticalOptions nomeados, que especificam como
uma view, deve ser posicionada em relação ao respectivo elemento principal (neste caso o ContentPage). Essas duas propriedades são de
LayoutOptions tipo, uma estrutura com oito campos estáticos públicos de somente leitura que retornam valores LayoutOptions: 
• Start
• Center
• End
• Fill
• StartAndExpand
• CenterAndExpand
• EndAndExpand
• FillAndExpand

A estrutura LayoutOptions também define duas propriedades da ocorrência que lhe permitem formular estas mesmas combinações: 
• Uma propriedade Alinhamento do tipo LayoutAlignment, uma enumeração com quatro membros: Iniciar, Centro, End, e Fill.
• Uma propriedade Expande do tipo bool.

A explicação mais completa de como funcionam estes, espera por você no Capítulo 4, "Rolagem a pilha", mas por agora você pode definir as
propriedades HorizontalOptions e VerticalOptions do rótulo para um dos campos estáticos definidos por valores LayoutOptions. Para
HorizontalOptions, a palavra significa Iniciar esquerda e End significa direita; para VerticalOptions, Start significa superior e End significa
inferior.
Dominar o uso das propriedades HorizontalOptions e VerticalOptions é uma parte importante de adquirir habilidade no sistema de layout
Xamarin.Forms, mas aqui está um exemplo simples que posiciona a etiqueta no centro da página:
Aqui está como é exibido:

Esta é a versão do programa Greetings que está incluído no código de exemplo para este capítulo. Você pode usar várias combinações de
HorizontalOptions e VerticalOptions para posicionar o texto em qualquer um dos nove lugares em relação à página.

5. Centralizar o texto com o label


O Label é destinado para exibir o texto até um parágrafo de comprimento. Muitas vezes é desejável para controlar como as linhas de texto
são alinhadas horizontalmente: alinhado à esquerda, à direita justificada, ou centralizado.
O Label view define uma propriedade xalign para o efeito e também uma propriedade YAlign para o texto posicionamento verticalmente. Ambas
as propriedades são definidas para um membro da enumeração TextAlignment, que tem membros nomeados em Iniciar, Centro, e acabam
de ser versátil o suficiente para o texto que vai da direita para a esquerda ou de cima para baixo. Para Inglês e outros idiomas europeus, Start
significa esquerda ou superior e End significa direita ou inferior.
Para esta solução definitiva para o problema barra de status iOS, definir xalign e YAlign para TextAlignment.Center:

Visualmente, o resultado com esta única linha de texto é o mesmo que definir HorizontalOptions e VerticalOptions para Center, e você também
pode usar várias combinações destas propriedades para posicionar o texto em um dos nove locais diferentes em torno da página. No entanto,
estas duas técnicas para centralizar o texto são realmente muito diferentes, como você verá no próximo capítulo.
Capítulo 3
Mais a fundo no texto
Apesar de quão gráficas as interfaces do usuário tornaram-se, o texto continua sendo a coluna vertebral da maioria das aplicações. Ainda
assim, o texto é potencialmente um dos objetos visuais mais complexos porque carrega uma bagagem de centenas de anos de tipografia. A
consideração primária é que o texto precisa ser legível. Isto requer que o texto não seja tão pequeno, nem deve ser tão grande que ocupe
grande espaço na tela.
Por estas razões, o assunto sobre texto será continuado em vários capítulos seguintes, como no Capítulo 5 “Tratando de tamanhos”.
Frequentemente, programadores Xamarin.Forms definem características da fonte nos estilos, os quais serão o assunto do Capítulo 12.

Quebra automática de texto nos parágrafos


Mostrar um parágrafo de texto é tão fácil como mostrar uma linha simples de texto. Apenas crie o texto longo o suficiente para fazer quebra
automática de texto em múltiplas linhas:

Note o uso de códigos Unicode embutidos para abertura e fechamento de “aspas duplas” (\u201C e \u201D) e para o travessão ‘em’ (\u2014).
A propriedade Padding foi ajustada para 5 unidades ao redor da página para evitar que o texto encoste nas bordas da tela, e a
VerticalOptions também foi usada para centralizar verticalmente todo o parágrafo na página:
Para este parágrafo de texto, ajustar a HorizontalOptions mudará todo o parágrafo horizontalmente levemente para a esquerda, centro
ou direita. O deslocamento é pequeno porque a largura do parágrafo é a largura da linha de texto mais longa. Já que a quebra de texto é
dirigida pela largura da página (menos o Padding), o parágrafo provavelmente ocupa apenas um pouco menos de largura que a largura
disponível para ele na página.
Porém, ajustar XAlign tem um efeito muito mais profundo: o ajuste desta propriedade afeta o alinhamento das linhas individualmente. Um
ajuste de TextAlignment.Center centralizará todas as linhas do parágrafo, e TextAlignment.Right alinhará todas à direita. Você pode
utilizar HorizontalOptions além de XAlign para deslocar todo o parágrafo levemente para o centro ou à direita.
No entanto, depois que você ajustar VerticalOptions para Start, Center, ou End, qualquer ajuste de YAlign não terá efeito.
o Label tem a propriedade LineBreakMode que você pode ajustar para uma das opções da lista enumerada de LineBreakMode do
Xamarin.Forms, se você não quiser que o texto quebre automaticamente, ou para selecionar opções de truncamento do texto.

Não há nenhuma propriedade para especificar um recuo da primeira linha para o parágrafo, mas você pode adicionar seu próprio recuo com
caracteres de espaço de vários tipos, tal como o código Unicode para espaço (\u2003).
Você pode mostrar vários parágrafos com um simples Label finalizando cada parágrafo com um ou mais caracteres de quebra de linha (\n).
No entanto, faz mais sentido usar um Label separado para cada parágrafo, como será demonstrado no Capítulo 4, “Rolando a pilha”.
A classe Label tem muita flexibilidade na formatação. Como você verá em breve, as propriedades definidas por Label permitem que você
especifique um tamanho de fonte, texto em negrito ou itálico, e você também pode especificar formatação de texto diferente dentro de um
único parágrafo.
O Label também permite especificar cores, e um pouco de experimentação com cores vai demonstrar a profunda diferença entre
HorizontalOptions e VerticalOptions e XAlign e YAlign.

Cores do texto e do fundo


Como você viu, o Label mostra o texto em uma cor apropriada para o dispositivo. Você pode substituir este comportamento definindo duas
propriedades, chamadas de TextColor e BackgroundColor. O Label autodefine o TextColor, mas ele herda o BackgroundColor
de VisualElement, o que significa que a Page e o Layout também têm uma propriedade BackgroundColor.
Você define TextColor e BackgroundColor com um valor do tipo Color, que é uma estrutura que define 17 campos estáticos para obtenção
de cores comuns. Você pode experimentar o uso destas propriedades com o programa Greetings do capítulo anterior. Aqui estão duas delas
usadas em conjunto com XAlign e YAlign para centralizar o texto:

O resultado pode surpreender você. Como as imagens a seguir ilustram, o Label realmente ocupa toda a área da página (incluindo embaixo
da barra de status do iOS), e as propriedades XAlign e YAlign posicionam o texto dentro desta área:
Aqui está um código onde as cores do texto são as mesmas, porém centraliza o texto usando as propriedades HorizontalOptions e
VeriticalOptions:

Agora o Label ocupa apenas o espaço necessário para o texto, e é posicionado no centro da página:

O valor padrão de HorizontalOptions e VerticalOptions não é LayoutOptions.Start, como a aparência padrão do texto poderia
sugerir. Em vez disso, o valor padrão é LayoutOptions.Fill. Este é o ajuste que faz com que o Label preencha toda a página. O valor padrão
de XAlign e YAlign é TextAlignment.Start, que fez com que o texto fosse posicionado no canto superior esquerdo na primeira versão
do programa Greetings no capítulo anterior.
Você pode se perguntar: quais são os valores padrões das propriedades TextColor e BackgroundColor, já que os valores padrões
resultam em cores diferentes para as diferentes plataformas?
O valor padrão de TextColor e BackgroundColor é na verdade um valor de cor especial chamado de Color.Default, o qual não
representa uma cor real, mas em vez disso é usada para referenciar a cor do texto e do fundo apropriada para a plataforma específica. Vamos
explorar a cor em mais detalhes.

A estrutura Color
Internamente, a estrutura Color armazena cores em duas maneiras diferentes:

• Como valores de vermelho, verde e azul (RGB) do tipo double que variam de 0 a 1. Propriedades somente leitura chamadas R, G
e B exibem estes valores.
• Como valores de matiz, saturação e luminosidade do tipo double, que também variam de 0 a 1. Estes valores são exibidos com
propriedades somente leitura chamadas Hue, Saturation e Luminosity.
A estrutura Color também suporta um canal alfa para indicar graus de opacidade. Uma propriedade somente leitura chamada de A exibe
este valor, que varia de 0 para transparente até 1 para opaco. Todas estas propriedades são somente leitura. Uma vez criada, um valor de
Color é imutável.
Você pode criar um valor de Color em uma das várias maneiras. Os três construtores são as mais fáceis:

● new Color(double grayShade)


● new Color(double r, double g, double b)
● new Color(double r, double g, double b, double a)
Os argumentos podem variar de 0 a 1. Color também define vários métodos de criação estáticos, incluindo:
● Color.FromRgb(double r, double g, double b)
● Color.FromRgb(int r, int g, int b)
● Color.FromRgba(double r, double g, double b, double a)
● Color.FromRgba(int r, int g, int b, int a)
● Color.FromHsla(double h, double s, double l, double a)
Os dois métodos estáticos com argumentos inteiros aceitam valores entre 0 e 255, que é normalmente a representação das cores RGB.
Internamente, o construtor simplesmente divide valores inteiro por 255.0 para converter para double.

Cuidado! Você pode pensar que está criando uma cor vermelha com esta chamada:
Color.FromRgb(1, 0, 0)

No entanto, o compilador C# assumirá que estes valores são inteiros. O método de inteiro será invocado, e o primeiro argumento será dividido
por 255.0, com um resultado perto de zero. Se você quer usar o método que tem argumentos double, seja explícito:
Color.FromRgb(1.0, 0, 0)

Color também define métodos de criação estáticos para um pacote no formato uint e um no formato hexadecimal em uma string, mas
estes são menos usados.
A estrutura Color também define 17 campos somente leitura estáticos e públicos do tipo Color. Na tabela a seguir, os valores RGB inteiro
que a estrutura Color utiliza internamente para definir estes campos são mostrados juntos com os valores correspondentes de Hue,
Saturation, e Luminosity, com arredondamento para maior clareza.

Com exceção do Pink, você pode reconhecer estas cores como os nomes de cor suportadas em HTML. Um 18º campo somente leitura
estático e público é chamado de Transparent, o qual tem todas as propriedades R, G, B definidas com zero.
Quando as pessoas têm a oportunidade de formular interativamente uma cor, o modelo de cor HSL é muitas vezes mais intuitivo que RGB.
Os ciclos Hue atravessam as cores do espectro visível (e do arco-íris) começando com vermelho em 0, verde em 0.33, azul em 0.67, e volta
ao vermelho em 1.
A Saturation indica o grau da matiz na cor, que varia de 0, que é sem matiz e resulta em um tom de cinza, até 1 para total saturação.
A Luminosity é uma medida de luminosidade, variando de 0 para preto e 1 para branco.
Programas de seleção de cores no Capítulo 15, “Interface interativa”, permite-lhe explorar os modelos RGB e HSL mais interativamente.
A estrutura Color contem vários exemplos de métodos interessantes que permitem criar novas cores que são modificações de cores
existentes:
● AddLuminosity(double delta)
● MultiplyAlpha(double alpha)
● WithHue(double newHue)
● WithLuminosity(double newLuminosity)
● WithSaturation(double newSaturation)
Finalmente, Color define duas propriedades especiais somente leitura estáticas do tipo Color:

● Color.Default
● Color.Accent
A propriedade Color.Default é usada extensivamente com Xamarin.Forms para definir a cor padrão das visões. A classe VisualElement
inicializa sua propriedade BackgroundColor com Color.Default, e a classe Label inicializa sua propriedade TextColor com
Color.Default.
No entanto, Color.Default é um valor de Color com todas suas propriedades R, G, B e A definidas com -1, o que significa ser um valor
“falso” especial que não significa nada em sí, mas indica que o valor real é específico da plataforma.
Para Label e ContentPage (e a maioria das classes que derivam de VisualElement), a propriedade BackgroundColor definida com
Color.Default significa transparente.
No Label, o valor de TextColor com Color.Default significa preto em um dispositivo iOS, branco em um dispositivo Android, e branco
ou preto em Windows Phone, dependendo da cor do tema selecionado pelo usuário.
Sem você escrever código que explore os objetos de interface do usuário específicos da plataforma, seu programa Xamarin.Forms não pode
determinar se o esquema de cores básico é branco sobre preto ou preto sobre branco. Isto pode ser um pouco frustrante se você quiser criar
cores que são compatíveis com o esquema de cores ─ por exemplo, uma cor de texto azul escuro se o fundo padrão for branco, ou um texto
na cor amarelo claro se o fundo for escuro.
Você tem duas estratégias para trabalhar com cor: Você pode escolher por fazer sua programação Xamarin.Forms de uma maneira bem
independente da plataforma e evitar fazer qualquer suposição sobre o esquema de cores padrão de qualquer telefone. Ou, você pode usar o
seu conhecimento sobre os esquemas de cores das várias plataformas e usar Device.OnPlataform para especificar as cores específicas
da plataforma.
Mas não tente simplesmente ignorar todos os padrões da plataforma e defina explicitamente todas as cores em seu aplicativo para seu próprio
esquema de cores. Isto provavelmente não funcionará bem como você espera porque muitas visões usam outras cores que se relacionam
com o tema de cor do sistema operacional que não são expostos através das propriedades Xamarin.Forms.
Uma opção simples é usar a propriedade Color.Accent como uma cor de texto alternativa. Nas plataformas iPhone e Android, é uma cor
que é visível sobre o fundo padrão, mas não é a cor de texto padrão. No Windows Phone, é uma cor selecionada pelo usuário como parte do
tema de cor.
Você pode ter texto semitransparente ao definir TextColor para um valor de Color com uma propriedade A menor que 1. No entanto, se
você quer uma versão semitransparente da cor de texto padrão, use a propriedade Opacity do Label como alternativa. Esta propriedade
é definida pela classe VisualElement e tem 1 como valor padrão. Defina-o com valores menores que 1 para ter vários graus de
transparência.

Tamanho de Fontes e atributos


O Label usa uma fonte padrão (ou do sistema) definida para cada plataforma, porém o Label também define varias propriedades que você
pode usar para mudar esta fonte. Label é uma de apenas duas classes que tem propriedades relacionadas com fontes; Button é a outra.
As propriedades que permitem você alterar esta fonte são:
● FontFamily do tipo string
● FontSize do tipo double
● FontAttributes do tipo FontAttributes, uma enumeração com três membros: None, Blod e Italic.
Há também uma propriedade Font correspondente a estrutura Font, mas esta está obsoleta e não deve ser usada.
A mais difícil destas propriedades para usar é FontFamily. Em teoria você pode ajustar com o nome da família de fonte tal como “Times
Roman”, porem só irá funcionar se esta família de fonte em particular for suportada na plataforma. Por esta razão, você provavelmente vai
usar FontFamily conectado com Device.OnPlatform, e você precisará conhecer os nomes das famílias de fonte suportadas em cada
plataforma. Por isso, uma demonstração de FontFamily deve esperar um capitulo futuro.
A propriedade FontSize é um pouco estranha também. Você precisa de um número que indica aproximadamente a altura da fonte, mas
quais números você deveria usar? Este é um assunto difícil, e por esta razão, é deixado para o Capítulo 5, "Tratando de tamanhos," quando
as ferramentas para escolher um bom tamanho de fonte estarão disponíveis. Até lá, entretanto, a classe Device ajuda com o método estático
chamado GetNamedSize. Este método requer um membro da enumeração NamedSize:
● Default
● Micro
● Small
● Medium
● Large
GetNamedSize também requer o tipo da classe que você está dimensionando com este tamanho de fonte, e o argumento será
typeof(Label) ou typeof(Button). Você também pode usar uma instância do próprio Label ou Button em vez de Type, mas isto é
frequentemente inconveniente.
Um aviso: specificar NamedSize.Medium não necessariamente retornará o mesmo tamanho como NamedSize.Default.
FontAttributes é a menos complicada das três propriedades relacionadas com fonte para usar. Você pode especificar Bold ou Italic
ou ambas, como este pequeno trecho de código (adaptado do programa Greetings do capitulo anterior) demonstra:
Aqui está nas três plataformas:

Texto Formatado
Como você viu, Label tem uma propriedade Text que você pode definir como uma string. Mas Label também tem uma propriedade
alternativa FormattedText que constrói um parágrafo com formatação não uniforme.
A propriedade FormattedText é do tipo FormattedString, que tem uma propriedade Spans do tipo IList<Span>, uma coleção de
objetos Span. Cada objeto Span é um pedaço de texto formatado de maneira uniforme que é definido por seis propriedades:
● Text
● FontFamily
● FontSize
● FontAttributes
● ForegroundColor
● BackgroundColor
Aqui está uma maneira de instanciar um objeto FormattedString e então adicionar instâncias Span a sua propriedade de coleção Spans:
À medida que cada Span é criado, é passado diretamente ao método Add da coleção Spans. Note que o Label tem um FontSize de
NamedSize.Large, e o Span com a configuração Bold também tem explicitamente o mesmo tamanho. Quando um Span tem uma
configuração de FontAttributes, atualmente ele não herda a configuração FontSize do Label.
Alternativamente, é possível inicializar o conteúdo da coleção Spans envolvendo-o com um par de chaves. Dentro destas chaves, os objetos
Span são instanciados. Porque não é necessárias as chamadas de método, toda a inicialização de FormattedString pode ocorrer dentro
da inicialização do Label:

Esta é a versão do programa que você vai ver na coleção de código de exemplo para este capítulo. Independentemente de qual abordagem
você usa, aqui está como ele parece:
Você também pode usar a propriedade FormattedText para deixar palavras em itálico ou negrito dentro de um parágrafo, como o programa
VariableFormattedParagraph demonstra:

O parágrafo começa com um espaço ‘em’ (Unicode \u2003) e contém chamadas de aspas (\u201C e \u201D), e várias palavras estão em
itálico:

Você pode ter um único Label para mostrar múltiplas linhas ou parágrafos com a inserção de caracteres de quebra de linha (\n). Isto é
demonstrado no programa NamedFontSizes. Vários objetos Span são adicionados a um objeto FormattedString em um laço foreach.
Cada objeto Span usa um valor diferente para NamedFont e também mostra o tamanho real retornado de Device.GetNamedSize:
Note que um Span separado contém duas quebras de linha para espaçar as linhas individuais. Isto garante que o espaçamento entre linhas
é baseado no tamanho padrão da fonte em vez do tamanho da fonte exibida:

Estes não são tamanhos de pixel! Tal como acontece com a altura da barra de status iOS, é melhor se referir a estes tamanhos como uma
espécie de "unidades" de tamanho. Alguma clareza adicional será vista no Capítulo 5.
Claro que, o uso de vários objetos Span em um único Label não é uma boa maneira para renderizar múltiplos parágrafos de texto. Além
disso, o texto muitas vezes tem tantos parágrafos que deve ser rolado. Este é o trabalho para o próximo capítulo e a exploração de
StackLayout e ScrollView.
Capítulo 4
Rolando a pilha
Se você é como a maioria dos programadores, assim que viu a lista estática das propriedades Color no capítulo anterior, quis escrever um
programa para exibir todos eles, talvez usando a propriedade Text da Label para identificar a cor, e a propriedade TextColor para mostrar a
cor real.
Embora você possa fazer isso com uma única Label utilizando um objeto FormattedString, é muito mais fácil com vários objetos Label. Porque
vários objetos de Label estão envolvidos, este trabalho também requer alguma forma de exibir todos os objetos Label na tela.
A classe ContentPage define uma propriedade de conteúdo do tipo de visualização que você pode definir para um objeto - mas apenas um
objeto. Exibir múltiplas views exige a criação de conteúdo para uma instância de uma classe que pode ter vários herdeiros de tipo View. Essa
classe é a Layout<T>, que define uma propriedade Children do tipo IList <T>.
A classe Layout <T> é abstrata, mas quatro classes derivam de Layout <View>, uma classe que pode ter várias crianças do tipo View. Em
ordem alfabética, estas quatro classes são:
• AbsoluteLayout
• Grid
• RelativeLayout
• StackLayout
Cada uma delas organiza seus filhos de uma forma característica. Este capítulo centra-se na StackLayout

Pilhas de views
A classe StackLayout organiza seus filhos em uma pilha. Ela define apenas duas propriedades próprias:
• Orientation do tipo StackOrientation, uma enumeração com dois membros: Vertical (o padrão) e Horizontal.
• Spacing do tipo double, inicializando em 6.
StackLayout parece ideal para o trabalho de listagem de cores. Você pode usar o método Add definido por IList<T> para adicionar um herdeiro
à coleção Children da instância StackLayout. Aqui está um código que cria vários objetos de Label a partir de duas matrizes e, em seguida,
adiciona cada Label para a coleção Children de um StackLayout:

O objeto StackLayout pode então ser definido como a propriedade Content da página.
Mas a técnica de usar matrizes paralelas é bastante perigosa. E se elas estiverem fora de sincronia ou tiverem um número diferente de
elementos? Uma abordagem melhor é manter a cor e o nome juntos, talvez em uma pequena estrutura com campos de Color e Name, ou
como uma matriz de valores Tuple <Color, string>, ou como um tipo anônimo, como demonstrado no programa ColorLoop:
Ou você pode inicializar a propriedade Children de StackLayout com a coleção explicita de views (semelhante ao modo da coleção Spans do
objeto FormattedString que foi inicializado no capítulo anterior). O programa ColorList define a propriedade Content da página para um objeto
StackLayout que então tem sua propriedade Children inicializada com 17 Label views:

Você não precisa ver o código de todos os 17 filhos para ter uma ideia! Independentemente de como você preencha a coleção Children, aqui
está o resultado:
Obviamente, isto não é ótimo. Algumas cores não são visíveis a todos, e algumas delas são muito fracas para ler bem. Além disso, a lista
transborda a página em duas plataformas, e não há nenhuma maneira de deslocá-lo para cima.
Uma solução consiste em reduzir o tamanho do texto. Em vez de usar NamedSize.Large, tente um dos valores menores.
Outra solução é no próprio StackLayout: StackLayout que define uma propriedade Spacing do tipo double que indica o quanto de espaço
deixar entre os herdeiros. Por padrão, e 6.0, mas você pode definir um valor menor (por exemplo, zero) para garantir que todos os itens se
encaixem:

Agora todas as visualizações Label ocupam apenas o espaço vertical como requerido para o texto. Você pode até mesmo definir Spacing para
valores negativos para fazer os itens se sobreporem.
Rolagem não é automática e deve ser adicionada com um ScrollView. Mas há outro problema com esses programas: eles precisam criar
explicitamente uma ordem de cores e nomes ou criar explicitamente uma Label view para cada cor. Isso é um pouco tedioso, e, portanto,
repulsivo para programadores. Poderia ser automatizado?

Rolando conteúdo
Tenha em mente que um programa Xamarin.Forms tem acesso às bibliotecas de classe básica .NET e pode usar a reflexão .NET para obter
informações sobre todas as classes e estruturas definidas em uma montagem, tais como Xamarin.Forms.Core. Isto sugere que a obtenção
dos campos estáticos e propriedades da estrutura de cores pode ser automatizado.
A maioria das reflexões .NET começa com um objeto Type. Você pode obter um objeto Type para qualquer classe ou estrutura usando o
operador typeof em C#. Por exemplo, a expressão typeof (Color) retorna um objeto Type para a estrutura Color.
Na versão .NET disponível no PCL, um método de extensão para a classe Type, denominado GetTypeInfo, retorna um objeto TypeInfo a partir
do qual as informações adicionais podem ser obtidas. Mas que não é necessário neste programa. Em vez disso, outros métodos de extensão
são definidos para a classe Type, nomeados GetRuntimeFields e GetRuntimeProperties, que retornam os campos e propriedades do tipo.
Estes são na forma de coleções dos objetos FieldInfo e PropertyInfo. Destes, os nomes, bem como os valores das propriedades podem ser
obtidas.
Isso é demonstrado pelo programa ReflectedColors. O arquivo ReflectedColorsPage.cs requer o using para System.Reflection.

Em duas declarações foreach separadas, a classe ReflectedColorsPage percorre todos os campos e as propriedades da estrutura Color. Para
todos os membros estáticos públicos que retornam valores de Color, os dois loops chamam o método CreateColorLabel para criar uma Label
com o valor da cor e nome e em seguida, adiciona ao StackLayout.
Ao incluir todos os campos estáticos públicos e propriedades, o programa lista Color.Transparent, Color.Default, e Color.Accent juntamente
com os 17 campos estáticos exibidos no programa anterior.
Perto do fim do construtor, o StackLayout está definido para a propriedade Content do
ScrollView, o qual é então ajustado para a propriedade Content da página.
Ao usar o código para adicionar os herdeiros a um StackLayout, é geralmente uma boa idéia que o StackLayout esteja desconectado da
página que eventualmente irá exibi-lo. Cada novo filho adicionado ao StackLayout faz com que o tamanho da StackLayout mude, e se o
StackLayout estiver ligado a página, muito do layout vai para algo que não é realmente necessário.
O método CreateColorLabel na classe tenta fazer com que cada cor seja visível, definindo um fundo contrastante. O método calcula um valor
de luminância com base na média ponderada normal dos componentes vermelho, verde e azul e, em seguida, seleciona um fundo branco ou
preto.
Esta técnica não vai funcionar para Transparent, de modo que o item não pode ser exibido em tudo, e o método trata Color.Default como um
caso especial e mostra que a cor (seja ela qual for) contra um fundo Color.Default.
Aqui estão os resultados, que ainda estão muito longe de ser esteticamente satisfatórios:
Mas você pode rolar a tela porque o StackLayout é herdeiro de um ScrollView. Você deve se lembrar que a classe Layout<T> define a
propriedade Children que StackLayout herda. A classe genérica Layout<T> é derivada da classe não genérica Layout, e ScrollView também
deriva da classe não genérica Layout. Teoricamente, ScrollView é um tipo de objeto Layout – mesmo que tenha apenas um filho.
Como você pode ver na captura de tela, a cor de fundo da Label extende-se a toda a largura do StackLayout, o que significa que cada Label
é tao larga quanto o StackLayout.
Vamos experimentar um pouco para obter uma melhor compreensão da disposição do Xamarin.Forms. Para estas experiências, você pode
querer temporariamente dar a StackLayout e o ScrollView cores de fundo distintas.

Objetos de layout usualmente tem fundo transparente por padrão. Embora ocupem uma area sobre a tela, não são visiveis diretamente. Dar
cores temporarias a objetos de layout é uma ótima maneira de ver exatamente onde eles estão na tela. É uma boa técnica de depuração para
layouts complexos.
Você vai descobrir que um StackLayout azul aparece para fora no espaço entre a Label view individual – este é um resultado padrão da
propriedade Spacing do StackLayout – e também através da Label para Color.Default, que tem um fundo transparente.
Tente definir a propriedade HorizontalOptions de todas as Label view para LayoutOptions.Start:

Agora, o fundo azul do StackLayout é ainda mais proeminente porque todas as Label views ocupam apenas a quantidade de espaço horizontal
que o texto requer, e todos eles são empurrados para o lado esquerdo. Porque cada Label view tem uma largura diferente, esta visualização
parece ainda mais feia do que a primeira versão.
Agora remova as definições de HorizontalOptions na Label, e em vez disso defina um HorizontalOptions no StackLayout:
Agora, o StackLayout se tornou tão larga quanto a Label mais larga. O StackLayout abraça as labels dentro do ScrollView – a esquerda no
iPhone e Android, e no centro (curiosamente) no Windows Phone – com fundo vermelho do ScrollView agora visualizado claramente.
Quando você começa a construir uma árvore de objetos visuais, esses objetos adquirem uma relação de pai-filho. Um objeto pai é muitas
vezes referido como o recipiente do seu filho ou filhos porque a localização e tamanho da criança está contida dentro de seu pai.
Por padrão, HorizontalOptions e VerticalOptions são definidos como LayoutOptions.Fill, que significa que os tipos de visualização filho tentam
preencher o recipiente pai. (Pelo menos com os recipientes encontrados até agora. Como você verá, as outras classes de layout têm um
comportamento um pouco diferente.) Mesmo uma Label preenche seu recipiente pai por padrão, embora sem uma cor de fundo, a Label
parece ocupar apenas a quantidade de espaço que requer.
Definindo uma propriedade view HorizontalOptions ou VerticalOptions para LayoutOptions.Start, Center ou End efetivamente força a
visualização a encolher – horizontal, vertical ou ambos – para um único tamanho que a visualização requer.
Um StackLayout tem este mesmo efeito em tamanho vertical de seu filho: cada filho em um StackLayout ocupa apenas a altura que exige.
Definindo a propriedade VerticalOptions no filho de um StackLayout para Start, Center, ou End não tem efeito! No entanto, uma view filho
ainda se expande para preencher a largura do StackLayout, exceto quando o filho recebe uma propriedade HorizontalOptions diferente de
LayoutOptions.Fill.
Se um StackLayout está definido para a propriedade Content de um ContentPage, você pode definir HorizontalOptions ou VerticalOptions no
StackLayout. Essa propriedade tem dois efeitos: primeiro, eles encolhem a largura ou altura (ou ambos) do StackLayout para o tamanho dos
seus filhos, e segundo, eles governam onde o StackLayout está posicionado em relação à página.
Se um StackLayout está numa ScrollView, o ScrollView faz com que o StackLayout seja apenas tão alto quanto a soma das alturas dos seus
filhos. Isto é como o ScrollView pode determinar como rolar verticalmente um StackLayout. Você pode continuar a definir HorizontalOptions
no StackLayout para controlar a largura e a colocação horizontal.
No entanto, o que você não quer fazer é definir VerticalOptions no ScrollView para LayoutOptions.Start, Center ou End. O ScrollView deve ser
capaz de deslocar o seu conteúdo filho, e a única maneira de fazer isso é forçando seu filho (geralmente um StackLayout) a assumir uma
altura que reflete apenas o que o filho precisa e, em seguida, usar a altura deste filho e sua própria altura para calcular o quanto rolar esse
conteúdo.
Se você define VerticalOptions no ScrollView para LayoutOptions.Start, Center ou End, você está efetivamente dizendo ao ScrollView para
ser tão alto quanto precisa ser. Mas o que é isso? Porque ScrollView pode rolar o seu conteúdo, ele não precisa ter qualquer altura particular,
então ele vai encolher para nada.
Embora colocar um StackLayout em um ScrollView é normal, colocar um ScrollView em um StackLayout é perigoso. O StackLayout forçará o
ScrollView ter uma altura de apenas o que requer, e que a altura necessária é praticamente zero.
No entanto, existe um modo para colocar um ScrollView num StackLayout com sucesso, e que será demonstrada em breve.
A discussão anterior aplica-se a um StackLayout orientado verticalmente e ScrollView. StackLayout possui uma propriedade chamada
Orientation que você pode definir para um membro de StackOrientation - Vertical (o padrão) ou horizontal. Da mesma forma, ScrollView tem
uma propriedade ScrollOrientation que você define para um membro de ScrollOrientation. Tente esta:

Agora os Labels views são empilhados horizontalmente, e o ScrollView preenche a página na vertical, mas permite deslizamento horizontal
do StackLayout, que verticalmente preenche o ScrollView.
Parece muito estranho com as opções de layout verticais padrão, mas aqueles poderiam ser fixados para torná-la um pouco melhor.

A opção Expands
Você provavelmente notou que as propriedades HorizontalOptions e VerticalOptions são plurais, como se houvesse mais que uma opção.
Essas propriedades são geralmente definidas como um campo estático da estrutura LayoutOptions – outro plural.
As discussões até agora se concentraram nos seguintes campos LayoutOptions somente leitura estáticos que retornam valores predefinidos
de LayoutOptions:
• LayoutOptions.Start
• LayoutOptions.Center
• LayoutOptions.End
• LayoutOptions.Fill
O padrão – estabelecido pela classe View – é LayoutOptions.Fill, o que quer dizer que as views preenchem o recipiente.
Como você viu, um VerticalOptions definido na Label não faz diferença quando a Label é um filho de um StackLayout vertical. O próprio
StackLayout restringe a altura dos seus filhos para apenas a altura de que necessitam, para que o filho não tenha liberdade para se mover
verticalmente dentro desse espaço.
Esteja preparado para esta regra ser ligeiramente alterada!
A estrutura LayoutOptions tem quatro campos estáticos adicionais somente leitura ainda não discutidos:
• LayoutOptions.StartAndExpand
• LayoutOptions.CenterAndExpand
• LayoutOptions.EndAndExpand
• LayoutOptions.FillAndExpand
LayoutOptions também define duas propriedades instância nomeadas Alignment e Expands. As quatro instâncias de LayoutOptions devolvidas
pelos campos estáticos que terminam com AndExpand todos têm a propriedade Expands definido como true. Essa propriedade Expands pode
ser muito útil para gerenciar o layout da página, mas pode ser confuso ao primeiro encontro. Estes são os requisitos para que Expands possa
desempenhar um papel num StackLayout vertical:
• O StackLayout vertical deve ter uma altura que é menor do que a altura do seu recipiente. Em outras palavras, algum espaço vertical
adicional não utilizado deve existir no StackLayout
• Essa primeira exigência implica que o StackLayout vertical não pode ter sua própria propriedade VerticalOptions definida como Start,
Center ou End, porque isso faria com que o StackLayout tivesse uma altura igual à altura total dos seus filhos e não teria espaço
extra.
• Pelo menos um filho do StackLayout deve ter um VerticalOptions definido com a propriedade Expands como true.
Se essas condições forem satisfeitas, o StackLayout aloca o espaço vertical adicional igualmente entre os filhos que tem um VerticalOptions
definido com a propriedade Expands igual a true. Cada um destes filhos recebe um espaço maior no StackLayout do que o normal. Como o
filho ocupa aquele espaço depende da propriedade Alignment ser definida como: Start, Center, End ou Fill.
Aqui está um programa, nomeado VerticalOptionsDemo, que usa reflecção para criar objetos Label com todas as possíveis configurações
do VerticalOptions em um StackLayout vertical. O fundo e as cores de primeiro plano são alternados para que você possa ver exatamente
quanto espaço cada Label ocupa. O programa usa a Language Integrated Query (LINQ) para classificar os campos da estrutura LayoutOptions
em uma forma visualmente mais esclarecedora:
Você pode querer estudar os resultados um pouco:

As Label views com texto amarelo em fundos azuis são aquelas com propriedades VerticalOptions, ajustadas para valores LayoutOptions sem
a flag Expands. Se a flag Expands não está definido no valor da LayoutOptions de um item em uma StackLayout vertical, a configuração
VerticalOptions é ignorada. Como visto, a Label ocupa apenas a quantidade de espaço vertical necessária na StackLayout.
A altura total dos filhos neste StackLayout é menor do que a altura do StackLayout, assim o StackLayout tem espaço extra. Ele contém quatro
filhos com suas propriedades VerticalOptions definidas para o valor da LayoutOptions com a flag Expands, assim, este espaço extra é alocado
igualmente entre aqueles quatro filhos.
Nestes quatro casos - a Label View com texto em azul e cor de fundo em amarelo - a propriedade Alignment de LayoutOptions indica como o
filho está alinhado dentro da área que inclui a espaço extra. O primeiro - com a propriedade VerticalOptions definido para
LayoutOptions.StartAndExpand - está acima deste espaço extra. A segunda (CenterAndExpand) está no meio do espaço extra. O terceiro
(EndAndExpand) abaixo do espaço extra. No entanto, em todos estes três casos, a Label está recebendo apenas o espaço vertical necessário,
como indicado pela cor de fundo. O resto do espaço pertence à StackLayout, que mostra a cor de fundo da página.
A última Label tem sua propriedade VerticalOptions definida para LayoutOptions.FillAndExpand. Nesse caso, a Label ocupa toda a área,
incluindo o espaço extra, como a grande área do fundo amarelo indica. O texto está na parte superior desta área; isto porque a configuração
padrão de YAlign é TextAlignment.Start. Defina-o para outra coisa para posicionar o texto verticalmente dentro da área.
A propriedade Expands da LayoutOptions desempenha um papel apenas quando é uma filha de um StackLayout. Em outros contextos, ela é
supérfula.
Frame e BoxView
Duas views retangulares simples são muitas vezes úteis para fins de apresentação:
O BoxView é um retângulo preenchido. Ele deriva de uma View e define uma propriedade Color que é transparente por padrão.
O Frame apresenta uma borda retangular em torno de algum conteúdo. Frame deriva de um Layout em forma de ContentView, a partir da qual
ela herda uma propriedade Content. O conteúdo de um frame pode ser uma única view ou um layout que contém várias outras views. De um
VisualElement, o Frame herda uma propriedade BackgroundColor que é branco no iPhone e transparente no Android e no Windows Phone.Do
Layout, o Frame herda uma propriedade Padding que inicializa 20 unidades em todos os lados para darem ao conteúdo um espaço para
respirar um pouco. O próprio Frame define a si mesmo uma propriedade OutlineColor que é transparente por padrão e uma propriedade
HasShadow que é true por padrão, mas a sombra aparece apenas no iPhone.
Tanto o contorno do Frame quanto o BoxView são transparentes por padrão, então você pode ter um pouco de incerteza sobre como colori-
los: Branco não vai aparecer contra o fundo padrão do iPhone, e preto não vai aparecer contra o fundo padrão do Android e do Windows
Phone. Uma boa escolha é Color.Accent, que é garantida e mostra-se independente. Ou, você pode assumir o controle sobre a coloração do
fundo, bem como o contorno da Frame e da BoxView.
Se o BoxView ou o Frame não é limitado em tamanho, de qualquer maneira, isto é, se não é em um StackLayout e tem suas HorizontalOptions
e VerticalOptions definidas para os valores padrão de LayoutOptions.Fill essas views expandem para preencher seus recipientes.
Por exemplo, aqui está um programa que tem uma label centralizada definida para a propriedade Content de um Frame:

O label está centralizado no Frame, mas o Frame preenche toda a página, e você pode até não ser capaz de ver o Frame claramente se a
página não tiver dado um Padding de 20 em todos os lados:

Para exibir o texto emoldurado centralizado, você terá de definir as propriedades HorizontalOptions e VerticalOptions do Frame (em vez do
Label) para LayoutOptions.Center:
Agora, o frame abraça o texto (mas com um padding padrão de 20 unidades) no centro da página.

A versão do FramedText incluída com o código de exemplo para este capítulo exerce a liberdade de dar uma cor personalizada a tudo:

O resultado parece praticamente o mesmo em todas as três plataformas.


Tente definir um BoxView para a propriedade Content de um ContentPage, assim:

Certifique-se de definir a propriedade de cores para que você possa vê-lo. O BoxView preenche toda a área de seu contêiner, assim como
uma label faz com as configurações padrão de HorizontalOptions ou de VerticalOptions:

É ainda subjacente a barra de status iOS!


Agora tente definir as propriedades HorizontalOptions e VerticalOptions do BoxView para algo diferente de Fill, como neste exemplo de código.
Neste caso, o BoxView assumirá as suas dimensões padrão de 40 unidades quadradas:

O BoxView é agora 40 unidades quadradas, porque o BoxView inicializa suas propriedades WidthRequest e HeightRequest para 40. Essas
duas propriedades requerem uma pequena explicação:
A VisualElement, define as propriedades de Width e Height, mas estas propriedades são somente leitura. A VisualElement também define as
propriedades WidthRequest e HeightRequest que são objetos do tipo get e set. Normalmente, todas estas propriedades são inicializadas
com valor -1 (que na prática significa que eles são indefinidos), mas algumas derivadas de views, tais como BoxView, definem as
propriedades WidthRequest e HeightRequest para valores específicos.
Seguindo o layout de uma página, as propriedades Width e Height indicam dimensões atuais da view – a área que a view ocupa na tela.
Pelo fato de Width e Height serem do tipo somente leitura, eles são apenas para fins informativos. (Capítulo 5, "Lidar com tamanhos",
descreve como trabalhar com esses valores.)
Se você quiser uma view de tamanho específico, você pode definir as propriedades WidthRequest e HeightRequest. Mas essas
propriedades indicam (como seus nomes sugerem) um tamanho solicitado ou um tamanho preferido. Se a view for definida para preencher o
recipiente, essas propriedades serão ignoradas.
O BoxView define suas próprias propriedades WidthRequest e HeightRequest para 40. Você pode pensar nessas configurações como um
tamanho que BoxView gostaria de ser, se ninguém tivesse qualquer opinião que importasse. Você já viu que WidthRequest e HeightRequest
são ignoradas quando o BoxView é definido para preencher a página. O WidthRequest entra em ação se o HorizontalOptions estiver definido
para LayoutOptions.Left, Center ou Right, ou se o BoxView for filho de um StackLayout horizontal. O HeightRequest se comporta de forma
semelhante.
Aqui está a versão do programa em que há um SizedBoxView incluído com o código para este capítulo:
Agora temos um BoxView com seu tamanho específico e as cores definidas explicitamente:

Vamos usar tanto o frame como o BoxView em uma lista de cores melhorada. O programa ColorBlocks tem um construtor de página que é
praticamente idêntico ao de ReflectedColors, exceto que ele chama um método nomeado CreateColorView ao invés de CreateColorLabel.
Aqui está este método:

O método CreateColorView retorna um frame contendo um StackLayout horizontal com uma BoxView indicando a cor, uma label para o nome
da cor, e outro StackLayout com mais duas label views para a composição de RGB e valores de Hue, Saturation e Luminosity. Os monitores
RGB e HSL são sem sentido para o valor de Color.Default, de modo que StackLayout tem sua propriedade IsVisible definido como false nesse
caso. O StackLayout ainda existe, mas é ignorado quando a página é processada.
O programa não sabe qual elemento vai determinar a altura de cada item de cor - o BoxView, a label com o nome da cor, ou as duas label
views com valores RGB e HSL - assim ele centraliza todas as label views. Como você pode ver, o BoxView expande em altura para acomodar
a altura do texto:

Agora, esta é uma lista de cores com rolagem que está começando a ser algo que pode nos dar um pouco de orgulho.

Um ScrollView numa StackLayout?


É comum colocar um StackLayout em um ScrollView, mas você pode colocar um ScrollView em um StackLayout? E por que você quer mesmo?
É uma regra geral em sistemas de layout como o do Xamarin.Forms que você não pode colocar uma rolagem de página em uma pilha. Um
ScrollView precisa ter uma altura específica para calcular a diferença entre a altura do seu conteúdo e a sua própria altura. Essa diferença é
o montante que o ScrollView pode rolar seus conteúdos. Se o ScrollView está em uma StackLayout, ele não recebe essa altura específica. O
StackLayout quer que o ScrollView seja o mais curto possível, e isso é a altura do conteúdo do ScrollView ou zero, e nenhuma solução
funciona.
Então, por que você quer um ScrollView em um StackLayout de qualquer maneira?
Às vezes é exatamente o que você precisa. Considere um leitor de e-book primitivo que implementa rolagem. Você pode querer uma label na
parte superior da página exibindo sempre o título do livro, seguido por um ScrollView contendo uma StackLayout com o conteúdo do livro em
si. Seria conveniente para esse label e para o ScrollView serem filhos de um StackLayout que preenche a página.
Com Xamarin.Forms, tal coisa é possível. Se você der a ScrollView um VerticalOptions definindo LayoutOptions.FillAndExpand, ele pode
realmente ser um filho de um StackLayout. O StackLayout vai dar ao ScrollView todo o espaço extra não exigida pelos outros filhos, e a
ScrollView terá, então, uma altura específica.
O projeto BlackCat exibe o texto de conto de Edgar Allan Poe "The Black Cat", que é armazenado em um arquivo de texto chamado
TheBlackCat.txt em um formato de uma linha-por-parágrafo.
Como o programa BlackCat acessa o arquivo com este conto? Às vezes é conveniente incorporar arquivos que um aplicativo requer
autorização no executável do programa ou - em caso de uma aplicação Xamarin.Forms - autorização direto na biblioteca DLL de Classes
Portátil. Esses arquivos são conhecidos como recursos incorporados, e é isso que o arquivo TheBlackCat.txt é neste programa.
Para fazer um recurso incorporado no Visual Studio ou no Xamarin Studio, você provavelmente vai querer primeiro criar uma pasta no projeto,
selecionando a opção Add > New Folder no menu projeto. A pasta para os arquivos de texto pode ser chamada de Textos, por exemplo. A
pasta é opcional, mas ajuda a organizar ativos do programa. Então, para essa pasta, você pode selecionar o Add> Existing Item no Visual
Studio ou Add > Add Files no Xamarin Studio. Navegue até o arquivo, selecione-o e clique em Add no Visual Studio ou Open em Xamarin
Studio.
Agora aqui é a parte mais importante: Uma vez que o arquivo é parte do projeto, abra o diálogo a partir do menu associado com o arquivo e
vá em Propriedades. Especifique que a Build Action para o arquivo é EmbeddedResource. Este é um passo fácil de esquecer, mas é
essencial.
Isso foi feito para o projeto BlackCat, e, consequentemente, o arquivo TheBlackCat.txt fica embutido no arquivo BlackCat.dll.
No código, o arquivo pode ser recuperado chamando o método GetManifestResourceStream definido pela classe Assembly no namespace
System.Reflection. Para chegar a assembly do PCL, tudo que você precisa fazer é obter o tipo de qualquer classe definida na assembly. Você
pode usar typeof com o tipo de página derivada da ContentPage ou GetType na instância dessa classe. Em seguida, deve-se chamar
GetTypeInfo neste objeto Type. Assembly é uma propriedade resultante do objeto TypeInfo:

No método GetManifestResourceStream da Assembly, você precisa especificar o nome do recurso. Para recursos incorporados, esse nome
não é o filename do recurso, mas a resource ID. É fácil confundir estes porque que ID pode parecer vagamente como um nome de arquivo
totalmente qualificado.
O resource ID começa com o namespace padrão do assembly. Este não é o namespace .NET! Para obter o namespace padrão do assembly
no Visual Studio, selecione Properties do projeto no menu, e na caixa de diálogo Propriedades, selecione Library no lado esquerdo e veja o
campo Default Namespace. Em Xamarin Studio, selecione Options no menu projeto e na caixa de diálogo Project Options, selecione Main
Settings à esquerda, e veja um campo denominado Default Namespace.
Para o projeto BlackCat, o namespace padrão é o mesmo que o assembly: "BlackCat". No entanto, você pode redefinir o namespace padrão
para o nome que quiser.
O resource ID começa com que há no namespace padrão, seguido por um período, seguido pelo nome da pasta que você pode ter usado,
seguido por outro período e o nome do arquivo. Para este exemplo, o resource ID é "BlackCat.Texts.TheBlackCat.txt" - e isso é o que você
verá passando para o método GetManifestResourceStream no código. O método retorna um objeto .NET Stream, e a partir desse, um
StreamReader pode ser criado para ler as linhas de texto.
É uma boa idéia usar declarações using com o objeto Stream retornado de GetManifestResourceStream e o objeto StreamReader, porque
isso irá descartar corretamente os objetos quando eles não forem mais necessários ou se causarem exceções.
Para fins de layout, o construtor BlackCatPage cria dois objetos StackLayout: mainStack e textStack. A primeira linha do arquivo (contendo
título e autor da história) se torna um label em mainStack em negrito e centralizado; todas as linhas subsequentes ficarão em textStack. A
instância mainStack também contém um ScrollView com textStack.

Observe que o ScrollView tem sua propriedade VerticalOptions definida para LayoutOptions.FillAndExpand. Sem isso, este programa não vai
funcionar. Com ele, o texto é rolável enquanto o título permanece no lugar.
Pelo fato deste ser basicamente um leitor de e-book, e os seres humanos leem o texto preto em papel branco por centenas de anos, o
BackgroundColor da página é definido como branco e o TextColor de cada label está definido para preto:
BlackCat é uma aplicação PCL. Também é possível escrever este programa usando um projeto de ativos compartilhados em vez de um PCL,
e incluído com o código para este capítulo está BlackCatSap. No entanto, se você colocar um recurso incorporado em um SAP, o nome da
pasta não é parte do resource ID. Ele será basicamente ignorado. Além disso, pelo fato do recurso tornar-se realmente parte do projeto de
aplicativo, você irá precisar do namespace padrão para a aplicação, e isso é diferente para cada plataforma. O código para definir a variável
de recurso parece com esse:

Se você estiver tendo problemas fazendo referência a um recurso incorporado, você pode estar usando um nome incorreto. Tente chamar
GetManifestResourceNames no objeto Assembly para obter uma lista dos resource IDs de todos os recursos incorporados.

Capítulo 5
Lidar com tamanhos
Algumas referências de tamanhos em conexão com vários elementos visuais:
• A barra de status iOS tem uma altura de 20, que você pode ajustar na página.
• O BoxView padrão solicitado de largura e altura é 40.
• O preenchimento padrão dentro de uma moldura é de 20.
• A propriedade padrão de espaçamento sobre a StackLayout é 6.

O comando Device.GetNamedSize, para vários membros da enumeração NamedSize retorna um número dependente de plataforma
apropriada para valores FontSize para uma Label ou Button.
Quais são esses números? Quais são as suas unidades? E como nós inteligentemente definimos propriedades que exigem tamanhos e outros
valores?
Boas perguntas. Tamanhos também afetam a exibição do texto. Como você viu, as três plataformas exibem uma quantidade diferente de texto
na tela. É a quantidade de texto algo que o Xamarin.Forms application pode prever ou controlar? E mesmo sendo possível, é uma prática de
programação correta? Caso um aplicativo ajustar tamanhos de fontes para conseguir uma densidade de texto desejado na tela?
Em geral, quando a programação de um aplicativo Xamarin.Forms, é melhor não ficar muito perto das dimensões numéricas atuais de objetos
visuais. É preferível confiar no Xamarin.Forms e para fazer a escolha padrão das três formas de plataformas.
No entanto, há momentos em que um programador precisa saber algo sobre o tamanho de determinados objetos visuais, e o tamanho da tela
em que aparecem.
Como você sabe, monitores de vídeo consistem de uma matriz retangular de pixels. Qualquer objeto exibido na tela também tem um tamanho
de pixel. Nos primeiros computadores pessoais, os programadores dimensionam e posicionam objetos visuais em unidades de pixels. Mas,
com uma maior variedade de tamanhos de tela e densidades de pixel tornou-se inviável trabalhar com pixels, para programadores que tentam
escrever aplicações que se parecem em diversos dispositivos. Outra solução foi necessária.
Pixels, points, dps, DIPs, e DIUs
As soluções com pixels começaram com sistemas operacionais para computadores desktop, e estas soluções foram, em seguida, adaptadas
para dispositivos móveis. Por esta razão, é esclarecedor para começar essa exploração com o desktop.
Monitores de desktop têm uma ampla gama de dimensões em pixels, da quase obsoleta 640 × 480, no máximo, para os milhares. A relação
de aspecto de 4:3 já foi padrão para telas de computador e para cinema e televisão, a relação de aspecto de alta definição de 16: 9 (ou
semelhante 16:10) é agora mais comum.

Monitor de desktop também têm uma dimensão física, geralmente é medido ao longo da diagonal da tela em polegadas ou centímetros. A
dimensão do pixel combinada com a dimensão física permite calcular resolução ou pixel densidade de exibição do vídeo em pontos por
polegada (DPI), às vezes também referidos como pixels por polegada (PPI). A resolução da tela também pode ser medida como um passo do
ponto, que é a distância entre os centros dos pixels adjacentes, normalmente medido em milímetros.
Por exemplo, você pode usar o teorema de Pitágoras para calcular um display antigo 800 × 600 tem um comprimento diagonal que seria
acomodar 1.000 pixels na horizontal ou na vertical. Se este monitor tem uma diagonal de 13 polegadas, que é uma densidade de pixels de 77
DPI, ou um dot pitch de 0,33 milímetros. No entanto, a tela de 13 polegadas em um laptop moderno pode ter dimensões de pixel de 2560 ×
1600, que é cerca de 230 DPI, ou um passo do ponto de cerca de 0,11 milímetros. Um objeto quadrado de 100 pixel na tela é um terço do
tamanho do mesmo objeto na tela mais antiga.
Por esta razão, os sistemas de computação de desktop permitem que os programadores trabalhem com a exibição de vídeo em alguma forma
de unidades independentes de dispositivo, em vez de pixels como a Apple e a Microsoft conceberam. A maior parte das dimensões que um
programador encontra e especifica estão nessas unidades independentes de dispositivo. É de responsabilidade do sistema operacional
converter estas unidades e pixels.
No mundo da Apple, monitor de desktop foram tradicionalmente assumindo uma resolução de 72 unidades por polegada. Este número vem
da tipografia, onde muitas medições são expressas em unidades de pontos. Em tipografia clássica, existem cerca de 72 pontos por polegada,
mas em tipografia digital, o ponto foi padronizado para exatamente 1/72 de uma polegada. Ao trabalhar com pontos em vez de pixels, um
programador tem um sentido intuitivo da relação entre os tamanhos numéricos da área que ocupam os objetos visuais na tela.
No mundo Windows, uma técnica similar foi desenvolvida, chamado pixels independentes de dispositivo (DIPs) ou unidades independentes
de dispositivo (UDE). Para um programador do Windows, na área de trabalho é assumido uma resolução de 96 DIUs, que é exatamente um
terço superior ao 72 DPI, embora possa ser ajustada.
Os dispositivos móveis, no entanto, têm regras um pouco diferentes: As densidades de pixels obtidos em telefones modernos, são tipicamente
muito maiores do que em desktops. Esta maior densidade de pixels permite que o texto e outros objetos visuais encolha muito mais em
tamanho.
Telefones fica mais perto do rosto do usuário do que é uma tela de desktop ou laptop. Esta diferença também implica que os objetos visuais
no telefone podem ser menores do que objetos nas telas de desktop ou laptop. Porque as dimensões físicas do telefone são muito menores
do que o desktop exibe, encolhendo para baixo objetos visuais é muito desejável, pois permite caber muito mais na tela.
A Apple continua referindo-se às unidades independentes de dispositivo no iPhone como pontos. Até recentemente, todos os Apple com alta
densidade exibem (que a Apple se refere pelo nome Retina) tem uma conversão de dois pixels para o ponto. Isto era verdade para o MacBook
Pro iPad e iPhone. A exceção recente é o iPhone 6 Plus, que tem três pixels para o ponto.
Por exemplo, o 640 × 960 pixels dimensão da tela de 3,5 polegadas do iPhone 4 tem uma densidade de pixels real de cerca de 320 DPI. Há
dois pixels por ponto, então um programa aplicativo em execução no iPhone 4, a tela parece ter uma dimensão de 320 × 480 pontos. O iPhone
3, na verdade, tinha uma dimensão de pixel de 320 × 480, e pontos igual ou pixels, então um programa, o video do iPhone 3 e do iPhone 4
parecem ser do mesmo tamanho. Apesar dos mesmos tamanhos percebidos, gráficos e texto são exibidos em maior resolução sobre o iPhone
4 do que o iPhone 3.
Para o iPhone 3 e iPhone 4, a relação entre o tamanho da tela e dimensões de ponto em pixels tem um fator de conversão de 160 pontos por
polegada em vez do padrão de desktop de 72.
O iPhone 5 tem uma tela de 4 polegadas, mas a dimensão dos pixel é 640 × 1136. A densidade de pixels é quase o mesmo que o iPhone 4.
Para um programa, esta tela tem um tamanho de 320 × 768 pontos.
O iPhone 6 tem uma tela de 4,7 polegadas e uma dimensão de pixel de 750 × 1334. A densidade de pixels também é de cerca de 320 DPI.
Existem dois pixels para o ponto, deste modo que um programa, a tela parece ter um tamanho de ponto de 375 × 667.
No entanto, o iPhone 6 Plus tem uma tela de 5,5 polegadas e uma dimensão de pixel de 1080 × 1920, que é uma densidade de pixels de 400
DPI. Esta maior densidade de pixels implica mais pixels ao ponto, e para o iPhone 6 Além disso, a Apple estabeleceu o ponto igual a três
pixels. Que normalmente implica um tamanho de tela percebida de 360 × 640 pontos, mas a um programa, a tela do iPhone 6 Plus tem um
tamanho de ponto de 414 × 736, deste modo a resolução percebida é de cerca de 150 pontos por polegada.
Esta informação está resumida na tabela a seguir:
Modelo iPhone 2.3 iPhone 4 iPhone 5 iPhone 6 iPhone 6 Plus
Tamanho Pixel 320 X 480 640 X 960 640 X 1136 750 × 1334 1080 × 1920
Tela Diagonal 3.5 in. 3.5 in. 4 in. 4.7 in. 5.5 in.
Densidade Pixel 165 DPI 330 DPI 326 DPI 326 DPI 401 DPI
Pixels por Ponto 1 2 2 2 3
Tamanho do Pont 320 × 480 320 × 480 320 × 568 375 × 667 414 × 736
Pontos por Polegada 165 165 163 163 154
Android faz algo bastante semelhante: dispositivos Android têm uma grande variedade de tamanhos e dimensões de pixel, um programador
Android geralmente trabalha com unidades independentes densidade pixels (dps). A relação entre pixels e dps é ajustado 160 dps por
polegada, o que significa que a Apple e dispositivos Android são muito semelhantes.
Microsoft teve uma abordagem diferente com o Windows Phone. Windows Phone 7 dispositivos
A Apple continua a referir-se às unidades independentes de dispositivo no iPhone como pontos. Até recentemente, todos tem uma dimensão
de pixel uniforme de ambos os 320 × 480, mas os dispositivos que utilizam este tamanho de tela eram muito raros e podem ser ignoradas
para esta discussão, ou 480 × 800, que é muitas vezes referida como WVGA (Wide Video Graphics Array). 7 programas Windows Phone
trabalhar com esta exposição em unidades de pixels. Se você assumir um tamanho de tela média de 4 polegadas para um dispositivo de 480
× 800 Windows Phone 7, isto significa que o Windows Phone é implicitamente assumindo uma densidade de pixels de cerca de 240 DPI. Isso
é 1,5 vezes o pixel den-sidade assumido de aparelhos iPhone e Android.
Com o Windows Phone 8, vários tamanhos de tela maior é permitido: 768 × 1280 (WXGA ), 720 × 1280 (referido usando de alta definição
jargão televisão como 720p), e 1080 × 1920 (chamado 1080p) .
Isto está resumido na tabela seguinte:
Tipo de Tela WVGA WXGA 720p 1080p
Tamanho Pixel 480 × 800 768 × 1280 720 × 1280 1080 × 1920
Fator escala 1 1.6 1.5 2.25
Tamanho em DIUs 480 × 800 480 × 800 480 × 853 480 × 853

Xamarin.Forms tem uma filosofia de utilizar de convenção das plataformas subjacentes, tanto quanto possível. De acordo com esta filosofia,
um programador Xamarin.Forms trabalha com tamanhos definidos por cada plataforma particular. Todos os tamanhos que o programador
encontra através da API Xamarin.Forms estão nessas, unidades independentes de dispositivo específicos da plataforma.
Programadores Xamarin.Forms podem tratar o visor do telefone de uma forma independente do dispositivo, mas um pouco diferente para
cada uma das três plataformas:
• iOS: assumir 160 unidades por polegada
• Android: assumir 160 unidades por polegada
• Windows Phone: assumir 240 unidades por polegada

É desejável objetos visuais de tamanhos diferentes para as três plataformas, no Windows Phone deve ter cerca de 150 por cento maior do
que as dimensões do iPhone e Android. Se você comparar o valor de 160 iOS com o valor da Apple, a área de trabalho de 72 unidades por
polegada, e o valor do Windows Phone de 240 com o valor de desktop de 96 do Windows, você vai descobrir um pressuposto implícito de que
os telefones são mantidos um pouco mais perto do que metade da distância entre os olhos do que é um monitor de mesa.
A classe VisualElement define duas propriedades, Width (largura) e Height (altura), que fornecem as dimensões prestados de pontos de vista,
layouts e páginas nestas unidades independentes de dispositivo. No entanto, as configurações iniciais de largura e altura são valores "fictícios"
de -1. Os valores dessas propriedades se tornam válidos apenas quando o sistema de layout tem posicionado e dimensionado tudo na página.
Além disso, mantenha em mente que o preenchimento padrão das opções horizontais ou opções verticais muitas vezes faz com que a view
ocupe mais espaço do que seria de outra forma. Os valores de Width (largura) e Height (altura) refletem esse espaço extra. Os valores de
largura e altura também incluir qualquer preenchimento que pode ser definido e são consistentes com a área colorida por BackgroundColor
propriedade da view.
VisualElement define um evento chamado SizeChanged que é acionado sempre que as propriedades de Width (largura) e Height (altura),
tenham mudanças de elementos visuais. Este evento faz parte das diversas notificações que ocorrem quando uma página é colocada para
fora, um processo que envolve os vários elementos da página que está sendo dimensionada e posicionada. Este processo de layout ocorre
após a primeira definição de uma página (geralmente no construtor da página), uma nova passagem pode ocorre em resposta a qualquer
alteração que possa afetar o layout, por exemplo, quando views são adicionados a ContentPage ou a um StackLayout, removido a partir
desses objetos, ou quando as propriedades são definidas em elementos visuais que possam resultar em mudança em seus tamanhos.
Um novo layout também é acionado quando altera o tamanho da tela. Isso acontece principalmente quando o telefone é girado entre os modos
retrato e paisagem.
A familiaridade completa com o sistema de layout Xamarin.Forms frequentemente acompanha o trabalho de escrever seu próprio layout
<View> derivativos. Esta tarefa nos espera em um capítulo futuro. Até então, simplesmente o saber quando Largura e Altura mudam as
propriedades é útil para trabalhar com tamanhos de objetos visuais. Você pode anexar um manipulador SizeChanged a qualquer objeto visual
na página, incluindo a própria página. O programa a baixo demonstra como obter o tamanho da página e exibi-lo:
Este é o primeiro exemplo de lidar com evento neste livro, e você pode ver que os eventos são tratados na maneira normal do C # e .NET. O
código, no final do construtor atribui o processador de eventos OnPageSizeChanged ao evento SizeChanged da página. O primeiro argumento
para o processador de eventos (habitualmente chamado sender) é o objeto disparar o evento, neste caso, o exemplo de WhatSizePage, mas
o processador de eventos que não utiliza. O manipulador de eventos nem usa o segundo argumento do evento.
Em vez disso, o manipulador de eventos acessa o elemento Label (convenientemente salva como um campo) para exibir as propriedades de
Width e Height da página. O carácter Unicode na chamada String.Format é um (×) símbolo.
O evento SizeChanged não é a única oportunidade de obter o tamanho de um elemento. VisualElement também define um método virtual
protegido chamado OnSizeAllocated que indica quando o elemento visual é atribuído um tamanho. Você pode substituir esse método em seu
derivado ContentPage ao invés de manipular o evento SizeChanged, mas OnSizeAllocated às vezes é chamado quando o tamanho não está
realmente mudando.
Aqui está o programa rodando em três plataformas:

Para o registro, estas são as fontes das telas nestas três imagens:
• O iPhone 6 simuladores, com dimensões de 750 × 1334 pixels.
• Uma LG Nexus 5 com um tamanho de tela de 1080 × 1920 pixels.
• A Nokia Lumia 925 com um tamanho de tela de 768 × 1280 pixels.

Note-se que o tamanho vertical percebido pelo programa no Android não inclui a área ocupada pela barra de estado ou botões de baixo; o
tamanho vertical no telefone Windows não inclui a área ocupada pela barra de estado.
Por padrão, todas as três plataformas respondem às mudanças de orientação do dispositivo. Se você ativar os telefones (ou emulators) 90
graus no sentido contrário, os telefones apresentar os seguintes tamanhos:

As capturas de tela para este livro são projetadas apenas para o modo retrato, assim você vai precisar transformar este aplicativo de lado para
ver o que o programa se parece na paisagem. A largura 598-pixel no Android exclui a área para os botões; a altura 335-pixel exclui a barra de
status, que aparece sempre acima da página. No Windows Phone, a largura 728-pixel exclui a área para a barra de status, que aparece no
mesmo lugar, mas com ícones girados para refletir a nova orientação.
O programa WhatSize cria um único Label em seu construtor e define a propriedade do Text no manipulador de eventos. Essa não é a única
maneira de escrever um programa desse tipo. O programa pode usar o manipulador SizeChanged para criar um Label inteiro com o novo texto
e definir um novo Label com o conteúdo da página, caso o Label anterior se tornaria não referenciado e, portanto, elegível para a coleta de
lixo. Mas a criação de novos elementos visuais é desnecessária e um desperdício neste programa. É melhor para o programa criar apenas
um Label e apenas definir a propriedade Text para indicar novo tamanho.
Monitoramento de alterações de tamanho é a única maneira de um aplicativo Xamarin.Forms detecta mudanças de orientação, sem a obtenção
de informações específicas da plataforma. A largura é maior do que a altura? Isso é paisagem. Caso contrário, é retrato.
Por padrão, os modelos do Visual Studio e Xamarin Studio para Xamarin.Forms, permitem mudanças de orientação para todas as três
plataformas. Se você quiser desativar as alterações de orientação, por exemplo, se você tiver um aplicativo que simplesmente não funciona
bem em modo retrato ou paisagem, você pode fazer.
Para iOS, primeiro exibir o conteúdo de Info.plist no Visual Studio ou Xamarin Studio. Na seção de Informações da implantação do iPhone,
use a Supported Device Orientations para especificar quais as orientações são permitidas.

Para Android, no atributo Activity na classe MainActivity, adicione:


ScreenOrientation = ScreenOrientation.Landscape
or
ScreenOrientation = ScreenOrientation.Portrait
O atributo Activity gerado pela solução contém um argumento ConfigurationChanges que também se refere a tela de orientação, mas o
propósito de ConfigurationChanges é inibir o reinício da atividade quando a orientação da tela ou mudanças de tamanho do telefone.
Para Windows Phone, no arquivo the MainPage.xaml.cs, altere o SupportedPageOrientation enumeration member para Portrait (retrato) ou
Landscape (paisagem).

Tamanhos métricos
Aqui, novamente, são as relações subjacentes assumidas entre unidades e polegadas independentes de dispositivo nas três plataformas:
• iOS: 160 unidades por polegada
• Android: 160 unidades por polegada
• Windows Phone: 240 unidades por polegada

Se o sistema métrico é mais confortável para você, aqui estão os mesmos valores para centímetros (arredondado para facilitar memorização
e números facilmente divisíveis):
• iOS: 64 centímetros
• Android: 64 centímetros
• Windows Phone: 96 centímetros

Isto significa que as aplicações Xamarin.Forms pode dimensionar um objeto visual em termos de dimensão métrica que é, em unidades
familiares de polegadas e centímetros. Aqui é um programa chamado MetricalBoxView que exibe uma BoxView com uma largura de
aproximadamente um centímetro e uma altura de cerca de uma polegada:

public class MetricalBoxViewPage: ContentPage


{ public MetricalBoxViewPage()
{
Content = new BoxView
{
Color = Color.Accent,
WidthRequest = Device.OnPlatform(64, 64, 96),
HeightRequest = Device.OnPlatform(160, 160, 240),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
}
}
Se você atualizar uma régua para o objeto na tela do seu telefone, você vai descobrir que não é exatamente o tamanho desejado, mas
certamente próximo a ele.
Tamanos de fonte estimado
A propriedade FontSize do Label e Button são do tipo double. FontSize, indica a altura aproximada de caracteres da fonte a partir do fundo
descendente para o topo ascendente, muitas vezes (dependendo do tipo de letra) incluindo marcas bem diacríticas. Na maioria dos casos,
você vai querer definir esta propriedade pelo valor retornado do método Device.GetNamedSize. Isso permite que você especifique NamedSize
(Default, Micro, Small, Medium, or Large.)
Você pode trabalhar com tamanhos de fonte numéricos reais, mas há um pequeno problema envolvido (a ser discutido em detalhes em breve).
Para a maior parte, os tamanhos de fonte são expressos nas mesmas unidades independentes do dispositivo, utilizado através Xamarin.Forms,
o que significa que você pode calcular o tamanho das fontes independentes de dispositivo com base na resolução plataforma.
Por exemplo, suponha que você queira usar uma fonte de 12 pontos em seu programa. A primeira coisa que você deve saber é que, enquanto
uma fonte de 12 pontos pode ter um tamanho confortável para o material impresso ou uma tela de desktop, em um telefone é muito grande.
Mas vamos continuar.
Há 72 pontos por polegada, por isso uma fonte de 12 pontos é um sexto de uma polegada. Multiplique pelo DPI de resolução. Isso é cerca de
27 unidades independentes de dispositivo no iOS e Android e 40 unidades independentes de dispositivo no Windows Phone.
Vamos escrever um pequeno programa chamado FontSizes, que começa com uma exibição semelhante ao programa NamedFontSizes no
Capítulo 3, mas, em seguida, exibe um texto com tamanho de ponto numéricos, convertido em unidades que utilizam o dispositivo resolução
deviceindependent:
Para facilitar as comparações entre as três telas, os fundos têm sido uniformemente definidos como branco e os rótulos para preto. Observe
a BoxView inserido no StackLayout entre os dois blocos foreach da definição HeightRequest, lhe dá uma altura de aproximadamente um
octogésimo de uma polegada, independente do dispositivo, e se assemelha a uma regra horizontal.
Os tamanhos visuais resultantes são bastante consistentes entre as três plataformas e fornece uma idéia aproximada do que você pode
esperar. Os números entre parênteses são os valores numéricos da FontSize em unidades independentes de dispositivo da plataforma
especificadas.

Existe um problema, no entanto.que envolve o Android. Execute o Android Settings app. Vá para a página de exibição e selecione o item
Tamanho da fonte. Você vai ver que pode selecionar um tamanho Pequeno, Normal (o padrão), Large, ou enorme. Esta facilidade é para o
benefício de pessoas que não podem ler confortavelmente o texto, porque é muito pequeno, ou que não têm nenhum problema de leitura de
texto minúsculo e realmente preferem aplicativos para mostrar um pouco mais de texto na tela.
Escolha algo diferente do que o normal. Quando você executar o programa FontSizes novamente, você vai ver que todo o texto exibido tem
um tamanho diferente, ou maior ou menor dependendo da configuração que você selecionou. Como você vai ser capaz de notar, a partir dos
números em parênteses na metade superior da lista, o método retorna Device.GetNamedSize diferentes valores com base nesta definição.
Para NamedSize.Default, o método retorna 14 quando o ajuste é Normal (como a captura de tela acima mostra), mas retorna 12 para um
cenário de Small, 16 para a Large, e 18 1/3 para Huge.
Além do valor devolvido no Device.GetNamedSize, a lógica de processamento de texto subjacente também afeta o tamanho do texto. O
programa continua a calcular os mesmos valores para os vários tamanhos de pontos, mas o texto na metade inferior da tela, também muda
de tamanho. Este é um resultado do Android para Label usando o valor do ComplexUnitType.Sp (que se traduz em constante Android
COMPLEX_UNIT_SP) para calcular o tamanho da fonte. O padrão de escape para SP é em pixel, uma escala adicional apenas para o texto
além do uso de pixels independentes do dispositivo.
Textos apropriados para tamanho de dispositivos
Talvez seja necessário encaixar um bloco de texto para uma área retangular. É possível calcular um valor para a propriedade FontSize
property do Label com base no número de caracteres de texto, o tamanho da área rectangular, com dois números. (No entanto, esta técnica
não funciona em Android, a menos que a definição do tamanho da fonte seja Normal.)
O primeiro número é o espaçamento entre linhas. Esta é a altura vertical de uma vista para Label por linha de texto. Para as fontes padrões
associados com as três plataformas, é mais ou menos relacionadas com a propriedade FontSize, como segue:
• iOS: lineSpacing = 1.2 * label.FontSize
• Android: lineSpacing = 1.2 * label.FontSize
• Windows Phone: lineSpacing = 1.3 * label.FontSize

O segundo número é a medida da largura do caractere. Para uma mistura normal de letras maiúsculas e minúsculas para as fontes padrões,
essa largura média de carácter é cerca da metade do tamanho da fonte, independentemente da plataforma:
• averageCharacterWidth = 0.5 * label.FontSize

Por exemplo, suponha que queira encaixar uma seqüência de texto contendo 80 caracteres em uma largura de 320 unidades, e você gostaria
que o tamanho da fonte possa ser tão grande quanto possível. Divida a largura (320) pela metade do número de caracteres (40), e você terá
um tamanho de fonte de 8, que pode ser definida com a propriedade FontSize do Label. Para o texto que é um pouco indeterminado e não
pode ser testado de antemão, você pode querer fazer este cálculo um pouco mais conservadora para evitar surpresas.
O seguinte programa usa tanto o espaçamento entre linhas e largura de carácter médio para caber um parágrafo do texto na página, ou
melhor, na página menos a área na parte superior do iPhone ocupada pela barra de status. Para fazer a exclusão da barra de status estatuto
do iOS, é um pouco mais fácil neste programa, o programa usa um ContentView.
ContentView deriva do layout, mas adiciona uma propriedade de conteúdo apenas para o que ele herda de Layout. ContentView é a classe
base para moldura, mas não acrescenta muita funcionalidade própria. No entanto, ele pode ser útil para preparar um grupo de pontosa para
definir uma nova exibição personalizada e para simular uma margem.
Como você deve ter notado, Xamarin.Forms não tem nenhum conceito de margem, que tradicionalmente é semelhante ao preenchimento,
exceto que o preenchimento é dentro de uma visão e de uma parte da view, enquanto a margem é fora da view e, na verdade, parte da view
do pai . A ContentView nos permite simular isso. Se você encontrar uma necessidade de definir uma margem em uma view, colocar a view
em um ContentView. ContentView e defina a propriedade Padding do Layout.
O programa EstimatedFontSize usa ContentView de uma forma ligeiramente diferente: ele define o preenchimento habitual na página para
evitar a barra de status do iOS, mas, em seguida, define um ContentView como o conteúdo dessa página. Assim, este ContentView é do
mesmo tamanho que a página, mas excluindo a barra de status iOS. É nesta ContentView que o evento SizeChanged está ligado, e é o
tamanho deste ContentView que é usada para calcular o tamanho da fonte de texto.
O SizeChanged usa o primeiro argumento para obter o evento do objeto firing (neste caso, o ContentView), que é o objeto em que a Label
tem de encaixar. O cálculo é descrito nos comentários:
Os marcadores de texto chamados "? 1", "2 ??", "3?", E "? 4" foram escolhidos para serem únicos, mas também para serem o mesmo número
de caracteres como os números que eles substituem.
Se o objetivo é fazer o texto tão grande quanto possível sem que o texto derrame para fora da página, os resultados validam essa abordagem:
Não é tão ruim assim. O texto, na verdade, exibido no iPhone e Android eem 14 linhas. Não é necessário para o mesmo FontSize ser calculados
no modo paisagem, mas isso acontece às vezes:

Um clock para ajustar tamanho


A classe de dispositivo inclui um método estático StartTimer que permite que você defina um temporizador que dispara um evento periódico.
A disponibilidade de um evento timer significa que a aplicação do relógio é possível, mesmo que ele exibe o tempo apenas em texto.
O primeiro argumento de um intervalo Device.StartTimer é expressa como um valor TimeSpan. O cronômetro dispara um evento
periodicamente com base nesse intervalo. (Você pode ir para baixo tão baixo em 15 ou 16 milissegundos, que é o período da taxa de quadros,
60 quadros por segundo comum em monitores de vídeo.) O manipulador de eventos não tem argumentos, mas deve retornar true para manter
o temporizador indo.
O programa FitToSizeClock cria um Label para exibir a hora e, em seguida, define dois eventos: O Evento SizeChanged para alterar o tamanho
da fonte, e o evento Device.StartTimer para intervalos de um segundo para mudar a propriedade de Text. Ambos os eventos simplesmente
alteram uma propriedade do Label, e ambos são expressos como funções lambda para que eles possam acessar o Label sem que seja
armazenado como campo:
O StartTimer especifica uma cadeia de formatação personalizada para DateTime que resulta em 10 ou 11 caracteres, mas dois deles são
letras maiúsculas, e são mais larga. O SizeChanged implicitamente assume que 12 caracteres são exibidos, definindo o tamanho da fonte
para um sexto da largura da página:

Naturalmente, o texto é muito maior no modo paisagem:

Mais uma vez, esta técnica funciona em Android apenas se o tamanho da fonte esteja definido para Normal.

O primeiro segundo não vai assinalar exatamente no início de cada segundo, assim que o tempo exibido pode não concordar precisamente
com outros mostradores de tempo no mesmo dispositivo. Você pode torná-lo mais preciso, definindo uma escala de timer mais frequente. O
desempenho não será afetado muito porque a tela ainda muda apenas uma vez por segundo e não vai exigir um novo ciclo de layout até
então.

Montagem de texto
Outra abordagem para a montagem de texto dentro de um retângulo, de um tamanho particular envolve determinar empiricamente o tamanho
do texto processado com base em um tamanho de fonte particular e, em seguida, ajustando-se ao tamanho da fonte ou para baixo. Esta
abordagem tem a vantagem de trabalhar em dispositivos Android, independentemente de como o usuário definiu a configuração de tamanho
da fonte.
Porém, o processo pode ser complicado: O primeiro problema é que não há uma relação linear clara entre o tamanho da fonte e a altura do
texto processado. Como o texto torna-se maior em relação à largura do seu recipiente, as linhas de ruptura com maior frequência entre
palavras, e mais espaço desperdiçado. Um cálculo para encontrar o tamanho da fonte muitas vezes envolve um loop.
Um segundo problema envolve o mecanismo real de se obter o tamanho de uma Label processado com um tamanho de fonte em particular.
Você pode definir um SizeChanged no Label, mas dentro desse manipulador você não quer fazer quaisquer alterações (como a definição da
FontSize property) que farão com que chamadas recursivas.
Uma abordagem melhor é chamar o método GetSizeRequest definido por VisualElement e herdada por Label e todos os outros pontos da
view. GetSizeRequest requer dois argumentos de uma restrição de largura e uma restrição de altura. Estes valores indicam o tamanho do
retângulo em que você quer se ajustar ao elemento, e um ou outro pode ser infinito. Ao usar GetSizeRequest com um Label, geralmente você
definir o argumento restrição de largura para a largura do recipiente e define o pedido da altura para to Double.PositiveInfinity.
O método GetSizeRequest retorna um valor do tipo SizeRequest, uma estrutura com duas propriedades nomeadas mínimo e Request, ambos
do tipo Size. A propriedade Request indica o tamanho do texto processado. (mais informações sobre este e métodos relacionados aparecer
nos próximos capítulos sobre modos de exibição personalizados e layouts.)
O projeto EmpiricalFontSize demonstra essa técnica. Por conveniência, ela define uma estrutura pequena chamada FontCalc que faz a
chamada para GetSizeRequest para uma Label particular (já inicializada com texto), um tamanho de fonte, e uma largura do texto:

A altura resultante Label é salvo na propriedade TextHeight.


Quando você faz uma chamada para GetSizeRequest em uma página ou um layout, a página ou o layout deve obter os tamanhos de todos
os seus filhos através da árvore view. Isto tem uma penalidade de desempenho, é claro, assim que você deve evitar fazer chamadas, a menos
que necessário. Mas um Label não tem filhos, então chamando GetSizeRequest em um Label não é tão ruim. No entanto, você ainda deve
tentar otimizar as chamadas. Evite loops através de para determinar valores máximos da fonte que não resulta em texto exceder a altura do
recipiente. Um processo que se estreita em algoritmos em um valor ideal é melhor.
GetSizeRequest requer que o elemento deve ser parte de uma árvore view e que o processo de disposição tenha pelo menos parcialmente
começado. Não chame GetSizeRequest no construtor de sua classe de página. Você não terá informações a partir dele. A primeira
oportunidade razoável é em uma substituição do método OnAppearing da página. Claro, você pode não ter informações suficientes neste
momento para passar argumentos para o método GetSizeRequest.
A classe EmpiricalFontSizePage instancia valores FontCalc no SizeChanged do ContentView que hospeda o Label. (Este é o mesmo
manipulador de eventos usados no programa EstimatedFontSize.) O construtor de cada valor FontCalc faz GetSizeRequest instanciar um
Label e salva o resultado no TextHeight. O SizeChanged começa com ensaios de tamanhos de fonte de 10 e 100 sob o pressuposto de que
o valor ideal está em algum lugar entre esses dois e que estes representam limites inferiores e superiores. Daí os nomes de variáveis
lowerFontCalc e upperFontCalc:
Em cada iteração do circuito de tempo, as propriedades FontSize desses dois valores FontCalc são calculados e um novo FontCalc é obtido.
Isto torna-se o novo valor lowerFontCalc ou upperFontCalc dependendo da altura do texto processado. O loop termina quando o tamanho da
fonte é calculado dentro de uma unidade do valor ótimo.
Cerca de sete iterações do ciclo são suficientes para obter um valor que é claramente melhor do que o valor estimado calculado no programa
anterior:
Rodar os lados do telefone desencadeia outro novo cálculo que resulta em um tamanho semelhante (embora não necessariamente o mesmo)
o de fonte:

Pode parecer que o algoritmo pode ser melhorado. Mas a relação entre o tamanho da fonte e altura texto processado é bastante complexo e,
às vezes a abordagem mais fácil é tão bom.

Capítulo 6
Cliques de Botão
Os componentes de uma interface gráfica podem ser divididos aproximadamente em componentes que são usados para a apresentação
(exibição de informações para o usuário) e interação (obtenção de entrada do usuário). Enquanto o componente Label é o mais básico de
apresentação, o componente Button é provavelmente a visão interativa arquetípica. O Button sinaliza um comando. É a maneira do usuário
de dizer ao programa para iniciar alguma ação—para fazer alguma coisa.
Um botão Xamarin.Forms exibe o texto, com ou sem uma imagem em anexo. (Somente botões de texto são descritos neste capítulo;
acrescentar um botão de imagem é descrito no Capítulo 13, "Bitmaps".) Quando um dedo pressiona o botão, o botão muda sua aparência um
pouco para fornecer resposta ao usuário. Quando o dedo soltá-lo, o botão dispara um evento Clicked. Os dois argumentos do evento Clicked
são típicos dos eventos Xamarin.Forms:
• O primeiro argumento é o objeto que disparou o evento. Para o evento Clicked, este é o próprio componente que foi pressionado.
• O segundo argumento, em alguns casos, fornece mais informações sobre o evento. Para o evento Clicked, o segundo argumento é
simplesmente um objeto EventArgs que não fornece informação adicional.
Quando um usuário começa a interagir com uma aplicação, algumas necessidades especiais surgem: O aplicativo deve fazer um esforço para
salvar os resultados dessa interação no caso do programa ser encerrado antes que o usuário tenha terminado de trabalhar com ele. Por esse
motivo, este capítulo também descreve como um aplicativo pode salvar os dados transitórios, especialmente no contexto dos eventos de ciclo
de vida do aplicativo.
O Processamento do Clique
Aqui está um programa chamado ButtonLogger com um Button que compartilha uma StackLayout com um ScrollView contendo outro
StackLayout. Cada vez que o Button é clicado, o programa adiciona um novo Label para o StackLayout de rolagem, de fato registrando todos
os cliques do botão:

Nos programas neste livro, os eventos recebem nomes que começam com a palavra On, seguido por algum tipo de identificação do
componente disparador do evento (por vezes apenas o tipo de componente), seguido do nome do evento. O nome resultante neste caso é
OnButtonClicked. O construtor atribui o evento Clicked para o Button logo depois que o Button é criado. A página então é montada com um
StackLayout contendo o Button e um ScrollView com outro StackLayout, chamado loggerLayout. Observe que o ScrollView tem suas
VerticalOptions definidas para FillAndExpand para que ele possa compartilhar o StackLayout com o Button e ainda ser visível e de rolagem.
Aqui está a tela depois de vários cliques do botão:
Como você pode ver, o Button parece um pouco diferente nas três telas. Isso porque o botão é processado de forma nativa em cada plataforma:
no iPhone é um UIButton, no Android é um Android Button, e no Windows Phone é um Windows Phone Button. Por padrão, o botão sempre
preenche a área disponível para ele e centraliza o texto internamente.
Button define várias propriedades que permitem personalizar sua aparência:
• FontFamily
• FontSize
• FontAttributes
• TextColor
• BorderColor
• BorderWidth
• BorderRadius
• Image (a ser discutido no Capítulo 13)

Button também herda a propriedade BackgroundColor (entre tantas outras propriedades) de VisualElement e herda HorizontalOptions e
VerticalOptions de View.
Algumas propriedades do Button podem não funcionar em todas as plataformas. No iPhone você precisa definir Border-Width para um valor
positivo para uma borda ser exibida, mas isso é normal para um botão de iPhone. O botão Android não vai exibir uma borda a menos que
BackgroundColor seja definida, e, em seguida, ele requer uma configuração não-padrão de BorderColor e um BorderWidth positivo. A
propriedade BorderRadius se destina a arredondar os cantos afiados da borda, mas não funciona no Windows Phone. Suponha que você
escreveu um programa semelhante ao ButtonLogger mas não salvou o objeto loggerLayout como um campo. Você poderia obter acesso ao
objeto StackLayout no evento Clicked? Sim! É possível obter elementos visuais pai e filho por meio da técnica de navegar pela árvore visual.
O argumento sender para o evento OnButtonClicked é o objeto que dispara o evento, neste caso, o Button, para que possa começar o evento
Clicked lançando esse argumento:
Button button = (Button)sender;
Você sabe que o Button é um nó de um StackLayout, de modo que objeto é acessível a partir da propriedade ParentView. Mais uma vez, é
necessário uma conversão:
StackLayout outerLayout = (StackLayout)button.ParentView;
O segundo nó deste StackLayout é o ScrollView, portanto, a propriedade Children pode ser indexada para conseguir que:
ScrollView scrollView = (ScrollView)outerLayout.Children[1];
A propriedade Content deste ScrollView é exatamente o StackLayout que você estava procurando:
StackLayout loggerLayout = (StackLayout)scrollView.Content;
É claro, o perigo em fazer algo parecido com isto é que você pode alterar o layout algum dia e se esqueça de alterar o código de navegação
da árvore visual semelhante. Mas a técnica é útil se o código que monta sua página é separado do código de tratamento de eventos da View
dessa página.

Compartilhando cliques de botão


Se um programa contém vários Button, cada Button pode ter seu próprio evento Clicked. Mas, em alguns casos, pode ser mais conveniente
para vários Button compartilhar um evento Clicked em comum.
Considere um programa de calculadora. Cada um dos botões de 0 a 9 faz basicamente a mesma coisa, e ter 10 eventos Clicked separados
para estes 10 botões—até mesmo se eles compartilham algum código em comum—simplesmente não faz muito sentido.
Você viu como o primeiro argumento para o evento Clicked pode ser convertido para um objeto do tipo Button. Mas como você sabe qual
Button é?Uma abordagem é armazenar todos os objetos Button como campos e, em seguida, comparar o objeto Button disparador do evento
com estes campos. O programa TwoButtons demonstra esta técnica. Este programa é similar ao programa anterior, mas com dois botões—
um para adicionar objetos Label para o StackLayout, e o outro para removê-los. Os dois objetos Button são armazenados como campos para
que o evento Clicked possa determinar qual deles disparou o evento:
Ambos os botões recebem um valor de HorizontalOptions e CenterAndExpand de modo que eles podem ser dispostos lado a lado na parte
superior da tela utilizando um StackLayout horizontal:
Observe que quando o evento Clicked detecta removeButton, ele simplesmente chama o método RemoveAt na propriedade Children:
loggerLayout.Children.RemoveAt(0);

Mas o que acontece se não há nós? O método RemoveAt não vai levantar uma exceção? Isso não pode acontecer! Quando o programa
TwoButtons começa, a propriedade IsEnabled do remove-Button é inicializada como false. Quando um botão está desabilitado, ele apresenta
uma aparência fraca que faz com que ele parece ser não funcional, e ele realmente não está funcionando. Ele não fornece resposta para o
usuário e não dispara eventos Clicked. Perto do final do evento Clicked, a propriedade IsEnabled do removeButton é definido como true
somente se o loggerLayout tem pelo menos um nó. Isso ilustra uma boa prática: se o seu código precisa determinar se um evento Clicked de
botão é válido, é provavelmente melhor evitar cliques inválidos de botão desativando-os.

Gerenciadores de Eventos Anônimos


Nos dias de hoje muitos programadores C# tendem a definir gerenciadores de eventos anônimos como funções lambda. Isto permite que o
código dos gerenciadores de eventos fiquem próximos do instanciamento e inicialização dos objetos disparando eventos ao invés de estarem
em qualquer outro lugar no arquivo. Isto também permite que os objetos sejam referenciados de dentro do gerenciador de eventos sem precisar
armazenar estes objetos como campos.
Observe abaixo o programa chamado ButtonLambdas que contém um Label para mostrar um número e mais dois botões. Um dos botões
dobra o valor do número e o outro divide. Normalmente, as variáveis number e Label seriam salvas como campos. Mas, devido a utilização
de gerenciamento de eventos anônimo diretamente no construtor depois que elas já estão definidas, os gereciadores de eventos têm acesso
as mesmas:
Note o uso de Device.GetNamedSize para obter o tamanho da fonte de texto para os objetos Label e Button. O segundo argumento de
GetNamedSize é diferente para cada um dos tipos de objeto obtendo assim o tamanho apropriado para cada um deles. Como demonstrado
no programa anterior, os dois botões ficam dispostos horizontalmente no mesmo StackLayout:

A desvantagem na utilização de gerenciadores de eventos como funções lambda é que eles não podem ser re-utilizados em diferentes
visualizações. (Na verdade, seria possível, mas para isto seria necessário utilizar técnicas de codificação baseadas em reflection que
acabariam deixando o código bem mais complexo)

Visualizações Separadas por Identificadores


No programa TwoButtons você observa uma técnica de compartilhamento de gerenciamento de eventos que separa as visualizações
comparando os objetos. Isto funciona bem quando não existem muitas visualizações, mas não seria muito adequado para um programa de
calculadora.
A classe Element define uma propriedade StyleId do tipo string com o objetivo específico de identificar as visualizações. Isto pode ser utilizado
onde quer que seja conveniente na aplicação. Você pode testar os valores utilizando declarações de bloco como if/else, switch/case ou você
pode utilizar um método Parse para converter strings em números ou enumerações.
O programa a seguir não é uma calculadora, mas sim um teclado numérico, o que certamente pode ser parte de uma calculadora. Este
programa é chamado SimplestKeypad e utiliza um StackLayout para organizar as linhas e colunas das teclas. (Um dos objetivos do programa
é demonstrar que StackLayout não necessariamente seria a ferramenta correta para este trabalho!)
O programa cria um total de cinco instâncias de StackLayout. The mainStack tem orientação vertical com quatro objetos StackLayout
horizontais dispostos em 10 botões para os dígitos. Para manter a simplicidade, o teclado numérico está ordenado como um teclado de telefone
ao invés de teclado de calculadora.
Todas as 10 teclas numéricas compartilham um único gerenciador Clicked. A propriedade StyleId indica o número que está associado a tecla
então o programa pode simplesmente adicionar o número na string mostrada no Label. O StyleId é idêntico a propriedade Text do Button então
Text poderia ser utilizada ao invés de StyleId, mas em geral, isto não seria assim tão conveniente.
O botão voltar funciona de forma diferente tendo o seu próprio gerenciador Clicked mesmo sabendo que seria possível combinar os dois
métodos em apenas um para obter a vantagem de escrever apenas um código para endereçar o que eles têm em comum. Para deixar o
teclado com um tamanho um pouco maior, o tamanho da fonte de texto FontSize é ajustado utilizando NamedSize.Large. Aqui estão as três
telas renderizadas pelo programa SimplestKeypad:

É claro que você poderia pressionar as teclas repetidamente para ver como o programa responde a uma grande quantidade de digitos e
descobrir que ele não está preparado para isto. Quando o conteúdo do Label se torna muito grande ele passa a comandar o comprimento total
do StackLayout vertical e os botões também começam a se movimentar.
Mesmo antes disso, você poderia notar algumas pequenas irregularidades no comprimento do Button, particularmente no Windows Phone.
Os comprimentos individuais dos objetos Button são baseados no seu conteúdo, e para dígitos decimais, muitas fontes tem comprimento
diferentes. Seria possível resolver este problema usando Expands flag na propriedade HorizontalOptions? Não. A flag Expands acaba
adicionando espaços extras igualmente distribuidos pelas visualizações no StackLayout. Cada visualização irá aumentar linearmente a mesma
quantidade então mesmo assim elas não estarão com o mesmo comprimento. Por exemplo, de uma olhada em dois botões no programa
TwoButtons ou no ButtonLambdas. Eles têm as suas propriedades HorizontalOptions inicializadas como FillAndExpand, mas elas têm
comprimentos diferentes porque os comprimentos do conteúdo do Button são diferentes.
Uma solução melhor para estes programas seria utilizar outro recurso conhecido como Grid que será abordado no capítudo 18.

Salvando dados temporários


Imagine que você está digitando um número importante no programa SimplestKeypad e antes de terminar o que estava fazendo você é
interrompido, talvez por uma ligação telefonica. Depois disso, você desliga o telefone e consequentemente o programa também é finalizado.
O que deveria acontecer na próxima vez que você rodar SimplestKeypad ? Os números previamente digitados deveriam ser descartados? Ou
o programa deveria reiniciar de onde parou mantendo o estado em que foi abandonado? Obviamente, isto não importa em um simples
programa de demonstração como SimplestKeypad mas, em geral, os usuários esperam que os aplicativos para dispositivos móveis lembrem
exatamente o que eles estavam fazendo desde a última vez que o programa foi utilizado. Por esta razão, a classe Application suporta duas
funcionalidades que ajudam o programa a salvar e restaurar dados:
• A propriedade Properties da classe Application é um dicionário de chaves do tipo string e items to tipo object. O conteudo deste
dicionário é salvo automaticamente antes da aplicação ser finalizada e todo o conteúdo salvo se torna disponível na próxima vez
que a aplicação roda.

• A classe Application implementa três metodos virtuais protegidos de nome OnStart, OnSleep, e OnResume, e a classe App gerada
pelo modelo do Xamarin.Forms sobrepõe/sobre-escreve estes métodos. Eles ajudam um programa a gerenciar o que conhecemos
como eventos de ciclo de vida da aplicação.

Para utilizar estas funcionalidades você precisa identificar quais informações do programa precisam ser salvos e, desta forma, poder restaurar
o seu estado depois de terminado e reiniciado. Em geral, esta é uma combinação de configurações da aplicação – como cores e tamanhos
de fontes de texto que o usuário possa ter escolhido – e dados temporários, como parte de dados já registrados nos campos.
As configurações geralmente são pertinentes a aplicação como um todo enquanto que dados temporários são únicos para cada página ou
tela da aplicação. Se cada item destes dados se referem a uma entrada no dicionário Properties, cada item precisará ter uma chave. Entretanto,
se um programa precisa salvar um arquivo de tamanho grande, por exemplo um documento gerado em processador de texto Word, ele não
deveria utilizar o dicionário Properties, mas sim utilizar o acesso a plataforma do sistema de arquivos diretamente. (Este é um assunto a ser
tratado num capítulo posterior.)
O programa SimplestKeypad precisa salvar apenas um único item de dados temporários e a chave displayLabelText no dicionário parece
suficiente para esta necessidade. Também é possível que um programa use o dicionário Properties para salvar e recuperar dados sem envolver
os eventos de ciclo de vida da aplicação. Por exemplo, o programa SimplestKeypad sabe exatamente quando a propriedade Text do
displayLabel é modificada. Isto ocorre apenas nos dois gerenciadores de eventos Clicked para as teclas numéricas e a tecla de apagar.
Estes dois gerenciadores de eventos poderiam simplesmente armazenar os novos valores no dicionário Properties. Mas espere: Properties é
uma propriedade da classe Application. Precisamos salvar a instância da classe App para que o código em SimplestKeypadPage possa
acessar o dicionário? Não, isto não é necessário. A classe Application implementa uma propriedade estática chamada Current que retorna
qual é a instância atual da aplicação. Para armazenar a propriedade Text do Label no dicionário, basta adicionar as seguintes linhas de código
ao final do gerenciador de eventos Clicked no programa SimplestKeypad:

Application.Current.Properties["displayLabelText"] = displayLabel.Text;

Não se preocupe caso a chave displayLabelText ainda não exista no dicionário: O dicionário Properties implementa uma interface genérica
IDictionary, a qual define explicitamente que o indexador substituia o item anterior caso a chave já exista ou adicione um novo item no dicionário
se a chave ainda não existir. Este comportamento é exatamente o que você esperava neste caso. O construtor SimplestKeypadPage pode
então concluir seu trabalho inicializando a propriedade Text do Label com o seguinte código que recupera o item do dicionário:

Isto é tudo que a sua aplicação precisa fazer: salvar a informação no dicionário Properties e recuperá-la. O Xamarin.Forms é que fica
responsável pelo trabalho de salvar e carregar os conteúdos do dicionário no sistema de armazenamento da aplicação para cada plataforma
específica. Em geral, no entanto, é melhor para uma aplicação interagir com o dicionário Properties de uma forma mais estruturada, e é aqui
onde os eventos de ciclo de vida da aplicação entram em cena. Estes são os três métodos que aparecem na classe App criada pelo modelo
Xamarin.Forms:
O mais importante é a chamada do evento OnSleep. Em geral, uma aplicação entra em modo suspenso quando ela perde o comando da tela
e se torna inativa (salvo qualquer execução de tarefa ou serviço em segundo plano que ela possa ter iniciado).
Estando no modo de suspensão, a aplicação pode ser reiniciada (sinalizada por uma chamada OnResume) ou finalizada. Mas, existe um
ponto importante: Depois de uma chamada OnSleep não existe nenhuma notificação adicional para informar que a aplicação foi finalizada. A
chamada OnSleep é o mais próximo possível que você pode chegar de uma notificação de finalização e ela sempre vai ter precedencia sobre
a finalização. Por exemplo, se a aplicação está rodando e o usuário desliga o telefone, a aplicação recebe uma chamada OnSleep uma vez
que o telefone está sendo desligado.
Na realidade, existem algumas exceções a esta regra onde uma chamada OnSleep sempre tem precedencia sobre a finalização: um programa
que trava não irá receber uma chamada OnSleep antes de travar, mas você provavelmente já teria esta expectativa. Por outro lado, aqui está
um caso para o qual você não anteciparia esta situação: Quando você está depurando uma aplicação Xamarin.Forms e utiliza o Visual Studio
ou Xamarin Studio, o programa é finalizado sem uma chamada antecipada de OnSleep. Isto significa que quando você está depurando código
que utiliza eventos de ciclo de vida da aplicação, você deve ter o hábito de usar o próprio telefone para suspender o programa, para reiniciar
o programa e para finalizá-lo.
Quando a sua aplicação Xamarin.Forms está rodando, a forma mais fácil de disparar uma chamada OnSleep num simulador de telefone é
pressionando o botão Home. Depois você pode trazer o programa novamente para primeiro plano e disparar uma chamada OnResume
selecionando a aplicação via home menu(em dispositivos iOS e Android) ou pressionando o botão Back (em dispositivos Android e Windows
Phone).
Se o seu programa Xamarin.Forms está rodando e você chama o seletor de aplicações do telefone – pressionando o botão Home duas vezes
em dispositivos iOS, pressionando o botão Multitask em dispositivos Android (ou mantendo o botão Home pressionado em dispositivos
Android mais antigos), ou mantendo o botão Back pressionado no Windows Phone – a aplicação recebe uma chamada OnSleep. Depois disto
se você selecionar o programa, a aplicação recebe uma chamada OnResume pois irá resumir a sua execução. Ao invés disto, caso a aplicação
seja terminada – arrastando a imagem do aplicativo para cima até “jogar fora” nos dispositivos iOS ou pressionando o botão X que aparece
no canto superior direito na imagem da aplicação nos dispositivos Android ou no Windows Phone – o programa para de executar sem receber
nenhuma notificação adicional.
Assim, aqui segue uma regra básica: não importa quando a sua aplicação recebe uma chamada OnSleep, você tem que garantir que o
dicionário Properties tenha todas as informações que você gostaria de salvar a respeito da aplicação.
Se você está utilizando eventos de ciclo de vida da aplicação apenas para salvar e restaurar dados do programa, você não precisa controlar
o método OnResume. Quando o seu programa recebe uma chamada OnResume, o sistema operacional restaura o conteúdo e estado da
aplicação automaticamente. Caso você queira, você pode utilizar OnResume como uma oportunidade para limpar o dicionário Properties
porque você tem certeza de que haverá outra chamada OnSleep antes do programa ser finalizado. Por outro lado, caso o seu programa tenha
estabelecido uma conexão com um serviço web – ou esteja em processo para estabelecer tal conexão – você poderia utilizar OnResume para
restaurar a conexão. Talvez o tempo limite de espera da conexão tenha expirado durante o período que o programa ficou inativo. Ou pode ser
que novos dados estejam disponíveis.
Você tem algumas flexibilidades quando você restaura dados do dicionário Properties para sua aplicação quando o programa começa a rodar.
Quando um programa Xamarin.Forms é inicializado, a primeira oportunidade que você tem para executar um código na biblioteca de classes
portáveis é no construtor da classe App. Neste momento o dicionário Properties já foi preenchido com dados salvos que vieram da plataforma
de armazenamento específica. O próximo código executado é geralmente o construtor da página inicial da sua aplicação que é instanciado
via construtor de App. A chamada OnStart em Application(e App) acontece em seguida, e então um método substituível chamado OnAppearing
é chamado na classe da página. Você pode recuperar os dados a qualquer momento durante este processo de inicialização.
Os dados que uma aplicação precisa salvar geralmente estão na classe de uma página, mas a substituição/sobreposição de OnSleep está na
classe App. Então de algum jeito as classes página e App tem que se comunicar. Uma forma que funciona bem para uma aplicação que tem
apenas uma página – de fato, a classe Application tem uma propriedade estática chamada MainPage a qual é definida no construtor App onde
o método OnSleep pode obter acesso aquela página – mas isto não vai funcionar muito bem para uma aplicação com várias páginas.
Veja abaixo uma forma um pouco diferente: defina todos os dados que precisam ser salvos como propriedades públicas na classe App, por
exemplo:

A classe (ou classes) de página podem então atribuir e recuperar valores destas propriedades quando for conveniente. A classe App consegue
restaurar este tipo de propriedade do dicionário Properties no seu construtor e antes de instanciar a página consegue armazenar as
propriedades no dicionário Properties no seu método sobreposto OnSleep.
Esta é a forma empregada no projeto PersistentKeypad. Este programa é idêntico ao programa Simplest-Keypad exceto que ele adiciona
código para salvar e restaurar as informações do teclado. Aqui está a classe App que implementa a propriedade DisplayLabelText que é salva
no método sobreposto OnSleep e carregada no construtor de App:

Para evitar erros de ortografia, a classe App declara a string da chave dicionário como uma constante. Ela tem o mesmo nome da propriedade,
mas começa com letra minúscula. Note que a propriedade DisplayLabelText é declarada antes de instanciar PersistentKeypadPage então ela
já está disponível no construtor PersistentKeypadPage.
Uma aplicação contendo vários itens poderia consolidá-los em uma classe de nome AppSettings (por exemplo), serializar esta classe em uma
string XML ou JSON, e então salvar a string no dicionário. A classe PersistentKeypadPage acessa a propriedade DisplayLabelText no seu
construtor e define a propriedade nos seus dois gerenciadores de eventos:
Durante a execução de testes de programas que usam o dicionário Properties e eventos de ciclo de vida de aplicação você poderia precisar,
ocasionalmente, desinstalar o programa do telefone ou simulador. Desinstalar um programa do dispositivo também remove qualquer dado
armazenado para aquele programa, então da próxima vez que o programa for transmitido/instalado do Visual Studio ou Xamarin Studio para/no
o aparelho ou simulador, o programa vai ter um dicionário vazio, como se estivesse rodando pela primeira vez.
Capítulo 7
XAML vs Código
C# é sem dúvida uma das maiores linguagens de programação que o mundo já viu. Você pode escrever aplicativos Xamarin.Forms inteiros
em C#, e é concebível que você encontrou C# para ser tão ideal para Xamarin.Forms que você não tenha sequer considerado usando qualquer
outra coisa.
Xamarin.Forms fornece uma alternativa para o C# que tem algumas vantagens distintas para determinados aspectos do desenvolvimento do
programa. Esta alternativa é o XAML (pronuncia-se "zammel"), que representa o Extensible Application Markup Language. Como C#, XAML
foi desenvolvido na Microsoft Corporation, e é apenas alguns anos mais jovem do que C#.
Como o próprio nome sugere, XAML adere à sintaxe XML, o Extensible Markup Language. Este livro pressupõe que você tenha familiaridade
com os conceitos e sintaxe XML básico.
No sentido mais geral, XAML é uma linguagem de marcação declarativa utilizada para instanciar e inicializar objetos. Essa definição pode
parecer excessivamente geral e XAML é de fato bastante flexível. Mas a maioria XAML do mundo real tem sido utilizado para a definição do
usuário visual em formato de árvore. A história das interfaces de usuário baseadas XAML começa com o Windows Presentation Foundation
(WPF) e continua com o Silverlight, Windows Phone 7 e 8 e Windows 8 e 10. Cada uma destas implementações de XAML suporta um conjunto
um tanto diferente de elementos visuais definido pela plataforma. Da mesma forma, a implementação XAML em Xamarin.Forms suporta os
elementos visuais definidos pela Xamarin.Forms, como Label, BoxView, Frame, Button, StackLayout e ContentPage.
Como você viu, uma aplicação Xamarin.Forms pode ser inteiramente escrita em código geralmente se define a aparência inicial de sua
interface do usuário no construtor de uma classe que deriva de ContentPage. Se você optar por usar XAML, a marcação geralmente substitui
este código construtor. Você vai descobrir que XAML fornece uma definição mais sucinta e elegante da interface do usuário e tem uma
estrutura visual que melhor mimetiza a organização em árvore dos elementos visuais na página.
XAML também é geralmente mais fácil de manter e modificar do que o código equivalente. Porque XAML é XML, também é potencialmente
toolable: XAML pode mais facilmente ser analisado e editado por ferramentas de software que o código C# equivalente. De fato, um impulso
para trás no início XAML era facilitar uma colaboração entre programadores e designers: Os designers podem usar ferramentas de projeto
que geram XAML, enquanto os programadores se concentrar no código que interage com a marcação. Esta visão talvez tenha sido cumprida
à perfeição, ele certamente sugere como os aplicativos podem ser estruturados para acomodar XAML. Você usa XAML para o visual e código
para a lógica subjacente.
No entanto, XAML vai além desta simples divisão de trabalho. Como você verá em um capítulo futuro, é possível definir ligações à direita no
XAML que vinculam objetos de interface do usuário com os dados subjacentes.
Ao criar XAML para plataformas da Microsoft, alguns desenvolvedores usam ferramentas de design interativos, como Microsoft Blend, mas
muitos outros preferem escrever à mão XAML. Nenhuma ferramenta de design está disponível para Xamarin.Forms, então escrever a mão é
a única opção. Obviamente, todos os exemplos XAML neste livro são escritos à mão. Mas mesmo quando as ferramentas de design estão
disponíveis, a capacidade de escrever à mão XAML é uma habilidade importante.
A perspectiva de escrever na mão o XAML pode causar alguma consternação entre os desenvolvedores por outra razão: XML é notoriamente
detalhado. No entanto, você verá quase imediatamente que XAML é muitas vezes mais conciso do que o código C# equivalente. O verdadeiro
poder do XAML torna-se evidente apenas incrementalmente, no entanto, e não serão totalmente visíveis até o capítulo 19, quando você usa
XAML para a construção de modelos para vários itens exibidos em um ListView.
É natural para os programadores que preferem linguagens fortemente tipadas, como C# para ser cético em relação a uma linguagem de
marcação onde tudo é uma cadeia de texto. Mas você verá em breve como XAML é um análogo muito rigoroso de código de programação.
Muito do que é permitido em seus arquivos XAML é definida pelas classes e propriedades que compõem a interface de programação de
aplicativo Xamarin.Forms. Por esta razão, você pode até começar a pensar em XAML como uma linguagem de marcação "fortemente digitado".
O analisador XAML faz o seu trabalho de uma forma muito mecânico com base na infra-estrutura de API subjacente. Um dos objetivos deste
capítulo e o próximo é desmistificar XAML e iluminar o que acontece quando o XAML é analisado.
No entanto, código e marcação são muito diferentes: Código define um processo de marcação enquanto define um estado. XAML tem várias
deficiências que são intrínsecos à marcação idiomas: XAML não tem loops, sem controle de fluxo, nenhuma sintaxe cálculo algébrico, e não
há manipuladores de eventos. No entanto, XAML define vários recursos que ajudam a compensar algumas dessas deficiências. Você vai ver
muitas destas características nos capítulos futuros.
Se você não quiser usar XAML, você não precisa. Tudo o que pode ser feito em XAML pode ser feito em C#. Mas cuidado: Às vezes os
desenvolvedores obtem um gostinho de XAML e se deixam levar e tentar fazer tudo em XAML! Como de costume, a melhor regra é "moderação
em todas as coisas." Muitas das melhores técnicas envolvem a combinação de código e XAML de maneiras interativas.
Vamos começar essa exploração com alguns trechos de código e XAML o equivalente, e depois ver como XAML e código se encaixam em
um aplicativo Xamarin.Forms.
Propriedades e atributos
Aqui está uma Label Xamarin.Forms instanciada e inicializada em código, tanto quanto poderia parecer no construtor de uma classe de
página:

new Label
{
Text = "Hello from Code!", IsVisible =
true,
Opacity = 0.75,
XAlign = TextAlignment.Center,
VerticalOptions = LayoutOptions.CenterAndExpand, TextColor =
Color.Blue,
BackgroundColor = Color.FromRgb(255, 128, 128),
FontSize = Device.GetNamedSize(NamedSize.Large,
typeof(Label)), FontAttributes = FontAttributes.Bold |
FontAttributes.Italic
};

Aqui está uma Label muito semelhante instanciada e inicializada em XAML, que você pode ver imediatamente é mais conciso do que o
código equivalente:

<Label Text="Hello from XAML!" IsVisible="True"


Opacity="0.75"

XAlign="Center"
VerticalOptions="CenterAndExpand"
TextColor="Blue"
BackgroundColor="#FF8080"
FontSize="Large"
FontAttributes="Bold,Italic" />

Classe em Xamarin.Forms como Label se tornar elementos XML em XAML. Propriedades como Text, IsVisible, e o resto se tornar
atributos XML em XAML.
Para ser instanciada em XAML, uma classe como a Label deve ter um construtor sem parâmetros públicos. (No próximo capítulo, você vai
ver que há uma técnica para passar argumentos para um construtor em XAML, mas é geralmente utilizado para fins especiais). As propriedades
definidas em XAML deve ter assessores set públicas. Por convenção, os espaços cercam um sinal de igual no código, mas não em XML (ou
XAML), mas você pode usar tanto espaço em branco como você quer.
A concisão do XAML resulta principalmente da brevidade do exemplo de atributo valores for, o uso da palavra "Grande" em vez de uma
chamada para o método Device.GetNamedSize. Essas abreviaturas não são incorporadas ao analisador XAML. O analisador XAML em
vez disso é assistida por várias classes de conversor definidas especificamente para esta finalidade.
Quando o analisador XAML encontra o elemento Label, ele pode usar o reflexo para determinar se Xamarin.Forms tem uma Label classe
chamada e, nesse caso, é possível criar uma instância dessa classe. Agora ele está pronto para inicializar esse objeto. A propriedade de Text
é do tipo string, eo valor do atributo é simplesmente atribuído a essa propriedade.
Porque XAML é XML, você pode incluir caracteres Unicode no texto usando a sintaxe XML padrão. Preceder o valor decimal Unicode com &#
(ou o valor hexadecimal Unicode com &#x) e segui-lo com um ponto e vírgula:
Text="Cost &#x2014; &#x20AC;123.45"
Esses são os valores Unicode para o travessão e símbolo do euro. Para forçar uma quebra de linha, use o caractere de avanço de linha
&#x000A, ou (porque zeros à esquerda não são obrigados) &#xA, ou com o código decimal, &#10.
As aspas têm um significado especial em XML, de modo a incluir esses caracteres em uma sequência de texto, use uma das entidades
predefinidas padrão:
• &lt; for <
• &gt; for >
• &amp; for &
• &apos; for '
• &quot; for "

As entidades predefinidas HTML como &nbsp; não são suportados. Para um uso do espaço não separável &#xA0; em vez disso.
Além disso, chaves ({ and }) tem um significado especial em XAML. Se você precisa para começar um valor de atributo com uma chave
esquerda, comece com um par de chaves ({}) e, em seguida, a chave esquerda.
As propriedades IsVisible e Opacity da Label são do tipo bool e double, respectivamente, e estes são tão simples como se poderia
esperar. O analisador XAML utiliza os métodos Boolean.Parse e Double.Parse para converter os valores de atributos. O método
Boolean.Parse é caso insensível, mas os valores booleanos geralmente são capitalizados como "True" e "False" em XAML. O método
Double.Parse é passado um argumento CultureInfo.InvariantCulture, de modo que a conversão não depende da cultura local
do programador ou utilizador.
A propriedade XAlign da Label é do tipo TextAlignment, que é uma enumeração. Para qualquer propriedade que é um tipo de
enumeração, o analisador XAML usa o método Enum.Parse para converter a partir da cadeia de valor.
A propriedade VerticalOptions é do tipo LayoutOptions, uma estrutura. Quando o analisador XAML referência a estrutura
LayoutOptions usando a reflexão, descobre que a estrutura tem um atributo de C# definida:
[TypeConverter (typeof(LayoutOptionsConverter))]
public struct LayoutOptions
{

}

(Atenção! Essa discussão envolve dois tipos de atributos: Atributos XML como XAlign e C# atributos como este TypeConverter)
O atributo TypeConverter é apoiada por uma classe chamada TypeConverterAttribute. Este atributo TypeConverter em
LayoutOptions faz referência a uma classe chamada LayoutOptionsConverter. Esta é uma classe privada para Xamarin.Forms,
mas deriva de uma classe abstrata chamada pública TypeConverter que define métodos chamados CanConvertFrom e ConvertFrom.
Quando o analisador XAML encontra esse atributo TypeConverter, ele instancia o LayoutOptionsConverter. Os
VerticalOptions atribuir no XAML é atribuído a cadeia "Center", de modo que o analisador XAML passa que "Center" string para o
método ConvertFrom de LayoutOptionsConverter, e fora aparece um valor LayoutOptions. Isto é atribuído à propriedade
VerticalOptions do objecto da Label.
Da mesma forma, quando o analisador XAML encontra as propriedades TextColor e BackgroundColor, ele usa reflexão para determinar
que essas propriedades são do tipo Color. A estrutura de cores também é adornada com um atributo TypeConverter:

[TypeConverter (typeof(ColorTypeConverter))]
public struct Color
{

}

ColorTypeConverter é uma classe pública, para que você possa experimentá-lo se você gostaria. Ele aceita definição de cores em vários
formatos: Ele pode converter uma string como "Accent" strings para os valores Color.Default e Color.Accent, "Blue" para o valor Color.Blue,
eo "Default" e. ColorTypeConverter também pode analisar seqüências que codificam valores vermelho-verde-azul, como "# FF8080", que é
um valor vermelho de 0xFF, um valor verde de 0x80, e um valor de 0x80 azul também.
Todos os valores RGB numéricos começam com um prefixo de sinal de número, mas que prefixo pode ser seguido com oito, seis, quatro, ou
três dígitos hexadecimais para especificar valores de cor com ou sem um canal alfa. Aqui está a mais extensa sintaxe:
BackgroundColor="#aarrggbb"
Cada uma das cartas representa um dígito hexadecimal, na ordem alfa (opacidade), vermelho, verde e azul. Para o canal alfa, tenha em mente
que 0xFF é totalmente opaco e 0x00 é totalmente transparente. Aqui está a sintaxe sem um canal alfa:
BackgroundColor="#rrggbb"
Neste caso, o valor de alfa é definido como 0xFF para opacidade total.
Dois outros formatos permitem que você especificar apenas um único dígito hexadecimal para cada canal:
BackgroundColor="#argb"
BackgroundColor="#rgb"

Nestes casos, o dígito é repetido para formar o valor. Por exemplo, # CF3 é a cor RGB 0xCC-0xFF0x33. Esses formatos curtos raramente são
utilizados.
A propriedade FontSize é do tipo double. Este é um pouco diferente de propriedades do tipo LayoutOptions e Color. Os
LayoutOptions e Color são estruturas da parte de Xamarin.Forms, para que eles possam ser marcados com o C# TypeConverter
atributo, mas não é possível marcar na estrutura .NET um Double como atributo TypeConverter apenas para tamanhos de fonte!
Em vez disso, a propriedade FontSize dentro da classe de etiqueta tem o atributo TypeConverter:

public class Label : View, IFontElement


{

[TypeConverter (typeof (FontSizeConverter))]

public double FontSize


{

}

}

O FontSizeConverter determina se a string passada a ele é um dos membros da enumeração NamedSize. Se não,
FontSizeConverter assume o valor é um Double.
O último atributo definido no exemplo é FontAttributes. A propriedade FontAttributes é uma enumeração nomeado
FontAttributes, e você já sabe que o analisador XAML trata tipos de enumeração automaticamente. No entanto, a enumeração
FontAttributes tem um C# Flags atributo definido da seguinte forma:

[Flags]
public enum FontAttributes
{
None = 0,
Bold = 1,
Italic = 2
}

Por isso, o analisador XAML permite que vários membros separados por vírgulas:
FontAttributes="Bold,Italic"
Esta demonstração da natureza mecânica do analisador XAML deve ser uma notícia muito boa. Isso significa que você pode incluir classes
personalizadas em XAML, e essas classes podem ter propriedades de tipos personalizados, ou as propriedades podem ser de tipos padrão,
mas permitir que os valores adicionais. Tudo que você precisa é que esses tipos ou propriedades com um Atributo C# TypeConverter e
fornecer uma classe que deriva de TypeConverter.

Sintaxe Propriedade – Elemento


Aqui está alguns C# que é semelhante ao código FramedText no Capítulo 4. Em um comando que instancia um quadro e um rótulo e define
o rótulo para o Content propriedade da moldura:

new Frame
{
OutlineColor = Color.Accent,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = new Label
{
Text = "Greetings, Xamarin.Forms!"
}
};

Mas quando você começa a duplicar este em XAML, você pode se tornar um pouco frustrado no ponto onde você definir o atributo Content:
<Frame OutlineColor="Accent" HorizontalOptions="Center"
VerticalOptions="Center">
</Frame>

Como pode esse atributo Content ser definido como todo um objeto Label?
A solução para este problema é a característica mais fundamental de sintaxe XAML. O primeiro passo é o de separar o marcador do Frame
em marcas de início e de fim:
<Frame OutlineColor="Accent" HorizontalOptions="Center"
VerticalOptions="Center">
</Frame>

Dentro dessas marcas, adicionar mais duas marcas que consistem de o elemento (Frame) ea propriedade que você deseja definir (Content),
ligada com um período de:

<Frame OutlineColor="Accent" HorizontalOptions="Center"


VerticalOptions="Center">
<Frame.Content>
</Frame.Content>
</Frame>
Agora coloque a Label dentro dessas tags:
<Frame OutlineColor="Accent" HorizontalOptions="Center"
VerticalOptions="Center">
<Frame.Content>
<Label Text="Greetings, Xamarin.Forms!" />
</Frame.Content>
</Frame>
Essa sintaxe é como você seta a Label e o Content no Frame.
Você pode se perguntar se esse recurso XAML viola regras de sintaxe XML. Isso não. O período não tem nenhum significado especial em
XML, então Frame.Content é uma tag XML perfeitamente válido. No entanto, XAML impõe suas próprias regras sobre essas tags: As tags
Frame.Content deve aparecer dentro de tags de Frame, e nenhum atributo pode ser definido na tag Frame.Content. O objeto definido
para a propriedade de Content aparece como o conteúdo XML dessas tags.
Uma vez que essa sintaxe é introduzida, alguma terminologia torna-se necessário. No trecho XAML final mostrada acima:
• Frame e Label são objetos C# expressas como elementos XML. Eles são chamados de elementos do objeto.
• OutlineColor, HorizontalOptions, VerticalOptions, e Text são propriedades de C# expressas como atributos
XML. Eles são chamados de atributos de propriedades.
• Frame.Content é uma propriedade C# expressa como um elemento XML, e por isso é chamado de elemento de propriedade.

Elementos de propriedade são muito comuns em XAML da vida real. Você vai ver inúmeros exemplos neste capítulo e os capítulos futuros, e
em breve você vai encontrar elementos de propriedade tornando-se uma segunda natureza para o seu uso do XAML. Mas cuidado: Às vezes,
os desenvolvedores devem se lembrar tanto que nós esquecemos o básico. Mesmo depois de ter sido usando XAML por um tempo, você
provavelmente vai encontrar uma situação em que não parece possível definir um determinado objeto a uma propriedade particular. A solução
é muitas vezes um elemento de propriedade.
Você também pode usar a sintaxe da propriedade de elemento para propriedades mais simples, por exemplo:
<Frame HorizontalOptions="Center">
<Frame.VerticalOptions> Center
</Frame.VerticalOptions>
<Frame.OutlineColor> Accent
</Frame.OutlineColor>
<Frame.Content>
<Label>
<Label.Text>
Greetings, Xamarin.Forms!
</Label.Text>
</Label>
</Frame.Content>
</Frame>

Agora as propriedades VerticalOptions e OutlineColor são do Frame e a propriedade Text da Label têm todos se tornam
elementos de propriedade. O valor desses atributos é o conteúdo do elemento de propriedade sem aspas.
Claro, isso não faz muito sentido para definir essas propriedades como elementos da propriedade. É desnecessariamente detalhada. Mas ele
funciona como deveria.
Vamos ir um pouco mais longe: Em vez de definir HorizontalOptions para "Center" (correspondente à propriedade
LayoutOptions.Center estática), você pode expressar HorizontalOptions como um elemento da propriedade e configurá-lo para
um valor LayoutOptions com suas propriedades individuais estabelecidos:
<Frame>
<Frame.HorizontalOptions>
<LayoutOptions Alignment="Center" Expands="False" />
</Frame.HorizontalOptions>
<Frame.VerticalOptions> Center
</Frame.VerticalOptions>
<Frame.OutlineColor> Accent
</Frame.OutlineColor>
<Frame.Content>
<Label>
<Label.Text>
Greetings, Xamarin.Forms!
</Label.Text>
</Label>
</Frame.Content>
</Frame>

E você pode expressar essas propriedades de LayoutOptions como elementos de propriedade:

<Frame>
<Frame.HorizontalOptions>
<LayoutOptions>
<LayoutOptions.Alignment> Center
</LayoutOptions.Alignment>
<LayoutOptions.Expands> False
</LayoutOptions.Expands>
</LayoutOptions>
</Frame.HorizontalOptions>

</Frame>

Você não pode definir a mesma propriedade como um atributo de propriedade e um elemento de propriedade. Que está definindo a propriedade
duas vezes, e não é permitido. E lembre-se que nada mais pode aparecer nas marcas de elemento propriedade-. O valor a ser definido para
a propriedade é sempre o conteúdo XML dessas tags.
Agora você deve saber como usar um StackLayout em XAML. Primeiro expressar a propriedade Children como o elemento de
propriedade StackLayout.Children e, em seguida, incluir os filhos da StackLayout como conteúdo XML das tags propriedade de
elementos. Aqui está um exemplo onde o primeiro StackLayout tem um outro StackLayout com uma orientação horizontal:

<StackLayout>
<StackLayout.Children>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Red" />
<Label Text="Red"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Green" />
<Label Text="Green"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Blue" />
<Label Text="Blue"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
</StackLayout.Children>
</StackLayout>

Cada StackLayout horizontal tem um BoxView com uma cor e uma Label com esse nome cor.
Claro, a marcação repetitiva aqui parece um pouco assustador! E se você quiser exibir 16 cores? Ou 140? Você pode ter sucesso em primeiro
lugar com um monte de copiar e colar, mas se você, então, necessário para refinar o visual um pouco, você estaria em má forma. No código
que você faria isso em um loop, mas XAML não tem essa característica.
Quando a marcação ameaça de ser excessivamente repetitivo, você sempre pode usar o código. Definindo alguns de uma interface de usuário
em XAML eo restante em código é perfeitamente razoável. Mas há outras soluções, como você verá em capítulos posteriores.
Adicionando uma página XAML para o seu projeto
Agora que você já viu alguns trechos de XAML, vamos olhar para uma página XAML no contexto de um programa completo. Em primeiro lugar,
crie um projeto Xamarin.Forms chamado CodePlusXaml usando Portable Class Library do template do projeto.

Agora adicione um XAML ContentPage para o PCL. Veja como:


No Visual Studio, clique com o botão direito no projeto CodePlusXaml em Solucion Explorer. Selecione Add > New Item. Na caixa de diálogo
Add New Ítem, selecione Visual C# na lista central. Nomeie CodePlusXamlPage.cs.

No Xamarin Studio, clique com o botão direito no projeto CodePlusXaml em Solution Explorer. Selecione Add > New Item. Na caixa de diálogo
Add New Item, selecione a opção Forms e Forms Content Page Xaml na lista central. Nomeie CodePlusXamlPage.cs.

Em ambos os casos, são criados dois arquivos:

• CodePlusXamlPage.xaml, o arquivo XAML; e

• CodePlusXamlPage.xaml.cs, um arquivo C # (apesar da extensão dupla no nome do arquivo)

Na lista de arquivos, o segundo arquivo é recuado abaixo do primeiro, indicando a sua relação estreita. O arquivo C # é muitas vezes referida como
o code-behind do arquivo XAML. Ele contém código que suporta a marcação.

Esses dois arquivos ambos contribuem para uma classe chamada CodePlusXamlPage que deriva de ContentPage.

Vamos examinar o código da classe. Excluindo-se os usings, veja:


namespace CodePlusXaml
{
public partial class CodePlusXamlPage : ContentPage
{
public CodePlusXamlPage()
{
InitializeComponent();
}
}
}

Como podemos ver, é uma classe chamada CodePlusXamlPage que deriva de ContentPage, tal como previsto.
No entanto, a definição da classe inclui uma palavra-chave partial, que geralmente indica que esta é apenas uma parte da definição de
classe CodePlusXamlPage. Em outro lugar deve haver uma outra definição de classe parcial para CodePlusXamlPage. Então, se ela
existe, onde está? É um mistério! (Por enquanto.)
Outro mistério é o método InitializeComponent que o construtor chama. A julgar unicamente da sintaxe, parece que este método deve
ser definido ou herdado por ContentPage. No entanto, você não vai encontrar InitializeComponent na documentação da API.
Vamos deixar esses dois mistérios de lado temporariamente e olhar para o arquivo XAML. O Visual Studio e o Xamarin Studio geram dois
arquivos XAML um pouco diferentes. Se você estiver usando Visual Studio, altere as marcações da Label e substitua por
ContentPage.Content property element tags de modo que ele se parece com a versão em Xamarin Studio:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CodePlusXaml.CodePlusXamlPage">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>

O elemento raiz é ContentPage, que é a classe que CodePlusXamlPage deriva. Essa tag começa com duas declarações de namespace
XML, sendo que ambos são URIs. Mas não se incomode em verificar os endereços da web! Não há nada lá. Esses URIs simplesmente indicam
que é dono do domínio e qual a função.
O namespace padrão pertence à Xamarin. Este é o namespace XML para elementos do arquivo com nenhum prefixo, como o tag
ContentPage. A URL inclui o ano em que este domínio surgiu e a palavra forms como uma abreviação para Xamarin.Forms.
O segundo namespace está associado com um prefixo de x por convenção, e que pertence a Microsoft. Este namespace refere-se a
elementos e atributos que são intrínsecos à XAML e são encontrados em todas as Implementação XAML. A palavra WinFX refere-se a um
nome uma vez utilizado para o .NET Framework 3.0, que introduziu WPF e XAML. O ano de 2009 refere-se a uma especificação XAML
particular, o que também implica uma coleção particular de elementos e atributos que constroem em cima da especificação XAML original,
que é datada de 2006. No entanto, Xamarin.Forms implementa apenas um subconjunto dos elementos e atributos na especificação de 2009.
A próxima linha é um dos atributos que é intrínseco a XAML, chamados de Class. Porque o prefixo x é quase universalmente utilizado para
este namespace, este atributo é comumente referido como x: Class e pronunciada "X class."
O atributo x:class pode aparecer apenas no elemento raiz de um arquivo XAML. Ele especifica o .NET e suas classes derivadas. A classe
base desta classe derivada é o elemento raiz. em Ou seja, a especificação deste x:Class indica que a classe no CodePlusXamlPage no
CodePlusXaml deriva ContentPage. Essa é exatamente a mesma definição de classe CodePlusXamlPage no arquivo
CodePlusXamlPage.xaml.cs.
Vamos adicionar algum conteúdo, o que significa a criação de algo para o Content property, que no Arquivo XAML significa colocar algo
entre ContentPage.Content e no property-element tags. Inicie o conteúdo com um StackLayout, e em seguida, adicione um Label
para o Children property:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CodePlusXaml.CodePlusXamlPage">
<ContentPage.Content>
<StackLayout>
<StackLayout.Children>
<Label Text="Hello from XAML!" IsVisible="True"
Opacity="0.75" XAlign="Center"
VerticalOptions="CenterAndExpand" TextColor="Blue"
BackgroundColor="#FF8080" FontSize="Large"
FontAttributes="Bold,Italic" />
</StackLayout.Children>
</StackLayout>
</ContentPage.Content>
</ContentPage>

Essa é a Label XAML que você viu no início deste capítulo


Agora você vai precisar alterar a classe App para instanciar esta página assim como você faz com um code-only derivado de ContentPage:

namespace CodePlusXaml
{
public class App : Application
{
public App()
{
MainPage = new CodePlusXamlPage();
}

}
}

Agora você pode fazer build e deploy este programa. Depois de fazer isso, é possível esclarecer um par de mistérios:
Em Visual Studio, no Solution Explorer, selecione o projeto CodePlusXaml, e clique no ícone na parte superior com o tooltip Show All Files.
Em Xamarin Studio, na lista de arquivos Solution,acessar o menu drop-down para toda a solução, e selecione Display Options > Show All
Files.
No projeto CodePlusXaml Portable Class Library, localize a pasta obj e dentro dela, a pasta Debug. Você verá um arquivo chamado
CodePlusXamlPage.xaml.g.cs. Observe o g no nome do arquivo. Isso significa gerado. Aqui está ele, completo com o comentário que diz que
este arquivo é gerado por uma ferramenta:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.35317
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CodePlusXaml
{
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

public partial class CodePlusXamlPage : ContentPage


{
private void InitializeComponent()
{
this.LoadFromXaml(typeof(CodePlusXamlPage));
}
}
}

Durante o processo de compilação, o arquivo XAML é analisado, e este arquivo de código é gerado. Observe que é uma definição de classe
parcial de CodePlusXamlPage, que deriva de ContentPage, e a classe contém um método chamado InitializeComponent.
Em outras palavras, é um ajuste perfeito para o arquivo de code-behind CodePlusXamlPage.xaml.cs. após o arquivo
CodePlusXamlPage.xaml.g.cs ser gerado, os dois arquivos podem ser compilados em conjunto como se fossem apenas C # normal. O arquivo
XAML não tem outro papel no processo de construção, mas o todo o arquivo XAML está vinculado para o executável como um recurso
incorporado (assim como o Edgar Allan Poe exemplificou no programa BlackCat no Capítulo 4).
Em tempo de execução, a classe App instancia a classe CodePlusXamlPage. O construtor CodePlusXamlPage (Definido no arquivo
code-behind) chama InitializeComponent (definido no arquivo gerado), e InitializeComponent chama LoadFromXaml. Este é
um método de extensão View definido nas extensões de classe do Xamarin.Forms.Xaml. LoadFromXaml carrega o arquivo XAML (que você
vai se lembrar é vinculado para o executável como um recurso incorporado) e analisa-lo pela segunda vez. Porque esta análise ocorre em
tempo de execução, LoadFromXaml pode instanciar e inicializar todos os elementos do arquivo XAML, exceto para o elemento de raiz, o
que já existe. Quando o InitializeComponent método retorna, toda a página estará no lugar, como se tudo tivesse sido instanciado e
inicializado em código no construtor CodePlusXamlPage.
É possível continuar a adicionar conteúdo à página após o retorno do InitializeComponent no construtor do arquivo code-behind. Vamos
usar esta oportunidade para criar outra label usando algum código anterior neste capítulo:

namespace CodePlusXaml
{
public partial class CodePlusXamlPage : ContentPage
{
public CodePlusXamlPage()
{
InitializeComponent();

Label label = new Label


{
Text = "Hello from Code!", IsVisible
= true,
Opacity = 0.75,
XAlign = TextAlignment.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
TextColor = Color.Blue,
BackgroundColor = Color.FromRgb(255, 128, 128),
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
FontAttributes = FontAttributes.Bold | FontAttributes.Italic
};

(Content as StackLayout).Children.Insert(0, label);


}
}
}
O construtor conclui acessando o StackLayout que sabemos que está definido para o Content property da página e inserindo a label
na parte superior. (No próximo capítulo, você verá melhores maneiras de fazer referência a objetos no arquivo XAML usando o atributo
x:Name) Você pode criar a label antes da chamada InitializeComponent, mas você não pode adicioná-lo à StackLayout porque
InitializeComponent é o que faz com que o Stacklayout (e todos os outros elementos XAML) seja instanciado. Aqui está o resultado:

Além do texto, os dois botões são idênticos.


Você não tem que gastar muito tempo examinando o arquivo de código gerado que o analisador XAML cria, mas é útil entender como o arquivo
XAML desempenha um papel tanto no processo de construção e durante tempo de execução. No entanto, às vezes um erro na chamada do
arquivo XAML gera uma exceção de tempo de execução no LoadFromXaml, então você provavelmente vai ver o arquivo de código gerado
aparecer com freqüência, e você deve saber o que é.

Plataformas específicas do arquivo XAML


Aqui está o arquivo XAML para um programa chamado ScaryColorList que é semelhante a um trecho de XAML que você viu anteriormente.
Mas agora a repetição é ainda mais assustadora, porque cada item de cor é cercado por um Frame:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="ScaryColorList.ScaryColorListPage">

<ContentPage.Content>
<StackLayout>
<StackLayout.Children>
<Frame OutlineColor="Accent">
<Frame.Content>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Red" />
<Label Text="Red"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
</Frame.Content>
</Frame>
<Frame OutlineColor="Accent">
<Frame.Content>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Green" />
<Label Text="Green"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
</Frame.Content>
</Frame>
<Frame OutlineColor="Accent">
<Frame.Content>
<StackLayout Orientation="Horizontal">
<StackLayout.Children>
<BoxView Color="Blue" />
<Label Text="Blue"
VerticalOptions="Center" />
</StackLayout.Children>
</StackLayout>
</Frame.Content>
</Frame>
</StackLayout.Children>
</StackLayout>
</ContentPage.Content>
</ContentPage>

O arquivo code-behind contém apenas uma chamada para InitializeComponent.


Além da marcação repetitiva, este programa tem um problema mais prático: Quando ele roda em iOS, o primeiro item sobrepõe a barra de
status. Este problema pode ser corrigido com uma chamada para Device.OnPlatform no construtor da página (como você viu no Capítulo
2). Porque Device.OnPlatform define o preenchimento de propriedade na página e não exige nada no arquivo XAML, que poderia ir antes
ou depois da chamada do InitializeComponent.
Aqui está uma maneira de fazê-lo:
public partial class ScaryColorListPage : ContentPage
{
public ScaryColorListPage()
{
Padding = Device.OnPlatform(new Thickness(0, 20, 0, 0),
new Thickness(0),
new Thickness(0));

InitializeComponent();
}
}

Ou, você pode definir um valor de preenchimento uniforme para todas as três plataformas à direita no elemento raiz do arquivo XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScaryColorList.ScaryColorListPage" Padding="0, 20, 0, 0">
<ContentPage.Content>

</ContentPage.Content>
</ContentPage>

Que define a propriedade Padding para a página. A classe ThicknessTypeConverter requer que os valores sejam separados por
vírgulas, mas você tem a mesma flexibilidade que com o construtor Thickness. Você pode especificar quatro valores na ordem esquerda,
superior, direita e inferior; dois valores (o primeiro para esquerda e direita, e o segundo para cima e em baixo); ou um valor.
No entanto, você também pode especificar valores específicos da plataforma direito no arquivo XAML usando a classe OnPlatform, cujo
nome sugere que é semelhante a função estática do método Device.OnPlatform.
OnPlatform é uma classe muito interessante, e vale a pena ter uma noção de como ele funciona. a classe é genérica, e tem três propriedades
do tipo T, bem como uma conversão implícita de T faz uso do valor Device.OS:

public class OnPlatform<T>


{
public T iOS { get; set; } public T
Android { get; set; } public T WinPhone
{ get; set; }
public static implicit operator T(OnPlatform<T> onPlatform)
{
// returns one of the three properties based on Device.OS
}
}

Em teoria, você pode usar a classe OnPlatform<T> assim como o construtor de um derivado ContentPage:

Padding = new OnPlatform<Thickness>


{
iOS = new Thickness(0, 20, 0, 0), Android
= new Thickness(0), WinPhone = new
Thickness(0)
};

Você pode configurar uma instância dessa classe diretamente para a propriedade Padding porque a classe OnPlatform define uma
conversão implícita de si para o argumento genérico (neste caso Thickness).
No entanto, você não deve usar OnPlatform no código. Use Device.OnPlatform que é projetado para XAML, e a única parte realmente
difícil é descobrir como especificar o argumento de tipo genérico.
Felizmente, a especificação XAML 2009 inclui um atributo projetado especificamente para classes genéricas, chamados TypeArguments.
Porque é parte da própria XAML, ele é usado com um prefixo de x, então ele aparece como x:TypeArguments. Veja como OnPlatform
é usado para selecionar entre três valores de Thickness:
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0"
Android="0"
WinPhone="0" />

Neste exemplo (e no exemplo de código anterior), as configurações do Android e do WinPhone não são necessárias porque elas são os
padrões. Note que as cadeias de Thickness podem ser definidas diretamente na propriedades porque essas propriedades são do tipo de
Thickness, e, consequentemente, o analisador XAML usará o ThicknessTypeConverter para converte-las.
Agora que temos a marcação OnPlatform, como podemos defini-lo para a propriedade de Padding da Página? Ao expressar Padding usando
a sintaxe do property-element, é claro!

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScaryColorList.ScaryColorListPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<ContentPage.Content>

</ContentPage.Content>
</ContentPage>
Esta é a forma como o programa ScaryColorList aparece na coleção de amostras deste livro e aqui como fica:
G07xx02: Três screenshots lado a lado, mostrando uma lista curta de cor XAML no iPhone, Android, e Windows Phone.

Semelhante a OnDevice, OnIdiom distingue entre telefone e tablet. Por razões que se tornarão aparente no próximo capítulo, você deve
tentar restringir o uso de OnDevice e OnIdiom para pequenos pedaços de marcação em vez de grandes blocos. Ele não deve se tornar um
elemento estrutural em seus arquivos XAML.

O atributo de propriedade de conteúdo


O arquivo XAML no programa ScaryColorList é realmente um pouco mais do que ele precisa ser. você pode eliminar as labels
ContentPage.Content, todas as tags StackLayout.Children, e todas as tag Frame.Content, e o programa irá funcionar da mesma
forma:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScaryColorList.ScaryColorListPage">

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>

<StackLayout>
<Frame OutlineColor="Accent">
<StackLayout Orientation="Horizontal">
<BoxView Color="Red" />
<Label Text="Red"
VerticalOptions="Center" />
</StackLayout>
</Frame>

<Frame OutlineColor="Accent">
<StackLayout Orientation="Horizontal">
<BoxView Color="Green" />
<Label Text="Green"
VerticalOptions="Center" />
</StackLayout>
</Frame>

<Frame OutlineColor="Accent">
<StackLayout Orientation="Horizontal">
<BoxView Color="Blue" />
<Label Text="Blue"
VerticalOptions="Center" />
</StackLayout>
</Frame>
</StackLayout>
</ContentPage>

Parece muito mais limpo agora. O único elemento de propriedade esquerda é para a propriedade Padding de ContentPage.
Tal como acontece com quase toda sintaxe XAML, esta eliminação de alguns elementos de propriedade é apoiada pelas classes subjacentes.
Em cada classe usado em XAML é permitido definir uma propriedade como uma propriedade con- tent (às vezes também chamado de
propriedade padrão da classe). Para esta propriedade de conteúdo, as tags do elemento não são necessárias, e qualquer conteúdo XML
dentro das tags de início e fim é automaticamente atribuído a esta propriedade. Muito convenientemente, a propriedade do conteúdo é
Content ContentPage, a propriedade de conteúdo de StackLayout é Children, e a propriedade de conteúdo de Frame é content.
Estas propriedades de conteúdo são documentadas, mas você precisa saber para onde olhar. Uma classe específica é propriedade de
conteúdo usando o ContentPropertyAttribute. Se esse atributo está ligado a uma classe, ele aparece na documentação on-line
Xamarin.Forms API, juntamente com a declaração de classe. É assim que aparece na documentação para ContentPage:

[Xamarin.Forms.ContentProperty("Content")] public
class ContentPage : Page
Se ler em voz alta, soa um pouco redundante: A propriedade Content é a propriedade de conteúdo de ContentPage.
A declaração para a classe Frame é semelhante:
[Xamarin.Forms.ContentProperty("Content")] public
class Frame : ContentView
StackLayout não tem um atributo ContentProperty aplicado, mas StackLayout deriva de Layout<View>, e Layout<T> tem um atributo
ContentProperty:
[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout, IViewContainer<T>
where T : Xamarin.Forms.View

O atributo ContentProperty é herdado pelas classes que derivam de Layout <T>, então Children é a propriedade de
conteúdo de StackLayout.
Certamente, não há nenhum problema se você incluir os elementos de propriedade quando não são necessários, mas na maioria dos
casos, eles não serão mais exibidos nos programas de exemplo deste livro.

Formatação do Texto
O texto apresentado por um arquivo XAML pode envolver apenas uma ou duas palavras, mas às vezes é necessário um parágrafo inteiro,
talvez com alguma formatação de caracteres incorporado. Isso nem sempre é tão óbvio, ou tão fácil, em XAML como pode ser sugerido por
nossa familiaridade com HTML.
A solução TextVariations tem um arquivo XAML que contém sete Label view para StackLayout:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextVariations.TextVariationsPage">

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>

<ScrollView>
<StackLayout>

</StackLayout>
</ScrollView>
</ContentPage>

Cada um dos sete Label view mostra uma maneira um pouco diferente de definição do texto exibido. Para fins de referência, aqui está o
programa em execução em todas as três plataformas:
A abordagem mais simples envolve apenas a criação algumas palavras para o atributo de texto do elemento Label:

<Label VerticalOptions="CenterAndExpand" Text="Single lines of text are easy." />

Você também pode definir a propriedade de texto, quebrando a como um elemento de propriedade:
<Label VerticalOptions="CenterAndExpand">
<Label.Text>
Text can also be content of the Text property.
</Label.Text>
</Label>

Text é a propriedade de conteúdo da Label, de modo que você não precisa das tags Label.Text:

<Label VerticalOptions="CenterAndExpand"> Text is the content property of Label.


</Label>

Quando você definir o texto como o conteúdo da Label(se você usar as tags Label.Text ou não), o texto é cortado: todos os espaços,
incluindo tabulações, é removido do início e no final do texto. No entanto, todos os espaços incorporado é retido, incluindo caracteres de fim
de linha.
Quando você definir a propriedade Text como um atributo de propriedade, todos os espaços dentro das aspas é mantido, mas os caracteres
de fim de linha são convertidos em espaços.
Resultando em todo um parágrafo de texto formatado de maneira uniforme é um pouco problemático. Você pode colocar todo este parágrafo
como uma única linha no arquivo XAML, mas se você preferir usar várias linhas, você deve justificar todo o parágrafo no arquivo XAML entre
aspas, da seguinte forma:
<Label VerticalOptions="CenterAndExpand" Text=
"Perhaps the best way to define a paragraph of
uniformly formatted text is by setting the Text
property as an attribute and left justifying the
block of text in the XAML file. End-of-line
characters are converted to a space character." />

Os caracteres de fim de linha são convertidos em caracteres de espaço para que as linhas individuais sejam devidamente concatenadas. Mas
cuidado: Não deixe quaisquer caracteres perdidos no final ou início das linhas individuais. Eles irão aparecer como caracteres estranhos dentro
do parágrafo.
Quando várias linhas de texto são especificadas como conteúdo do Label, somente espaços em branco no início e no final do texto é aparado.
Todos os espaços em branco incorporados são retido, incluindo caracteres de fim de linha:
<Label VerticalOptions="CenterAndExpand"> Text as content has the curse
Of breaks at each line's close. That's a format great for verse But not the best for prose.
</Label>

Este texto é processado como quatro linhas separadas. Se você está exibindo listas ou poesia em seu aplicativo Xamarin.Forms, isso é
exatamente o que você quer. Caso contrário, provavelmente não.
Se a sua linha ou parágrafo do texto requer alguma formatação de parágrafos não uniforme, você vai querer usar a propriedade
FormattedText de Label. Como você pode lembrar, em um atribuido FormattedString defina múltiplos objetos Span para a coleção
Spans do FormattedString. Em XAML, você precisa de tags de property-element para Label.FormattedString, mas Spans é a
propriedade de conteúdo de FormattedString:
<Label VerticalOptions="CenterAndExpand">
<Label.FormattedText>
<FormattedString>
<Span Text="A single line with " />
<Span Text="bold" FontAttributes="Bold" />
<Span Text=" and " />
<Span Text="italic" FontAttributes="Italic" />
<Span Text=" and " />
<Span Text="large" FontSize="Large" />
<Span Text=" text." />
</FormattedString>
</Label.FormattedText>
</Label>

Observe que as propriedades de Text não formatadas, tem espaços no início ou no fim da cadeia de texto, ou ambos, para que os
itens não concatenem ao outro.
No caso geral, no entanto, você pode estar trabalhando com um parágrafo inteiro. Você pode definir o atributo de Text de Span para
uma longa linha, ou você pode envolvê-lo em várias linhas. Tal como acontece com Label, mantenha o bloco inteiro justificado à
esquerda no arquivo XAML:
<Label VerticalOptions="CenterAndExpand">
<Label.FormattedText>
<FormattedString>
<Span Text=
"A paragraph of formatted text requires left justifying it within the XAML file. But the text
can include multiple kinds of character formatting, including " />
<Span Text="bold" FontAttributes="Bold" />
<Span Text=" and " />
<Span Text="italic" FontAttributes="Italic" />
<Span Text=" and " />
<Span Text="large" FontSize="Large" />
<Span Text=
" and whatever combinations you might desire to adorn your glorious prose." />
</FormattedString>
</Label.FormattedText>
</Label>

Você vai notar no screen shot que o texto com o tamanho da fonte grande está alinhado com o texto normal na linha de base, que é a
abordagem adequada tipograficamente, e o espaçamento entre linhas é ajustado para acomodar o texto maior.
Na maioria dos programas Xamarin.Forms, nem XAML nem código existem isoladamente, mas trabalham em conjunto. Elementos em XAML
pode desencadear eventos tratados no código, e código podem modificar elementos em XAML. No próximo capítulo, você vai ver como isso
funciona.
Capítulo 8 - Código e XAML em harmonia
O arquivo de código e o arquivo XAML sempre existem em pares. Ambos os arquivos se complementam. Apesar de ser referido como "code-
behind" para o arquivo de código de um arquivo XAML, muitas vezes o código é proeminente em assumir as partes mais ativas e interativas
da aplicação. Isso implica que o code-behind deve ser capaz de referenciar os elementos definidos em XAML com objetos facilmente
instanciados no código. Da mesma forma, os elementos do XAML devem ser capaz de ativar os eventos que possam ser manipulados pelo
arquivo código. Isso é oque veremos neste capítulo.
Mas primeiro, vamos explorar algumas técnicas incomuns para instanciar objetos em um arquivo XAML.

Passando Argumentos
Como você viu, o parser XAML instancia elementos, chamando o construtor sem parâmetro da classe ou estrutura correspondente e, em
seguida, inicializa o objeto resultante definindo as propriedades sobre os valores de atributos. Isso parece razoável, no entanto, os
desenvolvedores que usam XAML, por vezes, têm a necessidade de reinstanciar objetos com construtores que exigem argumentos ou chamar
o método de criação estática. Estas necessidades geralmente não envolvem a própria API, mas em vez disso envolvem classes de dados
externas referenciadas pelo arquivo XAML que interage com a API.
Em 2009 a especificação XAML introduziu o elemento x:Arguments e atributo x:FactoryMethod para estes casos, e Xamarim.Forms os
suporta. Estas técnicas não são frequentemente utilizadas em circunstancias ordinárias, mas você deve ver como elas funcionam em caso de
necessidade.

Construtor com argumentos


Para passar argumentos para um construtor de um elemento em XAML, o elemento deve ser separado em tags de início e fim. Inicialize as
tag de início e fim do elemento com x:Arguments. Dentro das tags x:Arguments inclua um ou mais argumentos do construtor.
Mas como especificar vários argumentos dos tipos comuns, como double ou int? Você separaria os argumentos com vírgulas?
Não. Cada argumento deve ser delimitado com tags de início e fim. Felizmente, a especificação XAML 2009 define elementos XML
para tipos básicos comuns. Você pode usar essas tags para esclarecer os tipos de elementos, para especificar os tipos genéricos em
OnPlatform, ou para delimitar argumentos do construtor. Confira o conjunto completo suportado por Xamarin.Forms. Observe que foram
duplicados os nomes de tipos .NET ao invés dos nomes de tipos C#:
● 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
● x:DateTime (Suportado pelo Xamarin.Forms mas não por especificação do XAML 2009)

Você será pressionado para encontrar uso para todos eles, mas provavelmente vai descobrir usos para apenas alguns deles.
O ParameteredConstructorDemo exemplo, a seguir, demonstra o uso de x:Arguments com argumentos delimitados por tags x:Double
usando 3 diferentes construtores da estrutura Color. O construtor requer 3 parâmetros vermelho, verde e azul com os valores que variam de
0 a 1. Construtor com 4 parâmetros (que é definido aqui como 0,5), e o construtor com parâmetro único indica uma sombra de 0 (preto) a 1
(branco):
O número de elementos dentro de tags x:Arguments, e os tipos destes elementos, deve corresponder a um dos construtores da classe ou
estrutura. Aqui está o resultado:

O BoxView azul é mais claro no fundo branco e escuro no fundo preto porque a transparência dele é de 50% deixando transparecer o fundo
da tela.
Posso chamar um método por XAML?
Anteriormente, a resposta a esta pergunta era "Não seja ridículo", mas agora podemos qualifica-la "Sim". No entanto, não fique muito animado.
Os únicos métodos que você pode chamar em XAML são aqueles que retornam objetos (ou valores) do mesmo tipo que a classe (ou estrutura)
que define o método. Esses métodos devem ser públicos e estáticos. Eles são chamados às vezes creation ou factory methods. Você pode
instanciar um elemento em XAML através de uma chamada para um desses métodos, especificando o nome do método que utiliza o atributo
x:FactoryMethod e seus argumentos, usando o elemento x:Arguments.
A estrutura Color define sete métodos estáticos que retornam valores de Color. Este arquivo XAML faz uso de três deles:

Os dois primeiros métodos estáticos chamados são ambos chamados Color.FromRgb, porém os tipos de elementos dentro da tag
x:Arguments distingui entre argumentos int que variam de 0 a 255 e argumentos double que variam de 0 a 1. O terceiro é o método
Color.FromHsla, que cria um valor de cor a partir de componentes matiz, saturação, luminosidade, e componentes alphas. Curiosamente,
esta é a única maneira de definir um valor de Color a partir de valores de HSL em um arquivo XAML usando a API Xamarin.Forms. Aqui está
o resultado:
O atributo x:Name
Na maioria das aplicações reais, o arquivo code-behind precisa referenciar elementos definidos no arquivo XAML. Você viu uma maneira de
fazer isso no programa CodePlusXaml no capítulo anterior: Se o arquivo de code-behind tem conhecimento do layout da árvore visual
definida no arquivo XAML, ele pode começar a partir do elemento raiz (a própria página) e localizar elementos específicos dentro da árvore.
Este processo é chamado de "walking the tree" e pode ser útil para localizar determinados elementos em uma página.
Geralmente, a melhor abordagem é dar aos elementos no arquivo XAML um nome semelhante a um nome de variável. Para isso você faz
usa de um atributo que é intrínseco ao XAML, chamado Name. Como o prefixo x é quase universal e usado para atributos intrínsecos ao
XAML, esse atributo Name é comumente referido como x:Name.
O projeto XamlClock demonstra o uso de x:Nome. Segue o arquivo XamlClockPage.xaml contendo 2 controles Label, chamado timeLabel
e dateLabel.

As regras para x:Name são as mesmas que para os nomes de variáveis em C#. (Você descobrirá o porquê em breve.) O nome deve começar
com uma letra ou sublinhado e conter apenas letras, sublinhados e números.
Similarmente ao programa de relógio no Capítulo 5, XamlClock usa Device.StartTimer para disparar um evento periódico para atualizar a
hora e a data. Aqui está o arquivo code-behind de XamlClockPage:
Este método de retorno de chamada do timer é chamado uma vez por segundo. O método deve retornar true para continuar o timer. Se ela
retorna false, o temporizador para e deve ser reiniciado com outra chamada para Device.Start-Timer.
O método de retorno referência o timeLabel e dateLabel como se fossem variáveis normais e define as propriedades de texto de cada um:

Este pode não ser um relógio visualmente impressionante, mas é, definitivamente, funcional.
Como é que o arquivo code-behind pode referenciar os elementos identificados com x:Name? Mágica? Claro que não. O mecanismo é muito
evidente quando você examinar o arquivo XamlClockPage.xaml.g.cs que o parser XAML gera a partir do arquivo XAML enquanto o projeto
está a sendo construído:
Durante o tempo de compilação, enquanto o parser XAML revira o arquivo XAML, cada atributo x:Name torna-se um campo privado neste
arquivo de código gerado. Isso permite que o código no arquivo code-behind referencie esses nomes como se fossem campos normais - que
eles definitivamente são. No entanto, os campos são inicialmente null. Somente quando InitializeComponent é chamado, em tempo de
execução, os dois campos são definidos através do método FindByName, que é definido na classe NameScopeExtensions. Se o construtor
de seu arquivo code-behind tentar fazer referência a estes dois campos antes da chamada InitializeComponent, eles terão valores null.
Este arquivo de código gerado também implica outra regra para valores x:Name que agora é muito óbvio, mas raramente estabelecido
explicitamente: os nomes não podem duplicar campos ou propriedade de nomes definidas no arquivo code-behind.
Uma vez que estes são campos privados, eles só podem ser acessados a partir do arquivo code-behind e não de outras classes. Se um
derivado de ContentPage precisa expor campos públicos ou propriedades para outras classes, você deve definir neles mesmo.
Obviamente, valores de x:Name deve ser único dentro de uma página XAML. Isto às vezes, pode ser um problema se você estiver usando
OnPlatform para elementos específicos da plataforma no arquivo XAML. Por exemplo, aqui está um arquivo XAML que expressa as
propriedades iOS, Android, e WinPhone de OnPlatform como elementos de propriedade para selecionar um dos três modos de exibição do
Label:
O código XAML a seguir não funciona!

Isso não vai funcionar porque vários elementos não podem ter o mesmo nome.
Você poderia dar-lhes nomes diferentes e lidar com os três nomes no arquivo code-behind usando Device.OnPlatform, mas uma solução
melhor é manter as especificidades de plataforma tão pequenas quanto possível. Segue a versão do programa PlatformSpecificLabels que
está incluído com o código de exemplo para este capítulo. Ele tem uma única etiqueta, e tudo é independente de plataforma, exceto para a
propriedade Text:
E o resultado:

A propriedade de Text é a propriedade de conteúdo do Label, de modo que você não precisa da tag Label.Text como no exemplo
anterior. Isso funciona bem:

Visualizações personalizadas baseadas em XAML


O programa ScaryColorList no capítulo anterior listou algumas cores em um StackLayout usando Frame, BoxView, e Label. Mesmo com
apenas três cores, a marcação repetitiva estava começando a parecer muito ameaçador. Infelizmente não há nenhuma marcação XAML que
duplique os loops for e while do C#, então a sua escolha é usar o código para gerar vários itens semelhantes, ou encontrar a melhor maneira
de fazê-lo na marcação.
Neste livro, você vai verá várias maneiras de listar cores em XAML, e, eventualmente, uma maneira muita limpa e elegante. Fazer este trabalho
se tornará claro. Mas isso requer mais alguns passos no aprendizado em Xamarin.Forms. Até então, nós estaremos olhando algumas outras
abordagens que podem ser úteis em circunstâncias semelhantes.
Uma estratégia é criar uma view personalizada que tem o único propósito de exibir uma cor com um nome e uma caixa colorida. E enquanto
estamos nisso, vamos exibir, também, os valores RGB hexadecimais das cores. Você pode então usar essa view personalizada em um arquivo
de página XAML para as cores individuais.
Como isso vai ficar em XAML?
Ou uma melhor pergunta seria: Como você gostaria que isso parecesse?
Se a marcação for semelhante com isto, a repetição não é ruim para todos, e não muito pior do que definir explicitamente valor do vetor de
Color em código:

Bem, na verdade ele não vai ficar exatamente desta assim. MyColorView é, obviamente, uma classe personalizada e não faz parte da API
Xamarin.Forms. Portanto, ele não pode aparecer no arquivo XAML sem um prefixo de namespace que é definido em uma declaração de
namespace XML.
Com esse prefixo XML aplicado, não haverá qualquer confusão sobre esta visão customizada ser parte da API Xamarin.Forms. Então vamos
dar um nome mais digno de ColorView ao invés de MyColorView.
Esta classe ColorView hipotética é um exemplo de uma visão personalizada simples porque consiste unicamente d views existentes,
especificamente Label, Frame, e BoxView - organizadas de uma maneira particular usando StackLayout. Xamarin.Forms define uma view
projetada especificamente para a finalidade de herdar um conjunto de views, chamado de ContentView. Como em ContentPage,
ContentView tem uma propriedade de conteúdo que você pode definir para uma árvore de outras views. Você pode definir o conteúdo do
ContentView no código, mas é mais divertido fazê-lo em XAML.
Vamos colocar uma solução chamado ColorViewList. Esta solução terá dois conjuntos de XAML e arquivos code-behind, o primeiro é uma
classe denominada ColorViewListPage, que deriva de ContentPage (como de costume), e o segundo é uma classe chamada ColorView,
que deriva de ContentView.
Para criar a classe ColorView no Visual Studio, utilize o mesmo procedimento de adicionar uma nova página XAML para o projeto
ColorViewList: Com o botão direito do mouse no nome do projeto no Solution Explorer selecione Add > New Item no menu de contexto.
No diálogo Add Item, selecione Visual C# > Code à esquerda e Forms Xaml Page. Digite o nome do ColorView.cs. Mas de imediato, antes
de esquecer, vá para o arquivo ColorView.xaml e mude as tags de início e fim do ContentPage para ContentView. No arquivo
ColorView.xaml.cs, altere a classe base para ContentView.
O processo é um pouco mais fácil no Xamarin Studio. A partir do menu de ferramentas do projeto ColorViewList, selecione Add > New
File. Na caixa de diálogo New File, selecione Forms à esquerda e Forms ContentView Xaml (não o Forms ContentPage XAML). Dê-lhe
o nome de ColorView.
Você também vai precisar criar o arquivo XAML e arquivo de code-behind e para a classe ColorViewListPage, como de costume.
O arquivo ColorView.xaml descreve a disposição dos itens de cores individuais, mas sem quaisquer valores de cores reais. Em vez disso, o
BoxView e dois modos de exibição da Label são nomes dados:

Em um programa da vida real, você terá tempo mais tarde para ajustar os visuais. Inicialmente, você só quer pegar todas as views nomeadas
lá.
Além dos recursos visuais, esta classe ColorView vai precisar de uma nova propriedade para definir a cor. Esta propriedade deve ser definida
no arquivo code-behind. A princípio, parece razoável dar a ColorView uma propriedade chamada Color do tipo Color (como o trecho XAML
anterior com MyColorView parece sugerir). Mas se essa propriedade foi do tipo Color, como é que o código obtém o nome da cor do que o
valor de Color? Não pode.
Em vez disso, faz mais sentido definir uma propriedade chamada Colorname do tipo string. O arquivo code-behind pode então usar a reflexão
para obter o campo estático da classe Color correspondente a esse nome.
Mas espere: Xamarin.Forms inclui uma classe pública ColorTypeConverter que o analisador XAML usa para converter um nome de cor de
texto como o "Red" ou "Blue" em um valor Color. Por que não aproveitar isso?
Aqui está o arquivo code-behind para ColorView. Ele define uma propriedade ColorName com um método de acesso set que define a
propriedade de texto do colorNameLabel do nome da cor, e então usa ColorTypeConverter para converter o nome para um valor Color.
Este valor Color é então utilizado para definir a propriedade Color de boxView e a propriedade de texto do colorValueLabel para os valores
RGB:
A classe ColorView está terminada. Agora vamos olhar para ColorViewListPage. O arquivo ColorViewListPage.xaml deve listar várias
instâncias de ColorView, por isso precisa de um novo namespace XML com um novo prefixo namespace para referenciar o elemento
ColorView.
A classe ColorView faz parte do mesmo projeto que ColorViewListPage. Geralmente, os programadores usam um prefixo namespace XML
de um local para estes casos. A nova declaração de namespace aparece no elemento raiz do arquivo XAML (como os outros dois) com o
seguinte formato:

No caso geral, uma declaração de namespace XML personalizado para XAML deve especificar um namespace e Common Language Runtime
(CLR) - também conhecido como o namespace .NET - e um assembly. As palavras-chave para especificá-los são clr-namespace e assembly.
Muitas vezes, o namespace CLR é o mesmo que a assembly, como eles são neste caso, mas eles não precisam ser necessariamente. As
duas partes estão conectadas por um ponto e vírgula.
Observe que um dois pontos segue clr-namespace, mas um sinal de igual segue assembly. Esta aparente incoerência é deliberada: o formato
da declaração de namespace se destina a imitar um URI encontrado no namespace de declarações convencionais, nas quais dois pontos
seguem o nome do esquema de URI.
Você usa a mesma sintaxe para fazer referência a objetos em bibliotecas de classes portáteis externas. A única diferença nesses casos é que
o projeto também precisa de uma referência a esse PCL externo. (Você verá um exemplo no capítulo 10, "extensões de marcação XAML.").
O prefixo local é comum para o código no mesmo assembly, e, nesse caso, a parte de assembly não é necessária:

Você pode incluí-lo se você quiser, mas não é necessário. Aqui está o XAML para a classe ColorViewListPage . O arquivo code-behind
contém nada além da chamada InitializeComponent:
Esta não é tão tedioso como o exemplo anterior, e demonstra como você pode encapsular visuais em suas próprias classes XAML. Observe
que o StackLayout é o filho de um ScrollView, para que a lista pode ser rolada:

No entanto, há um aspecto do projeto ColorViewList que não se qualifica como uma "melhor prática". É a definição da propriedade
ColorName em ColorView. Isto realmente deve ser implementado como um objeto BindableProperty. Investigar objetos bindable e
propriedades bindable é uma das prioridades e será explorada no capítulo 11.

Eventos e Handlers
Quando você toca em um Buttom do Xamarin.Forms, ele dispara um evento Clicked. Você pode instanciar um botão em XAML, mas o
próprio manipulador de eventos Clicked deve residir no arquivo code-behind. O Button é apenas um de um grupo de views que existem
essencialmente para gerar eventos, de modo que o processo de tratamento eventos é crucial para coordenar arquivos XAML e códigos.
Anexar um manipulador de eventos para um evento em XAML é tão simples como definir uma propriedade; é, de fato, visivelmente
indistinguível de uma definição da propriedade. O projeto XamlKeypad é uma versão XAML do projeto PersistentKeypad do Capítulo 6. Ela
ilustra a configuração de event handlers em XAML e lidar com esses eventos no arquivo code-behind. Ele também inclui a lógica para salvar
entradas de teclado quando o programa é terminado.
Se você der uma olhada novamente no código do construtor das classes SimplestKeypadPage ou PersistentKeypadPage, você verá um
par de repetições para criar os botões que compõem a parte numérica do teclado. Claro, isso é precisamente o tipo de coisa que você não
pode fazer em XAML, mas dá para perceber o quanto mais limpa é a marcação no XamlKeypadPage quando comparado com o código:

O arquivo é muito mais curto do que teria sido se as três propriedades em cada botão numérico fossem formatado em três linhas, entretanto,
empacotar todas juntas faz a uniformidade da marcação muito mais óbvia e proporciona clareza em vez de obscuridade.
A grande questão é qual você preferiria manter e modificar: o código no SimplestKeypadPage ou construtores PersistentKeypadPage ou a
marcação no arquivo XamlKeypadPage XAML?
Aqui está a captura de tela. Você vai ver que essas chaves estão agora organizadas em ordem da calculadora em vez de ordem de telefone:

O botão Backspace tem seu evento Clicked definido como o manipulador OnBackspaceButtonClicked, enquanto os botões numéricos
compartilham o manipulador OnDigitButtonClicked. Como você deve se lembrar, a propriedade StyleId é muitas vezes usado para
distinguir views que compartilham o mesmo manipulador de evento, o que significa que os dois manipuladores de eventos podem ser
implementados no arquivo code-behind exatamente como os programas só de código:

Parte do trabalho do método LoadFromXaml chamado por InitializeComponent envolve anexar esses event handlers para os objetos
instanciados a partir do arquivo XAML.
O projeto XamlKeypad inclui o código que foi adicionado à página e as classes de aplicativos no PersistentKeypad para salvar o texto
teclado quando o programa é encerrado. A classe App em XamlKeypad é basicamente a mesma que aquela em PersistentKeypad .

Gestos de Toque
O Buttom em Xamarin.Forms responde aos toques dos dedos, mas você pode pegar gesto de toque de qualquer classe que derivado de View,
incluindo Label, BoxView, e Frame. Estes eventos de toque não são construídos para a classe View, mas a classe View define uma
propriedade chamada GestureRecognizers. Toques são habilitados adicionando um objeto na coleção GestureRecognizers. Uma instância
de qualquer classe que deriva de GestureRecognizer podem ser adicionados a esta coleção, mas até agora só há uma:
TapGestureRecognizer.
Veja como adicionar um TapGestureRecognizer a um BoxView em código:

TapGestureRecognizer também define uma propriedade NumberOfTapsRequired com um valor padrão de 1.


Para gerar eventos aproveitado, o objeto View deve ter sua propriedade IsEnabled definida como true, a propriedade IsVisible definido
como true (ou não será visível), e sua propriedade InputTransparent definida como false. Estas são todas as condições padrão.
O manipulador Tapped é semelhante com handler Clicked de Buttom:

Normalmente, o argumento sender de um manipulador de eventos é o objeto que ativa o evento, neste caso seria o objeto
TapGestureRecognizer. Entretanto, isso não seria de muita utilidade. Em vez disso, o argumento de sender para o manipulador Tapped é
a view a ser aproveitado, neste caso, o BoxView. Isso é muito mais útil!
Como o Buttom, TapGestureRecognizer também define propriedades de Command e CommandParameter; estes são usados ao implementar
o padrão de design MVVM, e eles são discutidos em próximos capítulos.
TapGestureRecognizer também define propriedades nomeadas TappedCallback e TappedCallbackParameter e um construtor que inclui
um argumento TappedCallback. Estes são todos obsoletos e não devem ser utilizadas.
Em XAML, você pode anexar um TapGestureRecognizer a uma visão expressando a coleção GestureRecognizers como um elemento de
propriedade:

Como de costume, o XAML é um pouco mais curto do que o código equivalente.


Vamos fazer um programa que é inspirado em um dos primeiros jogos de computador autônomo.
A versão Xamarin.Forms deste jogo é chamado MonkeyTap porque é um jogo de imitação. Ele contém quatro elementos BoxView, coloridos
em vermelho, azul, amarelo, verde. Quando o jogo começa, um dos elementos BoxView brilha, e você deve, em seguida, tocar naquele
BoxView. BoxView pisca novamente seguido por outro, e agora você deve tocar em sequência. Em seguida, os dois flashes são seguidas por
uma terceira e assim por diante. É um jogo bastante cruel porque não há nenhuma maneira de vencer. O jogo está cada vez mais difícil e mais
difícil até que você perde.
O arquivo MonkeyTapPage.xaml instancia os quatro elementos BoxView e um botão no centro rotulado de "Begin".
Todos os quatro elementos BoxView aqui tem uma TapGestureRecognizer anexado, mas eles ainda possuem cores assinaladas. Isso é
tratado no arquivo code-behind porque as cores não vão ficar constantes. As cores precisam ser alteradas para o efeito intermitente.
O arquivo code-behind começa com algumas constantes e campos variáveis. (Você notará que um deles é sinalizada como protected; no
próximo capítulo, a classe irá derivar dele e exigir o acesso a este campo alguns métodos são definidos como protected).
O construtor coloca todos os quatro elementos BoxView em um vetor; o que lhes permite ser referenciado por um índice simples que tem
valores de 0, 1, 2 e 3. O método InitializeBoxViewColors define todos os elementos BoxView ao seu estado sem brilho ligeiramente
escurecida.
O programa está agora espera que o usuário pressione o botão Begin para iniciar o primeiro jogo. O mesmo botão lida com replays, assim
que inclui uma inicialização redundante das cores BoxView. O handler de Buttom também se prepara para construir a sequência de elementos
BoxView piscavam desmarcando a lista sequence e chamando StartSequence:

StartSequence adiciona um novo número aleatório para a lista de sequence, inicializa sequenceIndex a 0, e inicia o timer.
No caso normal, o handler de marcação timer é chamado para cada índice na lista de sequence e faz com que o BoxView correspondente
pisque com uma chamada para FlashBoxView. O manipulador de cronômetro retorna false quando a sequência está no fim, indicando também
definindo awaitingTaps que é hora para o usuário a imitar a sequência:
O flash é apenas um quarto de segundo de duração. O método FlashBoxView define primeiro a luminosidade para uma cor brilhante e cria
um timer "one-shot", assim chamado porque o método temporizador de chamada de retorno (aqui expressado como uma função lambda)
retorna falso e desliga o temporizador após a restauração da luminosidade da cor.
O manipulador Tapped para os elementos BoxView ignora o toque se o jogo já está encerrado (o que só acontece com um erro do usuário),
e termina o jogo, se o usuário toca prematuramente sem esperar o programa para passar pela sequência. Caso contrário, ele apenas
compara o BoxView tocado com o próximo na sequência , Brilha o BoxView se estiver correta , ou termina o jogo, caso não:
Se o usuário consegue acertar a sequência de todo o caminho, outra chamada para StartSequence acrescenta um novo índice para a lista
de sequência e começa a tocar de novo. Eventualmente, porém, haverá uma chamada para EndGame, que colore todas as caixas cinzentas
para enfatizar o fim, e reativa o botão para dar uma chance de tentar novamente.
Aqui está o programa depois que o Buttom foi clicado e escondido:

Eu sei eu sei. O jogo é uma chatice sem som.


Vamos aproveitar a oportunidade no próximo capítulo para corrigir isso.
Capítulo 9
Chamadas de API específicas da plataforma
Uma emergência surgiu. Qualquer um que joga MonkeyTap do capítulo anterior vai chegar rapidamente à conclusão de que ele precisa
desesperadamente de uma melhoria muito simples, e ele simplesmente não pode viver sem ela.
MonkeyTap precisa de som .
Ele não precisa de sons sofisticados apenas pequenos sinais sonoros para acompanhar os flashes dos quatro elementos BoxView. Mas a API
Xamarin.Forms não suporta som, o som não é algo que podemos adicionar ao MonkeyTap com apenas um par de chamadas de API. Para
facilitar suporte a som no Xamarin.Forms é necessário utilizar uma plataforma especifica de cada plataforma. Descobrir como fazer sons em
IOS, Android e Windows Phone é bastante difícil. Mas como é que o programa Xamarin.Forms consegue fazer chamadas para as plataformas
individuais?
Antes de abordar as complexidades de som, vamos examinar as diferentes abordagens para fazer chamadas de API específicas da plataforma
com um exemplo muito mais simples: Os três primeiros programas curtos mostrado abaixo fazem a mesma coisa: todos eles exibirão dois
pequenos itens de informações fornecidas pela plataforma subjacente do sistema operacional que irá revelar o modelo do dispositivo
executando o programa e a versão do sistema operacional.

Pré-processamento no compartilhamento do Projeto


Como você aprendeu no Capítulo 2, "Anatomia de um aplicativo", você pode usar tanto o compartilhamento do projeto ou uma Biblioteca de
Classes Portátil para o código que é comum a todas as três plataformas. O compartilhamento do projeto contém arquivos de código que são
compartilhados entre os projetos da plataforma, enquanto as classes portáteis incluem o código comum em uma biblioteca que só é acessível
através de tipos públicos.
Acessar APIs da plataforma a partir de um projeto com recursos compartilhados é um pouco mais simples do que
a partir de uma Biblioteca de Classes Portátil, porque envolve ferramentas de programação mais tradicionais,
então vamos tentar essa abordagem em primeiro lugar. Você pode criar uma solução Xamarin.Forms com
compartilhamento do projeto, utilizando o processo descrito no capítulo 2. Você pode então adicionar uma classe
ContentPage baseada em XAML para o local compartilhado da mesma maneira que você adiciona a uma classe
portátil.
Aqui está o arquivo XAML para um projeto chamado DisplayPlatformInfoSap1:
O arquivo code-behind deve definir as propriedades de texto para modelLabel e versionLabel.
Arquivos de código compartilhado no projeto são extensões do código nas plataformas individuais. Isto significa que o código no
compartilhamento pode fazer uso do C # pré-processamento, diretivas #if, #elif, #else, e #endif com compilação condicional definidas para as
três plataformas, como demonstrado nos capítulos 2 e 4. Estes símbolos são __IOS__ para iOS, __ANDROID__ para o Android, e
WINDOWS_PHONE para Windows Phone.
As APIs envolvidas na obtenção de informações sobre o modelo e versão são, diferentes para as três plataformas:
• Para iOS, use a classe UIDevice no namespace UIKit.
• Para Android, use várias propriedades da classe Build no namespace Android.OS.
• Para Windows Phone, use a classe devicestatus no namespace Microsoft.Phone.Info e a classe Ambiente no namespace System.
Aqui está o DisplayPlatformInfoSap1.xaml.cs arquivo code-behind que mostram como modelLabel e versionLabel são definidos com base nos
símbolos de compilação condicional:

Observe que estas diretivas de preprocessor são usados para selecionar diferentes using diretivas, bem como para fazer chamadas para APIs
específicas da plataforma. Em um programa tão simples como este, você poderia simplesmente incluir os namespaces com os nomes de
classe, mas para longos blocos de código, você provavelmente vai querer aqueles que utilizam directivas.
E, claro, ele funciona:
A vantagem dessa abordagem é que você tem todo o código para as três plataformas em um único lugar. Mas a listagem de código que vamos
enfrentar será muito feia, e isso remonta a uma época muito anterior em programação. Usando diretivas de pré-processamento pode não
parecer tão ruim para chamadas curtas e menos freqüentes, como neste exemplo, mas em um programa maior que você vai precisar fazer
malabarismos em blocos de código específico da plataforma e de código compartilhado, e a multidão de pré-processamento diretivas pode
facilmente tornar-se confuso. Diretivas de pré-processador deve ser usado para pequenas correções e geralmente não como elementos
estruturais no aplicativo.
Vamos tentar outra abordagem.

As classes paralelas e o compartilhamento do projeto


Embora o compartilhamento do projeto seja uma extensão da plataforma, a relação vai nos dois sentidos: da mesma maneira que um projeto
da plataforma pode fazer chamadas em código em um projeto de recurso compartilhado, o compartilhamento do projeto pode fazer chamadas
para as plataformas individuais.
Isto significa que pode restringir as chamadas de API específicas da plataforma para classes nos projetos das plataformas individuais. Se os
nomes e namespaces dessas classes nos projetos da plataforma são os mesmos, então o código no compartilhamento do projeto pode
acessar essas classes de uma maneira independente e transparente.
Na solução DisplayPlatformInfoSap2, cada um dos três projetos de plataformas tem uma classe chamada PlatformInfo que contém dois
métodos que retornam strings nomeadas getModel e GetVersion. Está aqui a versão desta classe no projeto iOS:

Observe o nome do namespace. Embora as outras classes neste projeto iOS usar o namespace DisplayPlatformInfoSap2.iOS, o namespace
para esta classe é apenas DisplayPlatformInfoSap2. Isso permite que a compartilhamento do projeto consiga acessar essa classe diretamente,
sem qualquer plataforma específica.
Aqui está à classe paralela no projeto Android. Mesmos nomes de métodos mesmo namespace, mesmo nome da classe, mas diferentes
implementações destes métodos usando chamadas de API do Android:
E aqui está o Windows Phone:

O arquivo XAML no projeto DisplayPlatformInfoSap2 é basicamente o mesmo que o de projeto DisplayPlatformInfoSap1. O


arquivo code-behind é consideravelmente mais simples:
A versão específica do PlatformInfo que é referenciado pela classe é única no projeto compilado. É quase como se nós tivéssemos definido
uma pequena extensão para Xamarin.Forms que reside nos projetos de plataformas individuais.

DependencyService e Biblioteca de Classes Portátil.


A técnica ilustrada no programa DisplayPlatformInfoSap2, pode ser implementado em uma solução com uma biblioteca de classes portátil?
De primeira, ele não parece ser possível. Apesar de projetos de aplicativos fazer chamadas para bibliotecas o tempo todo, bibliotecas
geralmente não podem fazer chamadas para aplicações exceto no contexto de eventos ou funções de retorno de chamada. A classe portátil
é fornecida com uma versão do .NET independente de dispositivo, capaz apenas de executar o código entre si ou em outras classes portátil
referenciada.
Mas espere: Quando um aplicativo Xamarin.Forms está sendo executado, ele pode usar reflexão .NET para obter acesso à sua própria
composição e todas as outras composições no programa. Isto significa que o código no compartilhamento do projeto pode usar reflexão para
acessar as classes que existem no conjunto de plataforma a partir de qual classe portátil é referenciado. Essas classes devem ser definidas
como público e claro, mas isso é apenas a única exigência.
Antes de começar a escrever o código que explora essa técnica, você deve saber que esta solução já existe sob a forma de uma classe
Xamarin.Forms chamado DependencyService. Essa classe usa reflexão .NET para pesquisar todas os outros conjuntos na aplicação, incluindo
o conjunto de plataforma especial em si, e fornecer acesso ao código específico da plataforma.
O uso de DependencyService é ilustrado na solução DisplayPlatformInfo, que utiliza uma biblioteca de classes portátil para o código
compartilhado. Você começa o processo de utilização DependencyService através da definição de um tipo de interface no projeto de classe
portátil que declara as assinaturas dos métodos que você deseja implementar nos projetos de plataformas. Aqui está IPlatformInfo:

Você viu aqueles dois métodos antes. Eles são os mesmos dois métodos implementados nas classes PlatformInfo nos projetos de plataformas
em DisplayPlatformInfoSap2.
De um modo muito semelhante ao DisplayPlatformInfoSap2, todos os três projetos de plataformas em DisplayPlatformInfo deve agora ter uma
classe que implementa a interface IPlatformInfo. Aqui está a classe no projeto iOS, chamado PlatformInfo:
Esta classe não é acessada diretamente pela classe portátil, por isso o nome do namespace pode ser um nome qualquer. Aqui ele está
definido para o mesmo namespace como o outro código no projeto iOS. O nome da classe também pode ser um nome qualquer. Seja qual for
o nome dela, no entanto, a classe deve explicitamente implementar a interface IPlatformInfo definido na classe portátil:

Além disso, esta classe deve ser referenciada em um atributo especial fora do bloco de namespace. Você vai vê-lo perto do topo do arquivo
seguindo as diretivas using:

A classe DependencyAttribute que define este atributo de dependência é parte de Xamarin.Forms e utilizado especificamente em conexão
com DependencyService. O argumento é um objeto Type de uma classe no projeto da plataforma que está disponível para acesso nas classes
portáteis. Neste caso, é esta classe PlatformInfo. Esse atributo está ligado ao próprio conjunto de plataforma, de modo que o código em
execução nas classes portáteis não tem que procurar em toda a biblioteca para encontrá-lo.
Aqui está a versão Android do PlatformInfo:
E aqui está a um para
Windows Phone:

Código do PCL pode então ter acesso a implementação da plataforma privada de IPlatformInfo usando a classe DependencyService. Esta é
uma classe estática com três métodos públicos, o mais importante dos quais é nomeado Get. Get é um método genérico cujo argumento é a
interface que você definiu, neste caso IPlatformInfo.

O método Get retorna uma instância da classe específico da plataforma que implementa a interface IPlatformInfo. Você pode usar esse objeto
para fazer chamadas específicas da plataforma. Isso é demonstrado no arquivo code-behind para o projeto DisplayPlatformInfo:
DependencyService armazena em cache as instâncias dos objetos que ele obtém através do método GET. Isso acelera usos subsequentes
Get e também permite que as implementações da plataforma da interface para manter o estado: os campos e propriedades nas
implementações da plataforma serão preservadas através de múltiplas chamadas GET. Essas classes também podem incluir eventos ou
implementar métodos de retorno de chamada.
DependencyService requer apenas um pouco mais de sobrecarga do que a abordagem mostrada no projeto DisplayPlatformInfoSap2 e é um
pouco mais estruturado porque as classes de plataformas individuais implementam uma interface definida no código compartilhado.
DependencyService não é a única maneira de implementar chamadas específicas da plataforma em uma classe portátil. Desenvolvedores
aventureiros podem querer usar técnicas dependencyinjection para configurar as classes portáteis para fazer chamadas em outras
plataformas. Mas DependencyService é muito fácil de usar, e elimina a maioria das razões para usar um projeto de recurso compartilhado em
um aplicativo Xamarin.Forms.

Plataforma especifica de renderização de som.


Agora, vamos para o verdadeiro objetivo deste capítulo: dar som para MonkeyTap. Todas as três plataformas de suporte a APIs permitem que
um programa possa gerar e jogar ondas sonoras dinamicamente. Esta é a abordagem adotada pelo programa MonkeyTapWithSound.
Ficheiros de música comerciais são frequentemente comprimidas em formatos como MP3. Mas quando um programa algoritmicamente gera
formas de onda, um formato não comprimido é muito mais conveniente. A técnica mais básica é suportada por todas as três plataformas e é
chamado de modulação de código de pulso ou PCM. Apesar do nome fantasia, é bastante simples, e é a técnica utilizada para armazenamento
de som em CDs de música.
Uma forma de onda PCM é descrito por uma série de amostras a uma taxa constante, conhecida como a frequência de amostragem. CDs de
música usam uma taxa normal de 44.100 amostras por segundo. Os arquivos de áudio gerados por programas de computador, muitas vezes
usam uma taxa de amostragem de metade desses (22.050) ou um quarto (11.025) quando não se é necessária alta qualidade de áudio. A
frequência mais elevada que pode ser gravado e reproduzido é metade da taxa de amostragem.
Cada amostra é um tamanho fixo que define a amplitude da forma de onda naquele ponto no tempo. As amostras de um CD de música estão
inscritas em valores de 16 bits. Amostras de 8 bits são comuns quando a qualidade do som não importa tanto. Alguns ambientes apoiam
valores de ponto flutuante. Várias amostras podem ser estéreos ou qualquer número de canais. Para efeitos de som simples em dispositivos
móveis, som mono geralmente é bom.
O algoritmo de geração de som em MonkeyTapWithSound é codificado para “mono” amostras de 16 bits, mas a taxa de amostragem é definido
por uma constante e pode ser facilmente alterada.
Agora que você sabe como DependencyService funciona, vamos examinar o código adicionado ao MonkeyTap para transformá-lo em
MonkeyTapWithSound, e vamos olhar para ele de cima para baixo. Para evitar reproduzir um monte de código, o novo projeto contém links
para os arquivos MonkeyTap.xaml e MonkeyTap.xaml.cs no projeto MonkeyTap.
No Visual Studio, você pode adicionar itens a projetos como ligações a arquivos existentes, selecionando “Add > Existing Item” no menu
projeto. Em seguida, use o diálogo “Add Existing Item” para navegar para o arquivo. Escolha “Add as Link” no menu drop-down no botão
“Add”.
Em Xamarin Studio, selecione “Add File to Folder” arquivo no menu projeto. Depois de abrir o arquivo ou arquivos, um “Add File to Folder”
caixa de alerta aparece. Escolha “Add a link to the file” para o arquivo.
No entanto, depois de seguir estes passos no Visual Studio, também foi necessário editar manualmente o arquivo
MonkeyTapWithSound.csproj para alterar o arquivo MonkeyTapPage.xaml a um EmbeddedResource e o Gerador de MSBuild:
UpdateDesignTimeXaml. Além disso, uma tag DependentUpon foi adicionada ao arquivo MonkeyTapPage.xaml.cs para referenciar o arquivo
MonkeyTapPage.xaml. Isso faz com que o arquivo code-behind para ser identado sob o arquivo XAML na lista de arquivos.
A classe MonkeyTapWithSoundPage então deriva da classe MonkeyTapPage. Embora a classe MonkeyTapPage seja definido por um arquivo
XAML e um arquivo code-behind, MonkeyTapWithSoundPage é apenas o código. Quando uma classe é derivada desta forma, manipuladores
de eventos no arquivo code-behind original para eventos no arquivo XAML deve ser definida como protegida, e este é o caso.
A classe MonkeyTap também definiu um flashDuration constante como protegida, e dois métodos foram definidos como protected e virtual. O
MonkeyTapWithSoundPage substitui estes dois métodos para chamar um método estático chamado SoundPlayer.PlaySound:
O método SoundPlayer.PlaySound aceita uma frequência e uma duração em milissegundos. Todo o resto, o volume, a composição harmônica
do som, e como o som é gerado é de responsabilidade do método PlaySound. No entanto, este código faz uma suposição implícita de que
SoundPlayer.PlaySound retorna imediatamente e não espera o som para completar de jogo. Felizmente, todas as três plataformas oferecem
suporte a APIs de geração de som que se comportam dessa maneira.
A classe SoundPlayer com o método estático PlaySound é parte do projeto MonkeyTapWithSound com biblioteca compartilhada. A
responsabilidade deste método é definir uma matriz de dados PCM para o som.
O tamanho desta matriz baseia-se na taxa de amostragem e a duração. Para o laço calcula amostras que definem uma onda triângulo da
freqüência solicitado:

Embora as amostras sejam inteiras de 16 bits, duas das plataformas precisam dos dados na forma de uma matriz de bytes, uma conversão
ocorre perto do final com Buffer.BlockCopy. A última linha do método usa DependencyService para passar esta matriz de bytes com a taxa de
amostragem para as plataformas individuais.
O método DependencyService.Get faz referência a interface IPlatformSoundPlayer que de-multas a assinatura do método PlaySound:
Agora vem a parte difícil: escrever este método PlaySound para as três plataformas!
A versão iOS usa AVAudioPlayer, o que requer dados que inclui o cabeçalho usado em Waveform Audio File Format (arquivos .wav). O código
aqui reúne dados em um MemoryBuffer e depois converte isso para um objeto NSData:

Observe os dois elementos essenciais: PlatformSoundPlayer implementa a interface IPlatformSoundPlayer, e a classe é marcada com o
atributo de dependência.
A versão Android usa a classe AudioTrack, e que acaba por ser um pouco mais fácil. No entanto, objetos Audiotrack não podem sobrepor-se,
por isso é necessário parar e salvar o objeto anterior e pará-lo antes de começar a jogar a próxima:
Um programa do
Windows Phone que usa a API do Silverlight (como programas Xamarin.Forms) tem acesso a funções de som em XNA-a de alto nível interface
de código gerenciado para DirectX. O código para usar DynamicSoundEffectInstance é extremamente simples:

No entanto, um pouco mais é requerido. Ao utilizar XNA para gerar som, um projeto do Windows Phone requer outra classe que faz chamadas
para FrameworkDispatcher.Update a um ritmo constante:
Uma instância dessa classe deve ser iniciada pelo aplicativo. Um lugar conveniente para fazer isso é no arquivo WindowsPhoneApp.xaml:

Neste ponto, você


deve ser capaz de ler e compreender um bom pedaço desse arquivo XAML do Windows Phone!
O uso de DependencyService para executar tarefas específicas da plataforma é muito poderoso, mas esta abordagem é insuficiente quando
se trata de elementos de interface do usuário. Se para expandir você precisa um arsenal de pontos de vista que adornam as páginas de seus
aplicativos Xamarin.Forms, esse trabalho envolve a criação de prestadores específicos da plataforma, um processo discutido em um capítulo
posterior.
Capítulo 10
Extensões de marcação XAML
No código, você pode definir uma propriedade em uma variedade de maneiras diferentes a partir de uma variedade de fontes diferentes:
triangle.Angle1 = 45;
triangle.Angle1 = * 180 radianos / Math.PI;
triangle.Angle1 = ângulos [i];
triangle.Angle1 animator.GetCurrentAngle = ();
Se este Angle1 é uma propriedade double, Tudo que é necessário é que a fonte seja um double ou de outra forma provenha um valor numérico
que é conversível para um double.
Na marcação, no entanto, uma propriedade do tipo double normalmente só pode ser definida a partir de uma seqüência de caracteres que se
qualifica como um argumento válido para Double.Parse. A única exceção que você já viu até agora é quando a propriedade alvo flagueada
com um atributo TypeConverter, tal como a propriedade FontSize.
Seria desejável se XAML fosse mais flexível - se você pudesse definir uma propriedade de outras fontes do que seqüências de texto explícitas.
Por exemplo, suponha que você queira definir uma outra maneira de setar uma propriedade do tipo Color, Talvez usando uma Matiz ,Saturação
e Luminosidade, mas sem o incômodo do elemento x: FactoryMethod. Apenas de improviso, não parece possível. O analisador XAML espera
que qualquer valor definido como um atributo do tipo Color é uma string aceitável para a classe ColorTypeConverter.
O objetivo das extensões de marcação XAML é contornar esta restrição aparente. Tenha certeza de que Extensões de marcação XAML não
são extensões para XML. XAML é sempre XML legal. As extensões de marcação XAML são extensões apenas no sentido de que eles
estendem as possibilidades de configurações de atributo na marcação. Uma extensão de marcação fornece essencialmente um valor de um
tipo particular, sem necessariamente ser uma representação de texto de um valor.

A infra-estrutura de código
Estritamente falando, uma extensão de marcação XAML é uma classe que implementa ImarkupExtension, Que é uma interface pública definida
no Xamarin.Forms.Core assembly comum, mas com o namespace Xamarin.Forms.Xaml:
public interface IMarkupExtension
{
object ProvideValue (IServiceProvider serviceProvider);
}
Como o nome sugere, ProvideValue é o método que fornece um valor para um atributo XAML. IserviceProvider faz parte das bibliotecas de
classe base do .NET e definido no namespace System:
public interface IServiceProvider
{
object GetService (Type type);
}
Obviamente, esta informação não fornece nada além de uma dica sobre como escrever extensões de marcação personalizadas, e na verdade,
podem ser complicadas. (Você verá um exemplo brevemente e outros exemplos mais adiante neste livro.) Felizmente, Xamarin.Forms fornece
várias extensões de marcação valiosas para você. Estas se dividem em três categorias:
• Extensões de marcação que são parte da especificação XAML de 2009. Estas aparecem em arquivos XAML com o habitual prefixo
x e são:

◦ x: Static
◦ x: Reference
◦ x: Type
◦ x: nulo
◦ x: Array
Estes são implementados em classes que consistem do nome da extensão de marcação com a palavra Extension anexado, são exemplos as
classes StaticExtension e ReferenceExtension. Estas classes são definidas na assembly Xamarin.Forms.Xaml.
• As seguintes extensões de marcação se originaram no Windows Presentation Foundation (WPF) e, com excessão da
DynamicResource, são suportados por outras implementaçãoes de XAML da Microsoft, incluindo Silverlight, Windows Phone 7 e 8
e Windows 8 e 10:
o StaticResource
o DynamicResource
o Binding

A classe DynamicResourceExtensioné pública; as StaticResourceExtension e BindingExtension não, mas estão disponíveis para seu uso em
arquivos XAML porque são acessíveis ao analisador XAML. Há apenas uma extensão de marcação que é exclusiva para Xamarin.Forms: a
classe ConstraintExpression usada com RelativeLayout.
Embora seja possível brincar com classes públicas de marcação de extensão em código, elas realmente só fazem sentido em XAML.
Acessando membros estáticos
Uma das implementações mais simples e úteis de ImarkupExtension está encapsulada na classe StaticExtension. Isto é parte da especificação
XAML original, de modo que habitualmente aparece em XAML com um prefixo x. StaticExtension define uma única propriedade denominada
Member do tipo string que você atribui a uma classe e membro nome de uma constante pública, propriedade estática, campo estático, ou
enumeração. Vamos ver como isso funciona. Aqui está uma Label com seis propriedades definidas como normalmente apareceriam em XAML.

Cinco destes atributos são atribuidos a seqüências de texto que eventualmente fazem referência a várias propriedades, campos estáticos, e
membros de enumeração, mas a conversão dessas cadeias de texto ocorre através de conversores de tipo e a análise XAML padrão de tipos
de enumeração.
Se você quiser ser mais explícito na definição desses atributos para essas diferentes propriedades, campos estáticos, e membros de
enumeração, você pode usar x: StaticExtension dentro de marcas de elemento de propriedade:

Color.Accent é uma propriedade estática. Color.Black e LayoutOptions.Center são campos estáticos. FontAttributes.Italic e
TextAlignment.Center são membros de enumeração.
Tendo em vista a facilidade com que estes atributos são definidos com cadeias de texto, utilizar a abordagem StaticExtension inicialmente
parece ridículo, mas note que é um mecanismo de propósito geral. Você pode usar qualquer propriedade estática, campo ou membro de
enumeração na tag StaticExtension, se seu tipo corresponde ao da propriedade de destino.
Por convenção, as classes que implementam ImarkupExtension incorporam a palavra Extension em seus nomes, mas você pode deixar isso
de lado no XAML, por isso que esta extensão de marcação é geralmente chamada de x:Static em vez de x: StaticExtension . A marcação a
seguir é ligeiramente mais curta do que o bloco anterior:
E agora, para um grande salto de sintaxe, uma mudança na sintaxe que faz com que as marcas de propriedade de elementos desapareçam
e as pegadas encolham consideravelmente. Extensões de marcação XAML quase sempre aparecem com o nome de extensão de marcação
e os argumentos dentro de um par de chaves:

Esta sintaxe com as chaves é tão usada em conexão com extensões de marcação XAML que muitos desenvolvedores consideram que
extensões de marcação são sinônimo de sintaxe chave. E isso é quase verdade: enquanto chaves sempre sinalizam a presença de uma
extensão de marcação XAML, em muitos casos uma extensão de marcação pode aparecer em XAML sem as chaves (como demonstrado
anteriormente) e às vezes é conveniente utilizá-los dessa forma.
Observe que não há aspas dentro das chaves. Dentro dessas chaves, regras muito diferentes de sintaxe se aplicam. A propriedade Member
da classe StaticExtension não é mais um atributo XML. Em termos de XML, toda a expressão delimitada pelas chaves é o valor do atributo, e
os argumentos dentro das chaves aparecem sem aspas.
Assim como os elementos, as extensões de marcação podem ter um atributo ContentProperty. Extensões de marcação que tem apenas uma
propriedade, tais como a classe StaticExtension com a sua única propriedade Member invariavelmente marca essa propriedade exclusiva
como a propriedade de conteúdo. Para extensões de marcação usando esta sintaxe, isto significa que o nome da propriedade Member e o
sinal de igual podem ser removidos:

Esta é a forma comum da extensão de marcação x: Static.


Obviamente, a utilização de x: Static para essas propriedades particulares é desnecessária, mas você pode definir seus próprios membros
estáticos de execução constantes de todo o aplicativo, e você pode fazer referência a estes em seus arquivos XAML. Isto é demonstrado no
projeto SharedStatics. O projeto SharedStatics contém uma classe chamada AppConstants que define algumas constantes e campos estáticos
que podem ser úteis para a formatação do texto:
O arquivo XAML usa então a extensão de marcação x:Static para fazer referência a esses itens. Observe a declaração de namespace XML
que associa o prefixo local com o namespace do projeto:
"Extensão de marcação XAML, um aplicativo pode manter uma coleção de configurações de propriedade comum definidos como constantes,
Você pode estar curioso porque cada um dos objetos Span com uma configuração FontAttributes repete a configuração FontSize que é
definida na própria Label. De fato, objetos Span não herdam as configurações de Fonte da Label quando outra configuração relacionadas
com o tipo de letra é aplicado. E aqui está:
Esta técnica permite que você use essas configurações de propriedade comuns em várias páginas, e se você precisar alterar os valores, você
só precisa mudar o arquivo AppSettings.
Também é possível utilizar x:Static com propriedades estáticas e campos definidos nas classes em bibliotecas externas. O exemplo a seguir,
denominado SystemStatics, é bastante artificial, ele define o BorderWidth de um Botão igual ao campo estático PI definido na classe Math e
usa a propriedade estática Environment.New-Line para quebras de linha no texto. Mas demonstra a técnica.
As classes Math e Environment são ambas definidas no namespace .NET System, assim que uma nova Declaração de namespace XML é
necessária para definir um prefixo chamado (por exemplo) sys para System. Note que esta declaração de namespace especifica o namespace
CLR como System mas o conjunto como mscorlib, que originalmente era para Microsoft Common Object Library Runtime mas agora está
disponível para Multilanguage Standard Common Object Runtime Library:

A borda do botão não aparece no Android a menos que a cor de fundo e cores de borda do botão também estejam setadas para valores não
padrão, então algumas marcações adicionais cuidam do problema. Em plataformas iOS, a borda do botão tende sobrepor o texto do botão,
então o texto é definido com espaços no início e no fim.
A julgar somente a partir dos recursos visuais, nós realmente temos que acreditar que a largura da borda do botão é cerca de 3,14 de largura,
mas as quebras de linha definitivamente funcionam:
A utilização de chaves para as extensões de marcação implica que você não pode exibir o texto cercado por chaves. As chaves neste texto
serão substituidas por uma extensão de marcação:

Isso não vai funcionar. Você pode ter chaves em outras partes da cadeia de texto, mas você não pode começar com uma chave esquerda.
Se você realmente precisa, no entanto, você pode garantir que o texto seja substituido por uma extensão XAML começando o texto com uma
sequência de escape que consiste em um par de chaves esquerda e direita:

Que irá exibir o texto que deseja.

Dicionários de recursos
Xamarin.Forms também suporta uma segunda abordagem para compartilhamento de objetos e valores, e enquanto essa abordagem tem um
pouco mais de peso do que a extensão de marcação x:Static, é de alguma forma a causa de tudo mais versátil – os objetos compartilhados e
os elementos visuais os usam.
VisualElement define uma propriedade chamada Resources que é do tipo ResourceDictionary - um dicionário com chaves string e valores do
tipo objeto . Os itens podem ser adicionados a este dicionário direto no XAML, e eles podem ser acessados em XAML com as extensões de
marcação StaticResource e DynamicResource.
Apesar de x:Static e StaticResource terem nomes um tanto similares, eles são bastante diferentes: x:Static faz referência a uma constante,
um campo estático, uma propriedade estática, ou um membro de enumeração, enquanto StaticResource recupera um objeto de um
ResourceDictionary. Enquanto a extensão de marcação x:Static é intrínseca ao XAML (e, portanto, aparece em XAML com um prefixo x), as
extensões de marcação StaticResource e DynamicResource não são. Elas faziam parte da implementação XAML original no Windows
Presentation Foundation, e StaticResource é também suportada em Silverlight, Windows Phone 7 e 8 e Windows 8 e 10.
Você vai usar StaticResource para a maioria dos propósitos e reservar DynamicResource para algumas aplicações especiais, então vamos
começar com StaticResource.
StaticResource para a maioria dos fins
Suponha que você tenha definido três botões em um StackLayout :

Claro, este é um código um tanto irrealista. Não há eventos Clicked setados para estes botões, e a BorderWidth não tem efeito nos dispositivos
Android porque o fundo botão e cor da borda têm seus valores padrão. Veja a aparência dos botões:

Para além do texto, todas as três teclas têm as mesmas propriedades definidas para os mesmos valores. Maracações repetitivas como estas
tendem a levar os programadores para o caminho errado. É uma afronta aos olhos e difícil de manter e mudar.
Eventualmente, você vai ver como usar estilos para realmente diminuir a marcação repetitiva. Por enquanto, de qualquer forma, o objetivo não
é fazer a marcação mais curta, mas consolidar os valores em um só lugar para que se você quiser mudar a propriedade TextColor de Vermelho
para Azul, você pode fazê-lo com uma edição o invés de três.
Obviamente, você pode usar x:Static para este trabalho, definindo os valores no código. Mas vamos fazer tudo em XAML, armazenando os
valores em um dicionário de recursos. Cada classe que deriva de VisualElement tem uma propriedade Resources do tipo ResourceDictionary.
Recursos que são usados ao longo de uma página são habitualmente armazenados na coleção Resources da Página de conteúdo.
O primeiro passo consiste em expressar a propriedade Resources de Página de conteúdo como um elemento de propriedade:
Se você também está definindo uma propriedade Padding na página usando tags de propriedade elementos, a ordem não importa.
Para efeitos de desempenho, a propriedade Resources é nula por padrão, então você precisa explicitamente instanciar o ResourceDictionary
:

Entre as tags ResourceDictionary, você define um ou mais objetos ou valores. Cada item no dicionário deve ser identificado com uma chave
de dicionário que você especificar com o atributo XAML x: Key. Por exemplo, aqui está a sintaxe para a inclusão de um valor LayoutOptions
no dicionário com uma chave descritiva que indica que este valor é definido para a setar opções horizontais:

Porque este é um valor LayoutOptions, o analisador XAML acessa a classe LayoutOptionsConverter (que é private para Xamarin.Forms) para
converter o conteúdo das tags, que é o texto "centro".
Uma segunda maneira de armazenar um valor LayoutOptions no dicionário é deixar o analisador XAML instanciar a estrutura e e setar as
propriedades LayoutOptions de atributos especificados:

A propriedade BorderWidth é do tipo double, então o elemento tipo de dado x:Double definido na especificação XAML 2009 é ideal:

Você pode armazenar um valor Color no dicionário de recursos com uma representação de texto da cor como conteúdo. O analisador XAML
usa o ColorTypeConverter normal para a conversão de texto:

Você não pode inicializar um valor Color, definindo suas propriedades R,G e B porque esses são apenas get. Mas você pode chamar um
contrutor Color ou um dos métodos de fábrica Color:
Um item de dicionário para a propriedade FontSize é um pouco problemático. A propriedade FontSize é do tipo Double, então se você está
armazenando um valor numérico real no dicionário, isso não é problema. Mas não é possível armazenar a palavra "grande" no dicionário como
se fosse um double. Somente quando uma string "grande" é definida como um atributo FontSize é que o analisador XAML usa o
FontSizeConverter. Por essa razão, você vai precisar para armazenar o item FontSize como uma string:

Aqui está o dicionário completo neste momento:

Esta é muitas vezes referida como uma seção de recursos para a página. Na programação da vida real, quase todos os arquivos XAML
começam com uma seção de recursos. Você pode fazer referência a itens no dicionário usando a extensão de marcação StaticResource, que
é suportado pela StaticResourceExtension, uma classe privada para Xamarin.Forms. StaticResourceExtension define uma propriedade
chamada key que definiu para o dicionário de chaves. Você pode usar uma StaticResourceExtension como um elemento dentro de tags de
propriedade de elementos, ou você pode usar StaticResourceExtension ou StaticResource entre chaves. Se você está usando a sintaxe chave,
você pode deixar de fora a Key e sinais iguais porque a Key é o conteúdo da propriedade de StaticResourceExtension.
O seguinte arquivo XAML completo ilustra três destas opções. Não coloque um prefixo x na propriedade Key de StaticResource. O atributo
x:Key é apenas para definir chaves de dicionário para itens no ResourceDictionary :
</ C

A sintaxe mais simples do terceiro botão é a mais comum e, na verdade, que a sintaxe é tão onipresente que muitos desenvolvedores XAML
de longa data podem estar totalmente familiarizados com as outras variações.
Objetos e valores no dicionário são compartilhados entre todas as referências StaticResource. Não é tão claro no exemplo anterior, mas é
algo a ter em mente. Por exemplo, suponha que você armazene um objeto Button no dicionário de recursos:
Você pode certamente usar esse objeto Button em sua página, adicionando-o às coleções Children de um StackLayout com o elemento de
sintaxe StaticResourceExtension:

No entanto, você não pode usar esse mesmo item de dicionário na esperança de colocar outra cópia no StackLayout :

Isso não vai funcionar. Ambos os elementos fazem referência ao mesmo objeto Button, e um elemento visual específicamente podem estar
em apenas um local particular na tela. Ele não pode estar em vários locais.
Por esta razão, elementos visuais não são normalmente armazenados num dicionário de recursos. Se você precisar de multiplos elementos
em sua página que têm na sua maioria as mesmas propriedades, você vai querer usar um Style, o que é explorada no Capítulo 12.

Uma árvore de dicionários


A classe ResourceDictionary impõe as mesmas regras que outros dicionários: todos os itens no dicionário deve ter chaves, mas chaves
duplicadas não são permitidos.
No entanto, porque cada instância de VisualElement potencialmente tem o seu próprio dicionário de recursos, sua página pode conter vários
dicionários, e você pode usar as mesmas chaves em diferentes dicionários apenas contanto que todas as chaves dentro de cada dicionário
são únicas. É concebível que cada elemento visual na árvore visual pode ter seu próprio dicionário, mas realmente só faz sentido um dicionário
de recursos se aplicar a vários elementos, então dicionários de recursos só são encontrados geralmente definido em Layout ou objetos Page.
Você pode construir uma árvore de dicionários com chaves de dicionário que efetivamente sobrescrevem as chaves em outros dicionários.
Isto é demonstrado no projeto ResourceTrees. O arquivo XAML para a classe ResourceTreesPage mostra um dicionário de Resources para
a Content Page que define recursos com chaves de horzOptions , vertOptions e textColor . O item textColor demonstra como usar OnPlatform
em um ResourceDictionary.
Um segundo dicionário de Resources é anexado a um StackLayout interior para recursos nomeados textColor e FontSize:
O dicionário de Resources no interior do StackLayout aplica-se apenas aos itens de dentro dessa StackLayput, que são os itens nesta captura
de tela:

Veja como funciona:


Quando o analisador XAML encontra um StaticResource em um atributo de um elemento visual, começa a procurar a chave dicionário. A
principio no ResourceDictionary para esse elemento visual, e se a chave não for encontrada, ele procura a chave no ResourceDictionary pai
e acima e acima através da árvore visual até que ele atinja a ResourceDictionary na página.
Mas algo está faltando aqui! Onde estão os itens de dicionário borderWidth e FontSize? Eles não parecem estar definidos no dicionário de
recursos da página!
Esses itens estão em outro lugar. A classe Application também define uma propriedade Resources do tipo ResourceDictionary. Isso é útil para
a definição de recursos que se aplicam a todo o aplicativo e não apenas a uma página ou layout específico. Quando o analisador XAML
pesquisa a árvore visual para uma correspondência chave de recurso, e a chave não é encontrada na ResourceDictionary para a página, ele
finalmente verifica o ResourceDictionary definido pela classe Application. Só se ele não for encontrado há uma XamlParseException lançada
pela StaticResourcechave.
O modelo de solução padrão gera um Xamarin.Forms gera uma classe App que deriva de Application e, assim, herda a propriedade Resources.
Você pode adicionar artigos a este dicionário de duas maneiras:
Uma abordagem é para adicionar os itens em código no App construtor. Certifique-se de fazer isso antes de instanciar a classe ContentPage
principal:
No entanto, a classe App também pode ter um arquivo XAML próprio, e os recursos de todo o aplicativo pode ser definido na coleção Resources
no arquivo XAML.
Para fazer isso, você vai querer apagar o arquivo App.cs criado pelo modelo de solução Xamarin.Forms. Não há nenhum item de modelo
para uma classe App, então você precisa fingir. Adicionar uma nova página de classe XAML - Forms Xaml page no Visual Studio ou Forms
ContentPage Xaml em Xamarin Studio - ao projeto. Nomeie o App . E imediatamente, antes que esqueça - vá para o arquivo App.xaml e
altere as tags raiz para Application , e vá para o arquivo App.xaml.cs e altere a classe base para Application.
Agora você tem uma classe App que deriva de Application e tem seu próprio arquivo XAML. No arquivo App.xaml que você pode, em seguida,
instanciar um ResourceDictionary dentro de tags de propriedade de elementos Application.Resources e adicionar itens a ela:

O construtor no arquivo code-


behind precisa chamar o InitializeComponent para analisar o arquivo App.xaml em tempo de execução e adicionar os itens ao dicionário. Isto
deve ser feito antes do trabalho normal de instância da classe ResourceTreesPage e defini-lo como propriedade MainPage:
Adicionar eventos de ciclo de vida é opcional.
Certifique-se de chamar InitializeComponent antes de instanciar a classe de página. O construtor da classe de página chama sua própria
InitializeComponent para analisar o arquivo XAML para a página, e as extensões de marcação StaticResource precisam de acesso às coleções
de Resources na classe App.
Cada dicionário de Resource tem um escopo específico: Para o dicionário de Resources na classe App, esse escopo é o aplicativo inteiro. Um
dicionário de Resources na ContentPage aplica-se à classe página inteira.
Como você verá no Capítulo 12, os itens mais importantes em um dicionário de Resources são geralmente objetos do tipo Style. Em geral,
você terá Styes para todo o aplicativo, objetos Style para a página, e objetos Style associados com partes menores da árvore visual.

DynamicResource para fins especiais


Uma alternativa para StaticResource para fazer referência a itens do dicionário de Resources é DynamicResource e se você apenas substituir
DynamicResource por StaticResource no exemplo mostrado acima, o programa irá executar aparentemente da mesma forma.
No entanto, as duas extensões de marcação são muito diferentes. StaticResource acessa o item no dicionário apenas uma vez, enquanto o
XAML está sendo analisado e a página está sendo construída. Mas DynamicResource mantém uma ligação entre o dicionário chave e a
propriedade definida a partir desse item de dicionário. E se o item no dicionário de recurso referenciado pelas chave mude, DynamicResource
que irá detectar a mudança e definir o novo valor à propriedade.
Cético? Vamos testá-lo. O projeto DynamicVsStatic tem um arquivo XAML que define um item de recurso do tipo string com uma chave de
CURRENTDATETIME, mesmo que o item no dicionário seja a string "Não é realmente um DateTime"!
Este item do dicionário é referenciado quatro vezes no arquivo XAML, mas uma das referências é descomentada. Nos dois primeiros exemplos,
a propriedade Text de uma Label é definida utilizando StaticResource e DynamicResource . No segundo dois exemplos, a propriedade Text
de um objeto Span é definida de forma semelhante, mas o uso de DynamicResource no Span aparece nos comentários:
Você provavelmente vai esperar todas as três referências para o item de dicionário CURRENTDATETIME para resultar na exibição do texto
"Não realmente um DateTime". No entanto, o arquivo de code-behind inicia um temporizador. A cada segundo, o retorno de chamada timer
substitui o item de dicionário com uma nova seqüência que representa um valor real de DateTime:
O resultado é que as propriedades Text definidas com StaticResource permanecem as mesmas, enquanto o outro com DynamicResource
muda a cada segundo para refletir o novo item no dicionário:

Aqui está outra diferença: se não houver nenhum item no dicionário com o nome de chave especificado, StaticResource irá lançar uma exceção
em tempo de execução, mas DynamicResource não irá.
Você pode tentar descomentar o bloco de marcação no final do projeto DynamicVsStatic, e você realmente vai encontrar uma exceção de
tempo de execução no sentido de que a propriedade Text não poderia ser encontrada. Apenas improvisadamente, esta exceção não soa
muito bem, mas ele está se referindo a uma diferença muito real.
O problema é que as propriedades Text na Label e Span são definidas de diferentes formas, e que essa diferença importa muito para
DynamicResource . Essa diferença será explorada no próximo capítulo, "A infraestrutura bindable."

Extensões de marcação Menos Divulgadas


Três extensões marcação não são usadas tanto quanto outras. Estas são:
• x: nulo
• x: Type
• x: Array

Você usa a extensão x:Null para definir uma propriedade para nulo. A sintaxe parecida com esta:

Isso não faz muito sentido, a menos que SomeProperty tem um valor padrão que não é nulo quando é desejavel para definir a propriedade
para nulo . Mas, como você verá no Capítulo 12, por vezes, uma propriedade pode adquirir um valor não-nulo de um estilo, e x:null é
praticamente a única maneira de substituir isso.
A extensão de marcação x:Type é usada para definir uma propriedade do tipo Type , a classe .NET descrevendo o tipo de uma classe ou
estrutura. Aqui está a sintaxe:
Você também vai usar x: Type em conexão com x: Array. A extensão de marcação x: Array é sempre usada com elementos regulares de
sintaxe, em vez de sintaxe chave. Tem um argumento necessário nomeado Type que você configura com a extensão de marcação x:Type.
Isto indica o tipo dos elementos na matriz.
Aqui é como uma matriz pode ser definida em um dicionário de recursos:

Uma extensão de marcação personalizada


Vamos criar a nossa própria extensão de marcação nomeada HslColorExtension. Isso nos permitirá definir qualquer propriedade do tipo Color
especificando valores de matiz, saturação e luminosidade, mas de uma forma muito mais simples do que a utilização da tag x:FactoryMethod
demonstrado no Capítulo 8.
Além disso, vamos colocar essa classe em uma biblioteca separada de Classes Portátil de modo que você pode usá-lo a partir de múltiplos
aplicações. Tal biblioteca pode ser encontrada com o outro código-fonte para este livro. Está em um diretório chamado Bibliotecas que é
paralela aos diretórios capítulo separado. O nome deste PCL (e o namespace das classes dentro dela) é Xamarin.FormsBook.Toolkit .
Você pode usar esta biblioteca em seus próprios aplicativos, definindo uma referência a ele ou através da inclusão o projeto em sua solução
de aplicação. Você pode então adicionar uma nova declaração de namespace XML nos seus Arquivos XAML para especificar esta biblioteca:

Com esse prefixo kit de ferramentas que você pode fazer referência a classe HslColorExtension da mesma maneira que você usa outras
extensões de marcação XAML:

Ao contrário de outras extensões de marcação XAML mostrados até agora, este tem várias propriedades, e se você estásetando-as como
argumentos com a sintaxe chave, eles devem ser separados por vírgulas.
Algo com isso seria útil? Vamos primeiro ver como criar uma biblioteca de classes que você gostaria de compartilhar entre os aplicativos:
No Visual Studio, a partir do menu arquivo, selecione Novo e Projeto. Na caixa de dialogo Novo projeto, selecione Visual C# e Mobile Apps à
esquerda, e Biblioteca de classes (Xamarin.Forms portáteis) a partir da lista. Encontre um local para o projeto e de um nome. Para o PCL
criado para este exemplo, o nome é Xamarin.FormsBook.Toolkit . Clique em OK. Junto com toda a sobrecarga para o projeto, o modelo cria
um arquivo de código Xamarin.FormsBook.Toolkit.cs nomeados contendo uma classe chamada Xamarin.FormsBook.Toolkit. Isso não é um
nome de classe válido, então basta apagar o arquivo. Em Xamarin Studio, a partir do menu arquivo, selecione Nova e Solution . Na caixa de
diálogo New Solution , selecione C# e Mobile Apps à esquerda, e Biblioteca de classes (Xamarin.Forms portáteis) a partir da lista. Encontre
um local para ele e de um nome ( Xamarin.FormsBook.Toolkit para este exemplo). Clique em OK. A solução template cria vários arquivos,
incluindo um arquivo chamado MyPage.cs. Excluir esse arquivo. Agora você pode adicionar classes para esse projeto na forma normal: No
Visual Studio, clique com o botão direito do mouse no nome do projeto, selecione Adicionar e New Item . Na caixa de diálogo Add New Item
, se você está apenas criando uma classe só de código, selecione Visual C# e código à esquerda, e selecione Classe a partir da lista. Dê-lhe
um nome (HslColorExtension.cs para este exemplo). Clique no botão Adicionar.
Em Xamarin Studio, no menu ferramenta para o projeto, selecione Adicionar e Novo arquivo . Na caixa de diálogo Novo arquivo, se você está
apenas criando uma classe só de código, selecione Geral à esquerda e à classe vazia na lista. Dar -lhe um nome (HslColorExtension.cs para
este exemplo). Clique no botão Novo. O arquivo HslColorExtension.cs (incluindo a necessária uso directivas) se parece com isso:
Observe que a classe é pública e implementa a interface ImarkupExtension, o que significa que deve incluir um método ProvideValue. Contudo,
o método não faz uso do argumento IserviceProvider por completo, principalmente porque não precisa saber de qualquer outra coisa externa
além de si mesma. Todas as necessidades de TI são as quatro propriedades para criar um valor Color, e se o valor A não for definido, o valor
padrão de 1 (totalmente opaco) é usado. Esta é uma solução com apenas um projecto de PCL. O projeto pode ser construído para gerar um
conjunto de PCL, mas não pode ser executado sem um aplicativo que usa essa montagem. Há duas maneiras de acessar essa biblioteca a
partir de uma solução da aplicação:
• No projeto comum PCL na solução de aplicativo, adicione uma referência ao conjunto de PCL.
• Incluir um link para o projeto de biblioteca em sua solução de aplicação, e adicionar uma referência a esse li-projeto brary
no projeto PCL comum.

A primeira opção é necessária se você tiver apenas o PCL e não o projeto com código fonte. Possivelmente você está licenciando a biblioteca
e não têm acesso à fonte. Mas se você tem acesso ao projeto, normalmente é melhor para incluir um link para o projeto de biblioteca em sua
solução para que você possa facilmente fazer alterações no código da biblioteca e reconstruir o projeto de biblioteca. O projeto final neste
capítulo é CustomExtensionDemo, que faz uso da classe HslColorExtension na nova biblioteca. A solução CustomExtensionDemo contém
um link para o projeto Xamarin.FormsBook.Toolkit PCL, e a seção Referências no projeto CustomExtensionDemo lista a montagem do
Xamarin.FormsBook.Toolkit. Agora, o projeto de aplicativo está aparentemente pronto para acessar o projeto de biblioteca para usar a classe
HslCol- orExtension dentro do arquivo XAML do aplicativo. Mas primeiro há um outro passo. Atualmente, uma referência para a biblioteca de
XAML é insuficiente para garantir que a biblioteca seja incluída com o aplicativo. A biblioteca precisa ser acessada a partir do código real.
Você pode adicionar uma simples declaração no arquivo App para fazer referência a classe HslColorExtension:

O seguinte arquivo XAML mostra a declaração de namespace XML para a biblioteca Xama-rin.FormsBook.Toolkit e três maneiras de acessar
a extensão de marcação XAML usando um elemento HslColorExtension definido com a sintaxe da propriedade elemento na propriedade Color
e utilizando tanto HslColorExtension e HslColorcom a sintaxe mais comum chave. Mais uma vez, observe o uso de vírgulas para separar os
argumentos dentro das chaves:
Os dois últimos exemplos setam a propriedade A para 50% a transparência, para que as caixas sejam exibidas em um tom de cinza (ou não
em todos), dependendo do fundo:

Dois grandes usos de extensões de marcação XAML ainda estão por vir. No Capítulo 12, você verá a classe Style, que é sem dúvida o item
mais popular para a inclusão em dicionários de recurso e, Capítulo 16, você verá a extensão de marcação poderoso chamado Obrigatório .
Capítulo 11
A infraestrutura Bindable
Uma das mais básicas construções de linguagem C# é membro de uma classe conhecida como a propriedade. Todos nós, desde cedo, em
nossos primeiros encontros com C# aprendemos a rotina geral da definição de uma propriedade. A propriedade é, muitas vezes, feita por um
campo privado e inclui definir e obter acessores que referenciam o campo privado, fazendo algo com um novo valor:

public class MyClass


{

double quality;

public double Quality


{
set
{

quality = value;
// Do something with the new value
}
get
{

return quality;
}
}

}
As propriedades são, as vezes, referidas como "campos inteligentes". Código que acessa uma propriedade sintaticamente assemelha-
se ao código que acessa um campo. No entanto, a propriedade pode executar um pouco de seu próprio código quando a esta for
acessada.
As propriedades são, também, como métodos. Na verdade, o código C# é compilado em Intermediate Language que implementa uma
propriedade, como qualidade com um par de métodos chamados set_Quality e get_Quality. Contudo, apesar da semelhança funcional
entre propriedades e o par de métodos definir e obter, a sintaxe da propriedade revela-se muito mais adequada quando movimentam-se
do código para remarcar. É difícil imaginar XAML construído sobre uma API subjacente que está faltando propriedades.
Então, você pode se surpreender ao saber que Xamarin.Forms implementa uma definição de propriedade reforçada que se baseia em
propriedades C#. Ou, talvez, você não será surpreendido. Se você já tem experiência com plataformas baseadas em XAML da Microsoft,
você vai encontrar alguns conceitos familiares neste capítulo.

A definição de propriedade mostrado acima é conhecida como uma propriedade CLR porque é apoiado pelo .NET Common Language
Runtime. A definição de propriedade reforçada em Xamarin.Forms se baseia no CLR propriedade e é chamado de propriedade vinculável
encapsulado pela classe BindableProperty e apoiada pela classe bindableobject.

A Hierarquia da Classe Xamarin.Forms


Antes de explorar os detalhes da importante classe bindableobject, vamos primeiro descobrir como a BindableObject se encaixa, em
geral, na arquitetura Xamarin.Forms globais através da construção de uma hierarquia de classes.
Na estrutura da programação orientada a objetos como Xamarin.Forms, uma hierarquia de classes pode, muitas vezes, revelar
importantes estruturas internas do ambiente. A hierarquia de classe revela como as várias classes relacionam-se com a outra, e as
propriedades, métodos e eventos que partilham, incluindo como propriedades vinculáveis são suportados.
Você pode construir uma hierarquia de classe partindo laboriosamente através da documentação online e tomando nota de quais classes
derivam de outras classes. Ou, você pode escrever um programa Xamarin.Forms para construir uma hierarquia de classes e exibi-lo no
telefone. Tal programa faz uso de .NET reflexão para obter todas as classes públicas, estruturas e enumerações no Xamarin.Forms.Core,
montando e organizando em uma árvore. A aplicação ClassHierarchy demonstra essa técnica.
Como de costume, o projeto ClassHierarchy contém uma classe que deriva de ContentPage chamado ClassHierarchyPage, mas
também contém duas classes nomeada
O programa cria uma instância TypeInformation para cada classe pública (e estrutura e enumeração) na montagem Xamarin.Forms.Core,
além de qualquer classe .NET, que serve como uma classe base para a classe Xamarin.Forms, com a exceção de objeto. (Essas classes
.NET são Atributo, Delegado, Enum, EventArgs, Exception, MulticastDelegate, e ValueType.) O construtor de TypeInformation requer
um Type objeto que identifica um tipo, mas também obtém algumas outras informações:

class TypeInformation
{
bool isBaseGenericType; Type
baseGenericTypeDef;

public TypeInformation(Type type, bool isXamarinForms)


{
Type = type;
IsXamarinForms = isXamarinForms; TypeInfo
typeInfo = type.GetTypeInfo(); BaseType =
typeInfo.BaseType;

if (BaseType != null)
{
TypeInfo baseTypeInfo = BaseType.GetTypeInfo();
isBaseGenericType = baseTypeInfo.IsGenericType;

if (isBaseGenericType)
{
baseGenericTypeDef = baseTypeInfo.GetGenericTypeDefinition();
}
}
}

public Type Type { private set; get; }


public Type BaseType { private set; get; }
public bool IsXamarinForms { private set; get; }

public bool IsDerivedDirectlyFrom(Type parentType)


{
if (BaseType != null && isBaseGenericType)
{
if (baseGenericTypeDef == parentType)
{
return true;
}
}
else if (BaseType == parentType)
{
return true;
}
return false;
}
}

Uma parte muito importante desta classe é o método IsDerivedDirectlyFrom, que retornará verdadeiro se passou um argumento que é o
tipo base desse tipo. Esta determinação é complicada se classes genéricas estão envolvidas, e essa questão explica em grande parte
da complexidade da classe.
A classe ClassAndSubclasses é consideravelmente mais curta:

class ClassAndSubclasses
{
public ClassAndSubclasses(Type parent, bool isXamarinForms)
{
Type = parent;
IsXamarinForms = isXamarinForms;
Subclasses = new List<ClassAndSubclasses>();
}

public Type Type { private set; get; }


public bool IsXamarinForms { private set; get; }
public List<ClassAndSubclasses> Subclasses { private set; get; }
}

O programa cria uma instância dessa classe para cada type exibido na hierarquia de classes, incluindo objeto, de modo que o programa
cria mais de uma instância ClassAndSubclasses que o número de instâncias de TypeInformation. A instância ClassAndSubclasses
associada com o objeto contém uma coleção de todas as classes que derivam diretamente do objeto, e cada uma dessas instâncias
ClassAndSubclasses contém uma coleção de todas as classes que derivam daquela, e assim por diante, para o restante da hierarquia
árvore.
A classe ClassHierarchyPage consiste em um arquivo XAML e um arquivo code-behind, mas o arquivo XAML contém pouco mais do que
a rolagem do StackLayout pronto para alguns elementos do label:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ClassHierarchy.ClassHierarchyPage">

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="5, 20, 0,
0" Android="5, 0, 0, 0" WinPhone="5, 0, 0,
0" />
</ContentPage.Padding>

<ScrollView>
<StackLayout x:Name="stackLayout" Spacing="0" />
</ScrollView>
</ContentPage>

O arquivo code-behind obtém referências para os dois objetos Xamarin.Forms Assembly e, em seguida .acumula todas as classes
públicas, estruturas e enumerações na coleção classList. Em seguida, ele verifica a necessidade de incluir qualquer base de classes dos
conjuntos .NET, classifica o resultado, e então chama dois métodos recorrentes, AddChildrenToParent e AddItemToStackLayout:

public partial class ClassHierarchyPage : ContentPage


{
public ClassHierarchyPage()
{
InitializeComponent();

List<TypeInformation> classList = new List<TypeInformation>();


// Get types in Xamarin.Forms.Core assembly.
GetPublicTypes(typeof(View).GetTypeInfo().Assembly, classList);

// Get types in Xamarin.Forms.Xaml assembly.


GetPublicTypes(typeof(Extensions).GetTypeInfo().Assembly, classList);

// Ensure that all classes have a base type in the list.


// (i.e., add Attribute, ValueType, Enum, EventArgs, etc.)
int index = 0;

// Watch out! Loops through expanding classList!


do
{
// Get a child type from the list. TypeInformation childType
= classList[index];

if (childType.Type != typeof(Object))
{
bool hasBaseType = false;

// Loop through the list looking for a base type. foreach


(TypeInformation parentType in classList)
{
if (childType.IsDerivedDirectlyFrom(parentType.Type))
{
hasBaseType = true;
}
}

// If there's no base type, add it.


if (!hasBaseType && childType.BaseType != typeof(Object))
{
classList.Add(new TypeInformation(childType.BaseType, false));
}
}
index++;
}
while (index < classList.Count);

// Now sort the list. classList.Sort((t1,


t2) =>
{
return String.Compare(t1.Type.Name, t2.Type.Name)

});
// Start the display with System.Object.
ClassAndSubclasses rootClass = new ClassAndSubclasses(typeof(Object), false);

// Recursive method to build the hierarchy tree.


AddChildrenToParent(rootClass, classList);

// Recursive method for adding items to StackLayout.


AddItemToStackLayout(rootClass, 0);
}

void GetPublicTypes(Assembly assembly, List<TypeInformation>


classList)
{
// Loop through all the types.
foreach (Type type in assembly.ExportedTypes)
{
TypeInfo typeInfo = type.GetTypeInfo();

// Public types only but exclude interfaces.


if (typeInfo.IsPublic && !typeInfo.IsInterface)
{
// Add type to list.
classList.Add(new TypeInformation(type, true));
}
}
}

void AddChildrenToParent(ClassAndSubclasses parentClass,


List<TypeInformation> classList)
{
foreach (TypeInformation typeInformation in classList)
{
if (typeInformation.IsDerivedDirectlyFrom(parentClass.Type))
{
ClassAndSubclasses subClass =
new ClassAndSubclasses(typeInformation.Type, typeInformation.IsXamarinForms);
parentClass.Subclasses.Add(subClass);
AddChildrenToParent(subClass, classList);
}
}
}

void AddItemToStackLayout(ClassAndSubclasses parentClass, int level)


{
// If assembly is not Xamarin.Forms, display full name.
string name = parentClass.IsXamarinForms ? parentClass.Type.Name :
parentClass.Type.FullName; TypeInfo

typeInfo = parentClass.Type.GetTypeInfo();

// If generic, display angle brackets and parameters. if


(typeInfo.IsGenericType)
{
Type[] parameters = typeInfo.GenericTypeParameters;
name = name.Substring(0, name.Length - 2);
name += "<";

for (int i = 0; i < parameters.Length; i++)


{
name += parameters[i].Name;
if (i < parameters.Length - 1)
{
name += ", ";
}
}
name += ">";
}
// Create Label and add to StackLayout. Label label =
new Label
{
Text = String.Format("{0}{1}", new string(' ', 4 * level), name), TextColor =
parentClass.Type.GetTypeInfo().IsAbstract ?
Color.Accent : Color.Default
};

stackLayout.Children.Add(label);

// Now display nested types.


foreach (ClassAndSubclasses subclass in parentClass.Subclasses)
{
AddItemToStackLayout(subclass, level + 1);
}
}
}
O método AddChildrenToParent recursiva monta a lista ligada as instancias de ClassAndSubclasses da coleção classList plana.
O AddItemToStackLayout também é recursiva, pois é responsável por adicionar as ClassesAndSubclasses nas listas ligadss ao objeto
StackLayout criando uma visualização da label para cada classe com um pouco de espaço em branco no início para o recuo adequado.
O método mostra os tipos Xamarin.Forms com apenas os nomes das classes, mas os tipos .NET com o nome totalmente qualificado
para distingui-los. O método usa a cor de destaque plataforma para as classes que não são instanciáveis ou porque eles são abstratos
ou estáticos:

No geral, você verá que elementos visuais das Xamarin.Forms têm a seguinte hierarquia geral:
System.Object
BindableObject
Element
VisualElement
View
...
Layout
...
Layout<T>
...
Page
...

Além do objeto, todas essas classes são implementadas na montagem Xamarin.Forms.Core.dll e associada a um espaço de nomes de
Xamarin.Forms.
Vamos examinar algumas dessas classes principais em detalhes.
Como o nome da classe bindableobject já diz, a função primária desta classe é suportar a vinculação de dados, a ligação de duas
propriedades, de dois objetos de modo que eles mantêm o mesmo valor. Mas bindableobject também suporta estilos e a extensão de
marcação DynamicResource muito bem. Ele faz isso de duas maneiras diferentes: definições de propriedade bindableobject na forma
de objetos BindableProperty, e também implementa a interface .NET INotifyPropertyChanged. Tudo isso será discutido com mais
detalhes neste capítulo e nos capítulos futuros.
Vamos continuar a descer a hierarquia: Como você viu, objetos de interface do usuário em Xamarin.Forms são frequentemente
organizados na página em uma hierarquia pai-filho, e a classe Element inclui suporte para pai e filho relacionamentos.
VisualElement é uma classe extremamente importante em Xamarin.Forms. Um elemento visual é qualquer coisa em Xamarin.Forms que
ocupa uma área na tela. A classe VisualElement define 28 propriedades relacionadas a tamanho, localização, cor de fundo e outras
características visuais e funcionais, tais como IsAtivado e IsVisable.
Em Xamarin.Forms a palavra view é muitas vezes usada para se referir a objetos visuais individuais, tais como botões, controles
deslizantes e caixas de entrada de texto, mas você pode ver que a classe View é pai para as classes de layout também. Curiosamente,
View só acrescenta um par de membros públicos do que ele herda da VisualElement. Aqueles incluem HorizontalOptions e
VerticalOptions que fazem sentido porque essas propriedades não se aplicam a páginas - e GestureRecognizers para apoiar a entrada
de toque.
Os descendentes de layout são capazes de ter views como filhas. Uma view filha aparece na tela visualmente dentro dos limites de seu
pai. Classes que derivam de layout podem ter apenas uma filha do tipo View, mas a classe genérica Layout<T> define uma propriedade
Children, que é uma coleção de várias views filhas, incluindo outros layouts. Você já viu que StackLayout, organiza seus filhos em uma
pilha horizontal ou vertical. Embora a classe Layout derive de uma View, layouts são tão importantes em Xamarin.Forms que muitas
vezes eles são considerados uma categoria em si mesmos.
ClassHierarchy lista todas as categorias de públicos, estruturas e enumerações definidas por Xamarin.Forms mas não lista interfaces.
Aqueles são importantes também, mas você vai ter que explorá-los em seu próprio país. (Ou melhorar o programa para enumerá-los.)

Um olhar sobre Bindableobject e BindableProperty

A existência de duas classes chamadas bindableobject e BindableProperty é provável que seja um pouco confuso no início. Tenha em
mente que bindableobject é muito parecido com Object em que ele serve como uma classe base para uma grande parte da API
Xamarin.Forms e, particularmente, para elemento e, portanto, VisualElement.
Bindableobject fornece suporte para objetos do tipo BindableProperty. Um objeto BindableProperty estende uma propriedade CLR. A
melhor visão sobre BindableProperty vem quando você cria alguns dos seus próprios - como você estará fazendo antes do final deste
capítulo, mas você também pode recolher algum entendimento, explorando as BindableProperty existentes.
Para o começo do Capítulo 7 ("XAML vs. Código") dois botões foram criados com muitas das mesmas configurações de propriedade,
exceto que as propriedades de um botão que foram setados no código usando a sintaxe do objeto de inicialização do C# 3.0 e outro
botão foi instanciado e inicializado em XAML.
Aqui está um programa só de códigos semelhantes chamado PropertySettings que também cria e inicializa dois botões de duas
maneiras diferentes. As propriedades da primeira label são definidas a maneira old-fashioned, enquanto as propriedades da segunda
label são definidos com uma técnica mais detalhado:

public class PropertySettingsPage : ContentPage


{
public PropertySettingsPage()
{
Label label1 = new Label();
label1.Text = "Text with CLR properties";
label1.IsVisible = true;
label1.Opacity = 0.75;
label1.XAlign = TextAlignment.Center; label1.VerticalOptions =
LayoutOptions.CenterAndExpand; label1.TextColor = Color.Blue;
label1.BackgroundColor = Color.FromRgb(255, 128, 128);
label1.FontSize = Device.GetNamedSize(NamedSize.Large, new Label());
label1.FontAttributes = FontAttributes.Bold | FontAttributes.Italic;

Label label2 = new Label();


label2.SetValue(Label.TextProperty, "Text with bindable properties");
label2.SetValue(Label.IsVisibleProperty, true); label2.SetValue(Label.OpacityProperty, 0.75);
label2.SetValue(Label.XAlignProperty, TextAlignment.Center);
label2.SetValue(Label.VerticalOptionsProperty, LayoutOptions.CenterAndExpand);
label2.SetValue(Label.TextColorProperty, Color.Blue);
label2.SetValue(Label.BackgroundColorProperty, Color.FromRgb(255, 128, 128));
label2.SetValue(Label.FontSizeProperty,
Device.GetNamedSize(NamedSize.Large, new Label()));
label2.SetValue(Label.FontAttributesProperty, FontAttributes.Bold |
FontAttributes.Italic);

Content = new StackLayout


{
Children =
{
label1, label2
}
};
}
}
Estas duas formas de definir propriedades são inteiramente consistentes:

No entanto, a sintaxe alternativa parece muito impar. Por exemplo:

label2.SetValue(Label.TextProperty, "Text with bindable properties");

O que é o método SetValue? SetValue é definida por bindableobject, a partir do qual cada objeto visual deriva. Bindableobject também
define um método GetValue.
Esse primeiro argumento para SetValue tem o nome de Label.TextProperty, o que indica que TextProperty é estático, mas apesar de
seu nome, não é uma propriedade em tudo. É um campo estático da classe da label. TextProperty também é só de leitura, e é definido
na classe Label, algo como isto:

public static readonly BindableProperty TextProperty;

Isso é um objeto do tipo BindableProperty. Claro, pode parecer um pouco perturbador que um campo é nomeado TextProperty, mas ele
está lá. Porque é estático, no entanto, que existe independentemente de quaisquer objetos de label que pode ou não existir.
Se você olhar na documentação da classe de label, você verá que ele define 10 propriedades INCLUINDO texto, TextColor, FontSize,
FontAttributes, e outros. Você também vai ver 10 correspondentes campos públicos estáticos somente de leitura do tipo BindableProperty
com os nomes TextProperty, TextCol- orProperty, FontSizeProperty, FontAttributesProperty, e assim por diante.
Essas propriedades e os campos estão intimamente relacionados. Na verdade, interno à classe de label, a propriedade text CLR é
definida como este para fazer referência ao objeto TextProperty correspondente:

public string Text


{
set { SetValue(Label.TextProperty, value); }
get { return (string)GetValue(Label.TextProperty); }
}

Então você vê porque é que o seu aplicativo chamado SetValue em Label.TextProperty é exatamente equivalente a configuração da
propriedade do text diretamente, e talvez apenas um pouco mais minúsculo mais rápido!
A definição interna da propriedade do text no label não é informação secreta. Este é o código padrão. Embora qualquer classe possa
definir um objeto BindableProperty, apenas uma classe que deriva de BindableObject pode ligar para o SetValue e para os métodos
GetValue que realmente implementam a propriedade em uma classe. Conversão é necessária para o método GetValue porque é definida
como retornando object.
Todo o trabalho real envolvido com a manutenção da propriedade de text está acontecendo nessas chamadas SetValue e GetValue. Os
objetos bindableobject e BindableProperty efetivamente estendem a funcionalidade de propriedades CLR padrão para fornecer formas
sistemáticas para:

• Definir propriedades
• Dar às propriedades valores padrões
• Armazenar seus valores atuais
• Fornecer mecanismos para validar valores de propriedade
• Manter a consistência entre as propriedades relacionadas em uma única classe
• Responder a alterações de propriedade
• Disparar notificações quando uma propriedade está prestes a mudar e mudou
• Suportar data binding
• Suportar estilos
• Suportar dynamic resources

A estreita relação de uma propriedade chamada de text com um BindableProperty chamado TextProperty se reflete na maneira que os
programadores falam sobre estas propriedades: Às vezes, um programador diz que a propriedade Text é "apoiado por" um TextProperty
BindableProperty chamado porque TextProperty fornece suporte para infra-estrutura text. Mas um atalho comum é dizer que text é em si
uma "propriedade bindable" e, geralmente, ninguém vai ser confundido.
Nem todas as propriedades Xamarin.Forms é uma propriedade ligável. Nem o Content propriedade de ContentPage nem o Children
propriedade de layout <T> é uma propriedade ligável. Das 28 propriedades demultado por VisualElement, 26 são apoiados por
propriedades vinculáveis, mas a propriedade Bounds e as propriedades de recursos não são.
A classe Span usado em conexão com FormattedString não deriva de BindableObjeto. Portanto Span não herda SetValue e GetValue,
e ele não pode implementar objetos BindableProperty.
Isto significa que a propriedade text de label é apoiado por uma propriedade ligável, mas o text de propriedade de Span não é. Faz
alguma diferença?
Claro que faz a diferença! Se bem se lembram o programa DynamicVsStatic no capítulo anterior, você descobriu que DynamicResource
trabalhou na propriedade de text de label, mas não a propriedade text de Span. Será que DynamicResource só funciona com
propriedades vinculáveis?
A suposição é praticamente confirmada pela definição do seguinte método público definido pelo Element:

public void SetDynamicResource(BindableProperty property, string key);

Isto é como a chave do dicionário é anexada a uma propriedade particular de um elemento quando essa propriedade é o destino de
uma extensão de marcação DynamicResource, e também permite que você defina uma ligação dinâmica de recursos em uma
propriedade no código.
Aqui está a classe de página de uma versão só de código de DynamicVsStatic chamado DynamicVsStaticCode. É um tanto
simplificada de excluir a utilização de um objeto FormattedString e Span mas, caso contrário, imita com bastante precisão como o
arquivo XAML anterior é analisado e, em particular, como as propriedades de texto dos elementos do rótulo são definidos pelo
analisador XAML:

public class DynamicVsStaticCodePage : ContentPage


{
public DynamicVsStaticCodePage()
{
Padding = new Thickness(5, 0);

// Create resource dictionary and add item. Resources = new


ResourceDictionary
{
{ "currentDateTime", "Not actually a DateTime" }
};

Content = new StackLayout


{
Children =
{
new Label
{
Text = "StaticResource on Label.Text:", VerticalOptions =
LayoutOptions.EndAndExpand,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
},

new Label
{
Text = (string)Resources["currentDateTime"],
VerticalOptions = LayoutOptions.StartAndExpand, XAlign =
TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
},

new Label
{
Text = "DynamicResource on Label.Text:", VerticalOptions =
LayoutOptions.EndAndExpand,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
}
}
};

// Create the final label with the dynamic resource. Label label =
new Label
{
VerticalOptions = LayoutOptions.StartAndExpand, XAlign =
TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
};

label.SetDynamicResource(Label.TextProperty, "currentDateTime");

((StackLayout)Content).Children.Add(label);

// Start the timer going.


Device.StartTimer(TimeSpan.FromSeconds(1),
() =>
{
Resources["currentDateTime"] = DateTime.Now.ToString();
return true

});
}
}
A propriedade de text da segunda label é definida diretamente no acesso do dicionário, e faz com que o uso do dicionário pareça um
pouco sem sentido neste contexto. Mas a propriedade de text da última label é vinculada à chave do dicionário por meio de uma chamada
para SetDynamicResource, que permite a propriedade ser atualizada quando o conteúdo do dicionário mudar:

Considere o seguinte: O que a assinatura deste método SetDynamicResource poderá ser se ele não poderá se referir a uma propriedade
usando o objeto BindableProperty? É fácil fazer referência a uma propriedade value em chamadas de método, mas não a própria
propriedade. Há um par de maneiras, tais como a classe PropertyInfo no System.Reflection, ou o objeto de expressão LINQ. Mas o objeto
BindableProper-ty foi projetado especificamente para este fim, bem como o trabalho essencial de lidar com a ligação subjacente entre a
propriedade e a chave do dicionário.
Da mesma forma, quando nós explorarmos estilos no próximo capítulo, vocês encontrarão uma classe Setter usado em conexão com
estilos. Setter define uma propriedade denominada Property do tipo BindableProperty, que determina que qualquer propriedade alvo de
um estilo deve ser apoiada por uma propriedade ligável. Isto permite que um estilo possa ser definido antes dos elementos visados pelo
modelo.
O mesmo vale para as ligações de dados. A classe bindableobject define um método SetBinding que é muito semelhante ao processo
definido no elemento SetDynamicResource:

public void SetBinding(BindableProperty targetProperty, BindingBase binding);

Novamente, observe o tipo do primeiro argumento. Qualquer propriedade alvo de uma ligação de dados deve ser apoiada por uma
propriedade ligável.
Por estas razões, sempre que você criar uma exibição personalizada e precisar definir propriedades públicas, sua inclinação padrão deve
ser defini-las como propriedades vinculáveis. Somente se após cuidadosa consideração de concluir que não é necessário ou apropriado
para a propriedade a ser alvo de um estilo ou uma ligação de dados, você deve recuar e definir uma propriedade CLR ordinária em vez
disso.
Assim, sempre que você criar uma classe que deriva de bindableobject, uma das primeiras peças de código que você deve estar digitando
a classe começa "BindableProperty public static readonly" - talvez a sequência mais característica de quatro palavras em toda a
programação Xamarin.Forms.

Definindo Bindable Properties


Suponha que você gostaria de uma classe Label avançada que permite que você especifique o tamanho das fontes em unidades de
pontos. Vamos chamar essa classe AltLabel de "Label alternativa." Ela deriva de Label e inclui uma nova propriedade denominada
PointSize.
PointSize deve ser apoiada por uma propriedade bindable? Claro! (Embora as vantagens reais de fazê-la não irá ser demonstrada até
os próximos capítulos.)
O único código da classe AltLabel está incluído na biblioteca Xamarin.FormsBook.Toolkit por isso é acessível a múltiplas aplicações.
A nova propriedade PointSize é implementada com um objeto erty BindableProp - chamado PointSizeProperty e uma propriedade CLR
chamada PointSize que referencia PointSizeProperty:

public class AltLabel : Label


{
public static readonly BindableProperty PointSizeProperty … ;

public double PointSize
{
set { SetValue(PointSizeProperty, value); }
get { return (double)GetValue(PointSizeProperty); }
}

}

Ambas as definições de propriedade devem ser públicas.


Porque PointSizeProperty é definido como estática e somente leitura, ele deve ser atribuído quer em um construtor estático ou para a
direita definição de campo, após o que não pode ser alterado. Geralmente, um objeto BindableProperty é atribuído na definição de campo
utilizando o método BindableProperty.Create estática. Quatro argumentos são necessários (mostrado aqui com os nomes de
argumentos):

• propertyName — O nome de texto da propriedade (neste caso "PointSize")


• returnType — O tipo da propriedade (um duplo neste exemplo)
• declaringType — O tipo da classe que define a propriedade (AltLabel)
• defaultValue — Um valor padrão (digamos 8 pontos)
Geralmente, o segundo e terceiro argumentos são definidos com expressões typeof. Aqui está a atribuição indicação com estes quatro
argumentos passados para BindableProperty.Create:

public class AltLabel : Label


{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType typeof(AltLabel),
// declaringType
8.0, // defaultValue
…);

}

Observe que o valor padrão é especificado como 8,0, em vez de apenas 8. Porque BindableProperty.Create é projetado para lidar com
propriedades de qualquer tipo, o parâmetro é definido como defaultValue objeto. Quando o compilador C# encontra apenas um conjunto
de 8 como sendo este argumento, ele assumirá que o 8 é um int e passa um int para o método. O problema não será revelado até a
execução, no entanto, quando o método BindableProperty.Create estará esperando o valor padrão para ser do tipo duplo e responder
levantando um TypeInitializationException.
Você deve ser explícito sobre o tipo do valor que você está especificando como padrão. Não fazê-lo é um erro muito comum na definição
de propriedades vinculáveis.
BindableProperty.Create também tem seis argumentos opcionais. Aqui estão eles com os nomes de argumentos e sua finalidade:

• defaultBindingMode — usado em ligação com a ligação de dados


• validateValue — um retorno de chamada para verificar se há um valor válido
• propertyChanged — uma chamada de retorno para indicar quando a propriedade foi alterada
• propertyChanging — uma chamada de retorno para indicar quando a propriedade está prestes a mudar
• coerceValue — um retorno de chamada para coagir um valor definido para outro valor (por exemplo, para restringir os valores
para um intervalo)
• defaultValueCreator — um retorno de chamada para criar um valor padrão. Esta função é geralmente utilizada para
instanciar um objeto padrão que não pode ser compartilhado entre todas as instâncias da classe, por exemplo, um objeto de
coleta, tais como a lista ou dicionário.

Não execute qualquer validação, coerção ou de propriedade alterado manipulação na propriedade CLR. A propriedade CLR deve ser
restrita a chamadas SetValue e GetValue. Tudo o resto deve ser feito nos retornos proporcionados pela infraestrutura de propriedade
bindable.
É muito raro que uma chamada especial para BindableProperty.Create precisaria de todos estes argumentos opcionais. Por essa razão,
esses argumentos opcionais são comumente indicados com o recurso argumento nomeado introduzido em C#4.0. Para especificar um
argumento opcional em particular, use o nome do argumento seguido por dois pontos. Por exemplo:

public class AltLabel : Label


{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType typeof(AltLabel),
// declaringType
8.0, // defaultValue
propertyChanged: OnPointSizeChanged);

}

Sem dúvida, propertyChanged é o mais importante dos argumentos opcionais porque a classe usa esse retorno de chamada para ser
notificado quando as alterações de propriedade, seja diretamente de uma chamada para SetValue ou através da propriedade CLR.
Neste exemplo, o manipulador de propriedade alterada é chamado OnPointSizeChanged. Ele será chamado somente quando a
propriedade realmente muda, e não quando é simplesmente definido para o mesmo valor. No entanto, porque OnPointSizeChanged é
referenciado a partir de um campo estático, o próprio método deve também ser estático. Aqui está o que parece:
public class AltLabel : Label
{

static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{

}

}
Isto parece um pouco estranho. Poderíamos ter várias instâncias AltLabel em um programa, ainda, sempre que a propriedade Pointsize
altera em qualquer um desses casos, esse mesmo método estático é chamado. Como o método de saber exactamente qual instância
AltLabel mudou?
O método sabe porque é sempre o primeiro argumento. Esse primeiro argumento é, na verdade, do tipo AltLabel, e indica que a
propriedade de AltLabel instância mudou. Isso significa que você pode lançar com segurança o primeiro argumento para uma instância
AltLabel:

static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)


{
AltLabel altLabel = (AltLabel)bindable;

}

Você pode fazer referência alguma coisa no caso particular de AltLabel cuja propriedade foi alterada. Os segundo e terceiro argumentos
são realmente do tipo double para este exemplo, e indicar o prévio valor e o novo valor.
Muitas vezes é conveniente para este método estático para chamar um método de instância com os argumentos convertidos com seus
tipos reais:

public class AltLabel : Label


{

static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
((AltLabel)bindable).OnPointSizeChanged((double)oldValue, (double)newValue);
}

void OnPointSizeChanged(double oldValue, double newValue)


{

}
}

O método de instância pode então fazer uso de quaisquer propriedades de instância ou métodos da classe base subjacente, tal como
faria normalmente.
Para esta classe, este método OnPointSizeChanged precisa definir a propriedade FontSize com base em o novo tamanho de ponto e
um fator de conversão depende do dispositivo. Além disso, o construtor tem de iniciar o funcionamento da propriedade
TamanhoDoTipoDeLetra com base no valor PointSize padrão. Isto é feito através de um método simples SetLabelFontSize. Aqui é a
classe final completo, que utiliza as resoluções dependentes da plataforma discutidos no Capítulo 5, "Lidar com tamanhos":

public class AltLabel : Label


{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType typeof(AltLabel),
// declaringType
8.0, // defaultValue
propertyChanged: OnPointSizeChanged);

public AltLabel()
{
SetLabelFontSize((double)PointSizeProperty.DefaultValue);
}

public double PointSize


{
set { SetValue(PointSizeProperty, value); }
get { return (double)GetValue(PointSizeProperty); }
}

static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)


{
((AltLabel)bindable).OnPointSizeChanged((double)oldValue, (double)newValue);
}
void OnPointSizeChanged(double oldValue, double newValue)
{
SetLabelFontSize(newValue);
}

void SetLabelFontSize(double pointSize)


{
FontSize = Device.OnPlatform(160, 160, 240) * pointSize / 72;
}
}

Também é possível para a propriedade instância OnPointSizeChanged para acessar a propriedade PointSize diretamente em vez de
usar newValue. No momento em que o manipulador de propriedade alterada é chamado, o valor da propriedade subjacente já foi alterada.
No entanto, você não tem acesso direto a esse valor subjacente, como você faz quando um campo privado apoia uma propriedade CLR.
Esse valor subjacente é privado para BindableObject e só são acessíveis por meio da chamada GetValue.
Claro, nada impede que o código que está usando AltLabel de definir a propriedade FontSize e substituindo a configuração PointSize,
mas vamos esperar que tal código é consciente disso. Aqui está algum código que é um programa chamado PointSizedText que usa
AltLabel para exibir tamanhos em pontos de 4 a 12:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="PointSizedText.PointSizedTextPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="5, 20, 0,
0" Android="5, 0, 0, 0" WinPhone="5, 0, 0,
0" />
</ContentPage.Padding>

<StackLayout x:Name="stackLayout">
<toolkit:AltLabel Text="Text of 4 points" PointSize="4" />
<toolkit:AltLabel Text="Text of 5 points" PointSize="5" />
<toolkit:AltLabel Text="Text of 6 points" PointSize="6" />
<toolkit:AltLabel Text="Text of 7 points" PointSize="7" />
<toolkit:AltLabel Text="Text of 8 points" PointSize="8" />
<toolkit:AltLabel Text="Text of 9 points" PointSize="9" />
<toolkit:AltLabel Text="Text of 10 points" PointSize="10" />
<toolkit:AltLabel Text="Text of 11 points" PointSize="11" />
<toolkit:AltLabel Text="Text of 12 points" PointSize="12" />
</StackLayout>
</ContentPage>

E aqui estão os screenshots:


Criando métodos genéricos
Vários problemas podem surgir quando se define um objeto BindableProperty. Você pode cometer erros de ortografia do texto capitulação
do nome da propriedade, ou você pode especificar um valor padrão que não é do mesmo tipo que a propriedade.
Pode eliminar estes dois problemas com uma forma genérica alternativa do método BindableProperty.Create. Os dois argumentos
genéricos são o tipo da classe que define a propriedade e o tipo da própria propriedade. Com esta informação, o método pode derivar
algumas das propriedades padrão e fornecer tipos de argumentos de valor padrão e os métodos de retorno de chamada. Além disso, o
primeiro argumento para este método genérico alternativo deve ser um objeto LINQ Expressão referenciando a propriedade CLR. Isso
permite que o método para obter a sequência de texto da propriedade.
A seguinte classe também AltLabelGeneric na biblioteca Xamarin.FormsBook.Toolkit, mas não fornecendo funcionalidade adicional
sobre AltLabel -demonstra essa técnica, e, além disso usa uma função lambda para o retorno de chamada de propriedade alterado:

public class AltLabelGeneric : Label


{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create<AltLabelGeneric, double>
(label => label.PointSize,
8,
propertyChanged: (bindable, oldValue, newValue) =>
{

}); ((AltLabelGeneric)bindable).SetLabelFontSize(newValue

public AltLabelGeneric()
{
SetLabelFontSize((double)PointSizeProperty.DefaultValue);
}

public double PointSize


{
set { SetValue(PointSizeProperty, value); }
get { return (double)GetValue(PointSizeProperty); }
}

void SetLabelFontSize(double pointSize)


{
FontSize = Device.OnPlatform(160, 160, 240) * pointSize / 72;
}
}
O primeiro argumento para a forma genérica do método BindableProperty.Create é um objeto expressão referenciando a propriedade
PointSize:
label => label.PointSize

O nome do objeto pode realmente ser muito curto:


l => l.PointSize

O método Create usa a reflexão sobre esse objeto Expressão para obter o nome do texto do CLR propriedade, que é "PointSize".
Observe que o valor padrão é especificado como 8, em vez do 8.0. Na versão genérica do método Binda- bleProperty.Create, este
argumento é do mesmo tipo que o segundo argumento genérico, portanto, o simples 8 será convertido para um casal durante a
compilação. Além disso, os argumentos OldValue e NewValue para o manipulador de propriedade alterado são do tipo casal e não tem
que ser convertido.
Este método BindableProperty.Create genérico ajuda o seu código à prova de balas, mas não fornece nenhuma funcionalidade adicional.
Interno a Xamarin.Forms, é convertido para o método BindableProperty.Create padrão.

The Read-Only Bindable Property

Suponha que você está trabalhando com uma aplicação em que é conveniente parágrafo saber o número de palavras o texto que é
apresentado por um elemento label. Talvez rápido você gostaria de construir esse direito facilidade em uma classe que deriva de Label.
Vamos Chamar esta nova classe CountedLabel .
Até agora, o seu primeiro pensamento deve ser o de definir um objeto chamado bindableproperty verbo- countproperty e um problema
clr correspondente chamado wordcount.
Mas espere: ela só faz sentido para esta propriedade wordcount um ser definido de dentro da classe countedlabel. isso significa que a
propriedade wordcount clr não deve ter um conjunto assessor público. Deve ser definidos desta forma:

public int WordCount


{
private set { SetValue(WordCountProperty, value); }
get { return (double)GetValue(WordCountProperty); }
}

O assessor get ainda é público, mas o assessor set é particular. Isso é suficiente?
Não exatamente. Apesar do assessor set privado na propriedade CLR, o código externo para CountedLabel ainda pode chamar SetValue
com o CountedLabel.WordCountProperty propriedade bindable objeto. Esse tipo de configuração de propriedade deve ser proibido
também. Mas como isso pode trabalho se o objeto WordCountProperty é público?
A solução é fazer uma propriedade vinculável somente leitura usando o método BindableProperty.CreateReadOnly. (Como Criar,
CreateReadOnly também existe em uma forma genérica.) A própria API Xamarin.Forms define várias propriedades somente leitura
bindable, por exemplo, as propriedades Largura e Altura definida por VisualElement.
Veja como você pode fazer uma de sua preferência:
O primeiro passo é chamar BindableProperty.CreateReadOnly com os mesmos argumentos que vinculativo ableProperty.Create. No
entanto, o método CreateReadOnly devolve um objeto de BindablePropertyKey em vez de BindableProperty. Definir este objeto como
estático e readonly como o BindableProperty, mas fazê-lo ser privado para a classe:

public class CountedLabel : Label


{
static readonly BindablePropertyKey WordCountKey =
BindableProperty.CreateReadOnly("WordCount", // propertyName
typeof(int), // returnType
typeof(CountedLabel), // declaringType
0); // defaultValue

}

Não pense deste objeto BindablePropertyKey como uma chave de criptografia ou qualquer coisa assim. É muito mais simples, na
verdade, apenas um objeto que é privado para a classe.
O segundo passo é fazer com que um objeto BindableProperty pública usando o BindableProperty propriedade do BindablePropertyKey:

public class CountedLabel : Label


{

public static readonly BindableProperty WordCountProperty = WordCountKey.BindableProperty;

}
Este objeto BindableProperty é público, mas é um tipo especial de BindableProperty: Ele não pode ser usado em uma chamada SetValue.
Tentar fazer isso irá gerar um InvalidOperationException.
No entanto, há uma sobrecarga do método SetValue que aceita um objeto BindablePropertyKey. O acessor set CLR pode chamar
SetValue usando este objeto, mas este conjunto de assessor deve ser privado para impedir a propriedade de ser definidas fora da classe:

public class CountedLabel : Label


{

public int WordCount
{
private set { SetValue(WordCountKey, value); }
get { return (int)GetValue(WordCountProperty); }
}

}

A propriedade WordCount agora pode ser definida a partir da classe CountedLabel, mas quando? Esta classe CountedLabel deriva de
label, mas precisa para detectar quando a propriedade de texto foi alterada para que ele possa contar as palavras.
Será que label tem um evento TextChanged? Não, não faz. No entanto, bindableobject implementa a interface INotifyPropertyChanged.
Esta é uma interface .NET muito importante, particularmente para utilização de aplicações que implementam a arquitetura Model-View-
ViewModel. Mais adiante neste livro que você vai ver como usá-lo em suas próprias classes de dados.
A interface INotifyPropertyChanged é definido no namespace System.ComponentModel assim:

public interface INotifyPropertyChanged


{
event PropertyChangedEventHandler PropertyChanged;
}

Cada classe que deriva de bindableobject dispara automaticamente este evento PropertyChanged sempre que qualquer propriedade
Apoiado por uma BindableProperty alterações. O objeto PropertyChangedEventArgs que acompanha este evento inclui uma propriedade
do tipo string chamada PropertyName que identifica a propriedade que mudou.
Então, tudo que é necessário é para CountedLabel para anexar um manipulador para o evento PropertyChanged e verificar se há um
nome de propriedade de "Texto". De lá, ele pode usar qualquer técnica que ele quer para calculando uma contagem de palavras. A classe
CountedLabel completo usa uma função lambda sobre o evento Changed propriedade. O manipulador chama de Split para quebrar a
sequência de caracteres em palavras e ver quantos pedaços resultado. O método Split divide o texto com base em espaços, traços e os
travessões (Unicode \ u2014)

public class CountedLabel : Label


{
static readonly BindablePropertyKey WordCountKey =
BindableProperty.CreateReadOnly("WordCount", // propertyName
typeof(int), // returnType
typeof(CountedLabel), // declaringType
0); // defaultValue
public static readonly BindableProperty WordCountProperty = WordCountKey.BindableProperty;

public CountedLabel()
{
// Set the WordCount property when the Text property changes. PropertyChanged +=
(object sender, PropertyChangedEventArgs args) =>
{
if (args.PropertyName == "Text")
{
if (String.IsNullOrEmpty(Text))
{
WordCount = 0;

}
else
{

WordCount = Text.Split(' ', '-', '\u2014').Length;


}
}
};
}
public int WordCount
{
private set { SetValue(WordCountKey, value); }
get { return (int)GetValue(WordCountProperty); }
}
}
A classe inclui uma diretiva using para o namespace System.ComponentModel para o argumento propriedade- ChangedEventArgs para
o manipulador. Watch Out: Xamarin.Forms define uma classe chamada PropertyChangingEventArgs (tempo presente). Isso não é o que
você quer para o manipulador PropertyChanged. Você quer PropertyChangedEventArgs (passado).
Porque esta chamada do método Split divide o texto em caracteres em branco, traços e os travessões, você pode supor que CountedLabel
será demonstrado com texto que contem alguns traços e os travessões. Isto é verdade. O programa BaskervillesCount é uma variação
do programa Baskervilles do Capítulo 3, exceto que o parágrafo de texto é exibido com um CountedLabel e uma etiqueta regular é incluído
para exibir a contagem de palavras:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="BaskervillesCount.BaskervillesCountPage" Padding="5, 0">

<StackLayout>
<toolkit:CountedLabel x:Name="countedLabel"
VerticalOptions="CenterAndExpand" Text=
"Mr. Sherlock Holmes, who was usually very late in the mornings,
save upon those not infrequent

occasions when he was up all night, was seated at the breakfast


table. I stood upon the hearth-rug and picked up the stick which our
visitor had left behind him the night before. It was a fine, thick
piece of wood, bulbous-headed, of the sort which
is known as a &#x201C;Penang lawyer.&#x201D; Just
under the head was a broad silver band, nearly an inch across,
&#x201C;To James Mortimer, M.R.C.S., from his friends of the
C.C.H.,&#x201D; was engraved upon it, with the date
&#x201C;1884.&#x201D; It was just such a stick as the old-fashioned
family practitioner used to carry&#x2014;dignified, solid, and
reassuring." />

<Label x:Name="wordCountLabel" Text="???"


FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

</StackLayout>
</ContentPage>

Esse regular Label é definido no arquivo code-behind:

public partial class BaskervillesCountPage : ContentPage


{
public BaskervillesCountPage()
{
InitializeComponent();

int wordCount = countedLabel.WordCount;


wordCountLabel.Text = wordCount + " words";
}
}
A contagem de palavras que ele calcula baseia-se no pressuposto de que todos os hifens no texto separadas duas palavras, devem ser
contadas como duas palavras cada. Isso não é sempre verdade, claro, mas a contagem de palavras não é tão simples como
algoritmicamente este código pode implicar:

Como é que o programa será estruturado se o texto alterado dinamicamente enquanto o programa estava em execução? Nesse caso,
seria necessário atualizar a contagem de palavras sempre que a propriedade do objeto WordCount CountedLabel for alterada. Você pode
anexar um manipulador PropertyChanged no objeto incontáveis edLabel e verificar a propriedade chamada "WordCount".
No entanto, tenha cuidado se você tentar definir como um manipulador de eventos a partir de XAML: Esse manipulador será acionado
quando a propriedade Text é definida pelo analisador XAML, mas o manipulador de eventos no arquivo code-behind não terá acesso a
um objeto Label para exibir uma contagem de palavras, porque esse campo wordCountLabel ainda será definido como nulo. Esta é uma
questão que vai aparecer novamente ao trabalhar com controles interativos no Capítulo 15, mas praticamente resolvido quando se trabalha
com ligação no Capítulo 16 dados.
Há uma outra variação da propriedade bindable chegando no Capítulo 14: Este é o estabelecimento capaz anexado vinculativo e é muito
útil na implementação de certos tipos de layouts.
Enquanto isso, vamos olhar para uma das aplicações mais importantes de propriedades vinculáveis: Estilos.
Capítulo 12
Estilos
Aplicações "Xamarin.Forms" geralmente contém múltiplos elementos com configurações de propriedades idênticas. Por exemplo, você
pode ter diversos botões com as mesmas cores, tamanhos de fonte, e opções de "layout". No código, você pode designar propriedades
idênticas para múltiplos botões em um "loop", porém "loops" não estão disponíveis em XAML. Se você quiser evitar muita remarcação
repetitiva, outra solução é necessária;
A solução é a classe "Style", que é uma coleção de configurações de propriedade consolidadas em um objeto conveniente. Você pode
definir um objeto "Style" para a propriedade "Style" de qualquer classe que deriva de "VisualElement".
Geralmente, você vai aplicar o mesmo objeto Style para vários elementos, eo estilo é compartilhado entre estes elementos.
O estilo é a principal ferramenta para dar uma aparência consistente aos elementos visuais em suas aplicações “Xamarin.Forms". Estilos
ajudam a reduzir marcação repetitiva em arquivos XAML e permite que aplicações sejam mais facilmente alteradas e mantidas.
Estilos foram projetados principalmente com "XAML" em mente, e eles provavelmente não teriam sido inventados em um ambiente
somente de código. No entanto, você verá neste capítulo como definir e usar estilos em código e como combinar o código e a formatação
para alterar o estilo do programa dinamicamente em tempo de execução.

Estilo básico
No capítulo 10, "extensões de marcação XAML", você viu um trio de botões que continha uma grande quantidade de marcação idêntica.
Aqui estão eles novamente:

Com exceção da propriedade de texto, todos os três botões têm as mesmas configurações de propriedade. Uma solução parcial para essa
marcação repetitiva envolve a definição de valores de propriedade de um recurso dicionário e referenciá-los com a extensão de marcação
"StaticResource". Como você viu no projeto "ResourceSharing" no Capítulo 10, esta técnica não reduz o volume de marcação, mas ela
faz consolidar os valores em um só lugar. Para reduzir o volume de marcação, você vai precisar de um estilo. Um objeto de estilo é quase
sempre definido em um "ResourceDictionary". Geralmente, você vai começar com uma seção de recursos na parte superior da página:
Instancie o estilo com tags de inicio e fim separados

Porque o estilo é um objeto em um "ResourceDictionary", você precisará de um atributo "x: Key" para dar-lhe uma chave de dicionário
descritiva. Você também deve definir a propriedade "TargetType". Este é o tipo de elemento visual para qual o estilo é projetado, que
neste caso é "Button".
Como você verá na próxima seção deste capítulo, você também pode definir um estilo no código. No código, o construtor do estilo requer
um objeto do tipo "Type" para a propriedade "TargetType". A propriedade "TargetType" não tem um método "set" público para acesso;
portanto, a propriedade "Targettype" não pode ser alterada após o estilo ser criado.
"Style" também define outra propriedade disponível somente por método "get" importante chamada "Setters" do tipo IList < Setter>, que é
uma coleção de objetos "Setter". Cada Setter é responsável por definir a configuração da propriedade no estilo. A classe "Setter" define
apenas duas propriedades:
• "Property" do tipo "BindableProperty".
• "Value" of type "Object".

As propriedades definidas no estilo devem ser apoiadas por propriedades vinculáveis! Mas quando você definir a propriedade
em XAML, não use a propriedade "name". Apenas especifique o texto, que é o mesmo que o nome da propriedade CLR relacionada. Aqui
está um exemplo:

O "XAML parser" usa as classes TypeConverter familiares ao analisar as configurações de valor destas instâncias "Setter", para que você
possa usar as mesmas configurações de propriedade que você usa normalmente. "Setters" é a propriedade de conteúdo de estilo, assim
você não precisa das tags Style.Setters para adicionar objetos "Setter" ao "Style".
O passo final é definir esse objeto de estilo para a propriedade estilo de cada botão. Use a extensão familiar de marcação
"StaticResource" para referenciar a chave dicionário. Aqui está o arquivo XAML completo no projeto "BasicStyle":

Agora todas essas configurações de propriedade estão em um objeto "Style" que é compartilhado entre múltiplos elementos "Button".
Os efeitos visuais são os mesmos que aqueles do programa "ResourceSharing" no capítulo 10, mas a marcação é muito mais concisa.
Supomos que você quisesse definir um "Setter" para o "TextColor" usando o método estático Color.FromHsla. Você pode definir uma cor
usando o atributo "x: FactoryMethod", mas como você pode possivelmente definir um pedaço tão pesado de marcação para a propriedade
"Value" do objeto Setter? Você pode usar a sintaxe "property-element", é claro!

Sim, você pode expressar a propriedade "Value" do "Setter" com "property-element tags" e, em seguida, definir o conteúdo para qualquer
objeto do tipo "Color". Aqui está outra maneira de fazê-lo: Definir o valor da cor como um item separado no dicionário de recursos e então
usar "StaticResource" para configurá-lo para a propriedade "Value" do "Setter":
Esta é uma boa técnica se você está compartilhando a mesma cor entre vários estilos ou múltiplos "Setters". Você pode sobrescrever a
propriedade "setting" de um estilo definindo com uma propriedade diretamente no elemento visual. Note que o segundo botão tem sua
propriedade TextColor definida como marrom:

O botão central terá texto marrom, enquanto os outros dois botões terão suas configurações "TextColor" vindas do estilo. Uma propriedade
definida diretamente no elemento visual é chamado às vezes de configuração local ou configuração manual, e ela sempre sobrescreve a
propriedade configurada pelo estilo. O objeto "Style" no programa "BasicStyle" é compartilhado entre os três botões. O compartilhamento
de estilos tem uma aplicação importante para os objetos "Setter". Qualquer objeto definido para a propriedade "Value" de um "Setter" deve
ser compartilhável. Não tente fazer algo parecido com isto:

Este "XAML" não funciona por duas razões: Conteúdo não é apoiado por uma "BindableProperty" e portanto não pode ser utilizado em um
"Setter". Mas a intenção óbvia aqui é para cada "Frame", ou pelo menos para cada estrutura em que este estilo é aplicado, obter esse
mesmo objeto "Label" como conteúdo. Um objeto "Label" único não pode aparecer em vários lugares na página. Uma maneira muito
melhor de fazer algo parecido com isto é derivar uma classe de "Frame" e definir uma "Label" como a propriedade de conteúdo, ou derivar
uma classe de "ContentView" que inclui um "Frame" e um "Label".
Você pode querer usar um estilo para definir um manipulador de eventos para um evento como um "click". Isso seria útil e conveniente,
mas não é suportado. Manipuladores de eventos deve ser definidos nos próprios elementos. (No entanto, a classe "Style" faz objetos de
apoio chamados gatilhos, que podem responder a eventos ou mudanças de propriedade. Gatilhos serão discutidos em um capítulo futuro.)
Você não pode definir a propriedade "GestureRecognizers" em um estilo. Isso seria útil, mas "GestureRecognizers" não é apoiada por
uma propriedade de ligação.
Se uma propriedade de ligação é um tipo de referência, e se o valor padrão é nulo, você pode usar um estilo para definir a propriedade
para um objeto não nulo. Mas você pode também querer sobrescrever esta configuração de estilo com uma configuração local que defina
a propriedade de volta para nulo. Você pode definir uma propriedade como nula em "XAML" com a extensão de marcação "{ x: Null }".

Estilos no código
Embora os estilos são mais os mais definidos e usados em XAML, você deve saber como eles se parecem quando definidos e usados no
código. Aqui está a classe de página para o projeto "BasicStyleCode" somente de código. O construtor da classe "BasicStyleCodePage"
usa a sintaxe objeto de inicialização para imitar a sintaxe "XAML" na definição do objeto Estilo e aplicá-lo para três botões:
É muito mais evidente no código que em "XAML" a propriedade "property" do "Setter" é do tipo "BindableProperty".
Os dois primeiros objetos "Setter" neste exemplo são inicializados com os objetos "BindableProperties" nomeados
"View.HorizontalOptionsProperty" e "View.VerticalOptionsProperty". Você poderia usar "Button.HorizontalOptionsProperty" e
"Button.VerticalOptionsProperty" em vez disso porque "Button" herda essas propriedades de "View". Ou você poderia mudar o nome da
classe para qualquer outra classe que deriva de "View".
Como de costume, o uso de um "ResourceDictionary" no código parece inútil. Você poderia eliminar o dicionário e apenas atribuir os
objetos de estilo diretamente para as propriedades de estilo dos botões. Contudo, mesmo em código, o estilo é uma maneira conveniente
de agrupar todas as configurações de propriedade juntas em um pacote compacto.

Herança de estilo
O "TargetType" do estilo faz duas funções diferentes: Uma dessas funções é descrita em a próxima seção sobre estilos implícitos. A outra
função é para o benefício do analisador "XAML". O analisador "XAML" deve ser capaz de resolver os nomes de propriedades nos objetos
"Setter" e para isso ele precisa que seja fornecido o nome de uma classe pelo "TargetType".
Todas as propriedades no estilo devem ser definidas por ou herdadas pela classe especificada na propriedade "TargetType". O tipo do
elemento visual no qual o estilo é definido deve ser o mesmo "TargetType" ou uma classe derivada de "TargetType".
Se você precisar de um estilo apenas para propriedades definidas por "View", você pode definir o "TargetType" para "View" e ainda usar
o estilo nos botões ou qualquer outro derivado de "View", como nesta versão modificada do programa "BasicStyle":

Como você pode ver, o mesmo estilo é aplicado a todos os "Button" e "Label" filhos do "StackLayout" :

Mas supomos que agora você quer expandir este estilo, mas de forma diferente para "Button" e "Label". Isso é possível?
Sim. Os estilos podem derivar de outros estilos. A classe estilo inclui uma propriedade chamada "BasedOn" do tipo estilo. No código, você
pode definir essa propriedade "BasedOn" diretamente a outro objeto estilo. Em "XAML" você definir o atributo "BasedOn" para uma
extensão de marcação "StaticResource" que faz referência a um estilo criado anteriormente. O novo estilo pode incluir objetos "Setter"
para novas propriedades ou usá-las para substituir propriedades no estilo anterior. O estilo "BasedOn" deve ter como alvo a mesma classe
ou uma classe ancestral do novo estilo "TargetType".
Aqui está o arquivo XAML para um projeto chamado "StyleInheritance". O aplicativo faz uma referência para"Xamarin.FormsBook.Toolkit"
para duas finalidades: ele usa a extensão de marcação "HslColor" para demonstrar que as extensões de marcação são ajustes dos valores
legítimos em objetos "Setter" e para demonstrar que um estilo pode ser definido para uma classe customizada, neste caso "AltLabel".
O "ResourceDictionary" contém quatro estilos : O primeiro tem uma chave de dicionário de "VisualStyle". O estilo com a chave do dicionário
de "baseStyle" deriva de " VisualStyle". Os estilos com teclas de "labelStyle "e" ButtonStyle" derivam de "baseStyle ". O Estilo do Botão
demonstra como definir uma propriedade de valor de um "Setter" a um objeto "OnPlatform":
Imediatamente após a seção de recursos uma marcação define a propriedade estilo da página com o estilo "VisualStyle":

Como a página deriva de "VisualElement" mas de "View", este é o único estilo no recurso dicionário que pode ser aplicada para a página.
No entanto, o modelo não pode ser aplicado a página até depois da seção de recursos, portanto, usar o elemento "form" de
"StaticResource" é uma boa solução aqui. O fundo inteiro da página é colorido com base neste estilo, e o estilo também é herdado por
todos os outros estilos:
Se o estilo para o "AltLabel" só incluiu objetos "setter" para propriedades definidas por "Label", o "Targettype" poderia ser "Label" em vez
de "AltLabel". Mas o estilo tem um "Setter" para a propriedade "PointSize". Essa propriedade é definida por "AltLabel" de modo que o
"TargetType" deve ser "tookit:AltLabel".
Um "setter" pode ser definido para a propriedade "PointSize" porque "PointSize" é apoiado por uma propriedade "bindable". Se você muda
a acessibilidade do objeto "BindableProperty" em "AltLabel" de público para privado, a propriedade continuará a funcionar para muitos
usos rotineiros de "AltLabel", mas agora "PointSize" pode não ser um "Setter" de estilo. O analisador XAML se queixa de que ele não pode
encontrar "PointSizeProperty", que é a propriedade "bindable" que faz a propriedade "PointSize".
Você descobriu no capítulo 10 como "StaticResource" funciona: Quando o analisador "XAML" encontra uma extensão de marcação
"StaticResource" ele busca uma árvore visual para uma chave de dicionário. Este processo tem implicações para estilos. Você pode definir
um estilo em uma seção de recursos e em seguida, sobreescrever capítulo 12 estilos 252 com outro estilo com a mesma chave de
dicionário em uma seção de recursos diferente localizada mais abaixo na árvore visual. Quando você definir a propriedade "BasedOn"
para uma extensão de marcação "StaticResource", o estilo que você está derivando deve ser definido na mesma seção Recursos (como
demonstrado no programa "StyleInheritance" ) ou uma seção de recursos superior na árvore visual.
Isto significa que você pode estruturar seus estilos em "XAML" de duas maneiras hierárquicas: você pode usar "BasedOn" para derivar
estilos de outros estilos, e você pode definir estilos em diferentes níveis na árvore visual que derivam de estilos mais elevados na árvore
visual ou substituí-los por completo.
Para aplicações maiores, com várias páginas e muita marcação, a recomendação para a definição de estilos é muito simples: definir seus
estilos tão perto quanto possível dos elementos que usam esses estilos.
Aderir a estas recomendações para manter o programa é particularmente importante quando se trabalha com estilos implícitos.

Estilos Implícitos
Cada entrada em um "ResourceDictionary" requer uma chave de dicionário. Este é um fato indiscutível. Se você tentar passar uma chave
nula para o método "Add" de um objeto "ResourceDictionary", você vai disparar uma exceção "Argument-NullException".
No entanto, há um caso especial aonde um programador não é necessário para fornecer essa chave dicionário e essa chave é em vez
disso gerada automaticamente.
Este caso especial é para um objeto "Style" adicionado a um "ResourceDictionary" sem uma configuração "x: Key". O "ResourceDictionary"
gera uma chave com base no "Targettype", que é sempre necessária. (Uma breve exploração revelará que esta chave dicionário especial
é o nome completo associado com o "TargetType" do estilo. Para um TargetType Button, por exemplo, a chave de dicionário é
"Xamarin.Forms.Button". Mas você não precisa saber disso.)
Você também pode adicionar um estilo a um "ResourceDictionary" sem uma chave de dicionário em código: uma sobrecarga do método
"Add" aceita um argumento do tipo estilo, mas não exige qualquer outra coisa.
Um objeto de estilo em um "ResourceDictionary" que tem uma dessas chaves geradas é conhecido como um estilo implícito, e a chave
de dicionário gerada é muito especial. Você não pode se referir a essa chave usando diretamente "StaticResource". No entanto, se um
elemento dentro do âmbito da "ResourceDictionary" tem o mesmo tipo que a chave do dicionário, e se esse elemento não tiver sua
propriedade de estilo explicitamente definida para outro objeto "Style," então este estilo implícito é aplicado automaticamente.
O seguinte "XAML" do projeto "ImplicitStyle" demonstra isso. É o mesmo que o arquivo "XAML" "BasicStyle", exceto que o estilo não tem
configuração "x : Key" e as propriedades de estilo nos botões não são definidas usando "StaticResource":
Apesar da ausência de qualquer ligação explícita entre os botões e o estilo, o estilo é definitivamente aplicado:

Um estilo implícito é aplicado somente quando a classe do elemento coincide com o "TargetType" do estilo exatamente. Se você incluir
um elemento que deriva de "Button" no "StackLayout", não terá o estilo aplicado.
Você pode usar as configurações de propriedade locais para substituir propriedades definidas através do estilo implícito, assim como você
pode substituir configurações de propriedade em um estilo definido com "StaticResource".
Você vai achar estilos implícitos um recurso muito poderoso e extremamente útil. Sempre que você tem várias "Views" do mesmo tipo e
você determinar que você quer que todas elas tenham uma configuração de propriedade idêntica ou dois, é muito fácil de definir
rapidamente um estilo implícito. Você não tem que tocar os próprios elementos.
No entanto, com tanto poder pelo menos alguma responsabilidade o programador tem. Porque nenhum estilo é referência nos próprios
elementos, o que pode ser confuso quando examinado o "XAML" para determinar se alguns elementos são estilizados ou não. Por vezes,
a aparência de uma página indica que um estilo implícito é aplicado a alguns elementos, mas não é bastante óbvio onde o estilo implícito
é definido. Se você então quiser mudar esse estilo implícito, você tem que procurar manualmente por ele na árvore visual.
Por esta razão, você deve definir estilos implícitos tão perto quanto possível dos elementos aos quais eles são aplicados. Se as "Views"
que estão utilizando o estilo implícito estão em um determinado "StackLayout", em seguida, definia o estilo implícito na seção Recursos
neste "StackLayout". Um comentário ou dois poderiam ajudar a evitar a confusão também.
Curiosamente, estilos implícitos têm uma restrição interna que pode persuadi-lo a mantê-los perto para os elementos aos quais são
aplicados. Aqui está a restrição: Você pode derivar um estilo implícito de um estilo com uma chave de dicionário explícito, mas você não
pode fazer o caminho inverso. Você não pode usar "BasedOn" para fazer referência a um estilo implícito.
Se você definir uma cadeia de estilos que utilizam "BasedOn" para derivar um do outro, o estilo implícito ( se houver) estará sempre no
final da cadeia. Não será possível a existência de outras derivações.
Isto implica que você pode estruturar seus estilos com três tipos de hierarquias:
• A partir de estilos definidos sobre a Aplicação e "Page Down" para estilos definidos em "layouts" mais abaixo na
árvore visual.
• A partir de estilos definidos para classes de base, tais como "VisualElement" e "View" para estilos definidos para
classes específicas.
• A partir de estilos com chaves de dicionário explícitas para estilos implícitos.
Isto é demonstrado no projeto "StyleHierarchy", que utiliza um semelhante (mas de certa forma simplificado) conjunto de estilos como você
viu no início do projeto "StyleInheritance". No entanto, esses estilos estão agora espalhados em mais de três seções de recursos.
Usando uma técnica que você viu no programa "ResourceTrees" no capítulo 10 , o projeto "StyleHierarchy" recebeu uma classe "App"
baseada em "XAML". A classe "App.xaml" tem um "ResourceDictionary" contendo um estilo com apenas uma propriedade "Setter":

Em um aplicativo de várias páginas, este estilo seria usado em todo o aplicativo.


O arquivo "code-behind" da classe "App" chama o método InitializeComponent para processar o arquivo "XAML" e definir a propriedade
"MainPage":

O arquivo "XAML" da classe de página define um estilo para a página inteira que deriva do estilo na classe "App" e, em seguida, dois
estilos implícitos que derivam do estilo para a página. Observe que a propriedade de estilo da página está definida para o estilo definido
na classe "App":
Os estilos implícitos são definidos como próximos aos elementos de destino quando possível.
Aqui está o resultado:

O incentivo para separar objetos de estilo em dicionários separados não faz muito sentido para pequenos programas como este, mas para
programas maiores, torna-se tão importante ter uma hierarquia estruturada de definições de estilos quanto ter uma hierarquia estruturada
de definições de classe.
As vezes você vai ter um estilo com uma chave de dicionário explícita (por exemplo, "myButtonStyle"), mas você vai querer que o mesmo
estilo seja implícito também. Basta definir um estilo com base nessa chave com nenhuma tecla ou "setters" próprios:

Estilos dinâmicos
A classe de estilo não deriva de "BindableObject" e não responde internamente a mudanças em suas propriedades. Por exemplo, se você
atribuir um estilo e opor-se a um elemento, e em seguida, modificar um dos objetos "setter", dando-lhe um novo valor, o novo valor não
vai aparecer no elemento. Da mesma forma, o elemento de destino não vai mudar se você adicionar um "setter" ou remover um "setter"
da coleção "setters". Para estes novos "setters" de propriedade terem efeito, você precisa usar o código para destacar o estilo do elemento,
definindo a propriedade Estilo para nulo, e em seguida, anexar novamente o estilo para o elemento.
No entanto, sua aplicação pode responder ao estilo mudando dinamicamente em tempo de execução através da utilização de
"DynamicResource". Você deve se lembrar que "DynamicResource" é semelhante ao "StaticResource" aonde ele usa uma chave de
dicionário para buscar um objeto ou um valor de um dicionário de recurso. A diferença é que "StaticResource" é um dicionário de pesquisa
de uma só vez enquanto "DynamicResource" mantém uma ligação para a real chave de dicionário. Se a entrada do dicionário associada
com essa chave é substituída por um novo objeto, essa alteração será propagada para o elemento.
Esta facilidade permite que um aplicativo implemente um recurso chamado às vezes de estilos dinâmicos. Por exemplo, você pode incluir
uma instalação em seu programa para temas estilísticos (envolvendo fontes e cores talvez), e você pode fazer estes temas selecionáveis
pelo usuário. O aplicativo pode alternar entre estes temas porque eles são implementados com estilos.
Não há nada em um estilo que indique que é um estilo dinâmico. Um estilo torna-se dinamicamente único sendo referenciado usando
"DynamicResource" ao invés de StaticResource".

A seção recursos define quatro estilos: um estilo simples, com a tecla baseButtonStyle", e em seguida, três estilos que derivam daquele
estilo com as teclas "buttonStyle1", "buttonStyle2" , e "buttonStyle3".
No entanto, os quatro elementos do botão em direção a parte inferior do arquivo "XAML" usam "DynamicResource" para fazer
referência a um estilo com a chave mais simples "ButtonStyle". Onde está o estilo com essa chave? Isso não existe. No entanto, como as
quatro propriedades estilo são definidas com "DynamicResource", a falta da chave de dicionário não é um problema. Nenhuma exceção
é levantada. Mas nenhum estilo é aplicado, o que significa que os botões têm uma aparência padrão:

Cada um dos quatro elementos "Button" tem um manipulador "Click" em anexo, e no arquivo "code-behind", os primeiros três
manipuladores definem uma entrada do dicionário com a tecla "ButtonStyle" para um dos três estilos numerados já definidos no dicionário:

Quando você pressiona um dos três primeiros botões, todos os quatro obtém o estilo selecionado. Aqui está o programa
executando em todas as três plataformas que mostram os resultados (da esquerda para a direita) quando os botões 1, 2, e 3 são
pressionados:
Pressionando o quarto botão tudo retorna as condições iniciais, definindo o valor associado a a tecla "ButtonStyle" como nulo.
(Você também pode considerar chamar "Remove" ou "Clean" no objeto "ResourceDictionary" para remover a chave inteiramente, mas que
não funciona na versão "Xamarin.Forms" utilizada neste capítulo.)
Suponha que você queira derivar outro estilo do estilo com a tecla "ButtonStyle". Como você faz isso no XAML, considerando
que a entrada de dicionário "ButtonStyle" não existe até que um dos primeiros três botões é pressionado?
Você não pode fazer desta maneira:

"StaticResource" irá lançar uma exceção se a tecla "ButtonStyle" não existir, e mesmo se a chave existir, o uso de "StaticResource" não
vai permitir que mudanças na entrada do dicionário sejam refletidas neste novo estilo.
No entanto, alterar "StaticResource" para "DynamicResource" não vai funcionar:

"DynamicResource" funciona apenas com propriedades garantidas por propriedades vinculáveis , e que não é o caso aqui. Estilo não
deriva de "bindableobject", por isso não pode suportar propriedades vinculáveis.
Em vez disso, estilos definem uma propriedade especificamente para o fim de herdar estilos dinâmicos. A propriedade é a
"BaseResourceKey", que se destina a ser fixada diretamente a uma chave de dicionário que pode ainda não existir ou cujo valor pode
mudar dinamicamente, que é o caso com a tecla "ButtonStyle":

O uso de "BaseResourceKey" é demonstrado pelo projeto "DynamicStylesInheritance", que é muito semelhante ao projeto
"DynamicStyles". Na verdade, o processamento de código subjacente é idêntico. Na parte inferior da seção de recursos, um novo estilo é
definido com uma chave de "newButtonStyle" que usa "BaseResourceKey" para referenciar a entrada "ButtonStyle" e adicionar um par de
propriedades, incluindo um que utiliza "OnPlatform":
Repare que os primeiros três elementos "Button" referenciam a entrada de dicionário "newButtonStyle" com "StaticResource".
"DynamicResource" não é necessária aqui, porque o objeto de estilo associado ao "newButtonStyle" em si não vai mudar, exceto para o
estilo que deriva dele. O estilo com a tecla "newButtonStyle" mantém uma ligação com "ButtonStyle" e, internamente, altera-se quando
ocorrem mudanças de estilo. Quando o programa começa a ser executado, apenas as propriedades definidas na "newButtonStyle" são
aplicadas a esses três botões:

O botão "Reset" continua a fazer referência a entrada "ButtonStyle".


Como no programa "DynamicStyles", o arquivo "code-behind" define a entrada de dicionário quando você clica em um dos três
primeiros botões, para que todos os botões peguem as propriedades "ButtonStyle" também. Aqui estão os resultados para (da esquerda
para a direita) os cliques dos botões 3 , 2 e 1:

Estilos de dispositivo
"Xamarin.Forms" inclui seis estilos dinâmicos internos. Estes são conhecidos como estilos de dispositivos, e eles são membros de uma
classe aninhada de dispositivos chamado "Styles". Esta classe "Styles" define 12 campos estáticos e apenas leitura que ajudam a
referenciar essas seis estilos em código :
• "BodyStyle" do tipo "Style".
• "BodyStyleKey" do tipo "string" e igual a “BodyStyle”.
• "TitleStyle" do tipo "Style".
• "TitleStyleKey" do tipo "string" e igual a “TitleStyle”.
• "SubtitleStyle" do tipo "Style".
• "SubtitleStyleKey" do tipo "string" e igual a “SubtitleStyle”.
• "CaptionStyle" do tipo "Style".
• "CaptionStyleKey" do tipo "string" e igual a “CaptionStyle”.
• "ListItemTextStyle" do tipo "Style".
• "ListItemTextStyleKey" do tipo "string" e igual a “ListItemTextStyle”.
• "ListItemDetailTextStyle" do tipo "Style".
• "ListItemDetailTextStyleKey" do tipo "string" e igual a “ListItemDetailTextStyle”.
Todos os seis estilos têm um "TargetType" do tipo "Label" armazenados em um dicionário, mas não um dicionário aonde aplicações podem
acessar diretamente.
No código, você deve usar os campos nesta lista para acessar os estilos de dispositivo. Por exemplo, você pode definir o objeto
"Device.Styles.BodyStyle" diretamente na propriedade estilo de um "Label" para o texto que pode ser apropriado para o corpo de um
número. Se você estiver definindo um estilo no código que deriva de um desses estilos de dispositivo, defina o "BaseResourceKey" para
"Device.Styles.BodyStyleKey" ou simplesmente "BodyStyle" se você não está com medo de errar.
Em "XAML", você irá simplesmente usar a tecla texto "BodyStyle" com "DynamicResource" para configurar este estilo para a propriedade
estilo de um "Label" ou para definir "BaseResourceKey" ao configurar o estilo de "Device.Styles.BodyStyle".
O programa "DeviceStylesList" demonstra como acessar esses estilos e como definir um novo estilo que herda de "SubtitleStyle" tanto em
"XAML" como no código. Aqui está o arquivo XAML:
O "StackLayout" contém duas combinações de "Label" e "BoxView" ( uma na parte superior e um na inferior) para exibir cabeçalhos
sublinhados. Após o primeiro desses cabeçalhos, elementos "Label" referenciam os estilos de dispositivo com "DynamicResource". O novo
estilo de legenda é definido no dicionário de recursos para a página.
O arquivo "code-behind" acessa os estilos de dispositivo usando as propriedades na classe "Device.Styles" e cria um novo estilo derivando
de "SubtitleStyle":
O código e "XAML" resultam em estilos idênticos, é claro, mas cada plataforma implementa estes estilos de dispositivo de uma forma
diferente:

No IOS , o código subjacente implementa estes estilos principalmente com propriedades estáticas da classe "UIFont". No Android, a
implementação envolve principalmente propriedades "TextAppearance" da classe "Resource.Attribute". No Windows Phone, são recursos
de aplicativos com chaves começando com "PhoneText".
A natureza dinâmica destes estilos é mais facilmente demonstrada no iOS : enquanto o programa "DeviceStyles" está em execução, toque
no botão "Home" e execute a chamada das configurações ("Settings"). Escolha o item "General", em seguida, "Acessibility", e "Larger
Text". Um controle deslizante está disponível para fazer o texto menor ou maior. Mude esse controle, toque duas vezes no botão "Home"
para mostrar as aplicações atuais e selecione "DeviceStyles" novamente. Você vai ver o conjunto do texto a partir de estilos dispositivo
(ou os estilos que derivam de estilos de dispositivo) mudar o tamanho, mas nenhum dos textos sem estilo alteram de tamanho. Novos
objetos substituíram os estilos de dispositivo no dicionário.
A natureza dinâmica dos estilos de dispositivo não é tão óbvia no Android porque as alterações ao item de tamanho de fonte da seção de
exibição em configurações afetam todos os tamanhos de fonte em um programa "Xamarin.Forms". No Windows Phone, o item tamanho
de texto na sessão facilidade de acesso da sessão configurações só tem função nas aplicações que usam a API do "Windows Runtime".
"Xamarin.Forms" atualmente usa a API do "Silverlight".
O próximo capítulo inclui um programa que demonstra como fazer um pequeno leitor de livros eletrônicos que permite você ler um capítulo
de Alice no País das Maravilhas. Este programa usa estilos de dispositivo para controlar a formatação de todo o texto, incluindo o livro e
títulos de capítulos.
Mas o que este pequeno leitor de livros eletrônicos inclui também são ilustrações, e isso exige uma exploração em objetos de "bitmaps".
Capítulo 14 - Absolute Layout
No Xamarin.Forms, o conceito de disposição abrange todas as maneiras que várias interfaces podem ser apresentadas na tela. Abaixo é
apresentada a hierarquia com todas as classes que derivam de Layout
• System.Object
o BindableObject
▪ Element
• VisualElement
o View
▪ Layout
• ContentView
o Frame
• ScrollView
• Layout<T>
o AbsoluteLayout
o Grid
o RelativeLayout
o StackLayout

Você já viu o ContentView, Frame e ScrollView (todos possuem a propriedade Content que você pode definir para um elemento filho), e
também o StackLayout, que herda a propriedade Children de Layout<T> e exibe o elemento filho em uma pilha vertical ou horizontal. O
Grid e RelativeLayout implementam modelos de layout um pouco mais complexos, que serão explorados nos próximos capítulos. O recurso
AbsoluteLayout é o principal assunto desse capítulo.
Primeiramente, a classe AbsoluteLayout parece implementar um modelo de layout bastante primitivo, em que remete aos não tão bons
velhos tempos de interfaces gráficas, onde os programadores eram obrigados a dispor cada elemento na tela individualmente. No entanto,
você vai descobrir que o AbsoluteLayout incorpora um posicionamento proporcional, que ajuda a transformar esse layout antigo em um
modelo mais moderno.
Com o AbsoluteLayout, muitas das regras sobre layout que você aprendeu até agora não se aplicam: as propriedades HorizontalOptions e
VerticalOptions que são tão importantes quando uma View é filho de um ContentPage ou StackLayou não tem absolutamente nenhum
efeito quando a View é filha de um AbsoluteLayout. O programa em vez de atribuir a cada elemento filho de um AbsoluteLayout um local
especifico nas coordenadas do dispositivo. O elemento filho também pode ser atribuído a um tamanho especifico ou permitido ao tamanho
de si mesmo.
Você pode usar o AbsoluteLayout em seu código ou no XAML. Para o XAML, a classe faz uso do recurso suportado pelo BindableObject e
o BindableProperty. Essa é a propriedade que permite fazer o bind com um tipo especial de propriedade, que pode ser definido em uma
instancia da classe que não seja a classe que define a propriedade.

Absolute Layout no Código


Você pode adicionar um elemento filho a coleção (Children) de um AbsoluteLayout. O mesmo é permitido no StackLayout:
absoluteLayout.Children.Add(child);
Porém você também tem outras opções. O AbsoluteLayout redefine a propriedade Children para ser do tipo
AbsoluteLayout.IAbsoluteList<View>, o qual inclui dois métodos Add, que permite especificar a posição do elemento e opcionalmente seu
tamanho.
Para especificar a posição e o tamanho, você utilizará a classe Rectangle. Rectangle é uma estrutura, e você pode criar um Rectangle
utilizando o construtor que aceita as classes Point e Size.
Point point = new Point(x, y);
Size size = new Size(width, height);

Rectangle rect = new Rectangle(point, size);

Ou você pode passar os valores de x, y, width e height diretamente para o construtor do Rectangle.
Rectangle rect = new Rectangle(x, y, width, height);
Você pode utilizar o método alternativo Add para adicionar uma View para a coleção de elementos do AbsoluteLayout:
absoluteLayout.Children.Add(child, rect);
Os valores de x e y indicam a posição da borda superior esquerda do elemento relativo a borda superior esquerda do AbsoluteLayout
principal. Se você preferir que o elemento permaneça com seu tamanho, você pode apenas informar um valor para Point.
absoluteLayout.Children.Add(child, point);
Um pequeno exemplo:
public class AbsoluteDemoPage : ContentPage
{
public AbsoluteDemoPage()
{
AbsoluteLayout absoluteLayout = new AbsoluteLayout
{
Padding = new Thickness(50)
};

absoluteLayout.Children.Add(
new BoxView
{
Color = Color.Accent
},
new Rectangle(0, 10, 200, 5));

absoluteLayout.Children.Add(
new BoxView
{
Color = Color.Accent
},
new Rectangle(0, 20, 200, 5));

absoluteLayout.Children.Add(
new BoxView
{
Color = Color.Accent
},
new Rectangle(10, 0, 5, 65));

absoluteLayout.Children.Add(
new BoxView
{
Color = Color.Accent
},
new Rectangle(20, 0, 5, 65));

absoluteLayout.Children.Add(
new Label
{
Text = "Stylish Header", FontSize = 24
},
new Point(30, 25));

absoluteLayout.Children.Add(
new Label
{
FormattedText = new FormattedString
{
Spans =
{
new Span
{
Text = "Although the "
},
new Span
{
Text = "AbsoluteLayout",
FontAttributes = FontAttributes.Italic
},
new Span
{
Text = "Label",
FontAttributes = FontAttributes.Italic
}
}
}
},
new Point(0, 80));

this.Content = absoluteLayout;
}
}
Quatro elementos do tipo BoxView formam uma sobreposição de padrão no topo para remover o cabeçalho, e o parágrafo que o segue.
As posições e tamanhos dos elementos BoxView são configuradas enquanto posiciona os dois componentes de Label.

Um pouco de tentativa e erro foi necessário para obter os tamanhos dos quatro elementos de BoxView e o texto do cabeçalho para ser
aproximadamente o mesmo tamanho. Mas note que os elementos BoxView sobrepões: AbsoluteLayout permite sobrescrever as views de
uma maneira livre, onde não é possível com o StackLayout (ou sem o uso de transformações, que serão explicados em um capitulo
posterior).
A grande desvantagem do AbsoluteLayout é que você precisa posicionar as coordenadas manualmente ou calcular conforme o tempo de
execução. Tudo que não é explicitamente dimensionado (como os dois elementos de Label), irá ter um tamanho que é obtido até antes da
página ser exibida. Se você procura adicionar outro parágrafo após a segunda Label, quais coordenadas você deverá usar?
Atualmente você pode posicionar múltiplos parágrafos de texto utilizando o StackLayout (ou o StackLayout dentro de um ScrollView) em
um AbsoluteLayout e então inserir os labels nele. Layouts podem ser aninhados.
Como você pode perceber, utilizar AbsoluteLayout é mais difícil de utilizar do que o StackLayout. Geralmente é muito mais fácil utilizar
Xamarin.Forms e outra classe Layout para tratar itens mais complexos de layout. Mas para casos especiais o AbsoluteLayout é ideal.
Como todo elemento visual, o AbsoluteLayout possui as propriedades HorizontalOptions e VerticalOptions para configurar o Fill por padrão,
que significa que o AbsoluteLayout preenche o conteúdo. Com outras configurações, um AbsoluteLayout se ajusta para o tamanho do seu
conteúdo, mas há algumas exceções: Tentar colocar o AbsoluteLayout no programa de exemplo com uma cor de fundo para que você
possa ver exatamente o espaço que ocupa na tela. Ele normalmente preenche toda a página, mas se você definir as propriedades
HorizontalOptions e VerticalOptions para Center, você verá que o tamanho do AbsoluteLayout automaticamente calcula e preenche o
conteúdo, mas apenas uma linha do parágrafo de texto.
Descobrir tamanhos para elementos visuais em um AbsoluteLayout pode ser complicado. Uma abordagem simples é demonstrada pelo
programa ChessboardFixed abaixo. O nome do programa tem o sufixo Fixed pois a posição e o tamanho de todos os quadrados dentro
do tabuleiro de xadrez são definidas no construtor.
Observe que o AbsoluteLayout é centralizado e então irá ser o tamanho que acomode todos os seus elementos filhos. O tabuleiro em si é
dado com uma cor marrom, e em seguida os 32 elementos BoxView verde escuros são exibidos em todas as outras posições do tabuleiro:
public class ChessboardFixedPage : ContentPage
{
public ChessboardFixedPage()
{
const double squareSize = 35;
AbsoluteLayout absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Color.FromRgb(240, 220, 130), HorizontalOptions
= LayoutOptions.Center, VerticalOptions = LayoutOptions.Center
};

for (int row = 0; row < 8; row++)


{
for (int col = 0; col < 8; col++)
{
// Skip every other square. if (((row ^
col) & 1) == 0)
continue;
BoxView boxView = new BoxView
{
Color = Color.FromRgb(0, 64, 0)
};

Rectangle rect = new Rectangle(col * squareSize, row *


squareSize,
squareSize, squareSize);

absoluteLayout.Children.Add(boxView, rect);
}
}
this.Content = absoluteLayout;
}
}

Os cálculos sobre as variáveis de linhas e colunas provocam que uma BoxView seja criada, conforme resultado a seguir:

Incluindo propriedades Bindable


Se quiséssemos que este tabuleiro seja o maior quanto possível na tela, nós precisamos adicionar os elementos BoxView ao AbsoluteLayout
durante o evento SizeChanged para a página, ou o evento SizeChanged teria de encontrar alguma maneira de mudar a posição e o tamanho
do BoxView nos elementos filhos.
São possíveis ambas as opções, mas o segundo é preferido pois podemos preencher a coleção de elementos filhos do AbsoluteLayout
apenas uma vez no construtor do programa, e em seguida, ajustar os tamanhos e posições.
No primeiro encontro, a sintaxe para definir a posição e o tamanho de um elemento filho dentro de um AbsoluteLayout pode parecer um
pouco estranho. Se a view é um objeto do tipo View e o retângulo é um valor do tipo Rectangle, aqui está um exemplo de obter a localização
e tamanho do retângulo:
AbsoluteLayout.SetLayoutBounds(view, rect);
Essa não é uma instancia do AbsoluteLayout na chamada do método SetLayoutBounds, e sim um método da classe AbsoluteLayout. Você
chamar esse método antes ou depois de adicionar o elemento filho a coleção do AbsoluteLayout, pois é um método estático. Uma instancia
particular de um AbsoluteLayout não está envolvido com todos os elementos no método SetLayoutBounds.
Vamos observar outro código que faz uso desse misterioso método SetLayoutBounds e examinar como ele funciona.
O construtor da página do programa ChessBoardDynamic usa o simples método Add sem posicionar ou dimensionar para adicionar os 32
elementos BoxView no AbsoluteLayout em um loop. Para proporcionar uma margem ao redor do tabuleiro, o AbsoluteLayout é um
elemento filho de um ContentView e o espaçamento é configurado na página. O ContentView possui um evento SizeChanged para
posicionar e dimensionar o AbsoluteLayout baseado no tamanho do seu conteúdo.
public class ChessboardDynamicPage : ContentPage
{
AbsoluteLayout absoluteLayout;
public ChessboardDynamicPage()
{
absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Color.FromRgb(240, 220, 130), HorizontalOptions
= LayoutOptions.Center, VerticalOptions = LayoutOptions.Center
};

for (int i = 0; i < 32; i++)


{
BoxView boxView = new BoxView
{
Color = Color.FromRgb(0, 64, 0)
};
absoluteLayout.Children.Add(boxView);
}
ContentView contentView = new ContentView
{
Content = absoluteLayout
};
contentView.SizeChanged += OnContentViewSizeChanged;

this.Padding = new Thickness(5, Device.OnPlatform(25, 5, 5), 5, 5);


this.Content = contentView;
}

void OnContentViewSizeChanged(object sender, EventArgs args)


{
ContentView contentView = (ContentView)sender;
double squareSize = Math.Min(contentView.Width, contentView.Height) / 8;
int index = 0;

for (int row = 0; row < 8; row++)


{
for (int col = 0; col < 8; col++)
{
// Skip every other square. if (((row ^ col)
& 1) == 0)
continue;

View view = absoluteLayout.Children[index]; Rectangle rect = new


Rectangle(col * squareSize,
row * squareSize,
squareSize, squareSize);

AbsoluteLayout.SetLayoutBounds(view, rect);
index++;
}
}
}
}
O evento SizeChanged possui muito da mesma lógica que o construtor definido com o atributo Fixed, exceto se o elemento BoxView já
está na coleção de elementos filhos do AbsoluteLayout. Tudo que é necessário é a posição e o tamanho de cada BoxView quando o
tamanho do conteúdo muda. O loop conclui com a chamada do método estático SetLayoutBounds para cada BoxView que é calculado.
Agora o tabuleiro de xadrez é dimensionando para preencher a tela com uma pequena margem:

Obviamente, o método SetLayoutBounds funciona, mas como? O que isso faz? E como é que ele consegue fazer o que ele faz, sem fazer
referência a um objeto AbsoluteLayout?
A chamada do método SetLayoutBounds no evento SizeChanged é esse:
AbsoluteLayout.SetLayoutBounds(view, rect);
Essa chamada é exatamente equivalente a chamada a seguir:
view.SetValue(AbsoluteLayout.LayoutBoundsProperty, rect);
Essa é a chamada SetValue no elemento view. Essas duas chamadas de métodos são exatamente equivalentes pois o segundo é como
o AbsoluteLayout define internamente o método estático SetLayoutBounds.
Você deve se lembrar que o SetValue e GetValue são definidos por BindableObject e é usado para implementar propriedades bindable. A
julgar unicamente a partir do nome, AbsoluteLayout.LayoutBoundsProperty certamente parece ser um objeto BindableProperty, e
realmente é. No entanto, é um tipo muito especial de propriedade bindable chamado como propriedade bindable anexado.
Propriedades bindable normais podem ser definidos apenas em instâncias da classe que define a propriedade ou sobre as instancias de
uma classe derivada. Propriedades bindable anexadas quebram essa regra: São definidas por uma classe, neste caso AbsoluteLayout,
mas definido como em outro objeto, neste caso como um elemento filho de AbsoluteLayout. A propriedade as vezes é anexado ao elemento
filho, por isso o seu nome.
O elemento filho do AbsoluteLayout é ignorado com a finalidade de passar a propriedade bindable anexada para o método SetValue, e
esse elemento filho não faz uso desse valor na sua lógica interna. O método SetValue do elemento simplesmente salva o valor de
Rectangle em um dicionário mantido pelo BindableObject com o elemento, anexando o valor para ser usado possivelmente em algum
momento pelo objeto pai no AbsoluteLayout.
Quando o AbsoluteLayout está colocando seus elementos filhos, pode interrogar o valor dessa propriedade em cada elemento chamando
o método estático GetLayoutBounds, o qual chama o GetValue com a propriedade bindable. A chamada para GetValue busca o valor do
Rectangle no dicionário armazenado no elemento.
Você pode ser perguntar: Por que tal processo é necessário para definir o posicionamento e o dimensionamento em um elemento filho do
AbsoluteLayout? Não teria sido mais fácil para a View definir simplesmente o valor de X e Y?
Talvez, mas essas propriedades seriam adequadas apenas para o AbsoluteLayout. Ao usar um Grid, um aplicativo precisa especificar os
valores das linhas e colunas nos elementos filhos da Grid e quando usar uma classe de layout de sua própria invenção, talvez algumas
outras propriedades são necessárias. Propriedades bindable anexadas podem lidar com todos esses casos e muito mais.
Propriedades bindable anexadas são um mecanismo de uso geral que permite que as propriedades definidas por uma classe sejam
armazenadas em instancias de outras classes. Você pode definir suas próprias propriedades bindable anexadas utilizando métodos de
criação estáticos de um objeto BindableObject nomeado CreateAttached e CreateAttachedReadOnly.
Propriedades anexadas são utilizadas principalmente em classes de layout. Como você verá, a um Grid define propriedades bindable
anexadas para especificar as linhas e colunas de cada elemento, e o RelativeLayout também define propriedades bindable anexadas.
Anteriormente você viu métodos Add definidos pela coleção de elementos filhos de um AbsoluteLayout. Este são, na verdade,
implementados usando essas propriedades anexadas. A chamada:
absoluteLayout.Children.Add(view, rect);
é implementada como:
AbsoluteLayout.SetLayoutBounds(view, rect);
absoluteLayout.Children.Add(view);
A chamada ao método Add com apenas um argumento do tipo Point define a posição do elemento filho:
absoluteLayout.Children.Add(view, new Point(x, y));
Isso é implementado com o mesmo método estático SetLayoutBounds mas usando uma constante especial para a view:
AbsoluteLayout.SetLayoutBounds(view,
new Rectangle(x, y, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
absoluteLayout.Children.Add(view);
Você pose usar a constante AbsoluteLayout.AutoSize em seu próprio código.

Posicionamento e dimensionamento proporcional


Como você viu, o programa ChessboardDynamic ajusta os elementos BoxView com cálculos baseados no tamanho no AbsoluteLayout.
Em outras palavras, o tamanho e a posição de cada elemento são proporcionais ao tamanho do conteúdo. Curiosamente, esse é um caso
com um AbsoluteLayout, e que poderia ser bom se o AbsoluteLayout acomodasse algumas situações automaticamente.
Ele faz!
AbsoluteLayout define uma segunda propriedade anexada, chamado de LayoutFlagsProperty, e mais dois métodos estáticos, chamado
SetLayoutFlags e GetLayoutFlags. Configurando a propriedade anexada, permite que você especifique a posição ou tamanho do elemento,
que são proporcionais ao tamanho do AbsoluteLayout. Ao inserir os elementos filhos, o AbsoluteLayout escala essas coordenadas e
tamanhos de forma adequada.
Você escolhe como esse recurso funciona com um membro do enumerador AbsoluteLayoutFlags:
• None (igual a 0)
• XProportional (1)
• YProportional (2)
• PositionProportional (3)
• WidthProportional (4)
• HeightProportional (8)
• SizeProportional (12)
• All (\xFFFFFFFF)

Você pode definir uma posição e tamanho proporcional de um elemento do AbsoluteLayout usando os métodos estáticos:
AbsoluteLayout.SetLayoutBounds(view, rect); AbsoluteLayout.SetLayoutFlags(view,
AbsoluteLayoutFlags.All);

Ou você pode usar uma versão do método Add de um elemento que aceita um membro do enumerador AbsoluteLayoutFlags:
absoluteLayout.Children.Add(view, rect, AbsoluteLayoutFlags.All);
Por exemplo, se você usar o SizeProportional e definir a largura de um elemento para 0.25 e a altura para 0.10, o elemento irá ser um
quarto da largura do AbsoluteLayout e um décimo da altura.
A flag PositionProporcional é similar, mas ele assume que o tamanho do elemento em conta: A posição de (0, 0) põe o elemento na borda
esquerda superior, e a posição (1, 1) põe o elemento na borda inferior direita, e a posição de (0.5, 0.5) centraliza o elemento com o
AbsoluteLayout. Tomando o tamanho do elemento em conta é ótimo em algumas tarefas, como centralizar o elemento em um
AbsoluteLayout ou exibir ele nos cantos, mas é um pouco ruim é outras tarefas.
Aqui está um tabuleiro proporcional. A maior parte do trabalho de posicionamento foi movido novamente para o construtor. O evento
SizeChanged agora apenas mantém o aspecto global definindo as propriedades WidthRequest e HeightRequest do AbsoluteLayout ao
mínimo da largura e altura do ContentView.
public class ChessboardProportionalPage : ContentPage
{
AbsoluteLayout absoluteLayout;
public ChessboardProportionalPage()
{
absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Color.FromRgb(240, 220, 130),
HorizontalOptions = LayoutOptions.Center, VerticalOptions =
LayoutOptions.Center
};

for (int row = 0; row < 8; row++)


{
for (int col = 0; col < 8; col++)
{
// Skip every other square. if (((row ^ col)
& 1) == 0)
continue;

BoxView boxView = new BoxView


{
Color = Color.FromRgb(0, 64, 0)
};

Rectangle rect = new Rectangle(col / 7.0, // x row / 7.0, //


y
1 / 8.0, // width
1 / 8.0); // height

absoluteLayout.Children.Add(boxView, rect, AbsoluteLayoutFlags.All);


}
}

ContentView contentView = new ContentView


{
Content = absoluteLayout
};
contentView.SizeChanged += OnContentViewSizeChanged;
this.Padding = new Thickness(5, Device.OnPlatform(25, 5, 5), 5, 5);
this.Content = contentView;
}
void OnContentViewSizeChanged(object sender, EventArgs args)
{
ContentView contentView = (ContentView)sender;
double boardSize = Math.Min(contentView.Width, contentView.Height);
absoluteLayout.WidthRequest = boardSize; absoluteLayout.HeightRequest =
boardSize;
}
}

A tela se parece exatamente como o programa ChessboardDynamic.


Cada BoxView é adicionado ao AbsoluteLayout com o código a seguir. Todos os denominadores são valores de ponto flutuante, então os
resultados das divisões são convertidos para double.
Rectangle rect = new Rectangle(col / 7.0, row / 7.0, 1 / 8.0, 1/
8.0);

absoluteLayout.Children.Add(boxView, rect, AbsoluteLayoutFlags.All);


A largura e a altura são sempre iguais a um oitavo da largura e altura do AbsoluteLayout. Isso é muito claro. Mas as variáveis de linha e
coluna são divididas por 7 (em vez de 9) para mantar a relação das coordenadas x e y. As variáveis de linha e coluna no loop variam de 0
até 7. Os valores de linha e coluna de 0 correspondem a esquerda ou topo, mas a fileira de valores de coluna de 7 deve mapear as
coordenadas para x e y de 1 para a posição do elemento contra a borda direita ou inferior.
Se você acha que pode precisar de algumas regras solidas para derivar coordenadas proporcionais, leia:
Trabalhando com coordenadas proporcionais
Trabalhar com posicionamento proporcional em um AbsoluteLayout pode ser complicado. Às vezes você precisa compensar o cálculo interno
com o tamanho que deve levar em consideração. Por exemplo, você pode preferir especificar as coordenadas de moto que um valor de X
igual 1 significa que a borda esquerda do elemento seja posicionado na borda direita do AbsoluteLayout, e você precisa converter a
coordenada para que o AbsoluteLayout entenda.
Na discussão que se segue, uma coordenada não leva em conta o tamanho de coordenadas em que 1 significa que o elemento está
posicionado ao lado de fora da borda direita ou na borda inferior do AbsoluteLayout é ferido como uma coordenada fracionada. O objeto
desta seção é desenvolver regras para conversão de um fracionário de coordenadas para um proporcional de coordenadas que você pode
usar em um AbsoluteLayout. Está conversão requer que você sabe o tamanho do elemento.
Suponha que você está colocando um elemento em um AbsoluteLayout, com um limite de layout retangular. Vamos restringir essa análise
para coordenadas horizontais e tamanhos. O processo é o mesmo para as coordenadas e tamanhos verticais.
Esse elemento deve primeiro obter uma largura de alguma forma. O elemento pode calcular a sua própria largura, ou uma largura em
unidades independentes de dispositivo podem ser atribuídas a ele através do LayoutBounds. Mas suponhamos que a flag
AbsoluteLayoutFlags.WidthProportional esteja definido, o que significa que a largura é calcula com base no campo Width dos limites da
disposição e a largura do AbsoluteLayout.
𝑐ℎ𝑖𝑙𝑑.𝑊𝑖𝑑𝑡ℎ=𝑙𝑎𝑦𝑜𝑢𝑡𝐵𝑜𝑢𝑛𝑑𝑠.𝑊𝑖𝑑𝑡ℎ∗𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑒𝐿𝑎𝑦𝑜𝑢𝑡.𝑊𝑖𝑑𝑡ℎ
Se a flag AbsoluteLayoutFlags.XProportional também estiver definida, então internamente o AbsoluteLayout calcula as coordenadas para o
elemento relativo a si mesmo tomando o tamanho do elemento em conta:
𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑒𝐶ℎ𝑖𝑙𝑑𝐶𝑜𝑜𝑟𝑑𝑖𝑛𝑎𝑡𝑒.𝑋=(𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑒𝐿𝑎𝑦𝑜𝑢𝑡.𝑊𝑖𝑑𝑡ℎ−𝑐ℎ𝑖𝑙𝑑.𝑊𝑖𝑑𝑡ℎ)∗𝑙𝑎𝑦𝑜𝑢𝑡𝐵𝑜𝑢𝑛𝑑𝑠.𝑋
Por exemplo, se o AbsoluteLayout tive uma largura de 400, e o elemento possui uma largura de 100, e o LayoutBounds.X for 0.5, então o
relativeChildCoordinate.X é calculado como 150. Isso significa que a borda esquerda do elemento é 150 pixels da borda esquerda do
elemento pai. Isso faz com que o elemento seja horizontalmente centralizado com o AbsoluteLayout.
Também é possível calcular a coordenada fracionada do elemento:
𝑓𝑟𝑎𝑐𝑡𝑖𝑜𝑛𝑎𝑙𝐶ℎ𝑖𝑙𝑑𝐶𝑜𝑜𝑟𝑑𝑖𝑛𝑎𝑡𝑒.𝑋=𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑒𝐶ℎ𝑖𝑙𝑑𝐶𝑜𝑜𝑟𝑑𝑖𝑛𝑎𝑡𝑒.𝑋𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑒𝐿𝑎𝑦𝑜𝑢𝑡.𝑊𝑖𝑑𝑡ℎ \ abosluteLayout.Width
Isso não e o mesmo que a coordenada proporcional pois a coordenada fracionada do elemento de 1 significa que a borda esquerda do
elemento fica fora da extremidade direta do AbsoluteLayout, e portanto, o elemento está fora da superfície do AbsoluteLayout. Para
continuar o exemplo, a coordenada fracionada do elemento é 150 dividido por 400 ou 0.375. A esquerda do elemento é posicionada em
(0.375 *400) ou 150 unidades da borda esquerda do AbsoluteLayout.
Vamos reorganizar os termos da formula que calcula os limites do elemento em relação as coordenadas do elemento para resolver
layoutBounds.X
𝑙𝑎𝑦𝑜𝑢𝑡𝐵𝑜𝑢𝑛𝑑𝑠.𝑋= 𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑒𝐶ℎ𝑖𝑙𝑑𝐶𝑜𝑜𝑟𝑑𝑖𝑛𝑎𝑡𝑒.𝑋(𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑒𝐿𝑎𝑦𝑜𝑢𝑡.𝑊𝑖𝑑𝑡ℎ−cℎ𝑖𝑙𝑑.𝑊𝑖𝑑𝑡ℎ) \ (absoluteLayout.Width – child.Width)
E vamos dividir a parte superior e inferior pela largura do AbsoluteLayout:
𝑙𝑎𝑦𝑜𝑢𝑡𝐵𝑜𝑢𝑛𝑑𝑠.𝑋= fractionalChildCoordinate.X \ (1- (child.Width \ absoluteLayout.Width))
Se você também utiliza a largura proporcional, então essa relação no denominador é layoutBounds.Width:
layoutBounds.X = fractionalChildCoordinates.X \ ( 1- layoutBounds.Width)
E isso muitas vezes é uma formula muito útil, pois permite que você converta de uma coordenada fracionada para uma coordenada
proporcional, para ser usada em um retângulo com limites de layout.
No exemplo do ChessboardProportional, quando a coluna é igual a 7, o factionalChildCoordinate.X é 7 dividido pelo número de colunas (8),
ou 7 \8. O denominador é 1 menos 1\8 (a proporção da largura no retângulo), ou 7\8 novamente.
Vejamos um exemplo onde a formula é aplicada em um código para coordenadas fracionadas. O programa ProportionalCoordinateCalc
tenta reproduzir esta simples figura usando oito elementos BoxView azuis em um AbsoluteLayout rosa.
A figura possui um aspecto 2:1. Os pares de retângulos azuis na parte superior e inferior tem uma altura de 0.1 unidades fracionais (em
relação à altura do AbsoluteLayout), e estão espaçadas 0.1 unidades do topo e de fundo entre si. Os retângulos verticais azuis parecem
estar espaçados e dimensionados de modo semelhante, mas por causa da relação de aspecto (2:1), os retângulos verticais têm uma largura
de 0.05 unidades e são espaçados com 0.05 unidades a partir da esquerda e direita, entre si.
O AbsoluteLayout é definido e centralizado no arquivo XAML com o seguinte código:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ProportionalCoordinateCalc.ProportionalCoordinateCalcPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="5, 25, 5, 5"
Android="5"
WinPhone="5" />
</ContentPage.Padding>

<ContentView SizeChanged="OnContentViewSizeChanged">
<AbsoluteLayout x:Name="absoluteLayout"
BackgroundColor="Pink"
HorizontalOptions="Center"
VerticalOptions="Center" />
</ContentView>
</ContentPage>
O arquivo code-behind define uma matriz de estruturas de retângulo com as coordenadas fracionárias para cada um dos oito elementos
BoxView. Em um loop o programa aplica uma ligeira variação da formula final mostrada a cima. Em vez de um denominador igual a 1
menos o valor de layoutBounds.Width (ou layoutBounds.Height), que utiliza a largura (ou altura) dos limites fracionais, que são o mesmo
valor.
public partial class ProportionalCoordinateCalcPage : ContentPage
{
public ProportionalCoordinateCalcPage()
{
InitializeComponent();

Rectangle[] fractionalRects =
{
new Rectangle(0.05, 0.1, 0.90, 0.1), // outer top
new Rectangle(0.05, 0.8, 0.90, 0.1), // outer bottom new
Rectangle(0.05, 0.1, 0.05, 0.8), // outer left
new Rectangle(0.90, 0.1, 0.05, 0.8), // outer right

new Rectangle(0.15, 0.3, 0.70, 0.1), // inner top


new Rectangle(0.15, 0.6, 0.70, 0.1), // inner bottom new
Rectangle(0.15, 0.3, 0.05, 0.4), // inner left new Rectangle(0.80,
0.3, 0.05, 0.4), // inner right
};

foreach (Rectangle fractionalRect in fractionalRects)


{
Rectangle layoutBounds = new Rectangle
{
// Proportional coordinate calculations.
X = fractionalRect.X / (1 - fractionalRect.Width),
Y = fractionalRect.Y / (1 - fractionalRect.Height),

Width = fractionalRect.Width, Height =


fractionalRect.Height
};

absoluteLayout.Children.Add(
new BoxView
{
Color = Color.Blue
},
layoutBounds, AbsoluteLayoutFlags.All);
}
}

void OnContentViewSizeChanged(object sender, EventArgs args)


{
ContentView contentView = (ContentView)sender;

// Figure has an aspect ratio of 2:1.


double height = Math.Min(contentView.Width / 2, contentView.Height);
absoluteLayout.WidthRequest = 2 * height; absoluteLayout.HeightRequest = height;
}
}
Aqui está o resultado:

E claro, você pode virar o telefone para o modo paisagem, e você verá o resultado:
XML e Layout Absoluto
Como você viu, você pode posicionar e dimensionar um filho de um AbsoluteLayout, usando um dos métodos Add disponíveis na coleção
Children ou definindo uma propriedade anexada por meio de uma chamada de método estático.
Mas como na terra você definir a posição e tamanho das crianças AbsoluteLayout em XAML?
Uma sintaxe especial está envolvida. Essa sintaxe é ilustrada por esta versão XAML do anterior programa AbsoluteDemo, chamado

AbsoluteXamlDemo:

O arquivo code-behind contém apenas uma chamada InitializeComponent.

Aqui está a primeira BoxView

Em XAML, uma propriedade bindable anexado é um atributo que consiste em um nome de classe (AbsoluteLayout) e um nome de
propriedade (LayoutBounds) separados por um ponto. Sempre que você vê como um atributo, é uma propriedade bindable anexado. Essa
é a única aplicação desta sintaxe de atributo.
Em resumo, as combinações de nomes de classes e nomes de propriedade só aparecem em XAML em três contextos específicos: Se
eles aparecem como elementos, eles são elementos de propriedade. Se eles aparecem como atributos, estão ligadas propriedades
vinculáveis. E o único outro contexto para um nome de classe e propriedade de nome é um argumento para um x: extensão de marcação
estática.
Neste caso, o atributo é definido como quatro números separados por vírgulas. Você também pode expressar
AbsoluteLayout.LayoutBounds como um elemento de propriedade:

Estes quatros números são analisados pelo BoundsTypeConverter e não o RectangleTypeConverter porque o BoundsTypeConverter permite
o uso de AutoDimensionar para as partes de largura e altura. Você pode ver os argumentos AutoSize mais tarde no arquivo XAML:

Ou você pode deixá-los fora:

A coisa estranha sobre propriedades vinculava á anexados é que você especificar no XAML que eles realmente não existem! Não há
nenhum campo, propriedade ou método em AbsoluteLayout chamado LayoutBounds. Há certamente um campo só de leitura estática
pública do tipo BindableProperty chamado LayoutBoundsProperty, e há métodos estáticos públicos nomeados SetLayoutBounds e
GetLayoutBounds, mas não há nada chamado LayoutBounds. O analisador XAML reconhece a sintaxe como uma referência a uma
propriedade bindable anexado e, em seguida, olha para LayoutBoundsProperty na classe AbsoluteLayout. De lá, ele pode chamar
SetValue na visão alvo com que BindableProperty objeto juntamente com o valor do BoundsTypeConverter.
A série tabuleiro de xadrez de programas parece um candidato improvável para a duplicação em XAML porque o arquivo precisaria de 32
casos de BoxView sem o benefício de loops. No entanto, o programa ChessboardXaml mostra como especificar duas propriedades de
BoxView em um estilo implícita, incluindo os ligados AbsoluteLayout.LayoutFlags propriedade bindable.
Sim, é um monte de elementos BoxView individuais, mas você não pode argumentar com a limpeza do arquivo. O arquivo code-behind
simplesmente ajusta a relação de aspecto:

Sobreposições
A capacidade de se sobrepõem crianças no AbsoluteLayout tem algumas aplicações interessantes e úteis, entre eles, sendo a
capacidade para encobrir a sua interface de usuário toda com algo chamado às vezes uma sobreposição. Talvez sua página está
realizando um trabalho demorado e você não quer que o usuário interagir com a página até que o trabalho seja concluído. Você pode
colocar uma sobreposição semitransparente sobre a página e talvez exibir um ActivityIndicator ou um ProgressBar.
Aqui está um programa chamado SimpleOverlay que demonstra essa técnica. O arquivo XAML começa com uma AbsoluteLayout
cobrindo a página inteira. O primeiro filho do que AbsoluteLayout é um StackLayout, o que você deseja preencher a página também. No
entanto, os HorizontalOptions padrão e colocações VerticalOptions de preenchimento na StackLayout não funcionam para crianças de
um AbsoluteLayout. Em vez disso, o StackLayout enche o AbsoluteLayout através da utilização dos AbsoluteLayout.LayoutBounds e
AbsoluteLayout.LayoutFlags ligados ligável propriedades:

201
O segundo filho do AbsoluteLayout é um ContentView, que também preenche o AbsoluteLayout e basicamente se senta em cima do
StackLayout. No entanto, observe que a propriedade IsVisible é definido como Falso, o que significa que este ContentView e seus filhos
não participam no layout. O ContentView ainda é uma criança do AbsoluteLayout, mas ele é simplesmente ignorado quando o sistema
de layout é dimensionamento e prestação de todos os elementos da página.
Este ContentView é a sobreposição. Quando IsVisible está definido para, ele bloqueia a entrada do usuário Fiel às vistas abaixo dele. O
BackgroundColor é definido como um cinza semitransparente, e um ProgressBar está centralizado verticalmente dentro dele. A
ProgressBar se assemelha a um Slider sem um polegar. A ProgressBar está sempre orientada horizontalmente. Não defina as
HorizontalOptions propriedade de um ProgressBar para Iniciar, Centro, ou Terminar a menos que você também defina sua propriedade
WidthRequest. Um programa pode indicar o progresso, definindo a propriedade Progresso da ProgressBar para um valor entre 0 e 1.
Isto é demonstrado no manipulador clicado para o único botão funcional na aplicação. Esse manipulador simula um trabalho demorado
que está sendo executada no código com um temporizador que determina quando cinco segundos terem passado:

202
O manipulador clicado começa definindo a propriedade IsVisible da sobreposição de verdade, o que revela a sobreposição e seu filho
ProgressBar e impede ainda mais a interação com a interface do usuário por baixo. O temporizador está definido para um décimo segundo
e calcula uma nova propriedade Progress para o ProgressBar baseado no tempo decorrido. Quando os cinco segundos acabarem, a
sobreposição é novamente oculto ao retorno de chamada timer retorna false.

Aqui está o que ele se parece com a sobreposição abrangendo a página e o longo trabalho em andamento:

Uma sobreposição pode não estar limitada a um ProgressBar ou um ActivityIndicator. Você pode incluir um botão Cancelar ou outros
pontos de vista.

203
Alguma Diversão
Como você provavelmente pode ver até agora, o AbsoluteLayout é muitas vezes usado para algumas finalidades especiais que não seria
fácil contrário. Alguns deles podem realmente ser classificada como "diversão".
DotMatrixClock exibe os dígitos da hora atual utilizando a 5 × 7 display dot matrix simulado. Cada ponto é um BoxView, individualmente
dimensionados e posicionados na tela e colorido vermelho ou cinza-claro, dependendo se o ponto está ligado ou desligado. É concebível
que os pontos de este relógio poderiam ser organizados em elementos StackLayout aninhados ou uma grade, mas cada BoxView precisa
ser dado um tamanho de qualquer maneira. A grande quantidade e regularidade desses pontos de vista sugere que o programador sabe
melhor do que uma classe de layout como organizá-los na tela, como a classe de layout precisa para executar os cálculos de localização
de uma forma mais generalizada. Por essa razão, este é um trabalho ideal para AbsoluteLayout.
Um arquivo XAML define um pouco acima na página e prepara um AbsoluteLayout para o preenchimento por código.

O arquivo code-behind contém vários campos, incluindo duas matrizes, numberPatterns nomeados e colonPattern, que definem os
padrões de matriz de pontos para os 10 dígitos e um separador por dois pontos:

204
Os campos também são definidos por uma matriz de objetos BoxView para os seis dígitos do tempo de dois dígitos cada hora para,
minutos e segundos. O número total de pontos na horizontal (definido como horzDots) inclui cinco pontos para cada um dos seis dígitos,
quatro pontos para os dois pontos entre as horas e os minutos, para quatro do cólon entre os minutos e os segundos, e uma largura de
pontos entre os dígitos de outra maneira.
O construtor do programa (ver abaixo) cria um total de 238 objetos BoxView e os adiciona a uma AbsoluteLayout, mas também poupa o
BoxView objetos para os dígitos na matriz digitBoxViews. (Em teoria, os objetos BoxView pode ser referenciado posteriormente pela
indexação da coleção Children of the AbsoluteLayout. Mas nessa coleção, eles aparecem simplesmente como uma lista linear.
Armazenando-os também em uma matriz multidimensional permite que eles sejam mais facilmente identificados e referenciados. ) Todo
o posicionamento e dimensionamento é proporcional com base numa AbsoluteLayout que é assumida para ter uma razão de aspecto
de 41 a 7, o qual compreende os 41 BoxView larguras e alturas 7 BoxView.

205
Como você deve se lembrar, os horzDots e vertDots funções são definidas para 41 e 7, respectivamente. Para encher o AbsoluteLayout,
cada BoxView precisa ocupar uma fracção da largura igual a 1 / horzDots e uma fracção da altura igual a 1 / vertDots. A altura e largura
definida para cada BoxView é de 85 por cento do referido valor para separar os pontos suficiente de modo que eles não fiquem em se:

Para posicionar cada BoxView, o construtor calcula xIncrement proporcional e valores yIncrement assim:

Os denominadores aqui são 40 e 6 para que o X final e Y coordenadas posicionais são valores de 1.
Os objetos BoxView para os dígitos de tempo não são coloridos em tudo no construtor, mas aqueles para os dois dois pontos são dadas
uma propriedade cores com base na matriz colonPattern. O construtor DotMatrixClockPage conclui por um segundo de timer.
O manipulador SizeChanged para a página é definido a partir do arquivo XAML. O AbsoluteLayout é automaticamente esticado
horizontalmente para preencher a largura da página (menos o estofamento), de modo que o HeightRequest realmente apenas define a
relação de aspecto:

206
Parece que o manipulador de eventos Device.StartTimer deve ser bastante complexo, pois é responsável por definir a propriedade de
cor de cada BoxView com base nos dígitos do tempo atua. No entanto, a semelhança entre as definições da matriz e a matriz
numberPatterns digitBoxViews torna surpreendentemente simples:

207
E aqui está o resultado:

É claro que, quanto maior, melhor, então você provavelmente vai querer ligar o telefone (ou o livro) de lado por algo grande o suficiente
para ler a partir do outro lado da sala:

Outro tipo especial de aplicação adequado para AbsoluteLayout é animação. O programa BouncingText usar seu arquivo XAML para
instanciar dois elementos do rótulo:

Observe que os atributos AbsoluteLayout.LayoutFlags estão definidas para PositionProportional. A etiqueta calcula seu próprio tamanho,
mas o posicionamento é proporcional. Valores entre 0 e 1 pode posicionar os dois elementos do rótulo em qualquer lugar dentro da
página.

208
O arquivo code-behind inicia um temporizador indo com uma duração de 15 milissegundos. Isto é equivalente a aproximadamente 60
carrapatos por segundo, o que é geralmente a taxa de atualização de monitores de vídeo. A duração do temporizador de 15
milissegundos é ideal para a realização de animações:

O manipulador OnTimerTick calcula um tempo decorrido desde que o programa começou e converte isso para um valor t (por tempo)
que vai de 0 a 1 a cada dois segundos. O segundo cálculo de t torna aumentar 0-1 e depois diminuir de volta para baixo a 0 a cada dois
segundos. Esse valor é passado diretamente para o construtor de retângulo nas duas chamadas AbsoluteLayout.SetLayoutBounds. O
resultado é que a primeira etiqueta move-se horizontalmente através do centro da tela e parece saltar fora os lados esquerdo e direito.
A segunda etiqueta move verticalmente para cima e para baixo do centro da tela e parece saltar fora a parte superior e inferior:

Os dois pontos de vista do rótulo conhecer brevemente no centro a cada segundo, como a captura de tela do Windows Phone confirma.
De agora em diante, as páginas de nossas aplicações Xamarin.Forms vai se tornar mais ativo e animado e dinâmico. No próximo capítulo,
você verá como as visualizações interativas de Xamarin.Forms estabelecer um meio de comunicação entre o usuário e o aplicativo.

209
Capítulo 13 - Bitmaps
Os elementos visuais de uma interface gráfica do usuário podem ser divididos entre elementos utilizados para a apresentação (como
texto) e aqueles capazes de interação com o usuário, como buttons, sliders, e list boxes.
Texto é essencial para a apresentação, mas as imagens são muitas vezes tão importantes como uma forma de complementar o texto e
transmitir informação crucial. A web, por exemplo, seria inconcebível sem fotos. Estas imagens são muitas vezes sob a forma de matrizes
retangulares de elementos de imagem (ou pixels) conhecidas como bitmaps.
Assim como uma view nomeada Label exibe texto, uma view nomeada Image exibe bitmaps. Os formatos bitmap suportados pelo iOS,
Android e Windows Runtime são um pouco diferentes, mas se você ficar com JPEG, PNG, GIF e BMP em suas aplicações
Xamarin.Forms, você provavelmente não sentiria qualquer problema.
Image possui uma propriedade Source onde você define um objeto do tipo ImageSource, que referencia o bitmap exibido pela imagem.
Bitmaps podem vir de uma variedade de sources, então a classe ImageSource define quatro métodos de criação estáticos que retornam
um objeto ImageSource:
• ImageSource.FromUri para acessar um bitmap através da web.
• ImageSource.FromResource para um bitmap armazenado como um recurso incorporado na aplicação PCL.
• ImageSource.FromFile para um bitmap armazenado como conteúdo em um projeto de plataforma individual.
• ImageSource.FromStream para carregar um bitmap usando um objeto .NET Stream.

ImageSource também tem três classes descendentes, denominados UriImageSource, FileImageSource e StreamImageSource, que você
pode usar em vez do primeiro, terceiro e quarto métodos de criação estáticos. Geralmente, os métodos estáticos são mais fáceis de usar
no código, mas as classes descendentes algumas vezes são necessárias em XAML.
Em geral, você vai usar os métodos ImageSource.FromUri e ImageSource.FromResource para obter bitmaps independentes de
plataforma para fins de apresentação e ImageSource.FromFile para carregar bitmaps específicos da plataforma para objetos de interface
do usuário. Bitmaps pequenos desempenham um papel crucial no MenuItem objetos e ToolbarItem, e você também pode adicionar um
bitmap em um Button.
Este capítulo começa com o uso de bitmaps independentes de plataforma obtidos a partir dos métodos ImageSource.FromUri e
ImageSource.FromResource. Em seguida, explora alguns usos do método ImageSource.FromStream. O capítulo termina com o uso de
ImageSource.FromFile para obter bitmaps específicos da plataforma para toolbars e buttons.

BITMAPS INDEPENDENTES DE PLATAFORMA


Aqui está um código simples de um programa chamado WebBitmapCode com uma classe de página que usa ImageSource.FromUri
para acessar um bitmap a partir do website Xamarin:

Se o URI passado para ImageSource.FromUri não aponta para um bitmap válido, nenhuma exceção é gerada.
Mesmo este pequeno programa pode ser simplificada. ImageSource define uma conversão implícita de string ou Uri para um objeto
ImageSource, para que possa definir a string com o URI diretamente para a Source da Image:

210
Ou, para torná-lo mais detalhado, você pode definir a propriedade Source do Image a um objeto UriImageSource com sua propriedade
Uri definida como um objeto Uri:

A classe UriImageSource pode ser preferível se você quiser controlar o cache de imagens web-based. A classe implementa seu próprio
cache que usa uma área de armazenamento privada do aplicativo disponível em cada plataforma. UriImageSource define uma
propriedade CachingEnabled que tem um valor padrão de true e uma propriedade CachingValidity do tipo TimeSpan que tem um valor
padrão de um dia. Isto significa que, se a imagem está reavaliada dentro de um dia, a imagem armazenada é usada. Você pode desabilitar
o cache completamente, definindo CachingEnabled como false, ou você pode alterar o tempo de expiração de armazenamento em
cache, definindo a propriedade CachingValidity para outro valor TimeSpan.
Independentemente de qual maneira que você faz, por padrão, o bitmap exibido pela Image na view é ampliada para o tamanho de seu
recipiente - o ContentPage neste caso - respeitando a proporção do bitmap:

211
Este bitmap é quadrado, então áreas em branco aparecem acima e abaixo da imagem. Como você pode mudar o seu telefone ou
emulador entre modo retrato e paisagem, o bitmap renderizado pode mudar de tamanho, e você verá um espaço em branco na parte
superior e inferior ou esquerda e à direita, onde o bitmap não alcança. Você pode colorir a área usando a propriedade BackgroundColor
que o Image herda de VisualElement.
O bitmap referenciado no programa WebBitmapCode é 4.096 pixels quadrado, mas um utilitário é instalado no site da Xamarin que
permite o download de um arquivo de bitmap muito menor, especificando o URI da seguinte forma:

Agora, o bitmap baixado é de 25 pixels quadrados, mas é novamente esticado para o tamanho de seu recipiente. Cada plataforma
implementa um algoritmo de interpolação numa tentativa para suavizar os pixels da imagem expandida para se ajustar à página:

No entanto, se você agora define HorizontalOptions e VerticalOptions na Image para Center - ou coloca o elemento Image em um
StackLayout - este bitmap 25-pixel colapsa em uma imagem muito pequena. Este fenómeno é discutido em mais detalhes mais adiante
neste capítulo.
Você também pode instanciar um elemento Image no XAML e carregar um bitmap a partir de uma URL, definindo a propriedade Source
diretamente a um endereço web. Aqui está o arquivo XAML a partir do programa WebBitmapXaml:

Uma abordagem mais detalhada envolve instanciar explicitamente um objeto UriImageSource e definindo a propriedade Uri:

Independentemente disso, aqui está como ele olha na tela:

212
Fit e fill
Se você definir a propriedade BackgroundColor do Image em qualquer parte do código anterior e exemplos XAML, você verá que este
Image realmente ocupa toda a área retangular da página. Image possui uma propriedade Aspect que controla como o bitmap é
processado dentro deste retângulo. Você define essa propriedade com um membro da enumeração Aspect:
• AspectFit - o padrão
• Fill - estende-se sem preservar a proporção
• AspectFill - preserva a relação de aspecto, mas corta a imagem
A configuração padrão é o membro de enumeração Aspect.AspectFit, o que significa que o bitmap se encaixa em limites de seu
recipiente, preservando a proporção do bitmap. Como você já viu, a relação entre as dimensões do bitmap e dimensões do recipiente
pode resultar em áreas de fundo na parte superior e inferior ou à direita e à esquerda.
Tente isto no projeto WebBitmapXaml:

Agora, o bitmap é expandido para as dimensões da página. Isso resulta na imagem sendo esticada na vertical, de modo que o carro
parece bastante curto e atarracado:

Se você virar o telefone de lado, a imagem é esticada na horizontal, mas o resultado não é tão extremo, porque o formato da imagem é
um pouco paisagem para começar.

213
A terceira opção é AspectFill:

Com esta opção, o bitmap enche totalmente o recipiente, mas a relação de aspecto do bitmap é mantida ao mesmo tempo. A única
maneira possível é cortando parte da imagem, e você verá que a imagem é realmente cortada, mas de uma maneira diferente nas três
plataformas. No iOS e Android, a imagem é cortada no lado superior e inferior ou esquerda e à direita, deixando apenas a parte central
do bitmap visível. Nas plataformas Windows Runtime, a imagem é cortada no lado direito ou na parte inferior, deixando o canto superior
esquerdo visível:

Recursos Incorporados
Acessar bitmaps através da Internet é conveniente, mas às vezes não é ideal. O processo requer uma ligação à Internet, uma garantia
de que os bitmaps não foram movidos, e algum tempo para download. Para o acesso rápido e garantido aos bitmaps, podemos deixá-
los ligados diretamente na aplicação.
Se você precisa de acesso a imagens que não são de uma plataforma específica, você pode incluir os bitmaps como recursos
incorporados em um projeto shared Portable Class Library e acessá-los com o método ImageSource.From-Resource. A solução
ResourceBitmapCode demonstra como fazê-lo.
O projeto ResourceBitmapCode PCL dentro desta solução tem uma pasta chamada Images que contém dois bitmaps, nomeado
ModernUserInterface.jpg (um grande bitmap) e ModernUserInter-face256.jpg (a mesma imagem, mas com uma largura de 256 pixels).
Ao adicionar qualquer tipo de recurso incorporado a um projeto PCL, certifique-se de definir o Build Action do recurso para
EmbeddedResource. Isto é crucial.
No código, você define a propriedade Source de um elemento Image para um objeto ImageSource retornado do método estático
ImageSource.FromResource. Este método requer o ID do recurso. O ID do recurso consiste na combinação do nome do conjunto seguido
em uma parte, em seguida, o nome da pasta seguido de outra parte, e em seguida o nome do arquivo, que contém outra parte com a
extensão do arquivo. Para este exemplo, o ID do recurso para acessar a menor das duas imagens no programa ResourceBitmapCode
é:
ResourceBitmapCode.Images.ModernUserInterface256.jpg
O código neste programa referência o menor bitmap e também define a HorizontalOptions e a VerticalOptions do elemento Image para
Center:

214
Como você pode ver, o bitmap, neste instancia não é esticado para preencher a página:

Um bitmap não é esticado para preencher o seu recipiente se:


• É menor do que o recipiente, e
• Os propriedades VerticalOptions e HorizontalOptions do elemento Image não estão definidos para Fill, ou se o Image é filho
de um StackLayout.
Se você comentar as configurações VerticalOptions e HorizontalOptions, ou se você faz referência ao bitmap grande (que não tem o
"256" no final do seu nome de arquivo), a imagem voltará a esticar para encher o recipiente.
Quando um bitmap não é esticado para se ajustar o seu recipiente, ele deve ser exibido em um determinado tamanho. Qual é esse
tamanho?
No iOS e Android, o bitmap é exibido no seu tamanho em pixel. Em outras palavras, o bitmap é renderizado com um mapeamento um-
para-um entre os pixels do bitmap e os pixels do monitor de vídeo. O simulador do iPhone 6 usado para essas imagens tem uma largura
de tela de 750 pixels, e você pode ver que a largura de 256 pixels do bitmap é cerca de um terço da largura. O telefone Android aqui é
um Nexus 5, que tem uma largura de pixel de 1080, e o bitmap é cerca de um quarto de sua largura.
Nas plataformas Windows Runtime, no entanto, o bitmap é exibido em unidades independentes de dispositivo - neste exemplo, 256
unidades independentes de dispositivo. O Nokia Lumia 925 utilizado para estas telas tem uma largura de 768, que é aproximadamente
o mesmo que o iPhone 6. No entanto, a largura da tela neste telefone Windows 10 Mobile é 341 unidades independentes de dispositivo,
e você pode ver que o bitmap renderizado é muito mais amplo do que nas outras plataformas.
Esta discussão sobre dimensionamento de bitmaps continua na próxima seção.
Como você faz referência a um bitmap armazenado como um recurso incorporado a partir do XAML? Infelizmente, não existe uma classe
ResourceImageSource. Se houvesse, você provavelmente tentaria instanciar essa classe XAML entre as tags Image.Source. Mas isso
não é uma opção.
Você pode considerar o uso x:FactoryMethod para chamar ImageSource.FromResource, mas isso não vai funcionar. Na implementação
atual, o método ImageSource.FromResource requer que o recurso bitmap esteja no mesmo conjunto que o código que chama o método.
Quando você usa x:FactoryMethod para chamar ImageSource.FromResource, a chamada é feita a partir da montagem
Xamarin.Forms.Xaml.

215
O que vai funcionar é uma extensão de marcação XAML muito simples. Aqui está um projeto denominado Stacked-Bitmap:

ImageResourceExtension tem uma única propriedade denominada Source que você define o ID do recurso. O método ProvideValue
simplesmente chama ImageSource.FromResource com a propriedade Source. Como é comum para marcação de extensões de
propriedade única, Source é também a propriedade de conteúdo da classe. Isso significa que você não precisa incluir explicitamente
"Source =" quando você está usando a sintaxe curly-braces para extensões de marcação XAML.
Mas cuidado: você não pode mover esta classe ImageResourceExtension a uma biblioteca, como Xamarin.FormsBook.Toolkit. A classe
deve fazer parte do mesmo conjunto que contém o recurso incorporado que você deseja carregar, que é geralmente uma aplicação
Portable Class Library.
Aqui está o arquivo XAML do projeto StackedBitmap. Um elemento Image compartilha uma StackLayout com dois elementos do Label:

O prefixo local refere-se ao namespace StackedBitmap na montagem StackedBitmap. A propriedade Source do elemento Image está
definida para a extensão de marcação ImageResource, que se referência um bitmap armazenado na pasta Images do projeto PCL e
sinalizado como um EmbeddedResource. O bitmap é de 320 pixels de largura e 240 pixels de altura. O Image também tem a propriedade
BackgroundColor; isso vai permitir-nos ver o tamanho inteiro da Image dentro da StackLayout.
O elemento Image tem o seu evento SizeChanged definido como um manipulador no arquivo code-behind:

216
O tamanho do elemento Image é limitado verticalmente pela StackLayout, portanto, o bitmap é exibido em seu tamanho em
pixel (no iOS e Android) e em unidades independentes de dispositivo no Windows Phone. A Label exibe o tamanho do elemento Image
em unidades independentes de dispositivo, que diferem em cada plataforma:

A largura do elemento Image exibido pelo Label alinhado abaixo inclui o fundo aqua igualmente à largura da página em unidades
independentes de dispositivo. Você pode usar as configurações Aspect de Fill ou AspectFill para fazer o bitmap preencher inteiramente
essa área aqua.
Se você preferir que o tamanho do elemento Image seja do mesmo tamanho que o bitmap processado em unidades independentes de
dispositivo, você pode definir a propriedade HorizontalOptions do Image para algo diferente do que o valor padrão de Fill:

Agora, o Label alinhado abaixo exibe apenas a largura do bitmap prestado. Configurações da propriedade Aspect não terão efeito:

217
Vamos referir-se a este Image renderizado como a sua dimensão natural, porque é baseado no tamanho do bitmap a sendo exibido.
O iPhone 6 tem uma largura de pixels de 750 pixels, mas como você descobriu ao executar o programa WhatSize no Capítulo 5, os
aplicativos percebem uma largura de tela de 375. Temos dois pixels por unidade independente do dispositivo, então um bitmap com uma
largura de 320 pixels é exibido com uma largura de 160 unidades.
O Nexus 5 tem uma largura de pixel de 1080, mas os aplicativos percebem uma largura de 360, por isso temos três pixels por unidade
independente de dispositivo, como a largura da imagem de 107 unidades confirma.
Em ambos os dispositivos iOS e Android, quando um bitmap é exibido em seu tamanho natural, há um mapeamento um-para-um entre
os pixels do bitmap e os pixels do display. Nos dispositivos Windows Runtime, porém, que não é o caso. O Nokia Lumia 925 utilizado
para estas telas tem uma largura de pixel de 768. Ao executar o sistema operacional Windows 10 Mobile, existem 2,25 pixels por unidade
de dispositivo independente, para que os aplicativos percebam uma largura de tela de 341. Mas as 320 × 240 pixels bitmap são exibidos
em um tamanho de 320 × 240 unidades independentes de dispositivo.
Esta inconsistência entre o Windows Runtime e as outras duas plataformas é realmente benéfica quando você está acessando bitmaps
a partir dos projetos de plataformas individuais. Como você verá, iOS e Android incluem uma funcionalidade que lhe permite fornecer
diferentes tamanhos de bitmaps para diferentes resoluções de dispositivo. Em efeito, isso permite que você especifique tamanhos de
bitmap em unidades independentes de dispositivo, o que significa que os dispositivos do Windows são consistentes com esses
esquemas.
Mas ao usar bitmaps independentes de plataforma, você provavelmente vai querer o tamanho dos bitmaps consistentes em todas as
três plataformas, e isso exige um mergulho mais fundo no assunto.

Mais Sobre Dimensionamento


Até agora, você já viu duas formas de dimensionar elementos Image:
Se o elemento Image não é limitado de qualquer forma, ele vai encher o seu recipiente, mantendo a relação de aspecto do bitmap, ou
preencher a área por completo, se você definir a propriedade de aspecto Fill ou AspectFill.
Se o bitmap é menor do que o tamanho de seu recipiente e o Image é restrito horizontalmente ou verticalmente, definindo
HorizontalOptions ou VerticalOptions para algo diferente de Fill, ou se o Image é colocado em um StackLayout, o bitmap é exibido em
seu tamanho natural. Esse é o tamanho em pixel em iOS e dispositivos Android, mas o tamanho em unidades independentes de
dispositivo em dispositivos Windows.
Você também pode controlar o tamanho, definindo WidthRequest ou HeightRequest a uma dimensão explícita em unidades
independentes de dispositivo. No entanto, existem algumas limitações.
A discussão a seguir é baseada na experimentação com a amostra StackedBitmap. Que se refere a elementos Image que são
verticalmente limitados por ser filho de um StackLayout vertical ou ter a propriedade VerticalOptions definida para algo diferente de Fill.
Os mesmos princípios se aplicam a um elemento de Image que está restrito horizontalmente.
Se um elemento Image é verticalmente limitado, você pode usar WidthRequest para reduzir o tamanho do bitmap de seu tamanho
natural, mas você não pode usá-lo para aumentar o tamanho. Por exemplo, tente definir WidthRequest a 100:

A altura resultante do bitmap é regida pela largura especificada e a relação de aspecto do bitmap, então agora a imagem é exibida com
um tamanho de 100 × 75 unidades independentes de dispositivo em todas as três plataformas:

218
A configuração de HorizontalOptions como Center não afeta o tamanho do bitmap renderizado. Se você remover essa linha, o elemento
Image será tão grande quanto a tela (como a cor de fundo do aqua irá demonstrar), mas o bitmap permanecerá o mesmo tamanho.
Não é possível utilizar WidthRequest para aumentar o tamanho do bitmap renderizado além do seu tamanho natural. Por exemplo, tente
definir WidthRequest a 1000:

Mesmo com o HorizontalOptions definido para Center, o elemento Image resultante é agora maior do que o bitmap representado, como
indicado pela cor de fundo:

Mas o próprio bitmap é exibido em seu tamanho natural. O StackLayout vertical é a altura preventiva do bitmap renderizado a partir do
excedente da altura natural.
Para superar essa restrição do StackLayout vertical, você precisa definir o HeightRequest. Entretanto, você também vai querer deixar o
HorizontalOptions em seu valor padrão de preenchimento. Caso contrário, a configuração HorizontalOptions irá impedir a largura do
bitmap renderizado a partir do excedente de seu tamanho natural.
Tal como aconteceu com o WidthRequest, você pode definir o HeightRequest para reduzir o tamanho do bitmap renderizado. O código
a seguir define o HeightRequest para 100 unidades independentes de dispositivo:

Observe também que a definição HorizontalOptions foi removido.


O bitmap renderizado é agora 100 unidades independentes de dispositivo de altura e com uma largura definida pela relação de aspecto.
O elemento Image se estende para os lados do StackLayout:

219
Neste caso particular, você pode definir o HorizontalOptions para Center sem alterar o tamanho do bitmap renderizado. O elemento
Image terá o tamanho do bitmap (133 x 100), e o fundo Aqua desaparecerá.
É importante deixar o HorizontalOptions em sua configuração padrão Fill quando definir o HeightRequest para um valor maior do que a
altura natural do bitmap, por exemplo 250:

Agora, o bitmap renderizado é maior do que seu tamanho natural:

No entanto, esta técnica tem um perigo embutido, que é revelada quando você define o HeightRequest para 400:

Eis o que acontece: o elemento Image, de fato, tem uma altura de 400 unidades independentes de dispositivo. Mas a largura do bitmap
renderizado nesse elemento Image está limitada pela largura da tela, o que significa que a altura do bitmap renderizado é menor do que
a altura do elemento de imagem:

220
Em um programa real, você provavelmente não teria a propriedade BackgroundColor set, e em vez disso um terreno baldio de tela em
branco irá ocupar a área na parte superior e inferior do bitmap prestados.
O que isto significa é que você não deve usar HeightRequest para controlar o tamanho de bitmaps em um na vertical StackLayout a
menos que você escrever código que garante que HeightRequest é limitada à largura das vezes StackLayout a relação entre a altura do
bitmap com a largura.
Se você sabe o tamanho do pixel do bitmap que você vai estar exibindo, uma abordagem mais fácil é definir WidthRequest e
HeightRequest para esse tamanho:

Agora, o bitmap é exibido com esse tamanho em unidades independentes de dispositivo em todas as plataformas:

O problema aqui é que o bitmap não está sendo exibido na sua resolução ideal. Cada pixel do bitmap ocupa, pelo menos, dois pixels da
tela, dependendo do dispositivo.
Se você quiser bitmaps de tamanho em um StackLayout vertical, de modo que eles olham aproximadamente o mesmo tamanho em uma
variedade de dispositivos, use WidthRequest em vez de HeightRequest. Você viu que a busca WidthRe- em um StackLayout vertical, só
pode diminuir o tamanho de bitmaps. Isso significa que você deve usar bitmaps que são maiores do que o tamanho em que eles serão
processados. Isto lhe dará uma resolução mal mais opti- quando a imagem é dimensionada em unidades independentes de dispositivo.
Você pode dimensionar o bitmap usando um tamanho métrica desejada em polegadas, juntamente com o número de unidades
independentes de dispositivo por polegada para o dispositivo em particular, que encontramos a ser 160 para estes três dispositivos.
Aqui está um projeto muito semelhante ao StackedBitmap chamado DeviceIndBitmapSize. É o mesmo bitmap mas agora 1200 × 900
pixels, que é maior que a largura do modo retrato de mesmo de alta resolução 1920 × 1080 exibe. A largura solicitada específica da
plataforma do bitmap corresponde a 1,5 polegadas:

221
Se a análise anterior sobre o dimensionamento é correto e tudo correr bem, este bitmap deve olhar aproximadamente o mesmo tamanho
em todas as três plataformas em relação à largura da tela, bem como proporcionar maior resolução fidelidade do que o programa anterior:

Com este conhecimento sobre o dimensionamento bitmaps, agora é possível fazer um leitor pouco e-livro com as fotografias, porque o
que é o uso de um livro sem imagens?
Este e-book reader exibe uma StackLayout rolagem com o texto completo do capítulo 7 das aventuras da Alice de Lewis Carroll no país
das maravilhas, incluindo três de ilustrações originais de John Tenniel. O texto e as ilustrações foram baixados da Universidade do
website da Adelaide. As ilustrações são incluídas como recursos incorporados no projeto MadTeaParty. Eles têm os mesmos nomes e
tamanhos como aqueles no site. Os nomes referem-se ao número da página do livro original:

• image113.jpg - 709 × 553


• image122.jpg - 485 × 545
• image129.jpg - 670 × 596

222
Lembre-se que o uso de WidthRequest para elementos de Image em um StackLayout só pode diminuir o tamanho de bitmaps prestados.
Esses bitmaps não são largas o suficiente para garantir que todos eles vão encolher para um tamanho adequado em todas as três
plataformas, mas vale a pena examinar os resultados de qualquer maneira porque isso é muito mais perto de um exemplo da vida real.
O programa MadTeaParty usa um estilo implícito para Imagem para definir a propriedade WidthRequest para um valor correspondente
a 1,5 polegadas. Tal como no exemplo anterior, este valor é de 240.
Para os três dispositivos usados para essas imagens, essa largura corresponde a:

• 480 pixels no iPhone 6


• 720 pixels no Nexus Android 5

• 540 pixels no Nokia Lumia 925 com Windows 10 Mobile


Isto significa que todas as três imagens irão diminuir de tamanho no iPhone 6, e todos eles terão uma largura processada de 240 unidades
independentes de dispositivo.
No entanto, nenhuma das três imagens irá diminuir de tamanho sobre o Nexus 5 porque todos eles têm larguras de pixel mais estreitas
do que o número de pixels em 1,5 polegadas. As três imagens terão uma largura processada (respectivamente) 236, 162, e 223 unidades
independentes de dispositivo no Nexus 5. (Que é a largura de pixel di- fornecida pelos 3.)
No dispositivo Windows Mobile 10, dois vai encolher e um não.
Vamos ver se as previsões estiverem corretas. O arquivo XAML inclui uma configuração BackgroundColor no elemento raiz que as cores
a página inteira brancos, como é apropriado para um livro. As definições de estilo são confinadas a um dicionário Resources na
StackLayout. Um estilo para o título do livro baseia-se na vice-TitleStyle de- mas com texto preto e centrado, e dois estilos implícitos para
Etiqueta e Imagem servir ao estilo a maioria dos elementos do rótulo e todos os três elementos de imagem. Apenas os primeiros e
últimos parágrafos do texto do capítulo são mostrados nesta lista do arquivo XAML:

223
224
Os três elementos de imagem simplesmente fazer referência os três recursos incorporados e recebem uma configuração da propriedade
WidthRequest através do estilo implícito:
<Image Source="{local:ImageResource MadTeaParty.Images.image113.jpg}" />

<Image Source="{local:ImageResource MadTeaParty.Images.image122.jpg}" />

<Image Source="{local:ImageResource MadTeaParty.Images.image129.jpg}" />

Aqui está a primeira tela:

É bastante consistente entre as três plataformas, mesmo que seja exibido em sua largura natural de 709 pixels no Nexus 5, mas isso é
muito perto dos 720 pixels que uma largura de 240 unidades independentes de dispositivo implica.
A diferença é muito maior com a segunda imagem:

225
Isso é exibido em seu tamanho pixel no Nexus 5, o que corresponde a 162 unidades independentes de dispositivo, mas é exibido com
uma largura de 240 unidades no iPhone 6 eo Nokia Lumia 925.
Embora as imagens não parecem ruim em qualquer uma das plataformas, levando-os todos sobre o mesmo tamanho exigiria começando
com bitmaps maiores.

Navegação e esperando
Outra característica da imagem é demonstrada no programa ImageBrowser, que permite que você navegue as fotos utilizados para
alguns dos exemplos neste livro. Como você pode ver no seguinte arquivo XAML, um elemento de imagem divide a tela com um rótulo
e duas visões do botão. Observe que um manipulador Changed propriedade- está definido na Imagem. Você aprendeu no Capítulo 11,
"A infra-estrutura bindable", que o manipulador de PropertyChanged é implementado por bindableobject e é acionado sempre que uma
propriedade bindable altera o valor.

226
Também nesta página é um ActivityIndicator. Eles geralmente usam este elemento quando um programa está à espera de uma longa
operação seja concluída (como o download de um mapa de bits), mas não pode fornecer qualquer informação sobre o progresso da
operação. Se o seu programa sabe o que fracção da operação for concluída, você pode usar um ProgressBar em vez disso. (ProgressBar
é demonstrado no próximo capítulo).
O ActivityIndicator tem uma propriedade booleana denominada IsRunning. Normalmente, essa propriedade é falsa eo ActivityIndicator é
invisível. Defina a propriedade como true para fazer as ActivityIn- dicator visível. Todas as três plataformas implementar uma animação
visual para indicar que o programa está funcionando, mas parece um pouco diferente em cada plataforma. No iOS é uma roda de fiar, e
sobre Android é um círculo parcial spinning. Em dispositivos Windows, uma série de pontos move pela tela.
Para fornecer navegando acesso às imagens, o ImageBrowser precisa baixar um arquivo JSON com uma lista de todos os nomes de
arquivos. Ao longo dos anos, várias versões do .NET introduziram várias classes capazes de baixar objetos através da web. No entanto,
nem todos estes estão disponíveis na versão do .NET que está disponível em uma biblioteca de classes portátil que tem o perfil
compatível com Xamarin.Forms. Uma classe que está disponível é WebRequest e sua classe descendente HttpWebRequest.
O método WebRequest.Create retorna um método WebRequest com base em um URI. (O valor de retorno é realmente um objeto
HttpWebRequest.) O método BeginGetResponse requer uma função de retorno de chamada que é chamada quando o fluxo de referência
a URI está disponível para acesso. O fluxo é ac- cessible de uma chamada para EndGetResponse e GetResponseStream.
Uma vez que o programa obtém acesso ao objeto de fluxo no código a seguir, ele usa a classe DataCon- tractJsonSerializer juntamente
com a classe ImageList incorporado definida perto do topo da classe ImageBrowserPage para converter o arquivo JSON para um objeto
ImageList:

227
228
O corpo inteiro do método WebRequestCallback é fechado em uma função lambda que é o argumento para o método
Device.BeginInvokeOnMainThread. WebRequest baixa o arquivo Referenced pela URI em um thread secundário de execução. Isso
garante que a operação não bloqueia thread principal do programa, que está a lidar com a interface do usuário. O método de retorno
também executa neste segmento secundário. No entanto, objetos de interface do usuário em um aplicativo Xamarin.Forms só pode ser
acessado a partir do thread principal.
O objetivo do método Device.BeginInvokeOnMainThread é para contornar este problema. O argumento para este método está na fila
para ser executado no thread principal do programa e pode acessar com segurança os objetos de interface do usuário.
Quando você clica nos dois botões, as chamadas para FetchPhoto usar UriImageSource o download de um novo mapa de bits. Isso
pode demorar um segundo ou assim. A classe de imagem define uma propriedade booleana denominada IsLoading isso é verdade
quando a imagem está em processo de carga (ou download) um bitmap. IsLoading é apoiado pelo IsLoadingProperty propriedade
bindable. Isso também significa que sempre que IsLoading valor mudanças, um evento PropertyChanged é acionado. O programa usa
o evento PropertyChanged handler- o método OnImagePropertyChanged na parte inferior da classe para definir a propriedade IsRunning
do ActivityIndicator para o mesmo valor que a propriedade IsLoading de Imagem.
Você verá no Capítulo 16, "Ligação de dados", como seus aplicativos podem vincular propriedades como IsLoading e IsRunning para
que eles mantêm o mesmo valor, sem quaisquer manipuladores de eventos explícitos.
Aqui está ImageBrowser em ação:

Algumas das imagens tem um EXIF bandeira conjunto orientação, e se a plataforma em particular ignora essa bandeira, a imagem é
exibida para os lados.
Se você executar este programa em modo paisagem, você vai descobrir que os botões desaparecem. A melhor layout opção para este
programa é uma grade, o que é demonstrado no capítulo 17.

Bitmaps de streaming
Se a classe ImageSource não têm métodos FromUri ou FromResource, você ainda seria capaz de acessar bitmaps através da web ou
armazenados como recursos no PCL. Você pode fazer ambos os empregos de, bem como vários outros, com ImageSource.FromStream
ou a classe StreamImageSource.
O método ImageSource.FromStream é um pouco mais fácil de usar do que StreamImageSource, mas ambos são um pouco estranhos.
O argumento para ImageSource.FromStream não é um objeto de fluxo, mas um objeto Func (um método sem argumentos) que retorna
um objeto Stream. A propriedade Stream of racionalizados ImageSource também não é um objeto Stream, mas um objeto Func que tem
um gument ar- CancellationToken e retorna um objeto Task <Corrente>.

Acessando os fluxos
O programa Streams Bitmap contém um arquivo XAML com dois elementos de imagem à espera de bitmaps, cada um dos quais é
definido no arquivo code-behind usando ImageSource.FromStream:

229
A primeira imagem é definida a partir de um recurso incorporado no PCL; o segundo é definido a partir de um bitmap acessado pela web.
No programa BlackCat no Capítulo 4, "Rolagem a pilha," você viu como obter um objeto Stream para qualquer recurso armazenado com
um Build Action do EmbeddedResource no PCL. Você pode usar a mesma técnica para acessar um bitmap armazenado como um
recurso incorporado:
O argumento para ImageSource.FromStream é definido como uma função que retorna um objeto Stream, de modo que o argumento é
aqui expressado como uma função lambda. A chamada para o método GetType retorna o tipo da classe BitmapStreamsPage e
GetTypeInfo fornece mais informações sobre esse tipo, incluindo o objeto Assembly que contém o tipo. Essa é a montagem
BitmapStream PCL, que é o conjunto com o recurso incorporado. GetManifestResourceStream retorna um objeto Stream, que é o valor
de retorno que ImageSource.FromStream quer.
Se você precisar de um pouco de ajuda com os nomes desses recursos, o GetManifestResourceNames retorna uma matriz de objetos
de cadeia com todos os IDs de recursos no PCL. Se você não consegue descobrir por que seu GetManifestResourceStream não está
funcionando, primeiro certifique-se de seus recursos têm um Build Action do EmbeddedResource, e depois chamar
GetManifestResourceNames para obter todos os IDs de recursos.
Para fazer download de um mapa de bits através da web, você pode usar o mesmo método WebRequest demonstrado anteriormente
no programa ImageBrowser. Neste programa, o retorno BeginGetResponse é uma função lambda:

O retorno de chamada BeginGetResponse também contém mais duas funções lambda embutidos! A primeira linha do retorno de
chamada obtém o objeto de fluxo para o bitmap. Este objeto Stream não é bastante adequado para Windows Runtime para que os
conteúdos são copiados para um MemoryStream.
A próxima instrução utiliza uma função lambda curta como o argumento para ImageSource.FromStream para definir uma função que
retorna esse fluxo. A última linha do retorno de chamada BeginGetResponse é uma chamada para Device.BeginInvokeOnMainThread
para definir o objeto ImageSource para a propriedade origem da imagem.

230
Pode parecer como se você tem mais controle sobre o download de imagens usando WebRequest e ImageSource.FromStream do que
com ImageSource.FromUri, mas o método ImageSource.FromUri tem uma grande vantagem: ele armazena os bitmaps baixados em
uma área de armazenamento privado para a aplicação. Como você viu, você pode desativar o cache, mas se você estiver usando Image-
Source.FromStream vez de ImageSource.FromUri, você pode encontrar a necessidade de armazenar em cache Images e que seria um
trabalho muito maior.

Geração de bitmaps em tempo de execução


Todas as três plataformas suportam o formato de arquivo BMP, que remonta ao início do Microsoft Windows. Apesar de sua herança
antiga, o formato de arquivo BMP é agora bastante padronizada com informações de cabeçalho mais extenso.
Embora existam algumas opções BMP que permitem alguma compressão rudimentar, a maioria dos arquivos BMP é descompactado.
Esta falta de compressão é geralmente considerada como uma desvantagem da BMP para- esteira, mas em alguns casos, não é uma
desvantagem em todos. Por exemplo, se você deseja gerar um bitmap algoritmo em tempo de execução, é muito mais fácil para gerar
um mapa de bits não comprimido em vez de um dos formatos de arquivos compactados. (Na verdade, mesmo se você tivesse uma
função de biblioteca para criar um arquivo JPEG ou PNG, você aplicar essa função para os dados de pixel não-comprimido.)
Você pode criar um bitmap de algoritmos em tempo de execução, preenchendo um MemoryStream com os cabeçalhos de arquivos BMP
e dados de pixel e, em seguida, de passagem, que MemoryStream com o método ImageSource.FromStream. A classe BmpMaker na
biblioteca Xamarin.FormsBook.Toolkit demonstra isso. Ele cria uma BMP na memória usando um pixel 32-bit formato-8 bits cada para
vermelho, verde, azul e alfa (opacidade) Canais.
A classe BmpMaker foi codificado com o desempenho em mente, na esperança de que ele pode ser usado para animação. Talvez um
dia ele vai ser, mas neste capítulo a única demonstração é um gradiente de cor simples.
O construtor cria uma matriz de byte buffer chamado que armazena toda a BMP começando com a informação de cabeçalho e seguido
pelos bits de pixel. O construtor, em seguida, usa um MemoryStream para escrever as informações de cabeçalho no início deste buffer:

231
232
Depois de criar um objeto BmpMaker, um programa pode então chamar um dos dois métodos setPixel para definir a cor de cada linha e
coluna particular. Ao fazer muitas chamadas, a chamada SetPixel que usa um valor Cor é significativamente mais lento do que aquele
que aceita vermelho explícita, verde, e os valores azuis.
O último passo é chamar o método gerar. Este método instancia outra MemoryStream ob- jecto com base na matriz de buffer e usa-o
para criar um objeto FileImageSource. Você pode ligar para gerarem várias vezes depois de definir novos dados de pixel. O método cria
um novo MemoryStream cada vez, porque ImageSource.FromStream fecha o objeto Stream quando estiver terminado com ele.
O DiyGradientBitmap programação "DIY" significa "faça você mesmo" demonstrado como usar BmpMaker para fazer um mapa de bits
com um gradiente simples e exibi-lo para preencher a página. A inclui arquivo XAML o elemento Image:

O arquivo code-behind instancia um Bmp Maker e percorre as linhas e colunas do mapa de bits para criar um gradiente que varia de
vermelho na parte superior para azul na parte inferior:

233
Aqui está o resultado:

Agora use a sua imaginação e veja o que você pode fazer com BmpMaker.

Bitmaps específicos da plataforma


Como você viu, você pode carregar bitmaps através da web ou do projeto PCL compartilhada. Você também pode carregar bitmaps
armazenados como recursos nos projetos de plataformas individuais. As ferramentas para este trabalho são o método estático Image-
Source.FromFile e a classe FileImageSource correspondente.
Você provavelmente vai usar este recurso principalmente para bitmaps conectados com elementos de interface do usuário. A propriedade
Ícone na MenuItem e ToolBarItem é do tipo FileImageSource. A propriedade de imagem no botão, também é do tipo FileImageSource.
Dois outros usos de FileImageSource não serão discutidos neste capítulo: Page classe define uma propriedade Ícone do tipo
FileImageSource e uma propriedade BackgroundImage do tipo string, mas que se presume ser o nome de um bitmap armazenado no
projeto da plataforma.
O armazenamento de bitmaps nos projetos de plataformas individuais permite um alto nível de especificidade plataforma. Você pode
pensar que você pode obter o mesmo grau de especificidade plataforma armazenando bitmaps para cada forma plataforma no projeto
PCL e usando o método Device.OnPlatform ou a classe OnPlatform para selecioná-los. No entanto, como você vai descobrir em breve,
todas as três plataformas têm provisões para armazenar mapas de bits de pixels de resolução diferente e, em seguida, acessar
automaticamente a um ideal. Você pode tirar proveito desse recurso valioso somente se as próprias plataformas individuais carregar os
bitmaps, e este é o caso apenas quando utiliza ImageSource.FromFile e FileImageSource.
Os projetos de plataformas em uma solução Xamarin.Forms recém-criado já contêm vários bitmaps. No projeto iOS, você vai encontrá-
los na pasta Recursos. No projeto Android, eles estão em subpastas da pasta Recursos. Nos vários projetos de Windows, eles estão na

234
pasta Ativos e subpastas. Esses bitmaps são ícones de aplicações e telas de apresentação, e você vai querer substituí-los quando você
a preparar trazer uma aplicação para o mercado.
Vamos escrever um pequeno projeto chamado PlatformBitmaps que acessa um ícone da aplicação de cada projeto de formulário
plataforma e exibe o tamanho processado do elemento de imagem. Se você estiver usando FileImageSource para carregar o bitmap
(como este programa faz), você precisa definir a propriedade de arquivo para uma string com o nome do arquivo de bitmap. Quase
sempre, você estará usando Device.OnPlatform no código ou OnPlatform em XAML para especificar os três nomes:

Quando você acessa um bitmap armazenado na pasta Recursos do projeto iOS ou a pasta de Recursos (ou subpastas) do projeto
Android, não prefaciar o nome do arquivo com um nome de pasta. Essas pastas são os repositórios padrão para bitmaps nessas
plataformas. Mas bitmaps pode estar em qualquer lugar no projeto do Windows Phone ou Windows (incluindo a raiz do projeto), para
que o nome da pasta (se houver) é necessária.
Em todos os três casos, o ícone padrão é o famoso logotipo Xamarin hexagonal (carinhosamente conhecido como o Xamagon), mas
cada plataforma tem diferentes convenções para seu tamanho ícone, assim os tamanhos prestados são diferentes:

235
Se você começar a explorar os bitmaps de ícone nos projetos iOS e Android, você pode ser um pouco confusos: parece haver vários
mapas de bits com os mesmos nomes (ou nomes semelhantes) no iOS e Android projetos.
Por hora de mergulhar mais profundamente no assunto da resolução bitmap.

Resoluções de bitmap
O nome do arquivo bitmap iOS especificado no PlatformBitmaps é Icon-Small-40.png, mas se você olhar na pasta recursos do projeto
iOS, você verá três arquivos com variações desse nome. Todos eles têm tamanhos diferentes:
• Icon-Small-40.png - 40 pixels quadrados
• Icon-Small-40@2x.png - 80 pixels quadrados
• Icon-Small-40@3x.png - 120 pixels quadrado

Como você descobriu no início deste capítulo, quando uma imagem é uma criança de um StackLayout, iOS exibe o bitmap em seu
tamanho do pixel com um mapeamento um-para-um entre os pixels do bitmap e os pixels da tela. Esta é a exibição óptima de um mapa
de bits.
No entanto, no simulador iPhone 6 utilizadas na captura de tela, a imagem tem um tamanho processado de 40 unidades independentes
de sitivo. No iPhone 6 há dois pixels por unidade independente do dispositivo, o que significa que o bitmap real que está sendo exibido
na tela que não é Icon-Small-40.png, mas Icon-Small-40@2x.png, que é duas vezes 40, ou 80 pixels quadrado.
Se você em vez disso executar o programa no iPhone 6 Plus-que tem uma unidade independente de dispositivo igual a três pixels, pois
você novamente ver um tamanho processado de 40 pixels, o que significa que o bitmap Icon-Small-40@3x.png é exibido. Agora tente
isso no simulador iPad 2. O iPad 2 tem um tamanho de tela de apenas 768 × 1024, e unidades independentes de dispositivo são os
mesmos pixels. Agora, o bitmap Icon-Small-40.png é exibido, eo tamanho processado ainda é 40 pixels.
Isso é o que você quer. Você quer ser capaz de controlar o tamanho processado de bitmaps em unidades pendentes de dispositivo de
dente, porque é assim que você pode atingir tamanhos de bitmap sensivelmente semelhantes em diferentes dispositivos e plataformas.
Quando você especifica o bitmap 40.png Icon-Small-, você quer que bitmap para ser processado como 40 independentes de dispositivo
unidades, ou cerca de um quarto de polegada-em todos os dispositivos iOS. Mas se o programa está sendo executado em um dispositivo
Apple Retina, você não quer um bitmap 40-pixel quadrado esticado para ser de 40 unidades independentes de sitivo. Por fidelidade
visual máximo, você quer um bitmap maior resolução apresentada, com um mapeamento um-para-um dos pixels de bitmap para a tela
pixels.
Se você olhar no diretório Resources Android, você vai encontrar quatro versões diferentes de um bitmap chamado icon.png. Estes são
armazenados em diferentes subpastas de recursos:
• drawable / icon.png - 72 pixels quadrados
• drawable-hdpi / icon.png - 72 pixels quadrados
• drawable-xdpi / icon.png - 96 pixels quadrados
• drawable-xxdpi / icon.png - 144 pixels quadrado

Independentemente do dispositivo Android, o ícone é processado com um tamanho de 48 unidades independentes de dispositivo. No
Nexus 5 utilizadas na captura de tela, há três pixels para a unidade independente do dispositivo, o que significa que o bitmap realmente
exibido nessa tela é a única na pasta drawable-xxdpi, que é de 144 pixels quadrado.
O que é agradável sobre ambos iOS e Android é que você só precisa fornecer bitmaps de vários tamanhos-e dar-lhes os nomes corretos
ou armazená-los nas pastas corretas e o sistema operacional escolhe a melhor imagem para a resolução específica do dispositivo.
A plataforma Windows Runtime tem uma facilidade similar. No projeto UWP você verá nomes de arquivos que incluem escala-200; por
exemplo, Square150x150Logo.scale-200.png. O número após a escala palavra é uma percentagem, e embora o nome do arquivo parece

236
indicar que este é um mapa de bits de 150 x 150, a imagem é na verdade, duas vezes maior: 300 × 300. No projeto do Windows você
verá nomes de arquivos que incluem escala-100 e no projeto WinPhone você verá scale-240.
No entanto, você viu que Xamarin.Forms no Windows Runtime exibe bitmaps em seus tamanhos de- independente de vício, e você ainda
vai precisar para tratar as plataformas Windows um pouco diferente. Mas em todas as três plataformas que você pode controlar o
tamanho de bitmaps em unidades independentes de dispositivo.
Ao criar suas próprias imagens específicas da plataforma, siga as orientações nas próximas três seções.

Bitmaps independentes de dispositivo para iOS


O esquema de nomenclatura iOS para bitmaps envolve um sufixo no nome de arquivo. O sistema operacional busca um bitmap em
particular com o nome do arquivo subjacente baseada na resolução de pixel aproximada do dispositivo:
• Sem sufixo para dispositivos 160 dpi (1 pixel para a unidade independente de dispositivo)
• @ 2x sufixo para 320 dispositivos DPI (2 pixels para a DIU)
• @ 3x sufixo: 480 dispositivos DPI (3 pixels para a DIU)

Por exemplo, suponha que você queira um bitmap chamado myimage.jpg a mostrar-se como cerca de uma polegada quadrada na tela.
Você deve fornecer três versões deste bitmap:
• myimage.jpg - 160 pixels quadrado
• MyImage@2x.jpg~~number=plural - 320 pixels quadrado
• MyImage@3x.jpg~~number=plural - 480 pixels quadrado

O bitmap serão renderizados como 160 unidades independentes de dispositivo. Para tamanhos prestados menor que uma polegada, de-
vinco os pixels proporcionalmente.
Ao criar esses bitmaps, comece com o maior. Então você pode usar qualquer utilitário de edição de bitmap para reduzir o tamanho do
pixel. Para algumas imagens, você pode querer ajustar ou completamente redesenhar as versões menores.
Como você deve ter notado ao examinar os vários arquivos de ícone que o modelo Xamarin.Forms inclui com o projeto iOS, nem todo
bitmap vem em todas as três resoluções. Se iOS não consegue encontrar um bitmap com o sufixo particular, ele quer, ele vai cair para
trás e usar um dos outros, escalando o bitmap cima ou para baixo no processo.

Bitmaps independentes de dispositivo para Android


Para Android, bitmaps são armazenados em vários subpastas de recursos que correspondem a um pixel de resolução de tela. Android
define seis nomes de diretórios diferentes para seis níveis diferentes de dispositivo de resolução:
• drawable-LDPI (baixo DPI) para 120 dispositivos DPI (0,75 pixels ao DIU)
• drawable-mdpi (médio) para dispositivos de 160 dpi (1 pixel ao DIU)
• drawable-hdpi (alto) para 240 dispositivos DPI (1,5 pixels ao DIU))
• drawable-xhdpi (extra alta) para 320 dispositivos DPI (2 pixels para a DIU)
• drawable-xxhdpi (extra extra alta) para 480 dispositivos DPI (3 pixels para a DIU)
• drawable-xxxhdpi (três elevações adicionais) para dispositivos de 640 dpi (4 pixels para o DIU)

Se você quer um bitmap chamado myimage.jpg para renderizar como um quadrado de uma polegada na tela, você pode fornecer até
seis versões deste bitmap usando o mesmo nome em todos esses diretórios. O tamanho deste mapa de bits de uma polegada quadrada
em pixels é igual a DPI associada a esse diretório:
• drawable-LDPI / myimage.jpg - 120 pixels quadrado
• drawable-mdpi / myimage.jpg - 160 pixels quadrado
• drawable-hdpi / myimage.jpg - 240 pixels quadrado
• drawable-xhdpi / myimage.jpg - 320 pixels quadrado
• drawable-xxdpi / myimage.jpg - 480 pixels quadrado
• drawable-xxxhdpi / myimage.jpg - 640 pixels quadrado

O bitmap serão renderizados como 160 unidades independentes de dispositivo.


Você não é obrigado a criar mapas de bits para as seis resoluções. O projeto Android criado pelo molde Xamarin.Forms inclui apenas
amovível-HDPI, amovível-xhdpi, e amovível-xxdpi, bem como uma pasta amovível desnecessária sem sufixo. Estas abrangem os
dispositivos mais comuns. Se o sistema operacional Android não encontrar um bitmap da resolução desejada, ele vai cair de volta para
um tamanho que está disponível e escalá-lo.

Bitmaps independentes de dispositivo para plataformas Windows


Runtime
O Windows Runtime suporta um esquema de bitmap de nomeação que permite incorporar um fator de escala de pixels por unidade
independente de dispositivo expresso como uma percentagem. Por exemplo, por um mapa de bits de uma polegada quadrada apontado
a um dispositivo que tem dois pixels para o aparelho, utilizar o nome:

237
• MyImage.scale-200.jpg - 320 pixels quadrado

A documentação do Windows não é clara sobre as percentagens reais que você pode usar. Quando a construção de um programa, às
vezes você verá mensagens de erro na janela de saída em relação percentagens que não são suportados na plataforma particular.
No entanto, dado que Xamarin.Forms exibe bitmaps Windows Runtime em seus tamanhos independentes de dispositivo, esta facilidade
é de uso limitado nesses dispositivos.
Vamos olhar para um programa que realmente faz fornecer bitmaps personalizados de vários tamanhos para as três plataformas. Esses
bitmaps são destinados a serem prestados cerca de uma polegada quadrada, o que é aproximadamente metade da largura da tela do
telefone no modo retrato.
Este programa ImageTap cria um par de objetos semelhantes a botões rudimentares, tappable que não exibir o texto, mas um bitmap.
Os dois botões que ImageTap cria pode substituir a tradicionais botões OK e Cancelar, mas talvez você queira usar rostos de pinturas
famosas para os botões. Talvez você deseja que o botão OK para exibir a face de Vênus de Botticelli e o botão para exibir o homem
tressed dis- no de Edvard Munch O Grito Cancelar.
No código de exemplo para este capítulo é um diretório chamado Imagens que contém essas imagens, nomeado Venus_xxx.jpg e
Scream_xxx.jpg, onde o xxx indica o tamanho do pixel. Cada imagem é em oito tamanhos diferentes: 60, 80, 120, 160, 240, 320, 480, e
640 pixels quadrado. Além disso, alguns dos arquivos têm nomes de Venus_xxx_id.jpg e Scream_xxx_id.jpg. Essas versões têm o
tamanho de pixel real exibido no canto inferior direito da imagem, de modo que podemos ver na tela exatamente o bitmap o sistema
operativo tenha selecionado.
Para evitar confusão, os bitmaps com os nomes originais foram adicionados às pastas do projeto ImageTap primeiro lugar, e em seguida,
eles foram renomeados dentro do Visual Studio.
Na pasta Recursos do projeto iOS, os seguintes arquivos foram renomeados:
• Venus_160_id.jpg tornou Venus.jpg
• Venus_320_id.jpg porque Venus@2x.jpg
• Venus_480_id.jpg tornou Venus@3x.jpg

Isso foi feito da mesma forma para os bitmaps Scream.jpg.


Nas várias subpastas da pasta Recursos do projeto Android, os seguintes arquivos foram renomeados:
• Venus_160_id.jpg tornou drawable-mdpi / Venus.jpg
• Venus_240_id.jpg tornou drawable-hdpi / Venus.jpg
• Venus_320_id.jpg tornou drawable-xhdpi / Venus.jpg
• Venus_480_id.jpg tornou drawable_xxhdpi / Venus.jpg

E da mesma forma para os bitmaps Scream.jpg.


Para a 8.1 projecto Windows Phone, os arquivos Venus_160_id.jpg e Scream_160_id.jpg foram copiados a uma pasta Imagens e
renomeado Venus.jpg e Scream.jpg.
O projeto do Windows 8.1 cria um executável que é executado não em telefones, mas em tábuas e desktops. Estes dispositivos têm
tradicionalmente assumiu uma resolução de 96 unidades por polegada, de modo que o _100_id.jpg Vênus e arquivos Scream_100_id.jpg
foram copiados para uma pasta de Imagens e renomeado Venus.jpg e Scream.jpg.
O projeto UWP tem como alvo todos os fatores de forma, por isso vários bitmaps foram copiados para uma pasta de Imagens e
renomeado para que os bitmaps quadrados de 160 pixels seriam usados em telefones, e os bitmaps quadrados de 100 pixels seria
usado em tablets e telas de desktop:
• Venus_160_id.jpg tornou Venus.scale-200.jpg
• Venus_100_id.jpg tornou Venus.scale-100.jpg E da mesma forma para os bitmaps Scream.jpg.

Cada um dos projetos requer um Build Action diferente para esses bitmaps. Isso deve ser definido automaticamente quando você
adiciona os arquivos para os projetos, mas você definitivamente querer verificar novamente para garantir que o Build Action está definido
corretamente:
• iOS: BundleResource
• Android: AndroidResource
• Windows Runtime: Conteúdo

Você não tem que memorizar estes. Em caso de dúvida, basta verificar a criar ação para os bitmaps incluí- pelo modelo de solução
Xamarin.Forms nos projetos de plataforma.
O arquivo XAML para o programa ImageTap coloca cada um dos dois elementos de imagem em um ContentView que é de cor branca
de um estilo implícito. Este ContentView branco é inteiramente coberto pela imagem, mas (como você verá) que entra em jogo quando
o programa pisca a imagem para sinalizar que tem sido aproveitado.

238
O arquivo XAML usa OnPlatform para selecionar os nomes dos ficheiros de recursos da plataforma. Observe que o atributo x:
TypeArguments de OnPlatform está definido para ImageSource porque este tipo deve corresponder exatamente o tipo da propriedade
de destino, que é a propriedade Fonte de imagem. ImageSource define uma conversão implícita de corda para si, por isso, especificando
os nomes dos arquivos é suficiente. (A lógica para essa conversão implícita primeiro verifica se a string tem um prefixo URI. Se não, ele
assume que a string é o nome de um arquivo incorporado no projeto de plataforma.)
Se você quiser evitar o uso de OnPlatform inteiramente em programas que usam bitmaps plataforma, você pode colocar os mapas de
bits do Windows no diretório raiz do projeto, em vez de em uma pasta.
Se tocar num destes botões faz duas coisas: O manipulador de Tapped define a propriedade opacidade da imagem para 0,75, o que
resulta em revelar parcialmente o fundo ContentView branco e simulem um flash. Um temporizador restaura a opacidade para o valor
padrão de um décimo de segundo mais tarde. O manipulador de Tapped também exibe o tamanho processado do elemento Image:

239
Isso tamanho processado comparados com os tamanhos de pixel sobre os bitmaps confirma que as três plataformas de fato selecionaram
o bitmap ideal:

Estes botões ocupar aproximadamente metade da largura da tela em todas as três plataformas. Este dimensionamento é inteiramente
baseado no tamanho dos próprios mapas de bits, sem qualquer informação adicional de colagem ou no código de marcação.

Barras de ferramentas e seus ícones


Um dos principais usos de bitmaps na interface do usuário é a barra de ferramentas Xamarin.Forms, que aparece na parte superior da
página no iOS e dispositivos Android e, na parte inferior da página em dispositivos Windows Phone. Itens da barra de ferramentas são
tappable e fogo clicado eventos bem como Button.
Não há classe para si barra de ferramentas. Em vez disso, você adiciona objetos do tipo ToolbarItem ao ToolbarItems propriedade de
coleção definida por página.
A classe ToolbarItem não deriva de ver como Etiqueta e Button em vez deriva do elemento por meio de MenuItemBase e MenuItem.
(MenuItem é usado somente em conexão com o TableView e não será discutida até o Capítulo 19.) Para definir as características de um
item de barra de ferramentas, use as seguintes propriedades:
• Texto - o texto que pode aparecer (dependendo da plataforma e da Ordem)
• Icon - um objeto FileImageSource referência a um bitmap do projeto de plataforma
• Ordem - um membro da enumeração ToolbarItemOrder: Padrão, Primary, ou Secundary

Há também uma propriedade de nome, mas ele só duplica a propriedade de texto e deve ser considerado obsoleto.
A propriedade Ordem governa se o ToolbarItem aparece como uma imagem (primária) ou texto (Secundário). O Windows Phone e
Windows 10 plataformas móveis são limitados a quatro itens primários, e tanto a dispositivos iPhone e Android começam a ficar lotada
com mais do que isso, então isso é uma limitação razoável. Itens secundários adicionais são apenas texto. No iPhone aparecem por
240
baixo os itens primários; no Android e Windows Phone eles não são vistos na tela até que o usuário toca umas reticências vertical ou
horizontal.
A propriedade Icon é crucial para itens primários, ea propriedade de texto é crucial para os itens secundários, mas o tempo de execução
do Windows também usa texto para exibir uma dica de texto curto por baixo dos ícones para itens primários.
Quando o ToolbarItem é aproveitado, ele dispara um evento clicado. ToolbarItem também tem comando e CommandParameter
propriedades como o Button, mas estes são para fins de ligação de dados e será demonstrado em um capítulo posterior.
A coleção ToolbarItems definido pela Página é do tipo IList <ToolbarItem>. Uma vez que você adicionar um ToolbarItem a esta coleção,
as propriedades ToolbarItem não pode ser alterado. As configurações de propriedade em vez disso são usados internamente para
construir objetos específicos da plataforma.
Você pode adicionar ToolbarItem objecções ao ContentPage no Windows Phone, mas iOS e Android barras de ferramentas rígidas para
a NavigationPage ou para uma página navegada de uma NavigationPage. Felizmente, este requisito não significa que todo o tema da
navegação de página precisa ser discutido antes que você pode usar a barra de ferramentas. Instanciar um NavigationPage em vez de
um ContentPage envolve simplesmente chamar o construtor NavigationPage com o objeto ContentPage recém-criado na classe App.
O programa ToolbarDemo reproduz a barra de ferramentas que você viu nas imagens no Capítulo 1. O ToolbarDemoPage deriva
ContentPage, mas a classe App passa o objeto ToolbarDemoPage para um construtor NavigationPage:

Isso é tudo que é necessário para obter a barra de ferramentas para trabalhar com iOS e Android, e tem algumas outras implicações
também. Um título que você pode definir com a propriedade Título da página é exibida na parte superior das telas iOS e Android, eo
ícone do aplicativo também é exibido na tela do Android. Outro resultado do uso NavigationPage é que você não precisa mais para
definir algum estofamento na parte superior da tela do iOS. A barra de status está agora fora da faixa da página do aplicativo.
Talvez o aspecto mais difícil de usar ToolbarItem está montando as imagens bitmap para a propriedade ícone. Cada plataforma tem
exigências diferentes para a composição de cores e tamanho destes ícones, e cada plataforma tem um pouco diferentes convenções
para o imaginário. O ícone padrão para partilhar, por exemplo, é diferente em todas as três plataformas.
Por estas razões, faz sentido para cada um dos projectos de plataformas para ter sua própria coleção de ícones da barra de ferramentas,
e é por isso Ícone é do tipo FileImageSource.
Vamos começar com as duas plataformas que oferecem coleções de ícones adequados para ToolbarItem.

Ícones para Android.


O site Android tem uma coleção download de ícones da barra de ferramentas disponíveis neste URL:
http://developer.android.com/design/downloads
Baixe o arquivo ZIP identificado como Barra de ação Icon Pack.
O conteúdo descompactados estão organizados em dois diretórios principais: Core_Icons (23 imagens) e ação ícones da barra (144
imagens). Estes são todos os arquivos PNG, ea barra de ícones de ação vêm em quatro tamanhos diferentes, indicados pelo nome do
diretório:
• drawable-mdpi (médio DPI) - 32 pixels quadrados
• drawable-hdpi (alta DPI) - 48 pixels quadrados
• drawable-xhdpi (extra alta DPI) - 64 pixels quadrados
• drawable-xxhdpi (DPI alta extra extra) - 96 pixels quadrados

Estes nomes de diretório são as mesmas que as pastas de recursos em seu projeto Android e implica que os ícones da barra de
ferramentas render em 32 unidades independentes de dispositivo, ou cerca de um quinto de polegada.
A pasta core_Icons também organiza seus ícones em quatro diretórios com os mesmos quatro tamanhos, mas estes diretórios são
nomeados mdpi, hdpi, xdpi, e sem escala.
A pasta de Ação Bar Icons tem uma organização diretório adicional usando os nomes holo_dark
e holo_light:
• imagem de primeiro plano holo_dark-branco sobre um fundo transparente
• imagem de primeiro plano holo_light-preto em um fundo transparente

A palavra "holo" significa "holográfico", e refere-se ao nome usa o Android para os seus temas de cores. Alt Hough os ícones holo_light
são muito mais fáceis de ver no Finder e Windows Explorer, para a maioria dos fins (e especialmente para itens da barra de ferramentas),
você deve usar os ícones holo_dark. (Claro, se você sabe como alterar o seu tema aplicativo no arquivo AndroidManifest.xml, então elas
provavelmente também sabem usar a outra cobrança de ícone.)

241
A pasta core_Icons contém apenas ícones com foregrounds brancas sobre um fundo transparente.
Para o programa ToolbarDemo, três ícones foram escolhidos a partir do diretório holo_dark em todas as quatro resoluções. Estes foram
copiados para as subpastas apropriadas do diretório de recursos no projeto Android:
• A partir do diretório 01_core_edit, os arquivos chamado ic_action_edit.png
• A partir do diretório 01_core_search, os arquivos chamado ic_action_search.png
• A partir do diretório 01_core_refresh, os arquivos chamado ic_action_refresh.png

Verifique as propriedades desses arquivos PNG. Eles devem ter um Build Action do AndroidResource. Ícones para as plataformas
Windows Runtime
Se você tiver uma versão do Visual Studio instalado para Windows Phone 8, você pode encontrar uma coleção de arquivos PNG
adequado para ToolbarItem no seguinte diretório em seu disco rígido:
C: \ Arquivos de Programas (x86) \ Microsoft SDKs \ Windows Phone \ v8.0 \ ícones que você pode usar estes para todas as plataformas
Windows Runtime.
Há dois subdiretórios, claro e escuro, cada um contendo os mesmos 37 imagens. Tal como acontece com An- droid, os ícones no diretório
escuro têm foregrounds brancas em fundos transparentes, e os ícones no diretório Luz tem foregrounds pretas em fundos transparentes.
Você deve usar os do diretório escuro para Windows Phone 8.1 e o diretório Luz para Windows 10 Mobile.
As imagens são um quadrado uniformes 76 pixels, mas foram projetados para aparecer dentro de um círculo. De fato, um dos arquivos
é nomeado basecircle.png, que pode servir como um guia se você gostaria de projetar seu próprio, portanto, não são realmente apenas
36 ícones utilizáveis na recolha e alguns deles são os mesmos.
Geralmente, em um projeto Windows Runtime, arquivos como estes são armazenados na pasta Assets (que já existe no projeto) ou uma
pasta chamada Images. Os seguintes bitmaps foram adicionados a uma pasta idades Im- em todas as três plataformas Windows:
• edit.png
• feature.search.png
• refresh.png

Para a plataforma Windows 8.1 (mas não o Windows Phone 8.1 plataforma), os ícones são necessários para todos os itens da barra de
ferramentas, então as seguintes bitmaps foram adicionados à pasta Imagens desse projeto:
• Icon1F435.png
• Icon1F440.png
• Icon1F52D.png

Estes foram gerados em um programa do Windows a partir da fonte Segoe UI Symbol, que suporta caracteres emoji. O número
hexadecimal de cinco dígitos no nome do arquivo é o ID Unicode para os caracteres.
Quando você adicionar ícones para um projeto Windows Runtime, verifique se o Build Action é o conteúdo. Ícones para dispositivos iOS
Esta é a plataforma mais problemático para ToolbarItem. Se você está programando diretamente para a API nativa iOS, um grupo de
constantes permitem que você selecione uma imagem para UIBarButtonItem, que é a implementação iOS subjacente de ToolbarItem.
Mas para o Xamarin.Forms ToolbarItem, você vai precisar para obter ícones de outra fonte, talvez licenciamento de uma coleção como
a que está em glyphish.com, ou fazer o seu próprio.
Para melhores resultados, você deve fornecer dois ou três arquivos de imagem para cada item da barra de ferramentas na pasta
Recursos. Uma imagem com um nome de arquivo, como image.png deve ser de 20 pixels quadrados, enquanto a mesma imagem
também deve ser fornecido em uma dimensão de 40-pixel quadrado com o nome image@2x.png e como um bitmap 60- pixel quadrado
chamado image@3x.png.
Aqui está uma coleção de ícones de utilização livre livres utilizados para o programa no Capítulo 1 e para o programa ToolbarDemo
neste capítulo:
http://www.smashingmagazine.com/2010/07/14/gcons-free-all-purpose-icons-for-designers-and-de- velopers-100-icons-psd/ 

No entanto, eles são uniformemente 32 pixels quadrados, e alguns básicos estão em falta. Independentemente disso, os seguintes aos
três bitmaps foram copiados para a pasta Resources no projeto iOS sob a suposição de que eles serão devidamente dimensionados:
• edit.png
• search.png
• reload.png

Outra opção é usar ícones Android a partir do diretório holo_light e dimensionar a maior imagem para os vários tamanhos iOS.
Para ícones da barra de ferramentas em um projeto do iOS, o Criar ação deve ser BundleResource.
Aqui está o arquivo ToolbarDemo XAML mostrando os vários objetos ToolbarItem adicionados à coleção ToolbarItems da página. O x:
TypeArguments atributo para OnPlatform deve ser FileImageSource neste caso, porque esse é o tipo da propriedade Ícone de
ToolbarItem. Os três itens marcados como secundário têm apenas o conjunto de propriedades de texto e não a propriedade ícone.
O elemento raiz tem uma propriedade título definido na página. Isso é exibido nas telas iOS e Android quando a página é instanciada
como um NavigationPage (ou navegou a partir de uma página navegação-):

242
Embora o elemento OnPlatform implica que existem os ícones secundárias para todas as plataformas Windows Runtime, eles não fazem,
mas nada de ruim acontece se o arquivo de ícone particular, está faltando no projeto.
Todos os eventos Clicked têm o mesmo processador atribuído. Você pode usar manipuladores exclusivos para os itens, é claro. Este
manipulador apenas exibe o texto da ToolbarItem usando a etiqueta centrado:

As imagens mostram os itens ícone da barra (e para iOS, os itens de texto) e o rótulo centrado com o item mais recentemente clicado:

243
Se tocar nas reticências na parte superior da tela Android ou nas reticências no canto inferior direito da tela móvel do Windows 10, os
itens de texto são exibidos e, além disso, os itens de texto associados com os ícones também são exibidos no Windows 10 mobile:

Independentemente da plataforma, a barra de ferramentas é a maneira padrão para adicionar comandos comuns a um aplicativo de
telefone.

Imagens de botão
Botão define uma propriedade de imagem do tipo FileImageSource que você pode usar para fornecer uma imagem pequena plemental
apoio que é exibido à esquerda do texto do botão. Este recurso não se destina a uma idade somente botão im-; se é isso que você quer,
o programa ImageTap neste capítulo é um bom ponto de partida.
Você quer as imagens para ser cerca de um quinto de polegada de tamanho. Isso significa que você quer que eles se render em 32
unidades independentes de dispositivo e mostrar-se contra o fundo do botão. Para iOS eo UWP, isso significa que uma imagem em preto
sobre um fundo branco ou transparente. Para Android, Windows 8.1, e Windows Phone 8.1, você vai querer uma imagem em branco
contra um fundo transparente.
Todos os bitmaps no projeto ButtonImage são do diretório Barra de ação do Android de- assinar coleção Icons ea 03_rating_good e
03_rating_bad subdiretórios. Estes são "polegares para cima" e "polegares para baixo" imagens.
As imagens são iOS a partir do diretório holo_light (imagens em preto sobre fundos transparentes) com as seguintes conversões de
nome de arquivo:
• drawable-mdpi / ic_action_good.png não renomeado
• drawable-xhdpi / ic_action_good.png renomeado para ic_action_good@2x.png~~V E da mesma forma para
ic_action_bad.png.

As imagens do Android estão a partir do diretório holo_dark (imagens brancas em fundos transparentes) e incluem todos os quatro
tamanhos dos subdiretórios drawable-mdpi (32 pixels quadrados), drawable-hdpi (48 pixels), drawable-xhdpi (64 pixels) e drawable -
xxhdpi (96 pixels quadrado).
As imagens para os vários projectos Windows Runtime são todos uniformemente o bitmaps de 32 de pixel dos diretórios drawable-mdpi.

244
Aqui está o arquivo XAML que define a propriedade do ícone por dois elementos de botão:

E aqui estão elas:

Não é muito, mas o bitmap acrescenta um pouco de brio para o botão normalmente somente texto.
Outro uso importante para pequenos bitmaps é o menu de contexto disponível para itens na TableView. Mas um pré-requisito para que
é uma exploração profunda das várias vistas que contribuem para a interface interactiva de Xamarin.Forms. Que está chegando no
Capítulo 15.
Mas primeiro vamos olhar para uma alternativa para StackLayout que lhe permite vistas criança de posição de uma forma completamente
flexível.

245
Capítulo 18
MVVM
Você pode se lembrar de suas primeiras experiências com programação? É provável que o seu principal objetivo foi apenas começando
o funcionamento do programa e, em seguida, fazê-lo funcionar corretamente. Você provavelmente não pensa muito sobre a organização
ou estrutura do programa. Isso foi algo que veio mais tarde.
A indústria de computadores como um todo passou por uma evolução similar. Como desenvolvedores, todos nós agora percebemos que
uma vez que um aplicativo começa a crescer em tamanho, é geralmente uma boa idéia de impor algum tipo de estrutura ou arquitetura
no código. A experiência com este processo sugere que muitas vezes é melhor começar a pensar sobre essa arquitetura, talvez, antes
de qualquer código é escrito em tudo. Na maioria dos casos, a estrutura do programa desejável esforça-se para uma "separação de
interesses" através da qual diferentes peças do foco do programa em diferentes tipos de tarefas.
Em um programa de gráfica interativa, uma técnica óbvia é a de separar a interface do usuário da lógica não-interface de usuário
subjacente, às vezes chamado de lógica de negócios. A primeira descrição formal de tal arquitetura de interfaces gráficas de usuário foi
chamado Model-View-Controller (MVC), mas isso chitecture ar- desde então tem dado origem a outros derivados a partir dele.
Em certa medida, a natureza da própria interface de programação influencia a aplicação arquitetural. Por exemplo, uma interface de
programação que inclui uma linguagem de marcação com ligações de dados podem sugerir uma determinada forma de estruturar uma
aplicação.
Há de fato um modelo de arquitetura que foi projetado especificamente com XAML em mente. Isto é conhecido como Model-View-
ViewModel ou MVVM. Este capítulo aborda os conceitos básicos de MVVM (incluindo a interface de comando), mas você verá mais
sobre MVVM no próximo capítulo, que abrange “collection views”. Além disso, alguns outros recursos de Xamarin.Forms são muitas
vezes utilizados em conjunto com MVVM; esses recursos incluem gatilhos e comportamentos, e eles são o assunto do Capítulo 23.

Inter-relacionamentos MVVM
MVVM divide um aplicativo em três camadas:
O modelo fornece dados subjacentes, às vezes envolvendo arquivo ou web acessos.
O ViewModel liga o Modelo a. Ele ajuda a gerenciar os dados do modelo para torná-lo mais receptivo à Vista, e vice-versa.
A vista é a camada de interface do usuário ou apresentação, geralmente implementado em XAML.
O modelo não conhece o ViewModel. Em outras palavras, o modelo não sabe nada sobre as propriedades publicas e métodos do
ViewModel, e certamente nada sobre seu funcionamento interno. Da mesma forma, o ViewModel não conhece a View. Se toda a
comunicação entre as três camadas ocorre por meio de chamadas de método e propriedade acessos, em seguida, chama em uma única
direção são permitidos. O View só faz chamadas para o ViewModel ou acessa propriedades do ViewModel eo ViewModel semelhante
só faz chamadas para o modelo ou acessa Propriedades modelo:

Estas chamadas de método permitem que a View para obter informações do ViewModel, que por sua vez recebe informações do modelo.
Em ambientes modernos, no entanto, os dados são muitas vezes dinâmicos. Muitas vezes, o modelo vai obter mais ou mais recente
dados que devem ser comunicados ao ViewModel e eventualmente para a View. Por esta razão, o Vista pode anexar manipuladores de
eventos que estão sendo implementados no ViewModel eo ViewModel pode anexar manipuladores de eventos definidos pelo modelo.
Isto permite uma comunicação bidireccional, escondendo a View do ViewModel e o ViewModel do Modelo:

MVVM foi projetado para tirar vantagem de XAML e ligações de dados particularmente baseados em XAML. Geralmente, a vista “page
class” que usa XAML para construir a interface do usuário. Portanto, a conexão entre a View e o ViewModel consiste em grande parte,
e talvez exclusivamente, de ligações de dados baseados em XAML:

246
Os programadores que estão muito apaixonados por MVVM muitas vezes têm uma meta informal de expressar todas as interações entre
o Vista eo ViewModel em uma classe página com ligações de dados baseada em XAML, e no processo de reduzir o código da página
de arquivo para um simples código subjacente chamada InitializeComponent. Este objetivo é difícil de alcançar na programação da vida
real, mas é um prazer quando isso acontece.
Pequenos programas, tais como aqueles em um livro como este, muitas vezes tornam-se maiores quando MVVM é introduzido. Não
deixe que isso desencorajar o seu uso do MVVM! Utilize os exemplos aqui para ajudá-lo a determinar como MVVM pode ser usado em
um programa maior, e se você verá que isto ajuda enormemente na arquitetura de suas aplicações.

Data binding e ViewModels


Em muitas manifestações bastante simples de MVVM, o Modelo está ausente, ou apenas implícita, e o ViewModel contém toda a lógica
de negócios. O View e ViewModel comunicam-se através de ligações de dados baseados em XAML. Os elementos visuais do View são
alvos de ligação de dados, e propriedades no ViewModel e ligam as dados fontes.
Idealmente, um ViewModel deve ser independente de qualquer plataforma específica. Esta independência permite ViewModels para ser
compartilhado entre outros ambientes baseados em XAML (como o Windows), além de Xamarin.Forms. Por esta razão, você deve tentar
evitar o uso a seguinte declaração em seus modelos Ver:
using Xamarin.Forms;
Essa regra é freqüentemente quebrada neste capítulo! Um dos ViewModels baseia-se na estrutura de cores do Xamarin.Forms, e outra
utiliza Device.StartTimer. Então, vamos chamar algo específico para Xamarin.Forms no ViewModel uma "sugestão" ao invés de uma
"regra".
elementos visuais do View qualificam como alvos de ligação de dados, porque as propriedades desses elementos visuais são apoiados
por propriedades que podem ser ligadas. Para ser uma fonte de ligação de dados, um ViewModel deve iomplementar um protocolo de
notificação para sinalizar quando uma propriedade em ViewModel mudou. Este protocolo de notificação é a interface
INotifyPropertyChanged, que é definido no “namespace” System.Component-Model de forma muito simples, com apenas um evento:

public interface INotifyPropertyChanged{

event PropertyChangedEventHandler PropertyChanged;


}

A interface INotifyPropertyChanged é tão central para mvvm que em discussões informais a interface é muitas vezes abreviada como
INPC.
O evento PropertyChanged na interface INotifyPropertyChanged é do tipo Property-Changed-EventHandler. Um manipulador para este
manipulador de eventos PropertyChanged recebe uma instância da classe PropertyChangedEventArgs, que define uma única
propriedade chamada PropertyName do tipo string indicando o que propriedade em ViewModel mudou. O manipulador de eventos pode
acessar essa propriedade.
Uma classe que implementa INotifyPropertyChanged deve disparar um evento PropertyChanged sempre que uma propriedade for
alterada, mas a classe não deve acionar o evento quando a propriedade é meramente definido, mas não mudou.
Algumas classes definem propriedades de propriedades que são inicializados no construtor e são imutáveis. Essas propriedades não
precisa acionar eventos PropertyChanged porque um manipulador dades ertyChanged pode ser ligado somente após o código no
construtor termina, e estas propriedades nunca mudam após esse tempo.
Em teoria, uma classe ViewModel pode ser derivada de bindableobject e implementar as suas propriedades públicas como objetos
BindableProperty. Bindableobject implementa INotifyPropertyChanged e automaticamente dispara um evento PropertyChanged quando
qualquer propriedade apoiado por uma BindableProperty mudanças. Mas decorrente bindableobject é um exagero para um ViewModel.
Porque bindableobject e BindableProperty são específicos para Xamarin.Forms, tal ViewModel já não é plataforma Independentes, ea
técnica não oferece vantagens reais sobre uma implementação mais simples de INotify- PropertyChanged.

Um relógio ViewModel
Suponha que você está escrevendo um programa que precisa de acesso para a data e hora atual, e que você gostaria de usar essa
informação através de ligações de dados. A biblioteca de classes base do .NET fornece data e hora informações através da estrutura
DateTime. Para obter a data e hora atual, basta acessar a propriedade DateTime.Now. Essa é a forma habitual de escrever uma aplicação
do relógio.

247
Mas para fins de ligação de dados, DateTime tem uma falha grave: Ele fornece apenas informações estáticas sem notificação quando a
data ou a hora mudou.
No contexto da MVVM, a estrutura DateTime, talvez, qualifica-se como um modelo no sentido de que DateTime fornece todos os dados
que precisamos, mas não de uma forma que seja propícia para a união de dados. É necessário escrever um ViewModel que faz uso de
DateTime, mas fornece notificações quando a data ou hora mudou.
A biblioteca Xamarin.FormsBook.Toolkit contém a classe DateTimeViewModel mostrado abaixo. A classe tem apenas uma propriedade,
que é nomeado DateTime do tipo DateTime, mas esta propriedade muda dinamicamente como resultado de chamadas frequentes para
DateTime.Now em uma chamada de retorno Device.StartTimer.
Observe que a classe DateTimeViewModel é baseado na interface INotifyPropertyChanged e inclui uma diretiva using para o namespace
System.ComponentModel que define esta interface. Para implementar essa interface, a classe define um evento público chamado
PropertyChanged.
Cuidado com: É muito fácil definir um evento PropertyChanged em sua classe sem explicitamente especificar qual a classe que
implementa INotifyPropertyChanged! As notificações serão ignorados se você não especificar explicitamente que a classe é baseada na
interface INotifyPropertyChanged:
using System;
using System.ComponentModel;
using Xamarin.Forms;

public class DateTimeViewModel : INotifyPropertyChanged


Chapter 18 MVVM 495
DateTime dateTime = DateTime.Now;
}
{
public event PropertyChangedEventHandler PropertyChanged;
public DateTimeViewModel()
{
}
Device.StartTimer(TimeSpan.FromMilliseconds(15), OnTimerTick);
bool OnTimerTick()
{
DateTime = DateTime.Now;
return true;
}
public DateTime DateTime
{
private set
{
if (dateTime != value)
{
dateTime = value;
// Fire the event.
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
}
{
}
return dateTime;

248
}

A única propriedade pública nesta classe é chamado de DateTime do tipo DateTime, e está associada com um campo de backup particular chamado
dateTime. As propriedades públicas em ViewModels geralmente têm campos de apoio privados. O acessor set da propriedade DateTime é privado para
a classe, e é atualizado a cada 15 milhões de milisegundos do retorno de chamada do timer.
Fora isso, o acessor conjunto é construído de uma forma muito padrão para ViewModels: ela primeiro verifica se o valor a ser definido para a propriedade
é diferente do campo dateTime de apoio. Se não, ele define que o campo de backup a partir do valor de entrada e dispara o manipulador PropertyChanged
com o nome da propriedade. Não é considerado boa prática disparar o manipulador PropertyChanged se a propriedade está apenas sendo definida para
seu valor existente, e pode até levar a problemas envolvendo infinitos ciclos de configurações de propriedade recursiva em ligações nos dois sentidos.
Este é o código que aciona o evento:
PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null)
{
handler(this, new PropertyChangedEventArgs("DateTime")); }

Essa forma é preferível a um código como esse, que não salva o manipulador em uma variável separada:
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime")); }

Em um ambiente de vários segmentos, um manipulador de PropertyChanged pode ser destacado entre a instrução if que verifica se há
um valor nulo e o disparo real do evento. Salvar o manipulador em uma variável impede causar um problema, por isso é um bom hábito
de adotar, mesmo se você ainda não está trabalhando em um ambiente multithread.
O acessor get simplesmente retorna o campo de backup dateTime.
O programa MvvmClock demonstra como a classe DateTimeViewModel é capaz de fornecer atualizada data e hora para a interface do
usuário através de ligações de dados:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="MvvmClock.MvvmClockPage">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:DateTimeViewModel x:Key="dateTimeViewModel" />
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout VerticalOptions="Center">
<Label Text="{Binding Source={x:Static sys:DateTime.Now},
StringFormat='This program started at {0:F}'}" />
<Label Text="But now..." />
Chapter 18 MVVM 497
<Label Text="{Binding Source={StaticResource dateTimeViewModel},
Path=DateTime.Hour,
249
StringFormat='The hour is {0}'}" />
<Label Text="{Binding Source={StaticResource dateTimeViewModel},
Path=DateTime.Minute,
StringFormat='The minute is {0}'}" />
<Label Text="{Binding Source={StaticResource dateTimeViewModel},
Path=DateTime.Second,
StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Source={StaticResource dateTimeViewModel},
Path=DateTime.Millisecond,
</StackLayout>
StringFormat='The milliseconds are {0}'}" />
</ContentPage>

A seção Recursos para a página instancia o DateTimeViewModel e também define um estilo implícito para o Label.
O primeiro dos seis elementos do rótulo define sua propriedade de texto para um objeto Binding que envolve a estrutura DateTime .NET.
A propriedade daquele que liga é: extensão de marcação estática que referencia a propriedade DateTime.Now estática para obter a data
e hora em que o primeiro programa começa a funcionar. Nenhum caminho é necessário nesta ligação. O "F" especificação de formatação
é para o padrão de data / tempo integral, com longas versões de data e hora cordas. Embora esta etiqueta exibe a data ea hora em que
o programa é iniciado, ele nunca vai ficar atualizado.
As quatro ligações de dados finais serão atualizados. Nestas ligações de dados, a propriedade Source estiver definida como uma
extensão de marcação StaticResource que referencia o objeto DateTimeViewModel. O caminho está definido para várias
subpropriedades da propriedade DateTime de que ViewModel. Nos bastidores, a infra-estrutura de ligação atribui um manipulador sobre
o evento PropertyChanged na DateTimeViewModel. Este manipulador verifica se há uma mudança na propriedade DateTime e atualiza
a propriedade texto do rótulo, sempre que a propriedade mudanças.
O arquivo code-behind é vazio, exceto por uma chamada InitializeComponent.
As ligações de dados dos últimos quatro rótulos exibir um tempo atualizado que muda tão rápido quanto a taxa de atualização do vídeo:

A marcação neste arquivo XAML pode ser simplificado, definindo a propriedade BindingContext do StackLayout a uma extensão de
marcação StaticResource que referencia o ViewModel. Isso é propagado através da árvore visual para que você possa remover as
definições de Fonte sobre os quatro elementos finais do rótulo:
<StackLayout VerticalOptions="Center"
BindingContext="{StaticResource dateTimeViewModel}">
<Label Text="{Binding Source={x:Static sys:DateTime.Now},

<Label Text="{Binding Path=DateTime.Hour,


250
StringFormat='This program started at {0:F}'}" />
StringFormat='The hour is {0}'}" />
<Label Text="{Binding Path=DateTime.Minute,
StringFormat='The minute is {0}'}" />
<Label Text="{Binding Path=DateTime.Second,
StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Path=DateTime.Millisecond,
StringFormat='The milliseconds are {0}'}" />
</StackLayout>
O binding na primeira etiqueta substitui o BindingContext com o seu próprio sistema de alimentação.
Você pode até mesmo remover o item DateTimeViewModel do ResourceDictionary e instanti- comeu bem no StackLayout entre
BindingContext marcas de propriedade de elemento:
<StackLayout VerticalOptions="Center">
<StackLayout.BindingContext>
<toolkit:DateTimeViewModel />
</StackLayout.BindingContext>
<Label Text="{Binding Source={x:Static sys:DateTime.Now},
StringFormat='This program started at {0:F}'}" />
<Label Text="But now..." />
<Label Text="{Binding Path=DateTime.Hour,
StringFormat='The hour is {0}'}" />
<Label Text="{Binding Path=DateTime.Minute,
StringFormat='The minute is {0}'}" />
<Label Text="{Binding Path=DateTime.Second,
StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Path=DateTime.Millisecond,
StringFormat='The milliseconds are {0}'}" />
</StackLayout>

Ou, você pode definir a propriedade BindingContext do StackLayout para uma ligação que inclui a propriedade DateTime. O
BindingContext torna-se então o valor DateTime, que permite as ligações individuais para simplesmente fazer referência propriedades da estrutura
.NET DateTime

<StackLayout VerticalOptions="Center"
BindingContext="{Binding Source={StaticResource dateTimeViewModel},
Path=DateTime}">
<Label Text="{Binding Source={x:Static sys:DateTime.Now},
StringFormat='This program started at {0:F}'}" />
<Label Text="{Binding Path=Hour,
StringFormat='The hour is {0}'}" />
<Label Text="{Binding Path=Minute,
StringFormat='The minute is {0}'}" />
<Label Text="{Binding Path=Second,
StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Path=Millisecond,
251
StringFormat='The milliseconds are {0}'}" />
</StackLayout>
Você pode ter dúvidas de que isso vai funcionar! Nos bastidores, a ligação de dados instala um manipulador de eventos PropertyChanged e relógios
para que propriedades particulares sejam alteradas, mas não pode, neste caso, porque a fonte de ligação de dados é um valor DateTime, e DateTime
não implementa INotifyPropertyChanged . No entanto, o BindingContext desses elementos do rótulo muda com cada alteração para a propriedade
DateTime no ViewModel, de modo a infra-estrutura de ligação acessa novos valores dessas propriedades.
Como as ligações individuais sobre as propriedades de texto diminuem em tamanho e complexidade, você pode retirar o caminho com o nome do
atributo e colocar tudo em uma linha e ninguém vai ser confundida:

<StackLayout VerticalOptions="Center">
<StackLayout.BindingContext>
<Binding Path="DateTime">
<Binding.Source>
<toolkit:DateTimeViewModel />
</Binding.Source>
</Binding>
</StackLayout.BindingContext>
<Label Text="{Binding Source={x:Static sys:DateTime.Now},
StringFormat='This program started at {0:F}'}" />
<Label Text="But now..." />

<Label Text="{Binding Hour, StringFormat='The hour is {0}'}" />

<Label Text="{Binding Minute, StringFormat='The minute is {0}'}" />


<Label Text="{Binding Second, StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Millisecond, StringFormat='The milliseconds are {0}'}" />
</StackLayout>

Em futuros programas neste livro, as ligações individuais a maioria irá ser tão curto e tão elegante quanto possível.

Propriedades interativas em um ViewModel


O segundo exemplo de um ViewModel faz algo tão básico que você nunca iria escrever uma ViewModel para esta finalidade. A classe
SimpleMultiplierViewModel simplesmente multiplica dois números juntos. Mas é um bom exemplo para demonstrar a sobrecarga e
mecânica de um ViewModel que tem várias propriedades interactivas. (E, embora você nunca escreveria um ViewModel para multiplicar
dois números to- Gether, você pode escrever uma ViewModel para resolver equações de segundo grau ou algo muito mais complexo.)
The Simple classe Multiplicador ViewModel é parte do projeto simples multiplicador:
using System;
using System.ComponentModel;
class SimpleMultiplierViewModel : INotifyPropertyChanged
{
double multiplicand, multiplier, product;
public double Multiplicand
{
set
{
if (multiplicand != value)
{
multiplicand = value;
OnPropertyChanged("Multiplicand");
252
UpdateProduct();
}
}
get
{
return multiplicand;
}
}
public double Multiplier
{
set
{
if (multiplier != value)
{
multiplier = value;
OnPropertyChanged("Multiplier");
UpdateProduct();
}
}
get
{
return multiplier;
}
}
public double Product
{
protected set
{
if (product != value)
{
product = value;
OnPropertyChanged("Product");
}
}
get
{
return product;
}
}
void UpdateProduct()
{
Product = Multiplicand * Multiplier;
}
253
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

A classe define três propriedades públicas do tipo double, chamado Multiplicando, Multiplicador, e do produto. Cada propriedade é
apoiado por um campo particular. O conjunto e obter assessores dos dois primeiros laços propriedades são públicos, mas o acessor
definido da propriedade do produto é protegido para evitar que seja definida fora da classe, enquanto ainda permitindo uma classe
descendente para mudá-lo.
O acessor conjunto de cada propriedade começa por verificar se o valor da propriedade é realmente tados, e se assim for, ele define o
campo de apoio a esse valor e chama um método chamado OnPropertyChanged com esse nome propriedade.
A interface INotifyPropertyChanged não requer um método OnPropertyChanged, mas as classes ViewModel muitas vezes incluem um
para reduzir a repetição de código. É geralmente definida como protegida em caso de necessidade para derivar um ViewModel do outro
e disparar o evento na classe derivada. Mais adiante neste capítulo, você verá técnicas de reduzir a repetição de código nas classes
INotifyPropertyChanged ainda mais.
Os “set accessors” para as propriedades multiplicando e multiplicador concluir chamando o método UpdateProduct. Este é o método que
realiza o trabalho de multiplicar os valores das duas propriedades e estabelecendo um novo valor para a propriedade do produto, que,
em seguida, dispara seu próprio evento Property-Changed.
Aqui está o arquivo XAML que faz uso deste ViewModel:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SimpleMultiplier"
x:Class="SimpleMultiplier.SimpleMultiplierPage"
Padding="10, 0">
<ContentPage.Resources>
<ResourceDictionary>
<local:SimpleMultiplierViewModel x:Key="viewModel" />
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
</Style>
</ResourceDictionary>
<StackLayout BindingContext="{StaticResource viewModel}">
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Multiplicand}" />
<Slider Value="{Binding Multiplier}" />
</StackLayout>
<StackLayout Orientation="Horizontal"
Spacing="0"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center">

254
<Label Text="{Binding Multiplicand, StringFormat='{0:F3}'}" />
<Label Text="{Binding Multiplier, StringFormat=' x {0:F3}'}" />
<Label Text="{Binding Product, StringFormat=' = {0:F3}'}" />
</StackLayout>
</StackLayout>
</ContentPage>
O SimpleMultiplierViewModel é instanciado no dicionário Recursos e definido para a propriedade BindingContext do StackLayout usando
uma extensão de marcação StaticResource. Isso BindingContext é herdada por todos os filhos e netos do StackLayout, que inclui duas
Slider e três elementos do Rótulo. O uso do BindingContext permite que essas ligações para ser tão simples quanto possível.
O modo de ligação padrão da propriedade Value do Slider é TwoWay. Mudanças na propriedade Value de cada Slider causar alterações
nas propriedades de ViewModel.
Os três elementos do rótulo exibir os valores de todas as três propriedades do ViewModel com alguma formatação que insere vezes e
sinais de igual com os números:
<Label Text="{Binding Multiplicand, StringFormat='{0:F3}'}" />
<Label Text="{Binding Multiplier, StringFormat=' x {0:F3}'}" />
<Label Text="{Binding Product, StringFormat=' = {0:F3}'}" />
Para os dois primeiros, você pode, alternativamente, vincular a propriedade Text dos Elementos do rótulo diretamente para a propriedade
valor do controle deslizante correspondente, mas isso exigiria que você dá a cada Slider um nome com x: Nome e referência esse nome
em um argumento de origem por usando o x: extensão de marcação de referência. A abordagem utilizada neste programa é muito mais
limpo e verifica se os dados está a fazer uma viagem cheia através do ViewModel de cada Slider para cada etiqueta.
Não há nada no arquivo code-behind, exceto uma chamada para InitializeComponent no construtor. Toda a lógica de negócios está no
ViewModel, e toda a interface de usuário é definida em XAML:

Se você gostaria, você pode inicializar o ViewModel como é instanciado no dicionário Recursos:
<local:SimpleMultiplierViewModel x:Key="viewModel"
Multiplicand="0.5"
Multiplier="0.5" />
Os elementos Slider vai ter esses valores iniciais como resultado da ligação a dois sentidos.
A vantagem de separar a interface do usuário da lógica de negócios subjacente torna-se evidente quando se deseja alterar a interface
do usuário um pouco, talvez substituindo um Stepper para o Slider para um ou ambos os números:
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Multiplicand}" />
<Stepper Value="{Binding Multiplier}" />

255
Para além das diferentes gamas de os dois elementos, a funcionalidade é idêntica:

Você também pode substituir uma entrada:

<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Multiplicand}" />
<Entry Text="{Binding Multiplier}" />
</StackLayout>

O modo de ligação para a propriedade texto da entrada padrão também é TwoWay, então tudo que você precisa se preocupar é a
conversão entre a propriedade de origem de casal e alvo de cadeia propriedade. Felizmente, esta conversão é manipulada pela infra-
estrutura de ligação:

Se você digitar uma série de caracteres que não pode ser convertido para um casal, a ligação irá manter o último valor válido. Se você
quiser validação mais sofisticado, você vai ter que implementar seu próprio (como com um gatilho, que será discutido no Capítulo 23).
Uma experiência interessante é digitar 1E-1, que é a notação científica que é conversível. Você vai vê-lo mudar imediatamente para "0,1"
na entrada. Este é o efeito da TwoWay de ligação: A propriedade Multiplicadora está definida para 1E-1 a partir da entrada, mas o método
ToString que a infra-estrutura de ligação chama quando o valor de volta para a entrada retorna o texto porque isso é diferente de "0.1."

256
e a partir do texto de entrada em vigor, o novo texto está definido. Para impedir que isso aconteça, você pode definir o modo de ligação
a OneWayToSource:
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Multiplicand}" />
<Entry Text="{Binding Multiplier, Mode=OneWayToSource}" />
</StackLayout>

Agora a propriedade Multiplicador do ViewModel é definido a partir da propriedade texto da entrada, mas não o contrário. Se você não
precisar destes dois pontos de vista a ser atualizado a partir do ViewModel, você pode definir os dois para OneWayToSource. Mas
geralmente você vai querer ligações MVVM ser TwoWay.
Se você se preocupar com ciclos infinitos em ligações nos dois sentidos? Normalmente não, porque propriedade- eventos alterados só
são disparados quando a propriedade realmente muda e não quando é meramente definido para o mesmo valor. Geralmente a origem
eo destino irá parar de atualizar-se após um salto ou dois. No entanto, é possível escrever um conversor de valor "patológica", que não
prevê de ida e volta as conversões, e que poderia realmente causar ciclos de atualização infinito em ligações nos dois sentidos.

ViewModel Colorido
Cores sempre fornecem um bom meio de explorar as características de uma interface gráfica de usuário, então você provavelmente não
vai se surpreender ao saber que a biblioteca Xamarin.FormsBook.Toolkit contém uma classe chamada ColorViewModel.
A classe ColorViewModel expõe uma propriedade de cor, mas também vermelho, verde, azul, alfa, matiz, saturação e luminosidade
propriedades, todos os quais são individualmente ajustáveis. Esta não é uma característica que a estrutura Xamarin.Form de cores
oferece. Uma vez que um valor Cor é criado a partir de um construtor ou um dos métodos em cores que começam com as palavras
Adicionar, From, Multiply, ou com, é imutável.
Esta classe ColorViewModel é complicada pela inter-relação dos seus bens a cores e todas as propriedades do componente. Por
exemplo, suponha que a propriedade de cores está definida. A classe deve disparar um manipulador dades ertyChanged não só para a
cor, mas também para qualquer componente (como o vermelho ou Hue), que também muda. Da mesma forma, se as alterações de
propriedade vermelhas, em seguida, a classe deve disparar um evento PropertyChanged para ambos vermelho e cores, e,
provavelmente, Hue, Saturação e Luminosidade também.
A classe ColorViewModel resolve este problema armazenando um campo de apoio para apenas a propriedade Color. Todos os
assessores estabelecidos para os componentes individuais criar uma nova cor usando o valor de entrada com uma chamada para
Color.FromRgba ou Color.FromHsla. Este novo valor Cor está definido para a propriedade de cores em vez do campo de cor, o que
significa que o novo valor Cor é submetido a processamento na acessor set da propriedade Cor:
public class ColorViewModel : INotifyPropertyChanged {
Color color;
public event PropertyChangedEventHandler PropertyChanged;

public double Red


{
set
{
if (Round(color.R) != value)
}
Color = Color.FromRgba(value, color.G, color.B, color.A);
get
{
}
return Round(color.R);
}
public double Green
{
set
{
257
if (Round(color.G) != value)
Color = Color.FromRgba(color.R, value, color.B, color.A);
}

get
{
return Round(color.G);
}
}
public double Blue
{
set
{
if (Round(color.B) != value)
Color = Color.FromRgba(color.R, color.G, value, color.A);
}
get
{
return Round(color.B);
}
}
public double Alpha
{
set
{
}
if (Round(color.A) != value)
Color = Color.FromRgba(color.R, color.G, color.B, value);
get
{
}
return Round(color.A);
}
public double Hue
{
set
{
if (Round(color.Hue) != value)
Color = Color.FromHsla(value, color.Saturation, color.Luminosity, color.A);
}
get
{
return Round(color.Hue);
258
}
}
public double Saturation
{
set
{
if (Round(color.Saturation) != value)
Color = Color.FromHsla(color.Hue, value, color.Luminosity, color.A);
}
get
{

return Round(color.Saturation);
}
}
public double Luminosity
{
set
{
if (Round(color.Luminosity) != value)
Color = Color.FromHsla(color.Hue, color.Saturation, value, color.A);
}
get
{
return Round(color.Luminosity);
}
}
public Color Color
{
set
{
Color oldColor = color;
if (color != value)
{
color = value;
OnPropertyChanged("Color");
}
if (color.R != oldColor.R)
OnPropertyChanged("Red");
if (color.G != oldColor.G)
OnPropertyChanged("Green");
if (color.B != oldColor.B)
OnPropertyChanged("Blue");
259
if (color.A != oldColor.A)
OnPropertyChanged("Alpha");
if (color.Hue != oldColor.Hue)
OnPropertyChanged("Hue");
if (color.Saturation != oldColor.Saturation)
OnPropertyChanged("Saturation");
if (color.Luminosity != oldColor.Luminosity)
}
OnPropertyChanged("Luminosity");
get
{
return color;
}

}
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
double Round(double value)
{
return Device.OnPlatform(value, Math.Round(value, 3), value);
}
}

O “accessor” definido para a propriedade cor é responsável pelos disparos de todos os eventos PropertyChanged com base em
alterações nas propriedades.
Observe o método dependente do dispositivo na parte inferior da classe e seu uso no conjunto e obter assessores das sete primeiras
propriedades. Este foi adicionada quando a amostra MultiColorSliders no Capítulo 23, "disparadores e comportamentos," revelou um
problema. Android parecia ser arredondar internamente os componentes de cor, provocando inconsistências entre as propriedades de
serem passados para os métodos e Color.FromRgba Color.FromHsla e as propriedades do valor de cor resultante, que levam ao infinito
definir e obter laçadas.
O programa HslSliders instancia o ColorViewModel entre as tags Grid.BindingContext para que se torne o BindingContext para todos os
elementos deslizante e etiqueta dentro da grade:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="HslSliders.HslSlidersPage"
SizeChanged="OnPageSizeChanged">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
260
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid x:Name="mainGrid">
<Grid.BindingContext>
<toolkit:ColorViewModel Color="Gray" />
</Grid.BindingContext>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<!-- Initialized for portrait mode. -->
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<BoxView Color="{Binding Color}"
Grid.Row="0" Grid.Column="0" />
<StackLayout x:Name="controlPanelStack"
Grid.Row="1" Grid.Column="0"
Padding="10, 5">
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
</StackLayout>
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
</StackLayout>
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</Grid>
</ContentPage>
261
Observe que a propriedade de cor de ColorViewModel é inicializado quando ColorViewModel é instanciado. Os dois sentidos ligações dos controles
deslizantes em seguida, pegar os valores resultantes das propriedades Hue, Saturação e Luminosidade.
Se você, em vez querer implementar uma exibição de valores hexadecimais de vermelho, verde e azul, você pode usar a classe DoubleToIntConverter
demonstrado em conexão com o programa GridRgbSliders no capítulo anterior.
O programa HslSliders implementa a mesma técnica para alternar entre retrato e modos scape terrestres como esse programa GridRgbSliders. O arquivo
code-behind lida com a mecânica deste switch:
public partial class HslSlidersPage : ContentPage {
public HslSlidersPage()
{
{
InitializeComponent();
}
void OnPageSizeChanged(object sender, EventArgs args)
// Portrait mode.
if (Width < Height)
{
mainGrid.RowDefinitions[1].Height = GridLength.Auto;

Grid.SetRow(controlPanelStack, 1);
Grid.SetColumn(controlPanelStack, 0);
}
// Landscape mode.
else
{
mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
}
mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
Grid.SetRow(controlPanelStack, 0);
Grid.SetColumn(controlPanelStack, 1);
}
}

Este arquivo de código subjacente não é tão bonito quanto um arquivo que apenas pede InitializeComponent, mas, mesmo no contexto
de MVVM, alternar entre os modos retrato e paisagem é um uso legítimo do código-behind arquivo porque ele é exclusivamente dedicado
ao interface com o usuário, em vez de lógica de negócios subjacente.
Aqui está o programa Hsl Sliders em ação:

262
Melhorando o ViewModel
Uma implementação típica de INotifyPropertyChanged tem um campo de apoio privado para cada propriedade pública definida pela
classe, por exemplo:
double number;


Ele também tem um método PropertyChanged responsável por disparar o evento PropertyChanged:
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
}
public double Number
{

if (handler != null)
{
}
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
A typical property definition looks like this:
set
{
if (number != value)
{
number = value;
OnPropertyChanged("Number");
// Do something with the new value.
Chapter 18 MVVM 514
}
}
get
263
{
}
return number;
}
Um problema potencial envolve a cadeia de texto que você passar para o método OnPropertyChanged. Se você cometer erros de
ortografia, você não vai obter qualquer tipo de mensagem de erro, e ainda ligações que envolvem essa propriedade não funcionará.
Além disso, o campo de backup aparece três vezes dentro desta propriedade única. Se você tivesse várias propriedades semelhantes e
definiu-los através de operações de copiar e colar, é possível omitir a mudança de nome de uma das três aparições do campo de backup,
e que o bug pode ser muito difícil de rastrear.
Você pode resolver o primeiro problema com um recurso introduzido no C # 5.0. A classe tributo CallerMemberNameAt permite substituir
um argumento método opcional com o nome do método de chamada ou propriedade.
Você pode fazer uso desse recurso, redefinindo o método OnPropertyChanged. Faça o argumento opcional, atribuindo nula a ele e
precedendo-o com o atributo CallerMemberName entre colchetes. Você também vai precisar de uma diretiva utilizando para
System.Runtime.CompilerServices:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
}
Agora a propriedade Number pode chamar o método OnPropertyChanged sem o argumento que indica o nome da propriedade. Esse
argumento será definido automaticamente para o "Número" nome da propriedade, porque é onde a chamada para OnPropertyChanged
é originário:
public double Number
{

if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

set
{
if (number != value)
{
number = value;
OnPropertyChanged();
}
get
}
// Do something with the new value.
{
return number;
}
}

Essa abordagem evita um nome de propriedade de texto com erros ortográficos e também permite que nomes de propriedades a ser
alterada durante o desenvolvimento do programa sem se preocupar com alterando também as cadeias de texto. De fato, uma das
principais razões para que o atributo CallerMemberName foi inventado foi simplificar classes que implementam INotifyPropertyChanged.

264
No entanto, isso só funciona quando OnPropertyChanged é chamado a partir da propriedade cujo valor está mudando. No
ColorViewModel anteriormente, nomes de propriedade explícita ainda seria necessário em todos, mas uma das chamadas para
OnPropertyChanged.
É possível ir ainda mais longe para simplificar a lógica definida acessor: Você precisará definir um método genérico, provavelmente
chamado SetProperty ou algo similar. Este método SetProperty também é definido com o atributo CallerMemberName:
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string
propertyName = null) {

if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)


{

PropertyChangedEventHandler handler = PropertyChanged;


if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
O primeiro argumento para SetProperty é uma referência para o campo de backup, e o segundo argumento é o valor a ser definido para
a propriedade. SetProperty automatiza a verificação ea fixação do campo de backup. Observe que inclui explicitamente o argumento
propertyName quando chamando OnProperty. (Caso contrário, o argumento propertyName se tornaria a string "SetProperty"!) O método
retorna true se a propriedade foi alterada. Você pode usar esse valor de retorno para executar processamento adicional com o novo
valor.
Embora SetProperty seja um método genérico, o compilador C # pode-se deduzir o tipo dos argumentos. Se você não precisa fazer nada
com o novo valor no “accessor” conjunto de propriedades, você pode até mesmo reduzir os dois acessores para linhas simples sem
obscurecer as operações:

public double Number


{
set { SetProperty(ref number, value); }
get { return number; }
}

Você pode gostar desta reformulação, tanto que você vai querer colocar o SetProperty e métodos OnPropertyChanged em sua própria
classe e derivam dessa classe ao criar seu próprio ViewModels Tal classe, chamada ViewModelBase, já está na biblioteca
Xamarin.FormsBook.Toolkit :
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Xamarin.FormsBook.Toolkit
{
public class ViewModelBase : INotifyPropertyChanged
{
265
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
}
{
}
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

A Interface de Comando
Ligações de dados são muito poderosos.Ligações de dados conectam propriedades dos elementos visuais na Vista com propriedades
de dados no ViewModel, e permitem que a manipulação direta de itens de dados através da interface do usuário.
Mas nem tudo é uma propriedade. Às vezes ViewModels expõem métodos públicos que devem ser chamados a partir do View com base
na interação do usuário com um elemento visual. Sem MVVM, você provavelmente chamar esse método a partir de um manipulador de
eventos Clicked de um botão ou um manipulador de eventos Tapped de um TapGestureRecognizer. Ao considerar essas necessidades,
todo o conceito de ligações de dados e MVVM pode começar a parecer irremediavelmente falho. Como pode o arquivo code-behind de
uma classe de página ser tirada de uma chamada InitializeComponent se ele ainda deve fazer chamadas de método do Vista para o
ViewModel?
Não desista de MVVM tão rapidamente! Xamarin.Forms suporta um recurso que permite ligações de dados para fazer chamadas de
método no ViewModel diretamente de Button e TapGestureRecognizer e alguns outros elementos. Este é um protocolo chamado a
interface de comando ou a interface de comando.
A interface de comando é suportado por oito classes:
• botão
• MenuItem (abordados no Capítulo 19, "vê a coleção"), e, portanto, também ToolbarItem
• Barra de pesquisa
• TextCell, e, portanto, também ImageCell (também a ser abordados no Capítulo 19)
• ListView (também a ser abordados no Capítulo 19)
TapGestureRecognizer Também é possível implementar comandando em suas próprias classes personalizadas. A interface de comando
é provável que seja um pouco confuso no início. Vamos nos concentrar no Button. Botão define duas maneiras para o código para ser
notificado quando o elemento é clicado. A primeira é o caso clicado. Mas você também pode usar a interface de comando do botão como
uma alternativa ao (ou no ção adi- a) o evento clicado. Esta interface consiste em duas propriedades públicas que Button define:
Comando do tipo System.Windows.Input.ICommand.

CommandParameter do tipo Object. Para apoiar comandante, um ViewModel deve definir uma propriedade pública do tipo ICommand
que é então ligado à propriedade de comando do botão através de dados normais de ligação.
266
Como INotifyPropertyChanged, a interface ICommand não é uma parte de Xamarin.Forms. É de- multado no namespace
System.Windows.Input e implementado no System.ObjectModel assembléia, que é um dos conjuntos .NET ligados a uma aplicação
Xamarin.Forms. ICommand é o único tipo no namespace System.Windows.Input que Xamarin.Forms suportes. De fato, é o único tipo
em qualquer namespace System.Windows apoiado por Xamarin.Forms.
É uma coincidência que INotifyPropertyChanged e ICommand são ambos definidos no assembleias .NET em vez de Xamarin.Forms?
Não. Estas interfaces são frequentemente utilizados em ViewModels, e alguns opers mento já pode ter ViewModels desenvolvido para
um ou mais dos ambientes baseados em XAML da Microsoft. É mais fácil para os desenvolvedores a incorporar essas ViewModels
existentes em Xamarin.Forms se INotifyPropertyChanged e ICommand são definidos em namespaces padrão NET e montagens ra- ther
do que em Xamarin.Forms.
A interface ICommand define dois métodos e um evento:

public interface ICommand

vazio Executar (objeto arg);

bool CanExecute (objeto arg);

evento EventHandler CanExecuteChanged;

O ViewModel define uma ou mais propriedades do tipo ICommand, o que significa que a propriedade é um tipo que implementa estes
dois métodos e do evento. A propriedade em ViewModel que implementa ICommand pode então ser ligado à propriedade de comando
de um botão. Quando o botão é clicado, o botão dispara seu evento normal clicados, como de costume, mas também chama o método
de execução do objeto vinculado a sua propriedade Command. O argumento para o método de execução é o objeto definido para a
propriedade CommandParameter do Button.
Essa é a técnica básica. No entanto, pode ser que determinadas condições no ViewModel proibir um clique de botão no momento atual.
Nesse caso, o botão deve ser desativado. Este é o objectivo do método e do evento CanExecute CanExecuteChanged em ICommand.
O botão chama CanExecute quando sua propriedade Command é primeiro set. Se CanExecute retorna false, o botão desativa próprio e
não gera Executar chamadas. O botão também instala um manipulador para o evento CanExecuteChanged. Depois disso, sempre que
o ViewModel aciona o evento CanExecuteChanged, o botão chama CanExecute novamente para determinar se o botão deve ser ativado.
A ViewModel que suporta a interface de comando define uma ou mais propriedades do tipo Command internamente define essa
propriedade para uma classe que implementa a interface ICommand. O que é essa classe, e como ele funciona?
Se você estavam a implementando o protocolo dominante em um dos ambientes baseados em XAML da Microsoft, você estaria
escrevendo sua própria classe que implementa ICommand, ou talvez usar um que você encontrou na web, ou um que foi incluído com
algumas ferramentas MVVM. Às vezes, essas classes são nomeadas CommandDelegate ou algo similar.
Você pode usar essa mesma classe nos ViewModels de seus aplicativos Xamarin.Forms. No entanto, para sua conveniência,
Xamarin.Forms inclui duas classes que implementam ICommand que você pode usar invés. Estas duas classes são chamados
simplesmente de Comando e Comando <T>, onde T é o tipo dos argumentos para executar e CanExecute.
Se você está realmente compartilhando um ViewModel entre ambientes e Xamarin.Forms da Microsoft, você não pode usar as classes
de comandos definidos pelo Xamarin.Forms. No entanto, você estará usando algo semelhante a estas classes de comando, de modo
que a discussão seguinte será, certamente, aplicável independentemente.
A classe Command inclui os dois métodos e eventos da interface ICommand e também define um método ChangeCanExecute. Este
método faz com que o objeto de comando para disparar o evento Changed CanExecute-, e essa facilidade acaba por ser muito útil.
Dentro do ViewModel, você provavelmente vai criar um objeto do tipo de comando ou Command <T> para cada propriedade pública no
ViewModel do tipo ICommand. O Comando ou Command <T> é um método de retorno na forma de um objeto de Acção que é chamado
quando o botão chama o método Execute da interface ICommand. O método CanExecute é opcional, mas assume a forma de um objeto
Func que retorna booleano.
Em muitos casos, as propriedades do tipo ICommand são definidos no construtor do ViewModel e não mudar depois. Por essa razão,
essas propriedades ICommand geralmente não precisa de fogo das Propriedades tyChanged eventos.

Execuções simples de Métodos


Vejamos um exemplo simples. Um programa chamado PowersOfThree permite usar dois botões para explorar várias potências de 3.
Um botão aumenta o expoente eo outro botão diminui o expoente.
A classe PowersViewModel deriva da classe ViewModelBase na biblioteca Xamarin.Forms- Book.Toolkit, mas o próprio ViewModel está
no projeto de aplicativo PowersOfThree. Ele não se limita a potências de 3, mas o construtor requer um argumento de que a classe usa

267
como um valor de base para o cálculo de energia, e que apresenta como a propriedade BaseValue. Como essa propriedade tem um
acessor definido privado e não muda após o construtor conclui, a propriedade não dispara um evento PropertyChanged.
Para implementar a resposta aos toques Button, a classe PowersViewModel define duas propriedades do tipo ICommand, chamado
IncreaseExponentCommand e DecreaseExponentCommand. Mais uma vez, ambas as propriedades de ter os acessores conjunto
privadas. Como você pode ver, o construtor define essas duas propriedades de objetos de comando stantiating ções que fazem referência
a métodos pouco privadas imediatamente a seguir ao tor construção. Estes dois métodos pequenos são chamados quando o método de
execução de comando é chamado. O VierwModelo usa a classe de comando, em vez de Command <T> porque o programa não faz uso
de qualquer argumento para os métodos execute:
class PowersViewModel : ViewModelBase
{
double exponent, power;

}
Exponent = 0;
// Initialize ICommand properties.
IncreaseExponentCommand = new Command(ExecuteIncreaseExponent);
DecreaseExponentCommand = new Command(ExecuteDecreaseExponent);
}
void ExecuteIncreaseExponent()
{
Exponent += 1;
}
void ExecuteDecreaseExponent()
{
Exponent -= 1;

}
public PowersViewModel(double baseValue)
{
// Initialize properties.
BaseValue = baseValue;
public double BaseValue { private set; get; }
public double Exponent
{
private set
{
if (SetProperty(ref exponent, value))
{
Power = Math.Pow(BaseValue, exponent);
}
}
get
{
return exponent;
}

268
}
public double Power
{
private set { SetProperty(ref power, value); }
get { return power; }
}
public ICommand IncreaseExponentCommand { private set; get; } public ICommand
DecreaseExponentCommand { private set; get; }

Os métodos ExecuteIncreaseExponent e ExecuteDecreaseExponent tanto mudam a propriedade Exponent (que dispara um


evento PropertyChanged), ea propriedade Exponent recalcula a propriedade Power, que também aciona um evento PropertyChanged.

Muitas vezes um ViewModel irá instanciar seus objetos de comando, passando funções lambda para o construtor de comando.
Essa abordagem permite que esses métodos para ser definido mesmo no structor con- ViewModel, assim:

IncreaseExponentCommand = new Command(() =>


{
});
Exponent += 1;
DecreaseExponentCommand = new Command(() =>
{
Exponent -= 1;
});

O arquivo PowersOfThreePage XAML vincula as propriedades de texto de três elementos label para o BaseValue, Exponent e
propriedades poder da classe PowersViewModel, e liga-se as propriedades de comando dos dois elementos botão para as propriedades
IncreaseExponentCommand e DecreaseExponentCommand do ViewModel.
Observe como um argumento de 3 é passado para o construtor de PowersViewModel como é instanciado no dicionário Resources.
Passando argumentos para construtores ViewModel é a principal razão para a existência do x: tag Argumentos:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PowersOfThree"
x:Class="PowersOfThree.PowersOfThreePage">
<ContentPage.Resources>
<ResourceDictionary>
<local:PowersViewModel x:Key="viewModel">
<x:Arguments>
<x:Double>3</x:Double>
</x:Arguments>
</local:PowersViewModel>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BindingContext="{StaticResource viewModel}">
<StackLayout Orientation="Horizontal"
Spacing="0"
HorizontalOptions="Center"
269
VerticalOptions="CenterAndExpand">
<Label FontSize="Large"
Text="{Binding BaseValue, StringFormat='{0}'}" />
<Label FontSize="Small"
Text="{Binding Exponent, StringFormat='{0}'}" />
Chapter 18 MVVM 522
<Label FontSize="Large"
Text="{Binding Power, StringFormat=' = {0}'}" />
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Button Text="Increase"
Command="{Binding IncreaseExponentCommand}"
HorizontalOptions="CenterAndExpand" />
<Button Text="Decrease"
</StackLayout>
Command="{Binding DecreaseExponentCommand}"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

Sem tocar no ViewModel ou mesmo renomear um manipulador de eventos para que se aplique a uma torneira ra- ther de um botão, o
programa funciona da mesma, mas com um olhar diferente:

Quase uma calculadora


Agora é hora de fazer um ViewModel mais sofisticado com objetos ICommand. O próximo programa é quase como uma calculadora,
exceto que ele só acrescenta uma série de números juntos. O ViewModel é nomeado AdderViewModel, e o programa é chamado de
Máquina de adição. Vamos olhar para os screenshots primeiros:

270
Na parte superior da página você pode ver uma história da série de números que já foram introduzi- e adicionados. Esta é uma etiqueta
em uma ScrollView, por isso pode ficar um pouco longo.
A soma desses números é exibido na visualização de entrada acima do teclado. Normalmente, esse ponto de vista Entrada contém o
número que você está digitando, mas depois de bater o grande sinal de mais à direita do teclado, a entrada exibe a soma acumulada e
o botão de sinal fica desativado. Você precisa começar a digitar outro número para o valor acumulado a desaparecer e para o botão com
o sinal de mais para ser ativado. Da mesma forma, o botão de retrocesso é ativado assim que você começa a digitar.
Estas não são as únicas teclas que podem ser desativados. O ponto decimal é desativado quando o número que já está digitando tem
um ponto decimal, e todas as teclas numéricas tornar-se deficientes quando o número contém 16 caracteres. Isso é para evitar o número
na entrada de se tornar demasiado longo para exibir.
A desativação destes botões é o resultado da implementação do método CanExecute na interface ICom- mand.
A classe AdderViewModel está na biblioteca Xamarin.FormsBook.Toolkit e deriva Ver- ModelBase. Aqui está a parte da classe com todas
as propriedades públicas e suas áreas de apoio:
public class AdderViewModel : ViewModelBase {

string currentEntry = "0";


string historyString = "";
Chapter 18 MVVM
525
...
public string CurrentEntry
{
}
private set { SetProperty(ref currentEntry, value); }
get { return currentEntry; }
public string HistoryString
{
private set { SetProperty(ref historyString, value); }
}
public ICommand
public ICommand
public ICommand
271
public ICommand
public ICommand
ClearCommand { private set; get; } ClearEntryCommand { private set; get; }
BackspaceCommand { private set; get; } NumericCommand { private set; get; }
DecimalPointCommand { private set; get; }
...
get { return historyString; }
public ICommand AddCommand { private set; get; }
}

Todas as propriedades de ter os acessores conjunto privadas. As duas propriedades do tipo string são apenas definir internamente com
base nas torneiras-chave e as propriedades do tipo ICommand são definidos no structor con- AdderViewModel (que você verá em breve).
Estes oito propriedades públicas são a única parte do AdderViewModel que o arquivo XAML no projeto AddingMachine precisa saber
sobre. Aqui está o arquivo XAML. Ele contém uma grade principal de dois linha e coluna dois para alternar entre modo retrato e paisagem,
e uma etiqueta, de entrada, e 15 elementos botão, todos os quais estão vinculados a uma das oito propriedades públicas do
AdderViewModel. Observe que as propriedades de comando de todas as 10 teclas numéricas são obrigados a propriedade
NumericCommand e que os botões são diferenciados pela propriedade CommandParameter. A definição dessa propriedade
CommandParameter é passado como um argumento para os métodos Execute e CanExecute:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AddingMachine.AddingMachinePage"
SizeChanged="OnPageSizeChanged">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="10, 20, 10, 10"
Android="10"
WinPhone="10" />
</ContentPage.Padding>
Chapter 18 MVVM 526
<Grid x:Name="mainGrid">
<!-- Initialized for Portrait mode. -->
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<!-- History display. -->
<ScrollView Grid.Row="0" Grid.Column="0"
<!-- Keypad. -->
Padding="5, 0">
<Label Text="{Binding HistoryString}" />
</ScrollView>
<Grid x:Name="keypadGrid"
272
Grid.Row="1" Grid.Column="0"
RowSpacing="2"
ColumnSpacing="2"
WidthRequest="240"
HeightRequest="360"
VerticalOptions="Center"
HorizontalOptions="Center">
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Button">
<Setter Property="FontSize" Value="Large" />
<Setter Property="BorderWidth" Value="1" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Label Text="{Binding CurrentEntry}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontSize="Large"
LineBreakMode="HeadTruncation"
VerticalOptions="Center"
HorizontalTextAlignment="End" />
<Button Text="C"
Grid.Row="1" Grid.Column="0"
Command="{Binding ClearCommand}" />
<Button Text="CE"
Grid.Row="1" Grid.Column="1"
Command="{Binding ClearEntryCommand}" />
<Button Text="&#x21E6;"
Grid.Row="1" Grid.Column="2"
Command="{Binding BackspaceCommand}" />
<Button Text="+"
<Button Text="7"
Grid.Row="1" Grid.Column="3" Grid.RowSpan="5"
Command="{Binding AddCommand}" />
Grid.Row="2" Grid.Column="0"
Command="{Binding NumericCommand}"
CommandParameter="7" />
<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="8" />
<Button Text="9"
Grid.Row="2" Grid.Column="2"
273
Command="{Binding NumericCommand}"
CommandParameter="9" />
<Button Text="4"
Grid.Row="3" Grid.Column="0"
<Button Text="5"
Command="{Binding NumericCommand}"
CommandParameter="4" />
<Button Text="6"
Grid.Row="3" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="5" />
Grid.Row="3" Grid.Column="2"
Command="{Binding NumericCommand}"
CommandParameter="6" />
<Button Text="1"
Grid.Row="4" Grid.Column="0"
<Button Text="2"
Command="{Binding NumericCommand}"
CommandParameter="1" />
<Button Text="3"
Grid.Row="4" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="2" />
Grid.Row="4" Grid.Column="2"
Command="{Binding NumericCommand}"
CommandParameter="3" />
<Button Text="0"
Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding NumericCommand}"
CommandParameter="0" />
</Grid>
<Button Text="&#x00B7;"
</Grid>
Grid.Row="5" Grid.Column="2"
Command="{Binding DecimalPointCommand}" />
</ContentPage>

O que você não vai encontrar no arquivo XAML é uma referência a AdderViewModel. Por razões que você verá em breve,
AdderViewModel é instanciado no código.
O núcleo da lógica do programa está em execução e os métodos CanExecute das seis propriedades mand ICom-. Estas propriedades
são todos inicializado no construtor AdderViewModel mostrado abaixo, e a Execute e métodos CanExecute são todas as funções lambda.
Quando apenas uma função lambda aparece no construtor de comando, que é o método de execução (como o nome do parâmetro
indica) e o botão está sempre ativada. Este é o caso de ClearCom- mand e ClearEntryCommand.
Todos os outros construtores de comando tem duas funções lambda. O primeiro é o método de execução, eo segundo é o método
CanExecute. O método CanExecute retorna true se o botão deve ser ativado e false de outra forma.

274
Todas as propriedades ICommand são definidos com a forma nongeneric da classe Command exceto para nu- mericCommand, o que
requer um argumento para os métodos Execute e CanExecute para identificar qual chave tem sido aproveitado:
public class AdderViewModel : ViewModelBase {
...
bool isSumDisplayed = false;
double accumulatedSum = 0;
public AdderViewModel()
{
ClearCommand = new Command(
execute: () =>
{
HistoryString = "";
accumulatedSum = 0;
CurrentEntry = "0";
isSumDisplayed = false;
RefreshCanExecutes();
});
ClearEntryCommand = new Command(
execute: () =>
{
CurrentEntry = "0";
isSumDisplayed = false;
RefreshCanExecutes();
});
Chapter 18 MVVM 529
BackspaceCommand = new Command(
execute: () =>
{
CurrentEntry = CurrentEntry.Substring(0, CurrentEntry.Length - 1);
if (CurrentEntry.Length == 0)
{
CurrentEntry = "0";
}
},
RefreshCanExecutes();
canExecute: () =>
{
});
return !isSumDisplayed && (CurrentEntry.Length > 1 || CurrentEntry[0] != '0');
NumericCommand = new Command<string>(
execute: (string parameter) =>
{
if (isSumDisplayed || CurrentEntry == "0")

275
else
CurrentEntry = parameter;
CurrentEntry += parameter;
isSumDisplayed = false;
},
RefreshCanExecutes();
canExecute: (string parameter) =>
{
return isSumDisplayed || CurrentEntry.Length < 16;
});
DecimalPointCommand = new Command(
execute: () =>
{
if (isSumDisplayed)
CurrentEntry = "0.";
else
CurrentEntry += ".";
isSumDisplayed = false;
RefreshCanExecutes();
},
canExecute: () =>
{
});
return isSumDisplayed || !CurrentEntry.Contains(".");
AddCommand = new Command(
execute: () =>
{
double value = Double.Parse(CurrentEntry);

Chapter 18 MVVM 530


HistoryString += value.ToString() + " + ";
accumulatedSum += value;
CurrentEntry = accumulatedSum.ToString();
isSumDisplayed = true;
RefreshCanExecutes();
},
canExecute: () =>
{
});
return !isSumDisplayed;
}
void RefreshCanExecutes()
{
276
((Command)BackspaceCommand).ChangeCanExecute();
((Command)NumericCommand).ChangeCanExecute();
((Command)DecimalPointCommand).ChangeCanExecute();
((Command)AddCommand).ChangeCanExecute();
} ...
}

Todos os métodos Executar concluir chamando um método chamado RefreshCanExecute seguindo o construtor. Este método chama o
método ChangeCanExecute de cada um dos quatro objetos Comando que implementam métodos CanExecute. Essa chamada de
método faz com que o objeto de comando para disparar um evento ChangeCanExecute. Cada botão responde a esse evento, fazendo
outra chamada para o método CanExecute para determinar se o botão deve ser habilitado ou não.
Não é necessário que cada método para concluir com um apelo a todos os quatro métodos ChangeCanExecute. Por exemplo, o método
para a ChangeCanExecute DecimalPointCommand não necessita de ser chamado quando o método de execução para
NumericCommand executa. No entanto, acabou por ser mais fácil, tanto em termos de lógica e código de consolidação-a simplesmente
chamá-los todos após cada toque chave.
Você pode ser mais confortável implementação destas Execute e métodos CanExecute como métodos não regulares, em vez de funções
lambda. Ou você pode ser mais confortável ter apenas um objeto dade mand que lida com todas as chaves. Cada tecla pode ter uma
cadeia CommandParameter identificação e você pode distinguir entre eles com um switch e caso.
Há muitas maneiras de implementar a lógica de comando, mas deve ficar claro que o uso de comandar tende a estruturar o código de
uma maneira flexível e ideal.
Uma vez que a lógica adicionando está no lugar, por que não acrescentar um par de mais botões para a subtração, a multiplicação e
divisão?
Bem, não é tão fácil de melhorar a lógica de aceitar várias operações ao invés de apenas uma operação. Se o programa suporta múltiplas
operações, em seguida, quando o usuário digita uma das teclas de operação, esta operação tem de ser guardado para aguardar o
próximo número. Somente após o próximo número é completado (sinalizado pela imprensa de uma outra tecla de operação ou igual a
chave) é que salvou operação aplicado.
Uma abordagem mais fácil seria escrever uma calculadora notação polonesa reversa (RPN), onde a operação segue a entrada do
segundo número. A simplicidade da lógica RPN é uma grande razão pela qual culators RPN cal- apelar para programadores tanto!

ViewModels e o ciclo de vida do aplicativo


Em um programa de calculadora real em um dispositivo móvel, uma característica importante envolve a economia de todo o estado da
calculadora quando o programa é encerrado, e restaurá-las quando o programa inicia-se novamente.
E mais uma vez, o conceito de ViewModel parece quebrar.
Claro, é possível escrever algum código de aplicativo que acessa as propriedades públicas do ViewModelo e salva-los, mas o estado da
calculadora depende de campos privados também. O isSum- apresentado e campos accumulatedSum de AdderViewModel são
essenciais para restaurar o estado do tor cálculo.
É óbvio que o código externo ao AdderViewModel não pode salvar e restaurar o estado do AdderViewModel sem o ViewModel expondo
mais propriedades públicas. Há apenas uma classe que sabe o que é necessário para representar todo o estado interno de um
ViewModel, e isso é o próprio ViewModel.
A solução é o ViewModel definir métodos públicos que salvar e restaurar seu estado interno. Mas porque um ViewModel deve se esforçar
para ser independente de plataforma, estes métodos não devem usar alguma coisa específica para uma plataforma particular. Por
exemplo, eles não devem acessar o objeto do aplciativo Xamarin.Forms, em seguida, adicionar itens a (ou recuperar itens de)
Propriedades do dicionário desse objeto aplicação. Isso é demasiado específico para Xamarin.Forms.
No entanto, trabalhar com um objeto IDictionary genérica em métodos chamado SaveState e RestoreState é possível em qualquer
ambiente .NET, e é assim que AdderViewModel implementa estes métodos:

public class AdderViewModel : ViewModelBase {

...
public void SaveState(IDictionary<string, object> dictionary)
{
dictionary["CurrentEntry"] = CurrentEntry;
dictionary["HistoryString"] = HistoryString;

277
dictionary["isSumDisplayed"] = isSumDisplayed; dictionary["accumulatedSum"] =
accumulatedSum;
}
public void RestoreState(IDictionary<string, object> dictionary)
{
CurrentEntry = GetDictionaryEntry(dictionary, "CurrentEntry", "0");
HistoryString = GetDictionaryEntry(dictionary, "HistoryString", "");
isSumDisplayed = GetDictionaryEntry(dictionary, "isSumDisplayed", false);
}

O código na AddingMachine envolvido em salvar e restaurar este estado é principalmente implementado na classe App. A
classe App instancia o AdderViewModel e chama RestoreState usando as propriedades dicionário da classe Application atual. Isso
AdderViewModel é então passada como um argumento para o construtor AddingMachinePage:

public class App : Application


{
AdderViewModel adderViewModel;

public App()
{
adderViewModel = new AdderViewModel();
adderViewModel.RestoreState(Current.Properties);
MainPage = new AddingMachinePage(adderViewModel);
}
protected override void OnStart()
{
// Handle when your app starts.
}
protected override void OnSleep()
{
// Handle when your app sleeps.
adderViewModel.SaveState(Current.Properties);
}
protected override void OnResume()
{
// Handle when your app resumes.
}
}
A classe App também é responsável por chamar SaveState em AdderViewModel durante o processamento do método OnSleep.
O construtor AddingMachinePage apenas precisa de definir a instância de AdderViewModel ao BindingContext propriedade da página.
O arquivo code-behind também gerencia a alternar entre layouts de retrato e paisagem:
public partial class AddingMachinePage : ContentPage {
public AddingMachinePage(AdderViewModel viewModel)
{
InitializeComponent();

278
// Set ViewModel as BindingContext.
}
{
BindingContext = viewModel;
void OnPageSizeChanged(object sender, EventArgs args)
// Portrait mode.
if (Width < Height)
{
mainGrid.RowDefinitions[1].Height = GridLength.Auto;
mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
Grid.SetRow(keypadGrid, 1);
Grid.SetColumn(keypadGrid, 0);
}
// Landscape mode.
else
{
mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
mainGrid.ColumnDefinitions[1].Width = GridLength.Auto;
Grid.SetRow(keypadGrid, 0);
Grid.SetColumn(keypadGrid, 1);
}
}
}

O programa AddingMachine demonstra uma maneira de lidar com o ViewModel, mas não é o único caminho. Como alternativa, é possível
para App para instanciar o AdderViewModel mas definem uma propriedade do tipo AdderViewModel que o construtor de
AddingMachinePage pode acessar.
Ou, se você quiser que a página tem o controle total sobre o ViewModel, você pode fazer isso também. Adding- MachinePage pode
definir seu próprio método OnSleep que é chamado a partir do método OnSleep na classe App, e a classe de página também pode lidar
com a instanciação de AdderViewModel ea chamada dos métodos RestoreState e savestate. No entanto, esta abordagem pode tornar-
se um pouco desajeitado para aplicações de várias páginas.
Em um aplicativo de várias páginas, você pode ter ViewModels separadas para cada página, talvez decorrente de um ViewModel com
propriedades aplicáveis a todo o aplicativo. Nesse caso, você vai querer evitar propriedades com o mesmo nome usando as mesmas
chaves de dicionário para salvar o estado de cada ViewModel. Você pode usar mais extensas chaves de dicionário que incluem o nome
da classe, por exemplo, "Adder- ViewModel.CurrentEntry".
Embora o poder e as vantagens de ligação de dados e ViewModels deve ser aparente até agora, esses recursos realmente florescer
quando usado com o ListView Xamarin.Forms. Isso é até no próximo capítulo.

279
Capítulo 27

Renderizadores Customizados
No centro do Xamarin.Forms está uma coisa que pode parecer mágica: a habilidade de um simples elemento como um Button aparecer
como um botão nativo nos sistemas operacionais iOS, Android e Windows. Neste capítulo você verá como nessas três plataformas como
cada elemento no Xamarin.Forms é suportado por uma classe especial conhecida como renderer. Por exemplo, a classe Button no
Xamarin.Forms é suportada por várias classes nas diversas plataformas, cada nomeada ButtonRenderer.
A notícia boa é que você pode escrever seus próprios renderizadores, e neste capítulo lhe mostraremos como. Entretanto, lembre-se
que renderizadores customizados é um tópico grande e este capítulo é apenas o começo.
Escrever um renderizador customizado não é tão fácil quanto escrever uma aplicação Xamarin.Forms. Você precisará estar familiarizado
com as plataformas iOS, Android e Windows. Mas obviamente é uma técnica poderosa. De fato, alguns desenvolvedores pensão no
valor final do Xamarin.Forms como fornecedor de um framework estruturado para escrever renderizadores customizados.

A hierarquia de classe completa


No Capítulo 11, "A infraestrutura bindable", você viu um programa chamado ClassHierarchy que mostra a hierarquia de classes do
Xamarin.Forms. Entretanto, este programa somente mostra os tipos nos assembles Xamarin.Forms.Core e Xamarin.Forms.Xaml, que
são os tipos normalmente utilizados em aplicações Xamarin.Forms.
Xamarin.Forms também contém assembles adicionais associados a cada plataforma. Estes assembles possuem o papel crucial de fazer
com que o Xamarin.Forms suporte cada plataforma, incluindo todos os renderizadores
Você provavelmente já está familiarizado com o nome desses montadores de vê-los na seção Reference de vários projetos no seu
Xamarin.Forms:

• Xamarin.Forms.Platform (muito pequeno)

• Xamarin.Forms.Platform.iOS

• Xamarin.Forms.Platform.Android

• Xamarin.Forms.Platform.UAP

• Xamarin.Forms.Platform.WinRT (maior que os próximos dois seguintes)


• Xamarin.Forms.Platform.WinRT.Tablet

• Xamarin.Forms.Platform.WinRT.Phone

Nesse livro, eles serão referenciados coletivamente como assembles de plataforma.


É possível escrever uma aplicação Xamarin.Forms que mostra uma hierarquia de classes de tipos nesses assembles de plataforma?
Sim! Entretanto, se você se restringiu a examinar somente os assembles normalmente carregados com a aplicação - e esta é certamente
a melhor abordagem - então a aplicação pode somente mostrar os tipos no assemble que são parte daquela aplicação. Por exemplo,
você só pode mostrar os tipos no assemble Xamarin.Forms.Plataform.iOS com um programa Xamarin.Forms rodando no iOS, e
similarmente para os outros assembles.
Mas ainda há um problema: Como você deve se lembrar, o programa inicial ClassHierarchy começou obtendo objetos Assembly .NET
para os assembles Xamarin.Forms.Core e Xamarin.Forms.Xaml baseados em duas classes (View e Extensions) que ele sabia
estar nesses dois assembles:

typeof(View).GetTypeInfo().Assembly
typeof(Extensions).GetTypeInfo().Assembly

Entretanto, uma biblioteca de classes portáveis de uma aplicação Xamarin.Forms não tem acesso direto aos assembles de plataforma.
Os assembles de plataforma são referenciados somente pelos projetos da aplicação. Isso significa que uma biblioteca de classes
portáveis de um Xamarin.Forms não pode utilizar um código similar para ter uma referência para o assemble de plataforma. Isto não vai
funcionar:

typeof(ButtonRenderer).GetTypeInfo().Assembly

280
Entretanto, estes assembles de plataforma são carregados quando a aplicação roda, de modo que o PCL em vez disso pode obter
objectos Assembly para os assembles de plataforma com base no nome assemble. O programa PlataformClassHierarchy começa
assim:

public partial class PlatformClassHierarchyPage : ContentPage


{
public PlatformClassHierarchyPage()
{
InitializeComponent();
List<TypeInformation> classList = new List<TypeInformation>();
string[] assemblyNames = Device.OnPlatform(
iOS: new string[] { "Xamarin.Forms.Platform.iOS" },
Android: new string[] { "Xamarin.Forms.Platform.Android" },
WinPhone: new string[] { "Xamarin.Forms.Platform.UAP",
"Xamarin.Forms.Platform.WinRT",
"Xamarin.Forms.Platform.WinRT.Tablet",
"Xamarin.Forms.Platform.WinRT.Phone" }
);
foreach (string assemblyName in assemblyNames)
{
try
{
Assembly assembly = Assembly.Load(new AssemblyName(assemblyName));
GetPublicTypes(assembly, classList);
}
catch
{
}
}

}

E para o programa PlataformClassHierarchy é o mesmo como o programa inicial ClassHierarchy.


Como você pode ver o laço foreach obtém o objeto Assembly de um método estático Assembly.Load. Entretanto, não há uma
maneira direta para o programa determinar se está executando em um Universal Windows Plataform ou em uma das outras plataformas
Windows, então se o Device.OnPlatform indica que é um dispositivo WinPhone, o programa tenta todos quatro assembles e usa o
try/cacth somente para ignorar os que não funcionam.
Alguns nomes de classes - e particularmente o nome completo das classes para classes fora do assemble - são um pouco grandes
demais para telas no modo retrato e são estranhamente abreviadas, mas aqui está parte da tela nas três plataformas. Cada uma foi
rolada para a parte da hierarquia de classes que começa com a classe genérica ViewRenderer. Normalmente esta será a classe que
você derivará para criar seu próprio renderizador customizado:

281
Observe os parâmetros genéricos para a classe ViewRenderer nomeados TView e TNativeView, ou TElement e
TNativeElement: como você verá TView ou TElement é um elemento Xamarin.Forms como Button, enquanto TNativeView ou
TNativeElement é o controle nativo para o Button.
Apesar do programa PlataformClassHierarchy não indicar isto, as restrições para os parâmetros genericos do ViewRenderer são
dependentes da plataforma:

• No iOS:
o TView é restringido a Xamarin.Forms.View
o TNativeView é restringido a UIKit.UIView

• No Android:
o TView é restringido a Xamarin.Forms.View
o TNativeView é restringido a Android.Views.View

• Na plataforma Windows:
o TElement é restringido a Xamarin.Forms.View
o TNativeElement é restringido a Windows.UI.Xaml.FrameworkElement
Para escrever um renderizador customizado, você deriva uma classe de ViewRenderer. Para atender todas plataformas, você deve
implementar o renderizador iOS usando uma classe que derive de UIView, implementar um renderizador Android com uma classe que
derive de View, e implementar um renderizador para a plataforma Windows que derive de FrameworkElement.

Olá renderizadores customizados!


O programa HelloRenderers demonstra a sobrecarga necessária para escrever um simples renderizador.
O programa define um HelloView derivado de View com a intenção de exibir um texto na tela. Aqui está o arquivo completo
HelloView.cs na biblioteca de classes portáveis do projeto HelloRenderers:

using Xamarin.Forms;
namespace HelloRenderers
{
public class HelloView : View
{
}
}
É isso! Entretanto, note que a classe é definida como public. Apesar de você poder pensar que esta classe é somente referenciada
dentro do PCL, este não é o caso. Ela deve estar visível para os assembles da plataforma.
O HelloRenderers PCL é tão simples que nem se preocupa com a classe page. Em vez disso ele instancia e apresenta um objeto
HelloView centralizado na página direto no arquivo App.cs:

namespace HelloRenderers
{
public class App : Application
{
public App()
{
MainPage = new ContentPage
{
Content = new HelloView
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center
}
};
}

}
}

Sem outro código qualquer, este programa executa adequadamente, mas na verdade você não verá o objeto HelloView na tela porque
ele é transparente. O que precisamos é um renderizador de plataforma para o HelloView.

282
Quando uma aplicação Xamarin.Forms inicia, o Xamarin.Forms utiliza a reflexão .NET para procurar as vários assembles que compõem
o aplicativo, procurando por conjunto atributos nomeados ExportRenderer. Um atributo ExportRenderer indica a presença de um
renderizador customizado que pode prover suporte a um elemento Xamarin.Forms.
O projeto HelloRenderers.iOS contém um arquivo chamado HelloViewRenderer.cs, apresentado a seguir por completo. Observe o
atributo ExportRenderer logo abaixo das diretivas using. Devido ele ser um atributo de montagem, ele deve ficar fora da declaração
do namespace. Basicamente o atributo ExportRenderer diz: "A classe HelloView é suportada por um renderizador do tipo
HelloViewRenderer”:

using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using HelloRenderers;
using HelloRenderers.iOS;
[assembly: ExportRenderer(typeof(HelloView), typeof(HelloViewRenderer))]
namespace HelloRenderers.iOS
{
public class HelloViewRenderer : ViewRenderer<HelloView, UILabel>
{
protected override void OnElementChanged(ElementChangedEventArgs<HelloView> args)
{
base.OnElementChanged(args);
if (Control == null)
{
UILabel label = new UILabel
{
Text = "Hello from iOS!",
Font = UIFont.SystemFontOfSize(24)
};
SetNativeControl(label);
}
}
}
}

A definição da classe HelloViewRenderer segue o atributo ExportRenderer. A classe deve ser pública. Ela deriva da classe
genérica ViewRenderer. Os dois parâmetros genéricos são nomeados TView, que é a classe Xamarin.Forms, e TNativeView, a qual
é a classe neste caso que é nativa do iOS.
No iOS, a classe que exibe texto é UILabel no namespace UIKit, e é oque foi usado aqui. Os dois argumentos genericos para
ViewRenderer basicamente dizem "O objeto HelloView é atualmente renderizado como um objeto iOS UILabel".
Um trabalho essencial para um derivado de ViewRenderer é sobrescrever o método OnElementChanged. Este método é chamado
quando um objeto HelloView é criado, e seu trabalho é criar um controle nativo para renderizar o objeto HelloView.
O método OnElementChanged sobrescrito começa checando a propriedade Control que a classe herda de ViewRenderer. A
propriedade Control é definida pelo ViewRenderer para ser do tipo TNativeView, então no HelloViewRenderer ela é do tipo
UILabel. A primeira vez que o método OnElementChanged é chamado, a propriedade Control será null. O objeto UILabel deve
ser criado. Isto é que o método faz, atribuindo a ele um texto e um tamanho. Aquele método UILabel é passado para o método
SetNativeControl. Depois disso, a propriedade Control será este objeto UILabel.
As diretivas using no começo do arquivo são divididas em três grupos:

• A diretiva using para o namespace Xamarin.Forms é requerida para o atributo ExportRenderer, enquanto
Xamarin.Forms.Plataform.iOS é requerida para a classe ViewRenderer.

• O namespace iOS UIKit é requerido para o UILabel.

• As diretivas using para HelloRenderers e HelloRenderers.iOS são requeridas somente para as referências
HelloView e HelloViewRenderer no atributo ExportRenderer porque o atributo deve estar fora do bloco do
namespace HelloRenderer.iOS.
As últimas duas diretivas using são particularmente irritantes porque eles são somente requeridos para um único propósito. Se você
quiser, você pode remover estas duas diretivas e colocar o nome completo das classes dentro do atributo ExportRenderer.
Isto é feito no renderizador a seguir. Aqui está o arquivo completo do HelloViewRenderer.cs do projeto HelloRenderers.Droid. O Android
widget para exibir texto é TextView no namespace Andorid.Widget:

283
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Util;
using Android.Widget;
[assembly: ExportRenderer(typeof(HelloRenderers.HelloView),
typeof(HelloRenderers.Droid.HelloViewRenderer))]
namespace HelloRenderers.Droid
{
public class HelloViewRenderer : ViewRenderer<HelloView, TextView>
{
protected override void OnElementChanged(ElementChangedEventArgs<HelloView> args)
{
base.OnElementChanged(args);
if (Control == null)
{
SetNativeControl(new TextView(Context)
{
Text = "Hello from Android!"
});
Control.SetTextSize(ComplexUnitType.Sp, 24);
}
}
}
}

Esta classe HelloViewRenderer deriva da versão do Android de ViewRenderer. Os argumentos genéricos para ViewRenderer
indica que a classe e suportada pelo widget Android TextView.
Mais uma vez, na primeira chamada para OnElementChanged, a propriedade Control será null. O método deve criar um widget
Android nativo TextView e chamar o método SetNativeControl. Observe que o construtor requer um objeto Android Context. Ele
está disponível como uma propriedade de OnElementChanged.
Depois da chamada de SetNativeControl, a propriedade Control definida por ViewRenderer é um widget Android nativo, neste
caso um objeto TextView. O método usa esta propriedade Control para chamar SetTextSize no objeto TextView. No Android,
o tamanho do texto pode ser escalado de diversas formas. O membro de enumeração ComplexUnitType.Sp indica "scaled pixels", o
qual é compatível como o Xamarin.Forms trata tamanhos de fontes para Label no Android.
Aqui está a versão UWP do HelloRenderer no projeto HelloRenderers.UWP

using Xamarin.Forms.Platform.UWP;
using Windows.UI.Xaml.Controls;
[assembly: ExportRenderer (typeof(HelloRenderers.HelloView),
typeof(HelloRenderers.UWP.HelloViewRenderer))]
namespace HelloRenderers.UWP
{
public class HelloViewRenderer : ViewRenderer<HelloView, TextBlock>
{
protected override void OnElementChanged(ElementChangedEventArgs<HelloView> args)
{
base.OnElementChanged(args);
if (Control == null)
{
SetNativeControl(new TextBlock
{
Text = "Hello from the UWP!",
FontSize = 24,
});
}
}
}
}

284
Em todas plataformas Windows, o objeto HelloView é renderizado pelo Windows Runtime TextBlock no namespace
Windows.UI.Xaml.Controls.
A classe HelloViewRenderer nos projetos HelloRenderers.Windows e HelloRenderers.WinPhone são basicamente a mesma
exceto pelo namespace e o texto usado para setar a propriedade Text do TextBlock.
Aqui está o programa rodando nas três plataformas padrão:

Observe como o texto é apropriadamente centralizado pelo uso das propriedades HorizontalOptions e VerticalOptions setadas
no objeto HelloView. Entretanto, você não pode setar as propriedades HorizontalTextAlignment e VerticalTextAlignment
no HelloView. Estas propriedades são definidas por Label e não pelo HelloView.
Para transformar o HelloView em uma view para exibir texto, você precisara adicionar propriedades à classe HelloView. Vamos
examinar como propriedades são adicionadas ao renderizador com um exemplo diferente.

Renderizadores e propriedades
Xamarin.Forms inclui um elemento BoxView para exibir blocos retangulares coloridos. Você já desejou ter algo parecido para desenhar
um círculo, ou para ser mais genérico, uma elipse?
Este é o propósito do EllipseView. Entretanto, como você pode querer utilizar o EllipseView em múltiplas aplicações, ele é
implementado na biblioteca Xamarin.FormsBook.Plataform, apresentado no capítulo 20, "Assincronismo e I/O de arquivo."
BoxView define uma propriedade - a propriedade Color do tipo Color - e EllipseView pode fazer o mesmo. Ele não precisa de
propriedades para definir a largura e altura da elipse porque ele herda de WidthRequest e HeightRequest de VisualElement.
Então aqui está um EllipseView como definida na biblioteca do projeto Xamarin.FormsBook.Plataform

namespace Xamarin.FormsBook.Platform
{
public class EllipseView : View
{
public static readonly BindableProperty ColorProperty =
BindableProperty.Create(
"Color",
typeof(Color),
typeof(EllipseView),
Color.Default);
public Color Color
{
set { SetValue(ColorProperty, value); }
get { return (Color)GetValue(ColorProperty); }
}
protected override SizeRequest OnSizeRequest(double widthConstraint,
double heightConstraint)
{
return new SizeRequest(new Size(40, 40));
}
}
285
}

A propriedade Color simplesmente envolve uma definição básica de uma propriedade ligável sem um manipulador de troca de
propriedade. A propriedade é definida, mas não parece estar fazendo nada. De algum modo a propriedade Color definida em
EllipseView tem que ser ligada com a propriedade no objeto que o renderizador está renderizando.
O único outro código no EllipseView é o método sobrescrito OnSizeRequest para definir o tamanho padrão da elipse, do mesmo
modo como em BoxView.
Vamos começar com a plataforma Windows. Acontece que o renderizador Windows para EllipseView é mais simples que os
renderizadores para iOS e Android.
Como você se lembra, a solução Xamarin.FormsBook.Plataform criada no capítulo 20 tem a facilidade de permitir o compartilhamento
de código entre as várias plataformas Windows: a biblioteca Xamarin.FormsBook.Plataform.UWP, a biblioteca
Xamarin.FormsBook.Plataform.Windows, e a biblioteca Xamarin.FormsBook.Plataform.WinPhone todas tem referências para a
biblioteca Xamarin.FormsBook.Plataform.WinRT, a qual não é absolutamente uma biblioteca mas um projeto compartilhado. Este
projeto compartilhado é onde a classe ViewRenderer para todas as plataformas Windows pode residir.
Nas plataformas Windows, um EllipseView pode ser renderizado por um elemento Windows nativo chamado Ellipse no
namespace Windows.UI.Xaml.Shapes, porque o Ellipse satisfaz o critério de derivar de
Windows.UI.Xaml.FrameworkElement.
O Ellipse é especificado como o segundo argumento genérico para a classe ViewRenderer. Como este arquivo é compartilhado
por todas plataformas Windows, ele precisa pré-processar as diretivas para incluir o namespace correto para as classes
ExportRendererAttribute e ViewRenderer.

using System.ComponentModel;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
#else
using Xamarin.Forms.Platform.WinRT;
#endif
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView),
typeof(Xamarin.FormsBook.Platform.WinRT.EllipseViewRenderer))]
namespace Xamarin.FormsBook.Platform.WinRT
{
public class EllipseViewRenderer : ViewRenderer<EllipseView, Ellipse>
{
protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)
{
base.OnElementChanged(args);
if (Control == null)
{
SetNativeControl(new Ellipse());
}
if (args.NewElement != null)
{
SetColor();
}
}

}
}

Como você poderia esperar por agora, o OnElementChanged sobrescrito primeiro verifica se a propriedade Control é null, e em
caso afirmativo, ele cria o objeto nativo, neste caso, uma Ellipse, e passa para SetNativeControl. Depois disso, a propriedade
Control é definida para este objeto Ellipse.
No método OnElementChanged sobrescrito também contém algum código adicional envolvendo o argumento
ElementChangedEventArgs. Ele requer uma pequena explicação:
Cada instância do renderizador - neste exemplo, uma instância da classe EllipseViewRenderer - mantém uma instância única do
objeto nativo, neste exemplo o Ellipse.

286
No entanto, a infraestrutura de renderização tem uma facilidade tanto para anexar uma instância renderizador para um elemento
Xamarin.Forms e para retirá-la e anexar um outro elemento Xamarin.Forms para o mesmo renderizador. Talvez Xamarin.Forms precise
recriar o elemento ou substituir outro elemento para o já associado com o renderizador.
Mudanças desse tipo são comunicadas ao renderizador com chamadas para OnElementChanged. O argumento
ElementChangedEventArgs inclui duas propriedades, OldElement e NewElement, tanto do tipo indicado no argumento genérico
para ElementChangedEventArgs, neste caso EllipseView. Em muitos casos, você não precisa se preocupar com diferentes
elementos Xamarin.Forms sendo conectados e desconectados de uma única instância renderizador. Mas, em alguns casos, você pode
querer usar a oportunidade de limpar ou liberar alguns recursos que o seu renderizador usa.
No caso mais simples e mais comum, cada instância do processador irá receber uma chamada para OnElementChanged para o
Xamarin.Forms ver que usa esse renderizador. Você vai usar a chamada para OnElementChanged para criar o elemento nativo e
passá-lo para SetNativeControl, como você já viu. Após essa chamada para SetNativeControl, a propriedade de Control
definido pelo ViewRenderer é o objeto nativo, neste caso, o Ellipse.
No momento que você começa a receber a chamada para OnElementChanged, o objeto Xamarin.Forms (neste caso, um
EllipseView) provavelmente já foi criado e que também pode ter algumas propriedades definidas. (Em outras palavras, o elemento
pode ser inicializado com algumas configurações de propriedade no momento em que o renderizador é exigido para exibir o elemento.)
Mas o sistema é concebido de modo que este não é necessariamente o caso. É possível que uma chamada subsequente para
OnElementChanged indica que um EllipseView foi criado.
O que interessa é a propriedade NewElement dos argumentos do evento. Se essa propriedade não é null (que é o caso normal),
esta a propriedade é o elemento Xamarin.Forms, e você deve transferir as definições de propriedade desse elemento Xamarin.Forms
para o objeto nativo. Esse é o propósito da chamada para o método SetColor mostrado acima. Você vai ver o corpo desse método
em breve.
O ViewRenderer define uma propriedade chamada Element que define o elemento Xamarin.Forms, neste caso, um EllipseView.
Se a chamada mais recente para OnElementChanged continha uma propriedade NewElement não nulo, então Element é o mesmo
objeto.
Resumindo, estas são as duas propriedades essenciais que você pode usar na sua classe de renderização:

• Element - o elemento Xamarin.Forms, válido se a chamada OnElementChanged mais recente tinha uma propriedade
NewElement não nula.

• Control - a view nativa, ou widget, ou objeto control, válido após uma chamada para SetNativeView.
Como você sabe, as propriedades dos elementos Xamarin.Forms podem mudar. Por exemplo, a propriedade Color de EllipseView
pode ser animada. Se uma propriedade, tal como Color é apoiado por uma propriedade ligável, qualquer alteração a essa propriedade
faz com que um evento PropertyChanged ser disparado.
O renderizador também é notificado da mudança de propriedade. Qualquer alteração a uma propriedade ligável em um elemento
Xamarin.Forms ligado a um renderizador também faz uma chamada para o método protegido virtual OnElementPropertyChanged
na classe ViewRenderer. Neste exemplo em particular, qualquer alteração a qualquer propriedade ligável em EllipseView (incluindo
a propriedade Color) gera uma chamada para OnElementPropertyChanged. Seu renderizador deve sobrescrever esse método e
verificar que a propriedade mudou:
namespace Xamarin.FormsBook.Platform.WinRT
{
public class EllipseViewRenderer : ViewRenderer<EllipseView, Ellipse>
{

protected override void OnElementPropertyChanged(object sender,
PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
{
SetColor();
}
}

}
}

Se a propriedade Color mudou, a propriedade PropertyName do argumento do evento é "Color", o nome do texto especificado
quando a propriedade ligável EllipseView.ColorProperty foi criada. Mas, para evitar erro de ortografia do nome, o método
OnElementPropertyChanged verifica o atual valor do texto na propriedade ligável. O renderizador deve responder ao transferir essa
nova configuração da propriedade Color para o objeto nativo, neste caso, o objeto do Windows Ellipse.
Este método SetColor é chamado de apenas dois lugares - no método sobrescrito OnElementChanged e no método sobrescrito
OnElementPropertyChanged. Não pense que você pode ignorar a chamada em OnElementChanged sob a suposição de que a
287
propriedade não foi alterada antes da chamada para OnElementChanged. É muito frequentemente o caso que OnElementChanged
é chamado após que um elemento foi inicializado com as configurações de propriedade.
No entanto, SetColor pode fazer algumas suposições válidas sobre a existência do elemento Xamarin.Forms e o controle nativo:
Quando SetColor é chamado a partir OnElementChanged, o controle nativo foi criado e NewElement não é null. Isto significa que
ambas as propriedades, Control e Element são válidas. A propriedade Element também é válida quando
OnElementPropertyChanged é chamado porque é o objeto cuja propriedade acaba de mudar.
Isto significa que o método SetColor pode simplesmente transferir uma cor de Element (o elemento Xamarin.Forms) para Control,
o objeto nativo. Para evitar conflitos de namespace, este método SetColor qualifica plenamente todas as referências a qualquer
estrutura chamada Color:

namespace Xamarin.FormsBook.Platform.WinRT
{
public class EllipseViewRenderer : ViewRenderer<EllipseView, Ellipse>
{

void SetColor()
{
if (Element.Color == Xamarin.Forms.Color.Default)
{
Control.Fill = null;
}
else
{
Xamarin.Forms.Color color = Element.Color;
global::Windows.UI.Color winColor =
global::Windows.UI.Color.FromArgb((byte)(color.A * 255),
(byte)(color.R * 255),
(byte)(color.G * 255),
(byte)(color.B * 255));
Control.Fill = new SolidColorBrush(winColor);
}
}
}
}

O objeto do Windows Ellipse tem uma propriedade denominada de Fill do tipo Brush. Por padrão, essa propriedade é null, e é
isso que o método SetColor define se a propriedade Color de EllipseView é Color.Default. Caso contrário, Xamarin.Forms
Color deve ser convertida para um Windows Color, que é então passada para o construtor de SolidColorBrush. Os objetos
SolidColorBrush é definido para a propriedade Fill de Ellipse.
Essa é a versão Windows, mas quando chega a hora de criar renderizadores iOS e Android para EllipseView, você pode sentir um
pouco frustrado. Aqui, novamente, são as restrições para o segundo parâmetro genérico para ViewRenderer:

• iOS: TNativeView é forçado para UIKit.UIView

• Android: TNativeView é forçado para Android.View.Views

• Windows: TNativeElement é forçado para Windows.UI.Xaml.FrameworkElement


Isto significa que para fazer um renderizador EllipseView para iOS, você precisa de um UIView derivado que exibe uma elipse.
Será que algo assim existe? Não, não existe. Portanto, você deve fazer um você mesmo. Este é o primeiro passo para fazer o
renderizador iOS.
Por essa razão, a biblioteca Xamarin.FormsBook.Platform.iOS contém uma classe chamada EllipseUIView que deriva de UIView
com o único propósito de desenhar uma elipse:

using CoreGraphics;
using UIKit;
namespace Xamarin.FormsBook.Platform.iOS
{
public class EllipseUIView : UIView
{
UIColor color = UIColor.Clear;
public EllipseUIView()
{
BackgroundColor = UIColor.Clear;
288
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
using (CGContext graphics = UIGraphics.GetCurrentContext())
{
//Create ellipse geometry based on rect field.
CGPath path = new CGPath();
path.AddEllipseInRect(rect);
path.CloseSubpath();

//Add geometry to graphics context and draw it.


color.SetFill();
graphics.AddPath(path);
graphics.DrawPath(CGPathDrawingMode.Fill);
}
}
public void SetColor(UIColor color)
{
this.color = color;
SetNeedsDisplay();
}
}
}
A classe sobrescreve o método OnDraw para criar um caminho gráfico de uma elipse e depois desenhá-la no contexto gráfico. A cor
que utiliza é armazenada como um campo e é inicialmente ajustado para UIColor.Clear, que é transparente. No entanto, você vai
notar um método SetColor na parte inferior. Isto proporciona nova cor para a classe e, em seguida, chama SetNeedsDisplay, o que
invalida a superfície de desenho e gera outra chamada para OnDraw.
Observe também que o BackgroundColor do UIView é definido no construtor para UIColor.Clear. Sem esse ajuste, a exibição
tem um fundo preto na área não coberta pela elipse.
Agora que existe a classe EllipseUIView para iOS, o EllipseViewRenderer pode ser escrito usando EllipseUIView como o
controle nativo. Estruturalmente, esta classe é praticamente idêntica ao renderizador do Windows:

using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView),
typeof(Xamarin.FormsBook.Platform.iOS.EllipseViewRenderer))]
namespace Xamarin.FormsBook.Platform.iOS
{
public class EllipseViewRenderer : ViewRenderer<EllipseView, EllipseUIView>
{
protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)
{
base.OnElementChanged(args);
if (Control == null)
{
SetNativeControl(new EllipseUIView());
}
if (args.NewElement != null)
{
SetColor();
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
{
SetColor();
}
289
}

void SetColor()
{
if (Element.Color != Color.Default)
{
Control.SetColor(Element.Color.ToUIColor());
}
else
{
Control.SetColor(UIColor.Clear);
}
}
}
}
As únicas diferenças reais entre este renderizador e a versão do Windows é que a propriedade de Control é definida como uma
instancia de ColorUIView, e o corpo do método SetColor na parte inferior é diferente. Ele agora chama o método SetColor em
ColorUIView. Este método SetColor também é capaz de fazer uso de um método público de extensão na biblioteca
Xamarin.Forms.Platform.iOS chamado ToUIColor para converter uma cor Xamarin.Forms para uma cor iOS.
Você deve ter notado que nem o representante do Windows nem o renderizador iOS teve que se preocupar sobre o dimensionamento.
Como você verá em breve, uma EllipseView pode ser ajustada para uma variedade de tamanhos, e o tamanho calculado no sistema
de layout Xamarin.Forms torna-se o tamanho do controle nativo.
Esta, infelizmente, acabou por não ser o caso com o renderizador Android. O renderizador Android precisa de alguma lógica de
dimensionamento. Como iOS, Android também está faltando um controle nativo que torna uma elipse. Portanto, a biblioteca
Xamarin.FormsBook.Platform.Android contém uma classe chamada EllipseDrawableView que deriva de View e desenha uma
elipse:

using Android.Content;
using Android.Views;
using Android.Graphics.Drawables;
using Android.Graphics.Drawables.Shapes;
using Android.Graphics;

namespace Xamarin.FormsBook.Platform.Android
{
public class EllipseDrawableView : View
{
ShapeDrawable drawable;
public EllipseDrawableView(Context context) : base(context)
{
drawable = new ShapeDrawable(new OvalShape());
}

protected override void OnDraw(Canvas canvas)


{
base.OnDraw(canvas);
drawable.Draw(canvas);
}

public void SetColor(Xamarin.Forms.Color color)


{
drawable.Paint.SetARGB((int)(255 * color.A),
(int)(255 * color.R),
(int)(255 * color.G),
(int)(255 * color.B));
Invalidate();
}
public void SetSize(double width, double height)
{
float pixelsPerDip = Resources.DisplayMetrics.Density;
drawable.SetBounds(0, 0, (int)(width * pixelsPerDip),
(int)(height * pixelsPerDip));
Invalidate();
290
}
}
}

Estruturalmente, este é semelhante à classe EllipseUIView definido para iOS, exceto que o construtor cria um objeto
ShapeDrawable para uma elipse, e o OnDraw substitui o renderizadores.
Essa classe tem dois métodos para definir as propriedades desta elipse. O método SetColor converte um Xamarin.Forms de cor para
definir a propriedade de Paint do objeto ShapeDrawable, e o método SetSize converte um tamanho em unidades independentes
de dispositivo de pixels para a definição dos limites do objeto ShapeDrawable. Ambos SetColor e SetSize conclui com uma chamada
para Invalidate para invalidar a superfície de desenho e gerar outra chamada para OnDraw.
O renderizador Android faz uso da classe EllipseDrawableView como seu objeto nativo:

using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView),
typeof(Xamarin.FormsBook.Platform.Android.EllipseViewRenderer))]
namespace Xamarin.FormsBook.Platform.Android
{
public class EllipseViewRenderer : ViewRenderer<EllipseView, EllipseDrawableView>
{
double width, height;

protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)


{
base.OnElementChanged(args);
if (Control == null)
{
SetNativeControl(new EllipseDrawableView(Context));
}
if (args.NewElement != null)
{
SetColor();
SetSize();
}
}

protected override void OnElementPropertyChanged(object sender,


PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == VisualElement.WidthProperty.PropertyName)
{
width = Element.Width;
SetSize();
}
else if (args.PropertyName == VisualElement.HeightProperty.PropertyName)
{
height = Element.Height;
SetSize();
}
else if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
{
SetColor();
}
}
void SetColor()
{
Control.SetColor(Element.Color);
}
void SetSize()
{
Control.SetSize(width, height);
291
}
}
}

Observe que o método OnElementPropertyChanged precisa verificar se há alterações nas propriedades Width e Height, e guardá-
las em campos que podem ser combinados em um único Bounds de definição para a chamada SetSize para EllipseDrawableView.
Com todos os renderizadores no lugar, é hora de ver se ele funciona. A solução EllipseDemo também contém links para os vários
projetos da solução Xamarin.FormsBook.Platform, e cada um dos projetos em EllipseDemo contém uma referência para o projeto de
biblioteca correspondente no Xamarin.Forms-Book.Platform.
Cada um dos projectos em EllipseDemo também contém uma chamada para o método Toolkit.Init na biblioteca correspondente.
Isto nem sempre é necessário. Mas tenha em mente que os vários renderers não estão diretamente referenciados por qualquer código
em qualquer um dos projetos, e algumas otimizações podem fazer com que o código não esteja disponível em tempo de execução. A
chamada para Toolkit.Init evita isso.
O arquivo XAML em EllipseDemo cria vários objetos EllipseView com diferentes cores e tamanhos, alguns limitados em tamanho,
enquanto outros são permitidos para preencher seu container:

<?xml version="1.0" encoding="utf-8" ?>


<ContentPage xmlns=”http://xamarin.com/schemas/2014/forms”
xmlns:x=”http://schemas.microsoft.com/winfx/2009/xaml”
xmlns:platform=
"clr-namespace:Xamarin.FormsBook.Platform;assembly=Xamarin.FormsBook.Platform"
x:Class="EllipseDemo.EllipseDemoPage">
<Grid>
<platform:EllipseView Color="Aqua" />
<StackLayout>
<StackLayout.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</StackLayout.Padding>

<platform:EllipseView Color="Red"
WidthRequest="40"
HeightRequest="80"
HorizontalOptions="Center" />
<platform:EllipseView Color="Green"
WidthRequest="160"
HeightRequest="80"
HorizontalOptions="Start" />
<platform:EllipseView Color="Blue"
WidthRequest="160"
HeightRequest="80"
HorizontalOptions="End" />
<platform:EllipseView Color="#80FF0000"
HorizontalOptions="Center" />
<ContentView Padding="50"
VerticalOptions="FillAndExpand">
<platform:EllipseView Color="Red"
BackgroundColor="#80FF0000" />
</ContentView>
</StackLayout>
</Grid>
</ContentPage>

Tome nota em particular do penúltimo EllipseView que se dá pela cor vermelha semi-opaca. Contra Aqua da grande elipse encher a
página, isso deve processar como cinza médio.
A última EllipseView dá-se uma configuração BackgroundColor de vermelho meio-opaco. Novamente, isso deve processar como
cinza contra a grande elipse Aqua, mas como uma luz vermelha sobre um fundo branco e vermelho escuro contra um fundo preto. Aqui
estão eles:

292
Depois de ter um EllipseView, é claro que você vai querer escrever um programa de salto-bola. A solução BouncingBall também
inclui links para todos os projetos na solução Xamarin.FormsBook.Platform, e todos os projetos de aplicativos têm referências aos
projetos de biblioteca correspondentes. O BouncingBall PCL tem também uma referência à biblioteca Xamarin.FormsBook.Toolkit
para uma estrutura chamada Vector2, um vector bidimensional.
O arquivo XAML posiciona um EllipseView no centro da página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x=”http://schemas.microsoft.com/winfx/2009/xaml”
xmlns:platform=
"clr-namespace:Xamarin.FormsBook.Platform;assembly=Xamarin.FormsBook.Platform"
x:Class="BouncingBall.BouncingBallPage">

<platform:EllipseView x:Name="ball"
WidthRequest="100"
HeightRequest="100"
HorizontalOptions="Center"
VerticalOptions="Center" />
</ContentPage>

O arquivo code-behind inicia-se duas animações que correm "para sempre". A primeira animação é definida no construtor e anima a
propriedade Color da bola quicando para levá-lo através das cores do arco-íris a cada 10 segundos.
A segunda animação bate a bola sobre as quatro “paredes” da tela. Para cada ciclo através do loop while, o primeiro código determina
que a parede que vai bater em primeiro lugar e a distância a que a parede em unidades independentes do dispositivo. O novo cálculo
do center para a extremidade do loop while é a posição da bola, uma vez que atinge uma parede. O novo cálculo do vector
determina um vetor de deflexão com base em um vetor existente e um vetor que é perpendicular à superfície que está a bater (chamado
um vetor normal):

public partial class BouncingBallPage : ContentPage


{
public BouncingBallPage()
{
InitializeComponent();

// Color animation: cycle through rainbow every 10 seconds.


new Animation(callback: v => ball.Color = Color.FromHsla(v, 1, 0.5),
start: 0,
end: 1
).Commit(owner: this,
name: "ColorAnimation",
length: 10000,
repeat: () => true);

293
BounceAnimationLoop();
}

async void BounceAnimationLoop()


{
// Wait until the dimensions are good.
while (Width == -1 && Height == -1)
{
await Task.Delay(100);
}

// Initialize points and vectors.


Point center = new Point();
Random rand = new Random();
Vector2 vector = new Vector2(rand.NextDouble(), rand.NextDouble());
vector = vector.Normalized;
Vector2[] walls = { new Vector2(1, 0), new Vector2(0, 1), // left, top
new Vector2(-1, 0), new Vector2(0, -1) }; // right, bottom

while (true)
{
// The locations of the four "walls" (taking ball size into account).
double right = Width / 2 - ball.Width / 2;
double left = -right;
double bottom = Height / 2 - ball.Height / 2;
double top = -bottom;

// Find the number of steps till a wall is hit.


double nX = Math.Abs(((vector.X > 0 ? right : left) - center.X) / vector.X);
double nY = Math.Abs(((vector.Y > 0 ? bottom : top) - center.Y) / vector.Y);
double n = Math.Min(nX, nY);

// Find the wall that's being hit.


Vector2 wall = walls[nX < nY ? (vector.X > 0 ? 2 : 0) : (vector.Y > 0 ? 3 : 1)];

// New center and vector after animation.


center += n * vector;
vector -= 2 * Vector2.DotProduct(vector, wall) * wall;

// Animate at 3 msec per unit.


await ball.TranslateTo(center.X, center.Y, (uint)(3 * n));
}
}
}

Claro, uma fotografia ainda não pode capturar a ação emocionante da animação:

294
Renderizadores e eventos
A maioria dos elementos Xamarin.Forms são interativos. Eles respondem a entrada do usuário, disparando eventos. Se você implementa
um evento em seu elemento Xamarin.Forms personalizado, você provavelmente também precisará definir um evento handler nos
renderizadores para o evento correspondente aos disparos de controle nativos. Esta secção irá mostrar-lhe como.
O elemento StepSlider foi inspirado por um problema de implementação do Xamarin.Forms do elemento Windows Slider. Por
padrão, o Xamarin.Forms Slider quando executado nas plataformas Windows tem apenas 10 passos de 0 a 1, de modo que só é
capaz de Value valor de 0, 0,1, 0,2, e assim por diante até 1,0.
Como o regulares Xamarin.Forms Slider, o elemento StepSlider tem propriedades Minimum, Maximum, e Value, mas também
define uma propriedade Step para especificar o número de passos entre Minimum and Maximum. Por exemplo, se Minimum é definida
como 5, Maximum é definido como 10, e Step está definido para 20, em seguida, os valores possíveis da propriedade Value são 5,00,
5,25, 5,50, 5,75, 6,00, e assim por diante até 10. O numero de valores possíveis Value é igual ao valor Step mais 1.
Curiosamente, implementando esta propriedade Step acabou por exigir uma abordagem diferente em todas as três plataformas, mas o
objetivo principal deste exercício é para demonstrar como implementar eventos.
Aqui é a classe StepSlider na biblioteca Xamarin.FormsBook.Platform. Observe a definição do evento ValueChanged na parte
superior e a queima desse evento por mudanças na propriedade Value. Grande parte da maior das definições de propriedades que
podem ser ligadas são dedicados aos métodos validateValue, que garantem que a propriedade está dentro de limites aceitáveis, e
os métodos CoerceValue, que garantem que as propriedades são consistentes entre si:

namespace Xamarin.FormsBook.Platform
{
public class StepSlider : View
{
public event EventHandler<ValueChangedEventArgs> ValueChanged;

public static readonly BindableProperty MinimumProperty =


BindableProperty.Create(
"Minimum",
typeof(double),
typeof(StepSlider),
0.0,
validateValue: (obj, min) => (double)min < ((StepSlider)obj).Maximum,
coerceValue: (obj, min) =>
{
StepSlider stepSlider = (StepSlider)obj;
stepSlider.Value = stepSlider.Coerce(stepSlider.Value,
(double)min,
stepSlider.Maximum);
return min;
});

public static readonly BindableProperty MaximumProperty =


295
BindableProperty.Create(
"Maximum",
typeof(double),
typeof(StepSlider),
100.0,
validateValue: (obj, max) => (double)max > ((StepSlider)obj).Minimum,
coerceValue: (obj, max) =>
{
StepSlider stepSlider = (StepSlider)obj;
stepSlider.Value = stepSlider.Coerce(stepSlider.Value,
stepSlider.Minimum,
(double)max);
return max;
});

public static readonly BindableProperty StepsProperty =


BindableProperty.Create(
"Steps",
typeof(int),
typeof(StepSlider),
100,
validateValue: (obj, steps) => (int)steps > 1);

public static readonly BindableProperty ValueProperty =


BindableProperty.Create(
"Value",
typeof(double),
typeof(StepSlider),
0.0,
BindingMode.TwoWay,
coerceValue: (obj, value) =>
{
StepSlider stepSlider = (StepSlider)obj;
return stepSlider.Coerce((double)value,
stepSlider.Minimum,
stepSlider.Maximum);
},
propertyChanged: (obj, oldValue, newValue) =>
{
StepSlider stepSlider = (StepSlider)obj;
EventHandler<ValueChangedEventArgs> handler = stepSlider.ValueChanged;
if (handler != null)
handler(obj, new ValueChangedEventArgs((double)oldValue,
(double)newValue));
});

public double Minimum


{
set { SetValue(MinimumProperty, value); }
get { return (double)GetValue(MinimumProperty); }
}

public double Maximum


{
set { SetValue(MaximumProperty, value); }
get { return (double)GetValue(MaximumProperty); }
}

public int Steps


{
set { SetValue(StepsProperty, value); }
get { return (int)GetValue(StepsProperty); }
}

296
public double Value
{
set { SetValue(ValueProperty, value); }
get { return (double)GetValue(ValueProperty); }
}

double Coerce(double value, double min, double max)


{
return Math.Max(min, Math.Min(value, max));
}
}
}

A classe StepSlider dispara a propriedade ValueChanged quando há mudanças na propriedade Value, mas não há nada nesta
classe que muda a propriedade Value quando o usuário manipula o renderizador da plataforma para StepSlider. Que é deixado para
a classe renderizador.
Mais uma vez, vamos primeiro olhar para a implementação do Windows de StepSliderRenderer no projeto Xama-
rin.FormsBook.Platform.WinRT compartilhada porque é um pouco mais simples. O renderizador usa o
Windows.UI.Xaml.Controls.Slider para o controle nativo. Para evitar um conflito no namespace entre Windows Slider e o
Xamarin.Forms Slider, uma using diretiva define a win prefixo para se referir ao namespace Windows e usa isso para fazer
referência do Windows Slider:

using System.ComponentModel;
using Xamarin.Forms;
using Win = Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;

#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
#else
using Xamarin.Forms.Platform.WinRT;
#endif

[assembly: ExportRenderer( typeof(Xamarin.FormsBook.Platform.StepSlider),


typeof(Xamarin.FormsBook.Platform.WinRT.StepSliderRenderer))]
namespace Xamarin.FormsBook.Platform.WinRT
{
public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
{
protected override void OnElementChanged(ElementChangedEventArgs<StepSlider> args) {
{
base.OnElementChanged(args);

if (Control == null)
{
SetNativeControl(new Win.Slider());
}

if (args.NewElement != null)
{
SetMinimum();
SetMaximum();
SetSteps();
SetValue();
Control.ValueChanged += OnWinSliderValueChanged;
}
else
{
Control.ValueChanged -= OnWinSliderValueChanged;
}
}
...
}
297
}

A grande diferença entre esse renderizador e aquele que você já viu antes é que este define um manipulador de eventos no evento
ValueChanged do Windows Slider nativo. (Você verá o manipulador de eventos em breve.) Se args.NewElement torna-se null,
no entanto, isso significa que não há mais um elemento Xamarin.Forms anexado ao processador e que o manipulador de eventos não é
mais necessário. Além disso, você vai ver em breve que o manipulador de eventos se refere à propriedade Element herdado da classe
ViewRenderer, e que a propriedade também será null se args.NewElement é null.
Por essa razão, OnElementChanged destaca o manipulador de eventos quando args.NewElement vem null. Da mesma forma,
todos os recursos que você aloca para o representante deve ser liberado sempre que args.NewElement torna-se null.
A substituição do método OnElementPropertyChanged para mudanças nas quatro propriedades que StepSlider define:

namespace Xamarin.FormsBook.Platform.WinRT
{
public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
{

protected override void OnElementPropertyChanged(object sender,
PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);

if (args.PropertyName == StepSlider.MinimumProperty.PropertyName)
{
SetMinimum();
}
else if (args.PropertyName == StepSlider.MaximumProperty.PropertyName)
{
SetMaximum();
}
else if (args.PropertyName == StepSlider.StepsProperty.PropertyName)
{
SetSteps();
}
else if (args.PropertyName == StepSlider.ValueProperty.PropertyName)
{
SetValue();
}
}

}
}

Windows Slider define propriedades Minimum, Maximum e Value assim como o Xamarin.Forms Slider e o novo StepSlider. Mas
não define uma propriedade Steps. Em vez disso, ela define uma propriedade StepFrequency, que é o oposto de uma propriedade
Steps. Para reproduzir o exemplo anterior (Minimum igual a 5, Maximum igual a 10, e Steps igual a 20), você deve definir
StepFrequency a 0,25. A conversão é bastante simples:

namespace Xamarin.FormsBook.Platform.WinRT
{
public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
{

void SetMinimum()
{
Control.Minimum = Element.Minimum;
}
void SetMaximum()
{
Control.Maximum = Element.Maximum;
}
void SetSteps()
{

298
Control.StepFrequency = (Element.Maximum - Element.Minimum) / Element.Steps;
}
void SetValue()
{
Control.Value = Element.Value;
}

}
}

Finalmente, aqui está o manipulador ValueChanged para Windows Slider. Isto tem a responsabilidade de definir a propriedade Value
na StepSlider, que, em seguida, dispara seu próprio evento ValueChanged. No entenato, existe um método especial para definir um
valor a partir de um renderizador. Este método, chamado SetValueFromRenderer, é definido pela interface IElementController
e implementado pela classe Xamarin.Forms Element:

namespace Xamarin.FormsBook.Platform.WinRT
{
public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
{

void OnControlValueChanged(object sender, RangeBaseValueChangedEventArgs args)
{
((IElementController)Element).SetValueFromRenderer(StepSlider.ValueProperty,
args.NewValue);
}
}
}

O iOS UISlider tem propriedades MinValue, MaxValue e Value, e define um evento ValueChanged, mas não tem nada parecido
com uma propriedade Steps ou StepFrequency. Em vez disso, a classe iOS StepSliderRenderer em
Xamarin.FormsBook.Platform.iOS faz um ajuste manual para a propriedade Value antes de chamar SetValueFromRenderer do
manipulador de eventos ValueChanged:

using System;
using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.StepSlider),
typeof(Xamarin.FormsBook.Platform.iOS.StepSliderRenderer))]
namespace Xamarin.FormsBook.Platform.iOS
{
public class StepSliderRenderer : ViewRenderer<StepSlider, UISlider>
{
int steps;

protected override void OnElementChanged(ElementChangedEventArgs<StepSlider> args)


{
base.OnElementChanged(args);

if (Control == null)
{
SetNativeControl(new UISlider());
}
if (args.NewElement != null)
{
SetMinimum();
SetMaximum();
SetSteps();
SetValue();
Control.ValueChanged += OnUISliderValueChanged;

299
}
else
{
Control.ValueChanged -= OnUISliderValueChanged;
}
}

protected override void OnElementPropertyChanged(object sender,


PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);

if (args.PropertyName == StepSlider.MinimumProperty.PropertyName)
{
SetMinimum();
}
else if (args.PropertyName == StepSlider.MaximumProperty.PropertyName)
{
SetMaximum();
}
else if (args.PropertyName == StepSlider.StepsProperty.PropertyName)
{
SetSteps();
}
else if (args.PropertyName == StepSlider.ValueProperty.PropertyName)
{
SetValue();
}
}

void SetMinimum()
{
Control.MinValue = (float)Element.Minimum;
}
void SetMaximum()
{
Control.MaxValue = (float)Element.Maximum;
}
void SetSteps()
{
steps = Element.Steps;
}
void SetValue()
{
Control.Value = (float)Element.Value;
}
void OnUISliderValueChanged(object sender, EventArgs args)
{
double increment = (Element.Maximum - Element.Minimum) / Element.Steps;
double value = increment * Math.Round(Control.Value / increment);
((IElementController)Element).SetValueFromRenderer(StepSlider.ValueProperty, value);
}
}
}

Curiosamente, o widget SeekBar Android tem um equivalente à propriedade Steps mas não é equivalente à propriedades Minimum e
Maximum! Como isso é possível? O SeekBar realmente define uma propriedade integer chamada Max, e a propriedade Progress da
SeekBar é sempre um número inteiro que varia de 0 a Max. Assim, a propriedade Max realmente indica o número de steps a SeekBar
pode fazer, e é necessária uma conversão entre a propriedade Progress da SeekBar e a propriedade Value do StepSlider.
Esta conversão ocorre em dois lugares: O método SetValue converte a partir da propriedade Value do StepSlider à propriedade
Progress da SeekBar, e o método OnProgressChanged converte a partir da propriedade Progress da SeekBar para a propriedade
Value da StepSlider.

300
Além disso, o processador de eventos é um pouco diferente. O método SetOnSeekBarChangeListener aceita um argumento do tipo
IOnSeekBarChangeListener, que define três métodos que reportam alterações no Seekbar, incluindo o método
OnProgressChanged. O renderizador por si mesmo implementa essa interface.
Aqui está a classe StepSliderRenderer completa na biblioteca Xamarin.FormsBook.Platform.Android:

using System.ComponentModel;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.StepSlider),
typeof(Xamarin.FormsBook.Platform.Android.StepSliderRenderer))]

namespace Xamarin.FormsBook.Platform.Android
{
public class StepSliderRenderer : ViewRenderer<StepSlider, SeekBar>,
SeekBar.IOnSeekBarChangeListener
{
double minimum, maximum;

protected override void OnElementChanged(ElementChangedEventArgs<StepSlider> args)


{
base.OnElementChanged(args);

if (Control == null)
{
SetNativeControl(new SeekBar(Context));
}
if (args.NewElement != null)
{
SetMinimum();
SetMaximum();
SetSteps();
SetValue();
Control.SetOnSeekBarChangeListener(this);
}
else
{
Control.SetOnSeekBarChangeListener(null);
}
}

protected override void OnElementPropertyChanged(object sender,


PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == StepSlider.MinimumProperty.PropertyName)
{
SetMinimum();
}
else if (args.PropertyName == StepSlider.MaximumProperty.PropertyName)
{
SetMaximum();
}
else if (args.PropertyName == StepSlider.StepsProperty.PropertyName)
{
SetSteps();
}
else if (args.PropertyName == StepSlider.ValueProperty.PropertyName)
{
SetValue();
}
}
301
void SetMinimum()
{
minimum = Element.Minimum;
}
void SetMaximum()
{
maximum = Element.Maximum;
}
void SetSteps()
{
Control.Max = Element.Steps;
}
void SetValue()
{
double value = Element.Value;
Control.Progress = (int)((value - minimum) / (maximum - minimum) * Element.Steps);
}

// Implementation of SeekBar.IOnSeekBarChangeListener
public void OnProgressChanged(SeekBar seekBar, int progress, bool fromUser)
{
double value = minimum + (maximum - minimum) * Control.Progress / Control.Max;
((IElementController)Element).SetValueFromRenderer(StepSlider.ValueProperty, value);
}
public void OnStartTrackingTouch(SeekBar seekBar)
{
}
public void OnStopTrackingTouch(SeekBar seekBar)
{
}
}
}

A solução StepSliderDemo contém links para as bibliotecas Xamarin.FormsBook.Platform e correspondente a referências a essas
bibliotecas. O arquivo StepSliderDemo.xaml instancia cinco elementos StepSlider, com ligações de dados em três deles e um
manipulador de eventos explícito sobre os outros dois:

<ContentPage xmlns=”http://xamarin.com/schemas/2014/forms”
xmlns:x=”http://schemas.microsoft.com/winfx/2009/xaml”
xmlns:platform= "clr-namespace:Xamarin.FormsBook.Platform;assembly=Xamarin.FormsBook.Platform"
x:Class="StepSliderDemo.StepSliderDemoPage">
<StackLayout Padding="10, 0">
<StackLayout.Resources>
<ResourceDictionary>
<Style TargetType="ContentView">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</StackLayout.Resources>

<ContentView>
<StackLayout>
<platform:StepSlider x:Name="stepSlider1" />
<Label Text="{Binding Source={x:Reference stepSlider1}, Path=Value}" />
</StackLayout>
</ContentView>

<ContentView>
<StackLayout>

302
<platform:StepSlider x:Name="stepSlider2"
Minimum="10"
Maximum="15"
Steps="20"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="label2" />
</StackLayout>
</ContentView>

<ContentView>
<StackLayout>
<platform:StepSlider x:Name="stepSlider3"
Steps="10" />
<Label Text="{Binding Source={x:Reference stepSlider3}, Path=Value}" />
</StackLayout>
</ContentView>

<ContentView>
<StackLayout>
<platform:StepSlider x:Name="stepSlider4"
Minimum="0"
Maximum="1"
Steps="100"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="label4" />
</StackLayout>
</ContentView>

<ContentView>
<StackLayout>
<platform:StepSlider x:Name="stepSlider5"
Minimum="10"
Maximum="20"
Steps="2" />
<Label Text="{Binding Source={x:Reference stepSlider5}, Path=Value}" />
</StackLayout>
</ContentView>
</StackLayout>
</ContentPage>

O arquivo code-behind tem o manipulador de eventos ValueChanged:

public partial class StepSliderDemoPage : ContentPage


{
public StepSliderDemoPage()
{
InitializeComponent();
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
StepSlider stepSlider = (StepSlider)sender;
if (stepSlider == stepSlider2)
{
label2.Text = stepSlider2.Value.ToString();
}
else if (stepSlider == stepSlider4)
{
label4.Text = stepSlider4.Value.ToString();
}
}
}

303
Você verá que as funções StepSlider como um Xamarin.Forms Slider normal, exceto que os possíveis valores do StepSlider
estão agora sob controle programático:

O primeiro StepSlider tem propriedades Value em incrementos de 1, o segundo em incrementos de 0,25, a terceira em incrementos
de 10, a quarta em incrementos de 0,01, e o quinto em incrementos de 5 com apenas três configurações possíveis.
E agora você pode ver como Xamarin.Forms fornece as ferramentas que permitem levá-lo além do que à primeira vista parece ser.
Qualquer coisa que você pode definir em três plataformas pode se tornar algo útil em apenas uma plataforma universal. Com a linguagem
de programação C #, e o poder de Xamarin.Forms e renderizadores, você pode entrar não só na programação iOS, ou programação
Android, ou de programação do Windows, mas todas as três de uma vez com uma única etapa, e continuar com um passo para o futuro
do desenvolvimento móvel.

Sobre o autor
Charles Petzold trabalha para Xamarin na equipe de documentação. Seus primeiros anos como freelancer foram gastos em grande
parte, escrevendo livros para a Microsoft Press sobre programação Windows, Windows Phone e .NET, incluindo seis edições da
programação lendária do Windows, de 1988 a 2012.
Petzold é também o autor de dois livros exclusivos sobre os fundamentos matemáticos e filosóficos da computação digital, computing,
Code: The Hidden Language of Computer Hardware and Software (Microsoft Press, 1999) e The Annotated Turing: A Guided Tour
through Alan Turing's Historic Paper on Computability and the Turing Machine (Wiley, 2008). Ele mora em Nova York com sua esposa,
a escritora Deirdre Sinnott.

304

Das könnte Ihnen auch gefallen