Resumen del capítulo 26. Diseños personalizados

Nota:

Este libro se publicó en la primavera de 2016 y no se ha actualizado desde entonces. Gran parte del libro sigue siendo útil, pero algunos de los materiales están anticuados y algunos temas ya no son completamente correctos o completos.

Xamarin.Forms incluye varias clases derivadas de Layout<View>:

  • StackLayout,
  • Grid,
  • AbsoluteLayout y
  • RelativeLayout.

En este capítulo se describe cómo crear sus propias clases que se derivan de Layout<View>.

Información general sobre el diseño

No hay ningún sistema centralizado que controle el diseño de Xamarin.Forms. Cada elemento es responsable de determinar cuál debe ser su propio tamaño y cómo representarse en un área determinada.

Elementos primarios y secundarios

Cada elemento que tenga elementos secundarios es responsable de colocar esos elementos secundarios en su interior. Es el elemento primario el que determina en última instancia el tamaño de sus elementos secundarios en función del tamaño que tiene disponible y el tamaño que el elemento secundario desea tener.

Ajuste del tamaño y el posicionamiento

El diseño comienza en la parte superior del árbol visual con la página y luego continúa a través de todas las ramas. El método público más importante en el diseño es Layout definido por VisualElement. Cada elemento que tiene un rango superior respecto a otros elementos llama a Layout para cada uno de sus elementos secundarios para darle a estos un tamaño y una posición con relación a sí mismo en forma de un valor Rectangle. Estas llamadas a Layout se propagan a través del árbol visual.

Se requiere una llamada a Layout para que un elemento aparezca en la pantalla, y hace que se establezcan las siguientes propiedades de solo lectura. Son coherentes con los Rectangle pasados al método:

  • Bounds de tipo Rectangle
  • X de tipo double
  • Y de tipo double
  • Width de tipo double
  • Height de tipo double

Antes de la llamada a Layout, Height y Width tienen valores ficticios de –1.

Una llamada a Layout también desencadena llamadas a los siguientes métodos protegidos:

Por último, se desencadena el siguiente evento:

El método OnSizeAllocated se reemplaza por Page y Layout, que son las dos únicas clases de Xamarin.Forms que pueden tener elementos secundarios. El método reemplazado llama a:

LayoutChildren a continuación, llama a Layout para cada uno de los elementos secundarios del elemento. Si al menos un elemento secundario tiene un nuevo valor de Bounds, se desencadena el siguiente evento:

Restricciones y solicitudes de tamaño

Para que LayoutChildren llame de forma inteligente a Layout en todos sus elementos secundarios, debe conocer un tamaño preferido o deseado para los elementos secundarios. Por lo tanto, las llamadas a Layout para cada uno de los elementos secundarios están precedidas por llamadas a

Una vez publicado el libro, el método de GetSizeRequest ha quedado en desuso y se ha reemplazado por

El método Measure aloja la propiedad Margin e incluye un argumento de tipo MeasureFlag, que tiene dos miembros:

Para muchos elementos, GetSizeRequest o Measure obtienen el tamaño nativo del elemento de su representador. Ambos métodos tienen parámetros para las restricciones de ancho y alto. Por ejemplo, Label utilizará la restricción de ancho para determinar cómo se ajustan varias líneas de texto.

Tanto GetSizeRequest como Measure devuelven un valor de tipo SizeRequest, que tiene dos propiedades:

Con frecuencia, estos dos valores son el mismo y, por lo general, el valor Minimum se puede pasar por alto.

VisualElement también define un método protegido similar a GetSizeRequest al que se llama desde GetSizeRequest:

Ese método ahora está en desuso y se reemplaza por:

Cada clase que se deriva de Layout o Layout<T> debe invalidar OnSizeRequest o OnMeasure. Aquí es donde una clase de diseño determina su propio tamaño, que generalmente se basa en el tamaño de sus elementos secundarios, que obtiene llamando a GetSizeRequest o Measure en los elementos secundarios. Antes y después de llamar a OnSizeRequest o OnMeasure, GetSizeRequest o Measure realiza ajustes en función de las siguientes propiedades:

Restricciones infinitas

Los argumentos de restricción pasados a GetSizeRequest (o Measure) y OnSizeRequest (o OnMeasure) pueden ser infinitos (es decir, valores de Double.PositiveInfinity). Sin embargo, los SizeRequest devueltos de estos métodos no pueden contener dimensiones infinitas.

Las restricciones infinitas indican que el tamaño solicitado debe reflejar el tamaño natural del elemento. Un StackLayout vertical llama a GetSizeRequest (o Measure) en sus elementos secundarios con una restricción de alto infinito. Un diseño de pila horizontal llama a GetSizeRequest (o Measure) en sus elementos secundarios con una restricción de ancho infinito. Un AbsoluteLayout llama a GetSizeRequest (o Measure) en sus elementos secundarios con restricciones de ancho y alto infinitos.

Inspección dentro del proceso

El elemento ExploreChildSize muestra la información de la solicitud de restricción y tamaño para un diseño sencillo.

Derivación de Diseño<Vista>

Una clase de diseño personalizado se deriva de Layout<View>. Tiene dos responsabilidades:

  • Invalidar OnMeasure para llamar a Measure en todos los elementos secundarios del diseño. Devolver un tamaño solicitado para el propio diseño.
  • Invalidar LayoutChildren para llamar a Layout en todos los elementos secundarios del diseño.

Los bucles for o foreach de estas invalidaciones deberían omitir cualquier elemento secundario cuya propiedad IsVisible esté establecida en false.

No se garantiza una llamada a OnMeasure. No se llamará a OnMeasure si el elemento primario del diseño controla el tamaño del diseño (por ejemplo, un diseño que llene una página). Por esta razón, LayoutChildren no puede basarse en los tamaños secundarios obtenidos durante la llamada a OnMeasure. Con mucha frecuencia, LayoutChildren debe llamar a Measure en los elementos secundarios del diseño, o puede implementar algún tipo de lógica de almacenamiento en caché del tamaño (que se tratará más adelante).

Un ejemplo sencillo

El ejemplo VerticalStackDemo contiene una clase VerticalStack simplificada y una demostración de su uso.

Posicionamiento vertical y horizontal simplificado

Uno de los trabajos que VerticalStack debe realizar se produce durante la invalidación de LayoutChildren. El método usa la propiedad HorizontalOptions del elemento secundario para determinar cómo colocar el elemento secundario dentro de su espacio en VerticalStack. En su lugar, puede llamar al método estático Layout.LayoutChildIntoBoundingRect. Este método llama a Measure en el elemento secundario y utiliza sus propiedades HorizontalOptions y VerticalOptions para colocar el elemento secundario dentro del rectángulo especificado.

Invalidación

A menudo, un cambio en la propiedad de un elemento afecta al modo en que ese elemento aparece en el diseño. El diseño se debe invalidar para desencadenar un nuevo diseño.

VisualElement define un método protegido InvalidateMeasure, al que se suele llamar mediante el controlador modificado por la propiedad de cualquier propiedad enlazable cuyo cambio afecte al tamaño del elemento. El método InvalidateMeasure activa un evento MeasureInvalidated.

La clase Layout define un método protegido similar denominado InvalidateLayout, al que un derivado de Layout debe llamar para cualquier cambio que afecte al modo en que coloca y ajusta el tamaño de sus elementos secundarios.

Algunas reglas para la codificación de diseños

  1. Las propiedades definidas por derivados de Layout<T> deben estar respaldadas por propiedades enlazables y los controladores modificados por propiedad deben llamar a InvalidateLayout.

  2. Un derivado de Layout<T> que define las propiedades enlazables asociadas debe reemplazar a OnAdded para agregar un controlador modificado por propiedad a sus elementos secundarios y OnRemoved para quitar ese controlador. El controlador debe comprobar si hay cambios en estas propiedades enlazables asociadas y responder llamando a InvalidateLayout.

  3. Un derivado de Layout<T> que implementa una memoria caché de tamaños secundarios debe reemplazar a InvalidateLayout y OnChildMeasureInvalidated y borrar la memoria caché cuando se llama a estos métodos.

Un diseño con propiedades

En la clase WrapLayout de Xamarin.FormsBook.Toolkit se supone que todos sus elementos secundarios tienen el mismo tamaño, y se ajustan los elementos secundarios de una fila (o columna) a la siguiente. Define una propiedad Orientation como StackLayout, y las propiedades ColumnSpacing y RowSpacing como Grid, y almacena en caché los tamaños secundarios.

En el ejemplo de PhotoWrap se coloca un WrapLayout en un ScrollView para mostrar las fotos en existencias.

No se permiten dimensiones sin restricciones.

El valor UniformGridLayout de la biblioteca Xamarin.FormsBook.Toolkit está diseñada para mostrar todos sus elementos secundarios en su interior. Por lo tanto, no puede tratar con dimensiones sin restricciones y genera una excepción si se encuentra una.

En el ejemplo PhotoGrid se muestra UniformGridLayout:

Captura de pantalla triple de la cuadrícula fotográfica

Elementos secundarios superpuestos

Un derivado de Layout<T> puede superponerse a sus elementos secundarios. Sin embargo, los elementos secundarios se representan en su orden en la colección de Children y no en el orden en que se llama a los métodos de Layout.

La clase Layout define dos métodos que le permiten desplazar un elemento secundario dentro de la colección:

  • LowerChild para desplazar un elemento secundario al principio de la colección
  • RaiseChild para desplazar un elemento secundario al final de la colección

En el caso de los elementos secundarios superpuestos, los elementos secundarios al final de la colección aparecen visualmente encima de los elementos secundarios al principio de la colección.

La clase OverlapLayout de la biblioteca Xamarin.FormsBook.Toolkit define una propiedad asociada para indicar el orden de representación y, por tanto, permitir que uno de sus elementos secundarios se muestre encima de los demás. En el ejemplo StudentCardFile se muestra lo siguiente:

Captura de pantalla triple de la cuadrícula de archivos de la tarjeta de alumnos

Más propiedades enlazables asociadas

La clase CartesianLayout de la biblioteca Xamarin.FormsBook.Toolkit define las propiedades enlazables asociadas para especificar dos valores de Point y un valor de grosor y manipula los elementos de BoxView para que se parezca a las líneas.

El ejemplo UnitCube se usa para dibujar un cubo 3D.

Layout y LayoutTo

Un derivado de Layout<T> puede llamar a LayoutTo en lugar de Layout para animar el diseño. La clase AnimatedCartesianLayout se ocupa de ello, y el ejemplo AnimatedUnitCube lo demuestra.