[Windows 10] Adaptive Triggers

Introducción

En la plataforma Windows contámos con una gran variedad de
dispositivos, PCs, tabletas, Phablets, teléfonos. Contamos con una gran
cantidad diversa de tamaños de pantalla, resoluciones y otros factores
que como desarrolladores debemos tener en cuenta. El usuario es
exigente, debe serlo. Es vital poder disfrutar de la mejor experiencia posible en cada uno de los dispositivos, y ese, es uno de nuestros retos.

Adaptando la interfaz

Adaptar la interfaz de usuario a distintos tamaños, orientaciones y
otros cambios no es algo nuevo. Con la llegada de las Apps Modern UI en
Windows 8.0 debíamos gestionar la vista Narrow. Con las Apps Universales
en Windows 8.1 desarrollábamos Apps con Windows Phone y Windows en
mente. Ante este tipo de situaciones, hasta ahora hemos utilizado
diferentes técnicas. Las más habituales son:

  • Vistas XAML separadas por plataforma.
  • Diseño flexible. Utilizar posiciones y tamaños relativos para permitir escalar facilmente.
  • Utilizar diferentes estados visuales (Visual States) para gestionar vistas en diferentes dispositivos, pantallas e incluso orientaciones.

Adaptive Triggers

Con la llegada del SDK de Windows 10 Preview tenemos la posibilidad de crear Apps Universales con un único binario
que funcione en múltiples plataformas. Es un paso importante pero que
conlleva realizar una acción que sera comun, diferenciar entre las
diferentes plataformas donde correrá dicho binario para poder adaptar la
interfaz de usuario. Con ese objetivo llegan los Adaptive Triggers. Recibimos potentes novedades en los Visual States:

  • Podemos modificar propiedades sin necesidad de animaciones.
    Anteriormente, la síntaxis era mucho más verbosa y necesitábamos
    utilizar StoryBoards para cambiar cualquier propiedad por simple que
    fuese.
  • Contamos con los StateTrigger. Podemos lanzar Triggers
    cuando se aplica un estado visual sin necesidad de código adyacente en
    el code-behind. El concepto es muy similar a los Triggers que tenemos
    disponible en WPF y Silverlight y el objetivo es el mismo, realizar un
    cambio cuando una condición cambia.

Actualmente contamos con un tipo de StateTrigger por defecto, el Adaptive Trigger que cuenta con los siguientes tipos:

  • MinWindowWidth
  • MinWindowHeight

Ambos nos permiten detectar cambios dinámicos en el tamaño
de la pantalla de modo que se adapte el tamaño de la pantalla entre
pantallas pequeñas y grandes. El funcionamiento es similar a las media
queries en CSS por ejemplo. Se crearán diferentes estados estableciendo
unos altos y anchos mínimos, de modo que, cuando la pantalla es más
grande que el tamaño asignado se activará el estado visual.

NOTA: Los valores se especifican en pixeles.

Para probar las posibilidades de los Adaptive Triggers crearemos un nuevo proyecto UAP:

Nuestro objetivo en el ejemplo serán:

  • Aprender a utilizar AdaptiveTrigger de la forma más sencilla
    posible. Utilizaremos un texto que modificaremos segun ciertas
    condiciones del ancho de la ventana.
  • Crearemos un StateTrigger personalizado.

Añadimos en nuestra infertaz un control para mostrar texto:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
     <TextBlock x:Name="Info"
                HorizontalAlignment="Center"
        VerticalAlignment="Center" />
</Grid>

Sera el control que modificaremos mediante el VisualState:

<!-- Using AdaptiveTrigger -->
<VisualState x:Name="Small">
     <VisualState.StateTriggers>
          <AdaptiveTrigger MinWindowWidth="0" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Pantalla pequeña" />
          <Setter Target="Info.FontSize" Value="32" />
          <Setter Target="Info.Foreground" Value="Red" />
     </VisualState.Setters>
</VisualState>
<VisualState x:Name="Big">
     <VisualState.StateTriggers>
          <AdaptiveTrigger MinWindowWidth="600" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Pantalla grande" />
          <Setter Target="Info.FontSize" Value="48" />
          <Setter Target="Info.Foreground" Value="Blue" />
     </VisualState.Setters>
</VisualState>

Analicemos el trozo de XAML de la parte superior. Hemos definido dos estados visuales donde utilizamos la propiedad MinWindowWidth del StateTrigger AdaptiveTrigger
para aplicar una serie de cambios en las propiedades del control
TextBlock Info. Podemos encadenar tantos Setters como sean necesarios y
no es necesario la gestión por medio de StoryBoards lo que nos permite
definirlo todo de una manera menos verbosa.

Si lanzamos nuestra App y el tamaño de la ventana es superior a 600 px veremos algo como lo siguiente:

Mientras que si reducimos el tamaño:

Muy potente, ¿verdad?. De esta forma podemos hacer distintos cambios
en la organización de elementos, estilos y otros detalles para ajustar
la interfaz de usuario segun el tamaño de la ventana.

Creando Adaptive Triggers personalizados

Hasta ahora hemos aprendido que:

  • Tenemos potentes novedades en los VisualStates.
  • Como gestionar los elementos de la interfaz de usuario para adaptar la interfaz de usuario a cada dispotivo.
  • El uso de Adaptative Triggers para controlar el ancho y/o alto de la pantalla y adaptar la UI.

Los Triggers disponibles son lo suficientemente potentes como para
permitirnos un trabajo correcto adaptando la interfaz de usuario pero…
¿podemos llegar más lejos?, ¿y si no es suficiente?

Si analizamos la clase AdaptiveTrigger utilizada hasta ahora:

Vemos que hereda de una clase base llamada StateTriggerBase disponible dentro del namespace Windows.UI.Xaml.

Podemos crear nuestros propios StateTriggers.

Crearíamos nuevas clases heredando de la clase mencionada. Vamos a
crear un nuevo StateTrigger que nos indique en que plataforma estamos
(Windows o Windows Phone).

Creamos una clase que herede de StateTriggerBase:

public class PlatformStateTrigger : StateTriggerBase
{
 
}

Al igual que en AdaptiveTrigger contamos con dos propiedades, MinWindowWidth y MinWindowHeight que nos permiten lanzar el Trigger dependiendo de condiciones utilizando las mismas.

En nuestro StateTrigger crearemos una propiedad de dependencia llamada Platform que nos permitirá pasar el nombre de la plataforma, de modo que si estamos en dicha plataforma lanzar la condición:

public string Platform
{
     get { return (string)GetValue(PlatformProperty); }
     set { SetValue(PlatformProperty, value); }
}
 
public static readonly DependencyProperty PlatformProperty =
     DependencyProperty.Register("Platform", typeof(string), typeof(PlatformStateTrigger),
     new PropertyMetadata(true, OnPlatformPropertyChanged));
 
private static void OnPlatformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
 
}

La parte más importante del Trigger es determinar con exactitud la
plataforma en la que se ejecuta la App. Entre las opciones disponibles,
la más sencilla es utilizar ResourceContext:

var qualifiers = ResourceContext.GetForCurrentView().QualifierValues;
_deviceFamily = qualifiers.First(q => q.Key.Equals("DeviceFamily")).Value;

Accedemos al listado de QualifierValues, un IObservableMap con claves
y valor que contiene información como el idioma de la App, el
contraste, el tema utilizado o la familia del dispositivo. Utilizaremos
esta última para determinar si estamos en Desktop o Phone.

De modo que ante el cambio de nuestra propiedad:

private static void OnPlatformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
     var obj = (PlatformStateTrigger)d;
     var platform = (string)e.NewValue;
 
     if(_deviceFamily.Equals("Desktop"))
    obj.SetTriggerValue(platform == "Windows");
     else
    obj.SetTriggerValue(platform == "WindowsPhone");
}

Nuestro objetivo final es llamar al método SetTriggerValue(bool)
que es el encargado de determinar si la condicion del estado se cumple o
no. Dependiendo del dispositivo lanzamos la condición correcta.

Por último, queda utilizar nuestro StateTrigger personalizado. En nuestra interfaz declaramos antes que nada el namespace donde lo hemos definido:

xmlns:customStateTriggers="using:AdaptiveTriggers.CustomStateTriggers"

Podemos crear VisualStates utilizando PlatformStateTrigger:

<VisualState x:Name="WindowsPhone">
     <VisualState.StateTriggers>
          <customStateTriggers:PlatformStateTrigger Platform="WindowsPhone" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Windows Phone" />
          <Setter Target="Info.FontSize" Value="32" />
          <Setter Target="Info.Foreground" Value="Red" />
     </VisualState.Setters>
</VisualState>
<VisualState x:Name="Windows">
     <VisualState.StateTriggers>
          <customStateTriggers:PlatformStateTrigger Platform="Windows" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Windows" />
          <Setter Target="Info.FontSize" Value="48" />
          <Setter Target="Info.Foreground" Value="Blue" />
     </VisualState.Setters>
</VisualState>

En el caso de estar en un dispositivo Windows Phone se lanzaría el
segundo estado visual, en el de Windows el primero, adaptando la
interfaz de usuario (en nuestro ejemplo un simple TextBlock).

De igual forma podemos crear Adaptive Triggers personalizados para
gestionar aspectos como el tipo de dispositivo, el tipo de orientación,
haciendo verificaciones de condiciones como datos, internet, etc.

Podéis descargar el ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *