Edição especial do Windows 10 - 2015

Volume 30 - Número 11

Design da Interface do Usuário - Aplicativos Adaptáveis para Windows 10

Por Clint Rutkas | Windows 2015

Com a UWP (Plataforma Universal do Windows) no Windows 10, agora os aplicativos podem ser executados em diversas famílias de dispositivos e ser dimensionados automaticamente entre os diferentes tamanhos de tela e janela, com suporte dos controles da plataforma. Como essas famílias de dispositivos prestam suporte à interação do usuário com seus aplicativos e como seus aplicativos reagem e se adaptam ao dispositivo no qual estão sendo executados? Vamos explorar isso e também as ferramentas e os recursos que a Microsoft fornece na plataforma para que você não tenha que escrever e manter um código complexo para aplicativos em execução em diferentes tipos de dispositivos.

Vamos começar com uma exploração das técnicas de resposta que você pode usar para otimizar a interface do usuário para diferentes famílias de dispositivos. Em seguida, vamos explicar de forma mais aprofundada como seu aplicativo pode se adaptar aos recursos de dispositivos específicos.

Antes de nos aprofundarmos nos controles, nas APIs e no código, vamos explorar as famílias de dispositivos sobre as quais estamos falando. Simplificando: Uma família de dispositivos é um grupo de dispositivos com um fator forma específico, variando desde dispositivos da IoT, smartphones, tablets e computadores desktop até consoles de jogos Xbox, dispositivos Surface Hub de tela grande e até dispositivos de vestir. Os aplicativos funcionarão em todas essas famílias de dispositivos, mas, ao projetá-los, é importante considerar as famílias de dispositivos em que eles poderiam ser usados.

Embora existam muitas famílias de dispositivos, a UWP é projetada de forma que 85% de suas APIs sejam totalmente acessíveis a qualquer aplicativo, independente de onde ele é executado. Além disso, quando examinamos os 1.000 principais aplicativos, 96,2% de todas as APIs usadas são contabilizadas no conjunto base de APIs Universais do Windows. A maior parte da funcionalidade está presente e disponível como parte da UWP, com APIs especializadas em cada dispositivo disponíveis para adequar ainda mais seu aplicativo.

Bem-vindo de volta, Windows!

Uma das maiores alterações na forma como os aplicativos são usados no Windows é algo com o qual você já está muito familiarizado: aplicativos em execução em uma janela. O Windows 8 e o Windows 8.1 permitem que aplicativos sejam executados em tela inteira ou lado a lado, com até quatro aplicativos sendo executados simultaneamente. Por outro lado, o Windows 10 permite que o usuário organize, redimensione e posicione os aplicativos da maneira que desejar. A nova abordagem no Windows 10 dá ao usuário maior flexibilidade na interface do usuário, mas pode exigir algum trabalho de sua parte para garantir a otimização. As melhorias em XAML no Windows 10 introduzem várias maneiras de implementar técnicas de resposta em seu aplicativo, de modo que ele tenha ótima aparência em qualquer tamanho de tela ou janela. Vamos explorar três dessas abordagens.

VisualStateManager No Windows 10, a classe VisualStateManager foi ampliada com dois mecanismos para implementar o design de resposta em seus aplicativos baseados em XAML. As novas APIs VisualState.StateTriggers e VisualState.Setters permitem definir estados visuais que correspondem a determinadas condições. Os estados visuais podem mudar com base na altura e largura da janela do aplicativo, usando o AdaptiveTrigger interno como o StateTrigger do VisualState e definindo as propriedades MinWindowHeight e MinWindowWidth. Você também pode estender Windows.UI.Xaml.StateTriggerBase para criar seus próprios gatilhos, por exemplo, disparando de acordo com a família de dispositivos ou o tipo de entrada. Observe o código na Figura 1.

Figura 1 Criar Gatilhos de Estado Personalizados

<Page>
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup>
        <VisualState>
          <VisualState.StateTriggers>
          <!-- VisualState to be triggered when window
            width is >=720 effective pixels. -->
            <AdaptiveTrigger MinWindowWidth="720" />
          </VisualState.StateTriggers>
          <VisualState.Setters>
            <Setter Target="myPanel.Orientation"
                    Value="Horizontal" />
          </VisualState.Setters>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <StackPanel x:Name="myPanel" Orientation="Vertical">
      <TextBlock Text="This is a block of text. It is text block 1. "
                 Style="{ThemeResource BodyTextBlockStyle}"/>
      <TextBlock Text="This is a block of text. It is text block 2. "
                 Style="{ThemeResource BodyTextBlockStyle}"/>
      <TextBlock Text="This is a block of text. It is text block 3. "
                 Style="{ThemeResource BodyTextBlockStyle}"/>
    </StackPanel>
  </Grid>
</Page>

No exemplo na Figura 1, a página exibe três elementos TextBlock empilhados em cima uns dos outros em seu estado padrão. O VisualStateManager tem um AdaptiveTrigger definido com MinWindowWidth de 720, o que faz com que a orientação do StackPanel seja alterada para Horizontal quando a janela tiver, pelo menos, 720 pixels efetivos de largura. Isso permite que você utilize o espaço horizontal adicional na tela quando os usuários redimensionam a janela ou mudam do modo retrato para o modo paisagem em um telefone ou tablet. Lembre-se de que se você definir as propriedades de largura e altura, seu gatilho só disparará se o aplicativo atender a ambas condições simultaneamente. Você pode explorar o exemplo de gatilhos de Estado no GitHub (wndw.ms/XUneob) para exibir mais cenários usando gatilhos, incluindo vários gatilhos personalizados.

RelativePanel No exemplo da Figura 1, um StateTrigger é usado para alterar a propriedade Orientação de um StackPanel. Os diversos elementos de contêiner no XAML, combinados a StateTriggers, permitem manipular a interface do usuário de muitas maneiras, mas eles não oferecem uma maneira de criar facilmente uma interface de usuário complexa e responsiva em que os elementos sejam dispostos em relação uns aos outros. É aí que entra o novo RelativePanel. Como mostrado na Figura 2, você pode usar um RelativePanel para dispor os elementos expressando relações espaciais entre eles. Isso significa que você pode facilmente usar o RelativePanel junto com AdaptiveTriggers para criar uma interface de usuário responsiva em que você move elementos com base no espaço disponível na tela.

Figura 2 Expressar relacionamentos espaciais com o RelativePanel

<RelativePanel BorderBrush="Gray" BorderThickness="10">
  <Rectangle x:Name="RedRect" Fill="Red" MinHeight="100" MinWidth="100"/>
  <Rectangle x:Name="BlueRect" Fill="Blue" MinHeight="100" MinWidth="100"
             RelativePanel.RightOf="RedRect" />
  <!-- Width is not set on the green and yellow rectangles.
       It's determined by the RelativePanel properties. -->
  <Rectangle x:Name="GreenRect" Fill="Green"
             MinHeight="100" Margin="0,5,0,0"
             RelativePanel.Below="RedRect"
             RelativePanel.AlignLeftWith="RedRect"
             RelativePanel.AlignRightWith="BlueRect"/>
  <Rectangle Fill="Yellow" MinHeight="100"
             RelativePanel.Below="GreenRect"
             RelativePanel.AlignLeftWith="BlueRect"
             RelativePanel.AlignRightWithPanel="True"/>
</RelativePanel>

Como um lembrete, a sintaxe que você usa com propriedades anexadas envolve parênteses adicionais, como mostrado aqui:

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup>
    <VisualState>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="720" />
      </VisualState.StateTriggers>
      <VisualState.Setters>
        <Setter Target="GreenRect.(RelativePanel.RightOf)"
                Value="BlueRect" />
      </VisualState.Setters>
    </VisualState>

Você pode conferir cenários adicionais usando RelativePanel no exemplo de técnicas de Resposta no GitHub (wndw.ms/cbdL0q).

SplitView O tamanho da janela do aplicativo afeta mais do que o conteúdo exibido em páginas de aplicativos. Ele pode exigir que elementos de navegação respondam a alterações no tamanho da própria janela. O novo controle SplitView, introduzido no Windows 10, geralmente é usado para criar uma experiência de navegação de nível superior, que pode ser ajustada para se comportar de forma diferente de acordo com o tamanho da janela do aplicativo. Lembre-se de que, embora esse seja um dos casos de uso comuns para o SplitView, ele não é estritamente limitado a esse uso. O SplitView é dividido em duas áreas distintas: painel e conteúdo.

Algumas propriedades do controle podem ser usadas para manipular a apresentação. Primeiro, a propriedade DisplayMode especifica como o painel é renderizado em relação à área de Conteúdo, com quatro modos disponíveis: Overlay, Inline, CompactOverlay e CompactInline. A Figura 3 mostra exemplos dos modos Inline, Overlay e CompactInline renderizados em um aplicativo.

Elementos de Navegação DisplayMode
Figura 3 Elementos de Navegação DisplayMode

A propriedade PanePlacement exibe o Painel à esquerda (padrão) ou à direita da área de Conteúdo. A propriedade OpenPaneLength especifica a largura do painel quando ele está totalmente expandido (padrão de 320 pixels efetivos).

Observe que o controle SplitView não inclui um elemento de interface do usuário interno para que os usuários alternem o estado do painel, como o menu comum "hambúrguer" frequentemente encontrado em aplicativos móveis. Se desejar expor esse comportamento, você deverá definir esse elemento da interface do usuário no aplicativo e fornecer o código para alternar a propriedade IsPaneOpen do SplitView.

Quer explorar o conjunto completo de recursos que o Splitview oferece? Consulte o exemplo de menu de navegação do XAML no GitHub (wndw.ms/qAUVr9).

Trazendo o Botão Voltar

Se desenvolveu aplicativos para versões anteriores do Windows Phone, você pode estar acostumado ao fato de que todos os dispositivos têm um botão voltar de software ou hardware, permitindo aos usuários navegar de volta no aplicativo. No entanto, para o Windows 8 e 8.1, você tinha que criar sua própria interface do usuário para a navegação de volta. Para facilitar as coisas ao direcionar várias famílias de dispositivos em seu aplicativo do Windows 10, há uma maneira de garantir um mecanismo de navegação de volta consistente para todos os usuários. Isso pode ajudar a liberar algum espaço na interface do usuário em seus aplicativos futuramente.

Para habilitar um sistema de botão voltar para o aplicativo, mesmo em famílias de dispositivos que não têm um botão voltar de hardware ou software (como laptops e computadores desktop), use a propriedade AppViewBackButtonVisibility na classe SystemNavigationManager. Basta obter a classe SystemNavigationManager para a exibição atual e definir a visibilidade do botão voltar, conforme mostrado no seguinte código:

SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
  AppViewBackButtonVisibility.Visible;

A classe SystemNavigationManager também expõe um evento BackRequested, que é acionado quando o usuário invoca o botão, gesto ou comando de voz fornecido pelo sistema para a navegação de volta. Isso significa que você pode manipular esse evento único para executar consistentemente a navegação de volta em seu aplicativo em todas as famílias de dispositivos.

Benefícios do Continuum

Por último, mas não menos importante, gostaria de mencionar um de nossos itens favoritos pessoais: O Continuum no Windows 10. Com o Continuum, o Windows 10 ajusta sua experiência ao que você quer fazer e à maneira como você deseja fazê-lo. Se seu aplicativo está sendo executado em um computador conversível do Windows, por exemplo, a implementação do Continuum em seu aplicativo permite aos usuários usar o toque ou um mouse e teclado para otimizar a produtividade. Com a propriedade UserInteractionMode na classe UIViewSettings, com apenas uma linha de código, seu aplicativo pode determinar se o usuário está interagindo com a exibição usando o toque ou um mouse e teclado:

UIViewSettings.GetForCurrentView().UserInteractionMode;
// Returns UserInteractionMode.Mouse or UserInteractionMode.Touch

Depois de detectar o modo de interação, é possível otimizar a interface do usuário de seu aplicativo, fazendo coisas como aumentar ou diminuir margens, mostrar ou ocultar recursos complexos e muito mais. Confira o artigo da TechNet "Aplicativos do Windows 10: Aproveite o recurso Continuum para alterar a interface do usuário para usuários de mouse/teclado usando o StateTrigger personalizado", por Lee McPherson (wndw.ms/y3gB0J), que mostra como você pode combinar os novos StateTriggers e o UserInteractionMode para compilar seu próprio StateTrigger do Continuum personalizado.

Aplicativos Adaptáveis

Aplicativos que podem reagir a alterações no tamanho e na orientação da tela são úteis, mas para proporcionar uma funcionalidade convincente e de plataforma cruzada, a UWP fornece aos desenvolvedores dois tipos adicionais de comportamento adaptável:

  • A versão dos aplicativos adaptáveis reagem a versões diferentes da UWP por meio da detecção de APIs e recursos disponíveis. Por exemplo, você pode querer que seu aplicativo use algumas APIs mais recentes que estão presentes apenas em dispositivos que executam as versões mais recentes da UWP, enquanto continua a dar suporte aos clientes que ainda não fizeram a atualização.
  • Os aplicativos adaptáveis da plataforma reagem a recursos exclusivos disponíveis em diferentes famílias de dispositivos. Assim, um aplicativo pode ser compilado para ser executado em todas as famílias de dispositivos, mas você pode querer usar algumas APIs móveis específicas quando ele é executado em um dispositivo móvel como um smartphone.

Como observado anteriormente, com o Windows 10, a grande maioria das APIs da UWP é totalmente acessível a qualquer aplicativo, independentemente do dispositivo no qual ele estiver sendo executado. As APIs especializadas associadas a cada família de dispositivos permitem então que os desenvolvedores adaptem ainda mais seus aplicativos.

A ideia fundamental por trás dos aplicativos adaptáveis é que o aplicativo verifica a funcionalidade (ou o recurso) de que necessita e só o utiliza quando ele estiver disponível. No passado, um aplicativo poderia verificar a versão do sistema operacional e chamar APIs associadas a essa versão. Com o Windows 10, o aplicativo pode verificar em tempo de execução se uma classe, um método, uma propriedade, um evento ou um contrato de API tem suporte no sistema operacional atual. Em caso afirmativo, o aplicativo chama a API apropriada. A classe ApiInformation localizada no namespace Windows.Foundation.Metadata contém vários métodos estáticos (como IsApiContractPresent, IsEventPresent e IsMethodPresent) que são usados para consultar APIs. Veja um exemplo:

using Windows.Foundation.Metadata;
if(ApiInformation.IsTypePresent("Windows.Media.Playlists.Playlist"))
{
  await myAwesomePlaylist.SaveAsAsync( ... );
}

Esse código faz duas coisas. Ele faz um teste de tempo de execução para verificar a presença da classe Playlist e chama o método SaveAsAsync em uma instância da classe. Observe também a facilidade de verificação da presença de um tipo no sistema operacional atual usando a API IsTypePresent. No passado, essa verificação poderia ter exigido LoadLibrary, GetProcAddress, QueryInterface, Reflection ou o uso da palavra-chave "dynamic" e outras, dependendo da linguagem e da estrutura. Observe também a referência fortemente tipada ao fazer a chamada ao método. Ao usar Reflection ou "dynamic", você perde os diagnósticos em tempo de compilação estáticos que, por exemplo, o informariam se você digitasse incorretamente o nome do método.

Detectando com Contratos de API

Em seu cerne, um contrato de API é um conjunto de APIs. Um contrato de APIs hipotético poderia representar um conjunto de APIs contendo duas classes, cinco interfaces, uma estrutura, dois enums e assim por diante. Agrupamos logicamente tipos relacionados em um contrato de API. De muitas maneiras, um contrato de API representa um recurso: um conjunto de APIs relacionadas que, juntas, oferecem alguma funcionalidade específica. Todas as APIs de Tempo de Execução do Windows, do Windows 10 em diante, são membros de algum contrato de API. A documentação em msdn.com/dn706135 descreve a variedade de contratos de API disponíveis. Você verá que a maioria deles representa um conjunto de APIs funcionalmente relacionadas.

O uso de contratos de API também fornece a você, desenvolvedor, algumas garantias adicionais. A mais importante é que, quando uma plataforma implementa qualquer API em um contrato de API, deve implementar todas as APIs nesse contrato. Em outras palavras, um contrato de API é uma unidade atômica e testar o suporte desse contrato de API é equivalente a testar se todas as APIs do conjunto têm suporte. Seu aplicativo pode chamar qualquer API do contrato de API detectado sem ter que verificar cada API individualmente.

O maior e mais comumente usado contrato de API é Windows.Foundation.UniversalApiContract. Ele contém quase todas as APIs da Plataforma Universal do Windows. Para ver se o sistema operacional atual oferece suporte ao UniversalApiContract, você deve escrever o seguinte código:

if (ApiInformation.IsApiContractPresent(
  "Windows.Foundation.UniversalApiContract"), 1, 0)
{
  // All APIs in the UniversalApiContract version 1.0 are available for use
}

Agora, a única versão existente do UniversalApiContract é a versão 1.0, sendo assim, essa verificação é um pouco desnecessária. Porém, uma atualização futura do Windows 10 poderia introduzir APIs adicionais, produzindo uma versão 2.0 do UniversalApiContract que inclua as novas APIs universais. No futuro, um aplicativo que deseje ser executado em todos os dispositivos, mas também queira usar novas APIs da versão 2.0, poderia usar o seguinte código:

if (ApiInformation.IsApiContractPresent(
  "Windows.Foundation.UniversalApiContract"), 2, 0)
{
  // This device supports all APIs in UniversalApiContract version 2.0
}

Se seu aplicativo só precisar chamar um método único de versão 2.0, poderá verificar o método diretamente usando IsMethodPresent. Nesse caso, você pode usar a abordagem que considerar mais fácil.

Há outros contratos de API além de UniversalApiContract. A maioria representa um recurso ou conjunto de APIs não universalmente presentes em todas as plataformas do Windows 10 e, em vez disso, estão presentes em uma ou mais famílias de dispositivos específicos. Como mencionado anteriormente, você não precisa mais procurar determinado tipo de dispositivo e inferir o suporte a uma API. Basta verificar o conjunto de APIs que seu aplicativo quer usar.

Agora posso reescrever meu exemplo original para verificar a presença de Windows.Media.Playlists.PlaylistsContract, em vez de apenas verificar a presença da classe Playlist:

if(ApiInformation.IsApiContractPresent(
  "Windows.Media.Playlists.PlaylistsContract"), 1, 0)
{
  // Now I can use all Playlist APIs
}

Sempre que seu aplicativo precisar chamar uma API que não esteja presente em todas as famílias de dispositivos, você deverá adicionar uma referência ao SDK de Extensão apropriado que define a API. No Visual Studio 2015, vá para a caixa de diálogo Adicionar Referência e abra as guias Extensões. Você pode encontrar as três extensões mais importantes: Extensão Móvel, Extensão da Área de Trabalho e Extensão da IoT.

No entanto, seu aplicativo precisa apenas verificar a presença do contrato de API desejado e chamar as APIs apropriadas condicionalmente. Não é preciso se preocupar com o tipo de dispositivo. Agora, a questão é: Preciso chamar a API Playlist, mas ela não é uma API universalmente disponível. A documentação (bit.ly/1QkYqky) me indica em que contrato de API a classe está. Porém, quais SDKs de Extensão a definem?

Como se constata, a classe Playlist (atualmente) só está disponível em dispositivos da Área de Trabalho, não em dispositivos Móveis, no Xbox e em outras famílias de dispositivos. Portanto, você deve adicionar uma referência ao SDK de Extensão da Área de Trabalho antes que qualquer parte do código anterior seja compilada.

Lucian Wischik, membro da equipe do Visual Studio e autor ocasional do MSDN Magazine, criou uma ferramenta que pode ajudar. Ela analisa o código do aplicativo quando ele faz uma chamada para uma API específica da plataforma, conferindo se uma verificação de adaptabilidade foi feita. Se nenhuma verificação tiver sido feita, o analisador relatará um aviso e fornecerá uma "correção rápida" prática para inserir a verificação correta no código, bastando pressionar Ctrl+Ponto ou clicar na lâmpada. (Consulte bit.ly/1JdXTeV para obter mais detalhes.) O analisador também pode ser instalado via NuGet (bit.ly/1KU9ozj).

Vamos encerrar examinando exemplos mais completos de codificação adaptável para o Windows 10. Primeiro, veja um código que não é corretamente adaptável:

// This code will crash if called from IoT or Mobile
async private Task CreatePlaylist()
{
  StorageFolder storageFolder = KnownFolders.MusicLibrary;
  StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
  Windows.Media.Playlists.Playlist myAwesomePlaylist =
    new Windows.Media.Playlists.Playlist();
  myAwesomePlaylist.Files.Add(pureRockFile);
  // Code will crash here as this is a Desktop-only call
  await myAwesomePlaylist.SaveAsAsync(KnownFolders.MusicLibrary,
    "My Awesome Playlist", NameCollisionOption.ReplaceExisting);
}

Agora, vamos ver o mesmo código, adicionando uma linha que verifica se a API opcional tem suporte no dispositivo de destino antes de chamar o método. Isso impedirá falhas de tempo de execução. Observe que você provavelmente vai querer explorar ainda mais esse exemplo e não exibir a interface do usuário que chama o método CreatePlaylist se seu aplicativo detectar que a funcionalidade de lista de execução não está disponível no dispositivo:

async private Task CreatePlaylist()
{
  StorageFolder storageFolder = KnownFolders.MusicLibrary;
  StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
  Windows.Media.Playlists.Playlist myAwesomePlaylist =
    new Windows.Media.Playlists.Playlist();
  myAwesomePlaylist.Files.Add(pureRockFile);
  // Now I'm a safe call! Cache this value if this will be queried a lot
  if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
    "Windows.Media.Playlists.Playlist"))
  {
      await myAwesomePlaylist.SaveAsAsync(
        KnownFolders.MusicLibrary, "My Awesome Playlist",
        NameCollisionOption.ReplaceExisting);
  }
}

Finalmente, veja um exemplo de código que tenta acessar o botão da câmera dedicado encontrado em muitos dispositivos móveis:

// Note: Cache the value instead of querying it more than once
bool isHardwareButtonsAPIPresent =
  Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
  "Windows.Phone.UI.Input.HardwareButtons");
if (isHardwareButtonsAPIPresent)
{
  Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
    HardwareButtons_CameraPressed;
}

Observe a etapa de detecção. Se eu fizesse uma referência direta ao objeto HardwareButtons para o evento CameraPressed enquanto estivesse em um computador desktop, sem verificar se HardwareButtons está presente, meu aplicativo falharia.

Há muitas novidades referentes à interface do usuário responsiva e aos aplicativos adaptáveis no Windows 10. Quer saber mais? Confira a ótima apresentação sobre Contratos de API que Brent Rector fez na conferência Build 2015 (wndw.ms/IgNy0I) e não deixe de ver o vídeo informativo da Microsoft Virtual Academy sobre código adaptável (bit.ly/1OhZWGs) que abrange esse tópico com mais detalhes.


Clint Rutkasé um gerente de produto sênior do Windows que se concentra na plataforma de desenvolvimento. Ele trabalhou em Halo na 343 Industries e em Channel 9 na Microsoft e criou alguns projetos loucos usando a tecnologia do Windows, como uma pista de dança de discoteca controlada por computador, um Ford Mustang personalizado, robôs que atiram camisetas e muito mais.

Rajen Kishnatrabalha atualmente como gerente sênior de marketing de produto na equipe de Marketing de Desenvolvedores da Plataforma Windows para a Microsoft em Redmond, Washington. Anteriormente, ele trabalhou como consultor e evangelista técnico para a Microsoft nos Países Baixos.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Sam Jarawan, Harini Kannan e Brent Rector