Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 2154

Contents

Introducción
¿Qué es Xamarin.Forms?
Instalación
Instalación de Xamarin en Windows
Instalación de versiones preliminares de Xamarin (Windows)
Desinstalación de Xamarin desde Visual Studio
Instalación de Visual Studio para Mac
Instalación de versiones preliminares de Xamarin (Mac)
Desinstalación de Visual Studio para Mac
Instrucciones de configuración del firewall para Xamarin
Plataformas compatibles
Primera aplicación
Guías de inicio rápido
Archivo > Nuevo
Varias páginas
Base de datos
Aplicación de estilos
Profundización
Tutoriales
Diseño de pila
Etiqueta
Botón
Entrada de texto
Editor de texto
Imágenes
Diseño de cuadrícula
Listas
Elementos emergentes
Ciclo de vida de la aplicación
Base de datos local
servicios Web
Obtener información sobre Xamarin
Desarrolladores de .NET
Desarrolladores de Java
Desarrolladores de Objective-C
Azure
Guía de desarrollo
XAML
Información general
XAML Basics (Conceptos básicos de XAML)
Parte 1. Introducción a XAML
Parte 2. Sintaxis XAML esencial
Parte 3. Extensiones de marcado XAML
Parte 4. Conceptos básicos del enlace de datos
Parte 5. Enlaces de datos a MVVM
Controles de XAML
Compilación de XAML
Extensiones de marcado XAML
Consumo de extensiones de marcado XAML
Creación de extensiones de marcado XAML
Tooling
Recarga activa de XAML
Cuadro de herramientas de XAML
Controlador de vista previa de XAML
Datos en tiempo de diseño
Controles personalizados
Espacios de nombres
Espacios de nombres XAML
Esquemas de espacio de nombres personalizado XAML
Prefijos recomendados de espacio de nombres de XAML
Funcionalidades adicionales
Propiedades enlazables
Propiedades asociadas
Diccionarios de recursos
Pasar argumentos
Genéricos
Modificadores de campo
Carga de XAML en tiempo de ejecución
XAML de Xamarin.Forms en Q&A
Principios de la aplicación
Información general
Accesibilidad
Propiedades de automatización
Accesibilidad del teclado
Clase de aplicación
Ciclo de vida de la aplicación
Indexación de la aplicación y vinculación en profundidad
comportamientos
Introducción
Comportamientos asociados
Comportamientos de Xamarin.Forms
EffectBehavior reutilizable
Representadores personalizados
Introducción
Clases base y controles nativos del representador
Personalización de una entrada
Personalización de una página de contenido
Personalización de un anclado de mapa
Personalización de una ListView
Personalización de ViewCell
Personalización de WebView
Implementación de una vista
Implementación de un reproductor de vídeo
Creación de reproductores de vídeo de plataforma
Reproducción de un vídeo web
Enlazar orígenes de vídeo con el reproductor
Carga de vídeos de recursos de aplicación
Acceder a la biblioteca de vídeos del dispositivo
Controles de transporte de vídeo personalizados
Barra de posición de vídeo personalizada
Enlace de datos
Enlaces básicos
Modo de enlace
Formato de cadena
Enlace de ruta de acceso
Enlace de convertidores de valores
Enlaces relativos
Conmutación por recuperación de enlaces
Enlaces múltiples
Interfaz de comandos
Enlaces compilados
DependencyService
Introducción
Registro y resolución
Selección de biblioteca de fotos
Doble pantalla
Primeros pasos
Patrones de doble pantalla
Diseño TwoPaneView
Clase auxiliar DualScreenInfo
Desencadenadores de doble pantalla
Efectos
Introducción
Creación de efectos
Pasar parámetros
Parámetros como propiedades CLR
Parámetros como propiedades asociadas
Invocación de eventos
RoundEffect reutilizable
Gestos
Pulsar
Reducir
Movimiento panorámico
Deslizar rápidamente
Arrastrar y colocar
Notificaciones locales
Localización
Localización de cadenas e imágenes
Localización de derecha a izquierda
MessagingCenter
Navegación
Navegación jerárquica
TabbedPage
CarouselPage
MasterDetailPage
Páginas modales
Shell
Introducción
Creación de una aplicación Shell
Flyout
Tabulaciones
Configuración de la página
Navegación
Buscar
Ciclo de vida
Representadores personalizados
Plantillas
Información general
Plantillas de control
Plantillas de datos
Introducción
Creación de plantilla de datos
Selección de plantilla de datos
Desencadenadores
Interfaz de usuario
Información general
Marcado de C#
Referencia de controles
Información general
Páginas
Diseños
Vistas
Celdas
Propiedades, métodos y eventos comunes
Controles de terceros
Presentación de datos
BoxView
Expander
Imagen
Etiqueta
Asignación
Información general
Inicialización y configuración
Control de mapa
Posición y distancia
Chinchetas
Polígonos, polilíneas y círculos
Codificación geográfica
Inicio de la aplicación de mapa nativa
MediaElement
Formas
Información general
Ellipse
Reglas de relleno
Geometrías
Línea
Rutas de acceso
Ruta de acceso
Sintaxis de marcado de ruta de acceso
Transformaciones de ruta de acceso
Polígono
Polilínea
Rectángulo
WebView
Inicio de comandos
Botón
ImageButton
RadioButton
RefreshView
Barra de búsqueda
SwipeView
Establecimiento de valores
CheckBox
DatePicker
Slider
Control de incremento
Modificador
TimePicker
Edición de texto
Editor
Entrada
Indicación de actividad
ActivityIndicator
ProgressBar
Visualización de colecciones
CarouselView
Introducción
Datos
Diseño
Interacción
EmptyView
Desplazarse
CollectionView
Introducción
Datos
Diseño
Selección
EmptyView
Desplazarse
Agrupar
IndicatorView
ListView
Orígenes de datos
Apariencia de etiqueta
Apariencia de línea
Interactividad
Rendimiento
Selector
Configuración de la propiedad ItemsSource de un selector
Adición de datos a la colección de elementos de un selector
TableView
Controles adicionales
MenuItem
ToolbarItem
Conceptos
Animación
Animaciones simples
Funciones de aceleración
Animaciones personalizadas
Pinceles
Introducción
Colores sólidos
Degradados
Introducción
Degradados lineales
Degradados radiales
Colores
DataPages
Primeros pasos
Controls Reference (Referencia de controles)
Visualización de elementos emergentes
Fuentes
Gráficos con SkiaSharp
Pantalla de presentación
Estilos
Aplicación de estilos para aplicaciones Xamarin.Forms con estilos XAML
Introducción
Estilos explícitos
Estilos implícitos
Estilos globales
Herencia de estilo
Estilos dinámicos
Estilos de dispositivo
Clases de estilo
Aplicación de estilos para aplicaciones Xamarin.Forms con hojas de estilo (CSS)
Temas
Aplicación de un tema a una aplicación
Responder a cambios de tema del sistema
Elemento visual
Objeto visual de material
Creación de un representador visual
Administrador de estado visual
Diseños
Información general
Elección de un diseño
Diseños principales
AbsoluteLayout
FlexLayout
Cuadrícula
RelativeLayout
StackLayout
Diseños adicionales
ContentView
Fotograma
ScrollView
Conceptos
Diseños enlazables
Diseños personalizados
Orientación del dispositivo
LayoutOptions
Compresión de diseño
Margen y relleno
Tableta y escritorio
Características de la plataforma
Información general
Android
Información general
Migración de AndroidX
AppCompat y Material Design
Relleno y sombras de los botones
Opciones de entrada del Editor de métodos de entrada
Sombras paralelas ImageButton
Desplazamiento rápido ListView
Altura de la barra NavigationPage
Eventos del ciclo de vida de la página
Modo de entrada de teclado en pantalla
Modo de transición de deslizamiento SwipeView
Deslizamiento de página TabbedPage
Animaciones de transición de página TabbedPage
Color y colocación de la barra de herramientas TabbedPage
Acciones de contexto de ViewCell
Elevación VisualElement
Modo de color heredado VisualElement
Contenido mixto WebView
Zoom de WebView
iOS
Información general
Escalado de accesibilidad para tamaños de fuente con nombre
Color de fondo de celda
Selección de elementos de DatePicker
Color del cursor de entrada
Tamaño de la fuente de entrada
Formato
Estilo de presentación de la página modal
Títulos de página grandes
Estilo de encabezado de grupo de ListView
Animaciones de la fila de ListView
Estilo del separador ListView
Actualizaciones de control del subproceso principal
Sombra MasterDetailPage
Separador de barra NavigationPage
Modo de color del texto de barra NavigationPage
Translucidez de barra NavigationPage
Visibilidad del indicador de página principal
Visibilidad de barra de estado de página
Selección de elementos de selector
Guía de diseño de área segura
Toques de contenido ScrollView
Estilo SearchBar
Reconocimiento de gestos simultáneos de desplazamiento lateral
Control deslizante con el uso de toques
Modo de transición de deslizamiento SwipeView
Barra de pestañas translúcida en TabbedPage
Selección de elementos de TimePicker
Desenfoque VisualElement
Sombras paralelas VisualElement
Modo de color heredado VisualElement
Primer respondedor VisualElement
Windows
Información general
Directorio de imagen predeterminado
Orden de lectura InputView
Modo de selección ListView
Barra de navegación MasterDetailPage
Colocación de la barra de herramientas de página
Configuración de la plataforma
Dirección de arrastre de RefreshView
Spell Check SearchBar
Iconos TabbedPage
Claves de acceso VisualElement
Modo de color heredado VisualElement
Alertas de JavaScript en WebView
Crear características específicas de las plataformas
Clase de dispositivo
Formularios nativos
Vistas nativas
Vistas nativas en XAML
Vistas nativas en C#
Inicio de sesión con Apple
Configuración para iOS
Configuración para otras plataformas
Uso del Inicio de sesión con Apple
Otras plataformas
GTK#
Mac
Tizen
WPF
Xamarin.Essentials
Primeros pasos
Compatibilidad de plataforma y características
Acelerómetro
Acciones de aplicación
Información de la aplicación:
Tema de la aplicación
Barómetro
Batería
Portapapeles
Convertidores de colores
Brújula
Conectividad
Contactos
Detección de vibraciones
Información de pantalla del dispositivo
Información del dispositivo
Correo electrónico
Selector de archivos
Asistentes del sistema de archivos
Linterna
Codificación geográfica
Geolocalización
Giroscopio
Comentarios hápticos
Selector
Magnetómetro
Subproceso principal
Asignaciones
Selector de archivos multimedia
Apertura del explorador
Sensor de orientación
Permisos
Marcador telefónico
Extensiones de plataforma (Size, Rect, Point)
Preferencias
Instantánea
Almacenamiento seguro
Compartir
SMS
Texto a voz
Convertidores de unidades
Seguimiento de versiones
Vibración
Autenticador web
Notas de la versión de Xamarin.Essentials
Solución de problemas
Xamarin.Essentials en Q&A
Datos y Azure Cloud Services
Información general
Almacenamiento de datos local
Información general
Control de archivos
Bases de datos locales
Servicios de Azure
Introducción a servicios de Azure
Base de datos de documentos de Azure Cosmos DB
Centros de notificaciones de Azure
Almacenamiento de Azure
Azure Search
Comprobación de
Azure Cognitive Services
Información general de Cognitive Services
Introducción
Reconocimiento de voz
Corrector ortográfico
Traducción de texto
Reconocimiento de emociones percibidas
Servicios Web
Información general de servicios web
Introducción
ASMX
WCF
REST
Autenticación
Información general sobre la autenticación
REST
Azure Active Directory B2C
Autenticación en Azure Cosmos DB
Implementación y pruebas
Información general
Información general
Mejorar el rendimiento
Reinicio rápido
Pruebas automatizadas con Visual Studio App Center
Publicación de aplicaciones de iOS
Publicación de aplicaciones de Android
Publicación de aplicaciones para UWP
Publicación de aplicaciones de Mac
Conceptos avanzados y funcionamiento interno
Información general
Jerarquía de clases de controles
Resolución de dependencias
Marcas experimentales
Representadores rápidos
SourceLink
Solución de problemas
Preguntas más frecuentes
¿Se puede actualizar la plantilla predeterminada de Xamarin.Forms en un paquete
NuGet más reciente?
¿Por qué no funciona el diseñador XAML de Visual Studio para los archivos XAML
de Xamarin.Forms?
Error de compilación de Android: error inesperado en la tarea LinkAssemblies
¿Por qué mi proyecto de Xamarin.Forms.Maps para Android produce el siguiente
error: COMPILETODALVIK : UNEXPECTED TOP-LEVEL ERROR?
Xamarin.Forms en Q&A
Notas de la versión
Muestras
Libro Creating Mobile Apps with Xamarin.Forms
Libro electrónico sobre patrones de aplicación empresarial
Gráficos de SkiaSharp en Xamarin.Forms
¿Qué es Xamarin.Forms?
18/12/2020 • 5 minutes to read • Edit Online

Xamarin.Forms es un marco de interfaz de usuario de código abierto. Xamarin.Forms permite a los desarrolladores
compilar aplicaciones en Xamarin.Android, Xamarin.iOS y Windows desde un único código base compartido.
Xamarin.Forms permite a los desarrolladores crear interfaces de usuario en XAML con código subyacente en C#.
Estas interfaces se representan como controles nativos con mejor rendimiento en cada plataforma.

A quién va destinado Xamarin.Forms


Xamarin.Forms es para desarrolladores con los siguientes objetivos:
Compartir el diseño de la interfaz de usuario entre plataformas.
Compartir código, pruebas y lógica de negocios entre plataformas.
Escribir aplicaciones multiplataforma en C# con Visual Studio.

Cómo funciona Xamarin.Forms


Diagrama de la arquitectura de

Xamarin.Forms proporciona una API coherente para crear elementos de interfaz de usuario entre plataformas. Esta
API se puede implementar en XAML o C#, y admite el enlace de datos para patrones como Model-View-ViewModel
(MVVM).
En tiempo de ejecución, Xamarin.Forms usa los representadores de plataforma para convertir los elementos de la
interfaz de usuario multiplataforma en controles nativos en Xamarin.Android, Xamarin.iOS y UWP. Esto permite a
los desarrolladores obtener una apariencia y un funcionamiento nativos, además de las ventajas del uso
compartido de código entre plataformas.
Las aplicaciones Xamarin.Forms suelen estar compuestas de una biblioteca compartida de .NET Standard y
proyectos de plataforma individuales. La biblioteca compartida contiene las vistas XAML o C#, y cualquier lógica de
negocios, como servicios, modelos u otro código. Los proyectos de plataforma contienen cualquier lógica o
paquete específico de la plataforma que la aplicación necesite.
Xamarin.Forms usa la plataforma Xamarin para ejecutar aplicaciones de .NET de forma nativa en todas las
plataformas. Para obtener más información sobre la plataforma Xamarin, vea ¿Qué es Xamarin?.

Funcionalidad adicional
Xamarin.Forms tiene un amplio ecosistema de bibliotecas que agregan diversas funciones a las aplicaciones. En
esta sección se describen algunas de estas funciones adicionales.
Xamarin.Essentials
Xamarin.Essentials es una biblioteca que proporciona API multiplataforma para características de dispositivos
nativos. Como el propio Xamarin, Xamarin.Essentials es una abstracción que simplifica el proceso de acceso a
utilidades nativas. Algunos ejemplos de las utilidades que proporciona Xamarin.Essentials son los siguientes:
Información del dispositivo
Sistema de archivos
Acelerómetro
Marcador telefónico
Texto a voz
Bloqueo de pantalla
Para obtener más información, vea Xamarin.Essentials.
Shell
Xamarin.Forms Shell reduce la complejidad del desarrollo de aplicaciones móviles al proporcionar las
características fundamentales que la mayoría de las aplicaciones necesita. Algunos ejemplos de características
proporcionadas por Shell son los siguientes:
Experiencia de navegación común
Esquema de navegación basado en URI
Controlador de búsqueda integrado
Para obtener más información, consulte Xamarin.Forms Shell.
Características específicas de las plataformas
Xamarin.Forms proporciona una API común que representa controles nativos entre plataformas, pero una
plataforma específica puede tener funciones que no existan en otras. Por ejemplo, la plataforma Android tiene
funcionalidad nativa para el desplazamiento rápido en un objeto ListView pero iOS no. Las características
específicas de la plataforma de Xamarin.Forms permiten usar funciones que solo están disponibles en una
plataforma concreta sin necesidad de crear representadores ni efectos personalizados.
Xamarin.Forms incluye soluciones prediseñadas para distintas funcionalidades específicas de la plataforma. Para
obtener más información, consulte:
Características específicas de la plataforma de Xamarin.Forms
Características específicas de la plataforma Android
Características específicas de la plataforma iOS
Características específicas de la plataforma Windows
Objeto visual de material
El objeto visual de material de Xamarin.Forms se usa para aplicar reglas de diseño de materiales a aplicaciones de
Xamarin.Forms. El objeto visual de material de Xamarin.Forms usa la propiedad Visual para aplicar de forma
selectiva representadores personalizados a la interfaz de usuario, lo que da lugar a una aplicación con una
apariencia coherente entre iOS y Android.
Para obtener más información, consulte Objeto visual de material de Xamarin.Forms.

Vínculos relacionados
Introducción a Xamarin.Forms
Xamarin.Essentials
Xamarin.Forms Shell
Objeto visual de material de Xamarin.Forms
Instalación de Xamarin
18/12/2020 • 4 minutes to read • Edit Online

Cómo configurar Visual Studio y Xamarin para comenzar a crear aplicaciones móviles con .NET.

Instalación de Xamarin en Windows

Instrucciones paso a paso


Xamarin se puede instalar como parte de una nueva instalación de Visual Studio 2019, con los pasos siguientes:
1. Descargue Visual Studio 2019 Community, Visual Studio Professional o Visual Studio Enterprise desde la
página de Visual Studio (en la parte inferior se encuentran los vínculos de descarga).
2. Haga doble clic en el paquete descargado para iniciar la instalación.
3. Seleccione la carga de trabajo Desarrollo para dispositivos móviles con .NET en la pantalla de
instalación:

4. Cuando esté listo para comenzar la instalación de Visual Studio 2019, haga clic en el botón Instalar de la
esquina inferior derecha:

Use las barras de progreso para supervisar la instalación:


5. Cuando haya finalizado la instalación de Visual Studio 2019, haga clic en el botón Iniciar para iniciar Visual
Studio:

Adición de Xamarin a Visual Studio 2019


Si Visual Studio 2019 ya está instalado, para agregar Xamarin vuelva a ejecutar el instalador de Visual Studio 2019
para modificar las cargas de trabajo (vea Modificación de Visual Studio para más información). Después, siga los
pasos indicados anteriormente para instalar Xamarin.
Para más información sobre la descarga e instalación de Visual Studio 2019, vea Instalación de Visual Studio 2019.

Instalación de Xamarin en Windows

Instrucciones paso a paso


Xamarin se puede instalar como parte de una nueva instalación de Visual Studio 2017, con los pasos siguientes:
1. Descargue Visual Studio 2017 Community, Visual Studio Professional o Visual Studio Enterprise desde la
página de Visual Studio (en la parte inferior se encuentran los vínculos de descarga).
2. Haga doble clic en el paquete descargado para iniciar la instalación.
3. Seleccione la carga de trabajo Desarrollo para dispositivos móviles con .NET en la pantalla de
instalación:
4. Con la opción Desarrollo para dispositivos móviles con .NET seleccionada, eche un vistazo al panel
Detalles de la instalación de la derecha. Aquí puede anular la selección de opciones de desarrollo para
dispositivos móviles que no quiera instalar.

5. Cuando esté listo para comenzar la instalación de Visual Studio 2017, haga clic en el botón Instalar situado
en la esquina inferior derecha:

En función de la edición de Visual Studio 2017 que vaya a instalar, el proceso de instalación puede tardar
bastante tiempo. Puede usar las barras de progreso para supervisar la instalación:
6. Cuando haya finalizado la instalación de Visual Studio 2017, haga clic en el botón Iniciar para iniciar Visual
Studio:

Agregar Xamarin a Visual Studio 2017


Si Visual Studio 2017 ya está instalado, agregue Xamarin al volver a ejecutar el instalador de Visual Studio 2017
para modificar las cargas de trabajo (consulte Modificar Visual Studio para más información). Después, siga los
pasos indicados anteriormente para instalar Xamarin.
Para obtener más información sobre la descarga e instalación de Visual Studio 2017, consulte Instalación de Visual
Studio 2017.

Instalación de Xamarin en macOS

Instrucciones paso a paso


Además de este vídeo, hay una guía de instalación paso a paso que cubre Visual Studio para Mac y Xamarin.

Vínculos relacionados
Desinstalación de Xamarin
Instrucciones de configuración del firewall de Xamarin
Instalación de Xamarin en Visual Studio 2019
18/12/2020 • 3 minutes to read • Edit Online

Compruebe los requisitos del sistema antes de empezar.

Instalación
Xamarin se puede instalar como parte de una nueva instalación de Visual Studio 2019, con los pasos siguientes:
1. Descargue Visual Studio 2019 Community, Visual Studio Professional o Visual Studio Enterprise desde la
página de Visual Studio (en la parte inferior se encuentran los vínculos de descarga).
2. Haga doble clic en el paquete descargado para iniciar la instalación.
3. Seleccione la carga de trabajo Desarrollo para dispositivos móviles con .NET en la pantalla de
instalación:

4. Cuando esté listo para comenzar la instalación de Visual Studio 2019, haga clic en el botón Instalar de la
esquina inferior derecha:

Use las barras de progreso para supervisar la instalación:

5. Cuando haya finalizado la instalación de Visual Studio 2019, haga clic en el botón Iniciar para iniciar Visual
Studio:

Adición de Xamarin a Visual Studio 2019


Si Visual Studio 2019 ya está instalado, para agregar Xamarin vuelva a ejecutar el instalador de Visual Studio 2019
para modificar las cargas de trabajo (vea Modificación de Visual Studio para más información). Después, siga los
pasos indicados anteriormente para instalar Xamarin.
Para más información sobre la descarga e instalación de Visual Studio 2019, vea Instalación de Visual Studio 2019.
En Visual Studio 2019, compruebe que Xamarin está instalado; para ello, haga clic en el menú Ayuda . Si está
instalado, debería ver el elemento de menú Xamarin , como se muestra en esta captura de pantalla:

Puede hacer clic en Ayuda > Acerca de Microsoft Visual Studio y desplazarse por la lista de productos
instalados para ver si Xamarin está instalado:
Para obtener más información sobre cómo encontrar la información de la versión, consulte Where can I find my
version information and logs? (¿Dónde puedo encontrar los registros y la información de la versión?).

Pasos siguientes
La instalación de Xamarin en Visual Studio 2019 le permite empezar a escribir código para las aplicaciones, pero
requiere configuración adicional para compilar e implementar las aplicaciones en el simulador, el emulador y el
dispositivo. Consulte las siguientes guías para completar la instalación y empezar a compilar aplicaciones
multiplataforma.
iOS
Para obtener información detallada, consulte la guía de instalación de Xamarin.iOS en Windows.
1. Instalación de Visual Studio para Mac
2. Conexión de Visual Studio con el host de compilación del equipo Mac
3. Configuración del desarrollador de iOS: necesaria para ejecutar la aplicación en el dispositivo
4. Simulador remoto de iOS
5. Introducción a Xamarin.iOS para Visual Studio
Android
Para obtener información detallada, consulte la guía de instalación de Xamarin.Android en Windows.
1. Configuración de Xamarin.Android
2. Uso del administrador de Android SDK de Xamarin
3. Emulador de Android SDK
4. Configurar el dispositivo para el desarrollo
Instalación de la versión preliminar de Xamarin en
Windows
18/12/2020 • 2 minutes to read • Edit Online

En Visual Studio 2019 y Visual Studio 2017 no se admiten los canales alfa, beta ni estable de la misma manera que
en versiones anteriores. En su lugar, hay solo dos opciones:
Lanzamiento : equivalente al canal estable en Visual Studio para Mac
Versión preliminar : equivalente a los canales alfa y beta en Visual Studio para Mac

TIP
Para probar las características de versión preliminar, debe descargar el instalador de Visual Studio Preview, que le ofrece la
opción de instalar versiones preliminares de Visual Studio en paralelo con la versión estable (versión de lanzamiento). En
las notas de la versión puede encontrar más información sobre las novedades de Visual Studio 2019.

La versión preliminar de Visual Studio puede contener las versiones preliminares correspondientes de la
funcionalidad de Xamarin, incluidas:
Xamarin.Forms
Xamarin.iOS
Xamarin.Android
Generador de perfiles de Xamarin
Xamarin Inspector
Simulador iOS remoto de Xamarin
La captura de pantalla del instalador de versión preliminar siguiente muestra las opciones de versión
preliminar y lanzamiento (tenga en cuenta los números de versión en gris: la versión 15.0 es de lanzamiento y la
versión 15.1 es una versión preliminar):
Durante el proceso de instalación, se puede aplicar un alias de instalación a la instalación en paralelo (por lo que
pueden distinguirse en el menú Inicio), tal y como se muestra a continuación:

Desinstalación de Visual Studio 2019 Preview


El Instalador de Visual Studio también se debe usar para desinstalar las versiones preliminares de Visual
Studio 2019. Lea la guía de desinstalación de Xamarin para más información.
Desinstalación de Xamarin de Visual Studio
18/12/2020 • 4 minutes to read • Edit Online

En esta guía se explica cómo quitar Xamarin de Visual Studio en Windows.

Visual Studio 2019 y Visual Studio 2017


Xamarin se desinstala de Visual Studio 2019 y Visual Studio 2017 mediante la aplicación del instalador:
1. Use el menú Inicio para abrir el instalador de Visual Studio .
2. Pulse el botón Modificar para la instancia que quiera cambiar.

3. En la pestaña Cargas de trabajo , anule la selección de la opción Desarrollo móvil con .NET (en la
sección Dispositivos móviles y juegos ).

4. Haga clic en el botón Modificar en la parte inferior derecha de la ventana.


5. El instalador quitará los componentes cuya selección se haya anulado (Visual Studio 2017 debe cerrarse
antes de que el instalador pueda realizar cambios).

Se quiere desinstalar componentes individuales de Xamarin, como el generador de perfiles o los libros, puede
cambiar a la pestaña Componentes individuales en el paso 3 y desactivar componentes específicos:
Para desinstalar completamente Visual Studio 2017, elija Desinstalar en el menú de tres barras que hay junto al
botón Iniciar .

IMPORTANT
Si tiene dos o más instancias de Visual Studio instaladas en paralelo (SxS), como una versión de lanzamiento y una versión
preliminar, es posible que al desinstalar una instancia se quiten algunas funciones de Xamarin de las otras instancias de Visual
Studio, incluidas:
Generador de perfiles de Xamarin
Libros/Inspector de Xamarin
Simulador iOS remoto de Xamarin
SDK de Apple Bonjour
Bajo ciertas condiciones, la desinstalación de una de las instancias SxS puede ocasionar la eliminación incorrecta de estas
características. Esto puede degradar el rendimiento de la plataforma Xamarin en las instancias de Visual Studio que
permanecen en el sistema después de desinstalar la instancia SxS.
Esto se puede resolver mediante la ejecución de la opción Reparación en el instalador de Visual Studio, que volverá a instalar
los componentes que falten.

Visual Studio 2015 y anterior


Para desinstalar Visual Studio 2015 por completo, use la respuesta de soporte técnico que encontrará en
visualstudio.com.
Xamarin puede desinstalarse de un equipo Windows mediante el Panel de control . Vaya a Programas y
características o Programas > Desinstalar un programa como se muestra a continuación:
Desde el Panel de control, desinstale cualquiera de los siguientes elementos:
Xamarin
Xamarin para Windows
Xamarin.Android
Xamarin.iOS
Xamarin para Visual Studio
En el Explorador, elimine los archivos restantes de las carpetas de la extensión de Xamarin Visual Studio (todas las
versiones, incluidas las de Archivos de programa y Archivos de programa (x86)):

C:\Program Files*\Microsoft Visual Studio 1*.0\Common7\IDE\Extensions\Xamarin

Elimine el directorio de la caché del componente MEF de Visual Studio, que debería estar en la ubicación siguiente:

%LOCALAPPDATA%\Microsoft\VisualStudio\1*.0\ComponentModelCache

Consulte el directorio Vir tualStore para ver si es posible que Windows haya almacenado allí algún archivo de
superposición para los directorios Extensions\Xamarin o ComponentModelCache :

%LOCALAPPDATA%\VirtualStore

Abra el editor del Registro (regedit) y busque la siguiente clave:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\SharedDlls

Busque y elimine las entradas que coincidan con este patrón:

C:\Program Files*\Microsoft Visual Studio 1*.0\Common7\IDE\Extensions\Xamarin

Busque esta clave:

HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\1*.0\ExtensionManager\PendingDeletions

Elimine las entradas que parezca que puedan estar relacionadas con Xamarin. Por ejemplo, todo lo que contenga
los términos mono o xamarin .
Abra un símbolo de sistema de administrador cmd.exe y, después, ejecute los comandos devenv /setup y
devenv /updateconfiguration para cada versión instalada de Visual Studio. Por ejemplo, para Visual Studio 2015:

"%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /setup


"%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /updateconfiguration
Instrucciones de configuración de firewall para
Xamarin
18/12/2020 • 3 minutes to read • Edit Online

Lista de los hosts que se deben permitir en el firewall para que la plataforma de Xamarin funcione en la empresa.
Para que los productos de Xamarin se instalen y funcionen correctamente, determinados puntos de conexión
deben ser accesibles para descargar las herramientas y las actualizaciones necesarias para el software. Si usted o la
empresa tienen una configuración de firewall estricta, puede experimentar problemas con la instalación, las
licencias, los componentes, etc. En este documento se indican algunos de los puntos de conexión conocidos que se
deben permitir en el firewall para que Xamarin funcione. No se incluyen los puntos de conexión necesarios para las
herramientas de otros fabricantes incluidas en la descarga. Si después de repasar esta lista sigue teniendo
problemas, consulte las guías de solución de problemas de instalación de Apple o Android.

Puntos de conexión permitidos


Instalador de Xamarin
Para que el software se instale correctamente con la versión más reciente del instalador de Xamarin, tiene que
agregar las siguientes direcciones conocidas:
xamarin.com (manifiestos del instalador)
dl.xamarin.com (ubicación de descarga del paquete)
dl.google.com (para descargar el SDK de Android)
download.oracle.com (JDK)
visualstudio.com (configurar ubicación de descarga de paquetes)
go.microsoft.com (configurar URL de resolución)
aka.ms (configurar URL de resolución)
Si está usando un equipo Mac y tiene problemas de instalación de Xamarin.Android, asegúrese de que macOS
pueda descargar Java.
NuGet (incluido Xamarin.Forms)
Para obtener acceso a NuGet (Xamarin.Forms está empaquetado como NuGet), tiene que agregar las siguientes
direcciones:
www.nuget.org (para acceder a NuGet)
globalcdn.nuget.org (descargas de NuGet)
dl-ssl.google.com (componentes de Google para Android y Xamarin.Forms)
Actualizaciones de software
Para garantizar que las actualizaciones de software se descarguen correctamente, tendrá que agregar las siguientes
direcciones:
software.xamarin.com (servicio de actualizador)
download.visualstudio.microsoft.com
dl.xamarin.com

Xamarin Mac Agent


Para conectar Visual Studio a un host de compilación de Mac mediante Xamarin Mac Agent, el puerto SSH tiene
que estar abierto. De forma predeterminada, es el puer to 22 .
Plataformas admitidas para Xamarin.Forms
18/12/2020 • 3 minutes to read • Edit Online

Las aplicaciones en Xamarin.Forms se pueden escribir para los siguientes sistemas operativos:
iOS 9 o versiones posteriores.
Android 4.4 (API 19) o versiones posteriores (más detalles). Pero se recomienda Android 5.0 (API 21) como la
API mínima. Esto garantiza la compatibilidad total con todas las bibliotecas de compatibilidad de Android, a la
vez que se destina a la mayoría de los dispositivos Android.
Plataforma universal de Windows de Windows 10, compilación 10.0.16299.0 o una superior para
compatibilidad con .NET Standard 2.0. Sin embargo, se recomienda la compilación 10.0.18362.0 o una superior.
Las aplicaciones en Xamarin.Forms para iOS, Android y la Plataforma universal de Windows (UWP) se pueden
compilar en Visual Studio. Pero se necesita un equipo Mac en red para el desarrollo de iOS con la última versión
de Xcode y la versión mínima de macOS que especifica Apple. Para más información, vea Requisitos de Windows.
Las aplicaciones en Xamarin.Forms para iOS y Android se pueden compilar en Visual Studio para Mac. Para más
información, vea Requisitos de macOS.

NOTE
Para desarrollar aplicaciones con Xamarin.Forms es necesario familiarizarse con .NET Standard.

Compatibilidad con plataforma adicional


Xamarin.Forms admite otras plataformas además de iOS, Android y Windows:
Samsung Tizen
macOS 10.13 o posterior
GTK#
WPF
El estado de estas plataformas está disponible en la wiki de compatibilidad con la plataforma de GitHub para
Xamarin.Forms.

Compatibilidad con la plataforma Android


Debe tener instalada la plataforma más reciente de Android SDK Tools y de la API de Android. Puede actualizar a
las versiones más recientes con Android SDK Manager.
Además, la versión de compilación o de destino de los proyectos de Android debe establecerse en Usar la última
plataforma instalada, aunque la versión mínima se puede establecer en API 19 para que pueda seguir admitiendo
dispositivos que usen Android 4.4 y versiones más recientes. Estos valores se establecen en Opciones del
proyecto :
Visual Studio
Visual Studio para Mac
Opciones de proyecto > Aplicación > Propiedades de la aplicación
Plataformas en desuso
No se admiten estas plataformas cuando se usa Xamarin.Forms 3.0 o una versión más reciente:
Windows 8.1 / Windows Phone 8.1 WinRT
Windows Phone 8 Silverlight
Compilación de la primera aplicación de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Vea este vídeo y siga el tutorial para crear una primera aplicación móvil con :::no-loc(Xamarin.Forms):::.

Instrucciones paso a paso para Windows


Descargar el ejemplo
Siga estos pasos, junto con el vídeo anterior:
1. Elija Archivo > Nuevo > Proyecto... o presione el botón Crear nuevo proyecto... :

2. Busque "Xamarin" o elija Dispositivos móviles en el menú Tipo de proyecto . Seleccione el tipo de
proyecto Aplicación móvil (:::no-loc(Xamarin.Forms):::) :
3. Elija un nombre de proyecto; en el ejemplo se usa "AwesomeApp":

4. Haga clic en el tipo de proyecto En blanco y asegúrese de que Android e iOS estén seleccionados:

5. Espere hasta que se restauren los paquetes de NuGet (aparecerá un mensaje de "Restauración completada"
en la barra de estado).
6. Las nuevas instalaciones de Visual Studio 2019 no tendrán un emulador de Android configurado. Haga clic
en la flecha desplegable del botón Depurar y elija Crear Android Emulator para iniciar la pantalla de
creación del emulador:
7. En la pantalla de creación del emulador, use la configuración predeterminada y haga clic en el botón Crear :

8. Al crear un emulador, se le devolverá a la ventana Administrador de dispositivos. Haga clic en el botón


Iniciar para iniciar el nuevo emulador:

9. Ahora en Visual Studio 2019 se debería mostrar el nombre del nuevo emulador en el botón Depurar :

10. Haga clic en el botón Depurar para compilar e implementar la aplicación en el emulador de Android:

Personalización de la aplicación
La aplicación se puede personalizar para agregar funcionalidad interactiva. Realice los pasos siguientes para
agregar la interacción del usuario a la aplicación:
1. Edite MainPage.xaml , agregando este XAML antes del final de </StackLayout> :

<Button Text="Click Me" Clicked="Button_Clicked" />

2. Edite MainPage.xaml , agregando este código al final de la clase:

int count = 0;
void Button_Clicked(object sender, System.EventArgs e)
{
count++;
((Button)sender).Text = $"You clicked {count} times.";
}

3. Depurar la aplicación en Android:

NOTE
En la aplicación de ejemplo se incluye la funcionalidad interactiva adicional que no se trata en el vídeo.

Compilación de una aplicación iOS en Visual Studio 2019


Es posible crear y depurar la aplicación iOS desde Visual Studio con un equipo Mac en red. Consulte las
instrucciones de configuración para obtener más información.
En este vídeo se describe el proceso de compilación y prueba de una aplicación iOS con Visual Studio 2019 en
Windows:
Instrucciones paso a paso para Windows
Descargar el ejemplo
Siga estos pasos, junto con el vídeo anterior:
1. Elija Archivo > Nuevo > Proyecto… o presione el botón Crear proyecto nuevo… y, luego, seleccione
Visual C# > Multiplataforma > Aplicación móvil (:::no-loc(Xamarin.Forms):::) :

2. Asegúrese de que Android y iOS están seleccionados, con uso compartido del código .NET Standard :

3. Espere hasta que se restauren los paquetes de NuGet (aparecerá un mensaje de "Restauración completada"
en la barra de estado).
4. Inicie Android Emulator presionando el botón de depuración (o el elemento de menú Depurar > Iniciar
depuración ).
5. Edite MainPage.xaml , agregando este XAML antes del final de </StackLayout> :

<Button Text="Click Me" Clicked="Button_Clicked" />

6. Edite MainPage.xaml , agregando este código al final de la clase:


int count = 0;
void Button_Clicked(object sender, System.EventArgs e)
{
count++;
((Button)sender).Text = $"You clicked {count} times.";
}

7. Depurar la aplicación en Android:

TIP
Es posible crear y depurar la aplicación de iOS desde Visual Studio con un equipo Mac en red. Consulte las
instrucciones de configuración para obtener más información.

Instrucciones paso a paso para Mac


Descargar el ejemplo
Siga estos pasos, junto con el vídeo anterior:
1. Elija Archivo > Nueva solución... o presione el botón Nuevo proyecto... y, posteriormente, seleccione
Multiplataforma > Aplicación > Aplicación de Forms en blanco :
2. Asegúrese de que Android y iOS están seleccionados, con uso compartido del código .NET Standard :

3. Restaurar paquetes de NuGet, haciendo clic en el botón derecho en la solución:

4. Inicie Android Emulator presionando el botón de depuración (o Ejecutar > Iniciar depuración ).
5. Edite MainPage.xaml , agregando este XAML antes del final de </StackLayout> :

<Button Text="Click Me" Clicked="Handle_Clicked" />

6. Edite MainPage.xaml , agregando este código al final de la clase:

int count = 0;
void Handle_Clicked(object sender, System.EventArgs e)
{
count++;
((Button)sender).Text = $"You clicked {count} times.";
}

7. Depurar la aplicación en Android:


8. Haga clic con el botón derecho para establecer iOS en el Proyecto de inicio :

9. Depurar la aplicación en iOS:


Puede descargar el código completo desde la galería de ejemplos o puede verlo en GitHub.

Pasos siguientes
Inicio rápido de una sola página: compilación de una aplicación más funcional.
Ejemplos de :::no-loc(Xamarin.Forms):::: descarga y ejecución de ejemplos de código y de aplicaciones.
Libro electrónico Creación de aplicaciones móviles: capítulos detallados en los que se describe el desarrollo de
:::no-loc(Xamarin.Forms):::, disponible como archivo PDF y con cientos de ejemplos adicionales.
Inicios rápidos de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Obtenga información sobre cómo crear aplicaciones para dispositivos móviles con Xamarin.Forms.

Creación de una aplicación de Xamarin.Forms de una página


Obtenga información sobre cómo crear una aplicación multiplataforma de Xamarin.Forms de una página, que
permite escribir una nota y conservarla en el almacenamiento del dispositivo.

Navegación en una aplicación Xamarin.Forms de varias páginas


Obtenga información sobre cómo convertir una aplicación de una página, capaz de almacenar una sola nota, en
una aplicación de varias páginas, capaz de almacenar varias notas.

Almacenamiento de datos en una base de datos de SQLite.NET local


Obtenga información sobre cómo almacenar datos en una base de datos local de SQLite.NET.

Estilo de una aplicación de Xamarin.Forms multiplataforma


Obtenga información sobre cómo diseñar una aplicación de Xamarin.Forms multiplataforma con estilos XAML.

Análisis detallado de inicio rápido


Obtenga información sobre los aspectos básicos del desarrollo de aplicaciones con Xamarin.Forms, centrándose en
la aplicación desarrollada en los inicios rápidos.
Creación de una aplicación de :::no-
loc(Xamarin.Forms)::: de una página
18/12/2020 • 24 minutes to read • Edit Online

Descargar el ejemplo
En este inicio rápido aprenderá a:
Crear una aplicación de :::no-loc(Xamarin.Forms)::: multiplataforma.
Definir la interfaz de usuario para una página mediante el lenguaje XAML.
Interactuar con los elementos de la interfaz de usuario XAML desde el código.
El inicio rápido le guía sobre cómo crear una aplicación de :::no-loc(Xamarin.Forms)::: multiplataforma que permite
escribir una nota y conservarla en el almacenamiento del dispositivo. A continuación se muestra la aplicación
final:

Requisitos previos
Visual Studio 2019 (última versión), con la carga de trabajo Desarrollo para dispositivos móviles con
.NET instalada.
Conocimientos de C#.
(opcional) Un equipo Mac emparejado para compilar la aplicación en iOS.
Para más información sobre estos requisitos previos, vea Instalación de Xamarin. Para obtener información sobre
cómo conectar Visual Studio 2019 a un host de compilación de Mac, consulte Emparejar con Mac para el
desarrollo de Xamarin.iOS.

Introducción a Visual Studio 2019


1. Inicie Visual Studio 2019 y, en la ventana de inicio, haga clic en Crear un proyecto nuevo para crear un
proyecto:
2. En la ventana Crear un proyecto nuevo , seleccione Móvil en la lista desplegable Tipo de proyecto ,
elija la plantilla Aplicación móvil (:::no-loc(Xamarin.Forms):::) y haga clic en el botón Siguiente :

3. En el cuadro de diálogo Configure su nuevo proyecto , establezca Nombre del proyecto en Notes ,
elija una ubicación adecuada para el proyecto y haga clic en el botón Crear :
IMPORTANT
Los fragmentos de XAML y C# en este inicio rápido requieren que la solución se denomine Notes . El uso de otro
nombre dará como resultado errores de compilación al copiar el código de este inicio rápido en la solución.

4. En el cuadro de diálogo New Cross Platform App (Nueva aplicación multiplataforma), haga clic en En
blanco y después en el botón Aceptar :

Para obtener más información sobre la biblioteca de .NET Standard creada, vea Anatomía de una aplicación
de :::no-loc(Xamarin.Forms)::: en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
5. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en MainPage.xaml para abrirlo:
6. En MainPage.xaml , quite todo el código de plantilla y sustitúyalo por el siguiente código:

<?xml version="1.0" encoding="utf-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.MainPage">
<StackLayout Margin="10,35,10,10">
<Label Text="Notes"
HorizontalOptions="Center"
FontAttributes="Bold" />
<Editor x:Name="editor"
Placeholder="Enter your note"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked"/>
</Grid>
</StackLayout>
</ContentPage>

Este código define mediante declaración la interfaz de usuario para la página, que consta de una instancia
de Label para mostrar texto, una instancia de Editor para entrada de texto y dos instancias de Button
que dirigen la aplicación para guardar o eliminar un archivo. Las dos instancias de Button se disponen
horizontalmente en Grid , mientras que Label , Editor y Grid se disponen verticalmente en
StackLayout . Para obtener más información sobre la creación de la interfaz de usuario, vea Interfaz de
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml presionando CTRL+S y cierre el archivo.
7. En el Explorador de soluciones , en el proyecto Notes , expanda MainPage.xaml y haga doble clic en
MainPage.xaml.cs para abrirlo:
8. En MainPage.xaml.cs , quite todo el código de plantilla y sustitúyalo por el siguiente código:

using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;

namespace Notes
{
public partial class MainPage : ContentPage
{
string _fileName =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "notes.txt");

public MainPage()
{
InitializeComponent();

if (File.Exists(_fileName))
{
editor.Text = File.ReadAllText(_fileName);
}
}

void OnSaveButtonClicked(object sender, EventArgs e)


{
File.WriteAllText(_fileName, editor.Text);
}

void OnDeleteButtonClicked(object sender, EventArgs e)


{
if (File.Exists(_fileName))
{
File.Delete(_fileName);
}
editor.Text = string.Empty;
}
}
}
Este código define un campo _fileName , que hace referencia a un archivo denominado notes.txt que
almacenará los datos de la nota en la carpeta de datos de la aplicación local para la aplicación. Cuando se
ejecuta el constructor de páginas, el archivo se lee, si existe, y se muestra en el Editor . Al presionar el
control Button Guardar , se ejecuta el controlador de eventos OnSaveButtonClicked , que guarda el
contenido de Editor en el archivo. Al presionar el control Button Eliminar , se ejecuta el controlador de
eventos OnDeleteButtonClicked , que elimina el archivo, siempre que exista, y quita cualquier texto de
Editor . Para obtener más información sobre la interacción del usuario, vea Respuesta a la interacción del
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml.cs presionando CTRL+S y cierre el archivo.
Compilar el tutorial rápido
1. En Visual Studio, seleccione el elemento de menú Compilación > Compilar solución (o presione F6). La
solución se compilará y aparecerá un mensaje de operación correcta en la barra de estado de Visual Studio:

Si hay errores, repita los pasos anteriores y corrija los errores hasta que la solución se compile
correctamente.
2. En la barra de herramientas de Visual Studio, pulse el botón Iniciar (el botón triangular que parece un
botón de reproducción) para iniciar la aplicación en la instancia elegida de Android Emulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.

NOTE
Los pasos siguientes solo deben realizarse si se tiene un equipo Mac emparejado que cumpla los requisitos del
sistema para el desarrollo de :::no-loc(Xamarin.Forms):::.

3. En la barra de herramientas de Visual Studio, haga clic con el botón derecho en el proyecto Notes.iOS y
seleccione Establecer como proyecto de inicio .

4. En la barra de herramientas de Visual Studio, pulse el botón Iniciar (el botón triangular que parece un
botón de reproducción) para iniciar la aplicación en la instancia elegida de iOS Simulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Requisitos previos
Visual Studio 2017, con la carga de trabajo Desarrollo para dispositivos móviles con .NET instalada.
Conocimientos de C#.
(opcional) Un equipo Mac emparejado para compilar la aplicación en iOS.
Para más información sobre estos requisitos previos, vea Instalación de Xamarin. Para obtener información sobre
cómo conectar Visual Studio 2019 a un host de compilación de Mac, consulte Emparejar con Mac para el
desarrollo de Xamarin.iOS.

Introducción a Visual Studio 2017


1. Para crear un proyecto, inicie Visual Studio 2017 y, en la página de inicio, haga clic en Crear nuevo
proyecto... :

2. En el cuadro de diálogo Nuevo proyecto , haga clic en Multiplataforma , seleccione la plantilla


Aplicación móvil (:::no-loc(Xamarin.Forms):::) , establezca el nombre en Notes , elija una ubicación
adecuada para el proyecto y haga clic en el botón Aceptar :

IMPORTANT
Los fragmentos de XAML y C# en este inicio rápido requieren que la solución se denomine Notes . El uso de otro
nombre dará como resultado errores de compilación al copiar el código de este inicio rápido en la solución.

3. En el cuadro de diálogo New Cross Platform App (Nueva aplicación multiplataforma), haga clic en
Aplicación en blanco , seleccione .NET Standard como estrategia de código de uso compartido y haga
clic en el botón Aceptar :

Para obtener más información sobre la biblioteca de .NET Standard creada, vea Anatomía de una aplicación
de :::no-loc(Xamarin.Forms)::: en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
4. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en MainPage.xaml para abrirlo:

5. En MainPage.xaml , quite todo el código de plantilla y sustitúyalo por el siguiente código:


<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.MainPage">
<StackLayout Margin="10,35,10,10">
<Label Text="Notes"
HorizontalOptions="Center"
FontAttributes="Bold" />
<Editor x:Name="editor"
Placeholder="Enter your note"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked"/>
</Grid>
</StackLayout>
</ContentPage>

Este código define mediante declaración la interfaz de usuario para la página, que consta de una instancia
de Label para mostrar texto, una instancia de Editor para entrada de texto y dos instancias de Button
que dirigen la aplicación para guardar o eliminar un archivo. Las dos instancias de Button se disponen
horizontalmente en Grid , mientras que Label , Editor y Grid se disponen verticalmente en
StackLayout . Para obtener más información sobre la creación de la interfaz de usuario, vea Interfaz de
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml presionando CTRL+S y cierre el archivo.
6. En el Explorador de soluciones , en el proyecto Notes , expanda MainPage.xaml y haga doble clic en
MainPage.xaml.cs para abrirlo:
7. En MainPage.xaml.cs , quite todo el código de plantilla y sustitúyalo por el siguiente código:
using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;

namespace Notes
{
public partial class MainPage : ContentPage
{
string _fileName =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "notes.txt");

public MainPage()
{
InitializeComponent();

if (File.Exists(_fileName))
{
editor.Text = File.ReadAllText(_fileName);
}
}

void OnSaveButtonClicked(object sender, EventArgs e)


{
File.WriteAllText(_fileName, editor.Text);
}

void OnDeleteButtonClicked(object sender, EventArgs e)


{
if (File.Exists(_fileName))
{
File.Delete(_fileName);
}
editor.Text = string.Empty;
}
}
}

Este código define un campo _fileName , que hace referencia a un archivo denominado notes.txt que
almacenará los datos de la nota en la carpeta de datos de la aplicación local para la aplicación. Cuando se
ejecuta el constructor de páginas, el archivo se lee, si existe, y se muestra en el Editor . Al presionar el
control Button Guardar , se ejecuta el controlador de eventos OnSaveButtonClicked , que guarda el
contenido de Editor en el archivo. Al presionar el control Button Eliminar , se ejecuta el controlador de
eventos OnDeleteButtonClicked , que elimina el archivo, siempre que exista, y quita cualquier texto de
Editor . Para obtener más información sobre la interacción del usuario, vea Respuesta a la interacción del
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml.cs presionando CTRL+S y cierre el archivo.
Compilar el tutorial rápido
1. En Visual Studio, seleccione el elemento de menú Compilación > Compilar solución (o presione F6). La
solución se compilará y aparecerá un mensaje de operación correcta en la barra de estado de Visual Studio:
Si hay errores, repita los pasos anteriores y corrija los errores hasta que la solución se compile
correctamente.
2. En la barra de herramientas de Visual Studio, pulse el botón Iniciar (el botón triangular que parece un
botón de reproducción) para iniciar la aplicación en la instancia elegida de Android Emulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.

NOTE
Los pasos siguientes solo deben realizarse si se tiene un equipo Mac emparejado que cumpla los requisitos del
sistema para el desarrollo de :::no-loc(Xamarin.Forms):::.

3. En la barra de herramientas de Visual Studio, haga clic con el botón derecho en el proyecto Notes.iOS y
seleccione Establecer como proyecto de inicio .
4. En la barra de herramientas de Visual Studio, pulse el botón Iniciar (el botón triangular que parece un
botón de reproducción) para iniciar la aplicación en la instancia elegida de iOS Simulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Requisitos previos
Visual Studio para Mac (última versión) con compatibilidad con la plataforma iOS y Android.
Xcode (última versión).
Conocimientos de C#.
Para más información sobre estos requisitos previos, vea Instalación de Xamarin.

Introducción a Visual Studio para Mac


1. Para crear un proyecto, inicie Visual Studio para Mac y, en la ventana de inicio, haga clic en Nuevo :
2. En el cuadro de diálogo Elija una plantilla para el nuevo proyecto , haga clic en Multiplataforma >
Aplicación , seleccione la plantilla Aplicación de Forms en blanco y haga clic en el botón Siguiente :

3. En el cuadro de diálogo Configurar su nueva aplicación de Forms en blanco , asigne a la nueva


aplicación el nombre Notes , asegúrese de que el botón de radio Usar .NET Standard esté seleccionado y
haga clic en el botón Siguiente :
4. En el cuadro de diálogo Configurar su nueva aplicación de Forms en blanco , deje los nombres de
solución y de proyecto establecidos en Notes , elija una ubicación adecuada para el proyecto y haga clic en
el botón Crear para crearlo:
IMPORTANT
Los fragmentos de XAML y C# en este inicio rápido requieren que tanto la solución como el proyecto se denominen
Notes . El uso de otro nombre dará como resultado errores de compilación al copiar el código de este inicio rápido
en la solución.

Para obtener más información sobre la biblioteca de .NET Standard creada, vea Anatomía de una aplicación
de :::no-loc(Xamarin.Forms)::: en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
5. En el Panel de solución , en el proyecto Notes , haga doble clic en MainPage.xaml para abrirlo:

6. En MainPage.xaml , quite todo el código de plantilla y sustitúyalo por el siguiente código:

<?xml version="1.0" encoding="utf-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.MainPage">
<StackLayout Margin="10,35,10,10">
<Label Text="Notes"
HorizontalOptions="Center"
FontAttributes="Bold" />
<Editor x:Name="editor"
Placeholder="Enter your note"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked"/>
</Grid>
</StackLayout>
</ContentPage>

Este código define mediante declaración la interfaz de usuario para la página, que consta de una instancia
de Label para mostrar texto, una instancia de Editor para entrada de texto y dos instancias de Button
que dirigen la aplicación para guardar o eliminar un archivo. Las dos instancias de Button se disponen
horizontalmente en Grid , mientras que Label , Editor y Grid se disponen verticalmente en
StackLayout . Para obtener más información sobre la creación de la interfaz de usuario, vea Interfaz de
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml eligiendo Archivo > Guardar (o presionando ⌘ + S ) y cierre el
archivo.
7. En el Panel de solución , en el proyecto Notes , expanda MainPage.xaml y haga doble clic en
MainPage.xaml.cs para abrirlo:
8. En MainPage.xaml.cs , quite todo el código de plantilla y sustitúyalo por el siguiente código:

using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;

namespace Notes
{
public partial class MainPage : ContentPage
{
string _fileName =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "notes.txt");

public MainPage()
{
InitializeComponent();

if (File.Exists(_fileName))
{
editor.Text = File.ReadAllText(_fileName);
}
}

void OnSaveButtonClicked(object sender, EventArgs e)


{
File.WriteAllText(_fileName, editor.Text);
}

void OnDeleteButtonClicked(object sender, EventArgs e)


{
if (File.Exists(_fileName))
{
File.Delete(_fileName);
}
editor.Text = string.Empty;
}
}
}

Este código define un campo _fileName , que hace referencia a un archivo denominado notes.txt que
almacenará los datos de la nota en la carpeta de datos de la aplicación local para la aplicación. Cuando se
ejecuta el constructor de páginas, el archivo se lee, si existe, y se muestra en el Editor . Al presionar el
control Button Guardar , se ejecuta el controlador de eventos OnSaveButtonClicked , que guarda el
contenido de Editor en el archivo. Al presionar el control Button Eliminar , se ejecuta el controlador de
eventos OnDeleteButtonClicked , que elimina el archivo, siempre que exista, y quita cualquier texto de
Editor . Para obtener más información sobre la interacción del usuario, vea Respuesta a la interacción del
usuario en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Guarde los cambios en MainPage.xaml.cs eligiendo Archivo > Guardar (o presionando ⌘ + S ) y
cierre el archivo.
Compilación del inicio rápido
1. En Visual Studio para Mac, seleccione el elemento de menú Compilación > Compilar todo (o pulse ⌘ +
B ). El proyecto se compilará y aparecerá un mensaje de operación correcta en la barra de herramientas de
Visual Studio para Mac.

Si hay errores, repita los pasos anteriores y corrija los errores hasta que la solución se compile
correctamente.
2. En el Panel de solución , seleccione el proyecto Notes.iOS , haga clic con el botón derecho y seleccione
Establecer como proyecto de inicio :

3. En la barra de herramientas de Visual Studio para Mac, pulse el botón Iniciar (el botón triangular similar a
un botón de reproducción) para iniciar la aplicación en la instancia elegida de iOS Simulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
4. En el Panel de solución , seleccione el proyecto Notes.Droid , haga clic con el botón derecho y
seleccione Establecer como proyecto de inicio :

5. En la barra de herramientas de Visual Studio para Mac, pulse el botón Iniciar (el botón triangular similar a
un botón de reproducción) para iniciar la aplicación en la instancia elegida de Android Emulator:

Escriba una nota y pulse el botón Guardar .


Para obtener más información sobre cómo se inicia la aplicación en cada plataforma, vea Inicio de la
aplicación en cada plataforma en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.

Pasos siguientes
En este inicio rápido ha aprendido a:
Crear una aplicación de :::no-loc(Xamarin.Forms)::: multiplataforma.
Definir la interfaz de usuario para una página mediante el lenguaje XAML.
Interactuar con los elementos de la interfaz de usuario XAML desde el código.
Para convertir esta aplicación de página única en una de varias páginas, continúe con el siguiente inicio rápido.
Siguiente

Vínculos relacionados
Notes (ejemplo)
Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::
Navegación en una aplicación :::no-
loc(Xamarin.Forms)::: de varias páginas
18/12/2020 • 26 minutes to read • Edit Online

Descargar el ejemplo
En este inicio rápido aprenderá a:
Agregar páginas adicionales a una solución :::no-loc(Xamarin.Forms):::.
Realizar la navegación entre las páginas.
Usar el enlace de datos para sincronizar datos entre los elementos de la interfaz de usuario y su origen de
datos.
En este inicio rápido se describe cómo convertir una aplicación :::no-loc(Xamarin.Forms)::: multiplataforma de una
página, capaz de almacenar una sola nota, en una aplicación de varias páginas, capaz de almacenar varias notas. A
continuación se muestra la aplicación final:

Requisitos previos
Antes de intentar este inicio rápido, debe completar correctamente el inicio rápido anterior. También puede
descargar el ejemplo del inicio rápido anterior y usarlo como punto de partida para este.

Actualizar la aplicación con Visual Studio


1. Inicie Visual Studio. En la ventana de inicio, haga clic en la solución Notes en la lista de proyectos o
soluciones recientes, o bien haga clic en Abrir un proyecto o una solución y, en el cuadro de diálogo
Abrir proyecto o solución , seleccione el archivo de solución del proyecto Notes:
2. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto Notes y seleccione
Agregar > Nueva carpeta :

3. En el Explorador de soluciones , asigne el nombre Modelos a la nueva carpeta:


4. En el Explorador de soluciones , seleccione la carpeta Modelos , haga clic con el botón derecho y
seleccione Agregar > Nuevo elemento... :

5. En el cuadro de diálogo Agregar nuevo elemento , seleccione Elementos de Visual C# > Clase ,
asigne el nombre Note al nuevo archivo y haga clic en el botón Agregar :

Esto agregará una clase denominada Note a la carpeta Modelos del proyecto Notes .
6. En Note.cs , quite todo el código de plantilla y sustitúyalo por el siguiente:
using System;

namespace Notes.Models
{
public class Note
{
public string Filename { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
}

Esta clase define un modelo Note que almacenará los datos sobre cada nota en la aplicación.
Presione CTRL+S para guardar los cambios en Note.cs y cierre el archivo.
7. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto Notes y seleccione
Agregar > Nuevo elemento... . En el cuadro de diálogo Agregar nuevo elemento , seleccione
Elementos de Visual C# > :::no-loc(Xamarin.Forms)::: > Página de contenido , asigne el nombre
NoteEntr yPage al nuevo archivo y haga clic en el botón Agregar :

Esto agregará una página nueva denominada NoteEntr yPage a la carpeta raíz del proyecto. Esta página
será la segunda de la aplicación.
8. En NoteEntr yPage.xaml , quite todo el código de plantilla y reemplácelo por el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
<StackLayout Margin="20">
<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked"/>
</Grid>
</StackLayout>
</ContentPage>

Este código define mediante declaración la interfaz de usuario para la página, que consta de una instancia
de Editor para la entrada de texto y dos instancias de Button que dirigen la aplicación para guardar o
eliminar un archivo. Las dos instancias de Button se disponen horizontalmente en Grid , mientras que
Editor y Grid se disponen en vertical en StackLayout . Además, Editor usa el enlace de datos para
enlazar con la propiedad Text del modelo Note . Para obtener más información sobre el enlace de datos,
vea Enlace de datos en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NoteEntr yPage.xaml y cierre el archivo.
9. En NoteEntr yPage.xaml.cs , quite todo el código de plantilla y reemplácelo por el siguiente:
using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;
using Notes.Models;

namespace Notes
{
public partial class NoteEntryPage : ContentPage
{
public NoteEntryPage()
{
InitializeComponent();
}

async void OnSaveButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;

if (string.IsNullOrWhiteSpace(note.Filename))
{
// Save
var filename = Path.Combine(App.FolderPath, $"{Path.GetRandomFileName()}.notes.txt");
File.WriteAllText(filename, note.Text);
}
else
{
// Update
File.WriteAllText(note.Filename, note.Text);
}

await Navigation.PopAsync();
}

async void OnDeleteButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;

if (File.Exists(note.Filename))
{
File.Delete(note.Filename);
}

await Navigation.PopAsync();
}
}
}

Este código almacena una instancia de Note , que representa una única nota, en el elemento
BindingContext de la página. Cuando se presiona el objeto Button Guardar se ejecuta el controlador de
eventos OnSaveButtonClicked , que guarda el contenido de Editor en un archivo nuevo con un nombre de
archivo generado de forma aleatoria, o bien en un archivo existente si se va a actualizar una nota. En ambos
casos, el archivo se almacena en la carpeta de datos de la aplicación local. Después, el método regresa a la
página anterior. Al pulsar el objeto Button Eliminar , se ejecuta el controlador de eventos
OnDeleteButtonClicked , que elimina el archivo, siempre que exista, y regresa a la página anterior. Para
obtener más información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de
:::no-loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NoteEntr yPage.xaml.cs y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.
10. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto Notes y seleccione
Agregar > Nuevo elemento... . En el cuadro de diálogo Agregar nuevo elemento , seleccione
Elementos de Visual C# > :::no-loc(Xamarin.Forms)::: > Página de contenido , asigne el nombre
NotesPage al nuevo archivo y haga clic en el botón Agregar .
Esto agregará una página nueva denominada NotesPage a la carpeta raíz del proyecto. Esta página será la
página raíz de la aplicación.
11. En NotesPage.xaml , quite todo el código de plantilla y sustitúyalo por el siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NotesPage"
Title="Notes">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnNoteAddedClicked" />
</ContentPage.ToolbarItems>
<ListView x:Name="listView"
Margin="20"
ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

Este código define mediante declaración la interfaz de usuario de la página, que consiste en los objetos
ListView y ToolbarItem . En ListView se usa el enlace de datos para mostrar las notas recuperadas por la
aplicación y, al seleccionar una nota, se navegará a NoteEntryPage donde la nota se puede modificar. Como
alternativa, se puede presionar el objeto ToolbarItem para crear una nota. Para obtener más información
sobre el enlace de datos, vea Enlace de datos en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NotesPage.xaml y cierre el archivo.
12. En NotesPage.xaml.cs , quite todo el código de plantilla y sustitúyalo por el siguiente:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using :::no-loc(Xamarin.Forms):::;
using Notes.Models;

namespace Notes
{
public partial class NotesPage : ContentPage
{
public NotesPage()
{
InitializeComponent();
}

protected override void OnAppearing()


{
base.OnAppearing();

var notes = new List<Note>();

var files = Directory.EnumerateFiles(App.FolderPath, "*.notes.txt");


foreach (var filename in files)
{
notes.Add(new Note
{
Filename = filename,
Text = File.ReadAllText(filename),
Date = File.GetCreationTime(filename)
});
}

listView.ItemsSource = notes
.OrderBy(d => d.Date)
.ToList();
}

async void OnNoteAddedClicked(object sender, EventArgs e)


{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = new Note()
});
}

async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)


{
if (e.SelectedItem != null)
{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = e.SelectedItem as Note
});
}
}
}
}

Este código define la funcionalidad de NotesPage . Cuando aparece la página, se ejecuta el método
OnAppearing , que rellena el objeto ListView con las notas que se han recuperado de la carpeta de datos de
la aplicación local. Cuando se presiona ToolbarItem , se ejecuta el controlador de eventos
OnNoteAddedClicked . Este método navega hasta NoteEntryPage , y establece el elemento BindingContext de
NoteEntryPage en una instancia nueva de Note . Cuando se selecciona un elemento de ListView , se
ejecuta el controlador de eventos OnListViewItemSelected . Este método navega hasta NoteEntryPage , y
establece el elemento BindingContext de NoteEntryPage en la instancia de Note seleccionada. Para
obtener más información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de
:::no-loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NotesPage.xaml.cs y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

13. En el Explorador de soluciones , haga doble clic en App.xaml.cs para abrirlo. Después, reemplace el
código existente con el siguiente:

using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;

namespace Notes
{
public partial class App : Application
{
public static string FolderPath { get; private set; }

public App()
{
InitializeComponent();
FolderPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
MainPage = new NavigationPage(new NotesPage());
}
// ...
}
}

Este código agrega una declaración de espacio de nombres para el espacio de nombres System.IO y una
declaración para una propiedad FolderPath estática de tipo string . La propiedad FolderPath se usa para
almacenar la ruta de acceso en el dispositivo en el que se almacenarán los datos de la nota. Además, el
código inicializa la propiedad FolderPath en el constructor App e inicializa la propiedad MainPage para
que sea un elemento NavigationPage que hospede una instancia de NotesPage . Para obtener más
información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Guarde los cambios en App.xaml.cs presionando CTRL+S y cierre el archivo.
14. En el Explorador de soluciones , en el proyecto Notes , haga clic con el botón derecho en
MainPage.xaml y seleccione Eliminar . En el cuadro de diálogo que aparece, presione el botón Aceptar
para quitar el archivo del disco duro.
Esto quita una página que ya no se usa.
15. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. Después de guardar
la nota, la aplicación volverá a NotesPage .
Escriba un número de notas, de longitud variable, para observar el comportamiento de la aplicación.
Actualizar la aplicación con Visual Studio para Mac
1. Inicie Visual Studio para Mac. En la ventana de inicio, haga clic en Abrir y, en el cuadro de diálogo,
seleccione el archivo de solución para el proyecto Notes:

2. En el Panel de solución , seleccione el proyecto Notes , haga clic con el botón derecho y seleccione
Agregar > Nueva carpeta :

3. En el Panel de solución , asigne el nombre Modelos a la nueva carpeta:


4. En el Panel de solución , seleccione la carpeta Modelos , haga clic con el botón derecho y seleccione
Agregar > Nuevo archivo... :

5. En el cuadro de diálogo Nuevo archivo , seleccione General > Clase vacía , asigne el nombre Note al
nuevo archivo y haga clic en el botón Nuevo :

Esto agregará una clase denominada Note a la carpeta Modelos del proyecto Notes .
6. En Note.cs , quite todo el código de plantilla y sustitúyalo por el siguiente:
using System;

namespace Notes.Models
{
public class Note
{
public string Filename { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
}

Esta clase define un modelo Note que almacenará los datos sobre cada nota en la aplicación.
Para guardar los cambios en Note.cs , seleccione Archivo > Guardar (o bien presione ⌘ + S ) y cierre el
archivo.
7. En el Panel de solución , seleccione el proyecto Notes , haga clic con el botón derecho y seleccione
Agregar > Nuevo archivo . En el cuadro de diálogo Nuevo archivo , seleccione Formularios > XAML
ContentPage de Forms , asigne el nombre NoteEntr yPage al nuevo archivo, y haga clic en el botón
Nuevo :

Esto agregará una página nueva denominada NoteEntr yPage a la carpeta raíz del proyecto. Esta página
será la segunda de la aplicación.
8. En NoteEntr yPage.xaml , quite todo el código de plantilla y reemplácelo por el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
<StackLayout Margin="20">
<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked"/>
</Grid>
</StackLayout>
</ContentPage>

Este código define mediante declaración la interfaz de usuario para la página, que consta de una instancia
de Editor para la entrada de texto y dos instancias de Button que dirigen la aplicación para guardar o
eliminar un archivo. Las dos instancias de Button se disponen horizontalmente en Grid , mientras que
Editor y Grid se disponen en vertical en StackLayout . Además, Editor usa el enlace de datos para
enlazar con la propiedad Text del modelo Note . Para obtener más información sobre el enlace de datos,
vea Enlace de datos en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Para guardar los cambios en NoteEntr yPage.xaml , seleccione Archivo > Guardar (o bien presione ⌘
+ S ) y cierre el archivo.
9. En NoteEntr yPage.xaml.cs , quite todo el código de plantilla y reemplácelo por el siguiente:
using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;
using Notes.Models;

namespace Notes
{
public partial class NoteEntryPage : ContentPage
{
public NoteEntryPage()
{
InitializeComponent();
}

async void OnSaveButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;

if (string.IsNullOrWhiteSpace(note.Filename))
{
// Save
var filename = Path.Combine(App.FolderPath, $"{Path.GetRandomFileName()}.notes.txt");
File.WriteAllText(filename, note.Text);
}
else
{
// Update
File.WriteAllText(note.Filename, note.Text);
}

await Navigation.PopAsync();
}

async void OnDeleteButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;

if (File.Exists(note.Filename))
{
File.Delete(note.Filename);
}

await Navigation.PopAsync();
}
}
}

Este código almacena una instancia de Note , que representa una única nota, en el elemento
BindingContext de la página. Cuando se presiona el objeto Button Guardar se ejecuta el controlador de
eventos OnSaveButtonClicked , que guarda el contenido de Editor en un archivo nuevo con un nombre de
archivo generado de forma aleatoria, o bien en un archivo existente si se va a actualizar una nota. En ambos
casos, el archivo se almacena en la carpeta de datos de la aplicación local. Después, el método regresa a la
página anterior. Al pulsar el objeto Button Eliminar , se ejecuta el controlador de eventos
OnDeleteButtonClicked , que elimina el archivo, siempre que exista, y regresa a la página anterior. Para
obtener más información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de
:::no-loc(Xamarin.Forms):::.
Para guardar los cambios en NoteEntr yPage.xaml.cs , seleccione Archivo > Guardar (o bien presione
⌘ + S ) y cierre el archivo.
WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

10. En el Panel de solución , seleccione el proyecto Notes , haga clic con el botón derecho y seleccione
Agregar > Nuevo archivo . En el cuadro de diálogo Nuevo archivo , seleccione Formularios > XAML
ContentPage de Forms , asigne el nombre NotesPage al nuevo archivo y haga clic en el botón Nuevo .
Esto agregará una página nueva denominada NotesPage a la carpeta raíz del proyecto. Esta página será la
página raíz de la aplicación.
11. En NotesPage.xaml , quite todo el código de plantilla y sustitúyalo por el siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NotesPage"
Title="Notes">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnNoteAddedClicked" />
</ContentPage.ToolbarItems>
<ListView x:Name="listView"
Margin="20"
ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

Este código define mediante declaración la interfaz de usuario de la página, que consiste en los objetos
ListView y ToolbarItem . En ListView se usa el enlace de datos para mostrar las notas recuperadas por la
aplicación y, al seleccionar una nota, se navegará a NoteEntryPage donde la nota se puede modificar. Como
alternativa, se puede presionar el objeto ToolbarItem para crear una nota. Para obtener más información
sobre el enlace de datos, vea Enlace de datos en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Para guardar los cambios en NotesPage.xaml , seleccione Archivo > Guardar (o bien presione ⌘ + S )
y cierre el archivo.
12. En NotesPage.xaml.cs , quite todo el código de plantilla y sustitúyalo por el siguiente:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using :::no-loc(Xamarin.Forms):::;
using Notes.Models;

namespace Notes
{
public partial class NotesPage : ContentPage
{
public NotesPage()
{
InitializeComponent();
}

protected override void OnAppearing()


{
base.OnAppearing();

var notes = new List<Note>();

var files = Directory.EnumerateFiles(App.FolderPath, "*.notes.txt");


foreach (var filename in files)
{
notes.Add(new Note
{
Filename = filename,
Text = File.ReadAllText(filename),
Date = File.GetCreationTime(filename)
});
}

listView.ItemsSource = notes
.OrderBy(d => d.Date)
.ToList();
}

async void OnNoteAddedClicked(object sender, EventArgs e)


{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = new Note()
});
}

async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)


{
if (e.SelectedItem != null)
{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = e.SelectedItem as Note
});
}
}
}
}

Este código define la funcionalidad de NotesPage . Cuando aparece la página, se ejecuta el método
OnAppearing , que rellena el objeto ListView con las notas que se han recuperado de la carpeta de datos de
la aplicación local. Cuando se presiona ToolbarItem , se ejecuta el controlador de eventos
OnNoteAddedClicked . Este método navega hasta NoteEntryPage , y establece el elemento BindingContext de
NoteEntryPage en una instancia nueva de Note . Cuando se selecciona un elemento de ListView , se
ejecuta el controlador de eventos OnListViewItemSelected . Este método navega hasta NoteEntryPage , y
establece el elemento BindingContext de NoteEntryPage en la instancia de Note seleccionada. Para
obtener más información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de
:::no-loc(Xamarin.Forms):::.
Para guardar los cambios en NotesPage.xaml.cs , seleccione Archivo > Guardar (o bien presione ⌘ +
S ) y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

13. En el Panel de solución , haga doble clic en App.xaml.cs para abrirlo. Después, reemplace el código
existente con el siguiente:

using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;

namespace Notes
{
public partial class App : Application
{
public static string FolderPath { get; private set; }

public App()
{
InitializeComponent();
FolderPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
MainPage = new NavigationPage(new NotesPage());
}
// ...
}
}

Este código agrega una declaración de espacio de nombres para el espacio de nombres System.IO y una
declaración para una propiedad FolderPath estática de tipo string . La propiedad FolderPath se usa para
almacenar la ruta de acceso en el dispositivo en el que se almacenarán los datos de la nota. Además, el
código inicializa la propiedad FolderPath en el constructor App e inicializa la propiedad MainPage para
que sea un elemento NavigationPage que hospede una instancia de NotesPage . Para obtener más
información sobre la navegación, vea Navegación en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Guarde los cambios en App.xaml.cs eligiendo Archivo > Guardar (o presionando ⌘ + S ) y cierre el
archivo.
14. En el Panel de solución , en el proyecto Notes , haga clic con el botón derecho en MainPage.xaml y
seleccione Quitar . En el cuadro de diálogo que aparece, presione el botón Eliminar para quitar el archivo
del disco duro.
Esto quita una página que ya no se usa.
15. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. Después de guardar
la nota, la aplicación volverá a NotesPage .
Escriba un número de notas, de longitud variable, para observar el comportamiento de la aplicación.
Pasos siguientes
En este inicio rápido ha aprendido a:
Agregar páginas adicionales a una solución :::no-loc(Xamarin.Forms):::.
Realizar la navegación entre las páginas.
Usar el enlace de datos para sincronizar datos entre los elementos de la interfaz de usuario y su origen de
datos.
Para modificar la aplicación de modo que almacene sus datos en una base de datos de SQLite.NET local, continúe
con el inicio rápido siguiente.
Siguiente

Vínculos relacionados
Notes (ejemplo)
Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::
Almacenamiento de datos en una base de datos de
SQLite.NET local
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
En este inicio rápido aprenderá a:
Usar el Administrador de paquetes de NuGet para agregar un paquete NuGet a un proyecto.
Almacenar datos de forma local en una base de datos de SQLite.NET.
En el inicio rápido se le guía sobre cómo almacenar datos en una base de datos de SQLite.NET local. A
continuación se muestra la aplicación final:

Requisitos previos
Antes de intentar este inicio rápido, debe completar correctamente el inicio rápido anterior. También puede
descargar el ejemplo del inicio rápido anterior y usarlo como punto de partida para este.

Actualizar la aplicación con Visual Studio


1. Inicie Visual Studio y abra la solución Notes.
2. En el Explorador de soluciones , seleccione el proyecto Notes , haga clic con el botón derecho y,
después, seleccione Administrar paquetes NuGet… :
3. En el Administrador de paquetes NuGet , seleccione la pestaña Examinar , busque el paquete NuGet
sqlite-net-pcl , selecciónelo y haga clic en el botón Instalar para agregarlo al proyecto:

NOTE
Hay varios paquetes NuGet con nombres similares. El paquete correcto tiene estos atributos:
Propietarios: praeclarum
Autores: SQLite-net
Vínculo de NuGet: sqlite-net-pcl
A pesar del nombre del paquete, este paquete NuGet puede usarse en proyectos de .NET Standard.

Este paquete se usará para incorporar operaciones de bases de datos en la aplicación.


4. En el Explorador de soluciones , en el proyecto Notes , abra Note.cs en la carpeta Modelos y
reemplace el código existente por el siguiente:
using System;
using SQLite;

namespace Notes.Models
{
public class Note
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
}

Esta clase define un modelo Note que almacenará los datos sobre cada nota en la aplicación. La propiedad
ID está marcada con los atributos PrimaryKey y AutoIncrement para garantizar que cada instancia de
Note en la base de datos de SQLite.NET tenga un identificador único proporcionado por SQLite.NET.

Presione CTRL+S para guardar los cambios en Note.cs y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

5. En el Explorador de soluciones , agregue una nueva carpeta denominada Datos al proyecto Notes .
6. En el Explorador de soluciones , en el proyecto Notes , agregue una nueva clase denominada
NoteDatabase a la carpeta Datos .
7. En NoteDatabase.cs , reemplace el código existente con el siguiente:
using System.Collections.Generic;
using System.Threading.Tasks;
using SQLite;
using Notes.Models;

namespace Notes.Data
{
public class NoteDatabase
{
readonly SQLiteAsyncConnection _database;

public NoteDatabase(string dbPath)


{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync<Note>().Wait();
}

public Task<List<Note>> GetNotesAsync()


{
return _database.Table<Note>().ToListAsync();
}

public Task<Note> GetNoteAsync(int id)


{
return _database.Table<Note>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}

public Task<int> SaveNoteAsync(Note note)


{
if (note.ID != 0)
{
return _database.UpdateAsync(note);
}
else
{
return _database.InsertAsync(note);
}
}

public Task<int> DeleteNoteAsync(Note note)


{
return _database.DeleteAsync(note);
}
}
}

Esta clase contiene código para crear la base de datos, leer datos de ella, escribir datos en ella y eliminarlos.
El código usa API asincrónicas de SQLite.NET que mueven las operaciones de base de datos a subprocesos
en segundo plano. Además, el constructor NoteDatabase toma la ruta de acceso del archivo de base de
datos como un argumento. La clase App proporcionará esta ruta de acceso en el paso siguiente.
Presione CTRL+S para guardar los cambios en NoteDatabase.cs y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

8. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en App.xaml.cs para abrirlo.
Después, reemplace el código existente con el siguiente:
using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;
using Notes.Data;

namespace Notes
{
public partial class App : Application
{
static NoteDatabase database;

public static NoteDatabase Database


{
get
{
if (database == null)
{
database = new
NoteDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Notes.db3"));
}
return database;
}
}

public App()
{
InitializeComponent();
MainPage = new NavigationPage(new NotesPage());
}

protected override void OnStart()


{
// Handle when your app starts
}

protected override void OnSleep()


{
// Handle when your app sleeps
}

protected override void OnResume()


{
// Handle when your app resumes
}
}
}

Este código define una propiedad Database que crea una instancia de NoteDatabase como singleton, y pasa
el nombre de archivo de la base de datos como argumento al constructor NoteDatabase . La ventaja de
exponer la base de datos como un singleton es que se crea una conexión de base de datos única que se
mantiene abierta mientras la aplicación se ejecuta, lo que evita el gasto de abrir y cerrar el archivo de base
de datos cada vez que se realiza una operación de base de datos.
Guarde los cambios en App.xaml.cs presionando CTRL+S y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

9. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en NotesPage.xaml.cs para


abrirlo. Después, reemplace el método OnAppearing con el código siguiente:
protected override async void OnAppearing()
{
base.OnAppearing();

listView.ItemsSource = await App.Database.GetNotesAsync();


}

Este código rellena el elemento ListView con todas las notas almacenadas en la base de datos.
Presione CTRL+S para guardar los cambios en NotesPage.xaml.cs y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

10. En el Explorador de soluciones , haga doble clic en NoteEntr yPage.xaml.cs para abrirlo. Después,
reemplace los métodos OnSaveButtonClicked y OnDeleteButtonClicked con el código siguiente:

async void OnSaveButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;
note.Date = DateTime.UtcNow;
await App.Database.SaveNoteAsync(note);
await Navigation.PopAsync();
}

async void OnDeleteButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;
await App.Database.DeleteNoteAsync(note);
await Navigation.PopAsync();
}

NoteEntryPage almacena una instancia de Note , que representa una única nota, en el objeto
BindingContext de la página. Cuando se ejecuta el controlador de eventos OnSaveButtonClicked , la instancia
de Note se guarda en la base de datos y la aplicación regresa a la página anterior. Cuando se ejecuta el
controlador de eventos OnDeleteButtonClicked , la instancia de Note se elimina de la base de datos y la
aplicación regresa a la página anterior.
Presione CTRL+S para guardar los cambios en NoteEntr yPage.xaml.cs y cierre el archivo.
11. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. Después de guardar
la nota, la aplicación volverá a NotesPage .
Escriba un número de notas, de longitud variable, para observar el comportamiento de la aplicación.

Actualizar la aplicación con Visual Studio para Mac


1. Inicie Visual Studio para Mac y abra el proyecto Notes.
2. En el Panel de solución , seleccione el proyecto Notes , haga clic con el botón derecho y seleccione
Agregar > Agregar paquetes NuGet... :
3. En la ventana Agregar paquetes , busque el paquete NuGet sqlite-net-pcl , selecciónelo y haga clic en el
botón Agregar paquete para agregarlo al proyecto:

NOTE
Hay varios paquetes NuGet con nombres similares. El paquete correcto tiene estos atributos:
Propietarios: praeclarum
Autores: SQLite-net
Vínculo de NuGet: sqlite-net-pcl
A pesar del nombre del paquete, este paquete NuGet puede usarse en proyectos de .NET Standard.

Este paquete se usará para incorporar operaciones de bases de datos en la aplicación.


4. En el Panel de solución , en el proyecto Notes , abra Note.cs en la carpeta Modelos y reemplace el
código existente por el siguiente:
using System;
using SQLite;

namespace Notes.Models
{
public class Note
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
}

Esta clase define un modelo Note que almacenará los datos sobre cada nota en la aplicación. La propiedad
ID está marcada con los atributos PrimaryKey y AutoIncrement para garantizar que cada instancia de
Note en la base de datos de SQLite.NET tenga un identificador único proporcionado por SQLite.NET.

Para guardar los cambios en Note.cs , seleccione Archivo > Guardar (o bien presione ⌘ + S ) y cierre el
archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

5. En el Panel de solución , agregue una nueva carpeta denominada Datos al proyecto Notes .
6. En el Panel de solución , en el proyecto Notes , agregue una nueva clase denominada NoteDatabase a
la carpeta Datos .
7. En NoteDatabase.cs , reemplace el código existente con el siguiente:
using System.Collections.Generic;
using System.Threading.Tasks;
using SQLite;
using Notes.Models;

namespace Notes.Data
{
public class NoteDatabase
{
readonly SQLiteAsyncConnection _database;

public NoteDatabase(string dbPath)


{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync<Note>().Wait();
}

public Task<List<Note>> GetNotesAsync()


{
return _database.Table<Note>().ToListAsync();
}

public Task<Note> GetNoteAsync(int id)


{
return _database.Table<Note>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}

public Task<int> SaveNoteAsync(Note note)


{
if (note.ID != 0)
{
return _database.UpdateAsync(note);
}
else
{
return _database.InsertAsync(note);
}
}

public Task<int> DeleteNoteAsync(Note note)


{
return _database.DeleteAsync(note);
}
}
}

Esta clase contiene código para crear la base de datos, leer datos de ella, escribir datos en ella y eliminarlos.
El código usa API asincrónicas de SQLite.NET que mueven las operaciones de base de datos a subprocesos
en segundo plano. Además, el constructor NoteDatabase toma la ruta de acceso del archivo de base de
datos como un argumento. La clase App proporcionará esta ruta de acceso en el paso siguiente.
Para guardar los cambios en NoteDatabase.cs , seleccione Archivo > Guardar (o bien presione ⌘ + S )
y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

8. En el Panel de solución , en el proyecto Notes , haga doble clic en App.xaml.cs para abrirlo. Después,
reemplace el código existente con el siguiente:
using System;
using System.IO;
using :::no-loc(Xamarin.Forms):::;
using Notes.Data;

namespace Notes
{
public partial class App : Application
{
static NoteDatabase database;

public static NoteDatabase Database


{
get
{
if (database == null)
{
database = new
NoteDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Notes.db3"));
}
return database;
}
}

public App()
{
InitializeComponent();
MainPage = new NavigationPage(new NotesPage());
}

protected override void OnStart()


{
// Handle when your app starts
}

protected override void OnSleep()


{
// Handle when your app sleeps
}

protected override void OnResume()


{
// Handle when your app resumes
}
}
}

Este código define una propiedad Database que crea una instancia de NoteDatabase como singleton, y pasa
el nombre de archivo de la base de datos como argumento al constructor NoteDatabase . La ventaja de
exponer la base de datos como un singleton es que se crea una conexión de base de datos única que se
mantiene abierta mientras la aplicación se ejecuta, lo que evita el gasto de abrir y cerrar el archivo de base
de datos cada vez que se realiza una operación de base de datos.
Guarde los cambios en App.xaml.cs eligiendo Archivo > Guardar (o presionando ⌘ + S ) y cierre el
archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

9. En el Panel de solución , en el proyecto Notes , haga doble clic en NotesPage.xaml.cs para abrirlo.
Después, reemplace el método OnAppearing con el código siguiente:

protected override async void OnAppearing()


{
base.OnAppearing();

listView.ItemsSource = await App.Database.GetNotesAsync();


}

Este código rellena el elemento ListView con todas las notas almacenadas en la base de datos.
Para guardar los cambios en NotesPage.xaml.cs , seleccione Archivo > Guardar (o bien presione ⌘ +
S ) y cierre el archivo.

WARNING
Si en este momento intenta compilar la aplicación, se producirán errores que se corregirán en pasos posteriores.

10. En el Panel de solución , haga doble clic en NoteEntr yPage.xaml.cs para abrirlo. Después, reemplace
los métodos OnSaveButtonClicked y OnDeleteButtonClicked con el código siguiente:

async void OnSaveButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;
note.Date = DateTime.UtcNow;
await App.Database.SaveNoteAsync(note);
await Navigation.PopAsync();
}

async void OnDeleteButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;
await App.Database.DeleteNoteAsync(note);
await Navigation.PopAsync();
}

NoteEntryPage almacena una instancia de Note , que representa una única nota, en el objeto
BindingContext de la página. Cuando se ejecuta el controlador de eventos OnSaveButtonClicked , la instancia
de Note se guarda en la base de datos y la aplicación regresa a la página anterior. Cuando se ejecuta el
controlador de eventos OnDeleteButtonClicked , la instancia de Note se elimina de la base de datos y la
aplicación regresa a la página anterior.
Para guardar los cambios en NoteEntr yPage.xaml.cs , seleccione Archivo > Guardar (o bien presione
⌘ + S ) y cierre el archivo.

11. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. Después de guardar
la nota, la aplicación volverá a NotesPage .
Escriba un número de notas, de longitud variable, para observar el comportamiento de la aplicación.

Pasos siguientes
En este inicio rápido ha aprendido a:
Usar el Administrador de paquetes de NuGet para agregar un paquete NuGet a un proyecto.
Almacenar datos de forma local en una base de datos de SQLite.NET.
Para aplicar estilo a la aplicación con estilos XAML, continúe con el inicio rápido siguiente.
Siguiente

Vínculos relacionados
Notes (ejemplo)
Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::
Estilo de una aplicación de :::no-loc(Xamarin.Forms):::
multiplataforma
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
En este inicio rápido aprenderá a:
Aplicar estilo a una aplicación :::no-loc(Xamarin.Forms)::: mediante estilos XAML.
En el inicio rápido se describe cómo aplicar estilo a una aplicación :::no-loc(Xamarin.Forms)::: multiplataforma con
estilos XAML. A continuación se muestra la aplicación final:

Requisitos previos
Antes de intentar este inicio rápido, debe completar correctamente el inicio rápido anterior. También puede
descargar el ejemplo del inicio rápido anterior y usarlo como punto de partida para este.

Actualizar la aplicación con Visual Studio


1. Inicie Visual Studio y abra la solución Notes.
2. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en App.xaml para abrirlo.
Después, reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.App">
<Application.Resources>

<Thickness x:Key="PageMargin">20</Thickness>

<!-- Colors -->


<Color x:Key="AppBackgroundColor">AliceBlue</Color>
<Color x:Key="NavigationBarColor">#1976D2</Color>
<Color x:Key="NavigationBarTextColor">White</Color>

<!-- Implicit styles -->


<Style TargetType="{x:Type NavigationPage}">
<Setter Property="BarBackgroundColor"
Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor"
Value="{StaticResource NavigationBarTextColor}" />
</Style>

<Style TargetType="{x:Type ContentPage}"


ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>

</Application.Resources>
</Application>

En este código se define un valor Thickness , una serie de valores Color y estilos implícitos para los
objetos NavigationPage y ContentPage . Tenga en cuenta que estos estilos, que están en el objeto
ResourceDictionary de nivel de aplicación, se pueden consumir en toda la aplicación. Para obtener más
información sobre los estilos XAML, vea Aplicación de estilos en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en App.xaml y cierre el archivo.
3. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en NotesPage.xaml para abrirlo.
Después, reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NotesPage"
Title="Notes">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type ListView}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>
</ContentPage.Resources>

<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnNoteAddedClicked" />
</ContentPage.ToolbarItems>

<ListView x:Name="listView"
Margin="{StaticResource PageMargin}"
ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
TextColor="Black"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

</ContentPage>

Este código agrega un estilo implícito para ListView al objeto ResourceDictionary de nivel de página, y
establece la propiedad ListView.Margin en un valor definido en el objeto ResourceDictionary de nivel de la
aplicación. Tenga en cuenta que el estilo implícito ListView se ha agregado al objeto ResourceDictionary
de nivel de página, porque solo lo usa NotesPage . Para obtener más información sobre los estilos XAML,
vea Aplicación de estilos en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NotesPage.xaml y cierre el archivo.
4. En el Explorador de soluciones , en el proyecto Notes , haga doble clic en NoteEntr yPage.xaml para
abrirlo. Después, reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type Editor}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>

<Style TargetType="Button"
ApplyToDerivedTypes="True"
CanCascade="True">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="BackgroundColor" Value="#1976D2" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
</ContentPage.Resources>

<StackLayout Margin="{StaticResource PageMargin}">


<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked" />
</Grid>
</StackLayout>

</ContentPage>

Este código agrega estilos implícitos para las vistas Editor y Button al objeto ResourceDictionary de nivel
de página, y establece la propiedad StackLayout.Margin en un valor definido en el objeto
ResourceDictionary de nivel de la aplicación. Tenga en cuenta que los estilos implícitos Editor y Button
sen ha agregado al objeto ResourceDictionary de nivel de página, porque solo los usa NoteEntryPage . Para
obtener más información sobre los estilos XAML, vea Aplicación de estilos en Análisis detallado de inicio
rápido de :::no-loc(Xamarin.Forms):::.
Presione CTRL+S para guardar los cambios en NoteEntr yPage.xaml y cierre el archivo.
5. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. En cada página,
observe cómo ha cambiado el estilo con respecto al inicio rápido anterior.

Actualizar la aplicación con Visual Studio para Mac


1. Inicie Visual Studio para Mac y abra el proyecto Notes.
2. En el Panel de solución , en el proyecto Notes , haga doble clic en App.xaml para abrirlo. Después,
reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.App">
<Application.Resources>

<Thickness x:Key="PageMargin">20</Thickness>

<!-- Colors -->


<Color x:Key="AppBackgroundColor">AliceBlue</Color>
<Color x:Key="NavigationBarColor">#1976D2</Color>
<Color x:Key="NavigationBarTextColor">White</Color>

<!-- Implicit styles -->


<Style TargetType="{x:Type NavigationPage}">
<Setter Property="BarBackgroundColor"
Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor"
Value="{StaticResource NavigationBarTextColor}" />
</Style>

<Style TargetType="{x:Type ContentPage}"


ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>

</Application.Resources>
</Application>

En este código se define un valor Thickness , una serie de valores Color y estilos implícitos para los
objetos NavigationPage y ContentPage . Tenga en cuenta que estos estilos, que están en el objeto
ResourceDictionary de nivel de aplicación, se pueden consumir en toda la aplicación. Para obtener más
información sobre los estilos XAML, vea Aplicación de estilos en Análisis detallado de inicio rápido de :::no-
loc(Xamarin.Forms):::.
Para guardar los cambios en App.xaml , seleccione Archivo > Guardar (o presione ⌘ + S ) y cierre el
archivo.
3. En el Panel de solución , en el proyecto Notes , haga doble clic en NotesPage.xaml para abrirlo.
Después, reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NotesPage"
Title="Notes">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type ListView}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>
</ContentPage.Resources>

<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnNoteAddedClicked" />
</ContentPage.ToolbarItems>

<ListView x:Name="listView"
Margin="{StaticResource PageMargin}"
ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
TextColor="Black"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

</ContentPage>

Este código agrega un estilo implícito para ListView al objeto ResourceDictionary de nivel de página, y
establece la propiedad ListView.Margin en un valor definido en el objeto ResourceDictionary de nivel de la
aplicación. Tenga en cuenta que el estilo implícito ListView se ha agregado al objeto ResourceDictionary
de nivel de página, porque solo lo usa NotesPage . Para obtener más información sobre los estilos XAML,
vea Aplicación de estilos en Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::.
Para guardar los cambios en NotesPage.xaml , seleccione Archivo > Guardar (o bien presione ⌘ + S )
y cierre el archivo.
4. En el Panel de solución , en el proyecto Notes , haga doble clic en NoteEntr yPage.xaml para abrirlo.
Después, reemplace el código existente con el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type Editor}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>

<Style TargetType="Button"
ApplyToDerivedTypes="True"
CanCascade="True">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="BackgroundColor" Value="#1976D2" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
</ContentPage.Resources>

<StackLayout Margin="{StaticResource PageMargin}">


<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
<Button Grid.Column="1"
Text="Delete"
Clicked="OnDeleteButtonClicked" />
</Grid>
</StackLayout>

</ContentPage>

Este código agrega estilos implícitos para las vistas Editor y Button al objeto ResourceDictionary de nivel
de página, y establece la propiedad StackLayout.Margin en un valor definido en el objeto
ResourceDictionary de nivel de la aplicación. Tenga en cuenta que los estilos implícitos Editor y Button
sen ha agregado al objeto ResourceDictionary de nivel de página, porque solo los usa NoteEntryPage . Para
obtener más información sobre los estilos XAML, vea Aplicación de estilos en Análisis detallado de inicio
rápido de :::no-loc(Xamarin.Forms):::.
Para guardar los cambios en NoteEntr yPage.xaml , seleccione Archivo > Guardar (o bien presione ⌘ +
S ) y cierre el archivo.
5. Compile y ejecute el proyecto en cada plataforma. Para más información, vea Compilación del inicio rápido.
En NotesPage presione el botón + para ir hasta NoteEntr yPage y escriba una nota. En cada página,
observe cómo ha cambiado el estilo con respecto al inicio rápido anterior.

Pasos siguientes
En este inicio rápido ha aprendido a:
Aplicar estilo a una aplicación :::no-loc(Xamarin.Forms)::: mediante estilos XAML.
Para obtener más información sobre los aspectos básicos del desarrollo de aplicaciones con :::no-
loc(Xamarin.Forms):::, continúe con el análisis detallado de inicio rápido.
Siguiente

Vínculos relacionados
Notes (ejemplo)
Análisis detallado de inicio rápido de :::no-loc(Xamarin.Forms):::
Análisis detallado de inicio rápido de
Xamarin.Forms
18/12/2020 • 36 minutes to read • Edit Online

En Inicio rápido de Xamarin.Forms se ha creado la aplicación Notes. En este artículo se revisa lo


que se ha compilado para comprender los aspectos fundamentales del funcionamiento de las
aplicaciones de Xamarin.Forms.

Introducción a Visual Studio


Visual Studio organiza el código en soluciones y proyectos. Una solución es un contenedor que
puede incluir uno o varios proyectos. Un proyecto puede ser una aplicación, una biblioteca
auxiliar o una aplicación de prueba, entre otros. La aplicación Notes consta de una solución que
contiene cuatro proyectos, como se muestra en la captura de pantalla siguiente:

Los proyectos son:


Notes: este proyecto es el de la biblioteca de .NET Standard que incluye todo el código
compartido y la interfaz de usuario compartida.
Notes.Android: este proyecto incluye el código específico de Android y es el punto de entrada
de la aplicación Android.
Notes.iOS: este proyecto incluye el código específico de iOS y es el punto de entrada de la
aplicación iOS.
Notes.UWP: este proyecto incluye el código específico de Plataforma universal de Windows
(UWP) y es el punto de entrada de la aplicación para UWP.

Anatomía de una aplicación de Xamarin.Forms


En la captura de pantalla siguiente se muestra el contenido del proyecto de biblioteca de .NET
Standard Notes en Visual Studio:
El proyecto tiene un nodo Dependencias que contiene los nodos NuGet y SDK :
NuGet : paquetes NuGet Xamarin.Forms y sqlite-net-pcl que se han agregado al proyecto.
SDK : el metapaquete NETStandard.Library que hace referencia al conjunto completo de
paquetes NuGet que definen .NET Standard.

Introducción a Visual Studio para Mac


Visual Studio para Mac sigue la práctica de Visual Studio consistente en organizar el código en
soluciones y proyectos. Una solución es un contenedor que puede incluir uno o varios proyectos.
Un proyecto puede ser una aplicación, una biblioteca auxiliar o una aplicación de prueba, entre
otros. La aplicación Notes consta de una solución que contiene tres proyectos, como se muestra
en la captura de pantalla siguiente:

Los proyectos son:


Notes: este proyecto es el de la biblioteca de .NET Standard que incluye todo el código
compartido y la interfaz de usuario compartida.
Notes.Android: este proyecto incluye el código específico de Android y es el punto de entrada
de las aplicaciones Android.
Notes.iOS: este proyecto incluye el código específico de iOS y es el punto de entrada de las
aplicaciones iOS.

Anatomía de una aplicación de Xamarin.Forms


En la captura de pantalla siguiente se muestra el contenido del proyecto de biblioteca de .NET
Standard Notes en Visual Studio para Mac:
El proyecto tiene un nodo Dependencias que contiene los nodos NuGet y SDK :
NuGet : paquetes NuGet Xamarin.Forms y sqlite-net-pcl que se han agregado al proyecto.
SDK : el metapaquete NETStandard.Library que hace referencia al conjunto completo de
paquetes NuGet que definen .NET Standard.
El proyecto también consta de varios archivos:
Data\NoteDatabase.cs : esta clase contiene código para crear la base de datos, leer datos de
ella, escribir datos en ella y eliminarlos.
Models\Note.cs : esta clase define un modelo Note cuyas instancias almacenarán datos
sobre cada nota en la aplicación.
App.xaml : el marcado XAML para la clase App , que define un diccionario de recursos para la
aplicación.
App.xaml.cs : el código subyacente para la clase App , que es el responsable de crear
instancias de la primera página que se mostrarán mediante la aplicación en cada plataforma, y
para controlar los eventos del ciclo de vida de la aplicación.
AssemblyInfo.cs : este archivo contiene un atributo de aplicación sobre el proyecto, que se
aplica en el nivel de ensamblado.
NotesPage.xaml : el marcado XAML para la clase NotesPage , que define la interfaz de usuario
para la página que se muestra al iniciar la aplicación.
NotesPage.xaml.cs : el código subyacente para la clase NotesPage , que contiene la lógica de
negocios que se ejecuta cuando el usuario interactúa con la página.
NoteEntr yPage.xaml : el marcado XAML para la clase NoteEntryPage , que define la interfaz
de usuario para la página que se muestra cuando el usuario escribe una nota.
NoteEntr yPage.xaml.cs : el código subyacente para la clase NoteEntryPage , que contiene la
lógica de negocios que se ejecuta cuando el usuario interactúa con la página.
Para obtener más información sobre la anatomía de una aplicación de Xamarin.iOS, consulte
Anatomía de una aplicación de Xamarin.iOS. Para obtener más información sobre la anatomía de
una aplicación de Xamarin.Android, consulte Anatomía de una aplicación de Xamarin.Android.

Arquitectura y aspectos básicos de la aplicación


Una aplicación de Xamarin.Forms tiene la misma arquitectura que una aplicación
multiplataforma tradicional. El código compartido normalmente se coloca en una biblioteca de
.NET Standard, y las aplicaciones específicas de la plataforma consumen el código compartido. En
el diagrama siguiente se muestra información general de esta relación para la aplicación Notes:

Para maximizar la reutilización del código de inicio, las aplicaciones de Xamarin.Forms tienen una
clase única denominada App que es responsable de crear instancias de la primera página que
mostrará la aplicación en cada plataforma, como se muestra en el siguiente ejemplo de código:
using Xamarin.Forms;

namespace Notes
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new NotesPage());
}
...
}
}

En este código se establece la propiedad MainPage de la clase App en una instancia de


NavigationPage cuyo contenido es una instancia de NotesPage .

Además, el archivo AssemblyInfo.cs contiene un único atributo de aplicación, que se aplica en


el nivel de ensamblado:

using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

El atributo XamlCompilation activa el compilador XAML, para que XAML se compile directamente
en lenguaje intermedio. Para obtener más información, consulte Compilación XAML.

Inicio de la aplicación en cada plataforma


iOS
Para abrir la página inicial de Xamarin.Forms en iOS, en el proyecto Notes.iOS se define la clase
AppDelegate , que hereda de la clase FormsApplicationDelegate :

namespace Notes.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate :
global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
}

El reemplazo FinishedLaunching inicializa el marco de Xamarin.Forms mediante una llamada al


método Init . Esto hace que la implementación específica de iOS de Xamarin.Forms se cargue
en la aplicación antes de que se establezca el controlador de vista raíz mediante la llamada al
método LoadApplication .
Android
Para iniciar la página de inicio de Xamarin.Forms en Android, el proyecto Notes.Android incluye
código para crear un elemento Activity con el atributo MainLauncher , y la actividad se hereda
de la clase FormsAppCompatActivity :

namespace Notes.Droid
{
[Activity(Label = "Notes",
Icon = "@mipmap/icon",
Theme = "@style/MainTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;

base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
}

El reemplazo OnCreate inicializa el marco de Xamarin.Forms mediante una llamada al método


Init . Esto provoca que la implementación específica de Android de Xamarin.Forms se cargue en
la aplicación antes de que lo haga la aplicación de Xamarin.Forms.
Plataforma universal de Windows
En las aplicaciones de Plataforma universal de Windows (UWP), el método Init que inicializa el
marco de Xamarin.Forms se invoca desde la clase App :

Xamarin.Forms.Forms.Init (e);

if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
...
}

Esto hace que la implementación específica de UWP de Xamarin.Forms se cargue en la aplicación.


La página inicial de Xamarin.Forms se inicia mediante la clase MainPage :

namespace Notes.UWP
{
public sealed partial class MainPage
{
public MainPage()
{
this.InitializeComponent();
this.LoadApplication(new Notes.App());
}
}
}

La aplicación de Xamarin.Forms se carga con el método LoadApplication .


NOTE
Las aplicaciones para la Plataforma universal de Windows se pueden compilar con Xamarin.Forms, pero
solo mediante Visual Studio en Windows.

Interfaz de usuario
Existen cuatro grupos de controles principales que se usan para crear la interfaz de usuario de
una aplicación Xamarin.Forms:
1. Páginas : las páginas de Xamarin.Forms representan pantallas de aplicaciones móviles
multiplataforma. En la aplicación Notes se usa la clase ContentPage para mostrar pantallas
únicas. Para obtener más información sobre las páginas de códigos, vea Páginas de
Xamarin.Forms.
2. Vistas : las vistas de Xamarin.Forms son los controles que se muestran en la interfaz de
usuario, como etiquetas, botones y cuadros de entrada de texto. En la aplicación Notes
terminada se usan las vistas ListView , Editor y Button . Para obtener más información
sobre las vistas, consulte Vistas de Xamarin.Forms.
3. Diseños : los diseños de Xamarin.Forms son contenedores que se usan para crear vistas en
estructuras lógicas. En la aplicación Notes se usa la clase StackLayout para organizar las
vistas en una pila vertical y la clase Grid para organizar los botones de forma horizontal.
Para obtener más información sobre los diseños, consulte Diseños de Xamarin.Forms.
4. Celdas : las celdas de Xamarin.Forms son elementos especializados que se usan para los
elementos de una lista, y describen cómo debe dibujarse cada elemento de una lista. En la
aplicación Notes se usa TextCell para mostrar dos elementos para cada fila de la lista. Para
obtener más información sobre las celdas, consulte Celdas de Xamarin.Forms.
En tiempo de ejecución, cada control se asignará a su equivalente nativo, que es lo que se
representará.
Diseño
En la aplicación Notes se usa StackLayout para simplificar el desarrollo de aplicaciones
multiplataforma mediante la disposición automática de las vistas en la pantalla,
independientemente del tamaño de esta. Cada elemento secundario se coloca uno detrás del
otro, ya sea horizontal o verticalmente, en el orden en el que se ha agregado. La cantidad de
espacio que usará la clase StackLayout depende de cómo se establezcan las propiedades
HorizontalOptions y VerticalOptions , pero StackLayout intentará usar toda la pantalla de
forma predeterminada.
En el código XAML siguiente se muestra un ejemplo de uso de una clase StackLayout para
organizar el control NoteEntryPage :
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
...
<StackLayout Margin="{StaticResource PageMargin}">
<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
...
</Grid>
</StackLayout>
</ContentPage>

De forma predeterminada, StackLayout asume una orientación vertical. Pero se puede cambiar a
una orientación horizontal si se establece la propiedad StackLayout.Orientation en el miembro
de enumeración StackOrientation.Horizontal .

NOTE
El tamaño de las vistas se puede establecer a través de las propiedades HeightRequest y
WidthRequest .

Para obtener más información sobre la clase StackLayout , consulte StackLayout.


Responder a la interacción del usuario
Un objeto que se ha definido en XAML puede desencadenar un evento que se controla en el
archivo de código subyacente. En el ejemplo de código siguiente se muestra el método
OnSaveButtonClicked del código subyacente de la clase NoteEntryPage , que se ejecuta en
respuesta al evento Clicked que se desencadena en el botón Guardar.

async void OnSaveButtonClicked(object sender, EventArgs e)


{
var note = (Note)BindingContext;
note.Date = DateTime.UtcNow;
await App.Database.SaveNoteAsync(note);
await Navigation.PopAsync();
}

El método OnSaveButtonClicked guarda la nota en la base de datos y regresa a la página anterior.

NOTE
El archivo de código subyacente de una clase XAML puede tener acceso a un objeto que se ha definido
en XAML con el nombre asignado a él con el atributo x:Name . El valor que se ha asignado a este
atributo tiene las mismas reglas que las variables de C#, ya que debe comenzar con una letra o guion
bajo y no contener espacios incrustados.

La conexión del botón Guardar con el método OnSaveButtonClicked se realiza en el marcado


XAML de la clase NoteEntryPage :
<Button Text="Save"
Clicked="OnSaveButtonClicked" />

Listas
ListView es responsable de mostrar una colección de elementos de forma vertical en una lista.
Cada elemento de ListView se incluirá en una sola celda.
En el ejemplo de código siguiente se muestra el elemento ListView de NotesPage :

<ListView x:Name="listView"
Margin="{StaticResource PageMargin}"
ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

El diseño de cada fila de ListView se define dentro del elemento ListView.ItemTemplate y se usa
el enlace de datos para mostrar las notas recuperadas por la aplicación. La propiedad
ListView.ItemsSource está establecida en el origen de datos, en NotesPage.xaml.cs :

protected override async void OnAppearing()


{
base.OnAppearing();

listView.ItemsSource = await App.Database.GetNotesAsync();


}

Este código rellena el elemento ListView con todas las notas almacenadas en la base de datos.
Cuando se selecciona una fila en ListView , se desencadena el evento ItemSelected . Cuando se
desencadena el evento, se ejecuta un controlador de eventos denominado
OnListViewItemSelected :

async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)


{
if (e.SelectedItem != null)
{
...
}
}

El evento ItemSelected puede acceder al objeto asociado a la celda a través de la propiedad


e.SelectedItem .
Para más información sobre la clase ListView , vea ListView.

Navegación
Xamarin.Forms proporciona una serie de experiencias de navegación de páginas diferente, en
función del tipo Page que se use. Para las instancias de ContentPage , la navegación puede ser
jerárquica o modal. Para obtener información sobre la navegación modal, consulte Páginas
modales de Xamarin.Forms.
NOTE
Las clases CarouselPage , MasterDetailPage y TabbedPage proporcionan experiencias de navegación
alternativas. Para obtener más información, consulte Navigation (Navegación).

En la navegación jerárquica, se usa la clase NavigationPage para navegar por una pila de objetos
ContentPage , hacia delante y atrás, como se prefiera. La clase implementa la navegación como
una pila de objetos Page en la que el último en entrar es el primero en salir (LIFO). Para pasar de
una página a otra, una aplicación insertará una nueva página en la pila de navegación, donde se
convertirá en la página activa. Para volver a la página anterior, la aplicación mostrará la página
actual de la pila de navegación y la nueva página de nivel superior se convertirá en la página
activa.
La clase NavigationPage también agregará una barra de navegación en la parte superior de la
página que muestra un título y un botón Atrás adecuado para la plataforma para volver a la
página anterior.
La primera página que se agrega a una pila de navegación se denomina página raíz de la
aplicación y, en el ejemplo de código siguiente, se muestra cómo se consigue esto en la
aplicación Notes:

public App ()
{
...
MainPage = new NavigationPage (new NotesPage ());
}

Todas las instancias ContentPage tienen una propiedad Navigation que expone métodos para
modificar la pila de la página. Solo se deberían invocar estos métodos si la aplicación incluye una
NavigationPage . Para navegar a la NoteEntryPage , es necesario invocar el método PushAsync
como se muestra en el ejemplo de código siguiente:

await Navigation.PushAsync(new NoteEntryPage());

Esto hace que el nuevo objeto NoteEntryPage se inserte en la pila de navegación, donde se
convierte en la página activa.
La página activa se puede extraer de la pila de navegación. Para ello, pulse el botón Atrás del
dispositivo, independientemente de si se trata de un botón físico en el dispositivo o de un botón
en la pantalla. Para volver mediante programación a la página original, el objeto NoteEntryPage
debe invocar el método PopAsync , como se muestra en el ejemplo de código siguiente:

await Navigation.PopAsync();

Para obtener más información sobre la navegación jerárquica, consulte Hierarchical Navigation
(Navegación jerárquica).

Enlace de datos
El enlace de datos se usa para simplificar la forma en que una aplicación de Xamarin.Forms
muestra sus datos e interactúa con ellos. Establece una conexión entre la interfaz de usuario y la
aplicación subyacente. La clase BindableObject contiene gran parte de la infraestructura para
admitir el enlace de datos.
El enlace de datos conecta dos objetos, denominados origen y destino. El objeto de origen
proporciona los datos. El objeto de destino usa (y, a menudo, muestra) los datos del objeto de
origen. Por ejemplo, un control Editor (objeto de destino) normalmente enlazará su propiedad
Text a una propiedad string pública en un objeto de origen. En el diagrama siguiente se
muestra la relación de enlace:

El principal beneficio del enlace de datos es que ya no tiene que preocuparse de sincronizar los
datos entre las vistas y el origen de datos. Los cambios en el objeto de origen se insertan
automáticamente en el objeto de destino en segundo plano por medio del marco de enlace,
mientras que los cambios en el objeto de destino pueden insertarse de manera opcional en el
objeto de origen.
El establecimiento del enlace de datos es un proceso de dos pasos:
La propiedad BindingContext del objeto de destino se debe establecer en el de origen.
Es necesario establecer un enlace entre el destino y el origen. En XAML, esto se consigue
mediante la extensión de marcado Binding .
En la aplicación Notes, el destino de enlace es el elemento Editor que muestra una nota,
mientras que la instancia de Note establecida como BindingContext de NoteEntryPage es el
origen de enlace.
El objeto BindingContext de NoteEntryPage se establece durante la navegación de la página,
como se muestra en el ejemplo de código siguiente:

async void OnNoteAddedClicked(object sender, EventArgs e)


{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = new Note()
});
}

async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)


{
if (e.SelectedItem != null)
{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = e.SelectedItem as Note
});
}
}

En el método OnNoteAddedClicked , que se ejecuta cuando se agrega una nueva nota a la


aplicación, el objeto BindingContext de NoteEntryPage se establece en una nueva instancia de
Note . En el método OnListViewItemSelected , que se ejecuta cuando se selecciona una nota
existente en el elemento ListView , el objeto BindingContext de NoteEntryPage se establece en la
instancia de Note seleccionada, a la que se accede a través de la propiedad e.SelectedItem .
IMPORTANT
Aunque la propiedad BindingContext de cada objeto de destino se puede establecer de manera
individual, no es necesario hacerlo. BindingContext es una propiedad especial que heredan todos sus
elementos secundarios. Por tanto, cuando la propiedad BindingContext de ContentPage se establece
en una instancia de Note , todos los elementos secundarios de ContentPage tienen la misma
propiedad BindingContext y se pueden enlazar a propiedades públicas del objeto Note .

Después, el objeto Editor de NoteEntryPage se enlaza a la propiedad Text del objeto Note :

<Editor Placeholder="Enter your note"


Text="{Binding Text}"
... />

Se establece un enlace entre la propiedad Editor.Text y la propiedad Text del objeto de origen.
Los cambios realizados en Editor se propagarán de forma automática al objeto Note . De forma
similar, si se realizan cambios en la propiedad Note.Text , el motor de enlace de Xamarin.Forms
también actualizará el contenido de Editor . Esto se conoce como enlace bidireccional.
Para obtener más información sobre el enlace de datos, consulte Enlace de datos de
Xamarin.Forms.

Aplicación de estilos
Las aplicaciones de Xamarin.Forms suelen contener varios elementos visuales que tienen un
aspecto idéntico. Establecer la apariencia de cada elemento visual puede ser repetitivo y
propenso a errores. En su lugar, se pueden crear estilos que definan el aspecto y, después,
aplicarlos a los elementos visuales necesarios.
La clase Style agrupa una colección de valores de propiedad en un objeto que después se
puede aplicar a varias instancias de elementos visuales. Los estilos se almacenan en un objeto
ResourceDictionary , ya sea en el nivel de la aplicación, de la página o de la vista. La elección de
dónde se puede definir un elemento Style afecta a dónde se puede usar:
Las instancias de Style definidas en el nivel de aplicación se pueden aplicar en toda la
aplicación.
Las instancias de Style definidas en el nivel de página se pueden aplicar a la página y a sus
elementos secundarios.
Las instancias de Style definidas en el nivel de vista se pueden aplicar a la vista y a sus
elementos secundarios.

IMPORTANT
Todos los estilos que se usen en la aplicación se almacenan en el diccionario de recursos de la aplicación
para evitar la duplicación. Pero el código de XAML que es específico de una página no debería incluirse en
el diccionario de recursos de la aplicación, dado que los recursos se analizarán en el inicio de la aplicación
en lugar de cuando los solicite una página.

Cada instancia de Style contiene una colección de uno o varios objetos Setter , donde cada
objeto Setter tiene un elemento Property y un elemento Value . Property es el nombre de la
propiedad enlazable del elemento al que se aplica el estilo y Value es el valor que se aplica a la
propiedad. En el ejemplo de código siguiente se muestra un estilo de NoteEntryPage :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.NoteEntryPage"
Title="Note Entry">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type Editor}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>
...
</ContentPage.Resources>
...
</ContentPage>

Este estilo se aplica a cualquier instancia de Editor de la página.


Al crear un elemento Style , siempre se requiere la propiedad TargetType .

NOTE
Normalmente, la aplicación de estilo a una aplicación Xamarin.Forms se realiza mediante estilos XAML.
Aun así, Xamarin.Forms también admite aplicar estilo a los elementos visuales mediante hojas de estilo
CSS (CSS). Para obtener más información, vea Aplicación de estilos para aplicaciones Xamarin.Forms con
hojas de estilo (CSS).

Para obtener más información sobre los estilos XAML, vea Aplicación de estilo a aplicaciones
Xamarin.Forms con estilos XAML.
Proporcionar estilos específicos de la plataforma
Las extensiones de marcado OnPlatform permiten personalizar la apariencia de la interfaz de
usuario para cada plataforma:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.App">
<Application.Resources>
...
<Color x:Key="iOSNavigationBarColor">WhiteSmoke</Color>
<Color x:Key="AndroidNavigationBarColor">#2196F3</Color>
<Color x:Key="iOSNavigationBarTextColor">Black</Color>
<Color x:Key="AndroidNavigationBarTextColor">White</Color>

<Style TargetType="{x:Type NavigationPage}">


<Setter Property="BarBackgroundColor"
Value="{OnPlatform iOS={StaticResource iOSNavigationBarColor},
Android={StaticResource AndroidNavigationBarColor}}"
/>
<Setter Property="BarTextColor"
Value="{OnPlatform iOS={StaticResource iOSNavigationBarTextColor},
Android={StaticResource
AndroidNavigationBarTextColor}}" />
</Style>
...
</Application.Resources>
</Application>

Este elemento Style establece otros valores Color para las propiedades BarBackgroundColor y
BarTextColor de NavigationPage , en función de la plataforma que se use.
Para más información sobre las extensiones de marcado, vea Extensiones de marcado para el
lenguaje XAML. Para obtener información sobre de la extensión de marcado OnPlatform , vea
Extensión de marcado OnPlatform.

Pruebas e implementación
Tanto Visual Studio para Mac como Visual Studio ofrecen numerosas opciones para probar e
implementar una aplicación. Depurar aplicaciones es una parte común del ciclo de vida del
desarrollo de la aplicación y ayuda a diagnosticar problemas de código. Para obtener más
información, consulte Set a Breakpoint (Establecer un punto de interrupción), Step Through Code
(Recorrer el código paso a paso) y Output Information to the Log Window (Información de salida
para la ventana Registro).
Los simuladores son un buen lugar para comenzar a implementar y probar una aplicación, y
cuentan con una funcionalidad que resulta útil a la hora de probar las aplicaciones. Sin embargo,
los usuarios no usarán la aplicación final en un simulador, por lo que las aplicaciones deben
probarse en dispositivos reales desde el primer momento y con frecuencia. Para obtener más
información sobre el aprovisionamiento de dispositivos de iOS, consulte Aprovisionamiento de
dispositivos. Para obtener más información sobre el aprovisionamiento de dispositivos de
Android, consulte Configurar el dispositivo para el desarrollo.

Pasos siguientes
En este análisis detallado se han examinado los aspectos fundamentales del desarrollo de
aplicaciones con Xamarin.Forms. Se recomienda que, como paso siguiente, lea sobre las
funcionalidades que se indican a continuación:
Existen cuatro grupos de controles principales que se usan para crear la interfaz de usuario de
una aplicación Xamarin.Forms. Para obtener más información, vea Referencia de controles.
El enlace de datos es la técnica que consiste en vincular las propiedades de dos objetos para
que los cambios en una propiedad se reflejen automáticamente en la otra propiedad. Para
obtener más información, vea Enlace de datos.
Xamarin.Forms proporciona una serie de experiencias de navegación de páginas diferente, en
función del tipo de página que se use. Para obtener más información, consulte Navigation
(Navegación).
Los estilos permiten reducir el uso de marcado repetitivo y conseguir que la apariencia de las
aplicaciones pueda cambiarse con mayor facilidad. Para obtener más información, consulte
Aplicación de estilos a aplicaciones Xamarin.Forms.
Las extensiones de marcado XAML extienden las funciones avanzadas y la flexibilidad de
XAML al permitir establecer atributos de elementos desde orígenes distintos de cadenas de
texto literales. Para obtener más información, vea Extensiones de marcado XAML.
Las plantillas de datos permiten definir la presentación de los datos en las vistas admitidas.
Para obtener más información, consulte Data Templates (Plantillas de datos).
Cada página, diseño y control se representan de forma diferente en cada plataforma mediante
una clase Renderer que, a su vez, crea un control nativo, lo organiza en la pantalla y agrega el
comportamiento especificado al código compartido. Los desarrolladores pueden implementar
sus propias clases Renderer personalizadas para personalizar la apariencia o el
comportamiento de un control. Para obtener más información, consulte Custom Renderers
(Representadores personalizados).
Los efectos también permiten personalizar los controles nativos de cada plataforma. Los
efectos se crean en proyectos específicos de la plataforma mediante la creación de subclases
de la clase PlatformEffect . Para usarlos, se adjuntan a un control adecuado de
Xamarin.Forms. Para obtener más información, consulte Effects (Efectos).
El código compartido puede tener acceso a la funcionalidad nativa mediante la clase
DependencyService . Para obtener más información, consulte Accessing Native Features with
DependencyService (Acceso a características nativas con DependencyService).
También es recomendable el libro de Charles Petzold titulado Creación de aplicaciones móviles
con Xamarin.Forms para obtener más información sobre Xamarin.Forms. El libro está disponible
como un archivo PDF o en una variedad de formatos de libro electrónico.

Vínculos relacionados
Lenguaje XAML (eXtensible Application Markup Language)
Enlace de datos
Controls Reference (Referencia de controles)
Extensiones de marcado XAML
Ejemplos de Xamarin.Forms
Getting Started Samples (Ejemplos de introducción)
Referencia de la API Xamarin.Forms
Aprendizaje autoguiado gratuito (vídeo)

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Multiplataforma para desarrolladores de escritorio
02/03/2020 • 2 minutes to read • Edit Online

Esta sección contiene información para ayudar a los desarrolladores de WPF y Windows Forms a aprender el
desarrollo de aplicaciones móviles con Xamarin, mediante la referencia cruzada de los conocimientos y la
experiencia existentes a los lenguajes móviles, y proporcionando ejemplos de migración de aplicaciones de
escritorio a Mobile.

Comparación del ciclo de vida de la aplicación


Descripción de las diferencias entre el inicio de la aplicación de WPF y el de Xamarin. Forms.

Comparación de controles de interfaz de usuario


Referencia rápida para buscar controles equivalentes en Windows Forms, WPF y Xamarin. Forms, incluidas
instrucciones adicionales sobre las diferencias entre WPF y Xamarin. Forms.

Guía de migración
Usar el analizador de portabilidad para ayudar a migrar el código de aplicación de escritorio (excluida la interfaz de
usuario) a Xamarin. Forms.

Muestras
Ejemplos de referencia que muestran la arquitectura de aplicaciones empresariales y el código de portabilidad de
WPF a Xamarin. Forms.

Obtener más información


Xamarin para desarrolladores de Java
18/12/2020 • 52 minutes to read • Edit Online

Si es usted es desarrollador de Java, ya tiene la posibilidad de empezar a aprovechar sus habilidades y el código
existente de Xamarin en la plataforma Xamarin, al tiempo que disfruta de los beneficios de reutilizar código de C#.
Observará que la sintaxis de C# es muy similar a la sintaxis de Java y que ambos lenguajes ofrecen características
muy similares. Además, encontrará características exclusivas de C# que facilitarán el trabajo a los desarrolladores.

Información general
En este artículo se proporciona una introducción a la programación en C# para los desarrolladores de Java y se
centra principalmente en las características del lenguaje C# con las que se encontrará al desarrollar aplicaciones
Xamarin.Android. Además, en este artículo se explican las diferencias de estas características con respecto a las de
Java y, además, se presentan características importantes de C# (relevantes para Xamarin.Android) que no se
encuentran disponibles en Java. Se incluyen vínculos a material de referencia adicional, para que pueda usar este
artículo como un punto de partida para obtener más información sobre C# y .NET.
Si está familiarizado con Java, no tardará en sentirse cómodo con la sintaxis de C#. La sintaxis de C# es muy similar
a la de Java – C# es un lenguaje "de apertura" como Java, C y C++. En muchos sentidos, la sintaxis de C# se lee
como un superconjunto de la sintaxis de Java, pero con algunas palabras clave agregadas y con el nombre
cambiado.
Muchas características clave de Java pueden encontrarse en C#:
Programación orientada a objetos basados en clases
Tipado fuerte
Compatibilidad con interfaces
Genéricos
Recolección de elementos no utilizados
Compilación en tiempo de ejecución
Java y C# se compilan en un lenguaje intermedio que se ejecuta en un entorno de ejecución administrado. C# y
Java son tipos estáticos, y ambos lenguajes tratan las cadenas como tipos inmutables. Ambos lenguajes utilizan una
jerarquía de clases de raíz única. Al igual que Java, C# solo admite una herencia única y no acepta los métodos
globales. En ambos lenguajes, los objetos se crean en el montón con la palabra clave new y se recoleccionan los
objetos no utilizados cuando ya no se utilizan. Ambos lenguajes ofrecen una compatibilidad formal con el control
de excepciones mediante la semántica try / catch . Los dos ofrecen también compatibilidad con la sincronización y
administración de subprocesos.
No obstante, existen muchas diferencias entre Java y C#. Por ejemplo:
Java (tal como se usa en Android) no admite las variables locales con tipo implícito (C# admite la palabra
clave var ).
En Java, puede pasar parámetros solo por valor, mientras que en C# puede pasarlos tanto por referencia
como por valor. (C# ofrece las palabras clave ref y out para pasar los parámetros por referencia; no hay
ningún equivalente en Java).
Java no admite las directivas de preprocesador como #define .
Java no admite los tipos de enteros sin signo, mientras que C# sí que proporciona tipos de enteros sin signo
como ulong , uint , ushort y byte .
Java no admite la sobrecarga de operador; en C# se pueden sobrecargar operadores y conversiones.
En una instrucción Java switch , el código puede pasar a la siguiente sección del modificador; en cambio, en
C#, el final de cada sección switch debe terminar el modificador (el final de cada sección debe cerrarse con
una instrucción break ).
En Java, puede especificar las excepciones generadas por un método con la palabra clave throws , pero C#
no tiene ningún concepto de excepciones comprobadas; la palabra clave throws no se admite en C#.
C# admite Language Integrated Query (LINQ), que permite usar las palabras reservadas from , select y
where para escribir consultas de colecciones de forma similar a las consultas de bases de datos.

Por supuesto, hay muchas más diferencias entre C# y Java que pueden tratarse en este artículo. Además, Java y C#
continúan evolucionando (por ejemplo, Java 8, que aún no se encuentra en la cadena de herramientas de Android,
admite expresiones lambda del estilo de C#), por lo que estas diferencias seguirán evolucionando. Aquí solo se
destacan las diferencias más importantes que han detectado los desarrolladores de Java que son nuevos en
Xamarin.Android.
En Pasar del desarrollo en Java al desarrollo en C# se ofrece una introducción a las diferencias
fundamentales entre C# y Java.
En Características de la programación orientada a objetos se explican las diferencias más importantes de la
característica orientada a objetos entre ambos lenguajes.
En Diferencias de palabras claves se ofrece una lista de equivalentes de palabras claves útiles, solo palabras
claves de C# y vínculos a las definiciones de las palabras claves de C#.
C# proporciona muchas características claves de Xamarin.Android que actualmente no están disponibles para los
desarrolladores de Java en Android. Estas características pueden ayudarle a escribir código mejor y en menos
tiempo:
Propiedades – Con el sistema de propiedades de C#, puede acceder a las variables de miembros de forma
segura y directa sin tener que escribir métodos de establecedor y captador.
Expresiones lambda – En C# puede usar métodos anónimos (también denominados lambdas) para expresar
la funcionalidad de forma más sucinta y eficaz. Puede evitar la sobrecarga de tener que escribir objetos de
un solo uso, y puede pasar el estado local a un método sin tener que agregar parámetros.
Control de eventos – C# ofrece compatibilidad a nivel de lenguaje para la programación orientada a eventos,
donde se puede registrar un objeto para recibir una notificación cuando se produzca algún evento de
interés. La palabra clave event define un mecanismo de multidifusión que una clase de publicador puede
usar para informar a los suscriptores de eventos.
Programación asincrónica – Las características de la programación asincrónica de C# ( async / await )
mantienen la capacidad de respuesta de las aplicaciones. La compatibilidad a nivel de lenguaje de esta
característica permite que la programación asincrónica resulte fácil de implementar y que sea menos
propensa a errores.
Además, Xamarin le permite aprovechar los recursos de Java existentes mediante una tecnología que se conoce
como enlaces. Puede llamar al código, los marcos de trabajo y las bibliotecas de Java existentes desde C# con el uso
de generadores de enlaces automáticos de Xamarin. Para ello, basta con crear una biblioteca estática en Java y
exponerla en C# mediante un enlace.
NOTE
La programación de Android usa una versión específica del lenguaje Java que admite todas las características de Java 7 y un
subconjunto de Java 8.
Algunas características mencionadas en esta página (como la palabra clave var en C#) están disponibles en versiones más
recientes de Java (p. ej., var en Java 10), pero siguen si estar a disposición de los desarrolladores de Android.

Pasar del desarrollo en Java al desarrollo en C#


En las secciones siguientes se describen las diferencias básicas de "introducción" entre C# y Java; en una sección
posterior se describen las diferencias orientadas a objetos entre estos lenguajes.
Bibliotecas frente a ensamblados
Java suele empaquetar clases relacionadas en archivos .jar . Sin embargo, en C# and .NET, los bits reutilizables de
código precompilado se empaquetan en ensamblados, que suelen empaquetarse como archivos .dll. Un
ensamblado es una unidad de implementación de código de C#/.NET, y cada ensamblado suele asociarse con un
proyecto de C#. Los ensamblados contienen código intermedio que se compilan Just-In-Time en tiempo de
ejecución.
Para obtener más información sobre los ensamblados, vea el tema Ensamblados y caché global de ensamblados.
Paquetes frente a espacios de nombres
C# usa la palabra clave namespace para agrupar tipos relacionados; es similar a la palabra clave package de Java.
Normalmente, una aplicación de Xamarin.Android residirá en un espacio de nombres creado para esa aplicación.
Por ejemplo, el siguiente código de C# declara el contenedor del espacio de nombres WeatherApp para un
aplicación de información meteorológica:

namespace WeatherApp
{
...

Importación de tipos
Al usar tipos definidos en los espacios de nombres externos, importe estos tipos con una instrucción using (que es
muy similar a la instrucción import de Java). En Java, puede importar un solo tipo con una instrucción similar a la
siguiente:

import javax.swing.JButton

Puede importar un paquete de Java completo con una instrucción como esta:

import javax.swing.*

La instrucción using de C# funciona de forma muy similar, pero permite importar un paquete completo sin
especificar un carácter comodín. Por ejemplo, a menudo verá una serie de instrucciones using al principio de los
archivos de origen de Xamarin.Android, como se observa en este ejemplo:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Net;
using System.IO;
using System.Json;
using System.Threading.Tasks;

Estas instrucciones importan la funcionalidad desde los espacios de nombres System , Android.App ,
Android.Content , etc.

Genéricos
Tanto Java como C# admiten genéricos, que son marcadores de posición que permiten conectar diferentes tipos en
tiempo de compilación. Sin embargo, los genéricos funcionan de forma algo diferente en C#. En Java, el borrado de
tipos habilita la información sobre tipos solo en tiempo de compilación, pero no en tiempo de ejecución. Por el
contrario, .NET Common Language Runtime (CLR) proporciona compatibilidad explícita para los tipos genéricos, lo
que significa que C# tiene acceso a información sobre tipos en tiempo de ejecución. En el desarrollo diario de
Xamarin.Android, la importancia de esta distinción no suele ser evidente, pero si usa la reflexión, dependerá de esta
característica para acceder a la información sobre tipos en tiempo de ejecución.
En Xamarin.Android, verá con frecuencia el método genérico FindViewById utilizado para obtener una referencia a
un control de diseño. Este método acepta un parámetro de tipo genérico que especifica el tipo de control que se
buscará. Por ejemplo:

TextView label = FindViewById<TextView> (Resource.Id.Label);

En este ejemplo de código, FindViewById obtiene una referencia al control TextView que se define en el diseño
como etiqueta ; a continuación, se devuelve como un tipo TextView .
Para obtener más información sobre los genéricos, vea el tema Genéricos. Tenga en cuenta que existen algunas
limitaciones en la compatibilidad de Xamarin.Android con las clases genéricas de C#; para más información, vea
Limitaciones.

Características de la programación orientada a objetos


Java y C# utilizan expresiones de programación orientada a objetos muy similares:
Todas las clases derivan en última instancia de un único objeto raíz; todos los objetos de Java derivan de
java.lang.Object , mientras que los de C# derivan de System.Object .

Las instancias de clases son tipos de referencia.


Cuando acceda a las propiedades y los métodos de una instancia, use el operador " . ".
Todas las instancias de clases se crean en el montón mediante el operador new .
Debido a que ambos lenguajes usan la recolección de elementos no utilizados, no hay ningún método para
liberar de forma explícita los objetos no utilizados (es decir, no hay ninguna palabra clave delete como hay
en C++).
Puede extender las clases mediante la herencia, y ambos lenguajes solo permiten una única clase base por
tipo.
Puede definir interfaces y una clase puede heredar de varias definiciones de interfaz (es decir, implementar).
Sin embargo, también hay algunas diferencias importantes:
Java tiene dos características eficaces que C# no admite: las clases anónimas y las clases internas. (Sin
embargo, C# permite el anidamiento de las definiciones de clases; las clases anidadas de C# son similares a
las clases anidadas estáticas de Java).
C# admite tipos de estructuras del estilo de C ( struct ), pero Java no.
En C#, puede implementar una definición de clase en los archivos de código fuente independientes mediante
la palabra clave partial .
Las interfaces de C# no pueden declarar campos.
C# utiliza la sintaxis de destructores de estilo de C++ para expresar los finalizadores. La sintaxis es diferente
del método finalize de Java, pero la semántica es prácticamente la misma. (Tenga en cuenta que, en C#, los
destructores llaman automáticamente al destructor de clase base –, a diferencia de Java, donde se usa una
llamada explícita a super.finalize ).
Herencia de clases
Para extender una clase de Java, utilice la palabra clave extends . Para extender una clase de C#, utilice dos puntos (
: ) para indicar la derivación. Por ejemplo, en aplicaciones de Xamarin.Android, a menudo verá derivaciones de
clases que se parecen al fragmento de código siguiente:

public class MainActivity : Activity


{
...

En este ejemplo, MainActivity hereda de la clase Activity .


Para declarar la compatibilidad con una interfaz en Java, use la palabra clave implements . Sin embargo, en C#, solo
tiene que agregar nombres de interfaz a la lista de clases de la que se hereda, como se muestra en este fragmento
de código:

public class SensorsActivity : Activity, ISensorEventListener


{
...

En este ejemplo, SensorsActivity hereda de Activity e implementa la funcionalidad declarada en la interfaz


ISensorEventListener . Tenga en cuenta que la lista de interfaces debe aparecer después de la clase base o, de lo
contrario, se producirá un error en tiempo de compilación. Por convención, los nombres de interfaz de C# van
precedidos de una letra mayúscula "I"; esto permite determinar qué clases son interfaces sin requerir una palabra
clave implements .
Si desea impedir que una clase derive en subclases en C#, coloque sealed – delante del nombre de clase; en Java,
coloque final delante del nombre de clase.
Para obtener más información sobre las definiciones de clases de C#, vea los temas Clases y Herencia.
Propiedades
En Java, los métodos mutadores (establecedores) y los métodos de inspector (captadores) a menudo se utilizan
para controlar cómo se realizan los cambios en los miembros de clase al ocultarlos y protegerlos del código
externo. Por ejemplo, la clase TextView de Android proporciona los métodos getText y setText . C# proporciona
un mecanismo similar pero más directo conocido como propiedades. Los usuarios de una clase de C# pueden
acceder a una propiedad de la misma manera en que accederían a un campo, pero cada acceso realmente produce
una llamada de método que es transparente para el autor de la llamada. Este método "pormenorizado" puede
implementar los efectos secundarios, como establecer otros valores, realizar conversiones o cambiar el estado del
objeto.
Las propiedades suelen utilizarse para acceder a los miembros de objetos de la interfaz de usuario y para
modificarlos. Por ejemplo:

int width = rulerView.MeasuredWidth;


int height = rulerView.MeasuredHeight;
...
rulerView.DrawingCacheEnabled = true;

En este ejemplo, se leen los valores de anchura y altura del objeto rulerView mediante el acceso a sus propiedades
MeasuredWidth y MeasuredHeight . Cuando se leen estas propiedades, los valores de sus valores de campos
asociados, pero ocultos, se capturan en segundo plano y se devuelven al autor de la llamada. El objeto rulerView
puede almacenar los valores de anchura y altura en una unidad de medida (es decir, píxeles) y convertirlos sobre la
marcha en una unidad de medida distinta (por ejemplo, milímetros) al acceder a las propiedades MeasuredWidth y
MeasuredHeight .

El objeto rulerView también tiene una propiedad denominada DrawingCacheEnabled – el código de ejemplo
establece esta propiedad en true para habilitar la caché de dibujo en rulerView . En segundo plano, se actualiza
un campo oculto asociado con el nuevo valor y es posible que se modifiquen otros aspectos del estado rulerView .
Por ejemplo, cuando DrawingCacheEnabled está establecido en false , rulerView también puede borrar cualquier
información de la caché de dibujo ya acumulada en el objeto.
El acceso a las propiedades puede ser de lectura/escritura, solo lectura o solo escritura. Además, puede usar
distintos modificadores de acceso para leer y escribir. Por ejemplo, puede definir una propiedad que tenga acceso
de lectura público, pero acceso de escritura privado.
Para obtener más información sobre las propiedades de C#, vea el tema Propiedades.
Llamar a métodos de clase base
Para llamar a un constructor de clase base en C#, utilice dos puntos ( : ) seguidos de la palabra clave base y una
lista de inicializadores; esta llamada al constructor base se coloca inmediatamente después de la lista de
parámetros del constructor derivada. Se llama al constructor de clase base al introducir el constructor derivado; el
compilador inserta la llamada en el constructor base al inicio del cuerpo del método. El siguiente fragmento de
código muestra un constructor base llamado desde un constructor derivado en una aplicación de Xamarin.Android:

public class PictureLayout : ViewGroup


{
...
public PictureLayout (Context context)
: base (context)
{
...
}
...
}

En este ejemplo, la clase PictureLayout se deriva de la clase ViewGroup . El constructor PictureLayout que se
muestra en este ejemplo acepta un argumento context y lo pasa al constructor ViewGroup mediante la llamada
base(context) .

Para llamar a un método de clase base en C#, use la palabra clave base . Por ejemplo, las aplicaciones de
Xamarin.Android suelen realizar llamadas a los métodos base, como se muestra aquí:
public class MainActivity : Activity
{
...
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);

En este caso, el método OnCreate definido por la clase derivada ( MainActivity ) llama al método OnCreate de la
clase base ( Activity ).
Modificadores de acceso
Java y C# admiten los modificadores de acceso public , private y protected . Sin embargo, C# admite dos
modificadores de acceso adicionales:
internal – Al miembro de clase se puede acceder únicamente dentro del ensamblado actual.
protected internal – Al miembro de clase se puede acceder dentro del ensamblado de definición, de la
clase de definición y de las clases derivadas (tienen acceso las clases derivadas dentro y fuera del
ensamblado).
Para obtener más información sobre los modificadores de acceso de C#, vea el tema Modificadores de acceso.
Métodos virtuales y de invalidación
Java y C# admiten el polimorfismo, que es la capacidad de tratar objetos relacionados de la misma manera. En
ambos lenguajes, se puede usar una referencia de clase base para hacer referencia a un objeto de clases derivada, y
los métodos de una clase derivada pueden invalidar los métodos de sus clases base. Ambos lenguajes tienen el
concepto de un método virtual, que es un método de una clase base diseñado para sustituirse por un método de
una clase derivada. Al igual que Java, C# admite clases y métodos abstract .
Sin embargo, hay algunas diferencias entre Java y C# en cómo declarar métodos virtuales e invalidarlos:
En C#, los métodos son no virtuales de forma predeterminada. Las clases principales deben etiquetar de
forma explícita qué métodos deben invalidarse con el uso de la palabra clave virtual . Por el contrario,
todos los métodos de Java son virtuales de forma predeterminada.
Para evitar la invalidación de un método en C#, solo tiene que excluir la palabra clave virtual . Por el
contrario, Java usa la palabra clave final para marcar un método con "override is not allowed" (No se
permite la invalidación).
Las clases derivadas de C# deben usar la palabra clave override para indicar de forma explícita que se va a
anular un método de clase base virtual.
Para obtener más información sobre la compatibilidad de C# con el polimorfismo, vea el tema Polimorfismo.

Expresiones lambda
C# permite crear clausuras: métodos anónimos insertados que pueden acceder al estado del método en que están
incluidos. Con el uso de expresiones lambda, puede escribir menos líneas de código para implementar la misma
funcionalidad que hubiera implementado en Java con muchas más líneas de código.
Las expresiones lambda permiten omitir los pasos adicionales necesarios para crear una clase de un solo uso o una
clase anónima que debería aplicar en Java; en su lugar, solo puede escribir la lógica de negocios del código del
método insertado. Además, como las expresiones lambda tienen acceso a las variables del método adyacente, no
tiene que crear una lista larga de parámetros para pasar el estado al código del método.
En C#, las expresiones lambda se crean con el operador => , como se muestra a continuación:
(arg1, arg2, ...) => {
// implementation code
};

En Xamarin.Android, a menudo se usan expresiones lambda para definir controladores de eventos. Por ejemplo:

button.Click += (sender, args) => {


clickCount += 1; // access variable in surrounding code
button.Text = string.Format ("Clicked {0} times.", clickCount);
};

En este ejemplo, el código de la expresión lambda (el código encerrado entre llaves) incrementa un recuento de
clics y actualiza el texto button para mostrar el recuento de clics. Esta expresión lambda se registra con el objeto
button como un controlador de eventos de clics al que se llama cada vez que se pulsa un botón. (Los
controladores de eventos se explican con más detalle a continuación). En este ejemplo, el código de la expresión
lambda no usa los parámetros sender y args , pero sí son necesarios en la expresión lambda para satisfacer los
requisitos de firma del método para el registro del evento. En segundo plano, el compilador de C# convierte la
expresión lambda en un método anónimo al que se llama cada vez que se hace clic en un botón.
Para obtener más información sobre C# y las expresiones lambda, vea el tema Expresiones lambda.

Control de eventos
Un eventos es la forma en que un objeto notifica a los suscriptores registrados cuándo sucede algo interesante a
dicho objeto. A diferencia de Java, donde un suscriptor que suele implementar una interfaz Listener que contiene
un método de devolución de llamada, C# ofrece compatibilidad a nivel de lenguaje para el control de eventos
mediante delegados. Un delegado es similar a un puntero de función con seguridad de tipos orientado a objetos;
encapsula una referencia de objeto y un token de método. Si un objeto de cliente desea suscribirse a un evento,
crea un delegado y pasa el delegado al objeto notificador. Cuando se produce el evento, el objeto notificador invoca
el método representado por el objeto delegado, para notificar al objeto de cliente de suscripción del evento. En C#,
los controladores de eventos básicamente no son más que métodos que se invocan mediante delegados.
Para obtener más información sobre los delegados, vea el tema Delegados.
En C#, los eventos son multidifusión; esto quiere decir que se puede notificar a más de un agente de escucha
cuando se produce un evento. Esta diferencia se observa al tener en cuenta las diferencias sintácticas entre el
registro de eventos de Java y C#. En Java, se llama a SetXXXListener para registrar notificaciones de eventos; en C#,
se usa el operador += para registrar notificaciones de eventos al "agregar" el delegado a la lista de agentes de
escucha de eventos. En Java, se llama a SetXXXListener para anular el registro, mientras que en C# se utiliza -=
para "restar" el delegado de la lista de agentes de escucha.
En Xamarin.Android, los eventos se usan con frecuencia para informar a los objetos cuando un usuario realiza una
acción en un control de interfaz de usuario. Normalmente, un control de interfaz de usuario tendrá miembros que
se definen con la palabra clave event ; asocie los delegados con estos miembros para suscribirse a eventos de ese
control de interfaz de usuario.
Para suscribirse a un evento:
1. Cree un objeto delegado que haga referencia al método que quiere invocar cuando se produce el evento.
2. Use el operador += para asociar el delegado al evento al que se va a suscribir.

En el ejemplo siguiente se define un delegado (con el uso explícito de la palabra clave delegate ) para suscribirse a
los clics de botón. Este controlador de clics de botón inicia una nueva actividad:
startActivityButton.Click += delegate {
Intent intent = new Intent (this, typeof (MyActivity));
StartActivity (intent);
};

Sin embargo, también puede utilizar una expresión lambda para registrar eventos, omitiendo por completo la
palabra clave delegate . Por ejemplo:

startActivityButton.Click += (sender, e) => {


Intent intent = new Intent (this, typeof (MyActivity));
StartActivity (intent);
};

En este ejemplo, el objeto startActivityButton tiene un evento que espera un delegado con una firma de método
determinada: uno que acepta argumentos de remitente y evento y devuelve un valor nulo. No obstante, como no
queremos ocasionar la molestia de definir de forma explícita dicho delegado o su método, declaramos la firma del
método con (sender, e) y usamos una expresión lambda para implementar el cuerpo del controlador de eventos.
Tenga en cuenta que tenemos que declarar esta lista de parámetros aunque usemos los parámetros sender y e .
Es importante recordar que se puede anular la suscripción de un delegado (con el operador -= ), pero no se puede
anular la suscripción de una expresión lambda; tratar de hacerlo puede causar fugas de memoria. Use la forma de
expresión lambda del registro de eventos solo si no se anula la suscripción del controlador al evento.
Normalmente, las expresiones lambda se usan para declarar controladores de eventos en el código de
Xamarin.Android. Esta forma abreviada de declarar controladores de eventos puede parecer críptica al principio,
pero ahorra una gran cantidad de tiempo al leer y escribir código. Cuando se familiarice más, se habituará a
reconocer este patrón, que ocurre con frecuencia en el código de Xamarin.Android, y podrá dedicar más tiempo a
pensar en la lógica de negocios de su aplicación y menos tiempo a repasar la sobrecarga sintáctica.

Programación asincrónica
La programación asincrónica es una manera de mejorar la capacidad de respuesta general de la aplicación. Las
características de la programación asincrónica permiten que el resto del código de la aplicación continúe
ejecutándose mientras que una operación larga bloquea alguna parte de la aplicación. El acceso a la Web, el
procesamiento de imágenes y la lectura y escritura de archivos son ejemplos de operaciones que pueden causar
que una aplicación parezca inmovilizada si no se escribe de forma asincrónica.
C# incluye compatibilidad a nivel de lenguaje con la programación asincrónica mediante las palabras clave async
y await . Estas características del lenguaje facilitan bastante la escritura de código que realiza tareas de ejecución
prolongada sin bloquear el subproceso principal de la aplicación. En resumen, use la palabra clave async en un
método para indicar que el código del método debe ejecutarse de forma asincrónica y no bloquear el subproceso
del autor de la llamada. Use la palabra clave await para llamar a métodos marcados como async . El compilador
interpreta await como el punto al que se va a mover la ejecución del método a un subproceso en segundo plano
(se devuelve una tarea al autor de la llamada). Una vez completada esta tarea, la ejecución del código se reanuda en
el subproceso del autor de la llamada en el punto await del punto, devolviendo los resultados de la llamada
async . Por convención, los métodos que se ejecutan de forma asincrónica llevan el sufijo Async en sus nombres.

En las aplicaciones de Xamarin.Android, async y await suelen usarse para liberar el subproceso de la interfaz de
usuario, para que pueda responder a la entrada del usuario (como pulsar un botón Cancelar ) mientras tiene lugar
una operación de ejecución prolongada en una tarea en segundo plano.
En el ejemplo siguiente, un controlador de eventos de clics de botón resulta en una operación asincrónica para
descargar una imagen de la Web:

downloadButton.Click += downloadAsync;
...
async void downloadAsync(object sender, System.EventArgs e)
{
webClient = new WebClient ();
var url = new Uri ("http://photojournal.jpl.nasa.gov/jpeg/PIA15416.jpg");
byte[] bytes = null;

bytes = await webClient.DownloadDataTaskAsync(url);

// display the downloaded image ...

En este ejemplo, cuando el usuario hace clic en el control downloadButton , el controlador de eventos downloadAsync
crea un objeto WebClient y un objeto Uri para capturar una imagen de la dirección URL especificada. A
continuación, llama al método DownloadDataTaskAsync del objeto WebClient con esta dirección URL para recuperar
la imagen.
Tenga en cuenta que la declaración del método downloadAsync va precedida de la palabra clave async para indicar
que se ejecutará de forma asincrónica y que devolverá una tarea. Tenga en cuenta también que la llamada a
DownloadDataTaskAsync va precedida de la palabra clave await . La aplicación mueve la ejecución del controlador de
eventos (a partir del punto en que await aparece) a un subproceso en segundo plano hasta que
DownloadDataTaskAsync se completa y realiza la devolución. Mientras tanto, el subproceso de la interfaz de usuario
de la aplicación puede responder aún a la entrada del usuario y activar los controladores de eventos para los otros
controles. Cuando DownloadDataTaskAsync se completa (que puede tardar varios segundos), la ejecución se reanuda
donde la variable bytes está establecida en el resultado de la llamada a DownloadDataTaskAsync , y el resto del
código del controlador de eventos muestra la imagen descargada en el subproceso del autor de la llamada (interfaz
de usuario).
Para obtener una introducción a async / await en C#, vea el tema Programación asincrónica con Async y Await.
Para más información sobre la compatibilidad de Xamarin con las características de la programación asincrónica,
vea Información general sobre la compatibilidad con Async.

Diferencias de palabras claves


Muchas de las palabras claves del lenguaje de Java también se usan en C#. También hay una serie de palabras clave
de Java que tienen un equivalente en C# pero con un nombre distinto, como se indica en esta tabla:

JAVA C# DESC RIP C IÓ N

boolean bool Se utiliza para declarar los valores


booleanos true y false.

extends : Precede a la clase e interfaces de las que


se hereda.

implements : Precede a la clase e interfaces de las que


se hereda.

import using Importa tipos de un espacio de


nombres, que también se usa para crear
un alias de espacio de nombres.
JAVA C# DESC RIP C IÓ N

final sealed Evita la derivación de clase; impide que


se invaliden los métodos y las
propiedades de la clase derivada.

instanceof is Evalúa si un objeto es compatible con


un tipo determinado.

native extern Declara un método que se implementa


externamente.

package namespace Declara un ámbito para un conjunto de


objetos relacionados.

T... params T Especifica un parámetro de método que


toma un número variable de
argumentos.

super base Se usa para acceder a los miembros de


la clase principal desde una clase
derivada.

synchronized lock Ajusta una sección crítica del código con


lanzamiento y adquisición de bloqueo.

Además, hay muchas palabras clave que son exclusivas de C# y que no tienen homólogo en el Java usado en
Android. El código de Xamarin.Android suele usar las siguientes palabras clave de C# (es útil hacer referencia a esta
tabla al leer código de ejemplo de Xamarin.Android):

C# DESC RIP C IÓ N

as Realiza conversiones entre tipos de referencia compatibles o


tipos que aceptan valores NULL.

async Especifica que un método o una expresión lambda son


asincrónicos.

await Suspende la ejecución de un método hasta que se completa


una tarea.

byte Tipo entero de 8 bits sin signo.

delegate Se utiliza para encapsular un método o un método anónimo.

enum Declara una enumeración, un conjunto de constantes con


nombre.

event Declara un evento en una clase de publicador.

fixed Impide la reubicación de una variable.

get Define un método de descriptor de acceso que recupera el


valor de una propiedad.
C# DESC RIP C IÓ N

in Permite que un parámetro acepte un tipo menos derivado en


una interfaz genérica.

object Un alias para el tipo Object en .NET Framework.

out Modificador de parámetros o declaración de parámetros de


tipo genérico.

override Amplía o modifica la implementación de un miembro


heredado.

partial Declara que una definición se va a dividir en varios archivos o


que se va a separar una definición de método de su
implementación.

readonly Declara que un miembro de clase puede asignarse solo en el


momento de la declaración o mediante el constructor de clase.

ref Hace que un argumento se pase por referencia en lugar de


por valor.

set Define un método de descriptor de acceso que establece el


valor de una propiedad.

string Alias para el tipo String en .NET Framework.

struct Un tipo de valor que encapsula un grupo de variables


relacionadas.

typeof Obtiene el tipo de un objeto.

var Declara una variable local con tipo implícito.

value Hace referencia al valor que el código de cliente desea asignar


a una propiedad.

virtual Permite la invalidación de un método en una clase derivada.

Interoperación con código de Java existente


Si dispone de una funcionalidad de Java existente que no desea convertir a C#, puede volver a usar las bibliotecas
de Java existentes en las aplicaciones de Xamarin.Android con estas dos técnicas:
Crear una biblioteca de enlaces de Java – Con este enfoque, se utilizan las herramientas de Xamarin
para generar contenedores de C# que incluyen tipos de Java. Estos contenedores se denominan enlaces.
Como resultado, la aplicación de Xamarin.Android puede usar el archivo .jar con una llamada a estos
contenedores.
Java Native Interface – Java Native Interface (JNI) es un marco que permite a las aplicaciones de C#
llamar al código de Java o recibir llamadas de este código.
Para obtener más información sobre estas técnicas, vea Java Integration Overview (Información general sobre la
integración de Java).
Información adicional
La guía de programación de C# es un recurso muy útil para iniciarse en el aprendizaje del lenguaje de
programación C#, y puede usar la referencia de C# para buscar características particulares del lenguaje C#.
De la misma forma que el conocimiento de Java tiene al menos tanto que ver con la familiaridad con las bibliotecas
de clases de Java como con el conocimiento del lenguaje Java, el conocimiento práctico de C# requiere alguna
familiaridad con .NET Framework. El paquete de aprendizaje Pasar a C# y .NET Framework, para desarrolladores de
Java de Microsoft es un buen recurso para aprender más sobre .NET Framework desde la perspectiva de Java (al
mismo tiempo que se adquiere un conocimiento más profundo de C#).
Cuando esté listo para abordar el primer proyecto de Xamarin.Android en C#, nuestra serie Hello, Android puede
facilitar la compilación de la primera aplicación de Xamarin.Android y ayudar a profundizar los conocimientos de
los aspectos fundamentales del desarrollo de aplicaciones de Android con Xamarin.

Resumen
Este artículo ofrece una introducción al entorno de programación de C# en Xamarin.Android desde la perspectiva
de los desarrolladores de Java. Se indican las similitudes entre C# y Java, además de explicar sus diferencias
prácticas. Aborda los ensamblados y los espacios de nombres, se explica cómo importar tipos externos y se ofrece
información general sobre las diferencias de los modificadores de acceso, los genéricos, la derivación de clases, la
llamada a métodos de clases base, la invalidación de métodos y el control de eventos. Se presentan características
de C# que no están disponibles en Java, como las propiedades, la programación asincrónica de async / await , las
expresiones lambda, los delegados de C# y el sistema de control de eventos de C#. Incluye tablas de palabras claves
importantes de C#, se explica cómo interoperar con bibliotecas de Java existentes y se ofrecen vínculos a
documentación relacionada realizar un estudio adicional.

Vínculos relacionados
Información general sobre la integración de Java
Guía de programación de C#
Referencia de C#
Pasar a C# y .NET Framework, para desarrolladores de Java
Xamarin para desarrolladores de Objective-C
18/12/2020 • 3 minutes to read • Edit Online

Xamarin ofrece a los desarrolladores que tienen iOS como destino una ruta trasladar su código que no es de
interfaz de usuario a C# independiente de la plataforma, de manera que se pueda utilizar en cualquier lugar en que
C# esté disponible, como Android a través de Xamarin.Android y las diferentes versiones de Windows. Sin
embargo, el mero hecho de usar C# con Xamarin no implica que pueda aprovechar sus habilidades y el código de
Objective-C. De hecho, conocer Objective-C lo convierte en un mejor desarrollador de Xamarin.iOS porque
Xamarin expone todas las API de plataforma nativas de iOS y OS X que conoce y que le encantan, como UIKit, Core
Animation, Core Foundation y Core Graphics, por nombrar algunas. Al mismo tiempo, obtiene la eficacia del
lenguaje C#, incluidas características como LINQ y Generics, así como completas bibliotecas de clase base de .NET
para usar en sus aplicaciones nativas.
Además, Xamarin le permite aprovechar los recursos de Objective-C existentes mediante una tecnología que se
conoce como "enlaces". Basta con crear una biblioteca estática en Objective C y exponerla a C# a través de un
enlace, como se muestra en el diagrama siguiente:

Esto no tiene por qué limitarse al código que no es de interfaz de usuario. Los enlaces pueden exponer también el
código de interfaz de usuario desarrollado en Objective-C.

Transición desde Objective-C


Encontrará una gran cantidad de información en nuestro sitio de documentación que le ayudará a facilitar la
transición a Xamarin, donde se muestra cómo integrar código de C# con lo que ya conoce. Estos son algunos de los
principales materiales por los que puede comenzar:
Manual de C# para los desarrolladores Objective-C: un breve manual para los desarrolladores de Objective-C
que desean pasarse a Xamarin y el lenguaje C#.
Tutorial: Enlace de una biblioteca de Objective-C: un tutorial detallado para volver a usar código existente de
Objective-C en una aplicación de Xamarin.iOS.

Enlace de Objective-C
Cuando haya adquirido unas nociones generales sobre las diferencias y similitudes entre C# y Objective-C y haya
trabajando con el tutorial de enlace anterior, estará listo para la transición a la plataforma Xamarin. Si quiere
profundizar en la materia, puede encontrar más información detallada sobre las tecnologías de enlace de
Xamarin.iOS, incluida una exhaustiva referencia de enlace, en la sección Enlace de Objective-C.
Desarrollo multiplataforma
Finalmente, después de pasarse a Xamarin.iOS, seguramente querrá consultar la guía multiplataforma de que
disponemos, con estudios de caso de aplicaciones de referencia que hemos desarrollado y procedimientos
recomendados para crear código multiplataforma reutilizable, contenidos en la sección Building Cross Platform
Applications (Generación de aplicaciones multiplataforma).
:::no-loc(Xamarin.Forms)::: Conceptos básicos de
XAML
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
El lenguaje de marcado de aplicaciones extensible (XAML) es un lenguaje basado en XML creado por Microsoft
como una alternativa a código de programación para la creación de instancias e inicialización de objetos, y la
organización de esos objetos en jerarquías de elementos primarios y secundarios. XAML se ha adaptado a varias
tecnologías de .NET Framework, pero ha encontrado su utilidad más importante para definir el diseño de las
interfaces de usuario en el Windows Presentation Foundation (WPF), Silverlight, el Windows Runtime y el
Plataforma universal de Windows (UWP).
XAML permite a los desarrolladores definir interfaces de usuario en :::no-loc(Xamarin.Forms)::: aplicaciones con
marcado en lugar de código. XAML no se requiere nunca en un :::no-loc(Xamarin.Forms)::: programa, pero suele
ser más conciso y visualmente coherente que el código equivalente, y que puede ser de utilidad. XAML está bien
adaptado para su uso con la popular arquitectura de aplicación MVVM (modelo-vista-ViewModel): XAML define la
vista que está vinculada al código ViewModel a través de enlaces de datos basados en XAML.
Dentro de un archivo XAML, el :::no-loc(Xamarin.Forms)::: desarrollador puede definir interfaces de usuario
mediante todas las :::no-loc(Xamarin.Forms)::: vistas, diseños y páginas, así como clases personalizadas. El archivo
XAML se puede compilar o incrustar en el ejecutable. En cualquier caso, la información XAML se analiza en tiempo
de compilación para buscar objetos con nombre y de nuevo en tiempo de ejecución para crear instancias e
inicializar objetos, y para establecer vínculos entre estos objetos y el código de programación.
XAML tiene varias ventajas con respecto al código equivalente:
XAML suele ser más conciso y legible que el código equivalente.
La jerarquía de elementos primarios y secundarios inherente en XML permite que XAML imite con mayor
claridad visual de la jerarquía de elementos primarios y secundarios de los objetos de la interfaz de usuario.
El código XAML puede escribirse fácilmente a los programadores, pero también se presta a ser herramienta y
generarse mediante herramientas de diseño visual.
También hay desventajas, principalmente relacionadas con las limitaciones que son intrínsecas a los lenguajes de
marcado:
XAML no puede contener código. Todos los controladores de eventos se deben definir en un archivo de código.
XAML no puede contener bucles para el procesamiento repetitivo. (Sin embargo, varios :::no-
loc(Xamarin.Forms)::: objetos visuales, en concreto, ListView pueden generar varios elementos secundarios
basados en los objetos de su ItemsSource colección).
XAML no puede contener procesamiento condicional (sin embargo, un enlace de datos puede hacer referencia
a un convertidor de enlace basado en código que permita de forma eficaz algún procesamiento condicional).
Por lo general, XAML no puede crear instancias de clases que no definen un constructor sin parámetros. (Sin
embargo, a veces hay una forma de evitar esta restricción).
Normalmente, XAML no puede llamar a métodos. (De nuevo, esta restricción a veces se puede solucionar).
Todavía no hay un diseñador visual para generar XAML en :::no-loc(Xamarin.Forms)::: aplicaciones. Todo XAML
debe estar escrito a mano, pero hay un controlador de vista previa de XAML. Es posible que los programadores
nuevos en XAML deseen compilar y ejecutar sus aplicaciones con frecuencia, especialmente después de cualquier
cosa que podría no ser correcta. Incluso los desarrolladores con mucha experiencia en XAML saben que la
experimentación es gratificante.
XAML es básicamente XML, pero XAML tiene algunas características de sintaxis únicas. Las más importantes son
las siguientes:
Elementos de propiedad
Propiedades adjuntas
Extensiones de marcado
Estas características no son extensiones XML. XAML es completamente XML legal. Pero estas características de
sintaxis XAML usan XML de maneras únicas. Se describen en detalle en los artículos siguientes, que concluyen con
una introducción al uso de XAML para implementar MVVM.

Requisitos
En este artículo se supone que está familiarizado con :::no-loc(Xamarin.Forms)::: . En este artículo también se da
por hecho que está familiarizado con XML, incluida la descripción del uso de las declaraciones de espacios de
nombres XML y el elemento de términos, la etiqueta y el atributo.
Cuando esté familiarizado con :::no-loc(Xamarin.Forms)::: y XML, empiece a leer la parte 1. Introducción con XAML.

Vínculos relacionados
XamlSamples
Crear Mobile Apps libro
Ejemplos de :::no-loc(Xamarin.Forms):::

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Parte 1. Introducción a XAML
18/12/2020 • 31 minutes to read • Edit Online

Descargar el ejemplo
En una :::no-loc(Xamarin.Forms)::: aplicación, XAML se usa principalmente para definir el contenido visual de una
página y funciona junto con un archivo de código subyacente de C#.
El archivo de código subyacente proporciona compatibilidad de código para el marcado. Juntos, estos dos
archivos contribuyen a una nueva definición de clase que incluye vistas secundarias e inicialización de
propiedades. En el archivo XAML, se hace referencia a las clases y propiedades con atributos y elementos XML, y
se establecen vínculos entre el marcado y el código.

Crear la solución
Para empezar a editar el primer archivo XAML, use Visual Studio o Visual Studio para Mac para crear una nueva
:::no-loc(Xamarin.Forms)::: solución. (Seleccione la pestaña siguiente correspondiente a su entorno).
Visual Studio
Visual Studio para Mac
En Windows, inicie Visual Studio 2019 y, en la ventana Inicio, haga clic en crear un nuevo proyecto para crear
un nuevo proyecto:

En la ventana Crear un proyecto nuevo , seleccione Móvil en la lista desplegable Tipo de proyecto , elija la
plantilla Aplicación móvil (:::no-loc(Xamarin.Forms):::) y haga clic en el botón Siguiente :
En la ventana configurar el nuevo proyecto , establezca el nombre del proyecto en XamlSamples (o el que
prefiera) y haga clic en el botón crear .
En el cuadro de diálogo nueva aplicación multiplataforma , haga clic en en blanco y haga clic en el botón
Aceptar :

Se crean cuatro proyectos en la solución: XamlSamples .net Standard Library, XamlSamples. Android ,
XamlSamples. iOS y la solución plataforma universal de Windows, XamlSamples. UWP .
Después de crear la solución XamlSamples , es posible que desee probar el entorno de desarrollo seleccionando
los distintos proyectos de plataforma como proyecto de inicio de la solución y compilando e implementando la
aplicación simple creada por la plantilla de proyecto en los emuladores de teléfono o en dispositivos reales.
A menos que necesite escribir código específico de la plataforma, el proyecto Shared XamlSamples .net Standard
Library es donde va a gastar prácticamente todo el tiempo de programación. Estos artículos no se tratarán fuera
de ese proyecto.
Anatomía de un archivo XAML
Dentro de la biblioteca de .NET Standard de XamlSamples hay un par de archivos con los nombres siguientes:
App. Xaml , el archivo XAML; etc
App.Xaml.CS , un archivo de código subyacente de C# asociado al archivo XAML.
Deberá hacer clic en la flecha situada junto a app. Xaml para ver el archivo de código subyacente.
Tanto app. Xaml como app.Xaml.CS contribuyen a una clase denominada App que se deriva de Application .
La mayoría de las demás clases con archivos XAML contribuyen a una clase que deriva de ContentPage ; esos
archivos usan XAML para definir el contenido visual de una página completa. Esto se aplica a los otros dos
archivos del proyecto XamlSamples :
Mainpage. Xaml , el archivo XAML; etc
Mainpage.Xaml.CS , el archivo de código subyacente de C#.
El archivo mainpage. Xaml tiene el siguiente aspecto (aunque el formato puede ser un poco diferente):

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.MainPage">

<StackLayout>
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin Forms!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>

</ContentPage>

Las dos declaraciones de espacio de nombres XML ( xmlns ) hacen referencia a los identificadores URI, el primero
aparentemente en el sitio web de Xamarin y el segundo en Microsoft. No moleste a comprobar a qué apuntan los
URI. No hay nada. Son simplemente los URI que pertenecen a Xamarin y Microsoft, y básicamente funcionan
como identificadores de versión.
La primera declaración de espacio de nombres XML significa que las etiquetas definidas dentro del archivo XAML
sin prefijo hacen referencia a las clases de :::no-loc(Xamarin.Forms)::: , por ejemplo ContentPage . La segunda
declaración de espacio de nombres define un prefijo de x . Se usa para varios elementos y atributos que son
intrínsecos al propio XAML y que son compatibles con otras implementaciones de XAML. Sin embargo, estos
elementos y atributos son ligeramente diferentes en función del año incrustado en el URI. :::no-
loc(Xamarin.Forms)::: admite la especificación XAML 2009, pero no todo.
La local declaración del espacio de nombres permite tener acceso a otras clases desde el proyecto de biblioteca
de .net Standard.
Al final de la primera etiqueta, x se usa el prefijo para un atributo denominado Class . Dado que el uso de este
x prefijo es prácticamente universal para el espacio de nombres XAML, los atributos XAML como Class se
conocen casi siempre como x:Class .
El x:Class atributo especifica un nombre de clase .net completo: la MainPage clase en el XamlSamples espacio de
nombres. Esto significa que este archivo XAML define una nueva clase denominada MainPage en el XamlSamples
espacio de nombres que se deriva de ContentPage , la etiqueta en la que x:Class aparece el atributo.
El x:Class atributo solo puede aparecer en el elemento raíz de un archivo XAML para definir una clase de C#
derivada. Esta es la única clase nueva que se define en el archivo XAML. Todo lo demás que aparece en el archivo
XAML se crea simplemente con instancias de las clases existentes y se inicializa.
El archivo mainpage.Xaml.CS tiene el siguiente aspecto (aparte de las using directivas no utilizadas):

using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
}

La MainPage clase se deriva de ContentPage , pero Fíjese en la partial definición de clase. Esto sugiere que
debería haber otra definición de clase parcial para MainPage , pero donde es. ¿Y qué es ese InitializeComponent
método?
Cuando Visual Studio compila el proyecto, analiza el archivo XAML para generar un archivo de código de C#. Si
busca en el directorio XamlSamples\XamlSamples\obj\Debug , encontrará un archivo denominado
XamlSamples.mainpage.Xaml.g.CS . ' G ' significa que se ha generado. Esta es la otra definición de clase parcial
de MainPage que contiene la definición del InitializeComponent método al que se llama desde el MainPage
constructor. Estas dos MainPage definiciones de clase parcial se pueden compilar a su vez. En función de si el
XAML está compilado o no, el archivo XAML o un formato binario del archivo XAML se incrustan en el ejecutable.
En tiempo de ejecución, el código del proyecto de plataforma concreto llama a un LoadApplication método,
pasándole una nueva instancia de la App clase en la biblioteca de .net Standard. App Crea instancias del
constructor de clase MainPage . El constructor de esa clase llama InitializeComponent a, que después llama al
LoadFromXaml método que extrae el archivo XAML (o su binario compilado) de la biblioteca de .net Standard.
LoadFromXaml Inicializa todos los objetos definidos en el archivo XAML, los conecta todos juntos en las relaciones
de elementos primarios y secundarios, asocia los controladores de eventos definidos en el código a los eventos
establecidos en el archivo XAML y establece el árbol resultante de objetos como el contenido de la página.
Aunque normalmente no es necesario dedicar mucho tiempo a los archivos de código generados, a veces se
producen excepciones en tiempo de ejecución en el código de los archivos generados, por lo que debe estar
familiarizado con ellos.
Al compilar y ejecutar este programa, el Label elemento aparece en el centro de la página, como sugiere el XAML:
Para los objetos visuales más interesantes, lo único que necesita es XAML más interesante.

Agregar nuevas páginas XAML


Visual Studio
Visual Studio para Mac
Para agregar otras clases basadas en XAML ContentPage al proyecto, seleccione el proyecto de biblioteca
XamlSamples .net Standard, haga clic con el botón derecho y seleccione Agregar > nuevo elemento.... En el
cuadro de diálogo Agregar nuevo elemento , seleccione elementos de Visual C# > :::no-
loc(Xamarin.Forms)::: página de contenido de > (no página de contenido (C#) , que crea una página de
solo código, o una vista de contenido , que no es una página). Asigne un nombre a la página, por ejemplo,
HelloXamlPage :

Dos archivos se agregan al proyecto, HelloXamlPage. Xaml y el archivo de código subyacente


HelloXamlPage.Xaml.CS .

Establecer el contenido de la página


Edite el archivo HelloXamlPage. Xaml para que las únicas etiquetas sean las ContentPage de
ContentPage.Content y:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.HelloXamlPage">
<ContentPage.Content>

</ContentPage.Content>
</ContentPage>

Las ContentPage.Content etiquetas forman parte de la sintaxis única de XAML. En primer lugar, es posible que
parezca que el código XML no es válido, pero son válidos. El punto no es un carácter especial en XML.
Las ContentPage.Content etiquetas se denominan etiquetas de elemento de propiedad . Content es una propiedad
de ContentPage y, generalmente, se establece en una vista única o un diseño con vistas secundarias.
Normalmente, las propiedades se convierten en atributos en XAML, pero sería difícil establecer un Content
atributo en un objeto complejo. Por ese motivo, la propiedad se expresa como un elemento XML que se compone
del nombre de la clase y el nombre de la propiedad separados por un punto. Ahora la Content propiedad se
puede establecer entre las ContentPage.Content etiquetas, de la siguiente manera:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.HelloXamlPage"
Title="Hello XAML Page">
<ContentPage.Content>

<Label Text="Hello, XAML!"


VerticalOptions="Center"
HorizontalTextAlignment="Center"
Rotation="-15"
IsVisible="true"
FontSize="Large"
FontAttributes="Bold"
TextColor="Blue" />

</ContentPage.Content>
</ContentPage>

Observe también que se ha Title establecido un atributo en la etiqueta raíz.


En este momento, la relación entre las clases, las propiedades y el XML debe ser evidente: una :::no-
loc(Xamarin.Forms)::: clase (como ContentPage o Label ) aparece en el archivo XAML como un elemento XML. Las
propiedades de esa clase, Title incluidas ContentPage las propiedades en y siete, Label normalmente aparecen
como atributos XML.
Existen muchos métodos abreviados para establecer los valores de estas propiedades. Algunas propiedades son
tipos de datos básicos: por ejemplo, Title las Text propiedades y son de tipo, String Rotation es de tipo
Double y IsVisible (que es de true forma predeterminada y se establece aquí solo para la ilustración) es de
tipo Boolean .
La HorizontalTextAlignment propiedad es de tipo TextAlignment , que es una enumeración. Para una propiedad de
cualquier tipo de enumeración, todo lo que necesita proporcionar es un nombre de miembro.
En el caso de las propiedades de tipos más complejos, sin embargo, los convertidores se utilizan para analizar el
código XAML. Se trata de clases de :::no-loc(Xamarin.Forms)::: que derivan de TypeConverter . Muchos son clases
públicas, pero otros no. Para este archivo XAML concreto, algunas de estas clases desempeñan un papel en
segundo plano:
LayoutOptionsConverter para la VerticalOptions propiedad
FontSizeConverter para la FontSize propiedad
ColorTypeConverter para la TextColor propiedad

Estos convertidores rigen la sintaxis permitida de los valores de propiedad.


ThicknessTypeConverter Puede controlar uno, dos o cuatro números separados por comas. Si se proporciona un
número, se aplica a los cuatro lados. Con dos números, el primero es el relleno izquierdo y derecho, y el segundo
es superior e inferior. Hay cuatro números en el orden izquierdo, superior, derecho e inferior.
LayoutOptionsConverter Puede convertir los nombres de los campos estáticos públicos de la LayoutOptions
estructura en valores de tipo LayoutOptions .
FontSizeConverter Puede controlar un NamedSize miembro o un tamaño de fuente numérico.
ColorTypeConverterAcepta los nombres de los campos estáticos públicos de la Color estructura o los valores
RGB hexadecimales, con o sin canal alfa, precedidos por un signo de número (#). Esta es la sintaxis sin un canal
alfa:
TextColor="#rrggbb"

Cada una de las letras pequeñas es un dígito hexadecimal. Aquí se muestra cómo se incluye un canal alfa:
TextColor="#aarrggbb">

En el canal alfa, tenga en cuenta que FF es totalmente opaco y 00 es completamente transparente.


Otros dos formatos permiten especificar un solo dígito hexadecimal para cada canal:
TextColor="#rgb" TextColor="#argb"

En estos casos, el dígito se repite para formar el valor. Por ejemplo, #CF3 es el color RGB CC-FF-33.

Navegación de páginas
Al ejecutar el programa XamlSamples , MainPage se muestra. Para ver el nuevo, HelloXamlPage puede
establecerlo como la nueva página de inicio en el archivo app.Xaml.CS o navegar a la nueva página desde
MainPage .

Para implementar la navegación, primero cambie el código en el constructor app.Xaml.CS para que
NavigationPage se cree un objeto:

public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}

En el constructor mainpage.Xaml.CS , puede crear un sencillo Button y usar el controlador de eventos para
navegar a HelloXamlPage :
public MainPage()
{
InitializeComponent();

Button button = new Button


{
Text = "Navigate!",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};

button.Clicked += async (sender, args) =>


{
await Navigation.PushAsync(new HelloXamlPage());
};

Content = button;
}

Al establecer la Content propiedad de la página, se reemplaza el valor de la Content propiedad en el archivo


XAML. Al compilar e implementar la nueva versión de este programa, aparece un botón en la pantalla. Al
presionarlo se navega a HelloXamlPage . Esta es la página resultante en iPhone, Android y UWP:

Puede volver a MainPage usar el botón atrás< en iOS, con la flecha izquierda en la parte superior de la página o
en la parte inferior del teléfono en Android, o mediante la flecha izquierda en la parte superior de la página en
Windows 10.
No dude en experimentar con el código XAML para diferentes maneras de representar Label . Si necesita insertar
caracteres Unicode en el texto, puede usar la sintaxis XML estándar. Por ejemplo, para poner el saludo en Comillas
inteligentes, use:
<Label Text="&#x201C;Hello, XAML!&#x201D;" … />

Este es su aspecto:
Interacciones de código y XAML
El ejemplo HelloXamlPage contiene solo un único Label en la página, pero esto es muy inusual. La mayoría
ContentPage de los derivados establecen la Content propiedad en un diseño de algún tipo, como StackLayout .
La Children propiedad de StackLayout se define como de tipo IList<View> , pero realmente es un objeto de tipo
ElementCollection<View> y esa colección se puede rellenar con varias vistas u otros diseños. En XAML, estas
relaciones de elementos primarios y secundarios se establecen con la jerarquía XML normal. A continuación se
muestra un archivo XAML para una nueva página denominada XamlPlusCodePage :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
Title="XAML + Code Page">
<StackLayout>
<Slider VerticalOptions="CenterAndExpand" />

<Label Text="A simple Label"


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

<Button Text="Click Me!"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

Este archivo XAML se completa sintácticamente y este es el aspecto siguiente:


Sin embargo, es probable que tenga en cuenta que este programa tiene un funcionamiento deficiente. Quizás
Slider se supone que se debe hacer que Label muestre el valor actual y que, Button probablemente, haga algo
en el programa.
Como verá en la parte 4. Aspectos básicos del enlace de datos, el trabajo de mostrar un Slider valor mediante un
Label puede controlarse completamente en XAML con un enlace de datos. Pero es útil ver primero la solución de
código. Incluso así, el control del Button clic requiere definitivamente el código. Esto significa que el archivo de
código subyacente de XamlPlusCodePage debe contener Controladores para el ValueChanged evento de Slider y
el Clicked evento de Button . Vamos a agregarlos:

namespace XamlSamples
{
public partial class XamlPlusCodePage
{
public XamlPlusCodePage()
{
InitializeComponent();
}

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)


{

void OnButtonClicked(object sender, EventArgs args)


{

}
}
}

No es necesario que estos controladores de eventos sean públicos.


De nuevo en el archivo XAML, Slider las Button etiquetas y deben incluir atributos para los ValueChanged
Clicked eventos y que hacen referencia a estos controladores:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
Title="XAML + Code Page">
<StackLayout>
<Slider VerticalOptions="CenterAndExpand"
ValueChanged="OnSliderValueChanged" />

<Label Text="A simple Label"


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

<Button Text="Click Me!"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnButtonClicked" />
</StackLayout>
</ContentPage>

Tenga en cuenta que la asignación de un controlador a un evento tiene la misma sintaxis que asignar un valor a
una propiedad.
Si el controlador del ValueChanged evento de va Slider Label a usar para mostrar el valor actual, el controlador
debe hacer referencia a ese objeto desde el código. Label Necesita un nombre, que se especifica con el x:Name
atributo.

<Label x:Name="valueLabel"
Text="A simple Label"
Font="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

El x prefijo del x:Name atributo indica que este atributo es intrínseco a XAML.
El nombre que asigna al x:Name atributo tiene las mismas reglas que los nombres de las variables de C#. Por
ejemplo, debe empezar por una letra o un carácter de subrayado y no contener espacios incrustados.
Ahora, el ValueChanged controlador de eventos puede establecer Label para mostrar el nuevo Slider valor. El
nuevo valor está disponible en los argumentos del evento:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)


{
valueLabel.Text = args.NewValue.ToString("F3");
}

O bien, el controlador podría obtener el Slider objeto que genera este evento desde el sender argumento y
obtener la Value propiedad de ese:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)


{
valueLabel.Text = ((Slider)sender).Value.ToString("F3");
}

La primera vez que se ejecuta el programa, el Label no muestra el Slider valor porque todavía no se
ValueChanged ha desencadenado el evento. Sin embargo, cualquier manipulación de Slider hace que se muestre
el valor:
Ahora para Button . Vamos a simular una respuesta a un Clicked evento mostrando una alerta con el Text del
botón. El controlador de eventos puede convertir con seguridad el sender argumento en Button y, a
continuación, tener acceso a sus propiedades:

async void OnButtonClicked(object sender, EventArgs args)


{
Button button = (Button)sender;
await DisplayAlert("Clicked!",
"The button labeled '" + button.Text + "' has been clicked",
"OK");
}

El método se define como async porque el DisplayAlert método es asincrónico y debe ir precedido por el
await operador, que devuelve cuando se completa el método. Dado que este método obtiene la Button
activación del evento desde el sender argumento, se podría usar el mismo controlador para varios botones.
Ha visto que un objeto definido en XAML puede desencadenar un evento que se administra en el archivo de
código subyacente y que el archivo de código subyacente puede tener acceso a un objeto definido en XAML
utilizando el nombre asignado a él con el x:Name atributo. Estas son las dos formas fundamentales en que
interactúan el código y XAML.
Puede obtener información adicional sobre cómo funciona XAML examinando el archivo
XamlPlusCode.Xaml.g.CS recién generado, que ahora incluye cualquier nombre asignado a cualquier x:Name
atributo como campo privado. Esta es una versión simplificada de ese archivo:

public partial class XamlPlusCodePage : ContentPage {

private Label valueLabel;

private void InitializeComponent() {


this.LoadFromXaml(typeof(XamlPlusCodePage));
valueLabel = this.FindByName<Label>("valueLabel");
}
}

La declaración de este campo permite que la variable se use libremente en cualquier parte del XamlPlusCodePage
archivo de clase parcial bajo su jurisdicción. En tiempo de ejecución, el campo se asigna después de haber
analizado el XAML. Esto significa que el valueLabel campo es null cuando se XamlPlusCodePage inicia el
constructor, pero válido después de InitializeComponent llamar a.
Después de InitializeComponent devolver el control al constructor, los objetos visuales de la página se han
construido como si se hubieran creado instancias e inicializadas en el código. El archivo XAML ya no juega ningún
rol en la clase. Puede manipular estos objetos en la página de la forma que desee, por ejemplo, agregando vistas
al StackLayout o estableciendo la Content propiedad de la página en otra cosa. Puede "recorrer el árbol"
examinando la Content propiedad de la página y los elementos de las Children colecciones de diseños. Puede
establecer propiedades en las vistas a las que se tiene acceso de esta manera, o bien asignarles los controladores
de eventos de forma dinámica.
No dude. Es la página y XAML es solo una herramienta para compilar su contenido.

Resumen
Con esta introducción, ha visto cómo un archivo XAML y un archivo de código contribuyen a una definición de
clase, y cómo interactúan los archivos XAML y de código. Sin embargo, XAML también tiene sus propias
características sintácticas únicas que permiten su uso de una manera muy flexible. Puede empezar a explorarlos en
la parte 2. Sintaxis básica de XAML.

Vínculos relacionados
XamlSamples
Parte 2. Sintaxis XAML básica
Parte 3. Extensiones de marcado XAML
Parte 4. Conceptos básicos del enlace de datos
Parte 5. Del enlace de datos a MVVM
Parte 2. Sintaxis XAML esencial
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
XAML está diseñado principalmente para crear instancias e inicializar objetos. Pero a menudo, las propiedades se
deben establecer en objetos complejos que no se pueden representar fácilmente como cadenas XML y, en
ocasiones, las propiedades definidas por una clase deben establecerse en una clase secundaria. Estas dos
necesidades requieren las características esenciales de la sintaxis XAML de los elementos de propiedad y las
propiedades adjuntas.

Elementos de propiedad
En XAML, las propiedades de las clases se establecen normalmente como atributos XML:

<Label Text="Hello, XAML!"


VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large"
TextColor="Aqua" />

Sin embargo, hay una manera alternativa de establecer una propiedad en XAML. Para probar esta alternativa con
TextColor , elimine primero la TextColor configuración existente:

<Label Text="Hello, XAML!"


VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large" />

Abra la etiqueta de elemento vacío Label separándolos en etiquetas de inicio y fin:

<Label Text="Hello, XAML!"


VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">

</Label>

Dentro de estas etiquetas, agregue las etiquetas inicial y final que consten del nombre de clase y un nombre de
propiedad separados por un punto:

<Label Text="Hello, XAML!"


VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
<Label.TextColor>

</Label.TextColor>
</Label>

Establezca el valor de la propiedad como contenido de estas etiquetas nuevas, de la siguiente manera:
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>

Estas dos maneras de especificar la TextColor propiedad son funcionalmente equivalentes, pero no usan las dos
maneras para la misma propiedad, ya que en realidad establecería la propiedad dos veces y podría ser ambigua.
Con esta nueva sintaxis, se puede introducir alguna terminología útil:
Label es un elemento de objeto. Es un :::no-loc(Xamarin.Forms)::: objeto expresado como un elemento XML.
Text , VerticalOptions FontAttributes y FontSize son atributos de propiedad. Son :::no-
loc(Xamarin.Forms)::: propiedades expresadas como atributos XML.
En ese fragmento de código final, TextColor se ha convertido en un elemento de propiedad. Es una :::no-
loc(Xamarin.Forms)::: propiedad, pero ahora es un elemento XML.
En primer lugar, la definición de los elementos de propiedad podría ser una infracción de la sintaxis XML, pero no
es así. El punto no tiene ningún significado especial en XML. En un descodificador XML, Label.TextColor es
simplemente un elemento secundario normal.
En XAML, sin embargo, esta sintaxis es muy especial. Una de las reglas para los elementos de propiedad es que
nada más puede aparecer en la Label.TextColor etiqueta. El valor de la propiedad siempre se define como
contenido entre las etiquetas de inicio y finalización del elemento de propiedad.
Puede usar la sintaxis de elementos de propiedad en más de una propiedad:

<Label Text="Hello, XAML!"


VerticalOptions="Center">
<Label.FontAttributes>
Bold
</Label.FontAttributes>
<Label.FontSize>
Large
</Label.FontSize>
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>

O bien, puede usar la sintaxis de elementos de propiedad para todas las propiedades:
<Label>
<Label.Text>
Hello, XAML!
</Label.Text>
<Label.FontAttributes>
Bold
</Label.FontAttributes>
<Label.FontSize>
Large
</Label.FontSize>
<Label.TextColor>
Aqua
</Label.TextColor>
<Label.VerticalOptions>
Center
</Label.VerticalOptions>
</Label>

En primer lugar, la sintaxis del elemento de propiedad podría parecerse a un reemplazo innecesario de tiempo de
inactividad en algo relativamente sencillo, y en estos ejemplos es ciertamente el caso.
Sin embargo, la sintaxis del elemento de propiedad se convierte en esencial cuando el valor de una propiedad es
demasiado complejo para expresarse como una cadena simple. Dentro de las etiquetas de elemento de propiedad
puede crear instancias de otro objeto y establecer sus propiedades. Por ejemplo, puede establecer explícitamente
una propiedad como VerticalOptions para un LayoutOptions valor con la configuración de la propiedad:

<Label>
...
<Label.VerticalOptions>
<LayoutOptions Alignment="Center" />
</Label.VerticalOptions>
</Label>

Otro ejemplo: Grid tiene dos propiedades denominadas RowDefinitions y ColumnDefinitions . Estas dos
propiedades son del tipo RowDefinitionCollection y ColumnDefinitionCollection , que son colecciones de
RowDefinition ColumnDefinition objetos y. Debe usar la sintaxis del elemento de propiedad para establecer estas
colecciones.
Este es el principio del archivo XAML para una GridDemoPage clase, que muestra las etiquetas del elemento de
propiedad para las RowDefinitions ColumnDefinitions colecciones y:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
Observe la sintaxis abreviada para definir celdas de tamaño automático, celdas de anchos de píxel y alto y
configuración de estrella.

Propiedades adjuntas
Acaba de observar que Grid requiere elementos de propiedad para las RowDefinitions colecciones y
ColumnDefinitions para definir las filas y columnas. Sin embargo, también debe haber alguna manera para que el
programador indique la fila y la columna donde reside cada elemento secundario de Grid .
Dentro de la etiqueta para cada elemento secundario de Grid , especifique la fila y la columna de ese elemento
secundario con los siguientes atributos:
Grid.Row
Grid.Column

Los valores predeterminados de estos atributos son 0. También puede indicar si un elemento secundario abarca
más de una fila o columna con estos atributos:
Grid.RowSpan
Grid.ColumnSpan

Estos dos atributos tienen valores predeterminados de 1.


Este es el archivo GridDemoPage. Xaml completo:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>

<Label Text="Autosized cell"


Grid.Row="0" Grid.Column="0"
TextColor="White"
BackgroundColor="Blue" />

<BoxView Color="Silver"
HeightRequest="0"
Grid.Row="0" Grid.Column="1" />

<BoxView Color="Teal"
Grid.Row="1" Grid.Column="0" />

<Label Text="Leftover space"


Grid.Row="1" Grid.Column="1"
TextColor="Purple"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />

<Label Text="Span two rows (or more if you want)"


Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"
TextColor="Yellow"
BackgroundColor="Blue"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />

<Label Text="Span two columns"


Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
TextColor="Blue"
BackgroundColor="Yellow"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />

<Label Text="Fixed 100x100"


Grid.Row="2" Grid.Column="2"
TextColor="Aqua"
BackgroundColor="Red"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />

</Grid>
</ContentPage>

Los Grid.Row Grid.Column valores de y de 0 no son necesarios, pero generalmente se incluyen para fines de
claridad.
Este es su aspecto:
Al juzgar únicamente a partir de la sintaxis, estos atributos,, Grid.Row Grid.Column Grid.RowSpan y
Grid.ColumnSpan parecen ser campos estáticos o propiedades de Grid , pero lo que es interesante, no Grid
define nada denominado Row , Column , RowSpan o ColumnSpan .
En su lugar, define cuatro propiedades enlazables denominadas RowProperty ,, ColumnProperty
Grid
RowSpanProperty y ColumnSpanProperty . Se trata de tipos especiales de propiedades enlazables conocidas como
propiedades adjuntas. Se definen mediante la Grid clase, pero se establecen en los elementos secundarios de
Grid .

Cuando desea usar estas propiedades adjuntas en el código, la Grid clase proporciona métodos estáticos
denominados SetRow , GetColumn , etc. Pero en XAML, estas propiedades adjuntas se establecen como atributos
en los elementos secundarios del Grid mediante nombres de propiedades simples.
Las propiedades adjuntas siempre son reconocibles en los archivos XAML como atributos que contienen una clase
y un nombre de propiedad separados por un punto. Se denominan propiedades adjuntas porque están definidas
por una clase (en este caso, Grid ) pero se asocian a otros objetos (en este caso, secundarios del Grid ). Durante
el diseño, Grid puede consultar los valores de estas propiedades adjuntas para saber dónde colocar cada
elemento secundario.
La AbsoluteLayout clase define dos propiedades adjuntas denominadas LayoutBounds y LayoutFlags . A
continuación se muestra un patrón de tablero de ajedrez realizado mediante las características de
posicionamiento y ajuste de tamaño proporcional de AbsoluteLayout :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.AbsoluteDemoPage"
Title="Absolute Demo Page">

<AbsoluteLayout BackgroundColor="#FF8080">
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.33, 0, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="1, 0, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0, 0.33, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.67, 0.33, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.33, 0.67, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="1, 0.67, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0, 1, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.67, 1, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />

</AbsoluteLayout>
</ContentPage>

Y aquí es:
Para algo parecido a esto, es posible que le resulte más importante usar XAML. En realidad, la repetición y la
regularidad del LayoutBounds rectángulo sugieren que es posible que se produzca mejor en el código.
Eso es ciertamente una preocupación legítima y no hay ningún problema con el equilibrio del uso del código y el
marcado al definir las interfaces de usuario. Es fácil definir algunos de los objetos visuales en XAML y, a
continuación, usar el constructor del archivo de código subyacente para agregar algunos objetos visuales más que
se puedan generar mejor en los bucles.

Propiedades de contenido
En los ejemplos anteriores, los StackLayout Grid objetos, y AbsoluteLayout se establecen en la Content
propiedad de ContentPage , y los elementos secundarios de estos diseños son realmente elementos de la
Children colección. Sin embargo Content , estas propiedades y no Children están en el archivo XAML.

En realidad, puede incluir Content las Children propiedades y como elementos de propiedad, como en el
ejemplo XamlPlusCode :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
Title="XAML + Code Page">
<ContentPage.Content>
<StackLayout>
<StackLayout.Children>
<Slider VerticalOptions="CenterAndExpand"
ValueChanged="OnSliderValueChanged" />

<Label x:Name="valueLabel"
Text="A simple Label"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Button Text="Click Me!"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnButtonClicked" />
</StackLayout.Children>
</StackLayout>
</ContentPage.Content>
</ContentPage>

La pregunta real es: ¿por qué estos elementos de propiedad no son necesarios en el archivo XAML?
Los elementos definidos en :::no-loc(Xamarin.Forms)::: para su uso en XAML pueden tener una propiedad marcada
en el ContentProperty atributo en la clase. Si busca la ContentPage clase en la documentación en línea :::no-
loc(Xamarin.Forms)::: , verá este atributo:

[:::no-loc(Xamarin.Forms):::.ContentProperty("Content")]
public class ContentPage : TemplatedPage

Esto significa que Content no se requieren etiquetas de elemento de propiedad. Se supone que el contenido XML
que aparece entre las etiquetas inicial y final se ContentPage asigna a la Content propiedad.
StackLayout , Grid , AbsoluteLayout y se RelativeLayout derivan de y, Layout<View> si busca Layout<T> en la
:::no-loc(Xamarin.Forms)::: documentación, verá otro ContentProperty atributo:

[:::no-loc(Xamarin.Forms):::.ContentProperty("Children")]
public abstract class Layout<T> : Layout ...

Que permite agregar automáticamente el contenido del diseño a la Children colección sin Children etiquetas de
elemento de propiedad explícitas.
Otras clases también tienen ContentProperty definiciones de atributos. Por ejemplo, la propiedad de contenido de
Label es Text . Consulte la documentación de la API para obtener más información.

Diferencias de plataforma con la plataforma


En las aplicaciones de una sola página, es habitual establecer la Padding propiedad en la página para evitar
sobrescribir la barra de estado de iOS. En el código, puede usar la Device.RuntimePlatform propiedad para este
propósito:
if (Device.RuntimePlatform == Device.iOS)
{
Padding = new Thickness(0, 20, 0, 0);
}

También puede hacer algo similar en XAML mediante las OnPlatform clases y On . En primer lugar, incluya los
elementos de propiedad de la Padding propiedad cerca de la parte superior de la página:

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

<ContentPage.Padding>

</ContentPage.Padding>
...
</ContentPage>

Dentro de estas etiquetas, incluya una OnPlatform etiqueta. OnPlatform es una clase genérica. Debe especificar el
argumento de tipo genérico, en este caso, Thickness , que es el tipo de Padding propiedad. Afortunadamente,
hay un atributo XAML específicamente para definir argumentos genéricos denominados x:TypeArguments . Debe
coincidir con el tipo de la propiedad que está estableciendo:

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

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">

</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>

OnPlatform tiene una propiedad denominada Platforms que es IList de On objetos. Use etiquetas de
elemento de propiedad para esa propiedad:

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

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>

</OnPlatform.Platforms>
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>

Ahora, agregue On elementos. Para cada uno de ellos, establezca la propiedad Platform y la Value propiedad en
marcado para la Thickness propiedad:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android" Value="0, 0, 0, 0" />
<On Platform="UWP" Value="0, 0, 0, 0" />
</OnPlatform.Platforms>
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>

Este marcado se puede simplificar. La propiedad de contenido de OnPlatform es Platforms , por lo que se pueden
quitar esas etiquetas de elemento de propiedad:

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

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android" Value="0, 0, 0, 0" />
<On Platform="UWP" Value="0, 0, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>

La Platform propiedad de On es de tipo IList<string> , por lo que puede incluir varias plataformas si los
valores son los mismos:

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

<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android, UWP" Value="0, 0, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>

Como Android y UWP se establecen en el valor predeterminado de Padding , se puede quitar esa etiqueta:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">

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

Esta es la manera estándar de establecer una propiedad dependiente de la plataforma Padding en XAML. Si la
Value configuración no se puede representar mediante una sola cadena, puede definir elementos de propiedad
para ella:

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

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

NOTE
La OnPlatform extensión de marcado también se puede usar en XAML para personalizar la apariencia de la interfaz de
usuario en cada plataforma. Proporciona la misma funcionalidad que las OnPlatform clases y On , pero con una
representación más concisa. Para obtener más información, consulte extensión de marcadoen la plataforma.

Resumen
Con elementos de propiedad y propiedades adjuntas, gran parte de la sintaxis básica de XAML se ha establecido.
Sin embargo, a veces es necesario establecer propiedades en los objetos de una manera indirecta, por ejemplo,
desde un diccionario de recursos. Este enfoque se trata en la siguiente parte, parte 3. Extensiones de marcado
XAML.

Vínculos relacionados
XamlSamples
Parte 1. Introducción con XAML
Parte 3. Extensiones de marcado XAML
Parte 4. Conceptos básicos del enlace de datos
Parte 5. Del enlace de datos a MVVM
Parte 3. Extensiones de marcado XAML
18/12/2020 • 21 minutes to read • Edit Online

Descargar el ejemplo
Las extensiones de marcado XAML constituyen una característica importante de XAML que permite establecer las
propiedades en objetos o valores a los que se hace referencia indirectamente desde otros orígenes. Las
extensiones de marcado XAML son especialmente importantes para compartir objetos y hacer referencia a
constantes usadas en una aplicación, pero encuentran su utilidad más grande en los enlaces de datos.

Extensiones de marcado XAML


En general, se usa XAML para establecer las propiedades de un objeto en valores explícitos, como una cadena, un
número, un miembro de enumeración o una cadena que se convierte en un valor en segundo plano.
Sin embargo, en ocasiones, las propiedades deben hacer referencia a valores definidos en otra parte, o que
podrían requerir un pequeño procesamiento por código en tiempo de ejecución. Para estos propósitos, están
disponibles las extensiones de marcado XAML.
Estas extensiones de marcado XAML no son extensiones de XML. XAML es completamente XML legal. Se
denominan "extensiones" porque están respaldadas por código en las clases que implementan IMarkupExtension
. Puede escribir sus propias extensiones de marcado personalizadas.
En muchos casos, las extensiones de marcado XAML se reconocen al instante en los archivos XAML porque
aparecen como valores de atributo delimitados por llaves: {y}, pero a veces las extensiones de marcado aparecen
en el marcado como elementos convencionales.

Recursos compartidos
Algunas páginas XAML contienen varias vistas cuyas propiedades se establecen en los mismos valores. Por
ejemplo, muchos de los valores de propiedad de estos Button objetos son los mismos:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">

<StackLayout>
<Button Text="Do this!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />

<Button Text="Do that!"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />

<Button Text="Do the other thing!"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />

</StackLayout>
</ContentPage>

Si es necesario cambiar alguna de estas propiedades, es preferible realizar el cambio una sola vez en lugar de
tres veces. Si se tratara de código, es probable que use constantes y objetos estáticos de solo lectura para ayudar
a mantener estos valores coherentes y fáciles de modificar.
En XAML, una solución popular es almacenar estos valores u objetos en un Diccionario de recursos. La
VisualElement clase define una propiedad denominada Resources de tipo ResourceDictionary , que es un
diccionario con claves de tipo string y valores de tipo object . Puede colocar objetos en este diccionario y
luego hacer referencia a ellos desde el marcado, todo en XAML.
Para usar un diccionario de recursos en una página, incluya un par de Resources etiquetas de elemento de
propiedad. Lo más conveniente es colocarlos en la parte superior de la página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">

<ContentPage.Resources>

</ContentPage.Resources>
...
</ContentPage>

También es necesario incluir explícitamente las ResourceDictionary Etiquetas:


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">

<ContentPage.Resources>
<ResourceDictionary>

</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>

Ahora se pueden agregar objetos y valores de varios tipos al Diccionario de recursos. Estos tipos deben ser
instancias. Por ejemplo, no pueden ser clases abstractas. Estos tipos también deben tener un constructor sin
parámetros público. Cada elemento requiere una clave de diccionario especificada con el x:Key atributo. Por
ejemplo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">

<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />

<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>

Estos dos elementos son valores del tipo de estructura LayoutOptions y cada uno tiene una clave única y una o
dos propiedades establecidas. En el código y el marcado, es mucho más habitual usar los campos estáticos de
LayoutOptions , pero aquí es más conveniente establecer las propiedades.

Ahora es necesario establecer las HorizontalOptions VerticalOptions propiedades y de estos botones en estos
recursos, y esto se hace con la extensión de StaticResource marcado XAML:

<Button Text="Do this!"


HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />

La StaticResource extensión de marcado siempre está delimitada por llaves e incluye la clave del diccionario.
El nombre StaticResourcelo distingue de DynamicResource , que :::no-loc(Xamarin.Forms)::: también admite.
DynamicResource es para las claves de diccionario asociadas a valores que pueden cambiar durante el tiempo de
ejecución, mientras que StaticResource obtiene acceso a los elementos del diccionario una sola vez cuando se
construyen los elementos de la página.
En el caso de la BorderWidth propiedad, es necesario almacenar un Double en el diccionario. XAML define las
etiquetas para tipos de datos comunes como x:Double y x:Int32 :

<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />

<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />

<x:Double x:Key="borderWidth">
3
</x:Double>
</ResourceDictionary>
</ContentPage.Resources>

No es necesario colocarlo en tres líneas. Esta entrada del diccionario para este ángulo de rotación solo ocupa una
línea:

<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />

<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />

<x:Double x:Key="borderWidth">
3
</x:Double>

<x:Double x:Key="rotationAngle">-15</x:Double>
</ResourceDictionary>
</ContentPage.Resources>

Se puede hacer referencia a esos dos recursos de la misma manera que los LayoutOptions valores:

<Button Text="Do this!"


HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="Red"
FontSize="24" />

En el caso de los recursos de tipo Color , puede usar las mismas representaciones de cadena que se utilizan al
asignar atributos directamente de estos tipos. Los convertidores de tipos se invocan cuando se crea el recurso.
Este es un recurso de tipo Color :

<Color x:Key="textColor">Red</Color>

A menudo, los programas establecen una FontSize propiedad en un miembro de la NamedSize enumeración
como Large . La FontSizeConverter clase funciona en segundo plano para convertirla en un valor dependiente
de la plataforma mediante el Device.GetNamedSized método. Sin embargo, al definir un recurso de tamaño de
fuente, tiene más sentido usar un valor numérico, que se muestra aquí como un x:Double tipo:
<x:Double x:Key="fontSize">24</x:Double>

Ahora todas las propiedades Text , excepto, se definen mediante la configuración de recursos:

<Button Text="Do this!"


HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />

También es posible usar OnPlatform dentro del Diccionario de recursos para definir valores diferentes para las
plataformas. A continuación se muestra cómo un OnPlatform objeto puede formar parte del Diccionario de
recursos para diferentes colores de texto:

<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>

Observe que OnPlatform obtiene un x:Key atributo porque es un objeto en el Diccionario y un x:TypeArguments
atributo porque es una clase genérica. Los iOS Android atributos, y UWP se convierten en Color valores
cuando se inicializa el objeto.
Este es el archivo XAML final completo con tres botones que obtienen acceso a seis valores compartidos:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">

<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />

<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />

<x:Double x:Key="borderWidth">3</x:Double>

<x:Double x:Key="rotationAngle">-15</x:Double>

<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>

<x:Double x:Key="fontSize">24</x:Double>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout>
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />

<Button Text="Do that!"


HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />

<Button Text="Do the other thing!"


HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />

</StackLayout>
</ContentPage>

Las capturas de pantallas comprueban el estilo coherente y el estilo que depende de la plataforma:
Aunque es más común definir la Resources colección en la parte superior de la página, tenga en cuenta que la
Resources propiedad está definida por VisualElement y puede tener Resources colecciones en otros elementos
de la página. Por ejemplo, intente agregar uno a StackLayout en este ejemplo:

<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<Color x:Key="textColor">Blue</Color>
</ResourceDictionary>
</StackLayout.Resources>
...
</StackLayout>

Descubrirá que el color del texto de los botones es ahora azul. Básicamente, cada vez que el analizador XAML
encuentra una StaticResource extensión de marcado, busca en el árbol visual y usa el primero que
ResourceDictionary encuentra la clave.

Uno de los tipos más comunes de objetos almacenados en los diccionarios de recursos es :::no-
loc(Xamarin.Forms)::: Style , que define una colección de valores de propiedad. Los estilos se describen en el
artículo estilos.
A veces, los desarrolladores nuevos en XAML preguntan si pueden colocar un elemento visual como Label o
Button en ResourceDictionary . Aunque es probable que sea posible, no tiene mucho sentido. El propósito de
ResourceDictionary es compartir objetos. No se puede compartir un elemento visual. La misma instancia no
puede aparecer dos veces en una sola página.

La extensión de marcado x:Static


A pesar de las similitudes de sus nombres, x:Static y StaticResource son muy diferentes. StaticResource
Devuelve un objeto de un diccionario de recursos mientras x:Static tiene acceso a uno de los elementos
siguientes:
un campo estático público
una propiedad estática pública
un campo de constante pública
miembro de la enumeración.
La StaticResource extensión de marcado es compatible con las implementaciones de XAML que definen un
diccionario de recursos, mientras que x:Static es una parte intrínseca de XAML, como x revela el prefijo.
Estos son algunos ejemplos que muestran cómo x:Static puede hacer referencia explícitamente a los campos
estáticos y a los miembros de enumeración:

<Label Text="Hello, XAML!"


VerticalOptions="{x:Static LayoutOptions.Start}"
HorizontalTextAlignment="{x:Static TextAlignment.Center}"
TextColor="{x:Static Color.Aqua}" />

Hasta ahora, esto no es muy impresionante. Sin embargo, la x:Static extensión de marcado también puede
hacer referencia a propiedades o campos estáticos de su propio código. Por ejemplo, esta es una AppConstants
clase que contiene algunos campos estáticos que podría querer usar en varias páginas en una aplicación:

using System;
using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
static class AppConstants
{
public static readonly Thickness PagePadding;

public static readonly Font TitleFont;

public static readonly Color BackgroundColor = Color.Aqua;

public static readonly Color ForegroundColor = Color.Brown;

static AppConstants()
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
PagePadding = new Thickness(5, 20, 5, 0);
TitleFont = Font.SystemFontOfSize(35, FontAttributes.Bold);
break;

case Device.Android:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(40, FontAttributes.Bold);
break;

case Device.UWP:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(50, FontAttributes.Bold);
break;
}
}
}
}

Para hacer referencia a los campos estáticos de esta clase en el archivo XAML, necesitará alguna manera de
indicar dentro del archivo XAML donde se encuentra este archivo. Para ello, debe tener una declaración de
espacio de nombres XML.
Recuerde que los archivos XAML creados como parte de la :::no-loc(Xamarin.Forms)::: plantilla XAML estándar
contienen dos declaraciones de espacio de nombres XML: una para tener acceso a :::no-loc(Xamarin.Forms)::: las
clases y otra para hacer referencia a etiquetas y atributos intrínsecos de XAML:
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

Necesitará declaraciones de espacio de nombres XML adicionales para tener acceso a otras clases. Cada
declaración de espacio de nombres XML adicional define un nuevo prefijo. Para tener acceso a las clases locales
de la biblioteca .NET Standard de aplicaciones compartidas, como AppConstants , los programadores de XAML
suelen usar el prefijo local . La declaración del espacio de nombres debe indicar el nombre del espacio de
nombres CLR (Common Language Runtime), también conocido como nombre del espacio de nombres .NET, que
es el nombre que aparece en una definición de C# namespace o en una using Directiva:

xmlns:local="clr-namespace:XamlSamples"

También puede definir declaraciones de espacios de nombres XML para espacios de nombres de .NET en
cualquier ensamblado al que haga referencia la biblioteca .NET Standard. Por ejemplo, este es un sys prefijo
para el espacio de nombres estándar de .net System , que se encuentra en el ensamblado netstandard . Dado
que se trata de otro ensamblado, también debe especificar el nombre del ensamblado, en este caso netstandard
:

xmlns:sys="clr-namespace:System;assembly=netstandard"

Observe que la palabra clave clr-namespace va seguida de dos puntos y el nombre del espacio de nombres de
.net, seguido de un punto y coma, la palabra clave assembly , un signo igual y el nombre del ensamblado.
Sí, a continuación se indican los dos puntos, clr-namespace pero el signo igual assembly . La sintaxis se definió
de esta manera deliberadamente: la mayoría de las declaraciones de espacios de nombres XML hacen referencia
a un URI que comienza con un nombre de esquema de URI como http , que siempre va seguido de un signo de
dos puntos. La clr-namespace parte de esta cadena está pensada para imitar esa Convención.
Ambas declaraciones de espacios de nombres se incluyen en el ejemplo StaticConstantsPage . Observe que las
BoxView dimensiones se establecen en Math.PI y Math.E , pero se escalan por un factor de 100:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.StaticConstantsPage"
Title="Static Constants Page"
Padding="{x:Static local:AppConstants.PagePadding}">

<StackLayout>
<Label Text="Hello, XAML!"
TextColor="{x:Static local:AppConstants.BackgroundColor}"
BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
Font="{x:Static local:AppConstants.TitleFont}"
HorizontalOptions="Center" />

<BoxView WidthRequest="{x:Static sys:Math.PI}"


HeightRequest="{x:Static sys:Math.E}"
Color="{x:Static local:AppConstants.ForegroundColor}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="100" />
</StackLayout>
</ContentPage>

El tamaño del resultado BoxView relativo a la pantalla depende de la plataforma:


Otras extensiones de marcado estándar
Varias extensiones de marcado son intrínsecas de XAML y se admiten en :::no-loc(Xamarin.Forms)::: los archivos
XAML. Algunos de ellos no se usan con frecuencia, pero son esenciales cuando se necesitan:
Si una propiedad tiene un valor que no es de null forma predeterminada pero desea establecerla en null ,
establézcala en la {x:Null} extensión de marcado.
Si una propiedad es de tipo Type , puede asignarla a un Type objeto utilizando la extensión de marcado
{x:Type someClass} .
Puede definir matrices en XAML mediante la extensión de x:Array marcado. Esta extensión de marcado tiene
un atributo necesario denominado Type que indica el tipo de los elementos de la matriz.
La Binding extensión de marcado se describe en la parte 4. Aspectos básicos del enlace de datos.
La RelativeSource extensión de marcado se describe en enlaces relativos.

La extensión de marcado ConstraintExpression


Las extensiones de marcado pueden tener propiedades, pero no se establecen como atributos XML. En una
extensión de marcado, los valores de propiedad se separan mediante comas y no aparecen comillas dentro de las
llaves.
Esto se puede ilustrar con la :::no-loc(Xamarin.Forms)::: extensión de marcado denominada ConstraintExpression
, que se usa con la RelativeLayout clase. Puede especificar la ubicación o el tamaño de una vista secundaria
como una constante, o en relación con un elemento primario u otra vista con nombre. La sintaxis de
ConstraintExpression permite establecer la posición o el tamaño de una vista usando una vez Factor una
propiedad de otra vista, más una Constant . Algo más complejo que requiere código.
Veamos un ejemplo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.RelativeLayoutPage"
Title="RelativeLayout Page">

<RelativeLayout>

<!-- Upper left -->


<BoxView Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Upper right -->
<BoxView Color="Green"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Lower left -->
<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />
<!-- Lower right -->
<BoxView Color="Yellow"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />

<!-- Centered and 1/3 width and height of parent -->


<BoxView x:Name="oneThird"
Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}" />

<!-- 1/3 width and height of previous -->


<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=X}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Y}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToView,
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Height,
Factor=0.33}" />
</RelativeLayout>
</ContentPage>

Quizás la lección más importante que debe tomar de este ejemplo es la sintaxis de la extensión de marcado: no
deben aparecer comillas dentro de las llaves de una extensión de marcado. Al escribir la extensión de marcado en
un archivo XAML, es natural incluir los valores de las propiedades entre comillas. Resista la tentación.
Este es el programa que se ejecuta:

Resumen
Las extensiones de marcado XAML que se muestran aquí proporcionan una compatibilidad importante para los
archivos XAML. Pero quizás la extensión de marcado XAML más valiosa es Binding , que se trata en la siguiente
parte de esta serie, parte 4. Aspectos básicos del enlace de datos.

Vínculos relacionados
XamlSamples
Parte 1. Introducción con XAML
Parte 2. Sintaxis XAML básica
Parte 4. Conceptos básicos del enlace de datos
Parte 5. Del enlace de datos a MVVM
Parte 4. Conceptos básicos del enlace de datos
18/12/2020 • 21 minutes to read • Edit Online

Descargar el ejemplo
Los enlaces de datos permiten vincular las propiedades de dos objetos para que un cambio en uno produzca un
cambio en el otro. Se trata de una herramienta muy valiosa y, mientras que los enlaces de datos se pueden
definir completamente en el código, XAML proporciona accesos directos y comodidad. Por consiguiente, una de
las extensiones de marcado más importantes en :::no-loc(Xamarin.Forms)::: es Binding.

Enlaces de datos
Los enlaces de datos conectan las propiedades de dos objetos, denominados origen y destino. En el código, se
requieren dos pasos: la BindingContext propiedad del objeto de destino debe establecerse en el objeto de
origen y SetBinding se debe llamar al método (que se usa a menudo junto con la Binding clase) en el objeto
de destino para enlazar una propiedad de ese objeto a una propiedad del objeto de origen.
La propiedad de destino debe ser una propiedad enlazable, lo que significa que el objeto de destino debe
derivarse de BindableObject . La documentación en línea :::no-loc(Xamarin.Forms)::: indica qué propiedades son
propiedades que se pueden enlazar. Una propiedad de como Label Text está asociada a la propiedad
enlazable TextProperty .
En el marcado, también debe realizar los mismos dos pasos que se requieren en el código, con la excepción de
que la Binding extensión de marcado toma el lugar de la SetBinding llamada y la Binding clase.
Sin embargo, cuando se definen los enlaces de datos en XAML, hay varias maneras de establecer el
BindingContext del objeto de destino. A veces se establece desde el archivo de código subyacente, a veces
mediante StaticResource una x:Static extensión de marcado o, y a veces como el contenido de las
BindingContext etiquetas de elemento de propiedad.

Los enlaces se usan con mayor frecuencia para conectar los objetos visuales de un programa con un modelo de
datos subyacente, normalmente en una realización de la arquitectura de la aplicación MVVM (modelo-vista-
ViewModel), como se describe en la parte 5. De los enlaces de datos a MVVM, pero también se pueden realizar
otros escenarios.

Enlaces de vista a vista


Puede definir enlaces de datos para vincular las propiedades de dos vistas en la misma página. En este caso, se
establece el BindingContext del objeto de destino mediante la x:Reference extensión de marcado.
Este es un archivo XAML que contiene una Slider y dos Label vistas, una de las cuales se gira por el Slider
valor y otra que muestra el Slider valor:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">

<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />

<Label BindingContext="{x:Reference slider}"


Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

Slider Contiene un x:Name atributo al que hacen referencia las dos Label vistas mediante la extensión de
x:Reference marcado.

La x:Reference extensión de enlace define una propiedad denominada Name para establecer en el nombre del
elemento al que se hace referencia, en este caso slider . Sin embargo, la ReferenceExtension clase que define
la x:Reference extensión de marcado también define un ContentProperty atributo para Name , lo que significa
que no se requiere explícitamente. Solo para la variedad, el primero x:Reference incluye "Name =", pero el
segundo no:

BindingContext="{x:Reference Name=slider}"

BindingContext="{x:Reference slider}"

La Binding extensión de marcado puede tener varias propiedades, al igual que BindingBase las Binding
clases y. ContentProperty Para Binding es Path , pero la parte "path =" de la extensión de marcado se puede
omitir si la ruta de acceso es el primer elemento de la extensión de Binding marcado. El primer ejemplo tiene
"path =", pero el segundo ejemplo lo omite:

Rotation="{Binding Path=Value}"

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Todas las propiedades pueden estar en una línea o dividirse en varias líneas:

Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"

Haga lo que sea conveniente.


Observe la StringFormat propiedad en la segunda Binding extensión de marcado. En :::no-
loc(Xamarin.Forms)::: , los enlaces no realizan ninguna conversión de tipo implícita y, si necesita mostrar un
objeto que no es una cadena como una cadena, debe proporcionar un convertidor de tipos o utilizar
StringFormat . En segundo plano, el String.Format método estático se usa para implementar StringFormat .
Esto puede ser un problema, ya que las especificaciones de formato de .NET incluyen llaves, que también se
utilizan para delimitar las extensiones de marcado. Esto crea un riesgo de confusión en el analizador de XAML.
Para evitarlo, coloque toda la cadena de formato entre comillas simples:

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Este es el programa en ejecución:

Modo de enlace
Una sola vista puede tener enlaces de datos en algunas de sus propiedades. Sin embargo, cada vista solo puede
tener una BindingContext , por lo que varios enlaces de datos de esa vista deben tener todas las propiedades
de referencia del mismo objeto.
La solución para este y otros problemas implica la Mode propiedad, que se establece en un miembro de la
BindingMode enumeración:

Default
OneWay : los valores se transfieren desde el origen al destino.
OneWayToSource : los valores se transfieren desde el destino al origen.
TwoWay : los valores se transfieren en ambos sentidos entre el origen y el destino.
OneTime : los datos van desde el origen hasta el destino, pero solo cuando los BindingContext cambios

En el programa siguiente se muestra un uso común de los OneWayToSource TwoWay modos de enlace y. Slider
Hay cuatro vistas diseñadas para controlar las Scale Rotate propiedades,, RotateX y RotateY de Label . En
primer lugar, parece como si estas cuatro propiedades de Label deben ser destinos de enlace de datos porque
cada una se establece mediante Slider . Sin embargo, el BindingContext de Label solo puede ser un objeto y
hay cuatro controles deslizantes diferentes.
Por ese motivo, todos los enlaces se establecen de manera aparentemente inversa: el BindingContext de cada
uno de los cuatro controles deslizantes se establece en Label , y los enlaces se establecen en las Value
propiedades de los controles deslizantes. Mediante el uso de los OneWayToSource TwoWay modos y, estas Value
propiedades pueden establecer las propiedades de origen, que son las Scale Rotate propiedades,, RotateX y
RotateY de Label :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderTransformsPage"
Padding="5"
Title="Slider Transforms Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<!-- Scaled and rotated Label -->


<Label x:Name="label"
Text="TEXT"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<!-- Slider and identifying Label for Scale -->


<Slider x:Name="scaleSlider"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Value="{Binding Scale, Mode=TwoWay}" />

<Label BindingContext="{x:Reference scaleSlider}"


Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
Grid.Row="1" Grid.Column="1"
VerticalTextAlignment="Center" />

<!-- Slider and identifying Label for Rotation -->


<Slider x:Name="rotationSlider"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Value="{Binding Rotation, Mode=OneWayToSource}" />

<Label BindingContext="{x:Reference rotationSlider}"


Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
Grid.Row="2" Grid.Column="1"
VerticalTextAlignment="Center" />

<!-- Slider and identifying Label for RotationX -->


<Slider x:Name="rotationXSlider"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Value="{Binding RotationX, Mode=OneWayToSource}" />

<Label BindingContext="{x:Reference rotationXSlider}"


Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
Grid.Row="3" Grid.Column="1"
VerticalTextAlignment="Center" />

<!-- Slider and identifying Label for RotationY -->


<Slider x:Name="rotationYSlider"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Value="{Binding RotationY, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationYSlider}"
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>

Los enlaces de tres de las Slider vistas son OneWayToSource , lo que significa que el Slider valor produce un
cambio en la propiedad de BindingContext , que es el Label denominado label . Estas tres Slider vistas
producen cambios en las Rotate RotateX propiedades, y RotateY de Label .
Sin embargo, el enlace para la Scale propiedad es TwoWay . Esto se debe a que la Scale propiedad tiene un
valor predeterminado de 1 y el uso de un TwoWay enlace hace que el Slider valor inicial se establezca en 1 en
lugar de 0. Si ese enlace era OneWayToSource , la Scale propiedad se establecería inicialmente en 0 desde el
Slider valor predeterminado. Label No sería visible y eso podría causar confusión al usuario.

NOTE
La VisualElement clase también tiene ScaleX ScaleY las propiedades y, que escalan VisualElement en el eje x e y,
respectivamente.

Enlaces y colecciones
Nada ilustra la eficacia de los enlaces de datos y XAML mejor que una plantilla ListView .
ListView define una ItemsSource propiedad de tipo IEnumerable y muestra los elementos de esa colección.
Estos elementos pueden ser objetos de cualquier tipo. De forma predeterminada, ListView usa el ToString
método de cada elemento para mostrar ese elemento. A veces, esto es exactamente lo que desea, pero en
muchos casos, ToString solo devuelve el nombre de clase completo del objeto.
Sin embargo, los elementos de la ListView colección se pueden mostrar de la forma que desee mediante el
uso de una plantilla , que implica una clase que deriva de Cell . La plantilla se clona para cada elemento de
ListView , y los enlaces de datos que se han establecido en la plantilla se transfieren a los clones individuales.

Con mucha frecuencia, querrá crear una celda personalizada para estos elementos mediante la ViewCell clase.
Este proceso es un poco confuso en el código, pero en XAML resulta muy sencillo.
En el proyecto XamlSamples se incluye una clase denominada NamedColor . Cada NamedColor objeto tiene
Name FriendlyName propiedades y de tipo string , y una Color propiedad de tipo Color . Además,
NamedColor tiene 141 campos estáticos de solo lectura de tipo Color correspondientes a los colores definidos
en la :::no-loc(Xamarin.Forms)::: Color clase. Un constructor estático crea una IEnumerable<NamedColor>
colección que contiene NamedColor objetos correspondientes a estos campos estáticos y lo asigna a su
propiedad estática pública All .
Establecer la NamedColor.All propiedad estática en ItemsSource de un ListView es fácil con la extensión de
x:Static marcado:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">

<ListView ItemsSource="{x:Static local:NamedColor.All}" />

</ContentPage>

La presentación resultante establece que los elementos son realmente de tipo XamlSamples.NamedColor :

No es mucha información, pero ListView es desplazable y seleccionable.


Para definir una plantilla para los elementos, querrá dividir la ItemTemplate propiedad como un elemento de
propiedad y establecerla en un DataTemplate , que luego hace referencia a un ViewCell . En la View propiedad
de puede ViewCell definir un diseño de una o varias vistas para mostrar cada elemento. Este es un ejemplo
sencillo:

<ListView ItemsSource="{x:Static local:NamedColor.All}">


<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding FriendlyName}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
NOTE
El origen de enlace para las celdas y los elementos secundarios de las celdas es la ListView.ItemsSource colección.

El Label elemento se establece en la View propiedad de ViewCell . (Las ViewCell.View etiquetas no son
necesarias porque la View propiedad es el contenido de ViewCell .) Este marcado muestra la FriendlyName
propiedad de cada NamedColor objeto:

Mucho mejor. Ahora todo lo que se necesita es spruce de la plantilla de elemento con más información y el
color real. Para admitir esta plantilla, algunos valores y objetos se han definido en el Diccionario de recursos de
la página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">

<ContentPage.Resources>
<ResourceDictionary>
<OnPlatform x:Key="boxSize"
x:TypeArguments="x:Double">
<On Platform="iOS, Android, UWP" Value="50" />
</OnPlatform>

<OnPlatform x:Key="rowHeight"
x:TypeArguments="x:Int32">
<On Platform="iOS, Android, UWP" Value="60" />
</OnPlatform>

<local:DoubleToIntConverter x:Key="intConverter" />

</ResourceDictionary>
</ContentPage.Resources>

<ListView ItemsSource="{x:Static local:NamedColor.All}"


RowHeight="{StaticResource rowHeight}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="5, 5, 0, 5"
Orientation="Horizontal"
Spacing="15">
<BoxView WidthRequest="{StaticResource boxSize}"
HeightRequest="{StaticResource boxSize}"
Color="{Binding Color}" />

<StackLayout Padding="5, 0, 0, 0"


VerticalOptions="Center">

<Label Text="{Binding FriendlyName}"


FontAttributes="Bold"
FontSize="Medium" />

<StackLayout Orientation="Horizontal"
Spacing="0">
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />

<Label Text="{Binding Color.G,


Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G={0:X2}'}" />

<Label Text="{Binding Color.B,


Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B={0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

Observe el uso de OnPlatform para definir el tamaño de un BoxView y el alto de las ListView filas. Aunque los
valores de todas las plataformas son los mismos, el marcado se puede adaptar fácilmente a otros valores para
ajustar la pantalla.

Enlace de convertidores de valores


En el archivo XAML de demostración de ListView anterior se muestran las R propiedades individuales, G y
B de la :::no-loc(Xamarin.Forms)::: Color estructura. Estas propiedades son del tipo double y van de 0 a 1. Si
desea mostrar los valores hexadecimales, no se puede usar StringFormat con una especificación de formato
"x2". Eso solo sirve para enteros y, además, los double valores se deben multiplicar por 255.
Este pequeño problema se resolvió con un convertidor de valores , también denominado convertidor de enlace.
Se trata de una clase que implementa la IValueConverter interfaz, lo que significa que tiene dos métodos
denominados Convert y ConvertBack . Convert Se llama al método cuando se transfiere un valor desde el
origen al destino; ConvertBack se llama al método para las transferencias del destino al origen en los
OneWayToSource TwoWay enlaces o:
using System;
using System.Globalization;
using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
class DoubleToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double multiplier;

if (!Double.TryParse(parameter as string, out multiplier))


multiplier = 1;

return (int)Math.Round(multiplier * (double)value);


}

public object ConvertBack(object value, Type targetType,


object parameter, CultureInfo culture)
{
double divider;

if (!Double.TryParse(parameter as string, out divider))


divider = 1;

return ((double)(int)value) / divider;


}
}
}

El ConvertBack método no desempeña un rol en este programa porque los enlaces son solo una forma de
origen a destino.
Un enlace hace referencia a un convertidor de enlace con la Converter propiedad. Un convertidor de enlaces
también puede aceptar un parámetro especificado con la ConverterParameter propiedad. Para algunas
versatilidad, este es el modo en que se especifica el multiplicador. El convertidor de enlace comprueba si el
parámetro de convertidor tiene un double valor válido.
Se crea una instancia del convertidor en el Diccionario de recursos para que se pueda compartir entre varios
enlaces:

<local:DoubleToIntConverter x:Key="intConverter" />

Tres enlaces de datos hacen referencia a esta instancia única. Tenga en cuenta que la Binding extensión de
marcado contiene una extensión de marcado incrustada StaticResource :

<Label Text="{Binding Color.R,


Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />

Este es el resultado:
ListView Es bastante sofisticado para controlar los cambios que podrían producirse dinámicamente en los
datos subyacentes, pero solo si se realizan determinados pasos. Si la colección de elementos asignada a la
ItemsSource propiedad de ListView cambia durante el tiempo de ejecución, es decir, si se pueden agregar o
quitar elementos de la colección, utilice una ObservableCollection clase para estos elementos.
ObservableCollection implementa la INotifyCollectionChanged interfaz e ListView instalará un controlador
para el CollectionChanged evento.
Si las propiedades de los elementos cambian durante el tiempo de ejecución, los elementos de la colección
deben implementar la INotifyPropertyChanged interfaz y los cambios de señal en los valores de propiedad
mediante el PropertyChanged evento. Esto se muestra en la siguiente parte de esta serie, parte 5. Del enlace de
datos a MVVM.

Resumen
Los enlaces de datos proporcionan un mecanismo eficaz para vincular propiedades entre dos objetos dentro de
una página o entre objetos visuales y datos subyacentes. Pero cuando la aplicación comienza a trabajar con
orígenes de datos, un patrón de arquitectura de aplicación popular comienza a surgir como un paradigma útil.
Esto se trata en la parte 5. De enlaces de datos a MVVM.

Vínculos relacionados
XamlSamples
Parte 1. Introducción con XAML (ejemplo)
Parte 2. Sintaxis XAML esencial (ejemplo)
Parte 3. Extensiones de marcado XAML (ejemplo)
Parte 5. De enlace de datos a MVVM (ejemplo)
Parte 5. Enlaces de datos a MVVM
18/12/2020 • 22 minutes to read • Edit Online

Descargar el ejemplo
El patrón arquitectónico Model-View-ViewModel (MVVM) se inventó pensando en XAML. El patrón exige una
separación entre tres niveles de software: la interfaz de usuario de XAML, denominada vista; los datos
subyacentes, denominados modelo; y un intermediario entre la vista y el modelo, denominado ViewModel. A
menudo, la vista y el ViewModel se conectan a través de los enlaces de datos definidos en el archivo XAML. El
BindingContext de la vista suele ser una instancia de ViewModel.

Un ViewModel sencillo
Como introducción a ViewModels, echemos un vistazo primero a un programa sin ninguno. Anteriormente,
vimos cómo definir una nueva declaración de espacio de nombres XML para permitir que un archivo XAML
haga referencia a clases de otros ensamblados. Este es un programa que define una declaración de espacio de
nombres XML para el System espacio de nombres:

xmlns:sys="clr-namespace:System;assembly=netstandard"

El programa puede usar x:Static para obtener la fecha y hora actuales de la DateTime.Now propiedad estática
y establecer ese DateTime valor en BindingContext en un StackLayout :

<StackLayout BindingContext="{x:Static sys:DateTime.Now}" …>

BindingContext es una propiedad especial: cuando se establece BindingContext en un elemento, lo heredan


todos los elementos secundarios de ese elemento. Esto significa que todos los elementos secundarios de
StackLayout tienen este mismo BindingContext y pueden contener enlaces simples a las propiedades de ese
objeto.
En el programa de fecha y hora de una captura , dos de los elementos secundarios contienen enlaces a las
propiedades de ese DateTime valor, pero otros dos elementos secundarios contienen enlaces a los que parece
que falta una ruta de acceso de enlace. Esto significa que el DateTime propio valor se utiliza para StringFormat :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.OneShotDateTimePage"
Title="One-Shot DateTime Page">

<StackLayout BindingContext="{x:Static sys:DateTime.Now}"


HorizontalOptions="Center"
VerticalOptions="Center">

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


<Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
<Label Text="{Binding Day, StringFormat='The day is {0}'}" />
<Label Text="{Binding StringFormat='The time is {0:T}'}" />

</StackLayout>
</ContentPage>
El problema es que la fecha y la hora se establecen una vez cuando la página se compila por primera vez y
nunca cambian:

Un archivo XAML puede mostrar un reloj que siempre muestra la hora actual, pero necesita código para
ayudarlo. Al pensar en términos de MVVM, el modelo y el ViewModel son clases escritas completamente en el
código. La vista suele ser un archivo XAML que hace referencia a las propiedades definidas en el ViewModel a
través de los enlaces de datos.
Un modelo adecuado no es lo mismo que el ViewModel y un ViewModel adecuado se ignora en la vista. Sin
embargo, a menudo un programador se pone a la cola de los tipos de datos expuestos por el ViewModel en los
tipos de datos asociados a determinadas interfaces de usuario. Por ejemplo, si un modelo obtiene acceso a una
base de datos que contiene cadenas ASCII de caracteres de 8 bits, el ViewModel necesitará convertir esas
cadenas en cadenas Unicode para dar cabida al uso exclusivo de Unicode en la interfaz de usuario.
En ejemplos sencillos de MVVM (como los que se muestran aquí), a menudo no hay ningún modelo, y el patrón
implica solo una vista y ViewModel vinculados a los enlaces de datos.
Aquí se muestra un ViewModel para un reloj con solo una propiedad denominada DateTime , que actualiza esa
DateTime propiedad cada segundo:
using System;
using System.ComponentModel;
using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
class ClockViewModel : INotifyPropertyChanged
{
DateTime dateTime;

public event PropertyChangedEventHandler PropertyChanged;

public ClockViewModel()
{
this.DateTime = DateTime.Now;

Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
this.DateTime = DateTime.Now;
return true;
});
}

public DateTime DateTime


{
set
{
if (dateTime != value)
{
dateTime = value;

if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}
}

ViewModels generalmente implementa la INotifyPropertyChanged interfaz, lo que significa que la clase activa un
PropertyChanged evento cada vez que cambia una de sus propiedades. El mecanismo de enlace de datos de
:::no-loc(Xamarin.Forms)::: asocia un controlador a este PropertyChanged evento para que se le pueda notificar
cuando una propiedad cambie y mantener el destino actualizado con el nuevo valor.
Un reloj basado en este ViewModel puede ser tan sencillo como el siguiente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ClockPage"
Title="Clock Page">

<Label Text="{Binding DateTime, StringFormat='{0:T}'}"


FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.BindingContext>
<local:ClockViewModel />
</Label.BindingContext>
</Label>
</ContentPage>

Observe cómo ClockViewModel se establece en el BindingContext de las Label etiquetas de elemento de


propiedad using. Como alternativa, puede crear una instancia ClockViewModel de en una Resources colección y
establecerla en a BindingContext través de una StaticResource extensión de marcado. O bien, el archivo de
código subyacente puede crear una instancia del ViewModel.
La Binding extensión de marcado de la Text propiedad de Label da formato a la DateTime propiedad. Esta es
la pantalla:

También es posible obtener acceso a las propiedades individuales de la DateTime propiedad del ViewModel
separando las propiedades con puntos:

<Label Text="{Binding DateTime.Second, StringFormat='{0}'}" … >

MVVM interactivo
MVVM se usa a menudo con enlaces de datos bidireccionales para una vista interactiva basada en un modelo de
datos subyacente.
Esta es una clase denominada HslViewModel que convierte un Color valor en Hue Saturation los valores, y
Luminosity , y viceversa:

using System;
using System.ComponentModel;
using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
public class HslViewModel : INotifyPropertyChanged
{
double hue, saturation, luminosity;
Color color;

public event PropertyChangedEventHandler PropertyChanged;

public double Hue


{
set
{
if (hue != value)
{
hue = value;
OnPropertyChanged("Hue");
SetNewColor();
}
}
get
{
return hue;
}
}

public double Saturation


{
set
{
if (saturation != value)
{
saturation = value;
OnPropertyChanged("Saturation");
SetNewColor();
}
}
get
{
return saturation;
}
}

public double Luminosity


{
set
{
if (luminosity != value)
{
luminosity = value;
OnPropertyChanged("Luminosity");
SetNewColor();
}
}
get
{
return luminosity;
}
}

public Color Color


{
set
{
if (color != value)
{
color = value;
OnPropertyChanged("Color");

Hue = value.Hue;
Saturation = value.Saturation;
Luminosity = value.Luminosity;
}
}
get
{
return color;
}
}

void SetNewColor()
{
Color = Color.FromHsla(Hue, Saturation, Luminosity);
}

protected virtual void OnPropertyChanged(string propertyName)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Los cambios en Hue las Saturation propiedades, y Luminosity hacen que la Color propiedad cambie y los
cambios en Color hacen que las otras tres propiedades cambien. Esto podría parecer un bucle infinito, salvo
que la clase no invoca el PropertyChanged evento a menos que la propiedad haya cambiado. Esto coloca un
extremo en el bucle de comentarios no controlable en caso contrario.
El siguiente archivo XAML contiene una BoxView cuya Color propiedad está enlazada a la Color propiedad del
ViewModel y tres Slider y tres Label vistas enlazadas a las Hue propiedades, Saturation y Luminosity :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.HslColorScrollPage"
Title="HSL Color Scroll Page">
<ContentPage.BindingContext>
<local:HslViewModel Color="Aqua" />
</ContentPage.BindingContext>

<StackLayout Padding="10, 0">


<BoxView Color="{Binding Color}"
VerticalOptions="FillAndExpand" />

<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}"


HorizontalOptions="Center" />

<Slider Value="{Binding Hue, Mode=TwoWay}" />

<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}"


HorizontalOptions="Center" />

<Slider Value="{Binding Saturation, Mode=TwoWay}" />

<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}"


HorizontalOptions="Center" />

<Slider Value="{Binding Luminosity, Mode=TwoWay}" />


</StackLayout>
</ContentPage>

El enlace en cada Label es el valor predeterminado OneWay . Solo necesita mostrar el valor. Pero el enlace en
cada Slider es TwoWay . Esto permite Slider que se inicialice desde el ViewModel. Observe que la Color
propiedad se establece en Aqua cuando se crea una instancia del ViewModel. Pero un cambio en Slider
también necesita establecer un nuevo valor para la propiedad en el ViewModel, que a continuación calcula un
nuevo color.

Comandos con ViewModels


En muchos casos, el patrón MVVM está restringido a la manipulación de los elementos de datos: objetos de la
interfaz de usuario en la vista objetos de datos en paralelo en el ViewModel.
Sin embargo, a veces la vista debe contener botones que desencadenen varias acciones en el ViewModel. Sin
embargo, el ViewModel no debe contener Clicked Controladores para los botones porque esto enlazaría el
modelo de administración a un paradigma determinado de la interfaz de usuario.
Para permitir que ViewModels sea más independiente de los objetos de interfaz de usuario concretos pero que
todavía permiten llamar a los métodos dentro de ViewModel, existe una interfaz de comandos . Esta interfaz de
comandos es compatible con los siguientes elementos de :::no-loc(Xamarin.Forms)::: :
Button
MenuItem
ToolbarItem
SearchBar
TextCell (y, por tanto, también ImageCell )
ListView
TapGestureRecognizer

A excepción del SearchBar ListView elemento y, estos elementos definen dos propiedades:
Command de tipo System.Windows.Input.ICommand
CommandParameter de tipo Object

SearchBar Define las SearchCommand SearchCommandParameter propiedades y, mientras que ListView define una
RefreshCommand propiedad de tipo ICommand .

La ICommand interfaz define dos métodos y un evento:


void Execute(object arg)
bool CanExecute(object arg)
event EventHandler CanExecuteChanged

El ViewModel puede definir propiedades de tipo ICommand . Después, puede enlazar estas propiedades a la
Command propiedad de cada Button u otro elemento, o quizás una vista personalizada que implemente esta
interfaz. Opcionalmente, puede establecer la CommandParameter propiedad para identificar Button objetos
individuales (u otros elementos) que estén enlazados a esta propiedad ViewModel. Internamente, Button llama
al Execute método cada vez que el usuario puntea Button y pasa al Execute método CommandParameter .
El CanExecute método y el CanExecuteChanged evento se usan para los casos en los que una Button derivación
no es válida actualmente, en cuyo caso se Button debe deshabilitar. Button Llama a CanExecute cuando la
Command propiedad se establece por primera vez y cada vez que CanExecuteChanged se desencadena el evento.
Si CanExecute devuelve false , se Button deshabilita a sí mismo y no genera Execute llamadas.
Para obtener ayuda con la adición de comandos a ViewModels, :::no-loc(Xamarin.Forms)::: define dos clases que
implementan ICommand : Command y Command<T> donde T es el tipo de los argumentos de Execute y
CanExecute . Estas dos clases definen varios constructores más un ChangeCanExecute método al que puede
llamar el ViewModel para forzar al Command objeto a desencadenar el CanExecuteChanged evento.
Este es un ViewModel para un teclado simple que está pensado para especificar números de teléfono. Observe
que el Execute CanExecute método y se definen como funciones lambda directamente en el constructor:

using System;
using System.ComponentModel;
using System.Windows.Input;
using :::no-loc(Xamarin.Forms):::;

namespace XamlSamples
{
class KeypadViewModel : INotifyPropertyChanged
{
string inputString = "";
string displayText = "";
char[] specialChars = { '*', '#' };

public event PropertyChangedEventHandler PropertyChanged;

// Constructor
public KeypadViewModel()
{
AddCharCommand = new Command<string>((key) =>
{
// Add the key to the input string.
InputString += key;
});

DeleteCharCommand = new Command(() =>


{
// Strip a character from the input string.
InputString = InputString.Substring(0, InputString.Length - 1);
},
() =>
{
// Return true if there's something to delete.
return InputString.Length > 0;
});
}

// Public properties
public string InputString
{
protected set
{
if (inputString != value)
{
{
inputString = value;
OnPropertyChanged("InputString");
DisplayText = FormatText(inputString);

// Perhaps the delete button must be enabled/disabled.


((Command)DeleteCharCommand).ChangeCanExecute();
}
}

get { return inputString; }


}

public string DisplayText


{
protected set
{
if (displayText != value)
{
displayText = value;
OnPropertyChanged("DisplayText");
}
}
get { return displayText; }
}

// ICommand implementations
public ICommand AddCharCommand { protected set; get; }

public ICommand DeleteCharCommand { protected set; get; }

string FormatText(string str)


{
bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
string formatted = str;

if (hasNonNumbers || str.Length < 4 || str.Length > 10)


{
}
else if (str.Length < 8)
{
formatted = String.Format("{0}-{1}",
str.Substring(0, 3),
str.Substring(3));
}
else
{
formatted = String.Format("({0}) {1}-{2}",
str.Substring(0, 3),
str.Substring(3, 3),
str.Substring(6));
}
return formatted;
}

protected void OnPropertyChanged(string propertyName)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Este ViewModel supone que la AddCharCommand propiedad está enlazada a la Command propiedad de varios
botones (o cualquier otro que tenga una interfaz de comandos), cada uno de los cuales se identifica mediante
CommandParameter . Estos botones agregan caracteres a una InputString propiedad, a la que se le aplica el
formato de número de teléfono para la DisplayText propiedad.
También hay una segunda propiedad de tipo ICommand denominada DeleteCharCommand . Está enlazado a un
botón de espaciado de retroceso, pero el botón debe deshabilitarse si no hay ningún carácter para eliminar.
El siguiente teclado no es tan sofisticado como podría ser. En su lugar, el marcado se ha reducido a un mínimo
para demostrar más claramente el uso de la interfaz de comandos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.KeypadPage"
Title="Keypad Page">

<Grid HorizontalOptions="Center"
VerticalOptions="Center">
<Grid.BindingContext>
<local:KeypadViewModel />
</Grid.BindingContext>

<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>

<!-- Internal Grid for top row of items -->


<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<Frame Grid.Column="0"
OutlineColor="Accent">
<Label Text="{Binding DisplayText}" />
</Frame>

<Button Text="&#x21E6;"
Command="{Binding DeleteCharCommand}"
Grid.Column="1"
BorderWidth="0" />
</Grid>

<Button Text="1"
Command="{Binding AddCharCommand}"
CommandParameter="1"
Grid.Row="1" Grid.Column="0" />

<Button Text="2"
Command="{Binding AddCharCommand}"
CommandParameter="2"
Grid.Row="1" Grid.Column="1" />

<Button Text="3"
Command="{Binding AddCharCommand}"
CommandParameter="3"
Grid.Row="1" Grid.Column="2" />

<Button Text="4"
Command="{Binding AddCharCommand}"
CommandParameter="4"
Grid.Row="2" Grid.Column="0" />

<Button Text="5"
Command="{Binding AddCharCommand}"
CommandParameter="5"
Grid.Row="2" Grid.Column="1" />

<Button Text="6"
Command="{Binding AddCharCommand}"
CommandParameter="6"
Grid.Row="2" Grid.Column="2" />

<Button Text="7"
Command="{Binding AddCharCommand}"
CommandParameter="7"
Grid.Row="3" Grid.Column="0" />

<Button Text="8"
Command="{Binding AddCharCommand}"
CommandParameter="8"
Grid.Row="3" Grid.Column="1" />

<Button Text="9"
Command="{Binding AddCharCommand}"
CommandParameter="9"
Grid.Row="3" Grid.Column="2" />

<Button Text="*"
Command="{Binding AddCharCommand}"
CommandParameter="*"
Grid.Row="4" Grid.Column="0" />

<Button Text="0"
Command="{Binding AddCharCommand}"
CommandParameter="0"
Grid.Row="4" Grid.Column="1" />

<Button Text="#"
Command="{Binding AddCharCommand}"
CommandParameter="#"
Grid.Row="4" Grid.Column="2" />
</Grid>
</ContentPage>

La Command propiedad de la primera Button que aparece en este marcado se enlaza a DeleteCharCommand ; el
resto se enlaza al AddCharCommand con un CommandParameter que es igual que el carácter que aparece en la
Button superficie. Este es el programa en acción:
Invocar métodos asincrónicos
Los comandos también pueden invocar métodos asincrónicos. Esto se logra mediante el uso de las async
await palabras clave y al especificar el Execute método:

DownloadCommand = new Command (async () => await DownloadAsync ());

Esto indica que el DownloadAsync método es Task y se debe esperar:

async Task DownloadAsync ()


{
await Task.Run (() => Download ());
}

void Download ()
{
...
}

Implementar un menú de navegación


El programa XamlSamples que contiene todo el código fuente de esta serie de artículos usa un ViewModel para
su página principal. Este ViewModel es una definición de una clase corta con tres propiedades denominadas
Type , Title y Description que contienen el tipo de cada una de las páginas de ejemplo, un título y una breve
descripción. Además, el ViewModel define una propiedad estática denominada All que es una colección de
todas las páginas del programa:

public class PageDataViewModel


{
public PageDataViewModel(Type type, string title, string description)
{
Type = type;
Title = title;
Description = description;
}

public Type Type { private set; get; }

public string Title { private set; get; }

public string Description { private set; get; }


public string Description { private set; get; }

static PageDataViewModel()
{
All = new List<PageDataViewModel>
{
// Part 1. Getting Started with XAML
new PageDataViewModel(typeof(HelloXamlPage), "Hello, XAML",
"Display a Label with many properties set"),

new PageDataViewModel(typeof(XamlPlusCodePage), "XAML + Code",


"Interact with a Slider and Button"),

// Part 2. Essential XAML Syntax


new PageDataViewModel(typeof(GridDemoPage), "Grid Demo",
"Explore XAML syntax with the Grid"),

new PageDataViewModel(typeof(AbsoluteDemoPage), "Absolute Demo",


"Explore XAML syntax with AbsoluteLayout"),

// Part 3. XAML Markup Extensions


new PageDataViewModel(typeof(SharedResourcesPage), "Shared Resources",
"Using resource dictionaries to share resources"),

new PageDataViewModel(typeof(StaticConstantsPage), "Static Constants",


"Using the x:Static markup extensions"),

new PageDataViewModel(typeof(RelativeLayoutPage), "Relative Layout",


"Explore XAML markup extensions"),

// Part 4. Data Binding Basics


new PageDataViewModel(typeof(SliderBindingsPage), "Slider Bindings",
"Bind properties of two views on the page"),

new PageDataViewModel(typeof(SliderTransformsPage), "Slider Transforms",


"Use Sliders with reverse bindings"),

new PageDataViewModel(typeof(ListViewDemoPage), "ListView Demo",


"Use a ListView with data bindings"),

// Part 5. From Data Bindings to MVVM


new PageDataViewModel(typeof(OneShotDateTimePage), "One-Shot DateTime",
"Obtain the current DateTime and display it"),

new PageDataViewModel(typeof(ClockPage), "Clock",


"Dynamically display the current time"),

new PageDataViewModel(typeof(HslColorScrollPage), "HSL Color Scroll",


"Use a view model to select HSL colors"),

new PageDataViewModel(typeof(KeypadPage), "Keypad",


"Use a view model for numeric keypad logic")
};
}

public static IList<PageDataViewModel> All { private set; get; }


}

El archivo XAML para MainPage define una ListBox cuya ItemsSource propiedad se establece en esa All
propiedad y que contiene un TextCell para mostrar las Title Description propiedades y de cada página:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.MainPage"
Padding="5, 0"
Title="XAML Samples">

<ListView ItemsSource="{x:Static local:PageDataViewModel.All}"


ItemSelected="OnListViewItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}"
Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

Las páginas se muestran en una lista desplazable:

El controlador del archivo de código subyacente se desencadena cuando el usuario selecciona un elemento. El
controlador establece la SelectedItem propiedad del de ListBox nuevo en null y, a continuación, crea una
instancia de la página seleccionada y navega a ella:

private async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)


{
(sender as ListView).SelectedItem = null;

if (args.SelectedItem != null)
{
PageDataViewModel pageData = args.SelectedItem as PageDataViewModel;
Page page = (Page)Activator.CreateInstance(pageData.Type);
await Navigation.PushAsync(page);
}
}

Vídeo
Xamarin evolucione 2016: MVVM facilitado con :::no-loc(Xamarin.Forms)::: y Prism
Resumen
XAML es una herramienta eficaz para definir interfaces de usuario en :::no-loc(Xamarin.Forms)::: aplicaciones,
especialmente cuando se usan enlace de datos y MVVM. El resultado es una representación limpia, elegante y
potencialmente compatible de una interfaz de usuario con toda la compatibilidad en segundo plano en el
código.

Vínculos relacionados
XamlSamples
Parte 1. Introducción con XAML
Parte 2. Sintaxis XAML básica
Parte 3. Extensiones de marcado XAML
Parte 4. Conceptos básicos del enlace de datos

Vídeos relacionados

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Controles de XAML
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
Las vistas son objetos de la interfaz de usuario, como etiquetas, botones y controles deslizantes que normalmente
se conocen como controles o widgets en otros entornos de programación gráficos. Las vistas admitidas por :::no-
loc(Xamarin.Forms)::: todas derivan de la View clase.
A todas las vistas que se definen en se :::no-loc(Xamarin.Forms)::: puede hacer referencia desde archivos XAML.

Vistas para presentación


VER E JEM P LO

BoxView
<BoxView Color="Accent"
Muestra un rectángulo de un color determinado. WidthRequest="150"
HeightRequest="150"
HorizontalOptions="Center">

API / de Guía de

Elipse
<Ellipse Fill="Red"
Muestra una elipse o un círculo. WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Center" />

API / de Guía de

Expander
<Expander>
Proporciona un contenedor expansible para hospedar <Expander.Header>
cualquier contenido. <Label Text="Baboon" />
</Expander.Header>
<Image Source="Baboon.png"
Aspect="AspectFill" />
</Expander>

Guía
VER E JEM P LO

Imagen
<Image Source="https://aka.ms/campus.jpg"
Muestra un mapa de bits. Aspect="AspectFit"
HorizontalOptions="Center" />

API / de Guía de

Etiqueta
<Label Text="Hello, :::no-loc(Xamarin.Forms):::!"
Muestra una o varias líneas de texto. FontSize="Large"
FontAttributes="Italic"
HorizontalTextAlignment="Center" />

API / de Guía de

Línea
<Line X1="40"
Mostrar una línea. Y1="0"
X2="0"
Y2="120"
Stroke="Red"
HorizontalOptions="Center" />

API / de Guía de

Map
<maps:Map ItemsSource="{Binding Locations}" />
Muestra un mapa.

API / de Guía de

MediaElement
<MediaElement
Reproducir vídeo o audio. Source="https://sec.ch9.ms/ch9/XamarinShow_mid.mp4"
AutoPlay="True"
ShowsPlaybackControls="True" />

API / de Guía de
VER E JEM P LO

Ruta de acceso
<Path Stroke="Black"
Mostrar curvas y formas complejas. Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.9,16.2
L32,16.2 32,31.9 13.9,30.1Z
M0,16.2
L11.9,16.2 11.9,29.9 0,28.6Z
API / de Guía de M11.9,2
L11.9,14.2 0,14.2 0,3.3Z
M32,0
L32,14.2 13.9,14.2 13.9,1.8Z" />

Polygon
<Polygon Points="0 48, 0 144, 96 150, 100 0, 192 0,
Muestra un polígono. 192 96,
50 96, 48 192, 150 200 144 48"
Fill="Blue"
Stroke="Red"
StrokeThickness="3"
HorizontalOptions="Center" />

API / de Guía de

Polilínea
<Polyline Points="0,0 10,30, 15,0 18,60 23,30 35,30
Muestra una serie de líneas rectas conectadas. 40,0
43,60 48,30 100,30"
Stroke="Red"
HorizontalOptions="Center" />

API / de Guía de

Rectángulo
<Rectangle Fill="Red"
Mostrar un rectángulo o un cuadrado. WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Center" />

API / de Guía de
VER E JEM P LO

WebView
<WebView
Muestra páginas web o contenido HTML. Source="https://docs.microsoft.com/xamarin/"
VerticalOptions="FillAndExpand" />

API / de Guía de

Vistas que inician comandos


VER E JEM P LO

Botón
<Button Text="Click Me!"
Muestra texto en un objeto rectangular. Font="Large"
BorderWidth="1"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnButtonClicked" />

API / de Guía de

ImageButton
<ImageButton Source="XamarinLogo.png"
Muestra una imagen en un objeto rectangular. HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnImageButtonClicked" />

API / de Guía de

RadioButton
<RadioButton Text="Pineapple"
Permite la selección de una opción de un conjunto.
CheckedChanged="OnRadioButtonCheckedChanged" />

Guía
VER E JEM P LO

RefreshView
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Proporciona funcionalidad de extracción a actualización para el Command="{Binding RefreshCommand}" >
contenido desplazable. <!-- Scrollable control goes here -->
</RefreshView>

Guía

Barra de búsqueda
<SearchBar Placeholder="Enter search term"
Acepta los datos proporcionados por el usuario que usa para
realizar una búsqueda. SearchButtonPressed="OnSearchBarButtonPressed" />

Guía

SwipeView
<SwipeView>
Proporciona elementos de menú contextual que se revelan <SwipeView.LeftItems>
mediante un gesto de deslizar rápidamente. <SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteInvoked" />
</SwipeItems>
Guía </SwipeView.LeftItems>
<!-- Content -->
</SwipeView>

Vistas para establecer valores


VER E JEM P LO

CheckBox
<CheckBox IsChecked="true"
Permite la selección de un boolean valor. HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

Guía
VER E JEM P LO

Control deslizante
<Slider Minimum="0"
Permite la selección de un double valor de un intervalo Maximum="100"
continuo. VerticalOptions="CenterAndExpand" />

API / de Guía de

Control de incremento
<Stepper Minimum="0"
Permite la selección de un double valor de un intervalo Maximum="10"
incremental. Increment="0.1"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

API / de Guía de

Switch
<Switch IsToggled="false"
Permite la selección de un boolean valor. HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

API / de Guía de

DatePicker
<DatePicker Format="D"
Permite seleccionar una fecha. VerticalOptions="CenterAndExpand" />

API / de Guía de
VER E JEM P LO

TimePicker
<TimePicker Format="T"
Permite la selección de una vez. VerticalOptions="CenterAndExpand" />

API / de Guía de

Vistas para editar texto


VER E JEM P LO

Entrada
<Entry Keyboard="Email"
Permite escribir y editar una sola línea de texto. Placeholder="Enter email address"
VerticalOptions="CenterAndExpand" />

API / de Guía de

Editor
<Editor VerticalOptions="FillAndExpand" />
Permite escribir y editar varias líneas de texto.

API / de Guía de

Vistas para indicar actividad


VER E JEM P LO

ActivityIndicator
<ActivityIndicator IsRunning="True"
Muestra una animación para mostrar que la aplicación está
ocupada en una actividad prolongada, sin indicar ningún VerticalOptions="CenterAndExpand" />
progreso.

API / de Guía de

ProgressBar
<ProgressBar Progress=".5"
Muestra una animación para mostrar que la aplicación VerticalOptions="CenterAndExpand" />
progresa a través de una actividad prolongada.

API / de Guía de

Vistas que muestran colecciones


VER E JEM P LO

CarouselView
<CarouselView ItemsSource="{Binding Monkeys}">
Muestra una lista desplazable de elementos de datos. ItemTemplate="{StaticResource
MonkeyTemplate}" />

Guía
VER E JEM P LO

CollectionView
<CollectionView ItemsSource="{Binding Monkeys}">
Muestra una lista desplazable de elementos de datos ItemTemplate="{StaticResource
seleccionables, con distintas especificaciones de diseño. MonkeyTemplate}"
ItemsLayout="VerticalGrid, 2" />

Guía

IndicatorView
<IndicatorView x:Name="indicatorView"
Muestra indicadores que representan el número de elementos IndicatorColor="LightGray"
de un CarouselView . SelectedIndicatorColor="DarkGray" />

Guía

ListView
<ListView ItemsSource="{Binding Monkeys}">
Muestra una lista desplazable de elementos de datos ItemTemplate="{StaticResource
seleccionables. MonkeyTemplate}" />

API / de Guía de
VER E JEM P LO

Selector
<Picker Title="Select a monkey"
Muestra un elemento seleccionado en una lista de cadenas de TitleColor="Red">
texto. <Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>

API / de Guía de

TableView
<TableView Intent="Settings">
Muestra una lista de filas interactivas. <TableRoot>
<TableSection Title="Ring">
<SwitchCell Text="New Voice Mail" />
<SwitchCell Text="New Mail" On="true"
/>
</TableSection>
</TableRoot>
API / de Guía de </TableView>

Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
Ejemplos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Documentación de API
Compilación XAML enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

XAML se puede compilar de forma opcional directamente en lenguaje intermedio (IL) con el compilador XAML
(XAMLC).
La compilación de XAML ofrece una serie de ventajas:
Realiza la comprobación en tiempo de compilación de XAML, notificando al usuario de los errores.
Reduce parte del tiempo de carga y creación de instancias para los elementos XAML.
Facilita reducir el tamaño de archivo del ensamblado final al dejar de incluir archivos .xaml.
La compilación XAML está deshabilitada de forma predeterminada para garantizar la compatibilidad con
versiones anteriores. Se puede habilitar en el nivel de ensamblado y de clase agregando el XamlCompilation
atributo.
En el ejemplo de código siguiente se muestra cómo habilitar la compilación XAML en el nivel de ensamblado:

using Xamarin.Forms.Xaml;
...
[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
namespace PhotoApp
{
...
}

En este ejemplo, se realizará la comprobación en tiempo de compilación de todo el código XAML incluido en el
ensamblado, con errores de XAML que se informan en tiempo de compilación en lugar de en tiempo de
ejecución. Por lo tanto, el assembly prefijo del XamlCompilation atributo especifica que el atributo se aplica a
todo el ensamblado.

NOTE
El XamlCompilation atributo y la XamlCompilationOptions enumeración residen en el Xamarin.Forms.Xaml espacio de
nombres, que se debe importar para usarlos.

En el ejemplo de código siguiente se muestra cómo habilitar la compilación XAML en el nivel de clase:

using Xamarin.Forms.Xaml;
...
[XamlCompilation (XamlCompilationOptions.Compile)]
public class HomePage : ContentPage
{
...
}

En este ejemplo, se realizará la comprobación en tiempo de compilación del XAML de la HomePage clase y se
registrarán los errores como parte del proceso de compilación.
NOTE
Los enlaces compilados se pueden habilitar para mejorar el rendimiento del enlace de datos en Xamarin.Forms las
aplicaciones. Para obtener más información, vea Enlaces compilados.

Vínculos relacionados
XamlCompilation
XamlCompilationOptions
Extensiones de marcado XAML
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Las extensiones de marcado XAML ayudan a ampliar la eficacia y flexibilidad de XAML, ya que permiten
establecer atributos de elementos a partir de orígenes que no sean cadenas de texto literal.
Por ejemplo, normalmente se establece la Color propiedad de la siguiente BoxView manera:

<BoxView Color="Blue" />

O bien, puede establecerlo en un valor de color RGB hexadecimal:

<BoxView Color="#FF0080" />

En cualquier caso, la cadena de texto establecida en el Color atributo se convierte en un Color valor por la
ColorTypeConverter clase.

En su lugar, es preferible establecer el Color atributo a partir de un valor almacenado en un diccionario de


recursos, o del valor de una propiedad estática de una clase que haya creado, o de una propiedad de tipo Color
de otro elemento de la página, o construido a partir de valores de matiz, saturación y luminosidad
independientes.
Todas estas opciones son posibles con las extensiones de marcado XAML. Pero no deje que la frase "extensiones
de marcado" SCARE: las extensiones de marcado XAML no son extensiones de XML. Incluso con las extensiones
de marcado XAML, XAML siempre es XML legal.
Una extensión de marcado es en realidad una forma diferente de expresar un atributo de un elemento. Las
extensiones de marcado XAML normalmente se pueden identificar mediante una configuración de atributo que
se incluye entre llaves:

<BoxView Color="{StaticResource themeColor}" />

Cualquier valor de atributo entre llaves siempre es una extensión de marcado XAML. Sin embargo, como verá,
también se puede hacer referencia a las extensiones de marcado XAML sin el uso de llaves.
Este artículo se divide en dos partes:

Consumo de extensiones de marcado XAML


Usar las extensiones de marcado XAML definidas en :::no-loc(Xamarin.Forms)::: .

Creación de extensiones de marcado XAML


Escriba sus propias extensiones de marcado XAML personalizadas.

Vínculos relacionados
Extensiones de marcado (ejemplo)
Capítulo de extensiones de marcado XAML del :::no-loc(Xamarin.Forms)::: libro
Diccionarios de recursos
Estilos dinámicos
Enlace de datos
Consumo de extensiones de marcado XAML
18/12/2020 • 36 minutes to read • Edit Online

Descargar el ejemplo
Las extensiones de marcado XAML ayudan a mejorar la eficacia y flexibilidad de XAML al permitir que se
establezcan atributos de elemento desde diversos orígenes. Varias extensiones de marcado XAML forman parte de
la especificación de XAML 2009. Aparecen en archivos XAML con el x Prefijo de espacio de nombres
personalizado y se suele hacer referencia a ellos con este prefijo. En este artículo se describen las siguientes
extensiones de marcado:
x:Static : hace referencia a propiedades estáticas, campos o miembros de enumeración.
x:Reference : hace referencia a los elementos con nombre de la página.
x:Type : establezca un atributo en un System.Type objeto.
x:Array : construye una matriz de objetos de un tipo determinado.
x:Null : establezca un atributo en un null valor.
OnPlatform : personalización de la apariencia de la interfaz de usuario en cada plataforma.
OnIdiom : Personalice la apariencia de la interfaz de usuario en función de la expresión del dispositivo en el que
se ejecuta la aplicación.
DataTemplate : convierte un tipo en un DataTemplate .
FontImage : muestra un icono de fuente en cualquier vista que puede mostrar un ImageSource .
AppThemeBinding : consumir un recurso basado en el tema del sistema actual.

Históricamente, las extensiones de marcado XAML adicionales se han admitido en otras implementaciones de
XAML y también se admiten en :::no-loc(Xamarin.Forms)::: . Estos se describen con más detalle en otros artículos:
StaticResource : hace referencia a los objetos de un diccionario de recursos, como se describe en el artículo
diccionarios de recursos .
DynamicResource : responde a los cambios en los objetos de un diccionario de recursos, como se describe en el
artículo estilos dinámicos .
Binding : establezca un vínculo entre las propiedades de dos objetos, como se describe en el artículo enlace
de datos .
TemplateBinding : realiza el enlace de datos de una plantilla de control, como se describe en el artículo :::no-
loc(Xamarin.Forms)::: plantillas de control .
RelativeSource : establece el origen de enlace con respecto a la posición del destino de enlace, como se
describe en el artículo enlaces relativos.
El RelativeLayout diseño hace uso de la extensión de marcado personalizada ConstraintExpression . Esta
extensión de marcado se describe en el artículo RelativeLayout .

x:Static (extensión de marcado)


La x:Static extensión de marcado es compatible con la StaticExtension clase. La clase tiene una propiedad única
denominada Member de tipo string que se establece en el nombre de una constante pública, una propiedad
estática, un campo estático o un miembro de enumeración.
Una forma habitual de usar x:Static es definir primero una clase con algunas constantes o variables estáticas,
como esta pequeña AppConstants clase en el programa MarkupExtensions :
static class AppConstants
{
public static double NormalFontSize = 18;
}

En la página de demostración de x:Static se muestran varias maneras de usar la x:Static extensión de


marcado. El enfoque más detallado crea instancias de la StaticExtension clase entre las Label.FontSize etiquetas
de elemento de propiedad:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.StaticDemoPage"
Title="x:Static Demo">
<StackLayout Margin="10, 0">
<Label Text="Label No. 1">
<Label.FontSize>
<x:StaticExtension Member="local:AppConstants.NormalFontSize" />
</Label.FontSize>
</Label>

···

</StackLayout>
</ContentPage>

El analizador de XAML también permite que la StaticExtension clase se abrevie como x:Static :

<Label Text="Label No. 2">


<Label.FontSize>
<x:Static Member="local:AppConstants.NormalFontSize" />
</Label.FontSize>
</Label>

Esto se puede simplificar aún más, pero el cambio introduce una nueva sintaxis: consiste en colocar la
StaticExtension clase y el valor de miembro entre llaves. La expresión resultante se establece directamente en el
FontSize atributo:

<Label Text="Label No. 3"


FontSize="{x:StaticExtension Member=local:AppConstants.NormalFontSize}" />

Observe que no hay comillas dentro de las llaves. La Member propiedad de ya StaticExtension no es un atributo
XML. En su lugar, es parte de la expresión para la extensión de marcado.
Del mismo modo que se puede abreviar x:StaticExtension a x:Static cuando se usa como un elemento de
objeto, también se puede abreviar en la expresión entre llaves:

<Label Text="Label No. 4"


FontSize="{x:Static Member=local:AppConstants.NormalFontSize}" />

La StaticExtension clase tiene un ContentProperty atributo que hace referencia a la propiedad Member , que
marca esta propiedad como la propiedad de contenido predeterminada de la clase. En el caso de las extensiones de
marcado XAML, que se expresan con llaves, puede eliminar la Member= parte de la expresión:
<Label Text="Label No. 5"
FontSize="{x:Static local:AppConstants.NormalFontSize}" />

Esta es la forma más común de la x:Static extensión de marcado.


La página de demostración estática contiene otros dos ejemplos. La etiqueta raíz del archivo XAML contiene una
declaración de espacio de nombres XML para el espacio de nombres de .NET System :

xmlns:sys="clr-namespace:System;assembly=netstandard"

Esto permite Label establecer el tamaño de fuente en el campo estático Math.PI . Esto da como resultado un
texto bastante pequeño, por lo que la Scale propiedad se establece en Math.E :

<Label Text="&#x03C0; &#x00D7; E sized text"


FontSize="{x:Static sys:Math.PI}"
Scale="{x:Static sys:Math.E}"
HorizontalOptions="Center" />

En el último ejemplo se muestra el Device.RuntimePlatform valor. La Environment.NewLine propiedad estática se


usa para insertar un carácter de nueva línea entre los dos Span objetos:

<Label HorizontalTextAlignment="Center"
FontSize="{x:Static local:AppConstants.NormalFontSize}">
<Label.FormattedText>
<FormattedString>
<Span Text="Runtime Platform: " />
<Span Text="{x:Static sys:Environment.NewLine}" />
<Span Text="{x:Static Device.RuntimePlatform}" />
</FormattedString>
</Label.FormattedText>
</Label>

Este es el ejemplo en ejecución:

extensión de marcado x:Reference


La x:Reference extensión de marcado es compatible con la ReferenceExtension clase. La clase tiene una
propiedad única denominada Name de tipo string que se establece en el nombre de un elemento de la página al
que se le ha asignado un nombre x:Name . Esta Name propiedad es la propiedad de contenido de
ReferenceExtension , por lo que Name= no es necesario cuando x:Reference aparece entre llaves.

La x:Reference extensión de marcado se usa exclusivamente con los enlaces de datos, que se describen con más
detalle en el artículo enlace de datos .
En la página de demostración de x:Reference se muestran dos usos de x:Reference con enlaces de datos, el
primero en el que se usa para establecer la Source propiedad del Binding objeto y el segundo donde se usa para
establecer la BindingContext propiedad para dos enlaces de datos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.ReferenceDemoPage"
x:Name="page"
Title="x:Reference Demo">

<StackLayout Margin="10, 0">

<Label Text="{Binding Source={x:Reference page},


StringFormat='The type of this page is {0}'}"
FontSize="18"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />

<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />

<Label BindingContext="{x:Reference slider}"


Text="{Binding Value, StringFormat='{0:F0}&#x00B0; rotation'}"
Rotation="{Binding Value}"
FontSize="24"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

</StackLayout>
</ContentPage>

Ambas x:Reference expresiones utilizan la versión abreviada del nombre de la ReferenceExtension clase y
eliminan la Name= parte de la expresión. En el primer ejemplo, la x:Reference extensión de marcado se incrusta
en la Binding extensión de marcado. Tenga en cuenta que los Source valores de y StringFormat se separan
mediante comas. Esta es la ejecución del programa:
x:Type (extensión de marcado)
La x:Type extensión de marcado es el equivalente de XAML de la typeof palabra clave de C#. Es compatible con
la TypeExtension clase, que define una propiedad denominada TypeName de tipo string que se establece en un
nombre de clase o estructura. La x:Type extensión de marcado devuelve el System.Type objeto de esa clase o
estructura. TypeName es la propiedad de contenido de TypeExtension , por lo que TypeName= no es necesario
cuando x:Type aparece con llaves.
Dentro de :::no-loc(Xamarin.Forms)::: , hay varias propiedades que tienen argumentos de tipo Type . Entre los
ejemplos TargetType se incluye la propiedad de Style y el atributo x:TypeArguments que se usa para especificar
argumentos en clases genéricas. Sin embargo, el analizador de XAML realiza la typeof operación
automáticamente y la x:Type extensión de marcado no se utiliza en estos casos.
Un lugar donde x:Type se requiere es con la x:Array extensión de marcado, que se describe en la sección
siguiente.
La x:Type extensión de marcado también es útil cuando se crea un menú en el que cada elemento de menú
corresponde a un objeto de un tipo determinado. Puede asociar un Type objeto a cada elemento de menú y, a
continuación, crear una instancia del objeto cuando se selecciona el elemento de menú.
Así es como funciona el menú de navegación en MainPage en el programa de extensiones de marcado . El
archivo mainpage. Xaml contiene un TableView con cada TextCell correspondiente a una página determinada
del programa:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.MainPage"
Title="Markup Extensions"
Padding="10">
<TableView Intent="Menu">
<TableRoot>
<TableSection>
<TextCell Text="x:Static Demo"
Detail="Access constants or statics"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:StaticDemoPage}" />

<TextCell Text="x:Reference Demo"


Detail="Reference named elements on the page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:ReferenceDemoPage}" />

<TextCell Text="x:Type Demo"


Detail="Associate a Button with a Type"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:TypeDemoPage}" />

<TextCell Text="x:Array Demo"


Detail="Use an array to fill a ListView"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:ArrayDemoPage}" />

···

</TableRoot>
</TableView>
</ContentPage>

Esta es la Página principal de apertura en las extensiones de marcado :

Cada CommandParameter propiedad se establece en una x:Type extensión de marcado que hace referencia a una de
las otras páginas. La Command propiedad está enlazada a una propiedad denominada NavigateCommand . Esta
propiedad se define en el MainPage archivo de código subyacente:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();

NavigateCommand = new Command<Type>(async (Type pageType) =>


{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
});

BindingContext = this;
}

public ICommand NavigateCommand { private set; get; }


}

La NavigateCommand propiedad es un Command objeto que implementa un comando Execute con un argumento de
tipo Type — el valor de CommandParameter . El método utiliza Activator.CreateInstance para crear una instancia
de la página y, a continuación, navega hasta ella. El constructor concluye estableciendo el BindingContext de la
página en sí mismo, lo que permite que Binding Command funcione. Vea el artículo sobre el enlace de datos y, en
particular, el artículo sobre comandos para obtener más información sobre este tipo de código.
La página de demostración de x:Type utiliza una técnica similar para crear instancias de :::no-
loc(Xamarin.Forms)::: los elementos y agregarlos a StackLayout . El archivo XAML está compuesto inicialmente de
tres Button elementos con sus Command propiedades establecidas en Binding y las CommandParameter
propiedades establecidas en tipos de tres :::no-loc(Xamarin.Forms)::: vistas:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.TypeDemoPage"
Title="x:Type Demo">

<StackLayout x:Name="stackLayout"
Padding="10, 0">

<Button Text="Create a Slider"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Command="{Binding CreateCommand}"
CommandParameter="{x:Type Slider}" />

<Button Text="Create a Stepper"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Command="{Binding CreateCommand}"
CommandParameter="{x:Type Stepper}" />

<Button Text="Create a Switch"


HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Command="{Binding CreateCommand}"
CommandParameter="{x:Type Switch}" />
</StackLayout>
</ContentPage>

El archivo de código subyacente define e inicializa la CreateCommand propiedad:


public partial class TypeDemoPage : ContentPage
{
public TypeDemoPage()
{
InitializeComponent();

CreateCommand = new Command<Type>((Type viewType) =>


{
View view = (View)Activator.CreateInstance(viewType);
view.VerticalOptions = LayoutOptions.CenterAndExpand;
stackLayout.Children.Add(view);
});

BindingContext = this;
}

public ICommand CreateCommand { private set; get; }


}

El método que se ejecuta cuando Button se presiona se crea una nueva instancia del argumento, establece su
VerticalOptions propiedad y la agrega a StackLayout . Los tres Button elementos compartirán la página con
vistas creadas dinámicamente:

x:Array (extensión de marcado)


La extensión de marcado le permite definir una matriz en el marcado. Es compatible con la
x:Array
ArrayExtension clase, que define dos propiedades:

Type de tipo Type , que indica el tipo de los elementos de la matriz.


Items de tipo IList , que es una colección de los propios elementos. Esta es la propiedad de contenido de
ArrayExtension .

La x:Array extensión de marcado no aparece nunca entre llaves. En su lugar, x:Array las etiquetas de inicio y fin
delimitan la lista de elementos. Establezca la Type propiedad en una x:Type extensión de marcado.
La página de demostración de x:Array muestra cómo usar x:Array para agregar elementos a un estableciendo
ListView la ItemsSource propiedad en una matriz:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.ArrayDemoPage"
Title="x:Array Demo Page">
<ListView Margin="10">
<ListView.ItemsSource>
<x:Array Type="{x:Type Color}">
<Color>Aqua</Color>
<Color>Black</Color>
<Color>Blue</Color>
<Color>Fuchsia</Color>
<Color>Gray</Color>
<Color>Green</Color>
<Color>Lime</Color>
<Color>Maroon</Color>
<Color>Navy</Color>
<Color>Olive</Color>
<Color>Pink</Color>
<Color>Purple</Color>
<Color>Red</Color>
<Color>Silver</Color>
<Color>Teal</Color>
<Color>White</Color>
<Color>Yellow</Color>
</x:Array>
</ListView.ItemsSource>

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<BoxView Color="{Binding}"
Margin="3" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

ViewCell Crea un sencillo BoxView para cada entrada de color:

Hay varias maneras de especificar los elementos individuales Color de esta matriz. Puede usar una x:Static
extensión de marcado:
<x:Static Member="Color.Blue" />

O bien, puede usar StaticResource para recuperar un color de un diccionario de recursos:

<StaticResource Key="myColor" />

Al final de este artículo, verá una extensión de marcado XAML personalizada que también crea un nuevo valor de
color:

<local:HslColor H="0.5" S="1.0" L="0.5" />

Al definir matrices de tipos comunes, como cadenas o números, use las etiquetas enumeradas en el artículo pasar
argumentos de constructor para delimitar los valores.

x:Null (extensión de marcado)


La x:Null extensión de marcado es compatible con la NullExtension clase. No tiene propiedades y es
simplemente el equivalente XAML de la null palabra clave de C#.
La x:Null extensión de marcado rara vez es necesaria y raramente se usa, pero si lo necesita, le alegrará de que
exista.
La página de demostración de x:null muestra un escenario en el x:Null que puede resultar práctico. Suponga
que define un Style para Label que incluya un Setter que establezca la FontFamily propiedad en un nombre
de familia dependiente de la plataforma:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.NullDemoPage"
Title="x:Null Demo">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="48" />
<Setter Property="FontFamily">
<Setter.Value>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="Times New Roman" />
<On Platform="Android" Value="serif" />
<On Platform="UWP" Value="Times New Roman" />
</OnPlatform>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<ContentPage.Content>
<StackLayout Padding="10, 0">
<Label Text="Text 1" />
<Label Text="Text 2" />

<Label Text="Text 3"


FontFamily="{x:Null}" />

<Label Text="Text 4" />


<Label Text="Text 5" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

A continuación, descubrirá que para uno de los Label elementos, deseará todos los valores de propiedad de la
implícita Style , salvo el FontFamily valor de, que desee que sea el predeterminado. Podría definir otro Style
para ese propósito, pero un enfoque más sencillo es simplemente establecer la FontFamily propiedad de Label
en particular en x:Null , como se muestra en el centro Label .
Esta es la ejecución del programa:

Observe que cuatro de los Label elementos tienen una fuente serif, pero el centro Label tiene la fuente sans-
serif predeterminada.
Extensión de marcado en la plataforma
La extensión de marcado OnPlatform le permite personalizar la apariencia de la interfaz de usuario para cada
plataforma. Proporciona la misma funcionalidad que las OnPlatform clases y On , pero con una representación
más concisa.
La OnPlatform clase admite la extensión de marcado OnPlatformExtension , que define las siguientes propiedades:
Default de tipo object , que se establece en un valor predeterminado que se va a aplicar a las propiedades
que representan las plataformas.
Android de tipo object , que se establece en un valor que se va a aplicar en Android.
GTK de tipo object , que se establece en un valor que se va a aplicar en plataformas GTK.
iOS de tipo object , que se establece en un valor que se va a aplicar en iOS.
macOS de tipo object , que se establece en un valor que se va a aplicar en MacOS.
Tizen de tipo object , que se establece en un valor que se va a aplicar a la plataforma Tizen.
UWP de tipo object , que se establece en un valor que se va a aplicar en el plataforma universal de Windows.
WPF de tipo object , que se establece en un valor que se va a aplicar en la plataforma Windows Presentation
Foundation.
Converter de tipo IValueConverter , que se puede establecer en una IValueConverter implementación de.
ConverterParameter de tipo object , que se puede establecer en un valor que se va a pasar a la
IValueConverter implementación.

NOTE
El analizador XAML permite OnPlatformExtension abreviar la clase como OnPlatform .

La Default propiedad es la propiedad de contenido de OnPlatformExtension . Por lo tanto, para las expresiones de
marcado XAML expresadas con llaves, puede eliminar la Default= parte de la expresión siempre que sea el primer
argumento. Si Default no se establece la propiedad, se usará de forma predeterminada el valor de la
BindableProperty.DefaultValue propiedad, siempre que la extensión de marcado esté destinada a un
BindableProperty .

IMPORTANT
El analizador de XAML espera que se proporcionen valores del tipo correcto a las propiedades que consumen la
OnPlatform extensión de marcado. Si es necesaria la conversión de tipos, la OnPlatform extensión de marcado intentará
realizarla utilizando los convertidores predeterminados proporcionados por :::no-loc(Xamarin.Forms)::: . Sin embargo, hay
algunas conversiones de tipos que no pueden realizar los convertidores predeterminados y, en estos casos, la Converter
propiedad debe establecerse en una IValueConverter implementación de.

En la página de demostración de la plataforma , se muestra cómo usar la OnPlatform extensión de marcado:

<BoxView Color="{OnPlatform Yellow, iOS=Red, Android=Green, UWP=Blue}"


WidthRequest="{OnPlatform 250, iOS=200, Android=300, UWP=400}"
HeightRequest="{OnPlatform 250, iOS=200, Android=300, UWP=400}"
HorizontalOptions="Center" />

En este ejemplo, las tres OnPlatform expresiones utilizan la versión abreviada del OnPlatformExtension nombre de
clase. Las tres OnPlatform extensiones de marcado establecen Color las WidthRequest propiedades, y
HeightRequest de BoxView en valores diferentes en iOS, Android y UWP. Las extensiones de marcado también
proporcionan valores predeterminados para estas propiedades en las plataformas que no se especifican, a la vez
que se elimina la Default= parte de la expresión. Observe que las propiedades de extensión de marcado que se
establecen están separadas por comas.
Esta es la ejecución del programa:

Extensión de marcado en idioma


La OnIdiom extensión de marcado le permite personalizar la apariencia de la interfaz de usuario en función de la
expresión del dispositivo en el que se ejecuta la aplicación. Es compatible con la OnIdiomExtension clase, que define
las siguientes propiedades:
Default de tipo object , que se establece en un valor predeterminado que se va a aplicar a las propiedades
que representan los expresiones de dispositivo.
Phone de tipo object , que se establece en un valor que se va a aplicar en los teléfonos.
Tablet de tipo object , que se establece en un valor que se va a aplicar en tabletas.
Desktop de tipo object , que se establece en un valor que se va a aplicar en las plataformas de escritorio.
TV de tipo object , que se establece en un valor que se va a aplicar en las plataformas de TV.
Watch de tipo object , que se establece en un valor que se va a aplicar en las plataformas de inspección.
Converter de tipo IValueConverter , que se puede establecer en una IValueConverter implementación de.
ConverterParameter de tipo object , que se puede establecer en un valor que se va a pasar a la
IValueConverter implementación.

NOTE
El analizador XAML permite OnIdiomExtension abreviar la clase como OnIdiom .

La Default propiedad es la propiedad de contenido de OnIdiomExtension . Por lo tanto, para las expresiones de
marcado XAML expresadas con llaves, puede eliminar la Default= parte de la expresión siempre que sea el primer
argumento.
IMPORTANT
El analizador de XAML espera que se proporcionen valores del tipo correcto a las propiedades que consumen la OnIdiom
extensión de marcado. Si es necesaria la conversión de tipos, la OnIdiom extensión de marcado intentará realizarla
utilizando los convertidores predeterminados proporcionados por :::no-loc(Xamarin.Forms)::: . Sin embargo, hay algunas
conversiones de tipos que no pueden realizar los convertidores predeterminados y, en estos casos, la Converter propiedad
debe establecerse en una IValueConverter implementación de.

En la página de demostración de idioma se muestra cómo usar la OnIdiom extensión de marcado:

<BoxView Color="{OnIdiom Yellow, Phone=Red, Tablet=Green, Desktop=Blue}"


WidthRequest="{OnIdiom 100, Phone=200, Tablet=300, Desktop=400}"
HeightRequest="{OnIdiom 100, Phone=200, Tablet=300, Desktop=400}"
HorizontalOptions="Center" />

En este ejemplo, las tres OnIdiom expresiones utilizan la versión abreviada del OnIdiomExtension nombre de clase.
Las tres OnIdiom extensiones de marcado establecen Color las WidthRequest propiedades, y HeightRequest de
BoxView en diferentes valores en las expresiones de teléfono, tableta y escritorio. Las extensiones de marcado
también proporcionan valores predeterminados para estas propiedades en las expresiones que no se especifican, a
la vez que se elimina la Default= parte de la expresión. Observe que las propiedades de extensión de marcado
que se establecen están separadas por comas.
Esta es la ejecución del programa:

Extensión de marcado DataTemplate


La DataTemplate extensión de marcado le permite convertir un tipo en DataTemplate . Es compatible con la
DataTemplateExtension clase, que define una TypeName propiedad, de tipo string , que se establece en el nombre
del tipo que se va a convertir en DataTemplate . La TypeName propiedad es la propiedad de contenido de
DataTemplateExtension . Por lo tanto, para las expresiones de marcado XAML expresadas con llaves, puede eliminar
la parte TypeName= de la expresión.

NOTE
El analizador de XAML permite abreviar la clase DataTemplateExtension como DataTemplate .
Un uso típico de esta extensión de marcado está en una aplicación de Shell, tal y como se muestra en el ejemplo
siguiente:

<ShellContent Title="Monkeys"
Icon="monkey.png"
ContentTemplate="{DataTemplate views:MonkeysPage}" />

En este ejemplo, MonkeysPagese convierte de ContentPage a DataTemplate , que se establece como el valor de la
ShellContent.ContentTemplate propiedad. Esto garantiza que MonkeysPage solo se crea cuando se produce la
navegación a la página, en lugar de al inicio de la aplicación.
Para obtener más información sobre las aplicaciones de Shell, consulte :::no-loc(Xamarin.Forms)::: Shell.

Extensión de marcado FontImage


La FontImage extensión de marcado le permite mostrar un icono de fuente en cualquier vista que pueda mostrar
un ImageSource . Proporciona la misma funcionalidad que la FontImageSource clase, pero con una representación
más concisa.
La clase FontImageExtension admite la extensión de marcado FontImage , que define las siguientes propiedades:
FontFamily de tipo string , la familia de fuentes a la que pertenece el icono de fuente.
Glyph de tipo string , el valor de carácter Unicode del icono de fuente.
Color de tipo Color , el color que se va a utilizar al mostrar el icono de fuente.
Size del tipo double , el tamaño, en unidades independientes del dispositivo, del icono de fuente
representada. El valor predeterminado es 30. Además, esta propiedad se puede establecer en un tamaño de
fuente con nombre.

NOTE
El analizador de XAML permite abreviar la clase FontImageExtension como FontImage .

La Glyph propiedad es la propiedad de contenido de FontImageExtension . Por lo tanto, para las expresiones de
marcado XAML expresadas con llaves, puede eliminar la Glyph= parte de la expresión siempre que sea el primer
argumento.
La página de demostración de FontImage muestra cómo usar la FontImage extensión de marcado:

<Image BackgroundColor="#D1D1D1"
Source="{FontImage &#xf30c;, FontFamily={OnPlatform iOS=Ionicons, Android=ionicons.ttf#}, Size=44}" />

En este ejemplo, la versión abreviada del FontImageExtension nombre de clase se usa para mostrar un icono Xbox,
de la familia de fuentes Ionicons, en un Image . La expresión también utiliza la OnPlatform extensión de marcado
para especificar diferentes FontFamily valores de propiedad en iOS y Android. Además, Glyph= se elimina la
parte de la expresión y las propiedades de extensión de marcado que se establecen se separan mediante comas.
Tenga en cuenta que, mientras que el carácter Unicode para el icono es \uf30c , tiene que incluir un carácter de
escape en XAML, por lo que se convierte en &#xf30c; .
Esta es la ejecución del programa:
Para obtener información sobre cómo mostrar los iconos de fuente especificando los datos del icono de fuente de
un FontImageSource objeto, vea Mostrar iconos de fuentes.

AppThemeBinding (extensión de marcado)


La AppThemeBinding extensión de marcado le permite especificar el recurso que se va a consumir, como una
imagen o un color, basándose en el tema del sistema actual.

IMPORTANT
La AppThemeBinding extensión de marcado tiene requisitos mínimos de sistema operativo. Para obtener más información,
consulte responder a los cambios de tema del sistema en :::no-loc(Xamarin.Forms)::: las aplicaciones.

La clase AppThemeBindingExtension admite la extensión de marcado AppThemeBinding , que define las siguientes
propiedades:
Default , de tipo object , que se establece en el recurso que se va a usar de forma predeterminada.
Light , de tipo object , que se establece en el recurso que se va a usar cuando el dispositivo esté usando su
tema claro.
Dark , de tipo object , que se establece en el recurso que se va a usar cuando el dispositivo esté usando su
tema oscuro.
Value , de tipo object , que devuelve el recurso que está utilizando actualmente la extensión de marcado.

NOTE
El analizador de XAML permite abreviar la clase AppThemeBindingExtension como AppBindingTheme .

La Default propiedad es la propiedad de contenido de AppThemeBindingExtension . Por lo tanto, para las


expresiones de marcado XAML expresadas con llaves, puede eliminar la Default= parte de la expresión siempre
que sea el primer argumento.
La página de demostración de AppThemeBinding muestra cómo usar la AppThemeBinding extensión de
marcado:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.AppThemeBindingDemoPage"
Title="AppThemeBinding Demo">
<ContentPage.Resources>

<Style x:Key="labelStyle"
TargetType="Label">
<Setter Property="TextColor"
Value="{AppThemeBinding Black, Light=Blue, Dark=Teal}" />
</Style>

</ContentPage.Resources>
<StackLayout Margin="20">
<Label Text="This text is green in light mode, and red in dark mode."
TextColor="{AppThemeBinding Light=Green, Dark=Red}" />
<Label Text="This text is black by default, blue in light mode, and teal in dark mode."
Style="{StaticResource labelStyle}" />
</StackLayout>
</ContentPage>

En este ejemplo, el color del texto de la primera Label se establece en verde cuando el dispositivo usa su tema
claro y se establece en rojo cuando el dispositivo está usando su tema oscuro. La segunda Label tiene su
TextColor propiedad establecida a través de Style . De Style forma predeterminada, se establece el color del
texto de Label en negro, en azul cuando el dispositivo usa el tema claro y en verde azulado cuando el dispositivo
usa su tema oscuro.
Esta es la ejecución del programa:

Definir extensiones de marcado


Si ha encontrado una necesidad de una extensión de marcado XAML que no está disponible en :::no-
loc(Xamarin.Forms)::: , puede crear la suya propia.

Vínculos relacionados
Extensiones de marcado (ejemplo)
Capítulo de extensiones de marcado XAML del :::no-loc(Xamarin.Forms)::: libro
Diccionarios de recursos
Estilos dinámicos
Enlace de datos
:::no-loc(Xamarin.Forms)::: Shell
Responder a los cambios de tema del sistema en :::no-loc(Xamarin.Forms)::: las aplicaciones
Creación de extensiones de marcado XAML
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
En el nivel de programación, una extensión de marcado XAML es una clase que implementa IMarkupExtension la
IMarkupExtension<T> interfaz o. Puede explorar el código fuente de las extensiones de marcado estándar que se
describen a continuación en el directorio MarkupExtensions del :::no-loc(Xamarin.Forms)::: repositorio de github.
También es posible definir sus propias extensiones de marcado XAML personalizadas mediante la derivación de
IMarkupExtension o IMarkupExtension<T> . Use la forma genérica si la extensión de marcado obtiene un valor de
un tipo determinado. Este es el caso con varias de las :::no-loc(Xamarin.Forms)::: extensiones de marcado:
TypeExtension deriva de IMarkupExtension<Type> .
ArrayExtension deriva de IMarkupExtension<Array> .
DynamicResourceExtension deriva de IMarkupExtension<DynamicResource> .
BindingExtension deriva de IMarkupExtension<BindingBase> .
ConstraintExpression deriva de IMarkupExtension<Constraint> .

Las dos IMarkupExtension interfaces definen solo un método, denominado ProvideValue :

public interface IMarkupExtension


{
object ProvideValue(IServiceProvider serviceProvider);
}

public interface IMarkupExtension<out T> : IMarkupExtension


{
new T ProvideValue(IServiceProvider serviceProvider);
}

Dado que IMarkupExtension<T> deriva de IMarkupExtension e incluye la new palabra clave en ProvideValue ,
contiene ambos ProvideValue métodos.
Con mucha frecuencia, las extensiones de marcado XAML definen propiedades que contribuyen al valor devuelto.
(La excepción obvia es NullExtension , en la que ProvideValue simplemente devuelve null ). El ProvideValue
método tiene un único argumento de tipo IServiceProvider que se tratará más adelante en este artículo.

Una extensión de marcado para especificar el color


La siguiente extensión de marcado XAML le permite construir un Color valor mediante los componentes Hue,
saturación y luminosidad. Define cuatro propiedades para los cuatro componentes del color, incluido un
componente alfa que se inicializa en 1. La clase se deriva de IMarkupExtension<Color> para indicar un Color valor
devuelto:
public class HslColorExtension : IMarkupExtension<Color>
{
public double H { set; get; }

public double S { set; get; }

public double L { set; get; }

public double A { set; get; } = 1.0;

public Color ProvideValue(IServiceProvider serviceProvider)


{
return Color.FromHsla(H, S, L, A);
}

object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)


{
return (this as IMarkupExtension<Color>).ProvideValue(serviceProvider);
}
}

Dado que IMarkupExtension<T> deriva de IMarkupExtension , la clase debe contener dos ProvideValue métodos,
uno que devuelva Color y otro que devuelva object , pero el segundo método simplemente puede llamar al
primer método.
La página de demostración de color HSL muestra varias formas que HslColorExtension pueden aparecer en un
archivo XAML para especificar el color de un BoxView :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.HslColorDemoPage"
Title="HSL Color Demo">

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="BoxView">
<Setter Property="WidthRequest" Value="80" />
<Setter Property="HeightRequest" Value="80" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout>
<BoxView>
<BoxView.Color>
<local:HslColorExtension H="0" S="1" L="0.5" A="1" />
</BoxView.Color>
</BoxView>

<BoxView>
<BoxView.Color>
<local:HslColor H="0.33" S="1" L="0.5" />
</BoxView.Color>
</BoxView>

<BoxView Color="{local:HslColorExtension H=0.67, S=1, L=0.5}" />

<BoxView Color="{local:HslColor H=0, S=0, L=0.5}" />

<BoxView Color="{local:HslColor A=0.5}" />


</StackLayout>
</ContentPage>

Tenga en cuenta que cuando HslColorExtension es una etiqueta XML, las cuatro propiedades se establecen como
atributos, pero cuando aparecen entre llaves, las cuatro propiedades se separan mediante comas sin comillas. Los
valores predeterminados de H , S y L son 0, y el valor predeterminado de A es 1, por lo que se pueden omitir
esas propiedades si desea que se establezcan en valores predeterminados. En el último ejemplo se muestra un
ejemplo en el que la luminosidad es 0, que normalmente resulta en negro, pero el canal alfa es 0,5, por lo que es la
mitad transparente y aparece atenuada en el fondo blanco de la página:
Una extensión de marcado para tener acceso a mapas de bits
El argumento de ProvideValue es un objeto que implementa la IServiceProvider interfaz, que se define en el
espacio de System nombres .net. Esta interfaz tiene un miembro, un método denominado GetService con un
Type argumento.

La ImageResourceExtensionclase que se muestra a continuación muestra un posible uso de IServiceProvider y


GetService para obtener un IXmlLineInfoProvider objeto que pueda proporcionar información de línea y de
carácter que indique dónde se detectó un error determinado. En este caso, se produce una excepción cuando no se
ha Source establecido la propiedad:

[ContentProperty("Source")]
class ImageResourceExtension : IMarkupExtension<ImageSource>
{
public string Source { set; get; }

public ImageSource ProvideValue(IServiceProvider serviceProvider)


{
if (String.IsNullOrEmpty(Source))
{
IXmlLineInfoProvider lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider))
as IXmlLineInfoProvider;
IXmlLineInfo lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new
XmlLineInfo();
throw new XamlParseException("ImageResourceExtension requires Source property to be set",
lineInfo);
}

string assemblyName = GetType().GetTypeInfo().Assembly.GetName().Name;


return ImageSource.FromResource(assemblyName + "." + Source,
typeof(ImageResourceExtension).GetTypeInfo().Assembly);
}

object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)


{
return (this as IMarkupExtension<ImageSource>).ProvideValue(serviceProvider);
}
}

resulta útil cuando un archivo XAML necesita tener acceso a un archivo de imagen
ImageResourceExtension
almacenado como un recurso incrustado en el proyecto de biblioteca de .NET Standard. Utiliza la Source
propiedad para llamar al método estático ImageSource.FromResource . Este método requiere un nombre de recurso
completo, que consta del nombre del ensamblado, el nombre de la carpeta y el nombre de archivo separados por
puntos. El segundo argumento del ImageSource.FromResource método proporciona el nombre del ensamblado y
solo es necesario para las compilaciones de versión en UWP. Independientemente de, ImageSource.FromResource se
debe llamar al método desde el ensamblado que contiene el mapa de bits, lo que significa que esta extensión de
recursos XAML no puede formar parte de una biblioteca externa a menos que las imágenes estén también en esa
biblioteca. (Consulte el artículo imágenes incrustadas para obtener más información sobre el acceso a mapas
de bits almacenados como recursos incrustados).
Aunque ImageResourceExtension requiere Source que se establezca la propiedad, la Source propiedad se indica
en un atributo como la propiedad de contenido de la clase. Esto significa que Source= se puede omitir la parte de
la expresión entre llaves. En la página de demostración de recursos de imagen , los Image elementos
capturan dos imágenes con el nombre de la carpeta y el nombre de archivo separados por puntos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.ImageResourceDemoPage"
Title="Image Resource Demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Image Source="{local:ImageResource Images.SeatedMonkey.jpg}"


Grid.Row="0" />

<Image Source="{local:ImageResource Images.FacePalm.jpg}"


Grid.Row="1" />

</Grid>
</ContentPage>

Esta es la ejecución del programa:

Proveedores de servicios
Al usar el IServiceProvider argumento para ProvideValue , las extensiones de marcado XAML pueden obtener
acceso a información útil sobre el archivo XAML en el que se usan. Pero para usar el IServiceProvider argumento
correctamente, debe saber qué tipo de servicios están disponibles en contextos concretos. La mejor manera de
comprender esta característica es estudiar el código fuente de las extensiones de marcado XAML existentes en la
carpeta MarkupExtensions del :::no-loc(Xamarin.Forms)::: repositorio en github. Tenga en cuenta que algunos
tipos de servicios son internos a :::no-loc(Xamarin.Forms)::: .
En algunas extensiones de marcado XAML, este servicio puede ser útil:

IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as


IProvideValueTarget;

La IProvideValueTarget interfaz define dos propiedades, TargetObject y TargetProperty . Cuando esta


información se obtiene en la ImageResourceExtension clase, TargetObject es Image y TargetProperty es un
BindableProperty objeto para la Source propiedad de Image . Esta es la propiedad en la que se ha establecido la
extensión de marcado XAML.
La GetService llamada a con un argumento de typeof(IProvideValueTarget) realmente devuelve un objeto de
tipo SimpleValueTargetProvider , que se define en el :::no-loc(Xamarin.Forms):::.Xaml.Internals espacio de
nombres. Si convierte el valor devuelto de GetService a ese tipo, también puede tener acceso a una
ParentObjects propiedad, que es una matriz que contiene el Image elemento, el elemento Grid primario y el
elemento ImageResourceDemoPage primario de Grid .

Conclusión
Las extensiones de marcado XAML desempeñan un papel fundamental en XAML mediante la ampliación de la
capacidad de establecer atributos de diversos orígenes. Además, si las extensiones de marcado XAML existentes no
proporcionan exactamente lo que necesita, también puede escribir las suyas propias.

Vínculos relacionados
Extensiones de marcado (ejemplo)
Capítulo de extensiones de marcado XAML del :::no-loc(Xamarin.Forms)::: libro
Recarga activa de XAML para Xamarin.Forms
18/12/2020 • 9 minutes to read • Edit Online

La recarga activa de XAML se conecta al flujo de trabajo existente para aumentar su productividad y ahorrar
tiempo. Sin la recarga activa de XAML, debe compilar e implementar la aplicación cada vez que quiera ver un
cambio de XAML. Con la recarga activa, al guardar el archivo XAML, los cambios se reflejan en directo en la
aplicación en ejecución. Además, se mantendrán el estado y los datos de navegación, lo que le permitirá iterar
rápidamente en la interfaz de usuario sin perder su lugar en la aplicación. Por lo tanto, con la recarga activa de
XAML, pasará menos tiempo recompilando e implementando sus aplicaciones para validar los cambios de la
interfaz de usuario.

NOTE
Si está escribiendo una aplicación de WPF o de UWP, consulte recarga en caliente de XAML para UWP y WPF.
La recarga activa de XAML Xamarin.Forms para no funciona actualmente en los proyectos de Xamarin.Forms UWP.

Requisitos del sistema


IDE/ M A RC O DE T RA B A JO VERSIÓ N N EC ESA RIA

Visual Studio 2019 16,4 o superior

Visual Studio 2019 para Mac 8,4 o superior

Xamarin.Forms 4,1 o superior

Habilitar la recarga activa de XAML para Xamarin.Forms


Si se inicia desde una plantilla, la recarga activa de XAML está activada de forma predeterminada y el proyecto está
configurado para funcionar sin ninguna configuración adicional. Depure la aplicación en un emulador, simulador o
dispositivo físico Android o iOS, cambie el código XAML y guarde el archivo para desencadenar una recarga activa
de XAML.
Si está trabajando desde una solución existente Xamarin.Forms , no se requiere ninguna instalación adicional para
usar la recarga activa de XAML, pero es posible que tenga que comprobar la configuración para garantizar la mejor
experiencia. En primer lugar, habilítelo en la configuración del IDE:
En Windows, active la casilla Habilitar recarga activa de Xamarin en herramientas > Opciones , >
Xamarin > recarga en caliente de Xamarin.
En Mac, active la casilla Habilitar la recarga activa de Xamarin en herramientas de preferencias de Visual
Studio > Preferences > para laTools for Xamarin > recarga activa de Xamarin XAML.
En versiones anteriores de Visual Studio para Mac, el menú se encuentra en Visual Studio >
Preferences > Projects > Xamarin Hot Reload .
Después, en la configuración de compilación de iOS y Android, compruebe que el enlazador está establecido en
"no vincular" o "vincular ninguno". Para usar la recarga activa de XAML con un dispositivo iOS físico, también tiene
que activar la casilla habilitar el intérprete mono (visual Studio 16,4 y versiones posteriores) o Add --
Interpreter para Mtouch args adicionales (Visual Studio 16,3 y versiones anteriores).
Puede usar el siguiente diagrama de flujo para comprobar la configuración del proyecto existente para su uso con
la recarga activa de XAML:

Recarga resistente
Si realiza un cambio que no se puede volver a cargar la recarga activa de XAML, se mostrará un mensaje de error
con IntelliSense. Estos cambios, conocidos como ediciones forzadas, incluyen escribir indirectamente el código
XAML o conectar un control a un controlador de eventos que no existe. Incluso con una edición forzada, puede
seguir recargando sin reiniciar la aplicación: haga otro cambio en otra parte del archivo XAML y presione guardar.
La edición forzada no se recargará, pero se seguirán aplicando los demás cambios.

Recarga en varias plataformas a la vez


La recarga activa de XAML admite la depuración simultánea en Visual Studio y Visual Studio para Mac. Puede
implementar un dispositivo Android y un destino iOS al mismo tiempo para ver los cambios reflejados en ambas
plataformas a la vez. Para depurar en varias plataformas, consulte:
Windows Cómo: establecer proyectos de inicio múltiples
Mac establecer varios proyectos de inicio

Restricciones conocidas
Xamarin.FormsTodavía no se admiten otros destinos, como UWP y MacOS. Aquípuede realizar un seguimiento
del progreso del soporte técnico de UWP.
No se pueden agregar, quitar ni cambiar el nombre de archivos o paquetes NuGet durante una sesión de
recarga activa de XAML. Si agrega o quita un archivo o un paquete de NuGet, vuelva a compilar y volver a
implementar la aplicación para seguir usando la recarga activa de XAML.
Establezca el enlazador en no vincular o vincular ninguno para obtener la mejor experiencia. La
configuración de Link SDK solo funciona la mayor parte del tiempo, pero puede producir un error en ciertos
casos. La configuración del enlazador se puede encontrar en las opciones de compilación de iOS y Android.
La depuración en un iPhone físico requiere que el intérprete use la recarga activa de XAML. Para ello, abra la
configuración del proyecto, seleccione la pestaña compilación de iOS y asegúrese de que la opción Habilitar
el intérprete mono está habilitada. Es posible que tenga que cambiar la opción de plataforma en la parte
superior de la página de propiedades a iPhone .
Las referencias creadas mediante la asignación de un control a otro campo o propiedad utilizando su x:Name
valor no se volverán a cargar.
La actualización de la jerarquía visual de la aplicación de Shell en AppShell. Xaml puede producir problemas al
mantener el estado de la aplicación. Si tiene problemas, vuelva a compilar la aplicación para continuar con la
recarga.
La recarga activa de XAML no puede volver a cargar el código de C#, incluidos los controladores de eventos, los
controles personalizados, el código subyacente de la página y las clases adicionales.

Más recursos
Sugerencias y trucos para la recarga activa de XAML
Recarga activa de XAML para Xamarin.Forms en profundidad: el programa Xamarin

Solución de problemas
Si no se puede inicializar la recarga activa de XAML:
Actualice su Xamarin.Forms versión.
Asegúrese de que se encuentre en la versión más reciente del IDE.
Establezca la configuración del enlazador de Android o iOS en no vincular en la configuración de
compilación del proyecto.
Si no ocurre nada al guardar el archivo XAML, asegúrese de que la recarga activa de XAML está habilitada en el
IDE.
Si realiza la depuración en un iPhone físico y la aplicación deja de responder, compruebe que el intérprete está
habilitado. Para activarla, Active la casilla habilitar el intérprete mono (Visual Studio 16.4/8.4 y versiones
arriba) o Add --Interpreter en el campo Mtouch argumentos adicionales (Visual Studio 16.3/8.3 y anterior)
en la configuración de compilación de iOS.
Para notificar un error, use la ayuda > Enviar comentarios > notificar un problema en Windows y ayudar a
> notificar un problema en Mac.
Xamarin.FormsCuadro de herramientas XAML
18/12/2020 • 2 minutes to read • Edit Online

Visual Studio 2017 versión 15,8 y Visual Studio para Mac 7,6 ahora tienen un cuadro de herramientas disponible al
editar Xamarin.Forms archivos XAML. El cuadro de herramientas contiene todos los Xamarin.Forms controles y
diseños integrados, que se pueden arrastrar en el editor XAML.
Visual Studio
Visual Studio para Mac
En Visual Studio 2017, abra un Xamarin.Forms archivo XAML para editarlo. Para mostrar el cuadro de herramientas,
presione Ctrl + W, X en el teclado o elija el elemento de menú Ver > cuadro de herramientas .

El cuadro de herramientas se puede ocultar y acoplar como otros paneles de Visual Studio 2017, mediante los
iconos de la parte superior derecha o del menú contextual. El Xamarin.Forms cuadro de herramientas XAML tiene
opciones de vista personalizada que se pueden cambiar haciendo clic con el botón derecho en cada sección. Alterne
la opción de vista de lista para cambiar entre las vistas de lista y compactar:

Cuando Xamarin.Forms se abre un archivo XAML para su edición, arrastre cualquier control o diseño desde el
cuadro de herramientas hasta el archivo y, a continuación, aproveche IntelliSense para personalizar la interfaz de
usuario.
Vista previa de XAML para Xamarin.Forms
18/12/2020 • 7 minutes to read • Edit Online

Vea los Xamarin.Forms diseños que se representan a medida que escribe.

WARNING
El previsor de XAML comenzará a salir de la fase en Visual Studio 2019, versión 16,8 y Visual Studio para Mac versión 8,8. La
forma recomendada de obtener una vista previa del código XAML es ahora con la recarga activa de XAML .

Información general
El previsor de XAML muestra el aspecto que Xamarin.Forms tendrá la página XAML en iOS y Android. Cuando
realice cambios en el código XAML, verá que se muestra una vista previa de inmediato junto con el código. El
previsor de XAML está disponible en Visual Studio y Visual Studio para Mac.

Introducción
Visual Studio 2019
Para abrir el cuadro de vista previa de XAML, haga clic en las flechas del panel vista en dos paneles. Si desea
cambiar el comportamiento predeterminado de la vista en dos paneles, use las opciones de herramientas > >
cuadro de diálogo de Xamarin.Forms vista previa de Xamarin > XAML . En este cuadro de diálogo, puede
seleccionar la vista de documento predeterminada y la orientación de división.

Al abrir un archivo XAML, el editor abrirá el tamaño completo o junto al previsualizador, en función de la
configuración seleccionada en las Opciones herramientas > > cuadro de diálogo de Xamarin.Forms vista
previa de Xamarin > XAML . Sin embargo, se puede cambiar la división de cada archivo en la ventana del
editor.
Controles de vista previa de XAML
Elija si desea ver el código, el previsor de XAML o ambos; para ello, seleccione estos botones en el panel vista en
dos paneles. El botón central intercambia el lado del código que está en la vista previa y el código:

Puede cambiar si la pantalla está dividida vertical u horizontalmente o contraer un panel por completo:
Habilitar o deshabilitar el previsor de XAML
Puede desactivar la vista previa de XAML en el cuadro de diálogo herramientas > opciones > Xamarin >
Xamarin.Forms XAML de vista previa seleccionando Editor XML predeterminado como editor XAML
predeterminado . Esto también desactiva el esquema del documento, el panel de propiedades y el cuadro de
herramientas XAML. Para volver a activar la vista previa de XAML y las herramientas, cambie el editor XAML
predeterminado a Xamarin.Forms vista previa .
Visual Studio para Mac
El botón vista previa se muestra en el editor cuando se abre una página XAML. Para mostrar u ocultar el previsor,
presione los botones vista previa o dividir en la parte inferior izquierda de cualquier ventana de documento
XAML:

NOTE
En versiones anteriores de Visual Studio para Mac, el botón vista previa se encontraba en la parte superior derecha de la
ventana.

Habilitar o deshabilitar el previsor de XAML


Puede desactivar la vista previa de XAML en el cuadro de diálogo preferencias de > de Visual Studio > editor
de texto > XAML seleccionando Editor XML predeterminado como editor XAML predeterminado . Esto
también desactiva el esquema del documento, el panel de propiedades y el cuadro de herramientas XAML. Para
volver a activar la vista previa de XAML y las herramientas, cambie el editor XAML predeterminado a
Xamarin.Forms vista previa .

Opciones de vista previa de XAML


Las opciones que aparecen a lo largo de la parte superior del panel de vista previa son:
Android : Mostrar la versión de Android de la pantalla
iOS : Mostrar la versión de iOS de la pantalla (Nota: Si usa Visual Studio en Windows, debe emparejarse con un
equipo Mac para usar este modo).
Lista desplegable de dispositivos de dispositivos Android o iOS, incluida la resolución y el tamaño de
pantalla
Ver tical (icono) : usa la orientación vertical para la vista previa
Horizontal (icono) : usa la orientación horizontal para la vista previa

Detectar el modo de diseño


La DesignMode.IsDesignModeEnabled propiedad estática indica si la aplicación se está ejecutando en el visor de vista
previa. Si lo usa, puede especificar código que solo se ejecutará cuando la aplicación se esté ejecutando en el visor
de vista previa:

if (DesignMode.IsDesignModeEnabled)
{
// Previewer only code
}

if (!DesignMode.IsDesignModeEnabled)
{
// Don't run in the Previewer
}

Esta propiedad es útil si Inicializa una biblioteca en el constructor de páginas que no se puede ejecutar en tiempo
de diseño.

Solución de problemas
Compruebe los problemas siguientes y los foros de Xamarin, si el visor de vista previa no funciona.
El previsor XAML no se muestra o muestra un error
Puede tardar algún tiempo en iniciarse el previsor; verá "inicializando representación" hasta que esté listo.
Intente cerrar y volver a abrir el archivo XAML.
Asegúrese de que la App clase tiene un constructor sin parámetros.
Compruebe su Xamarin.Forms versión: debe ser al menos Xamarin.Forms 3,6. Puede actualizar a la versión más
reciente Xamarin.Forms a través de NuGet.
Comprobar la instalación de JDK: la vista previa de Android requiere al menos JDK 8.
Intente envolver cualquier clase inicializada en el código C# de la página que se encuentra detrás de
if (!DesignMode.IsDesignModeEnabled) .

Los controles personalizados no se representan


Intente compilar el proyecto. El controlador de vista previa muestra la clase base del control si no puede
representar el control o si el creador del control optó por la representación en tiempo de diseño. Para obtener más
información, vea representar controles personalizados en la vista previa de XAML.
Usar datos de tiempo de diseño con el vista previa de
XAML
18/12/2020 • 6 minutes to read • Edit Online

Algunos diseños son difíciles de visualizar sin datos. Use estas sugerencias para sacar el máximo partido de las
páginas con gran cantidad de datos en el visor de vistas previas de XAML.

NOTE
Si usa Windows Presentation Foundation (WPF) o UWP, consulte usar datos de tiempo de diseño con el diseñador XAML para
aplicaciones de escritorio .

Aspectos básicos de los datos de tiempo de diseño


Los datos en tiempo de diseño son datos falsos que se establecen para que los controles sean más fáciles de
visualizar en el visor de vista previa de XAML. Para empezar, agregue las siguientes líneas de código al encabezado
de la página XAML:

xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"

Después de agregar los espacios de nombres, puede colocar d: delante de cualquier atributo o control para
mostrarlo en el controlador de vista previa de XAML. Los elementos con d: no se muestran en tiempo de
ejecución.
Por ejemplo, puede Agregar texto a una etiqueta que normalmente tiene datos enlazados a él.

<Label Text="{Binding Name}" d:Text="Name!" />

En este ejemplo, sin d:Text , el previsor de XAML no mostraría nada para la etiqueta. En su lugar, muestra "Name!"
donde la etiqueta tendrá datos reales en tiempo de ejecución.
Puede usar d: con cualquier atributo de un Xamarin.Forms control, como colores, tamaños de fuente y espaciado.
Incluso puede agregarlo al propio control:
<d:Button Text="Design Time Button" />

En este ejemplo, el botón solo aparece en tiempo de diseño. Use este método para colocar un marcador de
posición en para un control personalizado no admitido por el controlador de vista previa de XAML.

Vista previa de imágenes en tiempo de diseño


Puede establecer un origen de tiempo de diseño para las imágenes enlazadas a la página o cargadas
dinámicamente. En el proyecto de Android, agregue la imagen que quiere mostrar en el vista previa de XAML a los
recursos > carpeta drawable . En el proyecto de iOS, agregue la imagen a la carpeta recursos . Después, puede
mostrar esa imagen en la vista previa de XAML en tiempo de diseño:

<Image Source={Binding ProfilePicture} d:Source="DesignTimePicture.jpg" />


Datos de tiempo de diseño para ListView
Los controles ListView son una manera popular de mostrar los datos en una aplicación móvil. Sin embargo, son
difíciles de visualizar sin datos reales. Para usar los datos de tiempo de diseño con ellos, tiene que crear una matriz
de tiempo de diseño para usarla como ItemsSource. El objeto de vista previa de XAML muestra lo que hay en esa
matriz en el objeto ListView en tiempo de diseño.

<StackLayout>
<ListView ItemsSource="{Binding Items}">
<d:ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Item One</x:String>
<x:String>Item Two</x:String>
<x:String>Item Three</x:String>
</x:Array>
</d:ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding ItemName}"
d:Text="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
En este ejemplo se muestra un control ListView de tres TextCells en el objeto de vista previa de XAML. Puede
cambiar x:String a un modelo de datos existente en el proyecto.
También puede crear una matriz de objetos de datos. Por ejemplo, las propiedades públicas de un Monkey objeto
de datos se pueden construir como datos de tiempo de diseño:

namespace Monkeys.Models
{
public class Monkey
{
public string Name { get; set; }
public string Location { get; set; }
}
}

Para usar la clase en XAML, deberá importar el espacio de nombres en el nodo raíz:

xmlns:models="clr-namespace:Monkeys.Models"

<StackLayout>
<ListView ItemsSource="{Binding Items}">
<d:ListView.ItemsSource>
<x:Array Type="{x:Type models:Monkey}">
<models:Monkey Name="Baboon" Location="Africa and Asia"/>
<models:Monkey Name="Capuchin Monkey" Location="Central and South America"/>
<models:Monkey Name="Blue Monkey" Location="Central and East Africa"/>
</x:Array>
</d:ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Monkey">
<TextCell Text="{Binding Name}"
Detail="{Binding Location}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>

La ventaja es que puede enlazar con el modelo real que piensa usar.

Alternativa: codificar un ViewModel estático


Si no desea agregar datos de tiempo de diseño a controles individuales, puede configurar un almacén de datos
ficticios para enlazar a la página. Consulte la entrada de blog de James Montemagno sobre cómo agregar datos en
tiempo de diseño para ver cómo enlazar a un ViewModel estático en XAML.
Solucionar problemas
Requisitos
Los datos de tiempo de diseño requieren una versión mínima de Xamarin.Forms 3,6.
IntelliSense muestra líneas onduladas en los datos de tiempo de diseño
Se trata de un problema conocido y se corregirá en una próxima versión de Visual Studio. El proyecto se compilará
sin errores.
El previsor de XAML dejó de funcionar
Pruebe a cerrar y volver a abrir el archivo XAML, y limpie y vuelva a compilar el proyecto.
Representar controles personalizados en el previsor
de XAML
18/12/2020 • 3 minutes to read • Edit Online

A veces, los controles personalizados no funcionan como se esperaba en la vista previa de XAML. Siga las
instrucciones de este artículo para conocer las limitaciones de la vista previa de los controles personalizados.

Modo de vista previa básica


Incluso si no ha compilado el proyecto, el previsor de XAML representará las páginas. Hasta que se compile,
cualquier control que se base en el código subyacente mostrará su Xamarin.Forms tipo base. Cuando se compila el
proyecto, el previsor de XAML intentará mostrar los controles personalizados con la representación en tiempo de
diseño habilitada. Si se produce un error en el procesamiento, se mostrará el Xamarin.Forms tipo base.

Habilitar la representación en tiempo de diseño para controles


personalizados
Si crea sus propios controles personalizados o usa controles de una biblioteca de terceros, el previsor podría
mostrarlos incorrectamente. Los controles personalizados deben optar por la representación en tiempo de diseño
para que aparezcan en el controlador de vista previa, tanto si escribió el control como si lo importó desde una
biblioteca. Con los controles que ha creado, agregue [DesignTimeVisible(true)] a la clase del control para
mostrarlo en el controlador de vista previa:

namespace MyProject
{
[DesignTimeVisible(true)]
public class MyControl : BaseControl
{
// Your control's code here
}

Use la clase base ImageCirclePlugin's de James Montemagno como ejemplo.

Controles SkiaSharp
Actualmente, los controles de SkiaSharp solo se admiten cuando se realiza una vista previa en iOS. No se
representarán en la versión preliminar de Android.

Solucionar problemas
Comprobar la Xamarin.Forms versión
Asegúrese de que tiene instalado al menos Xamarin.Forms 3,6. Puede actualizar su Xamarin.Forms versión en
NuGet.
Incluso con [DesignTimeVisible(true)] , el control personalizado no se representa correctamente.
Los controles personalizados que se basan en gran medida en los datos de código subyacente o de back-end no
funcionan siempre en el visor de vista previa de XAML. Pruebe lo siguiente:
Mover el control para que no se inicialice si está habilitado el modo de diseño
Configurar los datos de tiempo de diseño para Mostrar datos falsos del back-end
El previsor de XAML muestra el error "los controles personalizados no se representan correctamente"
Intente limpiar y volver a generar el proyecto, o bien cierre y vuelva a abrir el archivo XAML.
Espacios de nombres XAML enXamarin.Forms
18/12/2020 • 8 minutes to read • Edit Online

XAML usa el atributo XML xmlns para las declaraciones de espacio de nombres. En este artículo se presenta la
sintaxis del espacio de nombres XAML y se muestra cómo declarar un espacio de nombres XAML para tener
acceso a un tipo.

Información general
Hay dos declaraciones de espacios de nombres XAML que siempre están dentro del elemento raíz de un archivo
XAML. El primero define el espacio de nombres predeterminado, tal y como se muestra en el siguiente ejemplo de
código XAML:

xmlns="http://xamarin.com/schemas/2014/forms"

El espacio de nombres predeterminado especifica que los elementos definidos dentro del archivo XAML sin
prefijo hagan referencia a Xamarin.Forms clases como, por ejemplo, ContentPage .
La segunda declaración de espacio de nombres usa el x prefijo, como se muestra en el siguiente ejemplo de
código XAML:

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

XAML usa prefijos para declarar espacios de nombres no predeterminados, con el prefijo que se usa al hacer
referencia a los tipos del espacio de nombres. La x declaración de espacio de nombres especifica que los
elementos definidos dentro de XAML con un prefijo de x se usan para los elementos y atributos que son
intrínsecos a XAML (concretamente, la especificación XAML 2009).
En la tabla siguiente se describen los x atributos de espacio de nombres admitidos por Xamarin.Forms :

C O N ST RUC C IÓ N DESC RIP C IÓ N

x:Arguments Especifica los argumentos de constructor para un constructor


no predeterminado o para una declaración de objeto Factory
Method.

x:Class Especifica el espacio de nombres y el nombre de clase de una


clase definida en XAML. El nombre de clase debe coincidir con
el nombre de clase del archivo de código subyacente. Tenga
en cuenta que esta construcción solo puede aparecer en el
elemento raíz de un archivo XAML.

x:DataType Especifica el tipo del objeto al que se enlazará el elemento


XAML y sus elementos secundarios.

x:FactoryMethod Especifica un Factory Method que se puede utilizar para


inicializar un objeto.

x:FieldModifier Especifica el nivel de acceso para los campos generados para


los elementos XAML con nombre.
C O N ST RUC C IÓ N DESC RIP C IÓ N

x:Key Especifica una clave única definida por el usuario para cada
recurso de un ResourceDictionary . El valor de la clave se
usa para recuperar el recurso XAML y se utiliza normalmente
como argumento para la StaticResource extensión de
marcado.

x:Name Especifica un nombre de objeto en tiempo de ejecución para


el elemento XAML. x:Name La configuración es similar a la
declaración de una variable en el código.

x:TypeArguments Especifica los argumentos de tipo genérico para el constructor


de un tipo genérico.

Para obtener más información sobre el x:DataType atributo, vea enlaces compilados. Para obtener más
información sobre el x:FieldModifier atributo, vea modificadores de campo. Para obtener más información
sobre x:Arguments los x:FactoryMethod atributos y, vea pasar argumentos en XAML. Para obtener más
información sobre el x:TypeArguments atributo, vea genéricos en XAML Xamarin.Forms con .

NOTE
Además de los atributos de espacio de nombres enumerados anteriormente, Xamarin.Forms también incluye extensiones de
marcado que se pueden consumir a través del x Prefijo de espacio de nombres. Para obtener más información, consulte
consumo de extensiones de marcado XAML.

En XAML, las declaraciones de espacio de nombres heredan del elemento primario al elemento secundario. Por lo
tanto, al definir un espacio de nombres en el elemento raíz de un archivo XAML, todos los elementos de ese
archivo heredan la declaración de espacio de nombres.

Declarar espacios de nombres para tipos


Se puede hacer referencia a los tipos en XAML declarando un espacio de nombres XAML con un prefijo, con la
declaración de espacio de nombres especificando el nombre del espacio de nombres de Common Language
Runtime (CLR) y, opcionalmente, un nombre de ensamblado. Esto se logra definiendo los valores de las siguientes
palabras clave dentro de la declaración del espacio de nombres:
CLR-namespace: o Using: : el espacio de nombres CLR declarado en el ensamblado que contiene los tipos
que se van a exponer como elementos XAML. Esta palabra clave es obligatoria.
Assembly = : el ensamblado que contiene el espacio de nombres CLR al que se hace referencia. Este valor es
el nombre del ensamblado, sin la extensión de archivo. La ruta de acceso al ensamblado debe establecerse
como una referencia en el archivo de proyecto que contiene el archivo XAML que hará referencia al
ensamblado. Esta palabra clave se puede omitir si el valor del espacio de nombres CLR está dentro del
mismo ensamblado que el código de aplicación que hace referencia a los tipos.
Tenga en cuenta que el carácter que separa el clr-namespace using token o de su valor es un signo de dos
puntos, mientras que el carácter que separa el assembly token de su valor es un signo igual. El carácter que se va
a usar entre los dos tokens es un punto y coma.
En el ejemplo de código siguiente se muestra una declaración de espacio de nombres XAML:
<ContentPage ... xmlns:local="clr-namespace:HelloWorld" ...>
...
</ContentPage>

Como alternativa, puede escribirse como:

<ContentPage ... xmlns:local="using:HelloWorld" ...>


...
</ContentPage>

El local prefijo es una Convención que se usa para indicar que los tipos del espacio de nombres son locales para
la aplicación. Como alternativa, si los tipos están en un ensamblado diferente, el nombre de ensamblado también
debe definirse en la declaración de espacio de nombres, tal y como se muestra en el siguiente ejemplo de código
XAML:

<ContentPage ... xmlns:behaviors="clr-namespace:Behaviors;assembly=BehaviorsLibrary" ...>


...
</ContentPage>

A continuación, se especifica el prefijo de espacio de nombres al declarar una instancia de un tipo a partir de un
espacio de nombres importado, tal y como se muestra en el siguiente ejemplo de código XAML:

<ListView ...>
<ListView.Behaviors>
<behaviors:EventToCommandBehavior EventName="ItemSelected" ... />
</ListView.Behaviors>
</ListView>

Para obtener información sobre cómo definir un esquema de espacio de nombres personalizado, vea esquemas
de espacios de nombres personalizados XAML.

Resumen
En este artículo se ha introducido la sintaxis del espacio de nombres XAML y se ha mostrado cómo declarar un
espacio de nombres XAML para tener acceso a un tipo. XAML usa el xmlns atributo XML para las declaraciones
de espacio de nombres y se puede hacer referencia a los tipos en XAML declarando un espacio de nombres XAML
con un prefijo.

Vínculos relacionados
Paso de argumentos en XAML
Genéricos en XAML conXamarin.Forms
Esquemas de espacios de nombres personalizados
XAML en :::no-loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Se puede hacer referencia a los tipos de una biblioteca en XAML declarando un espacio de nombres XAML para la
biblioteca, con la declaración de espacio de nombres que especifica el nombre del espacio de nombres de
Common Language Runtime (CLR) y un nombre de ensamblado:

<ContentPage ...
xmlns:controls="clr-namespace:MyCompany.Controls;assembly=MyCompany.Controls">
...
</ContentPage>

Sin embargo, si se especifica un nombre de ensamblado y un espacio de nombres CLR en una xmlns definición,
puede resultar difícil y propenso a errores. Además, es posible que se requieran varias declaraciones de espacios
de nombres XAML si la biblioteca contiene tipos en varios espacios de nombres.
Un enfoque alternativo es definir un esquema de espacio de nombres personalizado, como
http://mycompany.com/schemas/controls , que se asigna a uno o varios espacios de nombres CLR. Esto permite que
una única declaración de espacio de nombres XAML haga referencia a todos los tipos de un ensamblado, aunque
estén en diferentes espacios de nombres. También habilita una única declaración de espacio de nombres XAML
para hacer referencia a los tipos de varios ensamblados.
Para obtener más información sobre los espacios de nombres XAML, vea espacios :::no-loc(Xamarin.Forms)::: de
nombres XAML en .

Definir un esquema de espacio de nombres personalizado


La aplicación de ejemplo contiene una biblioteca que expone algunos controles simples, como CircleButton :

using :::no-loc(Xamarin.Forms):::;

namespace MyCompany.Controls
{
public class CircleButton : Button
{
...
}
}

Todos los controles de la biblioteca residen en el MyCompany.Controls espacio de nombres. Estos controles se
pueden exponer a un ensamblado de llamada a través de un esquema de espacio de nombres personalizado.
Un esquema de espacio de nombres personalizado se define con la XmlnsDefinitionAttribute clase, que especifica
la asignación entre un espacio de nombres XAML y uno o varios espacios de nombres CLR.
XmlnsDefinitionAttribute Toma dos argumentos: el nombre del espacio de nombres XAML y el nombre del
espacio de nombres CLR. El nombre del espacio de nombres XAML se almacena en la
XmlnsDefinitionAttribute.XmlNamespace propiedad y el nombre del espacio de nombres CLR se almacena en la
XmlnsDefinitionAttribute.ClrNamespace propiedad.
NOTE
La XmlnsDefinitionAttribute clase también tiene una propiedad denominada AssemblyName , que se puede establecer
opcionalmente en el nombre del ensamblado. Esto solo es necesario cuando un espacio de nombres CLR al que se hace
referencia desde un XmlnsDefinitionAttribute se encuentra en un ensamblado externo.

XmlnsDefinitionAttributeDebe definirse en el nivel de ensamblado del proyecto que contiene los espacios de
nombres CLR que se asignarán en el esquema de espacio de nombres personalizado. En el ejemplo siguiente se
muestra el archivo AssemblyInfo.CS de la aplicación de ejemplo:

using :::no-loc(Xamarin.Forms):::;
using MyCompany.Controls;

[assembly: Preserve]
[assembly: XmlnsDefinition("http://mycompany.com/schemas/controls", "MyCompany.Controls")]

Este código crea un esquema de espacio de nombres personalizado que asigna la


http://mycompany.com/schemas/controls dirección URL al MyCompany.Controls espacio de nombres CLR. Además, el
Preserve atributo se especifica en el ensamblado para asegurarse de que el vinculador conserva todos los tipos
del ensamblado.

IMPORTANT
El Preserve atributo se debe aplicar a las clases del ensamblado que se asignan a través del esquema del espacio de
nombres personalizado o se aplican a todo el ensamblado.

El esquema de espacio de nombres personalizado se puede usar para la resolución de tipos en los archivos XAML.

Consumir un esquema de espacio de nombres personalizado


Para consumir tipos del esquema de espacio de nombres personalizado, el compilador XAML requiere que haya
una referencia de código del ensamblado que consuma los tipos, al ensamblado que define los tipos. Esto puede
realizarse agregando una clase que contiene un Init método al ensamblado que define los tipos que se
consumirán a través de XAML:

namespace MyCompany.Controls
{
public static class Controls
{
public static void Init()
{
}
}
}

Init A continuación, se puede llamar al método desde el ensamblado que consume tipos del esquema de espacio
de nombres personalizado:
using :::no-loc(Xamarin.Forms):::;
using MyCompany.Controls;

namespace CustomNamespaceSchemaDemo
{
public partial class MainPage : ContentPage
{
public MainPage()
{
Controls.Init();
InitializeComponent();
}
}
}

WARNING
Si no se incluye este tipo de referencia de código, el compilador XAML no podrá encontrar el ensamblado que contiene los
tipos de esquema del espacio de nombres personalizado.

Para consumir el CircleButton control, se declara un espacio de nombres XAML, con la declaración del espacio de
nombres que especifica la dirección URL del esquema del espacio de nombres personalizado:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="http://mycompany.com/schemas/controls"
x:Class="CustomNamespaceSchemaDemo.MainPage">
<StackLayout Margin="20,35,20,20">
...
<controls:CircleButton Text="+"
BackgroundColor="Fuchsia"
BorderColor="Black"
CircleDiameter="100" />
<controls:CircleButton Text="-"
BackgroundColor="Teal"
BorderColor="Silver"
CircleDiameter="70" />
...
</StackLayout>
</ContentPage>

CircleButton después, las instancias se pueden agregar al ContentPage declararlas con el controls Prefijo de
espacio de nombres.
Para buscar los tipos de esquema de espacio de nombres personalizados, :::no-loc(Xamarin.Forms)::: buscará en los
ensamblados a los que se hace referencia XmlnsDefinitionAttribute . Si el xmlns atributo de un elemento de un
archivo XAML coincide con el XmlNamespace valor de propiedad en XmlnsDefinitionAttribute , :::no-
loc(Xamarin.Forms)::: intentará usar el XmlnsDefinitionAttribute.ClrNamespace valor de propiedad para la
resolución del tipo. Si se produce un error en la resolución de tipos, :::no-loc(Xamarin.Forms)::: seguirá intentando
la resolución de tipos basándose en las instancias coincidentes adicionales XmlnsDefinitionAttribute .
El resultado es que CircleButton se muestran dos instancias:
Vínculos relacionados
Esquemas de espacios de nombres personalizados (ejemplo)
Prefijos recomendados de espacio de nombres de XAML
Espacios de nombres XAML en :::no-loc(Xamarin.Forms):::
Prefijos recomendados del espacio de nombres
XAML enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Los XmlnsPrefixAttribute autores de controles pueden usar la clase para especificar un prefijo recomendado para
asociarlo a un espacio de nombres XAML, para el uso de XAML. El prefijo es útil al admitir la serialización del árbol
de objetos en XAML o al interactuar con un entorno de diseño que tiene características de edición de XAML. Por
ejemplo:
Los editores de texto XAML podrían usar XmlnsPrefixAttribute como sugerencia una asignación inicial de
espacio de nombres XAML xmlns .
Los entornos de diseño XAML podrían usar XmlnsPrefixAttribute para agregar asignaciones al XAML al
arrastrar objetos fuera de un cuadro de herramientas y a una superficie de diseño visual.
Los prefijos de espacio de nombres recomendados deben definirse en el nivel de ensamblado con el
XmlnsPrefixAttribute constructor, que toma dos argumentos: una cadena que especifica el identificador de un
espacio de nombres XAML y una cadena que especifica un prefijo recomendado:

[assembly: XmlnsPrefix("http://xamarin.com/schemas/2014/forms", "xf")]

Los prefijos deben usar cadenas cortas, porque el prefijo se aplica normalmente a todos los elementos serializados
que proceden del espacio de nombres XAML. Por lo tanto, la longitud de la cadena de prefijo puede tener un efecto
perceptible en el tamaño de la salida XAML serializada.

NOTE
XmlnsPrefixAttribute Se puede aplicar más de una a un ensamblado. Por ejemplo, si tiene un ensamblado que define los
tipos de más de un espacio de nombres XAML, puede definir diferentes valores de prefijo para cada espacio de nombres
XAML.

Vínculos relacionados
Esquemas de espacio de nombres personalizado XAML
Espacios de nombres XAML enXamarin.Forms
:::no-loc(Xamarin.Forms)::: Propiedades enlazables
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
Las propiedades enlazables extienden la funcionalidad de propiedad de CLR mediante la copia de seguridad de
una propiedad con un BindableProperty tipo, en lugar de hacer una copia de seguridad de una propiedad con un
campo. El propósito de las propiedades enlazables es proporcionar un sistema de propiedades que admita el
enlace de datos, los estilos, las plantillas y los valores establecidos mediante relaciones de elementos primarios y
secundarios. Además, las propiedades enlazables pueden proporcionar valores predeterminados, la validación de
los valores de propiedad y las devoluciones de llamada que supervisan los cambios de propiedad.
Las propiedades deben implementarse como propiedades enlazables para admitir una o varias de las
características siguientes:
Actuando como una propiedad de destino válida para el enlace de datos.
Establecer la propiedad a través de un estilo.
Proporcionar un valor de propiedad predeterminado diferente del predeterminado para el tipo de la
propiedad.
Validando el valor de la propiedad.
Supervisión de los cambios de propiedad.
Entre los ejemplos de :::no-loc(Xamarin.Forms)::: propiedades enlazables se incluyen Label.Text ,
Button.BorderRadius y StackLayout.Orientation . Cada propiedad enlazable tiene un public static readonly
campo correspondiente de tipo BindableProperty que se expone en la misma clase y que es el identificador de la
propiedad enlazable. Por ejemplo, el identificador de propiedad enlazable correspondiente para la Label.Text
propiedad es Label.TextProperty .

Crear una propiedad enlazable


El proceso para crear una propiedad enlazable es el siguiente:
1. Cree una BindableProperty instancia de con una de las BindableProperty.Create sobrecargas del método.
2. Definir los descriptores de acceso de las propiedades de la BindableProperty instancia.
Todas BindableProperty las instancias deben crearse en el subproceso de la interfaz de usuario. Esto significa que
solo el código que se ejecuta en el subproceso de la interfaz de usuario puede obtener o establecer el valor de una
propiedad enlazable. Sin embargo, BindableProperty se puede tener acceso a las instancias desde otros
subprocesos si se calculan las referencias al subproceso de interfaz de usuario con el
Device.BeginInvokeOnMainThread método.

Crear una propiedad


Para crear una BindableProperty instancia de, la clase contenedora debe derivar de la BindableObject clase. Sin
embargo, la BindableObject clase es alta en la jerarquía de clases, por lo que la mayoría de las clases utilizadas
para la funcionalidad de la interfaz de usuario admiten propiedades enlazables.
Se puede crear una propiedad enlazable mediante la declaración public static readonly de una propiedad de
tipo BindableProperty . La propiedad enlazable debe establecerse en el valor devuelto de uno de los [
BindableProperty.Create ] (XREF: :::no-loc(Xamarin.Forms)::: . BindableProperty. Create (System. String, System.
Type, System. Type, System. Object, :::no-loc(Xamarin.Forms)::: . BindingMode, :::no-loc(Xamarin.Forms)::: .
BindableProperty. ValidateValueDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
BindingPropertyChangedDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
BindingPropertyChangingDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty. CoerceValueDelegate, :::no-
loc(Xamarin.Forms)::: . Sobrecargas del método BindableProperty. CreateDefaultValueDelegate)). La declaración
debe estar dentro del cuerpo de la BindableObject clase derivada, pero fuera de las definiciones de miembro.
Como mínimo, se debe especificar un identificador al crear BindableProperty , junto con los parámetros
siguientes:
Nombre del BindableProperty .
Tipo de la propiedad.
Tipo del objeto propietario.
Valor predeterminado de la propiedad. Esto garantiza que la propiedad siempre devuelve un valor
predeterminado determinado cuando no está establecido y puede ser diferente del valor predeterminado para
el tipo de la propiedad. El valor predeterminado se restaurará cuando [ ClearValue ] (XREF: :::no-
loc(Xamarin.Forms)::: . BindableObject. ClearValue ( :::no-loc(Xamarin.Forms)::: . BindableProperty)) en la
propiedad enlazable.

IMPORTANT
La Convención de nomenclatura para las propiedades enlazables es que el identificador de la propiedad enlazable debe
coincidir con el nombre de propiedad especificado en el Create método, con la propiedad "Property" anexada.

En el código siguiente se muestra un ejemplo de una propiedad enlazable, con un identificador y valores para los
cuatro parámetros necesarios:

public static readonly BindableProperty EventNameProperty =


BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null);

Esto crea una BindableProperty instancia denominada EventNameProperty , de tipo string . La propiedad es
propiedad de la EventToCommandBehavior clase y su valor predeterminado es null .
Opcionalmente, al crear una BindableProperty instancia de, se pueden especificar los parámetros siguientes:
Modo de enlace. Se utiliza para especificar la dirección en la que se propagarán los cambios de valores de
propiedad. En el modo de enlace predeterminado, los cambios se propagarán desde el origen al destino.
Delegado de validación que se invocará cuando se establezca el valor de propiedad. Para obtener más
información, consulte devoluciones de llamada de validación.
Un delegado de propiedad cambiado que se invocará cuando el valor de propiedad haya cambiado. Para
obtener más información, vea detectar cambiosen las propiedades.
Un delegado de cambio de propiedad que se invocará cuando se cambie el valor de propiedad. Este delegado
tiene la misma firma que el delegado de propiedad cambiada.
Delegado de valor de conversión que se invocará cuando el valor de propiedad haya cambiado. Para obtener
más información, vea forzar devoluciones de llamada de valor.
Func Que se usa para inicializar un valor de propiedad predeterminado. Para obtener más información, vea
crear un valor predeterminado con una FUNC.
Crear descriptores de acceso
Los descriptores de acceso de propiedad son necesarios para utilizar la sintaxis de propiedad para tener acceso a
una propiedad enlazable. El Get descriptor de acceso debe devolver el valor incluido en la propiedad enlazable
correspondiente. Esto puede lograrse llamando a [ GetValue ] (XREF: :::no-loc(Xamarin.Forms)::: . BindableObject.
GetValue ( :::no-loc(Xamarin.Forms)::: . BindableProperty)), pasando el identificador de la propiedad enlazable en la
que se obtiene el valor y, a continuación, convirtiendo el resultado al tipo requerido. El Set descriptor de acceso
debe establecer el valor de la propiedad enlazable correspondiente. Esto puede lograrse llamando a [ SetValue ]
(XREF: :::no-loc(Xamarin.Forms)::: . BindableObject. SetValue ( :::no-loc(Xamarin.Forms)::: . BindableProperty, System.
Object)), pasando el identificador de la propiedad enlazable en la que se establece el valor y el valor que se va a
establecer.
En el ejemplo de código siguiente se muestran los descriptores de acceso de la EventName propiedad enlazable:

public string EventName


{
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}

Usar una propiedad enlazable


Una vez que se ha creado una propiedad enlazable, se puede usar desde XAML o código. En XAML, esto se logra
declarando un espacio de nombres con un prefijo, con la declaración del espacio de nombres que indica el
nombre del espacio de nombres CLR y, opcionalmente, un nombre de ensamblado. Para obtener más información,
vea espacios de nombres XAML.
En el ejemplo de código siguiente se muestra un espacio de nombres XAML para un tipo personalizado que
contiene una propiedad enlazable, que se define dentro del mismo ensamblado que el código de aplicación que
hace referencia al tipo personalizado:

<ContentPage ... xmlns:local="clr-namespace:EventToCommandBehavior" ...>


...
</ContentPage>

La declaración del espacio de nombres se usa al establecer la EventName propiedad enlazable, tal y como se
muestra en el siguiente ejemplo de código XAML:

<ListView ...>
<ListView.Behaviors>
<local:EventToCommandBehavior EventName="ItemSelected" ... />
</ListView.Behaviors>
</ListView>

El código de C# equivalente se muestra en el ejemplo de código siguiente:

var listView = new ListView ();


listView.Behaviors.Add (new EventToCommandBehavior
{
EventName = "ItemSelected",
...
});

Escenarios avanzados
Al crear una BindableProperty instancia, hay una serie de parámetros opcionales que se pueden establecer para
habilitar escenarios de propiedades enlazables avanzadas. En esta sección se analizan estos escenarios.
Detección de cambios de propiedades
Un static método de devolución de llamada de cambio de propiedad se puede registrar con una propiedad
enlazable especificando el propertyChanged parámetro para [ BindableProperty.Create ] (XREF: :::no-
loc(Xamarin.Forms)::: . BindableProperty. Create (System. String, System. Type, System. Type, System. Object, :::no-
loc(Xamarin.Forms)::: . BindingMode, :::no-loc(Xamarin.Forms)::: . BindableProperty. ValidateValueDelegate, :::no-
loc(Xamarin.Forms)::: . BindableProperty. BindingPropertyChangedDelegate, :::no-loc(Xamarin.Forms)::: .
BindableProperty. BindingPropertyChangingDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
CoerceValueDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty. CreateDefaultValueDelegate)). El método de
devolución de llamada especificado se invocará cuando cambie el valor de la propiedad enlazable.
En el ejemplo de código siguiente se muestra cómo la EventName propiedad enlazable registra el
OnEventNameChanged método como un método de devolución de llamada cambiado por propiedad:

public static readonly BindableProperty EventNameProperty =


BindableProperty.Create (
"EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
...

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


{
// Property changed implementation goes here
}

En el método de devolución de llamada de propiedad cambiada, el BindableObject parámetro se usa para indicar
qué instancia de la clase propietaria ha detectado un cambio y los valores de los dos object parámetros
representan los valores antiguos y nuevos de la propiedad enlazable.
Devoluciones de llamada de validación
Un static método de devolución de llamada de validación se puede registrar con una propiedad enlazable
especificando el validateValue parámetro para [ BindableProperty.Create ] (XREF: :::no-loc(Xamarin.Forms)::: .
BindableProperty. Create (System. String, System. Type, System. Type, System. Object, :::no-loc(Xamarin.Forms)::: .
BindingMode, :::no-loc(Xamarin.Forms)::: . BindableProperty. ValidateValueDelegate, :::no-loc(Xamarin.Forms)::: .
BindableProperty. BindingPropertyChangedDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
BindingPropertyChangingDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty. CoerceValueDelegate, :::no-
loc(Xamarin.Forms)::: . BindableProperty. CreateDefaultValueDelegate)). El método de devolución de llamada
especificado se invocará cuando se establezca el valor de la propiedad enlazable.
En el ejemplo de código siguiente se muestra cómo la Angle propiedad enlazable registra el IsValidValue
método como un método de devolución de llamada de validación:

public static readonly BindableProperty AngleProperty =


BindableProperty.Create ("Angle", typeof(double), typeof(HomePage), 0.0, validateValue: IsValidValue);
...

static bool IsValidValue (BindableObject view, object value)


{
double result;
bool isDouble = double.TryParse (value.ToString (), out result);
return (result >= 0 && result <= 360);
}

Las devoluciones de llamada de validación se proporcionan con un valor y deben devolver true si el valor es
válido para la propiedad; en caso contrario, false . Se producirá una excepción si se devuelve una devolución de
llamada de validación false , que debe ser administrada por el desarrollador. Un uso típico de un método de
devolución de llamada de validación es restringir los valores de enteros o dobles cuando se establece la propiedad
enlazable. Por ejemplo, el IsValidValue método comprueba que el valor de la propiedad es un double
comprendido entre 0 y 360.
Devoluciones de llamada de valor de coerción
Un static método de devolución de llamada de valor de coerción se puede registrar con una propiedad
enlazable especificando el coerceValue parámetro para [ BindableProperty.Create ] (XREF: :::no-
loc(Xamarin.Forms)::: . BindableProperty. Create (System. String, System. Type, System. Type, System. Object, :::no-
loc(Xamarin.Forms)::: . BindingMode, :::no-loc(Xamarin.Forms)::: . BindableProperty. ValidateValueDelegate, :::no-
loc(Xamarin.Forms)::: . BindableProperty. BindingPropertyChangedDelegate, :::no-loc(Xamarin.Forms)::: .
BindableProperty. BindingPropertyChangingDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
CoerceValueDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty. CreateDefaultValueDelegate)). El método de
devolución de llamada especificado se invocará cuando cambie el valor de la propiedad enlazable.

IMPORTANT
El BindableObject tipo tiene un CoerceValue método al que se puede llamar para forzar una reevaluación del valor de
su BindableProperty argumento, invocando su devolución de llamada de valor de coerción.

Las devoluciones de llamada de valor de coerción se usan para forzar una reevaluación de una propiedad
enlazable cuando el valor de la propiedad cambia. Por ejemplo, se puede usar una devolución de llamada de valor
de coerción para asegurarse de que el valor de una propiedad enlazable no sea mayor que el valor de otra
propiedad enlazable.
En el ejemplo de código siguiente se muestra cómo la Angle propiedad enlazable registra el CoerceAngle
método como un método de devolución de llamada de valor de conversión:

public static readonly BindableProperty AngleProperty = BindableProperty.Create (


"Angle", typeof(double), typeof(HomePage), 0.0, coerceValue: CoerceAngle);
public static readonly BindableProperty MaximumAngleProperty = BindableProperty.Create (
"MaximumAngle", typeof(double), typeof(HomePage), 360.0, propertyChanged: ForceCoerceValue);
...

static object CoerceAngle (BindableObject bindable, object value)


{
var homePage = bindable as HomePage;
double input = (double)value;

if (input > homePage.MaximumAngle)


{
input = homePage.MaximumAngle;
}
return input;
}

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


{
bindable.CoerceValue(AngleProperty);
}

El CoerceAngle método comprueba el valor de la MaximumAngle propiedad y, si el Angle valor de la propiedad es


mayor que, convierte el valor al valor de la MaximumAngle propiedad. Además, cuando la MaximumAngle propiedad
cambia la devolución de llamada de valor de coerción se invoca en la Angle propiedad llamando al CoerceValue
método.
Crear un valor predeterminado con FUNC
Func Se puede utilizar para inicializar el valor predeterminado de una propiedad enlazable, tal y como se muestra
en el ejemplo de código siguiente:
public static readonly BindableProperty SizeProperty =
BindableProperty.Create ("Size", typeof(double), typeof(HomePage), 0.0,
defaultValueCreator: bindable => Device.GetNamedSize (NamedSize.Large, (Label)bindable));

El defaultValueCreator parámetro se establece en Func que invoca [ Device.GetNamedSize ] (XREF: :::no-


loc(Xamarin.Forms)::: . Device. GetNamedSize ( :::no-loc(Xamarin.Forms)::: . NamedSize, System. Type)) para
devolver un double que representa el tamaño con nombre de la fuente que se usa en Label en la plataforma
nativa.

Vínculos relacionados
Espacios de nombres XAML
Comportamiento de eventos a comandos (ejemplo)
Devolución de llamada de validación (ejemplo)
Devolución de llamada de valor de coerción (ejemplo)
API de BindableProperty
API de BindableObject
Propiedades adjuntas
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
Las propiedades adjuntas permiten a un objeto asignar un valor para una propiedad que su propia clase no
define. Por ejemplo, los elementos secundarios pueden utilizar las propiedades adjuntas para informar a su
elemento primario de cómo deben presentarse en la interfaz de usuario. El Grid control permite especificar la
fila y la columna de un elemento secundario mediante el establecimiento de las Grid.Row Grid.Column
propiedades adjuntas y. Grid.Row y Grid.Column son propiedades adjuntas porque se establecen en elementos
que son elementos secundarios de un Grid , en lugar de en el Grid propio.
Las propiedades enlazables se deben implementar como propiedades adjuntas en los escenarios siguientes:
Cuando es necesario tener un mecanismo de configuración de propiedades disponible para las clases que no
sean la clase de definición.
Cuando la clase representa un servicio que debe integrarse fácilmente con otras clases.
Para obtener más información sobre las propiedades enlazables, vea propiedades enlazables.

Crear una propiedad adjunta


El proceso para crear una propiedad adjunta es el siguiente:
1. Cree una BindableProperty instancia de con una de las CreateAttached sobrecargas del método.
2. Proporcione static Get los métodos PropertyName y Set PropertyName como descriptores de acceso
para la propiedad adjunta.
Crear una propiedad
Al crear una propiedad adjunta para su uso en otros tipos, la clase en la que se crea la propiedad no tiene que
derivar de BindableObject . Sin embargo, la propiedad de destino de los descriptores de acceso debe ser de o
derivar de BindableObject .
Una propiedad adjunta se puede crear mediante la declaración public static readonly de una propiedad de
tipo BindableProperty . La propiedad enlazable debe establecerse en el valor devuelto de uno de los [
BindableProperty.CreateAttached ] (XREF: :::no-loc(Xamarin.Forms)::: . BindableProperty. CreateAttached (System.
String, System. Type, System. Type, System. Object, :::no-loc(Xamarin.Forms)::: . BindingMode, :::no-
loc(Xamarin.Forms)::: . BindableProperty. ValidateValueDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
BindingPropertyChangedDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty.
BindingPropertyChangingDelegate, :::no-loc(Xamarin.Forms)::: . BindableProperty. CoerceValueDelegate, :::no-
loc(Xamarin.Forms)::: . Sobrecargas del método BindableProperty. CreateDefaultValueDelegate)). La declaración
debe estar dentro del cuerpo de la clase propietaria, pero fuera de las definiciones de miembros.

IMPORTANT
La Convención de nomenclatura para las propiedades adjuntas es que el identificador de la propiedad adjunta debe
coincidir con el nombre de propiedad especificado en el CreateAttached método, con la propiedad "Property" anexada.

En el código siguiente se muestra un ejemplo de una propiedad adjunta:


public static readonly BindableProperty HasShadowProperty =
BindableProperty.CreateAttached ("HasShadow", typeof(bool), typeof(ShadowEffect), false);

Esto crea una propiedad adjunta denominada HasShadowProperty , de tipo bool . La propiedad es propiedad de
la ShadowEffect clase y su valor predeterminado es false .
Para obtener más información sobre cómo crear propiedades enlazables, incluidos los parámetros que se
pueden especificar durante la creación, vea crear una propiedad enlazable.
Crear descriptores de acceso
Los métodos estáticos Get PropertyName y Set PropertyName son necesarios como descriptores de acceso
para la propiedad adjunta; de lo contrario, el sistema de propiedades no podrá utilizar la propiedad adjunta. El
Get descriptor de acceso PropertyName debe cumplir la siguiente firma:

public static valueType GetPropertyName(BindableObject target)

El Get descriptor de acceso PropertyName debe devolver el valor incluido en el BindableProperty campo
correspondiente para la propiedad adjunta. Esto puede lograrse llamando a [ GetValue ] (XREF: :::no-
loc(Xamarin.Forms)::: . BindableObject. GetValue ( :::no-loc(Xamarin.Forms)::: . BindableProperty)), pasando el
identificador de la propiedad enlazable en la que se obtiene el valor y, a continuación, convirtiendo el valor
resultante al tipo requerido.
El Set descriptor de acceso PropertyName debe cumplir la siguiente firma:

public static void SetPropertyName(BindableObject target, valueType value)

El Set descriptor de acceso PropertyName debe establecer el valor del BindableProperty campo
correspondiente para la propiedad adjunta. Esto puede lograrse llamando a [ SetValue ] (XREF: :::no-
loc(Xamarin.Forms)::: . BindableObject. SetValue ( :::no-loc(Xamarin.Forms)::: . BindableProperty, System. Object)),
pasando el identificador de la propiedad enlazable en la que se establece el valor y el valor que se va a
establecer.
En ambos descriptores de acceso, el objeto de destino debe ser o derivar de BindableObject .
En el ejemplo de código siguiente se muestran los descriptores de acceso de la HasShadow propiedad adjunta:

public static bool GetHasShadow (BindableObject view)


{
return (bool)view.GetValue (HasShadowProperty);
}

public static void SetHasShadow (BindableObject view, bool value)


{
view.SetValue (HasShadowProperty, value);
}

Usar una propiedad adjunta


Una vez que se ha creado una propiedad adjunta, se puede usar desde XAML o desde código. En XAML, esto se
logra declarando un espacio de nombres con un prefijo, con la declaración del espacio de nombres que indica el
nombre del espacio de nombres de Common Language Runtime (CLR) y, opcionalmente, un nombre de
ensamblado. Para obtener más información, vea espacios de nombres XAML.
En el ejemplo de código siguiente se muestra un espacio de nombres XAML para un tipo personalizado que
contiene una propiedad adjunta, que se define dentro del mismo ensamblado que el código de aplicación que
hace referencia al tipo personalizado:

<ContentPage ... xmlns:local="clr-namespace:EffectsDemo" ...>


...
</ContentPage>

La declaración del espacio de nombres se utiliza a continuación al establecer la propiedad adjunta en un control
concreto, como se muestra en el siguiente ejemplo de código XAML:

<Label Text="Label Shadow Effect" local:ShadowEffect.HasShadow="true" />

El código de C# equivalente se muestra en el ejemplo de código siguiente:

var label = new Label { Text = "Label Shadow Effect" };


ShadowEffect.SetHasShadow (label, true);

Usar una propiedad adjunta con un estilo


Las propiedades adjuntas también se pueden agregar a un control mediante un estilo. En el ejemplo de código
XAML siguiente se muestra un estilo explícito que utiliza la HasShadow propiedad adjunta, que se puede aplicar a
Label los controles:

<Style x:Key="ShadowEffectStyle" TargetType="Label">


<Style.Setters>
<Setter Property="local:ShadowEffect.HasShadow" Value="true" />
</Style.Setters>
</Style>

Style se puede aplicar a un control Label si se establece su propiedad Style en la instancia de Style
mediante la extensión de marcado StaticResource , como se muestra en el ejemplo de código siguiente:

<Label Text="Label Shadow Effect" Style="{StaticResource ShadowEffectStyle}" />

Para obtener más información sobre los estilos, vea Estilos.

Escenarios avanzados
Al crear una propiedad adjunta, hay una serie de parámetros opcionales que se pueden establecer para habilitar
escenarios avanzados de propiedades adjuntas. Esto incluye la detección de cambios de propiedades, la
validación de los valores de propiedad y la conversión de valores de propiedad. Para obtener más información,
vea escenarios avanzados.

Vínculos relacionados
Propiedades enlazables
Espacios de nombres XAML
Efecto de sombra (ejemplo)
API de BindableProperty
API de BindableObject
:::no-loc(Xamarin.Forms)::: diccionarios de recursos
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
Un ResourceDictionary es un repositorio para los recursos utilizados por una :::no-loc(Xamarin.Forms):::
aplicación. Los recursos típicos que se almacenan en un ResourceDictionary incluyen estilos, plantillas de
control, plantillas de datos, colores y convertidores.
En XAML, se puede hacer referencia a los recursos que se almacenan en, ResourceDictionary y se pueden
aplicar a los elementos mediante la StaticResource extensión de DynamicResource marcado o. En C#, los
recursos también se pueden definir en ResourceDictionary y después se hace referencia a ellos y se aplican a
los elementos mediante un indexador basado en cadenas. Sin embargo, hay pocas ventajas en el uso
ResourceDictionary de en C#, ya que los objetos compartidos se pueden almacenar como campos o
propiedades, y se puede obtener acceso a ellos directamente sin tener que recuperarlos primero de un
diccionario.

Crear recursos en XAML


Cada VisualElement objeto derivado tiene una Resources propiedad, que es un ResourceDictionary que puede
contener recursos. Del mismo modo, un Application objeto derivado tiene una Resources propiedad, que es
un ResourceDictionary que puede contener recursos.
Una :::no-loc(Xamarin.Forms)::: aplicación solo contiene una clase que se deriva de Application , pero a menudo
usa muchas clases que derivan de VisualElement , incluidas páginas, diseños y controles. Cualquiera de estos
objetos puede tener su Resources propiedad establecida en un objeto ResourceDictionary que contenga
recursos. Elección del lugar en el que se colocarán ResourceDictionary los recursos que se pueden usar:
Los recursos de un ResourceDictionary que está asociado a una vista como Button o Label solo se pueden
aplicar a ese objeto concreto.
Los recursos de un ResourceDictionary asociado a un diseño como StackLayout o Grid se pueden aplicar
al diseño y a todos los elementos secundarios de ese diseño.
Los recursos de un ResourceDictionary definido en el nivel de página se pueden aplicar a la página y a todos
sus elementos secundarios.
Los recursos de un ResourceDictionary definido en el nivel de aplicación se pueden aplicar en toda la
aplicación.
A excepción de los estilos implícitos, cada recurso del Diccionario de recursos debe tener una clave de cadena
única que se defina con el x:Key atributo.
En el siguiente código XAML se muestran los recursos definidos en un nivel de aplicación ResourceDictionary
en el archivo app. Xaml :
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.App">
<Application.Resources>

<Thickness x:Key="PageMargin">20</Thickness>

<!-- Colors -->


<Color x:Key="AppBackgroundColor">AliceBlue</Color>
<Color x:Key="NavigationBarColor">#1976D2</Color>
<Color x:Key="NavigationBarTextColor">White</Color>
<Color x:Key="NormalTextColor">Black</Color>

<!-- Implicit styles -->


<Style TargetType="{x:Type NavigationPage}">
<Setter Property="BarBackgroundColor"
Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor"
Value="{StaticResource NavigationBarTextColor}" />
</Style>

<Style TargetType="{x:Type ContentPage}"


ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>

</Application.Resources>
</Application>

En este ejemplo, el Diccionario de recursos define un Thickness recurso, varios Color recursos y dos recursos
implícitos Style . Para obtener más información sobre la App clase, vea :::no-loc(Xamarin.Forms)::: App (clase).

NOTE
También es válido para colocar todos los recursos entre etiquetas explícitas ResourceDictionary . Sin embargo, como
:::no-loc(Xamarin.Forms)::: 3,0, las ResourceDictionary etiquetas no son necesarias. En su lugar, el
ResourceDictionary objeto se crea automáticamente y se pueden insertar directamente los recursos entre las
Resources etiquetas del elemento de propiedad.

Consumir recursos en XAML


Cada recurso tiene una clave que se especifica mediante el x:Key atributo, que se convierte en su clave de
diccionario en ResourceDictionary . La clave se usa para hacer referencia a un recurso desde
ResourceDictionary con StaticResource la DynamicResource extensión de marcado o.

La StaticResource extensión de marcado es similar a la DynamicResource extensión de marcado en que ambos


usan una clave de diccionario para hacer referencia a un valor de un diccionario de recursos. Sin embargo,
mientras que la StaticResource extensión de marcado realiza una búsqueda de un solo diccionario, la
DynamicResource extensión de marcado mantiene un vínculo a la clave del diccionario. Por lo tanto, si se
reemplaza la entrada del diccionario asociada a la clave, el cambio se aplica al elemento visual. Esto permite
realizar cambios en el recurso en tiempo de ejecución en una aplicación. Para obtener más información sobre
las extensiones de marcado, vea extensiones de marcado XAML.
En el siguiente ejemplo de XAML se muestra cómo consumir recursos y también se definen recursos adicionales
en un StackLayout :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.HomePage"
Title="Home Page">
<StackLayout Margin="{StaticResource PageMargin}">
<StackLayout.Resources>
<!-- Implicit style -->
<Style TargetType="Button">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="BackgroundColor" Value="#1976D2" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
</StackLayout.Resources>

<Label Text="This app demonstrates consuming resources that have been defined in resource
dictionaries." />
<Button Text="Navigate"
Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage>

En este ejemplo, el ContentPage objeto consume el estilo implícito definido en el Diccionario de recursos de
nivel de aplicación. El StackLayout objeto consume el PageMargin recurso definido en el Diccionario de
recursos de nivel de aplicación, mientras que el Button objeto consume el estilo implícito definido en el
StackLayout Diccionario de recursos. El resultado es el aspecto que se muestra en las capturas de pantalla
siguientes:

IMPORTANT
Los recursos que son específicos de una sola página no deben incluirse en un diccionario de recursos de nivel de
aplicación, ya que dichos recursos se analizarán en el inicio de la aplicación en lugar de cuando los solicite una página.
Para obtener más información, consulte reducir el tamaño del Diccionario de recursos de aplicación.

Comportamiento de búsqueda de recursos


El siguiente proceso de búsqueda se produce cuando se hace referencia a un recurso con la StaticResource
DynamicResource extensión de marcado o:

La clave solicitada se comprueba en el Diccionario de recursos, si existe, para el elemento que establece la
propiedad. Si se encuentra la clave solicitada, se devuelve su valor y finaliza el proceso de búsqueda.
Si no se encuentra ninguna coincidencia, el proceso de búsqueda busca en el árbol visual hacia arriba y
comprueba el Diccionario de recursos de cada elemento primario. Si se encuentra la clave solicitada, se
devuelve su valor y finaliza el proceso de búsqueda. De lo contrario, el proceso continúa hacia arriba hasta
que se alcanza el elemento raíz.
Si no se encuentra ninguna coincidencia en el elemento raíz, se examina el Diccionario de recursos de nivel
de aplicación.
Si todavía no se encuentra ninguna coincidencia, XamlParseException se produce una excepción.

Por lo tanto, cuando el analizador de XAML encuentra StaticResource una DynamicResource extensión de
marcado o, busca una clave coincidente yendo hacia arriba por el árbol visual, usando la primera coincidencia
que encuentra. Si esta búsqueda finaliza en la página y todavía no se ha encontrado la clave, el analizador de
XAML busca en el ResourceDictionary App objeto adjunto. Si todavía no se encuentra la clave, se produce una
excepción.

Invalidar recursos
Cuando los recursos comparten claves, los recursos definidos en un nivel inferior en el árbol visual tendrán
prioridad sobre las definidas más arriba. Por ejemplo, el establecimiento de un AppBackgroundColor recurso en
AliceBlue en el nivel de aplicación se reemplazará por un recurso de nivel de página AppBackgroundColor
establecido en Teal . Del mismo modo, un recurso de nivel de página reemplazará a un recurso de nivel de
página AppBackgroundColor AppBackgroundColor .

Diccionarios de recursos independientes


Una clase derivada de ResourceDictionary también puede estar en un archivo XAML independiente. Después, el
archivo XAML puede compartirse entre aplicaciones.
Para crear este tipo de archivo, agregue una nueva vista de contenido o elemento de página de contenido al
proyecto (pero no una vista de contenido o una página de contenido con solo un archivo de C#). Elimine el
archivo de código subyacente y, en el archivo XAML, cambie el nombre de la clase base de ContentView o
ContentPage a ResourceDictionary . Además, quite el x:Class atributo de la etiqueta raíz del archivo.

En el ejemplo de XAML siguiente se muestra un ResourceDictionary denominado MyResourceDictionar y.


Xaml :

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<DataTemplate x:Key="PersonDataTemplate">
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}"
TextColor="{StaticResource NormalTextColor}"
FontAttributes="Bold" />
<Label Grid.Column="1"
Text="{Binding Age}"
TextColor="{StaticResource NormalTextColor}" />
<Label Grid.Column="2"
Text="{Binding Location}"
TextColor="{StaticResource NormalTextColor}"
HorizontalTextAlignment="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ResourceDictionary>

En este ejemplo, ResourceDictionary contiene un único recurso, que es un objeto de tipo DataTemplate .
MyResourceDictionar y. Xaml se puede usar combinándolos en otro diccionario de recursos.
De forma predeterminada, el vinculador quitará los archivos XAML independientes de las compilaciones de
versión cuando el comportamiento del vinculador se establezca en vincular todos los ensamblados. Para
asegurarse de que los archivos XAML independientes permanecen en una versión de lanzamiento:
1. Agregue un Preserve atributo personalizado al ensamblado que contenga los archivos XAML
independientes. Para obtener más información, consulte preservación del código.
2. Establezca el Preserve atributo en el nivel de ensamblado:

[assembly:Preserve(AllMembers = true)]

Para obtener más información sobre la vinculación, consulte vinculación de aplicaciones de Xamarin. iOS y
vinculación en Android.

Diccionarios de recursos combinados


Los diccionarios de recursos combinados combinan uno o más ResourceDictionary objetos en otro
ResourceDictionary .

Combinar diccionarios de recursos locales


Un ResourceDictionary archivo local se puede combinar en otro ResourceDictionary creando un
ResourceDictionary objeto cuya Source propiedad se establece en el nombre del archivo XAML con los
recursos:

<ContentPage ...>
<ContentPage.Resources>
<!-- Add more resources here -->
<ResourceDictionary Source="MyResourceDictionary.xaml" />
<!-- Add more resources here -->
</ContentPage.Resources>
...
</ContentPage>

Esta sintaxis no crea instancias de la MyResourceDictionary clase. En su lugar, hace referencia al archivo XAML.
Por ese motivo, al establecer la Source propiedad, no es necesario un archivo de código subyacente y el
x:Class atributo se puede quitar de la etiqueta raíz del archivo MyResourceDictionar y. Xaml .

IMPORTANT
La Source propiedad solo se puede establecer desde XAML.

Combinar diccionarios de recursos de otros ensamblados


ResourceDictionary También se puede combinar en otro si se ResourceDictionary agrega a la
MergedDictionaries propiedad de ResourceDictionary . Esta técnica permite mezclar diccionarios de recursos,
independientemente del ensamblado en el que residen. La combinación de diccionarios de recursos de
ensamblados externos requiere ResourceDictionary que una acción de compilación se establezca en
EmbeddedResource , que tenga un archivo de código subyacente y que defina el x:Class atributo en la
etiqueta raíz del archivo.

WARNING
La ResourceDictionary clase también define una MergedWith propiedad. Sin embargo, esta propiedad ha quedado en
desuso y ya no debe usarse.
En el ejemplo de código siguiente se muestran dos diccionarios de recursos que se agregan a la
MergedDictionaries colección de un nivel de página ResourceDictionary :

<ContentPage ...
xmlns:local="clr-namespace:ResourceDictionaryDemo"
xmlns:theme="clr-namespace:MyThemes;assembly=MyThemes">
<ContentPage.Resources>
<ResourceDictionary>
<!-- Add more resources here -->
<ResourceDictionary.MergedDictionaries>
<!-- Add more resource dictionaries here -->
<local:MyResourceDictionary />
<theme:LightTheme />
<!-- Add more resource dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Add more resources here -->
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>

En este ejemplo, se combinan un diccionario de recursos del mismo ensamblado y un diccionario de recursos
de un ensamblado externo en el Diccionario de recursos de nivel de página. Además, también puede agregar
otros ResourceDictionary objetos dentro MergedDictionaries de las etiquetas del elemento de propiedad y
otros recursos fuera de esas etiquetas.

IMPORTANT
Solo puede haber una MergedDictionaries etiqueta de elemento de propiedad en ResourceDictionary , pero puede
colocar tantos objetos como ResourceDictionary sea necesario.

Cuando ResourceDictionary los recursos combinados comparten valores de atributo idénticos x:Key , :::no-
loc(Xamarin.Forms)::: usa la siguiente prioridad de recursos:
1. Recursos locales del Diccionario de recursos.
2. Los recursos incluidos en los diccionarios de recursos que se combinaron a través de la MergedDictionaries
colección, en el orden inverso, se enumeran en la MergedDictionaries propiedad.

NOTE
La búsqueda de diccionarios de recursos puede ser una tarea que requiere un uso intensivo de la informática si una
aplicación contiene varios diccionarios de recursos de gran tamaño. Por lo tanto, para evitar búsquedas innecesarias, debe
asegurarse de que todas las páginas de una aplicación solo usan diccionarios de recursos adecuados para la página.

Vínculos relacionados
Diccionarios de recursos (ejemplo)
Extensiones de marcado XAML
Estilos de :::no-loc(Xamarin.Forms):::
Vinculación de aplicaciones de Xamarin.iOS
Vincular en Android
API de ResourceDictionary

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Paso de argumentos en XAML
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
En este artículo se muestra el uso de los atributos XAML que se pueden usar para pasar argumentos a
constructores no predeterminados, para llamar a métodos de generador y para especificar el tipo de un
argumento genérico.

Información general
A menudo es necesario crear instancias de objetos con constructores que requieran argumentos o mediante una
llamada a un método de creación estático. Esto se puede lograr en XAML mediante el uso de los x:Arguments
x:FactoryMethod atributos y:

El x:Arguments atributo se utiliza para especificar argumentos de constructor para un constructor no


predeterminado o para una declaración de objeto Factory Method. Para obtener más información, vea pasar
argumentos de constructor.
El x:FactoryMethod atributo se utiliza para especificar un Factory Method que se puede utilizar para inicializar
un objeto. Para obtener más información, consulte llamar a métodos de generador.
Además, el x:TypeArguments atributo se puede utilizar para especificar los argumentos de tipo genérico para el
constructor de un tipo genérico. Para obtener más información, vea especificar un argumento de tipo genérico.

Pasar argumentos de constructor


Los argumentos se pueden pasar a un constructor no predeterminado mediante el x:Arguments atributo. Cada
argumento de constructor debe delimitarse dentro de un elemento XML que representa el tipo del argumento.
:::no-loc(Xamarin.Forms)::: admite los siguientes elementos para los tipos básicos:
x:Array
x:Boolean
x:Byte
x:Char
x:DateTime
x:Decimal
x:Double
x:Int16
x:Int32
x:Int64
x:Object
x:Single
x:String
x:TimeSpan

En el ejemplo de código siguiente se muestra cómo usar el x:Arguments atributo con tres Color constructores:
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Double>0.9</x:Double>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Double>0.25</x:Double>
<x:Double>0.5</x:Double>
<x:Double>0.75</x:Double>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Double>0.8</x:Double>
<x:Double>0.5</x:Double>
<x:Double>0.2</x:Double>
<x:Double>0.5</x:Double>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>

El número de elementos dentro de la x:Arguments etiqueta, así como los tipos de estos elementos, deben
coincidir con uno de los Color constructores. El Color constructor con un solo parámetro requiere un valor de
escala de grises de 0 (negro) a 1 (blanco). El Color constructor con tres parámetros requiere un valor rojo, verde
y azul comprendido entre 0 y 1. El Color constructor con cuatro parámetros agrega un canal alfa como cuarto
parámetro.
Las capturas de pantallas siguientes muestran el resultado de llamar a cada Color constructor con los valores de
argumento especificados:
Llamar a métodos de generador
Se puede llamar a los métodos de generador en XAML especificando el nombre del método mediante el
x:FactoryMethod atributo y sus argumentos mediante el x:Arguments atributo. Un Factory Method es un
public static método que devuelve objetos o valores del mismo tipo que la clase o estructura que define los
métodos.
La Color estructura define una serie de métodos de generador y en el ejemplo de código siguiente se muestra
cómo llamar a tres de ellos:
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromRgba">
<x:Arguments>
<x:Int32>192</x:Int32>
<x:Int32>75</x:Int32>
<x:Int32>150</x:Int32>
<x:Int32>128</x:Int32>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromHsla">
<x:Arguments>
<x:Double>0.23</x:Double>
<x:Double>0.42</x:Double>
<x:Double>0.69</x:Double>
<x:Double>0.7</x:Double>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150" WidthRequest="150" HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromHex">
<x:Arguments>
<x:String>#FF048B9A</x:String>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>

El número de elementos dentro de la x:Arguments etiqueta, así como los tipos de estos elementos, deben
coincidir con los argumentos del Factory Method que se va a llamar. El FromRgba Factory Method requiere cuatro
Int32 parámetros, que representan los valores rojo, verde, azul y alfa, comprendidos entre 0 y 255,
respectivamente. El FromHsla Factory Method requiere cuatro Double parámetros, que representan los valores
de matiz, saturación, luminosidad y alfa, comprendidos entre 0 y 1, respectivamente. El FromHex Factory Method
requiere un String que representa el color RGB hexadecimal (a).
Las capturas de pantallas siguientes muestran el resultado de llamar a cada Color Factory Method con los
valores de argumento especificados:
Especificar un argumento de tipo genérico
Los argumentos de tipo genérico para el constructor de un tipo genérico se pueden especificar utilizando el
x:TypeArguments atributo, como se muestra en el ejemplo de código siguiente:

<ContentPage ...>
<StackLayout>
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,20,0,0" />
<On Platform="Android" Value="5, 10" />
<On Platform="UWP" Value="10" />
</OnPlatform>
</StackLayout.Margin>
</StackLayout>
</ContentPage>

La OnPlatform clase es una clase genérica y se debe crear una instancia de con un x:TypeArguments atributo que
coincida con el tipo de destino. En la On clase, el Platform atributo puede aceptar un solo string valor o varios
valores delimitados por comas string . En este ejemplo, la StackLayout.Margin propiedad se establece en un
específico de la plataforma Thickness .
Para obtener más información sobre los argumentos de tipo genérico, vea genéricos en :::no-
loc(Xamarin.Forms)::: XAML.

Vínculos relacionados
Pasar argumentos de constructor (ejemplo)
Llamar a métodos de generador (ejemplo)
Espacios de nombres XAML
Genéricos en :::no-loc(Xamarin.Forms)::: XAML
Genéricos en :::no-loc(Xamarin.Forms)::: XAML
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: XAML proporciona compatibilidad para consumir tipos CLR genéricos especificando las
restricciones genéricas como argumentos de tipo. Esta compatibilidad se proporciona mediante la
x:TypeArguments Directiva, que pasa los argumentos de tipo restrictivos de un genérico al constructor del tipo
genérico.

IMPORTANT
No se admite la definición de clases genéricas en :::no-loc(Xamarin.Forms)::: XAML, con la x:TypeArguments Directiva.

Los argumentos de tipo se especifican como una cadena y normalmente tienen el prefijo, como sys:String y
sys:Int32 . El prefijo es necesario porque los tipos típicos de restricciones genéricas de CLR proceden de
bibliotecas que no están asignadas al :::no-loc(Xamarin.Forms)::: espacio de nombres predeterminado. Sin
embargo, los tipos integrados de XAML 2009 como x:String y x:Int32 , también se pueden especificar como
argumentos de tipo, donde x es el espacio de nombres del lenguaje xaml para XAML 2009. Para obtener más
información sobre los tipos integrados de XAML 2009, consulte primitivas del lenguaje xaml 2009.
Se pueden especificar varios argumentos de tipo mediante un delimitador de coma. Además, si una restricción
genérica usa tipos genéricos, los argumentos de tipo de restricción anidados deben incluirse entre paréntesis.

NOTE
La x:Type extensión de marcado proporciona una referencia de tipo de CLR para un tipo genérico y tiene una función
similar al typeof operador en C#. Para obtener más información, consulte extensión de marcado x:Type.

Argumento de tipo primitivo único


Se puede especificar un único argumento de tipo primitivo como argumento de cadena con prefijo mediante la
x:TypeArguments Directiva:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="x:String">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</scg:List>
</CollectionView.ItemsSource>
</CollectionView>
</ContentPage>

En este ejemplo, System.Collections.Generic se define como el scg espacio de nombres XAML. La


CollectionView.ItemsSource propiedad se establece en una List<T> instancia de que se crea con un string
argumento de tipo, utilizando el tipo integrado de XAML 2009 x:String . La List<string> colección se inicializa
con varios string elementos.
Como alternativa, pero equivalente, List<T> se pueden crear instancias de la colección con el String tipo clr:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=netstandard"
xmlns:sys="clr-namespace:System;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="sys:String">
<sys:String>Baboon</sys:String>
<sys:String>Capuchin Monkey</sys:String>
<sys:String>Blue Monkey</sys:String>
<sys:String>Squirrel Monkey</sys:String>
<sys:String>Golden Lion Tamarin</sys:String>
<sys:String>Howler Monkey</sys:String>
<sys:String>Japanese Macaque</sys:String>
</scg:List>
</CollectionView.ItemsSource>
</CollectionView>
</ContentPage>

Argumento de tipo de objeto único


Se puede especificar un único argumento de tipo de objeto como un argumento de cadena con prefijo mediante
la x:TypeArguments Directiva:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:GenericsDemo.Models"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="models:Monkey">
<models:Monkey Name="Baboon"
Location="Africa and Asia"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/
200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg" />
<models:Monkey Name="Capuchin Monkey"
Location="Central and South America"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-
Capuchin_Costa_Rica.jpg" />
<models:Monkey Name="Blue Monkey"
Location="Central and East Africa"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-BlueMonkey.jpg" />
</scg:List>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>

En este ejemplo, GenericsDemo.Models se define como el models espacio de nombres XAML y


System.Collections.Generic se define como el scg espacio de nombres XAML. La CollectionView.ItemsSource
propiedad se establece en una List<T> instancia de que se crea con un Monkey argumento de tipo. La
List<Monkey> colección se inicializa con varios Monkey elementos y una DataTemplate que define la apariencia
de cada Monkey objeto se establece como la ItemTemplate de CollectionView .

Varios argumentos de tipo


Se pueden especificar varios argumentos de tipo como argumentos de cadena con prefijo, delimitados por una
coma, mediante la x:TypeArguments Directiva. Cuando una restricción genérica usa tipos genéricos, los
argumentos de tipo de restricción anidados se incluyen entre paréntesis:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:GenericsDemo.Models"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="scg:KeyValuePair(x:String,models:Monkey)">
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Baboon</x:String>
<models:Monkey Location="Africa and Asia"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/
200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg" />
</x:Arguments>
</scg:KeyValuePair>
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Capuchin Monkey</x:String>
<models:Monkey Location="Central and South America"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-
Capuchin_Costa_Rica.jpg" />
</x:Arguments>
</scg:KeyValuePair>
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Blue Monkey</x:String>
<models:Monkey Location="Central and East Africa"

ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-BlueMonkey.jpg" />
</x:Arguments>
</scg:KeyValuePair>
</scg:List>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding Value.ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Key}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Value.Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage
En este ejemplo, GenericsDemo.Models se define como el models espacio de nombres XAML y
System.Collections.Generic se define como el scg espacio de nombres XAML. La CollectionView.ItemsSource
propiedad se establece en un List<T> que se crea una instancia de con una KeyValuePair<TKey, TValue>
restricción, con los argumentos de tipo de restricción interna string y Monkey . La
List<KeyValuePair<string,Monkey>> colección se inicializa con varios KeyValuePair elementos, mediante el
constructor no predeterminado KeyValuePair , y un DataTemplate que define la apariencia de cada Monkey
objeto se establece como la ItemTemplate de CollectionView . Para obtener información sobre cómo pasar
argumentos a un constructor no predeterminado, vea pasar argumentos de constructor.

Vínculos relacionados
Genéricos en XAML (ejemplo)
Primitivas del lenguaje XAML 2009
Extensión de marcado x:Type
Pasar argumentos de constructor
Modificadores de campo XAML enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

El x:FieldModifier atributo namespace especifica el nivel de acceso para los campos generados para los
elementos XAML con nombre. Los valores válidos del atributo son:
private : especifica que solo se puede tener acceso al campo generado para el elemento XAML dentro del
cuerpo de la clase en la que se declara.
public : especifica que el campo generado para el elemento XAML no tiene restricciones de acceso.
protected : especifica que el campo generado para el elemento XAML es accesible dentro de su clase y por
instancias de la clase derivada.
internal : especifica que solo se puede tener acceso al campo generado para el elemento XAML dentro de los
tipos del mismo ensamblado.
notpublic : especifica que solo se puede tener acceso al campo generado para el elemento XAML dentro de los
tipos del mismo ensamblado.
De forma predeterminada, si no se establece el valor del atributo, el campo generado para el elemento será
private .

NOTE
El valor del atributo puede usar cualquier distinción de mayúsculas y minúsculas, ya que se convertirá a minúsculas
Xamarin.Forms .

Se deben cumplir las condiciones siguientes para x:FieldModifier que se procese un atributo:
El elemento XAML de nivel superior debe ser un válido x:Class .
El elemento XAML actual tiene un x:Name especificado.
En el código XAML siguiente se muestran ejemplos del establecimiento del atributo:

<Label x:Name="privateLabel" />


<Label x:Name="internalLabel" x:FieldModifier="internal" />
<Label x:Name="publicLabel" x:FieldModifier="public" />

IMPORTANT
El x:FieldModifier atributo no se puede usar para especificar el nivel de acceso de una clase XAML.
Cargar XAML en tiempo de ejecución en :::no-
loc(Xamarin.Forms):::
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms):::.Xaml espacio de nombres incluye dos LoadFromXaml métodos de extensión que se
pueden usar para cargar y analizar XAML en tiempo de ejecución.

Información previa
Cuando :::no-loc(Xamarin.Forms)::: se construye una clase XAML, LoadFromXaml se llama indirectamente al método.
Esto se debe a que el archivo de código subyacente de una clase XAML llama al InitializeComponent método desde
su constructor:

public partial class MainPage : ContentPage


{
public MainPage()
{
InitializeComponent();
}
}

Cuando Visual Studio compila un proyecto que contiene un archivo XAML, analiza el archivo XAML para generar un
archivo de código de C# (por ejemplo, mainpage.Xaml.g.CS ) que contiene la definición del InitializeComponent
método:

private void InitializeComponent()


{
global:::::no-loc(Xamarin.Forms):::.Xaml.Extensions.LoadFromXaml(this, typeof(MainPage));
...
}

El InitializeComponent método llama al LoadFromXaml método para extraer el archivo XAML (o su binario
compilado) de la biblioteca de .net Standard. Después de la extracción, inicializa todos los objetos definidos en el
archivo XAML, los conecta todos juntos en las relaciones de elementos primarios y secundarios, asocia los
controladores de eventos definidos en el código a los eventos establecidos en el archivo XAML y establece el árbol
resultante de objetos como el contenido de la página.

Cargar XAML en tiempo de ejecución


Los LoadFromXaml métodos son public y, por tanto, se puede llamar desde :::no-loc(Xamarin.Forms)::: las
aplicaciones para cargar y analizar XAML en tiempo de ejecución. Esto permite escenarios como, por ejemplo, una
aplicación que descarga XAML de un servicio Web, crea la vista necesaria desde el código XAML y la muestra en la
aplicación.

WARNING
La carga de XAML en tiempo de ejecución tiene un costo de rendimiento considerable y, por lo general, debe evitarse.
En el ejemplo de código siguiente se muestra un uso sencillo:

using :::no-loc(Xamarin.Forms):::.Xaml;
...

string navigationButtonXAML = "<Button Text=\"Navigate\" />";


Button navigationButton = new Button().LoadFromXaml(navigationButtonXAML);
...
_stackLayout.Children.Add(navigationButton);

En este ejemplo, Button se crea una instancia de, con Text el valor de propiedad establecido en el código XAML
definido en string . Button A continuación, se agrega a un que se ha StackLayout definido en el XAML de la
página.

NOTE
Los LoadFromXaml métodos de extensión permiten especificar un argumento de tipo genérico. Sin embargo, rara vez es
necesario especificar el argumento de tipo, ya que se deduce del tipo de la instancia en la que se está trabajando.

El LoadFromXaml método se puede utilizar para aumentar cualquier código XAML, con el siguiente ejemplo de la
inflación de un ContentPage y, a continuación, desplazarse hasta él:

using :::no-loc(Xamarin.Forms):::.Xaml;
...

// See the sample for the full XAML string


string pageXAML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<ContentPage
xmlns=\"http://xamarin.com/schemas/2014/forms\"\nxmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"\nx:Cl
ass=\"LoadRuntimeXAML.CatalogItemsPage\"\nTitle=\"Catalog Items\">\n</ContentPage>";

ContentPage page = new ContentPage().LoadFromXaml(pageXAML);


await Navigation.PushAsync(page);

Obtener acceso a elementos


Cargar XAML en tiempo de ejecución con el LoadFromXaml método no permite el acceso fuertemente tipado a los
elementos XAML que tienen nombres de objeto en tiempo de ejecución especificados (mediante x:Name ). Sin
embargo, estos elementos XAML se pueden recuperar utilizando el FindByName método y, a continuación, se puede
obtener acceso a ellos según sea necesario:

// See the sample for the full XAML string


string pageXAML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<ContentPage
xmlns=\"http://xamarin.com/schemas/2014/forms\"\nxmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"\nx:Cl
ass=\"LoadRuntimeXAML.CatalogItemsPage\"\nTitle=\"Catalog Items\">\n<StackLayout>\n<Label
x:Name=\"monkeyName\"\n />\n</StackLayout>\n</ContentPage>";
ContentPage page = new ContentPage().LoadFromXaml(pageXAML);

Label monkeyLabel = page.FindByName<Label>("monkeyName");


monkeyLabel.Text = "Seated Monkey";
...

En este ejemplo, el XAML de un ContentPage se infla. Este código XAML incluye un Label denominado monkeyName
, que se recupera mediante el FindByName método, antes de que Text se establezca su propiedad.

Vínculos relacionados
LoadRuntimeXAML (ejemplo)
Accesibilidad de Xamarin.Forms
18/12/2020 • 3 minutes to read • Edit Online

Crear una aplicación accesible garantiza que la aplicación podrá ser usada por personas que interaccionan con la
interfaz de usuario con una variedad de necesidades y experiencias.
Hacer que una aplicación de Xamarin.Forms sea accesible significa pensar en el diseño de muchos elementos de
interfaz de usuario. Para obtener información sobre cuestiones que hay que tener en cuenta, vea la lista de
comprobación de accesibilidad. Ya se pueden solucionar muchos problemas de accesibilidad como las fuentes
grandes y la configuración adecuada de color y contraste mediante las API de Xamarin.Forms.
Las guías Accesibilidad para Android y Accesibilidad para iOS contienen información detallada de las API nativas
expuestas por Xamarin, y la Guía de accesibilidad UWP en MSDN explica el enfoque nativo en esa plataforma. Estas
API se usan para implementar por completo aplicaciones accesibles en cada plataforma.
Actualmente, Xamarin.Forms no tiene compatibilidad integrada con todas las API de accesibilidad que hay
disponibles en cada una de las plataformas subyacentes. Pero sí admite configurar las propiedades de
automatización en los elementos de la interfaz de usuario para admitir las herramientas de ayuda a la navegación y
el lector de pantalla, que es uno de los aspectos más importantes al crear aplicaciones accesibles. Para más
información, vea Propiedades de automatización.
En las aplicaciones de Xamarin.Forms también se puede especificar el orden de tabulación de los controles, con el
fin de mejorar la facilidad de uso y la accesibilidad. Para más información, consulte Accesibilidad del teclado.
Otras API de accesibilidad (como PostNotification en iOS) pueden ser más adecuadas para una implementación de
DependencyService o Representador personalizado. No se tratarán en esta guía.

Probar la accesibilidad
Las aplicaciones de Xamarin.Forms normalmente van dirigidas a varias plataformas, lo que significa que las
pruebas de las características de accesibilidad se realizan según la plataforma. Siga estos vínculos para saber más
sobre cómo probar la accesibilidad en cada plataforma:
Pruebas en iOS
Pruebas en Android
Windows AccScope (MSDN)

Vínculos relacionados
Accesibilidad multiplataforma
Propiedades de automatización
Accesibilidad de teclado

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Propiedades de automatización de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: permite que los valores de accesibilidad se establezcan en elementos de la interfaz de
usuario mediante el uso de las propiedades adjuntas de la clase AutomationProperties, que a su vez establece
valores de accesibilidad nativa. Este artículo explica cómo usar la clase AutomationProperties, para que un lector
de pantalla pueda leer los elementos en la página.
:::no-loc(Xamarin.Forms)::: permite que las propiedades de automatización se establezcan en elementos de la
interfaz de usuario a través de las propiedades adjuntas siguientes:
AutomationProperties.IsInAccessibleTree : indica si el elemento está disponible para una aplicación accesible.
Para obtener más información, consulte AutomationProperties.IsInAccessibleTree.
AutomationProperties.Name : una breve descripción del elemento que actúa como un identificador con capacidad
de lectura para el elemento. Para obtener más información, consulte AutomationProperties.Name.
AutomationProperties.HelpText : una descripción más larga del elemento, que puede considerarse como el texto
de información sobre herramientas asociado al elemento. Para obtener más información, consulte
AutomationProperties.HelpText.
AutomationProperties.LabeledBy : permite que otro elemento defina la información de accesibilidad para el
elemento actual. Para obtener más información, consulte AutomationProperties.LabeledBy.
Estas propiedades adjuntas establecen los valores de accesibilidad nativa para que un lector de pantalla pueda leer
el elemento. Para más información sobre las propiedades adjuntas, consulte Propiedades asociadas.

IMPORTANT
El uso de las propiedades adjuntas AutomationProperties puede afectar a la ejecución de prueba de IU en Android. Las
propiedades AutomationId , AutomationProperties.Name y AutomationProperties.HelpText , establecen la propiedad
ContentDescription nativa, con los valores de propiedad AutomationProperties.Name y
AutomationProperties.HelpText con prioridad sobre el valor AutomationId (si tanto AutomationProperties.Name
como AutomationProperties.HelpText están establecidos, los valores se concatenarán). Esto significa que cualquier
prueba que busque AutomationId fallará si AutomationProperties.Name o AutomationProperties.HelpText también
están establecidos en el elemento. En este escenario, las pruebas de IU deben modificarse para buscar el valor de
AutomationProperties.Name o AutomationProperties.HelpText , o una concatenación de ambos.

Cada plataforma tiene un lector de pantalla diferente para leer los valores de accesibilidad:
iOS tiene VoiceOver. Para obtener más información, consulte Test Accessibility on Your Device with VoiceOver
(Probar la accesibilidad en un dispositivo con VoiceOver), en developer.apple.com.
Android tiene TalkBack. Para obtener más información, consulte Testing Your App's Accessibility (Prueba de la
accesibilidad de una aplicación), en developer.android.com.
Windows tiene el Narrador. Para obtener más información, consulte Verify main app scenarios by using
Narrator (Comprobar escenarios de aplicaciones principales mediante el uso del Narrador).
Sin embargo, el comportamiento exacto de un lector de pantalla depende del software y de la configuración del
usuario en él. Por ejemplo, la mayoría de los lectores de pantalla leen el texto asociado con un control cuando
queda focalizado, permitiendo a los usuarios orientarse a medida que se mueven entre los controles en la página.
Algunos lectores de pantalla leen también la interfaz de usuario de la aplicación completa cuando aparece una
página, lo cual permite que el usuario reciba todo de contenido informativo disponible de la página antes de
intentar navegar por ella.
Los lectores de pantalla también leen valores de accesibilidad distintos. En la aplicación de ejemplo:
VoiceOver leerá el valor Placeholder de Entry , seguido de las instrucciones para usar el control.
TalkBack leerá el valor Placeholder de Entry , seguido del valor AutomationProperties.HelpText , seguido de las
instrucciones para usar el control.
El Narrador leerá el valor AutomationProperties.LabeledBy de Entry , seguido de las instrucciones para usar el
control.
Además, el Narrador dará prioridad a AutomationProperties.Name , AutomationProperties.LabeledBy y, a
continuación, AutomationProperties.HelpText . En Android, TalkBack puede combinar los valores
AutomationProperties.Name y AutomationProperties.HelpText . Por lo tanto, se recomienda llevar a cabo pruebas de
accesibilidad exhaustivas en cada plataforma para garantizar una experiencia óptima.

AutomationProperties.IsInAccessibleTree
La propiedad adjunta AutomationProperties.IsInAccessibleTree es un valor boolean que determina si el elemento
es accesible, y por lo tanto visible, para los lectores de pantalla. Debe establecerse en true para usar las otras
propiedades adjuntas de accesibilidad. Esto se puede lograr en XAML de la siguiente manera:

<Entry AutomationProperties.IsInAccessibleTree="true" />

Como alternativa, puede establecerse en C# de la siguiente manera:

var entry = new Entry();


AutomationProperties.SetIsInAccessibleTree(entry, true);

NOTE
Tenga en cuenta que el método también se puede usar para establecer la propiedad adjunta
SetValue
AutomationProperties.IsInAccessibleTree ,
entry.SetValue(AutomationProperties.IsInAccessibleTreeProperty, true); .

AutomationProperties.Name
El valor de la propiedad adjunta AutomationProperties.Name debe ser una cadena de texto corta y descriptiva que
usa un lector de pantalla anunciar un elemento. Esta propiedad debe establecerse para los elementos que tengan
un significado que sea importante para comprender el contenido o interactuar con la interfaz de usuario. Esto se
puede lograr en XAML de la siguiente manera:

<ActivityIndicator AutomationProperties.IsInAccessibleTree="true"
AutomationProperties.Name="Progress indicator" />

Como alternativa, puede establecerse en C# de la siguiente manera:


var activityIndicator = new ActivityIndicator();
AutomationProperties.SetIsInAccessibleTree(activityIndicator, true);
AutomationProperties.SetName(activityIndicator, "Progress indicator");

NOTE
Tenga en cuenta que el método SetValue también se puede usar para establecer la propiedad adjunta
AutomationProperties.Name ,
activityIndicator.SetValue(AutomationProperties.NameProperty, "Progress indicator"); .

AutomationProperties.HelpText
La propiedad adjunta de AutomationProperties.HelpText debe establecerse en el texto que describe el elemento de
interfaz de usuario y puede ser considerarse el texto de información sobre herramientas asociado al elemento.
Esto se puede lograr en XAML de la siguiente manera:

<Button Text="Toggle ActivityIndicator"


AutomationProperties.IsInAccessibleTree="true"
AutomationProperties.HelpText="Tap to toggle the activity indicator" />

Como alternativa, puede establecerse en C# de la siguiente manera:

var button = new Button { Text = "Toggle ActivityIndicator" };


AutomationProperties.SetIsInAccessibleTree(button, true);
AutomationProperties.SetHelpText(button, "Tap to toggle the activity indicator");

NOTE
Tenga en cuenta que el método SetValue también se puede usar para establecer la propiedad adjunta
AutomationProperties.HelpText ,
button.SetValue(AutomationProperties.HelpTextProperty, "Tap to toggle the activity indicator"); .

En algunas plataformas, para editar controles, como un elemento Entry , la propiedad HelpText a veces puede
omitirse y reemplazarse por el texto de marcador de posición. Por ejemplo, "Escriba aquí su nombre" es un buen
candidato para la propiedad Entry.Placeholder que coloca el texto en el control antes de la entrada real del
usuario.

AutomationProperties.LabeledBy
La propiedad adjunta AutomationProperties.LabeledBy permite que otro elemento defina la información de
accesibilidad para el elemento actual. Por ejemplo, un elemento Label junto a un elemento Entry puede
utilizarse para describir lo que representa Entry . Esto se puede lograr en XAML de la siguiente manera:

<Label x:Name="label" Text="Enter your name: " />


<Entry AutomationProperties.IsInAccessibleTree="true"
AutomationProperties.LabeledBy="{x:Reference label}" />

Como alternativa, puede establecerse en C# de la siguiente manera:


var nameLabel = new Label { Text = "Enter your name: " };
var entry = new Entry();
AutomationProperties.SetIsInAccessibleTree(entry, true);
AutomationProperties.SetLabeledBy(entry, nameLabel);

IMPORTANT
Todavía no se admite AutomationProperties.LabeledByProperty en iOS.

NOTE
Tenga en cuenta que el método también se puede usar para establecer la propiedad adjunta
SetValue
AutomationProperties.IsInAccessibleTree ,
entry.SetValue(AutomationProperties.LabeledByProperty, nameLabel); .

Pormenores relativos a la accesibilidad


En las secciones siguientes se describen los pormenores que supone el hecho de establecer valores de
accesibilidad en ciertos controles.
NavigationPage
En Android, para establecer el texto que el lector de pantalla leerá para la flecha Atrás de la barra de acciones
NavigationPage , defina las propiedades AutomationProperties.Name y AutomationProperties.HelpText en Page . Sin
embargo, tenga en cuenta que esto no se aplicará a los botones Atrás del sistema operativo.
MasterDetailPage
En iOS y la Plataforma Universal de Windows (UWP), para establecer el texto que el lector de pantalla leerá para el
botón de alternancia en MasterDetailPage , defina las propiedades AutomationProperties.Name y
AutomationProperties.HelpText en MasterDetailPage , o bien en la propiedad IconImageSource de la página
Master .

En Android, para establecer el texto que el lector de pantalla leerá para el botón de alternancia en
MasterDetailPage , agregue los recursos de cadena al proyecto de Android:

<resources>
<string name="app_name">Xamarin Forms Control Gallery</string>
<string name="btnMDPAutomationID_open">Open Side Menu message</string>
<string name="btnMDPAutomationID_close">Close Side Menu message</string>
</resources>

A continuación, defina la AutomationId propiedad de la propiedad IconImageSource de la página Master en la


cadena que corresponda:

var master = new ContentPage { ... };


master.IconImageSource.AutomationId = "btnMDPAutomationID";

ToolbarItem
En iOS, Android y UWP, los lectores de pantalla leerán el valor de propiedad Text de las instancias de
ToolbarItem , siempre que no haya definido los valores AutomationProperties.Name y
AutomationProperties.HelpText .
En iOS y UWP, el valor de propiedad AutomationProperties.Name reemplazará el valor de propiedad Text que el
lector de pantalla lee.
En Android, los valores de propiedad AutomationProperties.Name o AutomationProperties.HelpText reemplazarán
por completo el valor de propiedad Text , que es visible y el lector de pantalla leerá. Tenga en cuenta que se trata
de una limitación de API inferior a 26.

Vínculos relacionados
Propiedades asociadas
Accesibilidad (ejemplo)
Accesibilidad del teclado en Xamarin.Forms
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
Los usuarios que emplean lectores de pantalla, o que tienen problemas de movilidad, pueden tener dificultades
para usar aplicaciones que no proporcionan acceso adecuado con el teclado. En las aplicaciones de Xamarin.Forms
se puede especificar un orden de tabulación esperado para mejorar su facilidad de uso y accesibilidad. La
especificación de un orden de tabulación para los controles permite la navegación con el teclado, prepara las
páginas de la aplicación para recibir la entrada en un orden concreto y permite que los lectores de pantalla lean
elementos activables para el usuario.
De forma predeterminada, el orden de tabulación de los controles es el mismo orden en que se están indicados en
XAML o agregados mediante programación a una colección secundaria. Este es el orden en que se navegará por
los controles con un teclado y en que se leerán con un lector de pantalla; a menudo, este orden predeterminado es
el mejor orden posible. Sin embargo, el orden predeterminado no es siempre el mismo que el orden previsto,
como se muestra en el ejemplo de código XAML siguiente:

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="You"
HorizontalOptions="Center" />
<Label Grid.Column="1"
Text="Manager"
HorizontalOptions="Center" />
<Entry Grid.Row="1"
Placeholder="Enter forename" />
<Entry Grid.Column="1"
Grid.Row="1"
Placeholder="Enter forename" />
<Entry Grid.Row="2"
Placeholder="Enter surname" />
<Entry Grid.Column="1"
Grid.Row="2"
Placeholder="Enter surname" />
</Grid>

La captura de pantalla siguiente muestra el orden de tabulación predeterminado para este ejemplo de código:
Aquí, el orden de tabulación está basado en filas y es el orden en el cual los controles se indican en el XAML. Por lo
tanto, al presionar la tecla TAB se navega por instancias Entry de nombre, seguidas por instancias Entry de
apellido. Sin embargo, una experiencia más intuitiva sería usar una navegación por tabulación de la columna en
primer lugar, para que al presionar la tecla TAB se navegara por los pares de nombre y apellido. Esto puede
lograrse mediante la especificación del orden de tabulación de los controles de entrada.

NOTE
En la Plataforma universal de Windows, se pueden definir métodos abreviados de teclado que proporcionen una manera
intuitiva para que los usuarios puedan navegar rápidamente e interactuar con la interfaz de usuario visible de la aplicación a
través de un teclado, en lugar de a través de funciones táctiles o un mouse. Para obtener más información, consulte Setting
VisualElement Access Keys (Configuración de claves de acceso de VisualElement).

Configuración del orden de tabulación


La propiedad VisualElement.TabIndex se utiliza para indicar el orden en que las instancias VisualElement reciben
el foco cuando el usuario navega por los controles presionando la tecla TAB. El valor predeterminado de la
propiedad es 0, y se puede establecer en cualquier valor int .
Las reglas siguientes aplican cuando se usa el orden de tabulación predeterminado, o cuando se establece la
propiedad TabIndex :
Las instancias de VisualElement con TabIndex igual a 0 se agregan al orden de tabulación en función de su
orden de declaración en colecciones secundarias o XAML.
Las instancias de VisualElement con TabIndex mayor que 0 se agregan al orden de tabulación en función de
su valor TabIndex .
Las instancias de VisualElement con TabIndex menor que 0 se agregan al orden de tabulación y aparecen
antes que cualquier valor 0.
Los conflictos relacionados con TabIndex se resuelven por orden de declaración.
Tras definirse un orden de tabulación, al presionar la tecla TAB se recorrerá cíclicamente el foco a través de
controles en orden de TabIndex ascendente, con un ajuste alrededor del principio una vez alcanzado el control
final.

WARNING
En la Plataforma universal de Windows, la propiedad TabIndex de cada control se debe establecer en int.MaxValue para
que el orden de tabulación sea idéntico al orden de la declaración del control.

El siguiente ejemplo de XAML muestra la propiedad TabIndex establecida en los controles de entrada para
habilitar la navegación por tabulación de la columna en primer lugar:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="You"
HorizontalOptions="Center" />
<Label Grid.Column="1"
Text="Manager"
HorizontalOptions="Center" />
<Entry Grid.Row="1"
Placeholder="Enter forename"
TabIndex="1" />
<Entry Grid.Column="1"
Grid.Row="1"
Placeholder="Enter forename"
TabIndex="3" />
<Entry Grid.Row="2"
Placeholder="Enter surname"
TabIndex="2" />
<Entry Grid.Column="1"
Grid.Row="2"
Placeholder="Enter surname"
TabIndex="4" />
</Grid>

La captura de pantalla siguiente muestra el orden de tabulación para este ejemplo de código:

Aquí, el orden de tabulación está basado en columnas. Por lo tanto, al presionar la tecla TAB se navega por pares
Entry de nombre y apellido.

IMPORTANT
Los lectores de pantalla de iOS y Android respetarán la propiedad TabIndex de un objeto VisualElement al leer los
elementos accesible en la pantalla.

Exclusión de controles del orden de tabulación


Además de establecer el orden de tabulación de los controles, puede ser necesario excluir los controles del orden
de tabulación. Una manera de conseguirlo es estableciendo la propiedad IsEnabled de los controles en false , ya
que los controles deshabilitados se excluyen del orden de tabulación.
Sin embargo, puede ser necesario excluir los controles del orden de tabulación incluso cuando no están
deshabilitados. Esto puede lograrse con la propiedad VisualElement.IsTabStop , que indica si un elemento
VisualElement se incluye en la navegación por tabulación. Su valor predeterminado es true , y cuando su valor es
false , la infraestructura de navegación mediante tabulación omite el control, independientemente de si se ha
definido un TabIndex .

Controles admitidos
Las propiedades TabIndex y IsTabStop se admiten en los siguientes controles, que aceptan entradas de teclado
en una o varias plataformas:
Button
DatePicker
Editor
Entry
NavigationPage
Picker
ProgressBar
SearchBar
Slider
Stepper
Switch
TabbedPage
TimePicker

NOTE
Cada uno de estos controles no puede recibir el foco del tabulador en todas las plataformas.

Vínculos relacionados
Accesibilidad (ejemplo)
Clase de aplicación de Xamarin.Forms
18/12/2020 • 8 minutes to read • Edit Online

La clase base Application ofrece las siguientes características, que se exponen en la subclase predeterminada
App de los proyectos:

Una propiedad MainPage , que es donde se establece la página inicial de la aplicación.


Un diccionario Properties persistente para almacenar valores simples a lo largo de los cambios de estado del
ciclo de vida.
Una propiedad estática Current que contiene una referencia al objeto de aplicación actual.
También expone Métodos del ciclo de vida como OnStart , OnSleep y OnResume , así como eventos de navegación
modales.
Según la plantilla que se elija, la clase App puede definirse de alguna de estas dos maneras:
C# , o bien
XAML y C#
Para crear una clase App mediante XAML, se debe reemplazar la clase App predeterminada por una clase App de
XAML y el código subyacente asociado, como se muestra en el ejemplo de código siguiente:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Photos.App">

</Application>

El ejemplo de código siguiente muestra el código subyacente asociado:

public partial class App : Application


{
public App ()
{
InitializeComponent ();
MainPage = new HomePage ();
}
...
}

Además de establecer la propiedad MainPage , el código subyacente también debe llamar al método
InitializeComponent para cargar y analizar el XAML asociado.

Propiedad MainPage
La propiedad MainPage de la clase Application establece la página raíz de la aplicación.
Por ejemplo, puede crear lógica en la clase App para mostrar otra página en función de si el usuario ha iniciado
sesión o no.
La propiedad MainPage debe establecerse en el constructor App ,
public class App : Xamarin.Forms.Application
{
public App ()
{
MainPage = new ContentPage { Title = "App Lifecycle Sample" }; // your page here
}
}

Diccionario de propiedades
La subclase Application tiene un diccionario estático Properties que se puede usar para almacenar datos, en
particular para su uso en los métodos OnStart , OnSleep y OnResume . Se puede acceder a él desde cualquier lugar
del código de Xamarin.Forms con Application.Current.Properties .
El diccionario Properties usa una clave string y almacena un valor object .
Por ejemplo, puede establecer una propiedad persistente "id" en cualquier lugar del código (al seleccionar un
elemento, en el método OnDisappearing de una página, o en el método OnSleep ) del modo siguiente:

Application.Current.Properties ["id"] = someClass.ID;

En los métodos OnStart u OnResume puede usar este valor para volver a crear la experiencia del usuario de
alguna manera. El diccionario Properties almacena elementos object , por lo que debe convertir su valor antes
de usarlo.

if (Application.Current.Properties.ContainsKey("id"))
{
var id = Application.Current.Properties ["id"] as int;
// do something with id
}

Verifique siempre la presencia de la clave antes de acceder para evitar errores inesperados.

NOTE
El diccionario Properties solo puede serializar tipos primitivos para el almacenamiento. El intento de almacenar otros tipos
(como List<string> ) puede producir un error en modo silencioso.

Persistencia
El diccionario Properties se guarda automáticamente en el dispositivo. Los datos agregados al diccionario están
disponibles cuando la aplicación vuelve desde el segundo plano o incluso después de su reinicio.
Xamarin.Forms 1.4 ha incorporado un método adicional a la clase Application ( SavePropertiesAsync() ) al que se
puede llamar para conservar de forma proactiva el diccionario Properties . Esto permite guardar propiedades
después de actualizaciones importantes en lugar de arriesgarse a que no se serialicen debido a un bloqueo o a su
eliminación por parte del sistema operativo.
Encontrará referencias al uso del diccionario Properties en los capítulos 6, 15 y 20 del libro Creating Mobile
Apps with Xamarin.Forms (Creación de aplicaciones móviles con Xamarin.Forms) y en los ejemplos asociados.

La clase Application
A continuación se muestra una completa implementación de la clase Application como referencia:
public class App : Xamarin.Forms.Application
{
public App ()
{
MainPage = new ContentPage { Title = "App Lifecycle Sample" }; // your page here
}

protected override void OnStart()


{
// Handle when your app starts
Debug.WriteLine ("OnStart");
}

protected override void OnSleep()


{
// Handle when your app sleeps
Debug.WriteLine ("OnSleep");
}

protected override void OnResume()


{
// Handle when your app resumes
Debug.WriteLine ("OnResume");
}
}

Luego se crean instancias de esta clase en cada proyecto específico de plataforma y se pasa al método
LoadApplication , que es donde se carga MainPage y se muestra al usuario. El código para cada plataforma se
muestra en las secciones siguientes. Las plantillas de solución de Xamarin.Forms más recientes ya contienen todo
este código, preconfigurado para la aplicación.
Proyecto de iOS
La clase AppDelegate de iOS hereda de FormsApplicationDelegate . Debe:
Llamar a LoadApplication con una instancia de la clase App .
Devolver siempre base.FinishedLaunching (app, options); .

[Register ("AppDelegate")]
public partial class AppDelegate :
global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate // superclass new in 1.3
{
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();

LoadApplication (new App ()); // method is new in 1.3

return base.FinishedLaunching (app, options);


}
}

Proyecto de Android
MainActivity de Android hereda de FormsAppCompatActivity . En la invalidación OnCreate , se llama al método
LoadApplication con una instancia de la clase App .
[Activity (Label = "App Lifecycle Sample", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher =
true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);

global::Xamarin.Forms.Forms.Init (this, bundle);

LoadApplication (new App ()); // method is new in 1.3


}
}

Proyecto universal de Windows (UWP) para Windows 10


La página principal del proyecto de UWP debe heredar de WindowsPage :

<forms:WindowsPage
...
xmlns:forms="using:Xamarin.Forms.Platform.UWP"
...>
</forms:WindowsPage>

El código de C# detrás de la construcción debe llamar a LoadApplication para crear una instancia del elemento
Xamarin.Forms de App . Tenga en cuenta que es recomendable usar explícitamente el espacio de nombres de
aplicación para calificar a App , dado que las aplicaciones de UWP también tienen su propia clase App no
relacionada con Xamarin.Forms.

public sealed partial class MainPage


{
public MainPage()
{
InitializeComponent();

LoadApplication(new YOUR_NAMESPACE.App());
}
}

Tenga en cuenta que Forms.Init() debe llamarse desde App.xaml.cs en el proyecto UWP.
Para obtener más información, consulte Configurar proyectos de Windows, donde se incluyen instrucciones para
agregar un proyecto UWP a una solución existente de Xamarin.Forms cuyo destino no es UWP.

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Ciclo de vida de la aplicación Xamarin.Forms
18/12/2020 • 3 minutes to read • Edit Online

La clase base Application proporciona las características siguientes:


Métodos de ciclo de vida OnStart , OnSleep y OnResume .
Eventos de navegación de página PageAppearing y PageDisappearing .
Eventos de navegación modal ModalPushing , ModalPushed , ModalPopping y ModalPopped .

Métodos de ciclo de vida


La clase Application contiene tres métodos virtuales que pueden reemplazarse para responder a cambios en el
ciclo de vida:
OnStart : se llama cuando se inicia la aplicación.
OnSleep : se llama cada vez que la aplicación pasa a segundo plano.
OnResume : se llama cuando se reanuda la aplicación, después de haberla enviado a segundo plano.

NOTE
No hay ningún método para la finalización de aplicaciones. En circunstancias normales (es decir, si no se produce un
bloqueo), la finalización de la aplicación se producirá desde el estado OnSleep, sin que se muestren notificaciones en el
código.

Para observar cuándo se llama a estos métodos, implemente una llamada WriteLine en cada uno (como se
muestra a continuación) y pruébelos en cada plataforma.

protected override void OnStart()


{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}

IMPORTANT
En Android, se llama al método OnStart por rotación y también cuando la aplicación se inicia por primera vez si la
actividad principal carece de ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation en el
atributo [Activity()] .

Eventos de navegación de página


Hay dos eventos en la clase Application que proporcionan notificaciones de páginas que aparecen y
desaparecen:
PageAppearing : se genera cuando una página va a aparecer en la pantalla.
PageDisappearing : se genera cuando una página va a desaparecer de la pantalla.

Estos eventos pueden usarse en escenarios en los que quiera realizar un seguimiento de las páginas a medida que
aparezcan en la pantalla.

NOTE
Los eventos PageAppearing y PageDisappearing se generan a partir de la clase base Page inmediatamente después de
los eventos Page.Appearing y Page.Disappearing , respectivamente.

Eventos de navegación modal


Hay cuatro eventos en la clase Application , cada uno con sus propios argumentos de evento, que le permiten
responder a páginas modales que empiezan a mostrarse y se descartan:
ModalPushing : se genera cuando se inserta una página de forma modal.
ModalPushed : se genera después de que se ha insertado una página de forma modal.
ModalPopping : se genera cuando se extrae una página de forma modal.
ModalPopped : se genera después de que se ha extraído una página de forma modal.

NOTE
Los argumentos de evento ModalPopping de tipo ModalPoppingEventArgs contienen una propiedad Cancel . Cuando
Cancel se establece en true , se cancela la ventana emergente modal.
Indexación de la aplicación y vinculación en
profundidad
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
La indexación de la aplicación permite a aplicaciones que de lo contrario se olvidarían tras unos pocos usos
mantener su pertinencia al aparecer en los resultados de la búsqueda. La vinculación en profundidad permite a las
aplicaciones responder a un resultado de la búsqueda que contiene datos de aplicación, normalmente al navegar a
una página a la que se hace referencia a partir de un vínculo profundo. En este artículo se explica cómo usar la
indexación de la aplicación y la vinculación en profundidad para que el contenido de las aplicaciones :::no-
loc(Xamarin.Forms)::: permita efectuar búsquedas en dispositivos iOS y Android.

Vídeo de vinculación en profundidad con :::no-loc(Xamarin.Forms)::: y Azure


La indexación de la aplicación y la vinculación en profundidad de :::no-loc(Xamarin.Forms)::: proporcionan una API
para la publicación de metadatos y la indexación de las aplicaciones a medida que los usuarios navegan por ellas.
Posteriormente, el contenido indexado se puede buscar en una búsqueda web, de Spotlight o de Google. Al pulsar
en un resultado de la búsqueda que contenga un vínculo profundo, se desencadenará un evento que normalmente
se usa para navegar a la página a la que se hace referencia desde el vínculo profundo y que puede controlarse
mediante una aplicación.
La aplicación de ejemplo muestra una aplicación de lista de tareas pendientes en la que los datos se almacenan en
una base de datos SQLite local, tal como aparece en las siguientes capturas de pantalla:
Se indexan todas las instancias del elemento TodoItem que crea el usuario. A continuación, se puede usar una
búsqueda específica de la plataforma para localizar los datos indexados de la aplicación. Cuando el usuario pulsa
un resultado de la búsqueda de la aplicación, esta se inicia, se navega al elemento TodoItemPage y se muestra el
elemento TodoItem al que se hace referencia desde el vínculo profundo.
Para obtener más información sobre el uso de una base de datos SQLite, consulte Bases de datos locales de :::no-
loc(Xamarin.Forms):::.

NOTE
La función de indexación de la aplicación y vinculación en profundidad de :::no-loc(Xamarin.Forms)::: solo está disponible en
las plataformas iOS y Android y necesita como mínimo iOS 9 y API 23, respectivamente.

Programa de instalación
En las siguientes secciones se proporcionan las instrucciones de instalación adicionales para usar esta característica
en las plataformas iOS y Android.
iOS
En la plataforma iOS, asegúrese de que el proyecto de plataforma de iOS establezca el archivo Entitlements.plist
como de derechos personalizados para firmar el conjunto.
Para usar vínculos universales de iOS, realice lo siguiente:
1. Agregue un derecho de dominios asociados a la aplicación con la clave applinks , incluidos todos los dominios
que admitirá la aplicación.
2. Agregue un archivo de asociación de sitios de la aplicación de Apple a su sitio web.
3. Agregue la clave applinks al archivo de asociación de sitios de la aplicación de Apple.
Para obtener más información, vea Allowing Apps and Websites to Link to Your Content (Permitir la vinculación de
contenido a aplicaciones y sitios web) en developer.apple.com.
Android
En la plataforma Android, se deben cumplir los siguientes requisitos previos para usar la funcionalidad de la
indexación de la aplicación y de la vinculación en profundidad:
1. Es necesario tener una versión de la aplicación publicada en Google Play.
2. Es necesario haber registrado un sitio web complementario para la aplicación en la consola del desarrollador de
Google. Una vez que la aplicación esté asociada a un sitio web, las direcciones URL se pueden indexar de forma
que funcionen tanto para el sitio web como para la aplicación. De este modo, podrán aparecer después en los
resultados de la búsqueda. Para obtener más información, vea Indexar aplicaciones en la Búsqueda de Google
en el sitio web de Google.
3. La aplicación debe admitir las intenciones de dirección URL HTTP en la clase MainActivity , que indican a la
indexación de la aplicación los tipos de esquemas de datos de direcciones URL a los que esta puede dar
respuesta. Para obtener más información, vea Configuración del filtro de intención.
Una vez cumplidos estos requisitos previos, para usar la indexación de la aplicación y la vinculación en profundidad
de :::no-loc(Xamarin.Forms)::: en la plataforma Android es necesaria la siguiente configuración adicional:
1. Instale el paquete NuGet :::no-loc(Xamarin.Forms):::.AppLinks en el proyecto de la aplicación de Android.
2. En el archivo MainActivity.cs , agregue una declaración para usar el espacio de nombres
:::no-loc(Xamarin.Forms):::.Platform.Android.AppLinks .
3. En el archivo MainActivity.cs , agregue una declaración para usar el espacio de nombres Firebase .
4. En un explorador web, cree un nuevo proyecto con la consola Firebase.
5. En la consola Firebase, agregue Firebase a la aplicación Android y escriba los datos necesarios.
6. Descargue el archivo google-ser vices.json resultante.
7. Agregue el archivo google-ser vices.json al directorio raíz del proyecto de Android y establezca el valor
Acción de compilación en GoogleSer vicesJson .
8. En la invalidación del elemento MainActivity.OnCreate , agregue la siguiente línea de código debajo de
Forms.Init(this, bundle) :

FirebaseApp.InitializeApp(this);
AndroidAppLinks.Init(this);

Al agregar google-ser vices.json al proyecto (y al establecer la acción de compilación GoogleServicesJson *), el


proceso de compilación extraerá la clave de API y el id. de cliente. A continuación, las credenciales se agregarán al
archivo de manifiesto generado.

NOTE
En este artículo, los términos vínculos de aplicación y vínculos profundos suelen usarse indistintamente. Sin embargo, en
Android estos términos tienen significados diferentes. En Android, un vínculo profundo es un filtro de intención que permite a
los usuarios especificar directamente una actividad concreta en la aplicación. Al hacer clic en un vínculo profundo se puede
abrir un cuadro de diálogo de desambiguación, que permite al usuario seleccionar una de las distintas aplicaciones que
pueden controlar la dirección URL. Un vínculo de aplicación de Android es un vínculo profundo basado en la dirección URL de
su sitio web, que se ha verificado que pertenece al sitio web. Al hacer clic en un vínculo de aplicación, se abre la aplicación si
está instalada, sin que se abra un cuadro de diálogo de desambiguación.

Para obtener más información, vea Contenido de vínculos profundos con la navegación de direcciones URL de :::no-
loc(Xamarin.Forms)::: en el blog de Xamarin.
Indexación de una página
El proceso de indexación de una página y de exposición en una búsqueda de Spotlight y Google es el que sigue:
1. Cree un elemento AppLinkEntry que contenga los metadatos necesarios para indexar la página y un vínculo
profundo para volver a ella cuando el usuario seleccione el contenido indexado en los resultados de la
búsqueda.
2. Registre la instancia AppLinkEntry con el fin de indexarla para la búsqueda.

En el siguiente ejemplo de código se muestra cómo crear una instancia AppLinkEntry :

AppLinkEntry GetAppLink(TodoItem item)


{
var pageType = GetType().ToString();
var pageLink = new AppLinkEntry
{
Title = item.Name,
Description = item.Notes,
AppLinkUri = new Uri($"http://{App.AppName}/{pageType}?id={item.ID}", UriKind.RelativeOrAbsolute),
IsLinkActive = true,
Thumbnail = ImageSource.FromFile("monkey.png")
};

pageLink.KeyValues.Add("contentType", "TodoItemPage");
pageLink.KeyValues.Add("appName", App.AppName);
pageLink.KeyValues.Add("companyName", "Xamarin");

return pageLink;
}

La instancia AppLinkEntry contiene un número de propiedades cuyos valores son necesarios para indexar la página
y crear un vínculo profundo. Las propiedades Title , Description y Thumbnail se usan para identificar el
contenido indexado cuando este aparece en los resultados de la búsqueda. La propiedad IsLinkActive se establece
en true para indicar que se está visualizando el contenido indexado. La propiedad AppLinkUri es un elemento
Uri que contiene la información necesaria para volver a la página actual y mostrar el elemento TodoItem actual. A
continuación encontrará un elemento Uri de muestra para la aplicación de ejemplo:

http://deeplinking/DeepLinking.TodoItemPage?id=2

Este elemento Uricontiene toda la información necesaria para iniciar la aplicación deeplinking , navegar a
DeepLinking.TodoItemPage y mostrar TodoItem , que tiene un valor ID de 2.

Registro del contenido para la indexación


Una vez que la instancia AppLinkEntry se ha creado, esta se debe registrar para la indexación con el fin de que
aparezca en los resultados de la búsqueda. Esto se consigue con el método RegisterLink , como se muestra en el
ejemplo de código siguiente:

Application.Current.AppLinks.RegisterLink (appLink);

Esto agrega la instancia AppLinkEntry a la colección AppLinks de la aplicación.

NOTE
El método RegisterLink también se puede utilizar para actualizar el contenido que se ha indexado para una página.
Una vez que la instancia AppLinkEntry se ha registrado para la indexación, esta puede aparecer en los resultados
de la búsqueda. La siguiente captura de pantalla muestra el contenido indexado que aparece en los resultados de la
búsqueda en la plataforma iOS:

Anulación del registro del contenido indexado


El método DeregisterLink se usa para eliminar el contenido indexado de los resultados de la búsqueda, tal como
se muestra en el siguiente ejemplo de código:

Application.Current.AppLinks.DeregisterLink (appLink);

Esto elimina la instancia AppLinkEntry de la colección AppLinks de la aplicación.

NOTE
En Android no es posible eliminar contenido indexado de los resultados de la búsqueda.

Respuesta a un vínculo profundo


Cuando aparece contenido indexado en los resultados de la búsqueda y un usuario lo selecciona, la clase App de la
aplicación recibirá una solicitud para controlar el elemento Uri incluido en dicho contenido indexado. Esta
solicitud se puede procesar en la invalidación del elemento OnAppLinkRequestReceived , tal como se muestra en el
siguiente ejemplo de código:
public class App : Application
{
...
protected override async void OnAppLinkRequestReceived(Uri uri)
{
string appDomain = "http://" + App.AppName.ToLowerInvariant() + "/";
if (!uri.ToString().ToLowerInvariant().StartsWith(appDomain, StringComparison.Ordinal))
return;

string pageUrl = uri.ToString().Replace(appDomain, string.Empty).Trim();


var parts = pageUrl.Split('?');
string page = parts[0];
string pageParameter = parts[1].Replace("id=", string.Empty);

var formsPage = Activator.CreateInstance(Type.GetType(page));


var todoItemPage = formsPage as TodoItemPage;
if (todoItemPage != null)
{
var todoItem = await App.Database.GetItemAsync(int.Parse(pageParameter));
todoItemPage.BindingContext = todoItem;
await MainPage.Navigation.PushAsync(formsPage as Page);
}
base.OnAppLinkRequestReceived(uri);
}
}

El método OnAppLinkRequestReceived comprueba que el elemento Uri recibido esté dirigido a la aplicación, antes
de analizar el elemento Uri para la página a la que se navegará y el parámetro que se pasará a la página. Luego,
se crea una instancia de la página a la que se navegará y se recupera el elemento TodoItem representado por el
parámetro de la página. Posteriormente, el elemento BindingContext de la página a la que se navegará se establece
en TodoItem . Esto garantiza que, cuando el método PushAsync muestre TodoItemPage , se muestre también el
elemento TodoItem cuyo valor ID se encuentra en el vínculo profundo.

Disponibilidad del contenido para la indexación de la búsqueda


Cada vez que se muestra la página representada por un vínculo profundo, la propiedad AppLinkEntry.IsLinkActive
se puede establecer en true . En iOS y Android, esto hace que la instancia AppLinkEntry esté disponible para la
indexación de la búsqueda y, solo en iOS, también facilita la instancia AppLinkEntry para Handoff. Para obtener más
información sobre Handoff, vea la introducción a Handoff.
El siguiente ejemplo de código muestra cómo establecer la propiedad AppLinkEntry.IsLinkActive en true en la
invalidación del elemento Page.OnAppearing :

protected override void OnAppearing()


{
appLink = GetAppLink(BindingContext as TodoItem);
if (appLink != null)
{
appLink.IsLinkActive = true;
}
}

Asimismo, al salir de una página representada por un vínculo profundo, la propiedad AppLinkEntry.IsLinkActive se
puede establecer en false . En iOS y Android, esto evita que la instancia AppLinkEntry se anuncie en la indexación
de la búsqueda y, solo en iOS, también evita anunciar la instancia AppLinkEntry para Handoff. Esto se puede
conseguir en la invalidación del elemento Page.OnDisappearing , tal como se muestra en el siguiente ejemplo de
código:
protected override void OnDisappearing()
{
if (appLink != null)
{
appLink.IsLinkActive = false;
}
}

Envío de datos a Handoff


En iOS, los datos específicos de la aplicación se pueden almacenar al realizar la indexación de la página. Para
hacerlo, agregue datos a la colección KeyValues , que es un elemento Dictionary<string, string> para almacenar
los pares clave-valor que se usan en Handoff. Handoff es una forma que tiene el usuario de iniciar una actividad en
uno de sus dispositivos y continuar con ella en otro (de acuerdo con la identificación de la cuenta de iCloud del
usuario). El siguiente código muestra un ejemplo de almacenamiento de pares clave-valor específicos de la
aplicación:

var pageLink = new AppLinkEntry


{
...
};
pageLink.KeyValues.Add("appName", App.AppName);
pageLink.KeyValues.Add("companyName", "Xamarin");

Los valores almacenados en la colección KeyValues se almacenarán en los metadatos de la página indexada y se
restaurarán cuando el usuario pulse un resultado de la búsqueda que contenga un vínculo profundo, o bien al
utilizar Handoff para ver el contenido en otro dispositivo en el que haya iniciado sesión.
Además, se pueden especificar valores para las siguientes claves:
contentType : string que especifica el identificador de tipo uniforme del contenido indexado. La convención
que se recomienda usar para este valor es el nombre del tipo de la página que contiene el contenido indexado.
associatedWebPage : string que representa la página web que se va a visitar si el contenido indexado también
se puede ver en la Web, o bien si la aplicación admite vínculos profundos de Safari.
shouldAddToPublicIndex : string de true o false que controla si se debe agregar el contenido indexado al
índice de la nube pública de Apple y que, posteriormente, se puede presentar a los usuarios que no hayan
instalado la aplicación en su dispositivo iOS. Sin embargo, el hecho de que el contenido se haya establecido para
la indexación pública no significa que se agregue automáticamente al índice de la nube pública de Apple. Para
obtener más información, vea Indexación de búsqueda pública. Tenga en cuenta que, al agregar datos
personales a la colección KeyValues , esta clave se debe establecer en false .

NOTE
La colección KeyValues no se usa en la plataforma Android.

Para obtener más información sobre Handoff, vea la introducción a Handoff.

Resumen
En este artículo se explica cómo usar la indexación de la aplicación y la vinculación en profundidad para que el
contenido de las aplicaciones :::no-loc(Xamarin.Forms)::: permita efectuar búsquedas en dispositivos iOS y Android.
La indexación de la aplicación permite a aplicaciones que de lo contrario se olvidarían tras unos pocos usos
mantener su pertinencia al aparecer en los resultados de la búsqueda.
Vínculos relacionados
Muestra de vinculación en profundidad
API de búsqueda iOS
Vinculación de aplicaciones en Android 6.0
AppLinkEntry
IAppLinkEntry
IAppLinks
Comportamientos de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Los comportamientos permiten agregar funciones a los controles de interfaz de usuario sin tener que incluir
subclases. Los comportamientos se escriben en código y se agregan a controles en XAML o código.

Introducción a comportamientos
Los comportamientos permiten implementar el código que normalmente tendría que escribir como código
subyacente, ya que interactúa directamente con la API del control de manera que puede asociarse al control de
manera concisa. En este artículo, se proporciona una introducción a los comportamientos.

Comportamientos asociados
Los comportamientos asociados son clases static con una o varias propiedades asociadas. En este artículo, se
explica cómo crear y usar comportamientos asociados.

Comportamientos de Xamarin.Forms
Los comportamientos de Xamarin.Forms se crean mediante la derivación de la clase Behavior o Behavior<T> . En
este artículo se explica cómo crear y consumir comportamientos de Xamarin.Forms.

EffectBehavior reutilizable
Los comportamientos son una manera útil de agregar un efecto a un control, quitando el código de control de
efecto reutilizable de los archivos de código subyacente. En este artículo se explica cómo crear y consumir un
comportamiento de Xamarin.Forms para agregar un efecto a un control.
Introducción a los comportamientos
18/12/2020 • 2 minutes to read • Edit Online

Los comportamientos permiten agregar funciones a los controles de la interfaz de usuario sin tener que incluirlos
en subclases. En su lugar, la función se implementa en una clase de comportamiento y se asocia al control como si
fuera parte de este. En este artículo, se proporciona una introducción a los comportamientos.
Los comportamientos permiten implementar código que normalmente tendría que escribirse como código
subyacente, ya que interactúan directamente con la API del control, de manera que pueden asociarse al control de
manera concisa y empaquetarse para reutilizarlos en más de una aplicación. Pueden usarse para proporcionar una
amplia variedad de funciones para los controles, como:
Agregar un validador de correo electrónico a un elemento Entry .
Crear un control de calificación mediante un reconocedor de gestos de pulsar.
Controlar una animación.
Agregar un efecto a un control.
Los comportamientos también permiten escenarios más avanzados. En el contexto de los comandos, los
comportamientos son un método útil para conectar un control a un comando. Además, también pueden usarse
para asociar comandos a los controles que no se han diseñado para interactuar con los comandos. Por ejemplo,
pueden usarse para invocar un comando en respuesta a la activación de un evento.
Xamarin.Forms admite dos estilos de comportamiento:
Compor tamientos de Xamarin.Forms : clases que derivan de la clase Behavior o Behavior<T> , donde T es
el tipo del control en el que tiene que aplicarse el comportamiento. Para obtener más información sobre los
comportamientos de Xamarin.Forms, vea Comportamientos de Xamarin.Forms.
Compor tamientos asociados : clases de static con una o varias propiedades asociadas. Para obtener más
información sobre los comportamientos asociados, vea Comportamientos asociados.
Esta guía se centra en los comportamientos de Xamarin.Forms, ya que son el método preferido para la creación de
comportamientos.

Vínculos relacionados
Comportamiento
Comportamiento<T>
Comportamientos asociados
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Los comportamientos asociados son clases estáticas con una o varias propiedades asociadas. En este artículo, se
explica cómo crear y usar comportamientos asociados.

Información general
Una propiedad asociada es un tipo especial de propiedad enlazable. Se define en una clase, pero se asocia a otros
objetos y puede reconocerse en XAML como un atributo que contiene una clase y un nombre de propiedad
separado por un punto.
Una propiedad asociada puede definir un delegado propertyChanged que se ejecutará cuando cambie el valor de la
propiedad (por ejemplo, cuando la propiedad se establezca en un control). Cuando se ejecute el delegado
propertyChanged , se pasará una referencia al control donde esté asociado, así como los parámetros que contienen
los valores nuevos y anteriores de la propiedad. Este delegado puede usarse para agregar nuevas funciones al
control al que se asocie la propiedad; para hacerlo, se manipula la referencia en la que se pasa, como se muestra a
continuación:
1. El delegado propertyChanged transmite la referencia del control, que se recibe como un elemento
BindableObject , al tipo de control cuyo comportamiento se ha diseñado para mejorar.
2. El delegado propertyChanged modifica las propiedades del control, llama a métodos del control o registra
controladores de eventos expuestos por el control para implementar la función básica de comportamiento.
Un problema con los comportamientos asociados es que se definen en una clase static , con propiedades y
métodos de static . Esto hace que sea difícil crear comportamientos asociados que tengan un estado. Además,
los comportamientos de :::no-loc(Xamarin.Forms)::: han reemplazado los comportamientos asociados como
método preferido para la creación de comportamientos. Para obtener más información sobre los
comportamientos de :::no-loc(Xamarin.Forms):::, vea Comportamientos de :::no-loc(Xamarin.Forms):::.

Crear un comportamiento asociado


En la aplicación de ejemplo, se muestra un elemento NumericValidationBehavior , que resalta el valor especificado
por el usuario en un control Entry en color rojo, si no es del tipo double . El comportamiento se muestra en el
siguiente ejemplo de código:
public static class NumericValidationBehavior
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached (
"AttachBehavior",
typeof(bool),
typeof(NumericValidationBehavior),
false,
propertyChanged:OnAttachBehaviorChanged);

public static bool GetAttachBehavior (BindableObject view)


{
return (bool)view.GetValue (AttachBehaviorProperty);
}

public static void SetAttachBehavior (BindableObject view, bool value)


{
view.SetValue (AttachBehaviorProperty, value);
}

static void OnAttachBehaviorChanged (BindableObject view, object oldValue, object newValue)


{
var entry = view as Entry;
if (entry == null) {
return;
}

bool attachBehavior = (bool)newValue;


if (attachBehavior) {
entry.TextChanged += OnEntryTextChanged;
} else {
entry.TextChanged -= OnEntryTextChanged;
}
}

static void OnEntryTextChanged (object sender, TextChangedEventArgs args)


{
double result;
bool isValid = double.TryParse (args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}

La clase NumericValidationBehavior contiene una propiedad asociada denominada AttachBehavior con un


captador y establecedor de static , que controla la adición o eliminación del comportamiento del control al que
se va a asociar. Esta propiedad asociada registra el método OnAttachBehaviorChanged que se ejecutará cuando
cambie el valor de la propiedad. Este método registra o anula el registro de un controlador de eventos para el
evento TextChanged basándose en el valor de la propiedad asociada AttachBehavior . El método
OnEntryTextChanged proporciona la función básica del comportamiento, que analiza el valor especificado por el
usuario en el elemento y establece la propiedad Entry TextColor en rojo si el valor no es del tipo double .

Usar un comportamiento asociado


Para usar la clase NumericValidationBehavior , se puede agregar la propiedad asociada AttachBehavior a un
control Entry , como se muestra en el siguiente ejemplo de código de XAML:

<ContentPage ... xmlns:local="clr-namespace:WorkingWithBehaviors;assembly=WorkingWithBehaviors" ...>


...
<Entry Placeholder="Enter a System.Double" local:NumericValidationBehavior.AttachBehavior="true" />
...
</ContentPage>
En el siguiente ejemplo de código, se muestra el control Entry equivalente en C#:

var entry = new Entry { Placeholder = "Enter a System.Double" };


NumericValidationBehavior.SetAttachBehavior (entry, true);

En tiempo de ejecución, el comportamiento responderá a la interacción con el control, según la implementación


del comportamiento. En las capturas de pantalla siguientes, se muestra la respuesta del comportamiento asociado
en respuesta a entradas no válidas:

NOTE
Los comportamientos asociados se escriben para un tipo de control específico (o una superclase que se puede aplicar a
muchos controles) y solo se tienen que agregar a un control compatible. Al intentar asociar un comportamiento a un control
incompatible, se producirá un comportamiento desconocido que depende de la implementación del comportamiento.

Quitar un comportamiento asociado de un control


Para quitar la clase NumericValidationBehavior de un control, establezca la propiedad asociada AttachBehavior en
false , como se muestra en el siguiente ejemplo de código de XAML:

<Entry Placeholder="Enter a System.Double" local:NumericValidationBehavior.AttachBehavior="false" />

En el siguiente ejemplo de código, se muestra el control Entry equivalente en C#:

var entry = new Entry { Placeholder = "Enter a System.Double" };


NumericValidationBehavior.SetAttachBehavior (entry, false);

En tiempo de ejecución, el método OnAttachBehaviorChanged se ejecutará cuando el valor de la propiedad asociada


AttachBehavior se establezca en false . Después, el método OnAttachBehaviorChanged anulará el registro del
controlador de eventos del evento TextChanged con el fin de garantizar que el comportamiento no se ejecute
cuando el usuario interactúe con el control.

Resumen
En este artículo, se explica cómo crear y consumir comportamientos asociados. Los comportamientos asociados
son clases static con una o varias propiedades asociadas.
Vínculos relacionados
Comportamientos asociados (ejemplo)
Creación de comportamientos de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
Los comportamientos de :::no-loc(Xamarin.Forms)::: se crean mediante la derivación de la clase Behavior o
Behavior<T>. En este artículo se explica cómo crear y consumir comportamientos de :::no-loc(Xamarin.Forms):::.

Información general
El proceso de creación de un comportamiento de :::no-loc(Xamarin.Forms)::: es el siguiente:
1. Cree una clase que herede de la clase Behavior o Behavior<T> , donde T sea el tipo del control al que se debe
aplicar el comportamiento.
2. Invalide el método OnAttachedTo para realizar cualquier configuración necesaria.
3. Invalide el método OnDetachingFrom para realizar cualquier limpieza necesaria.
4. Implemente la funcionalidad básica del comportamiento.
El resultado es la estructura que se muestra en el ejemplo de código siguiente:

public class CustomBehavior : Behavior<View>


{
protected override void OnAttachedTo (View bindable)
{
base.OnAttachedTo (bindable);
// Perform setup
}

protected override void OnDetachingFrom (View bindable)


{
base.OnDetachingFrom (bindable);
// Perform clean up
}

// Behavior implementation
}

El método OnAttachedTo se desencadena justo después de que se adjunte el comportamiento a un control. Este
método recibe una referencia al control al que se adjunta, y se puede usar para registrar controladores de eventos
o realizar otra configuración necesaria para admitir la funcionalidad del comportamiento. Por ejemplo, se podría
suscribir a un evento en un control. Después, se implementaría la funcionalidad del comportamiento en el
controlador de eventos para el evento.
El método OnDetachingFrom se desencadena cuando se quita el comportamiento del control. Este método recibe
una referencia al control al que se adjunta y se usa para realizar cualquier limpieza necesaria. Por ejemplo, podría
cancelar la suscripción a un evento en un control para evitar fugas de memoria.
Después, el comportamiento se puede consumir si se adjunta a la colección Behaviors del control adecuado.

Creación de un comportamiento de :::no-loc(Xamarin.Forms):::


En la aplicación de ejemplo, se muestra un elemento NumericValidationBehavior , que resalta el valor especificado
por el usuario en un control Entry en color rojo, si no es del tipo double . El comportamiento se muestra en el
ejemplo de código siguiente:

public class NumericValidationBehavior : Behavior<Entry>


{
protected override void OnAttachedTo(Entry entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}

protected override void OnDetachingFrom(Entry entry)


{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
}

void OnEntryTextChanged(object sender, TextChangedEventArgs args)


{
double result;
bool isValid = double.TryParse (args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}

NumericValidationBehavior se deriva de la clase Behavior<T> , donde T es un elemento Entry . El método


OnAttachedTo method registers an event handler for the TextChanged event, with the OnDetachingFrom anula el
registro del evento TextChanged para evitar fugas de memorias. El método OnEntryTextChanged proporciona la
funcionalidad básica del comportamiento y analiza el valor especificado por el usuario en el elemento Entry y
establece la propiedad TextColor en rojo si el valor no es de tipo double .

NOTE
:::no-loc(Xamarin.Forms)::: no establece el elemento BindingContext de un comportamiento, ya que los comportamientos
se pueden compartir y aplicar a varios controles mediante estilos.

Consumo de un comportamiento de :::no-loc(Xamarin.Forms):::


Todos los controles de :::no-loc(Xamarin.Forms)::: tienen una colección Behaviors , a la que se pueden agregar uno
o varios comportamientos, como se muestra en el ejemplo de código XAML siguiente:

<Entry Placeholder="Enter a System.Double">


<Entry.Behaviors>
<local:NumericValidationBehavior />
</Entry.Behaviors>
</Entry>

En el siguiente ejemplo de código, se muestra el control Entry equivalente en C#:

var entry = new Entry { Placeholder = "Enter a System.Double" };


entry.Behaviors.Add (new NumericValidationBehavior ());

En tiempo de ejecución, el comportamiento responderá a la interacción con el control, en función de la


implementación del comportamiento. En las capturas de pantalla siguientes se muestra la respuesta del
comportamiento a entradas no válidas:
NOTE
Los comportamientos se escriben para un tipo de control específico (o una superclase que se puede aplicar a muchos
controles), y solo se deben agregar a un control compatible. El intento de adjuntar un comportamiento a un control
incompatible iniciará una excepción.

Consumo de un comportamiento de :::no -loc(Xamarin.Forms)::: con un estilo


Los comportamientos también se pueden consumir mediante un estilo explícito o implícito. Pero no se puede
crear un estilo que establece la propiedad Behaviors de un control porque la propiedad es de solo lectura. La
solución consiste en agregar una propiedad adjunta a la clase de comportamiento que controle la adición y
eliminación del comportamiento. El proceso es el siguiente:
1. Se agrega una propiedad adjunta a la clase de comportamiento que se usará para controlar la adición o
eliminación del comportamiento del control al que se va a conectar el comportamiento. Se asegura que la
propiedad adjunta registra un delegado propertyChanged que se ejecutará cuando cambie el valor de la
propiedad.
2. Se crea un captador y establecedor static para la propiedad adjunta.
3. Se implementa la lógica en el delegado propertyChanged para agregar y quitar el comportamiento.

En el ejemplo de código siguiente se muestra una propiedad adjunta que controla la adición y eliminación de
NumericValidationBehavior :
public class NumericValidationBehavior : Behavior<Entry>
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached ("AttachBehavior", typeof(bool), typeof(NumericValidationBehavior),
false, propertyChanged: OnAttachBehaviorChanged);

public static bool GetAttachBehavior (BindableObject view)


{
return (bool)view.GetValue (AttachBehaviorProperty);
}

public static void SetAttachBehavior (BindableObject view, bool value)


{
view.SetValue (AttachBehaviorProperty, value);
}

static void OnAttachBehaviorChanged (BindableObject view, object oldValue, object newValue)


{
var entry = view as Entry;
if (entry == null) {
return;
}

bool attachBehavior = (bool)newValue;


if (attachBehavior) {
entry.Behaviors.Add (new NumericValidationBehavior ());
} else {
var toRemove = entry.Behaviors.FirstOrDefault (b => b is NumericValidationBehavior);
if (toRemove != null) {
entry.Behaviors.Remove (toRemove);
}
}
}
...
}

La clase NumericValidationBehavior contiene una propiedad adjunta denominada AttachBehavior con un


captador y establecedor static , que controla la adición o eliminación del comportamiento del control al que se
va a adjuntar. Esta propiedad asociada registra el método OnAttachBehaviorChanged que se ejecutará cuando
cambie el valor de la propiedad. Este método agrega o quita el comportamiento del control, en función del valor
de la propiedad adjunta AttachBehavior .
En el ejemplo de código siguiente se muestra un estilo explícito para el elemento NumericValidationBehavior que
usa la propiedad adjunta AttachBehavior y que se puede aplicar a controles Entry :

<Style x:Key="NumericValidationStyle" TargetType="Entry">


<Style.Setters>
<Setter Property="local:NumericValidationBehavior.AttachBehavior" Value="true" />
</Style.Setters>
</Style>

Style se puede aplicar a un control Entry si se establece su propiedad Style en la instancia de Style
mediante la extensión de marcado StaticResource , como se muestra en el ejemplo de código siguiente:

<Entry Placeholder="Enter a System.Double" Style="{StaticResource NumericValidationStyle}">

Para obtener más información sobre los estilos, vea Estilos.


NOTE
Aunque se pueden agregar propiedades enlazables a un comportamiento que se establece o se consulta en XAML, si se
crean comportamientos que tienen estado, no se deberían compartir entre los controles en un elemento Style de un
objeto ResourceDictionary .

Eliminación de un comportamiento de un control


Colección OnDetachingFrom method is fired when a behavior is removed from a control, and is used to perform
any required cleanup such as unsubscribing from an event to prevent a memory leak. However, behaviors are not
implicitly removed from controls unless the control's Behaviors collection is modified by a Remove or Clear
method. The following code example demonstrates removing a specific behavior from a control's Behaviors\ :

var toRemove = entry.Behaviors.FirstOrDefault (b => b is NumericValidationBehavior);


if (toRemove != null) {
entry.Behaviors.Remove (toRemove);
}

Como alternativa, se puede borrar la colección Behaviors del control, como se muestra en el ejemplo de código
siguiente:

entry.Behaviors.Clear();

Además, tenga en cuenta que los comportamientos no se quitan de forma implícita de los controles cuando se
extraen páginas de la pila de navegación. En su lugar, se deben quitar explícitamente antes de que las páginas
queden fuera del ámbito.

Resumen
En este artículo se explica cómo crear y consumir comportamientos de :::no-loc(Xamarin.Forms):::. Los
comportamientos de :::no-loc(Xamarin.Forms)::: se crean mediante la derivación de la clase Behavior o
Behavior<T> .

Vínculos relacionados
Comportamiento de :::no-loc(Xamarin.Forms)::: (ejemplo)
Comportamiento de :::no-loc(Xamarin.Forms)::: aplicado con un estilo (ejemplo)
Comportamiento
Behavior<T>
EffectBehavior reutilizable
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Los comportamientos son una manera útil de agregar un efecto a un control, quitando el código de control de
efecto reutilizable de los archivos de código subyacente. En este artículo se explica cómo crear y consumir un
comportamiento de :::no-loc(Xamarin.Forms)::: para agregar un efecto a un control.

Información general
La clase EffectBehavior es un comportamiento personalizado de :::no-loc(Xamarin.Forms)::: reutilizable que
agrega una instancia de Effect a un control cuando el comportamiento se asocia al control, y quita la instancia de
Effect cuando el comportamiento se desasocia del control.

Para usar el comportamiento, se deben establecer las propiedades de comportamiento siguientes:


Group : el valor del atributo ResolutionGroupName para la clase del efecto.
Name : el valor del atributo ExportEffect para la clase del efecto.
Para obtener más información sobre los efectos, vea Efectos.

NOTE
EffectBehavior es una clase personalizada que se puede encontrar en el ejemplo Comportamiento de un efecto y que no
forma parte de :::no-loc(Xamarin.Forms):::.

Creación del comportamiento


La clase EffectBehaviorse deriva de la clase Behavior<T> , donde T es un objeto View . Esto significa que la clase
EffectBehavior se puede asociar a cualquier control de :::no-loc(Xamarin.Forms):::.
Implementación de propiedades enlazables
La clase EffectBehavior define dos instancias de BindableProperty , que se usan para agregar un elemento
Effect a un control cuando el comportamiento está asociado al control. Estas propiedades se muestran en el
ejemplo de código siguiente:
public class EffectBehavior : Behavior<View>
{
public static readonly BindableProperty GroupProperty =
BindableProperty.Create ("Group", typeof(string), typeof(EffectBehavior), null);
public static readonly BindableProperty NameProperty =
BindableProperty.Create ("Name", typeof(string), typeof(EffectBehavior), null);

public string Group {


get { return (string)GetValue (GroupProperty); }
set { SetValue (GroupProperty, value); }
}

public string Name {


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

Cuando se consume EffectBehavior , la propiedad Group se debe establecer en el valor del atributo
ResolutionGroupName para el efecto. Además, la propiedad Name se debe establecer en el valor del atributo
ExportEffect para la clase de efecto.
Implementación de las invalidaciones
La clase EffectBehavior invalida los métodos OnAttachedTo and OnDetachingFrom de la clase Behavior<T> , como
se muestra en el ejemplo de código siguiente:

public class EffectBehavior : Behavior<View>


{
...
protected override void OnAttachedTo (BindableObject bindable)
{
base.OnAttachedTo (bindable);
AddEffect (bindable as View);
}

protected override void OnDetachingFrom (BindableObject bindable)


{
RemoveEffect (bindable as View);
base.OnDetachingFrom (bindable);
}
...
}

El método OnAttachedTo method performs setup by calling the AddEffect method, passing in the attached control
as a parameter. The OnDetachingFrom realiza una limpieza mediante una llamada al método RemoveEffect , para lo
que pasa el control asociado como parámetro.
Implementación de la funcionalidad del comportamiento
El propósito del comportamiento es agregar el elemento Effect definido en las propiedades Group y Name a un
control cuando el comportamiento está asociado al control, y quitar el elemento Effect cuando es el
comportamiento se desasocia del control. La funcionalidad básica del comportamiento se muestra en el ejemplo
de código siguiente:
public class EffectBehavior : Behavior<View>
{
...
void AddEffect (View view)
{
var effect = GetEffect ();
if (effect != null) {
view.Effects.Add (GetEffect ());
}
}

void RemoveEffect (View view)


{
var effect = GetEffect ();
if (effect != null) {
view.Effects.Remove (GetEffect ());
}
}

Effect GetEffect ()
{
if (!string.IsNullOrWhiteSpace (Group) && !string.IsNullOrWhiteSpace (Name)) {
return Effect.Resolve (string.Format ("{0}.{1}", Group, Name));
}
return null;
}
}

El método AddEffect se ejecuta en respuesta a la asociación de EffectBehavior a un control y recibe el control


adjunto como un parámetro. Después, el método agrega el efecto recuperado a la colección Effects del control. El
método RemoveEffect se ejecuta en respuesta a la desasociación de EffectBehavior de un control y recibe el
control adjunto como un parámetro. Después, el método quita el efecto de la colección Effects del control.
El método GetEffect usa el método Effect.Resolve para recuperar el elemento Effect . El efecto se localiza a
través de una concatenación de los valores de propiedad Group y Name . Si una plataforma no proporciona el
efecto, el método Effect.Resolve devolverá un valor que no es null .

Consumo del comportamiento


La clase EffectBehavior se puede adjuntar a la colección Behaviors de un control, como se muestra en el ejemplo
de código XAML siguiente:

<Label Text="Label Shadow Effect" ...>


<Label.Behaviors>
<local:EffectBehavior Group="Xamarin" Name="LabelShadowEffect" />
</Label.Behaviors>
</Label>

El código de C# equivalente se muestra en el ejemplo de código siguiente:

var label = new Label {


Text = "Label Shadow Effect",
...
};
label.Behaviors.Add (new EffectBehavior {
Group = "Xamarin",
Name = "LabelShadowEffect"
});
Las propiedades Group y del comportamiento se establecen en los valores de los atributos
Name
ResolutionGroupName y ExportEffect para la clase de efecto de cada proyecto específico de la plataforma.
En tiempo de ejecución, cuando el comportamiento se asocia al control Label , se agregará
Xamarin.LabelShadowEffect a la colección Effects del control. Como resultado, se agrega una sombra al texto
mostrado por el control Label , como se muestra en las capturas de pantalla siguientes:

La ventaja de usar este comportamiento para agregar y quitar efectos de los controles es que se puede quitar el
código de control de efecto reutilizable de los archivos de código subyacente.

Resumen
En este artículo se ha mostrado el uso de un comportamiento para agregar un efecto a un control. La clase
EffectBehavior es un comportamiento personalizado de :::no-loc(Xamarin.Forms)::: reutilizable que agrega una
instancia de Effect a un control cuando el comportamiento se asocia al control, y quita la instancia de Effect
cuando el comportamiento se desasocia del control.

Vínculos relacionados
Efectos
Comportamiento de un efecto (ejemplo)
Comportamiento
Comportamiento<T>
Representadores personalizados de Xamarin.Forms
18/12/2020 • 5 minutes to read • Edit Online

Las interfaces de usuario de Xamarin.Forms se representan mediante los controles nativos de la plataforma de
destino, lo que permite que las aplicaciones de Xamarin.Forms conserven la apariencia adecuada para cada
plataforma. Los representadores personalizados permiten que los desarrolladores reemplacen este proceso
para personalizar la apariencia y el comportamiento de los controles de Xamarin.Forms en cada plataforma.

Introducción a los representadores personalizados


Los representadores personalizados proporcionan un método eficaz para personalizar la apariencia y el
comportamiento de los controles de Xamarin.Forms. Se pueden usar para realizar pequeños cambios de estilo
o para una personalización sofisticada del diseño y el comportamiento específicos de una plataforma. En este
artículo se proporciona una introducción a los representadores personalizados y se describe el proceso para
crear un representador personalizado.

Clases base y controles nativos del representador


Todos los controles de Xamarin.Forms tienen un representador que las acompaña para cada plataforma y que
crea una instancia de un control nativo. En este artículo se enumeran las clases de representador y control
nativo que implementan cada página, diseño, vista y celda de Xamarin.Forms.

Personalización de una entrada


El control Entry de Xamarin.Forms permite que se edite una sola línea de texto. En este artículo se muestra
cómo crear un representador personalizado para el control Entry , lo que permite que los desarrolladores
reemplacen la representación nativa de forma predeterminada con su propia personalización específica de la
plataforma.

Personalización de una página de contenido


Un ContentPage es un elemento visual que muestra una vista única y ocupa la mayor parte de la pantalla. En
este artículo se muestra cómo crear un representador personalizado para la página ContentPage , lo que
permite que los desarrolladores reemplacen la representación nativa de forma predeterminada con su propia
personalización específica de la plataforma.

Personalización de un anclado de mapa


Xamarin.Forms.Maps proporciona una abstracción multiplataforma para mostrar mapas que usan la API de
mapa nativo en cada plataforma y proporcionar una experiencia de mapa rápida y familiar para los usuarios.
En este tema se muestra cómo crear representadores personalizados para el control Map , lo que permite que
los desarrolladores reemplacen la representación nativa predeterminada por una personalización propia
específica de la plataforma.

Personalización de la clase ListView


Una ListView de Xamarin.Forms es una vista que muestra una colección de datos como una lista vertical. En
este artículo se muestra cómo crear un representador personalizado que encapsula los controles de lista
específica de la plataforma y los diseños de celda nativa, lo que permite tener más control sobre el
rendimiento del control de lista nativa.

Personalización de la clase ViewCell


ViewCell de Xamarin.Forms es una celda que se puede agregar a ListView o TableView y que contiene una
vista definida por el desarrollador. En este artículo se muestra cómo crear un representador personalizado
para un ViewCell que se hospeda dentro de un control ListView de Xamarin.Forms. Esto impide que se llame
varias veces a los cálculos de diseño de Xamarin.Forms durante el desplazamiento de ListView .

Personalización de WebView
Un objeto WebView de Xamarin.Forms es una vista que muestra contenido web y HTML en la aplicación. En
este artículo se explica cómo crear un representador personalizado que extienda WebView para permitir la
invocación de código de C# desde JavaScript.

Implementación de una vista


Los controles de interfaces de usuario personalizadas de Xamarin.Forms deben derivar de la clase View , que
se usa para colocar los diseños y los controles en la pantalla. En este artículo se muestra cómo crear un
representador personalizado para un control personalizado de Xamarin.Forms que se usa para mostrar una
secuencia de vídeo de vista previa de la cámara del dispositivo.

Implementación de un reproductor de vídeo


En este artículo se muestra cómo escribir los representadores para implementar un control personalizado
VideoPlayer que puede reproducir vídeos de la web, vídeos insertados como recursos de la aplicación o
vídeos almacenados en la biblioteca de vídeos en el dispositivo del usuario. Se muestran varias técnicas,
incluida la implementación de métodos y propiedades enlazables de solo lectura.
Introducción a los representadores personalizados
18/12/2020 • 9 minutes to read • Edit Online

Los representadores personalizados proporcionan un método eficaz para personalizar la apariencia y el


comportamiento de los controles de Xamarin.Forms. Se pueden usar para realizar pequeños cambios de estilo o
para una personalización sofisticada del diseño y el comportamiento específicos de una plataforma. En este
artículo, se proporciona una introducción a los representadores personalizados y se describe el proceso para crear
un representador personalizado.
En Páginas, diseños y controles de Xamarin.Forms, se presenta una API común para describir interfaces de usuario
móviles multiplataforma. Cada página, diseño y control se representa de forma distinta en cada plataforma
mediante una clase Renderer que, a su vez, crea un control nativo (correspondiente a la representación de
Xamarin.Forms), lo coloca en la pantalla y agrega el comportamiento especificado en el código compartido.
Los desarrolladores pueden implementar sus propias clases Renderer personalizadas para personalizar la
apariencia o el comportamiento de un control. Los representadores personalizados para un tipo específico pueden
agregarse a un proyecto de aplicación para personalizar el control en un lugar, mientras se permite el
comportamiento predeterminado en otras plataformas; también pueden agregarse diferentes representadores
personalizados a cada proyecto de aplicación para crear una apariencia distinta en iOS, Android y la Plataforma
universal de Windows (UWP). Pero la implementación de una clase de representador personalizado para llevar a
cabo una personalización de controles simples suele ser una respuesta compleja. Los efectos simplifican este
proceso y suelen usarse para pequeños cambios de estilo. Para obtener más información, consulte Effects (Efectos).

Examinar por qué es necesario usar representadores personalizados


Cambiar la apariencia de un control de Xamarin.Forms sin usar un representador personalizado es un proceso de
dos pasos en el que se crea un control personalizado mediante subclases y, después, se usa el control
personalizado en lugar del control original. En el siguiente ejemplo de código, se muestra cómo crear una subclase
del control Entry :

public class MyEntry : Entry


{
public MyEntry ()
{
BackgroundColor = Color.Gray;
}
}

El control MyEntry es un control de Entry en el que BackgroundColor se establece en gris y al que puede hacerse
referencia en XAML mediante la declaración de un espacio de nombres para su ubicación y el uso del prefijo de
espacio de nombres en el elemento de control. En el siguiente ejemplo de código, se muestra cómo usar el control
personalizado MyEntry en una ContentPage :

<ContentPage
...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
...
<local:MyEntry Text="In Shared Code" />
...
</ContentPage>
El prefijo de espacio de nombres local puede ser cualquier texto. Pero los valores namespace y assembly tienen
que coincidir con los detalles del control personalizado. Después de declarar el espacio de nombres, el prefijo se
usa para hacer referencia al control personalizado.

NOTE
Definir el elemento xmlns es mucho más fácil en proyectos de biblioteca de .NET Standard que en proyectos compartidos.
Una biblioteca de .NET Standard se compila en un ensamblado, por lo que resulta más sencillo determinar cuál tendría que
ser el valor de assembly=CustomRenderer . Al usar proyectos compartidos, todos los activos compartidos (incluido el código
XAML) se compilan en cada uno de los proyectos a los que se hace referencia, lo que significa que, si los proyectos de iOS,
Android y UWP tienen sus propios nombres de ensamblado, resulta imposible escribir la declaración xmlns , ya que el valor
tiene que ser distinto para cada aplicación. Para usar los controles personalizados en XAML para proyectos compartidos, es
necesario configurar cada proyecto de aplicación con el mismo nombre de ensamblado.

Después, el control personalizado MyEntry se representa en cada plataforma, con un fondo gris, como se muestra
en las capturas de pantalla siguientes:

El cambio del color de fondo del control en cada plataforma se ha obtenido simplemente mediante la creación de
subclases del control. Pero los resultados que pueden obtenerse con esta técnica son limitados, ya que no es
posible aprovechar las mejoras y personalizaciones específicas de cada plataforma. Cuando sea necesario usarlos,
tendrán que implementarse representadores personalizados.

Crear una clase de representador personalizada


Siga este procedimiento para crear una clase de representador personalizada:
1. Cree una subclase de la clase de representador que represente el control nativo.
2. Reemplace el método que representa el control nativo y escriba la lógica para personalizar el control. Con
frecuencia, el método OnElementChanged se usa para representar el control nativo, al que se llama cuando se
crea el control Xamarin.Forms correspondiente.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el control de Xamarin.Forms. Este atributo se usa para registrar al representador personalizado
con Xamarin.Forms.

NOTE
Para la mayoría de los elementos de Xamarin.Forms, proporcionar un representador personalizado en cada proyecto de la
plataforma es un paso opcional. Si no se registra un representador personalizado, se usará el representador predeterminado
de la clase base del control. Pero los representadores personalizados son necesarios en cada proyecto de la plataforma al
representar un elemento View o ViewCell.
En los temas de esta serie, se proporcionarán demostraciones y explicaciones de este proceso para distintos
elementos de Xamarin.Forms.

Solución de problemas
Si se incluye un control personalizado en un proyecto de biblioteca de .NET Standard agregado a la solución (es
decir, no la biblioteca de .NET Standard creada por la plantilla de proyecto de aplicación de Xamarin.Forms de
Visual Studio o Visual Studio para Mac), puede producirse una excepción en iOS al intentar acceder al control
personalizado. Si se produce este problema, puede solucionarse mediante la creación de una referencia al control
personalizado desde la clase AppDelegate :

var temp = new ClassInPCL(); // in AppDelegate, but temp not used anywhere

Esto obliga al compilador a reconocer el tipo ClassInPCL y solucionarlo. Como alternativa, el atributo Preserve
puede agregarse a la clase AppDelegate para obtener el mismo resultado:

[assembly: Preserve (typeof (ClassInPCL))]

De esta forma, se crea una referencia al tipo ClassInPCL , lo que indica que se necesita en tiempo de ejecución. Para
obtener más información, vea Conservar código.

Resumen
En este artículo, se proporciona una introducción a los representadores personalizados y se describe el proceso
para crear un representador personalizado. Los representadores personalizados proporcionan un método eficaz
para personalizar la apariencia y el comportamiento de los controles de Xamarin.Forms. Se pueden usar para
realizar pequeños cambios de estilo o para una personalización sofisticada del diseño y el comportamiento
específicos de una plataforma.

Vínculos relacionados
Efectos
Clases base y controles nativos del representador
18/12/2020 • 6 minutes to read • Edit Online

Todos los controles de Xamarin.Forms tienen un representador que las acompaña para cada plataforma y que
crea una instancia de un control nativo. En este artículo se enumeran las clases de representador y control nativo
que implementan cada página, diseño, vista y celda de Xamarin.Forms.
A excepción de la clase MapRenderer , los representadores específicos de la plataforma se pueden encontrar en
los espacios de nombres siguientes:
iOS : Xamarin.Forms.Platform.iOS
Android : Xamarin.Forms.Platform.Android
Android (AppCompat) : Xamarin.Forms.Platform.Android.AppCompat
Android (FastRenderers) - Xamarin.Forms.Platform.Android.FastRenderers
Plataforma universal de Windows (UWP) : Xamarin.Forms.Platform.UWP
Para obtener más información sobre los representadores rápidos, consulte Representadores rápidos de
Xamarin.Forms.
La clase MapRenderer se puede encontrar en los espacios de nombres siguientes:
iOS : Xamarin.Forms.Maps.iOS
Android : Xamarin.Forms.Maps.Android
Plataforma universal de Windows (UWP) : Xamarin.Forms.Maps.UWP

NOTE
Para obtener información sobre cómo crear representadores personalizados para aplicaciones de Shell, consulte
Representadores personalizados de Xamarin.Forms Shell.

Páginas
En la tabla siguiente se enumeran las clases de representador y control nativo que implementan cada tipo Page
de Xamarin.Forms:

A N DRO ID
PÁ GIN A REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

ContentPage PageRenderer UIViewController ViewGroup FrameworkEleme


nt
A N DRO ID
PÁ GIN A REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

MasterDetailPage PhoneMasterDet UIViewController DrawerLayout DrawerLayout FrameworkEleme


ailRenderer (iOS: (teléfono), (v4) (v4) nt (Control
teléfono), UISplitViewContr personalizado)
TabletMasterDet oller (tableta)
ailPageRenderer
(iOS: tableta),
MasterDetailRen
derer (Android),
MasterDetailPag
eRenderer
(Android
AppCompat),
MasterDetailPag
eRenderer (UWP)

NavigationPage NavigationRende UIToolbar ViewGroup ViewGroup FrameworkEleme


rer (iOS y nt (Control
Android), personalizado)
NavigationPageR
enderer
(AppCompat
Android),
NavigationPageR
enderer (UWP)

TabbedPage TabbedRenderer UIView ViewPager ViewPager FrameworkEleme


(iOS y Android), nt (Pivot)
TabbedPageRend
erer (Android
AppCompat),
TabbedPageRend
erer (UWP)

TemplatedPage PageRenderer UIViewController ViewGroup FrameworkEleme


nt

CarouselPage CarouselPageRen UIScrollView ViewPager ViewPager FrameworkEleme


derer nt (FlipView)

Diseños
En la tabla siguiente se enumeran las clases de representador y control nativo que implementan cada tipo Layout
de Xamarin.Forms:

A N DRO ID
DISEÑ O REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

ContentPresenter ViewRenderer UIView Ver Ver FrameworkEleme


nt

ContentView ViewRenderer UIView Ver Ver FrameworkEleme


nt

FlexLayout ViewRenderer UIView Ver Ver FrameworkEleme


nt
A N DRO ID
DISEÑ O REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

Frame FrameRenderer UIView ViewGroup CardView Borde

ScrollView ScrollViewRender UIScrollView ScrollView ScrollView ScrollViewer


er

TemplatedView ViewRenderer UIView Ver Ver FrameworkEleme


nt

AbsoluteLayout ViewRenderer UIView Ver Ver FrameworkEleme


nt

Grid ViewRenderer UIView Ver Ver FrameworkEleme


nt

RelativeLayout ViewRenderer UIView Ver Ver FrameworkEleme


nt

StackLayout ViewRenderer UIView Ver Ver FrameworkEleme


nt

Vistas
En la tabla siguiente se enumeran las clases de representador y control nativo que implementan cada tipo View
de Xamarin.Forms:

A N DRO ID
VISTA S REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

ActivityIndicator ActivityIndicator UIActivityIndicat ProgressBar ProgressBar


Renderer or

BoxView BoxRenderer (iOS UIView ViewGroup Rectángulo


y Android),
BoxViewRenderer
(UWP)

Button ButtonRenderer UIButton Botón AppCompatButt Botón


on

CarouselView CarouselViewRen UICollectionView RecyclerView ListViewBase


derer

CheckBox CheckBoxRender UIButton AppCompatChec CheckBox


er kBox

CollectionView CollectionViewRe UICollectionView RecyclerView ListViewBase


nderer

DatePicker DatePickerRende UITextField EditText DatePicker


rer

Editor EditorRenderer UITextView EditText TextBox


A N DRO ID
VISTA S REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

Ellipse EllipseRenderer CALayer Ver Ellipse

Entry EntryRenderer UITextField EditText TextBox

Image ImageRenderer UIImageView ImageView Imagen

ImageButton ImageButtonRen UIButton AppCompatImag Botón


derer eButton

IndicatorView IndicatorViewRen UIPageControl LinearLayout


derer

Label LabelRenderer UILabel TextView TextBlock

Line LineRenderer CALayer Ver Línea

ListView ListViewRenderer UITableView ListView ListView

Map MapRenderer MKMapView MapView MapControl

MediaElement MediaElementRe UIView VideoView MediaElement


nderer

Path PathRenderer CALayer Ver Ruta de acceso

Picker PickerRenderer UITextField EditText EditText ComboBox

Polygon PolygonRenderer CALayer Ver Polígono

Polyline PolylineRenderer CALayer Ver Polilínea

ProgressBar ProgressBarRend UIProgressView ProgressBar ProgressBar


erer

RadioButton RadioButtonRend UIButton AppCompatRadi RadioButton


erer oButton

Rectangle RectangleRender CALayer Ver Rectángulo


er

RefreshView RefreshViewRend UIView SwipeRefreshLay RefreshContainer


erer out

SearchBar SearchBarRender UISearchBar SearchView AutoSuggestBox


er

Slider SliderRenderer UISlider SeekBar Slider

Stepper StepperRenderer UIStepper LinearLayout Control


A N DRO ID
VISTA S REP RESEN TA DO R IO S A N DRO ID ( A P P C O M PAT ) UW P

SwipeView SwipeViewRender UIView Ver SwipeControl


er

Switch SwitchRenderer UISwitch Modificador SwitchCompat ToggleSwitch

TableView TableViewRender UITableView ListView ListView


er

TimePicker TimePickerRende UITextField EditText TimePicker


rer

WebView WkWebViewRen WkWebView WebView WebView


derer (iOS),
WebViewRendere
r (Android y
UWP)

NOTE
El control Expander se implementa usando un elemento StackLayout con animación. Por lo tanto, no tiene ningún
representador de plataforma.

Celdas
En la tabla siguiente se enumeran las clases de representador y control nativo que implementan cada tipo Cell de
Xamarin.Forms:

C EL DA S REP RESEN TA DO R IO S A N DRO ID UW P

EntryCell EntryCellRenderer UITableViewCell con LinearLayout con DataTemplate con un


UITextField TextView y EditText control TextBox

SwitchCell SwitchCellRenderer UITableViewCell con Modificador DataTemplate con un


UISwitch elemento Grid que
contiene controles
TextBlock y
ToggleSwitch

TextCell TextCellRenderer UITableViewCell LinearLayout con dos DataTemplate con un


objetos TextView elemento StackPanel
que contiene dos
elementos TextBlock

ImageCell ImageCellRenderer UITableViewCell con LinearLayout con dos DataTemplate con un


UIImage objetos TextView y un elemento Grid que
objeto ImageView contiene un control
Image y dos
TextBlock

ViewCell ViewCellRenderer UITableViewCell Ver DataTemplate con un


elemento
ContentPresenter
Vínculos relacionados
Representadores rápidos de Xamarin.Forms
Representadores personalizados de Xamarin.Forms Shell
Personalización de una entrada
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
El control Entry de :::no-loc(Xamarin.Forms)::: permite que se edite una sola línea de texto. En este artículo se
muestra cómo crear un representador personalizado para el control Entry, lo que permite que los desarrolladores
reemplacen la representación nativa de forma predeterminada con su propia personalización específica de la
plataforma.
Todos los controles de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma
y que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
control Entry , se crea en iOS una instancia de la clase EntryRenderer que, a su vez, crea una instancia de un
control UITextField nativo. En la plataforma Android, la clase EntryRenderer crea una instancia de un control
EditText . En la Plataforma universal de Windows (UWP), la clase EntryRenderer crea una instancia de un control
TextBox . Para obtener más información sobre el representador y las clases de control nativo a las que se asignan
los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.
El siguiente diagrama muestra la relación entre el control Entry y los controles nativos correspondientes que lo
implementan:

El proceso de representación puede aprovecharse para implementar las personalizaciones específicas de la


plataforma creando un representador personalizado para el control Entry en cada plataforma. Para hacerlo, siga
este procedimiento:
1. Cree un control personalizado de :::no-loc(Xamarin.Forms):::.
2. Consuma el control personalizado de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para el control en cada plataforma.
Ahora se analizará en detalle cada elemento, para implementar un control Entry que tiene un color de fondo
diferente en cada plataforma.
IMPORTANT
En este artículo se explica cómo crear un representador personalizado simple. Empero, no es necesario crear un
representador personalizado para implementar un Entry que tiene un color de fondo diferente en cada plataforma. Esto
puede realizarse más fácilmente usando la clase Device o la extensión de marcado OnPlatform , para proporcionar los
valores específicos de la plataforma. Para obtener más información, vea Providing Platform-Specific Values (Proporcionar
valores específicos de la plataforma) y OnPlatform Markup Extension (Extensión de marcado OnPlatform).

Creación de un control Entry personalizado


Se puede crear un control Entry personalizado mediante la creación de subclases del control Entry , como se
muestra en el siguiente ejemplo de código:

public class MyEntry : Entry


{
}

El control MyEntry se crea en el proyecto de biblioteca de .NET Standard y es simplemente un control Entry . La
personalización del control se llevará a cabo en el representador personalizado, por lo que no se requiere ninguna
implementación adicional en el control MyEntry .

Uso del control personalizado


En XAML puede hacerse referencia al control MyEntry en el proyecto de biblioteca de .NET Standard declarando un
espacio de nombres para su ubicación y usando el prefijo del espacio de nombres en el elemento de control. El
siguiente ejemplo de código muestra cómo el control personalizado MyEntry puede utilizarse en una página
XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
...
<local:MyEntry Text="In Shared Code" />
...
</ContentPage>

El prefijo de espacio de nombres local puede tener cualquier nombre. Pero los valores clr-namespace y
assembly deben coincidir con los detalles del control personalizado. Una vez que se declara el espacio de
nombres, el prefijo se usa para hacer referencia al control personalizado.
El siguiente ejemplo de código muestra cómo el control personalizado MyEntry puede utilizarse en una página C#:
public class MainPage : ContentPage
{
public MainPage ()
{
Content = new StackLayout {
Children = {
new Label {
Text = "Hello, Custom Renderer !",
},
new MyEntry {
Text = "In Shared Code",
}
},
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
}
}

Este código crea una instancia de un nuevo objeto ContentPage que mostrará un Label y un control MyEntry
centrado tanto vertical como horizontalmente en la página.
Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para personalizar la
apariencia del control en cada plataforma.

Creación del representador personalizado en cada plataforma


El proceso para crear la clase del representador personalizado es el siguiente:
1. Cree una subclase de la clase EntryRenderer que representa el control nativo.
2. Invalide el método OnElementChanged que representa el control nativo y escriba lógica para personalizar el
control. Se llama a este método cuando se crea el correspondiente control de :::no-loc(Xamarin.Forms):::.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el control de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al representador
personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Proporcionar un representador personalizado en cada proyecto de la plataforma es un paso opcional. Si no se registra un
representador personalizado, se usará el representador predeterminado de la clase base del control.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

Las clases MyEntryRendererdel representador específico de la plataforma, que se derivan de la clase


EntryRenderer para cada plataforma, representan el control MyEntry . Esto da como resultado que cada control
MyEntry se represente con un color de fondo específico de la plataforma, como se muestra en las siguientes
capturas de pantalla:

La clase EntryRenderer expone el método OnElementChanged , al que se llama cuando se crea el control de :::no-
loc(Xamarin.Forms)::: para representar el control nativo correspondiente. Este método toma un parámetro
ElementChangedEventArgs que contiene propiedades OldElement y NewElement . Estas propiedades representan al
elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de :::no-
loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente. En la aplicación de ejemplo, la
propiedad OldElement es null y la propiedad NewElement contiene una referencia al control de MyEntry .
El lugar para realizar la personalización del control nativo es una versión reemplazada del método
OnElementChanged en la clase MyEntryRenderer . Una referencia con tipo para el control nativo que se usa en la
plataforma puede obtenerse a través de la propiedad Control . Además, se puede obtener una referencia al control
de :::no-loc(Xamarin.Forms)::: que se representa mediante la propiedad Element , aunque no se usa en la aplicación
de ejemplo.
Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control de
:::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador personalizado. El prefijo
assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.

En las secciones siguientes se describe la implementación de cada clase de representador personalizado


MyEntryRenderer específico de plataforma.

Creación del representador personalizado en iOS


El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:

using :::no-loc(Xamarin.Forms):::.Platform.iOS;

[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]


namespace CustomRenderer.iOS
{
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);

if (Control != null) {
// do whatever you want to the UITextField here!
Control.BackgroundColor = UIColor.FromRGB (204, 153, 255);
Control.BorderStyle = UITextBorderStyle.Line;
}
}
}
}
La llamada al método OnElementChanged de la clase base crea una instancia de un control UITextField de iOS, con
una referencia al control que se asigna en la propiedad Control del representador. Después se establece el color
de fondo en púrpura claro con el método UIColor.FromRGB .
Creación del representador personalizado en Android
En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:

using :::no-loc(Xamarin.Forms):::.Platform.Android;

[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]


namespace CustomRenderer.Android
{
class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}

protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)


{
base.OnElementChanged(e);

if (Control != null)
{
Control.SetBackgroundColor(global::Android.Graphics.Color.LightGreen);
}
}
}
}

La llamada al método OnElementChanged de la clase base crea una instancia de un control EditText de Android,
con una referencia al control que se asigna en la propiedad Control del representador. Después se establece el
color de fondo en verde claro con el método Control.SetBackgroundColor .
Creación del representador personalizado en UWP
En el siguiente ejemplo de código se muestra el representador personalizado para UWP:

[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]


namespace CustomRenderer.UWP
{
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);

if (Control != null)
{
Control.Background = new SolidColorBrush(Colors.Cyan);
}
}
}
}

La llamada al método OnElementChanged de la clase base crea una instancia de un control TextBox , con una
referencia al control que se asigna en la propiedad Control del representador. Después se establece el color de
fondo en cian mediante la creación de una instancia de SolidColorBrush .

Resumen
En este artículo se ha mostrado cómo crear un representador de control personalizado para el control Entry de
:::no-loc(Xamarin.Forms):::, lo que permite que los desarrolladores reemplacen la representación nativa
predeterminada por su propia representación específica de la plataforma. Los representadores personalizados
proporcionan un método eficaz para personalizar la apariencia de los controles de :::no-loc(Xamarin.Forms):::. Se
pueden usar para realizar pequeños cambios de estilo o para una personalización sofisticada del diseño y el
comportamiento específicos de una plataforma.

Vínculos relacionados
CustomRendererEntry (sample) (CustomRendererEntry [ejemplo])
Personalización de una página de contenido
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
Un ContentPage es un elemento visual que muestra una vista única y ocupa la mayor parte de la pantalla. En este
artículo se muestra cómo crear un representador personalizado para la página ContentPage, lo que permite que
los desarrolladores reemplacen la representación nativa de forma predeterminada con su propia personalización
específica de la plataforma.
Todos los controles de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma
y que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
ContentPage , se crea en iOS la instancia de la clase PageRenderer , que a su vez crea una instancia del control
UIViewController nativo. En la plataforma Android, la clase PageRenderer crea una instancia de un control
ViewGroup . En la Plataforma universal de Windows (UWP), la clase PageRenderer crea una instancia de un control
FrameworkElement . Para obtener más información sobre el representador y las clases de control nativo a las que se
asignan los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.
El siguiente diagrama muestra la relación entre la clase ContentPage y los controles nativos correspondientes que
la implementan:

El proceso de representación puede aprovecharse para implementar las personalizaciones específicas de la


plataforma creando un representador personalizado para una ContentPage en cada plataforma. Para hacerlo, siga
este procedimiento:
1. Cree una página de :::no-loc(Xamarin.Forms):::.
2. Consuma la página de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para la página en cada plataforma.
Ahora se analizará en detalle cada elemento, para implementar un CameraPage que proporciona una fuente de la
cámara en vivo y la capacidad de capturar una foto.

Creación de la página :::no-loc(Xamarin.Forms):::


Puede agregarse una ContentPage sin modificar al proyecto de :::no-loc(Xamarin.Forms)::: compartido, como se
muestra en el siguiente ejemplo de código XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomRenderer.CameraPage">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>

De forma similar, el archivo de código subyacente para el ContentPage también debe permanecer sin
modificaciones, como se muestra en el siguiente ejemplo de código:

public partial class CameraPage : ContentPage


{
public CameraPage ()
{
// A custom renderer is used to display the camera UI
InitializeComponent ();
}
}

En el siguiente ejemplo de código se muestra cómo puede crearse la página en C#:

public class CameraPageCS : ContentPage


{
public CameraPageCS ()
{
}
}

Una instancia de la CameraPage se usará para mostrar la fuente de la cámara en directo en cada plataforma. La
personalización del control se llevará a cabo en el representador personalizado, por lo que no se requiere ninguna
implementación adicional en la clase CameraPage .

Consumo de la página de :::no-loc(Xamarin.Forms):::


La aplicación de :::no-loc(Xamarin.Forms)::: debe mostrar la CameraPage vacía. Esto se produce cuando se pulsa un
botón en la instancia de MainPage , lo que a su vez ejecuta el método OnTakePhotoButtonClicked , como se muestra
en el siguiente ejemplo de código:

async void OnTakePhotoButtonClicked (object sender, EventArgs e)


{
await Navigation.PushAsync (new CameraPage ());
}

Este código simplemente se desplaza a la CameraPage , en la que representadores personalizados personalizarán el


aspecto de la página en cada plataforma.

Creación del representador de página en cada plataforma


El proceso para crear la clase del representador personalizado es el siguiente:
1. Se crea una subclase de la clase PageRenderer .
2. Invalide el método OnElementChanged que representa la página nativa y escriba lógica para personalizar la
página. Se llama al método OnElementChanged cuando se crea el correspondiente control de :::no-
loc(Xamarin.Forms):::.
3. Agregue un atributo ExportRenderer a la clase de representador de página para especificar que se usará para
representar la página de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al representador
personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Proporcionar un representador de página en cada proyecto de la plataforma es un paso opcional. Si no hay un representador
de página registrado, se usa el representador predeterminado de la página.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

Las clases CameraPageRendererdel representador específico de la plataforma, que se derivan de la clase


PageRenderer para esa plataforma, representan la instancia CameraPage . Esto da como resultado que cada
instancia de CameraPage se represente con una fuente de la cámara en directo, como se muestra en las siguientes
capturas de pantalla:

La clase PageRenderer expone el método OnElementChanged , al que se llama cuando se crea la página de :::no-
loc(Xamarin.Forms)::: para representar el control nativo correspondiente. Este método toma un parámetro
ElementChangedEventArgs que contiene propiedades OldElement y NewElement . Estas propiedades representan al
elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de :::no-
loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente. En la aplicación de ejemplo, la
propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de CameraPage .
El lugar para realizar la personalización de páginas nativas es una versión reemplazada del método
OnElementChanged en la clase CameraPageRenderer . Se puede obtener una referencia a la instancia de la página de
:::no-loc(Xamarin.Forms)::: que se representa mediante la propiedad Element .
Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo de la página de
:::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador personalizado. El prefijo
assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.

En las secciones siguientes se describe la implementación del representador personalizado de CameraPageRenderer


para cada plataforma.
Creación del representador de página en iOS
El siguiente ejemplo de código muestra el representador de página para la plataforma iOS:

[assembly:ExportRenderer (typeof(CameraPage), typeof(CameraPageRenderer))]


namespace CustomRenderer.iOS
{
public class CameraPageRenderer : PageRenderer
{
...

protected override void OnElementChanged (VisualElementChangedEventArgs e)


{
base.OnElementChanged (e);

if (e.OldElement != null || Element == null) {


return;
}

try {
SetupUserInterface ();
SetupEventHandlers ();
SetupLiveCameraStream ();
AuthorizeCameraUse ();
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine (@" ERROR: ", ex.Message);
}
}
...
}
}

La llamada al método OnElementChanged de la clase base crea una instancia de un control UIViewController de iOS.
Solo se procesa la secuencia en directo de cámara si el representador aún no está unido a un elemento existente
de :::no-loc(Xamarin.Forms):::, y siempre que exista una instancia de la página que se representa mediante el
representador personalizado.
Después, se personaliza la página mediante una serie de métodos que usan la API de AVCapture para proporcionar
la secuencia en directo desde la cámara y la capacidad para capturar una foto.
Creación del representador de página en Android
En el ejemplo de código siguiente se muestra el representador de página para la plataforma Android:
[assembly: ExportRenderer(typeof(CameraPage), typeof(CameraPageRenderer))]
namespace CustomRenderer.Droid
{
public class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener
{
...
public CameraPageRenderer(Context context) : base(context)
{
}

protected override void OnElementChanged(ElementChangedEventArgs<Page> e)


{
base.OnElementChanged(e);

if (e.OldElement != null || Element == null)


{
return;
}

try
{
SetupUserInterface();
SetupEventHandlers();
AddView(view);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
}
}
...
}
}

La llamada al método de la clase base OnElementChanged crea una instancia de un control ViewGroup de Android,
que es un grupo de vistas. Solo se procesa la secuencia en directo de cámara si el representador aún no está unido
a un elemento existente de :::no-loc(Xamarin.Forms):::, y siempre que exista una instancia de la página que se
representa mediante el representador personalizado.
Después se personaliza la página mediante la invocación de una serie de métodos que usan la API de Camera para
proporcionar la secuencia en directo desde la cámara y la capacidad de capturar una foto, antes de que se invoque
al método AddView para agregar la interfaz de usuario de la transmisión de cámara en vivo al ViewGroup . Tenga en
cuenta que en Android también es necesario reemplazar el método OnLayout para realizar operaciones de medida
y de diseño en la vista. Para obtener más información, vea el ejemplo de representador de ContentPage.
Creación del representador de página en UWP
En el siguiente ejemplo de código se muestra el representador de página para UWP:
[assembly: ExportRenderer(typeof(CameraPage), typeof(CameraPageRenderer))]
namespace CustomRenderer.UWP
{
public class CameraPageRenderer : PageRenderer
{
...
protected override void OnElementChanged(ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.Page> e)
{
base.OnElementChanged(e);

if (e.OldElement != null || Element == null)


{
return;
}

try
{
...
SetupUserInterface();
SetupBasedOnStateAsync();

this.Children.Add(page);
}
...
}

protected override Size ArrangeOverride(Size finalSize)


{
page.Arrange(new Windows.Foundation.Rect(0, 0, finalSize.Width, finalSize.Height));
return finalSize;
}
...
}
}

La llamada al método OnElementChanged de la clase base crea una instancia de un control FrameworkElement , en el
que se representa la página. Solo se procesa la secuencia en directo de cámara si el representador aún no está
unido a un elemento existente de :::no-loc(Xamarin.Forms):::, y siempre que exista una instancia de la página que se
representa mediante el representador personalizado. Después se personaliza la página mediante la invocación de
una serie de métodos que usan la API de MediaCapture para proporcionar la secuencia en directo desde la cámara
y la capacidad de capturar una foto, antes de que se agregue la página personalizada a la colección Children para
mostrarla.
Al implementar un representador personalizado que derive de PageRenderer en UWP, el método ArrangeOverride
también debe implementarse para organizar los controles de página, ya que el representador de base no sabe qué
hacer con ellos. En caso contrario, da como resultado una página en blanco. Por lo tanto, en este ejemplo el
método ArrangeOverride llama al método Arrange en la instancia de Page .

NOTE
Es importante detener y eliminar los objetos que proporcionan acceso a la cámara en una aplicación de UWP. Si no lo hace
puede interferir con otras aplicaciones que intentan acceder a la cámara del dispositivo. Para obtener más información, vea
Display the camera preview (Mostar la vista previa de la cámara).

Resumen
En este artículo se mostró cómo crear un representador personalizado para la página ContentPage , lo que permite
que los desarrolladores reemplacen la representación nativa de forma predeterminada con su propia
personalización específica de la plataforma. Un ContentPage es un elemento visual que muestra una vista única y
ocupa la mayor parte de la pantalla.

Vínculos relacionados
CustomRendererContentPage (sample) (CustomRendererContentPage [ejemplo])
Personalización de un anclado de mapa
18/12/2020 • 38 minutes to read • Edit Online

Descargar el ejemplo
En este artículo se explica cómo crear un representador personalizado para el control de mapa, que muestra un
mapa nativo con una marca personalizada y una vista personalizada de los datos de marca en cada plataforma.
Todas las vistas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y
que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
Map en iOS, se crea la instancia de la clase MapRenderer , que a su vez crea una instancia del control MKMapView
nativo. En la plataforma de Android, la clase MapRenderer crea una instancia de un control MapView nativo. En la
Plataforma universal de Windows (UWP), la clase MapRenderer crea una instancia de un elemento MapControl
nativo. Para obtener más información sobre el representador y las clases de control nativo a las que se asignan los
controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.
El siguiente diagrama muestra la relación entre la clase Map y los controles nativos correspondientes que la
implementan:

El proceso de representación puede usarse para implementar personalizaciones específicas de plataforma al crear
un representador personalizado para una clase Map en cada plataforma. Para hacerlo, siga este procedimiento:
1. Cree un mapa personalizado de :::no-loc(Xamarin.Forms):::.
2. Consuma el mapa personalizado de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para el mapa en cada plataforma.
Se explicará cada elemento uno por uno, para implementar un representador CustomMap que muestra un mapa
nativo con una marca personalizada y una vista personalizada de los datos de marcas en cada plataforma.

NOTE
:::no-loc(Xamarin.Forms):::.Maps tiene que inicializarse y configurarse antes de cada uso. Para obtener más
información, vea Maps Control .

Crear un mapa personalizado


Se puede crear un control de mapa personalizado mediante la creación de subclases de la clase Map , como se
muestra en el siguiente ejemplo de código:

public class CustomMap : Map


{
public List<CustomPin> CustomPins { get; set; }
}

El control CustomMap se crea en el proyecto de biblioteca de .NET Standard y define la API para el mapa
personalizado. El mapa personalizado expone la propiedad CustomPins que representa la colección de objetos
CustomPin que va a representar el control de mapa nativo en cada plataforma. La clase CustomPin se muestra en
el siguiente ejemplo de código:

public class CustomPin : Pin


{
public string Name { get; set; }
public string Url { get; set; }
}

Esta clase define que un objeto CustomPin hereda las propiedades de la clase Pin y agrega las propiedades
Name y Url .

Usar el mapa personalizado


En XAML puede hacerse referencia al control personalizado CustomMap en el proyecto de biblioteca de .NET
Standard declarando un espacio de nombres para su ubicación y usando el prefijo del espacio de nombres en el
control de mapa personalizado. El siguiente ejemplo de código muestra cómo el control personalizado CustomMap
puede utilizarse en una página XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer">
<local:CustomMap x:Name="customMap"
MapType="Street" />
</ContentPage>

El prefijo del espacio de nombres local puede tener cualquier nombre. Empero, los valores deben
clr-namespace y assembly coincidir con los detalles del mapa personalizado. Una vez que se declare el espacio de
nombres, el prefijo se utiliza para hacer referencia al mapa personalizado.
El siguiente ejemplo de código muestra cómo el control personalizado CustomMap puede utilizarse en una página
C#:

public class MapPageCS : ContentPage


{
public MapPageCS()
{
CustomMap customMap = new CustomMap
{
MapType = MapType.Street
};
// ...
Content = customMap;
}
}

La instancia CustomMap se usará para mostrar el mapa nativo en cada plataforma. La propiedad MapType
establece el estilo de presentación del Map y los valores posibles que se definen en la enumeración MapType .
La ubicación del mapa y las marcas que contiene se inicializan como se muestra en el siguiente ejemplo de
código:

public MapPage()
{
// ...
CustomPin pin = new CustomPin
{
Type = PinType.Place,
Position = new Position(37.79752, -122.40183),
Label = "Xamarin San Francisco Office",
Address = "394 Pacific Ave, San Francisco CA",
Name = "Xamarin",
Url = "http://xamarin.com/about/"
};
customMap.CustomPins = new List<CustomPin> { pin };
customMap.Pins.Add(pin);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183),
Distance.FromMiles(1.0)));
}

Esta inicialización agrega una marca personalizada y coloca la vista del mapa con el método MoveToRegion , que
cambia la posición y el nivel de zoom del mapa mediante la creación de un MapSpan desde un Position y un
Distance .

Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para personalizar los
controles de mapa nativos.

Creación del representador personalizado en cada plataforma


El proceso de creación de la clase de representador personalizada es el siguiente:
1. Cree una subclase de la clase MapRenderer que represente el mapa personalizado.
2. Invalide el método OnElementChanged que representa el mapa personalizado y escriba una lógica para
personalizarlo. Se llama a este método cuando se crea el mapa personalizado de :::no-loc(Xamarin.Forms):::
correspondiente.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el mapa personalizado de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al
representador personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Proporcionar un representador personalizado en cada proyecto de la plataforma es un paso opcional. Si no se registra un
representador personalizado, se usará el representador predeterminado de la clase base del control.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:
Las clases del representador específico de la plataforma, que se derivan de la clase MapRenderer para cada
plataforma, representan el control CustomMap . Esto da como resultado que cada control CustomMap se represente
con controles específicos de la plataforma, como se muestra en las siguientes capturas de pantalla:

La clase MapRenderer expone el método OnElementChanged , al que se llama cuando se crea el mapa personalizado
de :::no-loc(Xamarin.Forms)::: para representar el control nativo correspondiente. Este método toma un parámetro
ElementChangedEventArgs que contiene propiedades OldElement y NewElement . Estas propiedades representan al
elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de :::no-
loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente. En la aplicación de ejemplo, la
propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de CustomMap .
El lugar para realizar la personalización de controles nativos es una versión reemplazada del método
OnElementChanged en cada clase de representador específica de la plataforma. Una referencia con tipo para el
control nativo que se usa en la plataforma puede obtenerse a través de la propiedad Control . Además, mediante
la propiedad :::no-loc(Xamarin.Forms)::: se puede obtener una referencia al control de Element que se representa.
Debe tener cuidado al suscribirse a los controladores de eventos en el método OnElementChanged , como se
muestra en el siguiente ejemplo de código:

protected override void OnElementChanged (ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.View> e)


{
base.OnElementChanged (e);

if (e.OldElement != null)
{
// Unsubscribe from event handlers
}

if (e.NewElement != null)
{
// Configure the native control and subscribe to event handlers
}
}
Solo se debe configurar el control nativo y suscribir a los controladores de eventos cuando se adjunta el
representador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::. De forma similar, solo se debe
cancelar la suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que
está asociado el presentador. Adoptar este enfoque facilita crear un representador personalizado que no sufra
pérdidas de memoria.
Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control
personalizado de :::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador
personalizado. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.
En las secciones siguientes se describe la implementación de cada clase de representador personalizado
específico de plataforma.
Creación del representador personalizado en iOS
Las siguientes capturas de pantalla muestran el mapa antes y después de la personalización:

En iOS la marca se denomina anotación y puede ser una imagen personalizada o una marca definida por el
sistema de varios colores. Opcionalmente las anotaciones pueden mostrar una llamada , que se muestra en
respuesta a que el usuario seleccione la anotación. La llamada muestra las propiedades Label y Address de la
instancia Pin , con vistas adicionales a la derecha y a la izquierda. En la captura de pantalla anterior, la vista
adicional izquierda es la imagen de un mono y la vista adicional derecha es el botón Información.
El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.iOS
{
public class CustomMapRenderer : MapRenderer
{
UIView customPinView;
List<CustomPin> customPins;

protected override void OnElementChanged(ElementChangedEventArgs<View> e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
var nativeMap = Control as MKMapView;
if (nativeMap != null)
{
nativeMap.RemoveAnnotations(nativeMap.Annotations);
nativeMap.GetViewForAnnotation = null;
nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
}
}

if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
var nativeMap = Control as MKMapView;
customPins = formsMap.CustomPins;

nativeMap.GetViewForAnnotation = GetViewForAnnotation;
nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
}
}
// ...
}
}

El método OnElementChanged realiza la siguiente configuración de la instancia MKMapView , siempre que se adjunte
el presentador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms)::::
La propiedad GetViewForAnnotation se establece en el método GetViewForAnnotation . Se llama a este método
cuando la ubicación de la anotación se vuelve visible en el mapa y se usa para personalizar la anotación antes
de mostrarla.
Los controladores de eventos para los eventos CalloutAccessoryControlTapped , DidSelectAnnotationView y
DidDeselectAnnotationView se registran. Estos eventos se activan cuando el usuario pulsa el accesorio derecho
de la llamada y cuando el usuario selecciona y anula la selección de la anotación, respectivamente. Se cancela
la suscripción de los eventos solo cuando cambia el representador al que está adjunto el elemento.
Mostrar la anotación
Se llama al método GetViewForAnnotation cuando la ubicación de la anotación se vuelve visible en el mapa y se
usa para personalizar la anotación antes de mostrarla. Una anotación tiene dos partes:
MkAnnotation: incluye el título, el subtítulo y la ubicación de la anotación.
MkAnnotationView : contiene la imagen para representar la anotación y, opcionalmente, una llamada que se
muestra cuando el usuario pulsa la anotación.
El método GetViewForAnnotation acepta un IMKAnnotation que contiene los datos de la anotación y devuelve un
MKAnnotationView para su presentación en el mapa. Se muestra en el siguiente ejemplo de código:

protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)


{
MKAnnotationView annotationView = null;

if (annotation is MKUserLocation)
return null;

var customPin = GetCustomPin(annotation as MKPointAnnotation);


if (customPin == null)
{
throw new Exception("Custom pin not found");
}

annotationView = mapView.DequeueReusableAnnotation(customPin.Name);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Name);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Name;
((CustomMKAnnotationView)annotationView).Url = customPin.Url;
}
annotationView.CanShowCallout = true;

return annotationView;
}

Este método garantiza que la anotación se muestre como una imagen personalizada, en lugar de como una marca
definida por el sistema y que, cuando se pulsa la anotación, se muestre una llamada que incluye contenido
adicional a la izquierda y a la derecha del título y la dirección de la anotación. Esto se logra de la siguiente manera:
1. Se llama al método GetCustomPin para devolver los datos de marca personalizada para la anotación.
2. Para ahorrar memoria, la vista de la anotación se agrupa para volver a usarla con la llamada a
DequeueReusableAnnotation .
3. La clase CustomMKAnnotationView extiende la clase MKAnnotationView con las propiedades Name y Url que
corresponden a las propiedades idénticas en la instancia CustomPin . Se crea una nueva instancia de la
CustomMKAnnotationView , siempre que la anotación sea null :
La propiedad CustomMKAnnotationView.Image se establece en la imagen que representará la anotación en
el mapa.
La propiedad CustomMKAnnotationView.CalloutOffset se establece en un CGPoint que especifica que la
llamada se centrará por encima de la anotación.
La propiedad CustomMKAnnotationView.LeftCalloutAccessoryView se establece en una imagen de un mono
que aparecerá a la izquierda del título y la dirección de la anotación.
La propiedad CustomMKAnnotationView.RightCalloutAccessoryView se establece en un botón Información
que aparecerá a la derecha del título y la dirección de la anotación.
La propiedad CustomMKAnnotationView.Name se establece en la propiedad CustomPin.Name devuelta por el
método GetCustomPin . Esto permite que la anotación pueda identificarse de forma que su llamada
pueda personalizarse aún más si así lo desea.
La propiedad CustomMKAnnotationView.Url se establece en la propiedad CustomPin.Url devuelta por el
método GetCustomPin . La dirección URL se abrirá cuando el usuario pulse el botón que se muestra en la
vista de accesorios de llamada correcta.
4. La propiedad MKAnnotationView.CanShowCallout se establece en true para que se muestre la llamada cuando
se pulsa la anotación.
5. La anotación se devuelve para su visualización en el mapa.
Seleccionar la anotación
Cuando el usuario pulsa en la anotación, se desencadena el evento DidSelectAnnotationView , que a su vez ejecuta
el método OnDidSelectAnnotationView :

void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)


{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
customPinView = new UIView();

if (customView.Name.Equals("Xamarin"))
{
customPinView.Frame = new CGRect(0, 0, 200, 84);
var image = new UIImageView(new CGRect(0, 0, 200, 84));
image.Image = UIImage.FromFile("xamarin.png");
customPinView.AddSubview(image);
customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
e.View.AddSubview(customPinView);
}
}

Este método extiende la llamada existente (que contiene las vistas adicionales izquierdas y derechas) mediante la
adición de una instancia de UIView a ella que contiene una imagen del logotipo de Xamarin, siempre que la
anotación seleccionada tenga su propiedad Name establecida en Xamarin . Esto permite escenarios donde se
pueden mostrar llamadas diferentes para distintas anotaciones. La instancia UIView se mostrará centrada por
encima de la llamada existente.
Pulsar en la vista adicional de llamada derecha
Cuando el usuario pulsa el botón Información en la vista adicional de llamada derecha, se desencadena el evento
CalloutAccessoryControlTapped , que a su vez ejecuta el método OnCalloutAccessoryControlTapped :

void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)


{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
if (!string.IsNullOrWhiteSpace(customView.Url))
{
UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
}
}

Este método abre un explorador web y navega a la dirección almacenada en la propiedad


CustomMKAnnotationView.Url . Tenga en cuenta que la dirección se definió al crear la colección CustomPin en el
proyecto de biblioteca de .NET Standard.
Anule la selección de la anotación
Cuando la anotación se muestra y el usuario pulsa en el mapa, se desencadena el evento
DidDeselectAnnotationView , que a su vez ejecuta el método OnDidDeselectAnnotationView :

void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)


{
if (!e.View.Selected)
{
customPinView.RemoveFromSuperview();
customPinView.Dispose();
customPinView = null;
}
}
Este método garantiza que cuando la llamada existente no está seleccionada, la parte extendida de la llamada (la
imagen del logotipo de Xamarin) también dejará de mostrarse y se liberarán sus recursos.
Para obtener más información sobre cómo personalizar una instancia de MKMapView , vea Maps in Xamarin.iOS
(Mapas en Xamarin.iOS).
Creación del representador personalizado en Android
Las siguientes capturas de pantalla muestran el mapa antes y después de la personalización:

En Android la marca se denomina marcador y puede ser una imagen personalizada o un marcador definido por el
sistema de varios colores. Los marcadores pueden mostrar una ventana de información , que se muestra en la
respuesta para el usuario que pulsa en el marcador. Muestra la ventana de información de las propiedades Label
y Address de la instancia Pin y se pueden personalizar para incluir otro tipo de contenido. Con todo, solo una
ventana de información puede mostrarse al mismo tiempo.
En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.Droid
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
{
List<CustomPin> customPins;

public CustomMapRenderer(Context context) : base(context)


{
}

protected override void OnElementChanged(:::no-


loc(Xamarin.Forms):::.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);

if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}

if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
}
}

protected override void OnMapReady(GoogleMap map)


{
base.OnMapReady(map);

NativeMap.InfoWindowClick += OnInfoWindowClick;
NativeMap.SetInfoWindowAdapter(this);
}
...
}
}

Siempre que el representador personalizado esté asociado a un nuevo elemento de :::no-loc(Xamarin.Forms):::, el


método OnElementChanged recupera la lista de marcas personalizadas del control. Una vez que la instancia
GoogleMap esté disponible, se invocará la invalidación OnMapReady . Este método registra un controlador de
eventos para el evento InfoWindowClick , que se desencadena cuando se hace clic en la ventana de información y
cuya suscripción solo se cancela cuando cambia el elemento al que está adjunto el representador. La invalidación
OnMapReady también llama al método SetInfoWindowAdapter para especificar que la instancia de la clase
CustomMapRenderer proporcionará los métodos para personalizar la ventana de información.

La clase CustomMapRenderer implementa la interfaz GoogleMap.IInfoWindowAdapter para personalizar la ventana de


información. Esta interfaz especifica que se deben implementar los siguientes métodos:
public Android.Views.View GetInfoWindow(Marker marker) : se llama a este método para devolver una ventana de
información personalizada para un marcador. Si se devuelve null , se usará la representación de la ventana
predeterminada. Si se devuelve un View , View se colocará dentro del marco de la ventana de información.
public Android.Views.View GetInfoContents(Marker marker) : se llama a este método para devolver un View que
contiene el contenido de la ventana de información, y solo se llamará si el método GetInfoWindow devuelve
null . Si devuelve null , se usará la representación predeterminada del contenido de la ventana de
información.
En la aplicación de ejemplo, solo se personaliza el contenido de la ventana de información, de forma que el
método GetInfoWindow devuelve null para habilitar esto.
Personalización del marcador
El icono utilizado para representar un marcador puede personalizarse mediante una llamada al método
MarkerOptions.SetIcon . Esto puede realizarse invalidando el método CreateMarker , que se invoca para cada Pin
que se agrega al mapa:

protected override MarkerOptions CreateMarker(Pin pin)


{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
return marker;
}

Este método crea una nueva instancia de MarkerOption para cada instancia de Pin . Después de establecer la
posición, la etiqueta y la dirección del marcador, su icono se establece con el método SetIcon . Este método toma
un objeto BitmapDescriptor que contiene los datos necesarios para representar el icono y la clase
BitmapDescriptorFactory proporciona métodos auxiliares para simplificar la creación de la BitmapDescriptor . Para
obtener más información sobre el uso de la clase BitmapDescriptorFactory para personalizar un marcador, vea
Customizing a Marker (Personalización de un marcador).

NOTE
Si es necesario, el método GetMarkerForPin se puede invocar en el representador de mapa para recuperar un Marker de
una Pin .

Personalización de la ventana de información


Cuando un usuario pulsa en el marcador, el método GetInfoContents se ejecuta, siempre que el método
GetInfoWindow devuelva null . El siguiente ejemplo de código muestra el método GetInfoContents :
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as
Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;

var customPin = GetCustomPin(marker);


if (customPin == null)
{
throw new Exception("Custom pin not found");
}

if (customPin.Name.Equals("Xamarin"))
{
view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
}
else
{
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
}

var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);


var infoSubtitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);

if (infoTitle != null)
{
infoTitle.Text = marker.Title;
}
if (infoSubtitle != null)
{
infoSubtitle.Text = marker.Snippet;
}

return view;
}
return null;
}

Este método devuelve un View con el contenido de la ventana de información. Esto se logra de la siguiente
manera:
Se recupera una instancia de LayoutInflater . Esta se usa para crear una instancia de un archivo XML de
diseño en su View correspondiente.
Se llama al método GetCustomPin para devolver los datos de marca personalizada para la ventana de
información.
Se aumenta el diseño de XamarinMapInfoWindow si la propiedad CustomPin.Name es igual a Xamarin . En caso
contrario, se aumenta el diseño de MapInfoWindow . Esto permite escenarios donde se pueden mostrar
diferentes diseños de ventana de información para distintos marcadores.
Se recuperan los recursos InfoWindowTitle y InfoWindowSubtitle desde el diseño aumentado y sus
propiedades Text se establecen en los datos correspondientes de la instancia de Marker , siempre que los
recursos no sean null .
La instancia de View se devuelve para su visualización en el mapa.
NOTE
Una ventana de información no es una View dinámica. En su lugar, Android convertirá la View a mapa de bits estático y
la mostrará como una imagen. Esto significa que, mientras que una ventana de información puede responder a un evento
de clic, no puede responder a los eventos de toque o gestos, y los controles individuales en la ventana de información no
pueden responder a sus propios eventos de clic.

Hacer clic en la ventana de información


Cuando el usuario hace clic en la ventana de información, se desencadena el evento InfoWindowClick , que a su
vez ejecuta el método OnInfoWindowClick :

void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)


{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}

if (!string.IsNullOrWhiteSpace(customPin.Url))
{
var url = Android.Net.Uri.Parse(customPin.Url);
var intent = new Intent(Intent.ActionView, url);
intent.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
}
}

Este método abre un explorador web y navega a la dirección almacenada en la propiedad Url de la instancia
CustomPin recuperada para el Marker . Tenga en cuenta que la dirección se definió al crear la colección CustomPin
en el proyecto de biblioteca de .NET Standard.
Para obtener más información sobre cómo personalizar una instancia de MapView , vea Using the Google Maps
API in your application (Uso de la API de Google Maps en su aplicación).
Creación del representador personalizado en la Plataforma universal de Windows
Las siguientes capturas de pantalla muestran el mapa antes y después de la personalización:

En la UWP la marca se denomina icono de mapa y puede ser una imagen personalizada o la imagen
predeterminada definida por el sistema. Un icono de mapa puede mostrar un UserControl , que se muestra en la
respuesta para el usuario que pulsa en el icono de mapa. El UserControl puede mostrar cualquier contenido,
incluyendo las propiedades Label y Address de la instancia Pin .
El siguiente ejemplo de código muestra el representador personalizado de UWP:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.UWP
{
public class CustomMapRenderer : MapRenderer
{
MapControl nativeMap;
List<CustomPin> customPins;
XamarinMapOverlay mapOverlay;
bool xamarinOverlayShown = false;

protected override void OnElementChanged(ElementChangedEventArgs<Map> e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
nativeMap.MapElementClick -= OnMapElementClick;
nativeMap.Children.Clear();
mapOverlay = null;
nativeMap = null;
}

if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
nativeMap = Control as MapControl;
customPins = formsMap.CustomPins;

nativeMap.Children.Clear();
nativeMap.MapElementClick += OnMapElementClick;

foreach (var pin in customPins)


{
var snPosition = new BasicGeoposition { Latitude = pin.Pin.Position.Latitude, Longitude =
pin.Pin.Position.Longitude };
var snPoint = new Geopoint(snPosition);

var mapIcon = new MapIcon();


mapIcon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///pin.png"));
mapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
mapIcon.Location = snPoint;
mapIcon.NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0);

nativeMap.MapElements.Add(mapIcon);
}
}
}
...
}
}

El método OnElementChanged realiza las siguientes operaciones, siempre que se adjunte el presentador
personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms)::::
Borra la colección de MapControl.Children para quitar los elementos de interfaz de usuario existentes del
mapa y después registra un controlador de eventos para el evento de MapElementClick . Este evento se
desencadena cuando el usuario pulsa o hace clic en un MapElement en el MapControl y solo se cancela su
suscripción cuando cambia el elemento al que está adjunto el representador.
Cada marca en la colección de customPins se muestra en la ubicación geográfica correcta en el mapa como
sigue:
La ubicación para la marca se crea como una instancia de Geopoint .
Una instancia de MapIcon se crea para representar la marca.
La imagen utilizada para representar el MapIcon se especifica estableciendo la propiedad
MapIcon.Image . Con todo, no siempre se puede garantizar que se muestre la imagen del icono de mapa,
ya que puede estar ocultada por otros elementos del mapa. Por lo tanto, la propiedad
CollisionBehaviorDesired del icono del mapa se establece en
MapElementCollisionBehavior.RemainVisible , para asegurarse de que está visible.
La ubicación del MapIcon se especifica configurando la propiedad MapIcon.Location .
La propiedad MapIcon.NormalizedAnchorPoint se establece en la ubicación aproximada del puntero en la
imagen. Si esta propiedad conserva su valor predeterminado de (0,0), que representa la esquina
superior izquierda de la imagen, los cambios en el nivel de zoom del mapa pueden dar lugar a que la
imagen apunte a una ubicación distinta.
La instancia MapIcon se agrega a la colección MapControl.MapElements . Esto da como resultado que el
icono de mapa se muestre en el MapControl .

NOTE
Cuando se usa la misma imagen para varios iconos de mapa, la instancia de RandomAccessStreamReference debe
declararse en el nivel de página o aplicación para mejorar el rendimiento.

Mostrar el UserControl
El método OnMapElementClick se ejecuta cuando un usuario pulsa en el icono de mapa. El siguiente ejemplo de
código muestra este método:

private void OnMapElementClick(MapControl sender, MapElementClickEventArgs args)


{
var mapIcon = args.MapElements.FirstOrDefault(x => x is MapIcon) as MapIcon;
if (mapIcon != null)
{
if (!xamarinOverlayShown)
{
var customPin = GetCustomPin(mapIcon.Location.Position);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}

if (customPin.Name.Equals("Xamarin"))
{
if (mapOverlay == null)
{
mapOverlay = new XamarinMapOverlay(customPin);
}

var snPosition = new BasicGeoposition { Latitude = customPin.Position.Latitude, Longitude =


customPin.Position.Longitude };
var snPoint = new Geopoint(snPosition);

nativeMap.Children.Add(mapOverlay);
MapControl.SetLocation(mapOverlay, snPoint);
MapControl.SetNormalizedAnchorPoint(mapOverlay, new Windows.Foundation.Point(0.5, 1.0));
xamarinOverlayShown = true;
}
}
else
{
nativeMap.Children.Remove(mapOverlay);
xamarinOverlayShown = false;
}
}
}
Este método crea una instancia de UserControl que muestra información sobre la marca. Esto se logra de la
siguiente manera:
Se recupera la instancia de MapIcon .
Se llama al método GetCustomPin para devolver los datos de marca personalizada que se mostrarán.
Se crea una instancia de XamarinMapOverlay para mostrar los datos de marca personalizada. Esta clase es un
control de usuario.
Se crea la ubicación geográfica en la que se mostrará la instancia de XamarinMapOverlay en el MapControl
como una instancia de Geopoint .
La instancia XamarinMapOverlay se agrega a la colección MapControl.Children . Esta colección contiene
elementos de interfaz de usuario de XAML que se mostrarán en el mapa.
La ubicación geográfica de la instancia de XamarinMapOverlay en el mapa se establece mediante una llamada al
método SetLocation .
La ubicación relativa de la instancia de XamarinMapOverlay que corresponde a la ubicación especificada se
establece mediante una llamada al método SetNormalizedAnchorPoint . Esto garantiza que los cambios en el
nivel de zoom del mapa tendrán como resultado que la instancia de XamarinMapOverlay siempre se muestre en
la ubicación correcta.
Como alternativa, si ya se muestra información sobre la marca en el mapa, pulsar en el mapa quita la instancia de
XamarinMapOverlay de la colección de MapControl.Children .

Pulsar en el botón Información


Cuando el usuario pulsa el botón Información en el control de usuario de XamarinMapOverlay , se desencadena el
evento Tapped , que a su vez ejecuta el método OnInfoButtonTapped :

private async void OnInfoButtonTapped(object sender, TappedRoutedEventArgs e)


{
await Launcher.LaunchUriAsync(new Uri(customPin.Url));
}

Este método abre un explorador web y navega a la dirección almacenada en la propiedad Url de la instancia de
CustomPin . Tenga en cuenta que la dirección se definió al crear la colección CustomPin en el proyecto de
biblioteca de .NET Standard.
Para obtener más información sobre cómo personalizar una instancia de MapControl , vea Introducción a
ubicación y mapas en MSDN.

Vínculos relacionados
Xamarin.Forms Map (Mapa de Xamarin.Forms)
Maps in Xamarin.iOS (Mapas en Xamarin.iOS)
API de Maps
Customized Pin (sample) (Marca personalizada [ejemplo])
Personalización de una ListView
18/12/2020 • 28 minutes to read • Edit Online

Descargar el ejemplo
Una ListView de :::no-loc(Xamarin.Forms)::: es una vista que muestra una colección de datos como una lista
vertical. En este artículo se muestra cómo crear un representador personalizado que encapsula los controles de
lista específica de la plataforma y los diseños de celda nativa, lo que permite tener más control sobre el
rendimiento del control de lista nativa.
Todas las vistas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y
que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
ListView , se crea en iOS la instancia de la clase ListViewRenderer , que a su vez crea una instancia del control
UITableView nativo. En la plataforma de Android, la clase ListViewRenderer crea una instancia de un control
ListView nativo. En Plataforma universal de Windows (UWP), la clase ListViewRenderer crea una instancia de un
control ListView nativo. Para obtener más información sobre el representador y las clases de control nativo a las
que se asignan los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.
El siguiente diagrama muestra la relación entre el control ListView y los controles nativos correspondientes que
lo implementan:

El proceso de representación puede aprovecharse para implementar las personalizaciones específicas de la


plataforma creando un representador personalizado para una ListView en cada plataforma. Para hacerlo, siga
este procedimiento:
1. Cree un control personalizado de :::no-loc(Xamarin.Forms):::.
2. Consuma el control personalizado de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para el control en cada plataforma.
Ahora se analizará en detalle cada elemento, para implementar un representador NativeListView que aproveche
las ventajas de los diseños de celda nativos y los controles de lista específicos de la plataforma. Este escenario es
útil al migrar una aplicación nativa existente que contiene la lista y el código de la celda que se puede volver a usar.
Además, permite la personalización detallada de las características de control de lista que pueden afectar al
rendimiento, como la virtualización de datos.

Crear el control ListView personalizado


Se puede crear un control personalizado ListView mediante la creación de subclases de la clase ListView , como
se muestra en el siguiente ejemplo de código:

public class NativeListView : ListView


{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create ("Items", typeof(IEnumerable<DataSource>), typeof(NativeListView), new
List<DataSource> ());

public IEnumerable<DataSource> Items {


get { return (IEnumerable<DataSource>)GetValue (ItemsProperty); }
set { SetValue (ItemsProperty, value); }
}

public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;

public void NotifyItemSelected (object item)


{
if (ItemSelected != null) {
ItemSelected (this, new SelectedItemChangedEventArgs (item));
}
}
}

El NativeListView se crea en el proyecto de biblioteca de .NET Standard y define la API para el control
personalizado. Este control expone una propiedad Items que se usa para rellenar el ListView con los datos y que
puede enlazarse a datos para fines de presentación. También expone un evento ItemSelected que se desencadena
cada vez que se selecciona un elemento en un control de lista nativo específico de la plataforma. Para más
información sobre el enlace de datos, consulte Data Binding Basics (Aspectos básicos del enlace de datos).

Uso del control personalizado


En XAML puede hacerse referencia al control personalizado NativeListView en el proyecto de biblioteca de .NET
Standard declarando un espacio de nombres para su ubicación y usando el prefijo del espacio de nombres en el
control. El siguiente ejemplo de código muestra cómo se puede usar el control personalizado NativeListView en
una página XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
...
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="{x:Static local:App.Description}" HorizontalTextAlignment="Center" />
<local:NativeListView Grid.Row="1" x:Name="nativeListView" ItemSelected="OnItemSelected"
VerticalOptions="FillAndExpand" />
</Grid>
</ContentPage.Content>
</ContentPage>

El prefijo de espacio de nombres local puede tener cualquier nombre. Pero los valores clr-namespace y
assembly tienen que coincidir con los detalles del control personalizado. Una vez que se declara el espacio de
nombres, el prefijo se usa para hacer referencia al control personalizado.
El siguiente ejemplo de código muestra cómo se puede usar el control personalizado NativeListView en una
página C#:
public class MainPageCS : ContentPage
{
NativeListView nativeListView;

public MainPageCS()
{
nativeListView = new NativeListView
{
Items = DataSource.GetList(),
VerticalOptions = LayoutOptions.FillAndExpand
};

switch (Device.RuntimePlatform)
{
case Device.iOS:
Padding = new Thickness(0, 20, 0, 0);
break;
case Device.Android:
case Device.UWP:
Padding = new Thickness(0);
break;
}

Content = new Grid


{
RowDefinitions = {
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength (1, GridUnitType.Star) }
},
Children = {
new Label { Text = App.Description, HorizontalTextAlignment = TextAlignment.Center },
nativeListView
}
};
nativeListView.ItemSelected += OnItemSelected;
}
...
}

El control personalizado NativeListView utiliza los representadores personalizados específicos de la plataforma


para mostrar una lista de datos que se rellena mediante la propiedad Items . Cada fila de la lista contiene tres
elementos de datos, un nombre, una categoría y un nombre de archivo de imagen. El diseño de cada fila de la lista
se define mediante el representador personalizado específico de la plataforma.

NOTE
Dado que el control personalizado NativeListView se representa mediante controles de lista específicos de la plataforma
que incluyen capacidad de desplazamiento, el control personalizado no debe hospedarse en los controles de diseño
desplazable, como ScrollView .

Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para crear controles de
lista específicos de la plataforma y diseños de celda nativos.

Creación del representador personalizado en cada plataforma


El proceso para crear la clase del representador personalizado es el siguiente:
1. Cree una subclase de la clase ListViewRenderer que representa el control personalizado.
2. Invalide el método OnElementChanged que representa el control personalizado y escriba lógica para
personalizarlo. Se llama a este método cuando se crea el correspondiente ListView de :::no-
loc(Xamarin.Forms):::.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el control personalizado de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al
representador personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Proporcionar un representador personalizado en cada proyecto de la plataforma es un paso opcional. Si no hay un
representador personalizado registrado, se usa el representador predeterminado de la clase base de la celda.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

El control personalizado NativeListView se representa mediante clases de representador específicas de la


plataforma, que se derivan de la clase ListViewRenderer de cada plataforma. Esto da lugar a que cada control
personalizado NativeListView se represente con diseños de celda nativos y controles de lista específicos de la
plataforma, como se muestra en las siguientes capturas de pantalla:

La clase ListViewRenderer expone el método OnElementChanged , al que se llama cuando se crea el control
personalizado de :::no-loc(Xamarin.Forms)::: para representar el control nativo correspondiente. Este método toma
un parámetro ElementChangedEventArgs que contiene propiedades OldElement y NewElement . Estas propiedades
representan al elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de
:::no-loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente. En la aplicación de ejemplo, la
propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de
NativeListView .
El lugar para realizar la personalización de controles nativos es una versión reemplazada del método
OnElementChanged en cada clase de representador específica de la plataforma. Una referencia con tipo para el
control nativo que se usa en la plataforma puede obtenerse a través de la propiedad Control . Además, mediante
la propiedad :::no-loc(Xamarin.Forms)::: se puede obtener una referencia al control de Element que se representa.
Debe tener cuidado al suscribirse a los controladores de eventos en el método OnElementChanged , como se
muestra en el siguiente ejemplo de código:

protected override void OnElementChanged (ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.ListView> e)


{
base.OnElementChanged (e);

if (e.OldElement != null) {
// Unsubscribe from event handlers and cleanup any resources
}

if (e.NewElement != null) {
// Configure the native control and subscribe to event handlers
}
}

Solo se debe configurar el control nativo y suscribir a los controladores de eventos cuando se adjunta el
representador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::. De forma similar, solo se debe
cancelar la suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que está
asociado el representador. Adoptar este enfoque facilita crear un representador personalizado que no sufra
pérdidas de memoria.
Una versión invalidada del método OnElementPropertyChanged , en cada clase de representador específico de la
plataforma, es el lugar para responder a los cambios de propiedad enlazable en el control personalizado de :::no-
loc(Xamarin.Forms):::. Siempre se debe realizar una comprobación de la propiedad que ha modificado, ya que esta
invalidación se puede llamar varias veces.
Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control
personalizado de :::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador
personalizado. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.
En las secciones siguientes se describe la implementación de cada clase de representador personalizado específico
de plataforma.
Creación del representador personalizado en iOS
El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:
[assembly: ExportRenderer (typeof(NativeListView), typeof(NativeiOSListViewRenderer))]
namespace CustomRenderer.iOS
{
public class NativeiOSListViewRenderer : ListViewRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<:::no-
loc(Xamarin.Forms):::.ListView> e)
{
base.OnElementChanged (e);

if (e.OldElement != null) {
// Unsubscribe
}

if (e.NewElement != null) {
Control.Source = new NativeiOSListViewSource (e.NewElement as NativeListView);
}
}
}
}

El control UITableView se configura mediante la creación de una instancia de la clase NativeiOSListViewSource ,


siempre que se adjunte el representador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::. Esta
clase proporciona datos para el control UITableView invalidando los métodos RowsInSection y GetCell desde la
clase UITableViewSource y exponiendo una propiedad Items que contiene la lista de datos que se mostrarán. La
clase también proporciona una invalidación del método RowSelected que invoca el evento ItemSelected
proporcionado por el control personalizado NativeListView . Para obtener más información sobre las
invalidaciones de método, vea Subclassing UITableViewSource (Creación de subclases de UITableViewSource). El
método GetCell devuelve un UITableCellView que se rellena con datos para cada fila de la lista y se muestra en
el siguiente ejemplo de código:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)


{
// request a recycled cell to save memory
NativeiOSListViewCell cell = tableView.DequeueReusableCell (cellIdentifier) as NativeiOSListViewCell;

// if there are no cells to reuse, create a new one


if (cell == null) {
cell = new NativeiOSListViewCell (cellIdentifier);
}

if (String.IsNullOrWhiteSpace (tableItems [indexPath.Row].ImageFilename)) {


cell.UpdateCell (tableItems [indexPath.Row].Name
, tableItems [indexPath.Row].Category
, null);
} else {
cell.UpdateCell (tableItems [indexPath.Row].Name
, tableItems [indexPath.Row].Category
, UIImage.FromFile ("Images/" + tableItems [indexPath.Row].ImageFilename + ".jpg"));
}

return cell;
}

Este método crea una instancia de NativeiOSListViewCell para cada fila de datos que se mostrará en la pantalla.
La instancia de NativeiOSCell define el diseño de cada celda y los datos de la celda. Cuando una celda
desaparezca de la pantalla debido al desplazamiento, la celda estará disponible para su reutilización. Esto evita
desperdiciar memoria garantizando que solo hay instancias de NativeiOSCell para los datos que se muestran en
la pantalla, en lugar de todos los datos en la lista. Para obtener más información sobre la reutilización de celdas,
vea Cell Reuse (Reutilización de celdas). El método GetCell también lee la propiedad ImageFilename de cada fila
de datos, siempre que exista, y lee la imagen y la almacena como una instancia de UIImage antes de actualizar la
instancia de NativeiOSListViewCell con los datos (nombre, categoría e imagen) de la fila.
La clase NativeiOSListViewCell define el diseño de cada celda y se muestra en el siguiente ejemplo de código:

public class NativeiOSListViewCell : UITableViewCell


{
UILabel headingLabel, subheadingLabel;
UIImageView imageView;

public NativeiOSListViewCell (NSString cellId) : base (UITableViewCellStyle.Default, cellId)


{
SelectionStyle = UITableViewCellSelectionStyle.Gray;

ContentView.BackgroundColor = UIColor.FromRGB (218, 255, 127);

imageView = new UIImageView ();

headingLabel = new UILabel () {


Font = UIFont.FromName ("Cochin-BoldItalic", 22f),
TextColor = UIColor.FromRGB (127, 51, 0),
BackgroundColor = UIColor.Clear
};

subheadingLabel = new UILabel () {


Font = UIFont.FromName ("AmericanTypewriter", 12f),
TextColor = UIColor.FromRGB (38, 127, 0),
TextAlignment = UITextAlignment.Center,
BackgroundColor = UIColor.Clear
};

ContentView.Add (headingLabel);
ContentView.Add (subheadingLabel);
ContentView.Add (imageView);
}

public void UpdateCell (string caption, string subtitle, UIImage image)


{
headingLabel.Text = caption;
subheadingLabel.Text = subtitle;
imageView.Image = image;
}

public override void LayoutSubviews ()


{
base.LayoutSubviews ();

headingLabel.Frame = new CoreGraphics.CGRect (5, 4, ContentView.Bounds.Width - 63, 25);


subheadingLabel.Frame = new CoreGraphics.CGRect (100, 18, 100, 20);
imageView.Frame = new CoreGraphics.CGRect (ContentView.Bounds.Width - 63, 5, 33, 33);
}
}

Esta clase define los controles utilizados para representar el contenido de la celda y su diseño. El constructor
NativeiOSListViewCell crea instancias de controles de UILabel y UIImageView e inicializa su apariencia. Estos
controles se usan para mostrar datos de cada fila, y el método UpdateCell se usa para establecer estos datos en
las instancias de UILabel y UIImageView . El método LayoutSubviews invalidado establece la ubicación de estas
instancias especificando sus coordenadas dentro de la celda.
Responde a un cambio de propiedad en el control personalizado
Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el
representador personalizado debe responder mostrando los cambios. Esto puede realizarse invalidando el método
OnElementPropertyChanged , que se muestra en el siguiente ejemplo de código:
protected override void OnElementPropertyChanged (object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);

if (e.PropertyName == NativeListView.ItemsProperty.PropertyName) {
Control.Source = new NativeiOSListViewSource (Element as NativeListView);
}
}

El método crea una nueva instancia de la clase NativeiOSListViewSource que proporciona datos para el control
UITableView , siempre que la propiedad enlazable NativeListView.Items haya cambiado.

Creación del representador personalizado en Android


En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:

[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeAndroidListViewRenderer))]


namespace CustomRenderer.Droid
{
public class NativeAndroidListViewRenderer : ListViewRenderer
{
Context _context;

public NativeAndroidListViewRenderer(Context context) : base(context)


{
_context = context;
}

protected override void OnElementChanged(ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.ListView>


e)
{
base.OnElementChanged(e);

if (e.OldElement != null)
{
// unsubscribe
Control.ItemClick -= OnItemClick;
}

if (e.NewElement != null)
{
// subscribe
Control.Adapter = new NativeAndroidListViewAdapter(_context as Android.App.Activity,
e.NewElement as NativeListView);
Control.ItemClick += OnItemClick;
}
}
...

void OnItemClick(object sender, Android.Widget.AdapterView.ItemClickEventArgs e)


{
((NativeListView)Element).NotifyItemSelected(((NativeListView)Element).Items.ToList()[e.Position -
1]);
}
}
}

El control nativo ListView se configura siempre y cuando el representador personalizado esté asociado a un
nuevo elemento de :::no-loc(Xamarin.Forms):::. Esta configuración implica la creación de una instancia de la clase
NativeAndroidListViewAdapter que proporciona datos al control ListView nativo y el registro de un controlador
de eventos para procesar el evento ItemClick . A su vez, este controlador invocará el evento ItemSelected
proporcionado por el control personalizado NativeListView . Se cancela la suscripción del evento ItemClick solo
si cambia el representador al que está adjunto el elemento de :::no-loc(Xamarin.Forms):::.
El NativeAndroidListViewAdapter deriva de la clase BaseAdapter y expone una propiedad Items que contiene la
lista de datos que se mostrarán, además de invalidar los métodos Count , GetView , GetItemId y this[int] . Para
obtener más información sobre estas invalidaciones de método, vea Implementing a ListAdapter (Implementación
de un ListAdapter). El método GetView devuelve una vista para cada fila, que se rellena con datos y se muestra en
el siguiente ejemplo de código:

public override View GetView (int position, View convertView, ViewGroup parent)
{
var item = tableItems [position];

var view = convertView;


if (view == null) {
// no view to re-use, create new
view = context.LayoutInflater.Inflate (Resource.Layout.NativeAndroidListViewCell, null);
}
view.FindViewById<TextView> (Resource.Id.Text1).Text = item.Name;
view.FindViewById<TextView> (Resource.Id.Text2).Text = item.Category;

// grab the old image and dispose of it


if (view.FindViewById<ImageView> (Resource.Id.Image).Drawable != null) {
using (var image = view.FindViewById<ImageView> (Resource.Id.Image).Drawable as BitmapDrawable) {
if (image != null) {
if (image.Bitmap != null) {
//image.Bitmap.Recycle ();
image.Bitmap.Dispose ();
}
}
}
}

// If a new image is required, display it


if (!String.IsNullOrWhiteSpace (item.ImageFilename)) {
context.Resources.GetBitmapAsync (item.ImageFilename).ContinueWith ((t) => {
var bitmap = t.Result;
if (bitmap != null) {
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (bitmap);
bitmap.Dispose ();
}
}, TaskScheduler.FromCurrentSynchronizationContext ());
} else {
// clear the image
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (null);
}

return view;
}

Se llama al método GetView para devolver la celda que se va a representar, como una View , para cada fila de
datos en la lista. Crea una instancia de View para cada fila de datos que se mostrará en la pantalla, con la
apariencia de la instancia de View que se define en un archivo de diseño. Cuando una celda desaparezca de la
pantalla debido al desplazamiento, la celda estará disponible para su reutilización. Esto evita desperdiciar memoria
garantizando que solo hay instancias de View para los datos que se muestran en la pantalla, en lugar de todos los
datos en la lista. Para obtener más información sobre la reutilización de vistas, vea Row View Re-use (Reutilización
de vistas de fila).
El método GetView también rellena la instancia View con datos, incluyendo la lectura de los datos de imagen del
nombre de archivo especificado en la propiedad ImageFilename .
El diseño de cada celda mostrada por el ListView nativo se define en el archivo de diseño
NativeAndroidListViewCell.axml , que aumenta el método LayoutInflater.Inflate . En el siguiente ejemplo de
código se muestra la definición del diseño:

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/CustomSelector">
<LinearLayout
android:id="@+id/Text"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip">
<TextView
android:id="@+id/Text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF7F3300"
android:textSize="20dip"
android:textStyle="italic" />
<TextView
android:id="@+id/Text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dip"
android:textColor="#FF267F00"
android:paddingLeft="100dip" />
</LinearLayout>
<ImageView
android:id="@+id/Image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="5dp"
android:src="@drawable/icon"
android:layout_alignParentRight="true" />
</RelativeLayout>

Este diseño especifica que dos controles de TextView y un control de ImageView se usan para mostrar el
contenido de la celda. Los dos controles de TextView están orientados verticalmente dentro de un control de
LinearLayout , con todos los controles contenidos en un RelativeLayout .

Responde a un cambio de propiedad en el control personalizado


Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el
representador personalizado debe responder mostrando los cambios. Esto puede realizarse invalidando el método
OnElementPropertyChanged , que se muestra en el siguiente ejemplo de código:

protected override void OnElementPropertyChanged (object sender,


System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);

if (e.PropertyName == NativeListView.ItemsProperty.PropertyName) {
Control.Adapter = new NativeAndroidListViewAdapter (_context as Android.App.Activity, Element as
NativeListView);
}
}

El método crea una nueva instancia de la clase NativeAndroidListViewAdapter que proporciona datos para el
control ListView nativo, siempre que el la propiedad enlazable NativeListView.Items haya cambiado.
Creación del representador personalizado en UWP
En el siguiente ejemplo de código se muestra el representador personalizado para UWP:

[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeUWPListViewRenderer))]


namespace CustomRenderer.UWP
{
public class NativeUWPListViewRenderer : ListViewRenderer
{
ListView listView;

protected override void OnElementChanged(ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.ListView>


e)
{
base.OnElementChanged(e);

listView = Control as ListView;

if (e.OldElement != null)
{
// Unsubscribe
listView.SelectionChanged -= OnSelectedItemChanged;
}

if (e.NewElement != null)
{
listView.SelectionMode = ListViewSelectionMode.Single;
listView.IsItemClickEnabled = false;
listView.ItemsSource = ((NativeListView)e.NewElement).Items;
listView.ItemTemplate = App.Current.Resources["ListViewItemTemplate"] as
Windows.UI.Xaml.DataTemplate;
// Subscribe
listView.SelectionChanged += OnSelectedItemChanged;
}
}

void OnSelectedItemChanged(object sender, SelectionChangedEventArgs e)


{
((NativeListView)Element).NotifyItemSelected(listView.SelectedItem);
}
}
}

El control nativo ListView se configura siempre y cuando el representador personalizado esté asociado a un
nuevo elemento de :::no-loc(Xamarin.Forms):::. Esta configuración implica configurar el modo en que el control
ListView nativo responderá a los elementos seleccionados, rellenar los datos mostrados por el control, definir la
apariencia y el contenido de cada celda y registrar un controlador de eventos para procesar el evento
SelectionChanged . A su vez, este controlador invocará el evento ItemSelected proporcionado por el control
personalizado NativeListView . Se cancela la suscripción del evento SelectionChanged solo si cambia el
representador al que está adjunto el elemento de :::no-loc(Xamarin.Forms):::.
La apariencia y el contenido de cada celda ListView nativa se definen mediante un DataTemplate denominado
ListViewItemTemplate . Este DataTemplate se almacena en el diccionario de recursos de nivel de aplicación y se
muestra en el siguiente ejemplo de código:
<DataTemplate x:Key="ListViewItemTemplate">
<Grid Background="#DAFF7F">
<Grid.Resources>
<local:ConcatImageExtensionConverter x:Name="ConcatImageExtensionConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.40*" />
<ColumnDefinition Width="0.40*"/>
<ColumnDefinition Width="0.20*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="2" Foreground="#7F3300" FontStyle="Italic" FontSize="22"
VerticalAlignment="Top" Text="{Binding Name}" />
<TextBlock Grid.RowSpan="2" Grid.Column="1" Foreground="#267F00" FontWeight="Bold" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Category}" />
<Image Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Center" Source="
{Binding ImageFilename, Converter={StaticResource ConcatImageExtensionConverter}}" Width="50" Height="50" />
<Line Grid.Row="1" Grid.ColumnSpan="3" X1="0" X2="1" Margin="30,20,0,0" StrokeThickness="1"
Stroke="LightGray" Stretch="Fill" VerticalAlignment="Bottom" />
</Grid>
</DataTemplate>

El DataTemplate especifica los controles utilizados para mostrar el contenido de la celda y su diseño y apariencia.
Dos controles de TextBlock y un control de Image se usan para mostrar el contenido de la celda mediante el
enlace de datos. Además, una instancia de ConcatImageExtensionConverter se utiliza para concatenar la extensión
de archivo .jpg para cada nombre de archivo de imagen. Esto garantiza que el control Image puede cargar y
representar la imagen cuando se establece su propiedad Source .
Responde a un cambio de propiedad en el control personalizado
Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el
representador personalizado debe responder mostrando los cambios. Esto puede realizarse invalidando el método
OnElementPropertyChanged , que se muestra en el siguiente ejemplo de código:

protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs


e)
{
base.OnElementPropertyChanged(sender, e);

if (e.PropertyName == NativeListView.ItemsProperty.PropertyName)
{
listView.ItemsSource = ((NativeListView)Element).Items;
}
}

El método rellena el control ListView nativo con los datos modificados, siempre que la propiedad
NativeListView.Items enlazable haya cambiado.

Resumen
En este artículo se mostró cómo crear un representador personalizado que encapsula los controles de lista
específica de la plataforma y los diseños de celda nativa, lo que permite tener más control sobre el rendimiento del
control de lista nativa.

Vínculos relacionados
CustomRendererListView (sample) (CustomRendererListView [ejemplo])
Personalización de ViewCell
18/12/2020 • 26 minutes to read • Edit Online

Descargar el ejemplo
Un ViewCell de :::no-loc(Xamarin.Forms)::: es una celda que se puede agregar a ListView o TableView y que
contiene una vista definida por el desarrollador. En este artículo se muestra cómo crear un representador
personalizado para un ViewCell que se hospeda dentro de un control ListView de :::no-loc(Xamarin.Forms):::. Esto
impide que se llame varias veces a los cálculos de diseño de :::no-loc(Xamarin.Forms)::: durante el desplazamiento
de ListView.
Todas las celdas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y
que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
ViewCell , se crea en iOS la instancia de la clase ViewCellRenderer , que a su vez crea una instancia del control
UITableViewCell nativo. En la plataforma de Android, la clase ViewCellRenderer crea una instancia de un control
View nativo. En la Plataforma universal de Windows (UWP), la clase ViewCellRenderer crea una instancia de un
elemento DataTemplate nativo. Para obtener más información sobre el representador y las clases de control
nativo a las que se asignan los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del
representador.
El siguiente diagrama muestra la relación entre la clase ViewCell y los controles nativos correspondientes que la
implementan:

El proceso de representación puede aprovecharse para implementar las personalizaciones específicas de la


plataforma creando un representador personalizado para una ViewCell en cada plataforma. Para hacerlo, siga
este procedimiento:
1. Cree una celda personalizada de :::no-loc(Xamarin.Forms):::.
2. Consuma la celda personalizada de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para la celda en cada plataforma.
Cada elemento ahora se explicará a su vez, para implementar un representador NativeCell que aproveche las
ventajas de un diseño específico de la plataforma para cada celda que se hospeda dentro de un control :::no-
loc(Xamarin.Forms)::: ListView de . Esto impide que se llame varias veces a los cálculos de diseño de :::no-
loc(Xamarin.Forms)::: durante el desplazamiento de ListView .

Creación de la celda personalizada


Se puede crear un control de celda personalizado mediante la creación de subclases de la clase ViewCell , como
se muestra en el siguiente ejemplo de código:

public class NativeCell : ViewCell


{
public static readonly BindableProperty NameProperty =
BindableProperty.Create ("Name", typeof(string), typeof(NativeCell), "");

public string Name {


get { return (string)GetValue (NameProperty); }
set { SetValue (NameProperty, value); }
}

public static readonly BindableProperty CategoryProperty =


BindableProperty.Create ("Category", typeof(string), typeof(NativeCell), "");

public string Category {


get { return (string)GetValue (CategoryProperty); }
set { SetValue (CategoryProperty, value); }
}

public static readonly BindableProperty ImageFilenameProperty =


BindableProperty.Create ("ImageFilename", typeof(string), typeof(NativeCell), "");

public string ImageFilename {


get { return (string)GetValue (ImageFilenameProperty); }
set { SetValue (ImageFilenameProperty, value); }
}
}

La clase NativeCell se crea en el proyecto de biblioteca de .NET Standard y define la API para la celda
personalizada. La celda personalizada expone las propiedades Name , Category y ImageFilename que se pueden
mostrar mediante el enlace de datos. Para más información sobre el enlace de datos, consulte Data Binding Basics
(Aspectos básicos del enlace de datos).

Consumo de la celda personalizada


En XAML puede hacerse referencia a la celda personalizada NativeCell en el proyecto de biblioteca de .NET
Standard. Para ello, se declara un espacio de nombres para su ubicación y se usa el prefijo del espacio de nombres
en el elemento de celda personalizado. El siguiente ejemplo de código muestra cómo la celda personalizada
NativeCell puede utilizarse en una página XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
...
<ContentPage.Content>
<StackLayout>
<Label Text=":::no-loc(Xamarin.Forms)::: native cell" HorizontalTextAlignment="Center" />
<ListView x:Name="listView" CachingStrategy="RecycleElement" ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<local:NativeCell Name="{Binding Name}" Category="{Binding Category}"
ImageFilename="{Binding ImageFilename}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
El prefijo de espacio de nombres local puede tener cualquier nombre. Empero, los valores clr-namespace y
assembly deben coincidir con los detalles del control personalizado. Una vez que se declare el espacio de
nombres, el prefijo se utiliza para hacer referencia a la celda personalizada.
El siguiente ejemplo de código muestra cómo una página de C# puede consumir la celda personalizada
NativeCell :

public class NativeCellPageCS : ContentPage


{
ListView listView;

public NativeCellPageCS()
{
listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = DataSource.GetList(),
ItemTemplate = new DataTemplate(() =>
{
var nativeCell = new NativeCell();
nativeCell.SetBinding(NativeCell.NameProperty, "Name");
nativeCell.SetBinding(NativeCell.CategoryProperty, "Category");
nativeCell.SetBinding(NativeCell.ImageFilenameProperty, "ImageFilename");

return nativeCell;
})
};

switch (Device.RuntimePlatform)
{
case Device.iOS:
Padding = new Thickness(0, 20, 0, 0);
break;
case Device.Android:
case Device.UWP:
Padding = new Thickness(0);
break;
}

Content = new StackLayout


{
Children = {
new Label { Text = ":::no-loc(Xamarin.Forms)::: native cell", HorizontalTextAlignment =
TextAlignment.Center },
listView
}
};
listView.ItemSelected += OnItemSelected;
}
...
}

Un control :::no-loc(Xamarin.Forms)::: ListView de se usa para mostrar una lista de los datos, que se rellena
mediante la propiedad ItemSource . La estrategia de almacenamiento en caché RecycleElement intenta minimizar
la velocidad de ejecución y el consumo de memoria de ListView mediante el reciclaje de las celdas de la lista.
Para obtener más información, vea Estrategia de almacenamiento en caché.
Cada fila de la lista contiene tres elementos de datos: un nombre, una categoría y un nombre de archivo de
imagen. El diseño de cada fila de la lista está definido por el DataTemplate al que se hace referencia mediante la
propiedad enlazable ListView.ItemTemplate . DataTemplate define que cada fila de datos en la lista será una
NativeCell que muestra sus propiedades Name , Category y ImageFilename mediante el enlace de datos. Para
obtener más información sobre el control ListView , vea ListView de Xamarin.Forms.
Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para personalizar el diseño
específico de la plataforma para cada celda.

Creación del representador personalizado en cada plataforma


El proceso para crear la clase del representador personalizado es el siguiente:
1. Cree una subclase de la clase ViewCellRenderer que represente la celda personalizada.
2. Invalide el método específico de la plataforma que representa la celda personalizada y escriba una lógica para
personalizarla.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar la celda personalizada de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al
representador personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Para la mayoría de los elementos de :::no-loc(Xamarin.Forms):::, proporcionar un representador personalizado en cada
proyecto de la plataforma es un paso opcional. Si no se registra un representador personalizado, se usará el representador
predeterminado de la clase base del control. Con todo, los representadores personalizados son necesarios en cada proyecto
de la plataforma al representar un elemento ViewCell.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

Las clases del representador específico de la plataforma, que se derivan de la clase ViewCellRenderer para cada
plataforma, representan la celda personalizada NativeCell . Esto da como resultado que cada celda personalizada
NativeCell se represente con diseño específico de la plataforma, como se muestra en las siguientes capturas de
pantalla:
La clase ViewCellRenderer expone métodos específicos de la plataforma para representar la celda personalizada.
Estos son el método GetCell en la plataforma iOS, el método GetCellCore en la plataforma Android y el método
GetTemplate en UWP.

Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo de la celda de
:::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador personalizado. El prefijo
assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.

En las secciones siguientes se describe la implementación de cada clase de representador personalizado específico
de plataforma.
Creación del representador personalizado en iOS
El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:

[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeiOSCellRenderer))]


namespace CustomRenderer.iOS
{
public class NativeiOSCellRenderer : ViewCellRenderer
{
NativeiOSCell cell;

public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)


{
var nativeCell = (NativeCell)item;

cell = reusableCell as NativeiOSCell;


if (cell == null)
cell = new NativeiOSCell(item.GetType().FullName, nativeCell);
else
cell.NativeCell.PropertyChanged -= OnNativeCellPropertyChanged;

nativeCell.PropertyChanged += OnNativeCellPropertyChanged;
cell.UpdateCell(nativeCell);
return cell;
}
...
}
}

Se llama al método GetCell para crear cada celda que se mostrará. Cada celda es una instancia de NativeiOSCell
que define el diseño de la celda y sus datos. La operación del método GetCell depende de la estrategia de
almacenamiento en caché ListView :
Cuando la estrategia de almacenamiento en caché ListView sea RetainElement , se invocará el método
GetCell para cada celda. Se creará una instancia de NativeiOSCell para cada instancia de NativeCell que
se muestre inicialmente en la pantalla. Cuando el usuario se desplace a través de la ListView , se volverán a
usar las instancias de NativeiOSCell . Para obtener más información sobre la reutilización de celdas de iOS,
vea Reutilización de celda.

NOTE
Este código de representador personalizado llevará a cabo cierta reutilización de celda cuando la ListView se
establezca para conservar las celdas.

El método UpdateCell actualizará los datos mostrados por cada instancia de NativeiOSCell , ya sean recién
creados o se vuelvan a utilizar, con los datos de cada instancia de NativeCell .

NOTE
El método OnNativeCellPropertyChanged nunca se invocará cuando la estrategia de almacenamiento en caché
ListView se establezca para conservar las celdas.

Cuando la estrategia de almacenamiento en caché ListView sea RecycleElement , se invocará el método


GetCell para cada celda que se muestra inicialmente en la pantalla. Se creará una instancia de
NativeiOSCell para cada instancia de NativeCell que se muestre inicialmente en la pantalla. El método
UpdateCell actualizará los datos mostrados por cada instancia de NativeiOSCell con los datos de cada
instancia de NativeCell . Empero, el método GetCell no se invoca cuando el usuario se desplaza por la
ListView . En su lugar, se reutilizarán las instancias de NativeiOSCell . Los eventos de PropertyChanged se
generarán en la instancia de NativeCell cuando cambien sus datos y el controlador de eventos
OnNativeCellPropertyChanged actualizará los datos en cada instancia de NativeiOSCell reutilizada.

El siguiente ejemplo de código muestra el método OnNativeCellPropertyChanged que se invoca cuando se provoca
un evento PropertyChanged :
namespace CustomRenderer.iOS
{
public class NativeiOSCellRenderer : ViewCellRenderer
{
...

void OnNativeCellPropertyChanged(object sender, PropertyChangedEventArgs e)


{
var nativeCell = (NativeCell)sender;
if (e.PropertyName == NativeCell.NameProperty.PropertyName)
{
cell.HeadingLabel.Text = nativeCell.Name;
}
else if (e.PropertyName == NativeCell.CategoryProperty.PropertyName)
{
cell.SubheadingLabel.Text = nativeCell.Category;
}
else if (e.PropertyName == NativeCell.ImageFilenameProperty.PropertyName)
{
cell.CellImageView.Image = cell.GetImage(nativeCell.ImageFilename);
}
}
}
}

Este método actualiza los datos que muestran las instancias de NativeiOSCell reutilizadas. Se realiza una
comprobación para la propiedad que se ha modificado, ya que el método puede llamarse varias veces.
La clase NativeiOSCell define el diseño de cada celda y se muestra en el siguiente ejemplo de código:
internal class NativeiOSCell : UITableViewCell, INativeElementView
{
public UILabel HeadingLabel { get; set; }
public UILabel SubheadingLabel { get; set; }
public UIImageView CellImageView { get; set; }

public NativeCell NativeCell { get; private set; }


public Element Element => NativeCell;

public NativeiOSCell(string cellId, NativeCell cell) : base(UITableViewCellStyle.Default, cellId)


{
NativeCell = cell;

SelectionStyle = UITableViewCellSelectionStyle.Gray;
ContentView.BackgroundColor = UIColor.FromRGB(255, 255, 224);
CellImageView = new UIImageView();

HeadingLabel = new UILabel()


{
Font = UIFont.FromName("Cochin-BoldItalic", 22f),
TextColor = UIColor.FromRGB(127, 51, 0),
BackgroundColor = UIColor.Clear
};

SubheadingLabel = new UILabel()


{
Font = UIFont.FromName("AmericanTypewriter", 12f),
TextColor = UIColor.FromRGB(38, 127, 0),
TextAlignment = UITextAlignment.Center,
BackgroundColor = UIColor.Clear
};

ContentView.Add(HeadingLabel);
ContentView.Add(SubheadingLabel);
ContentView.Add(CellImageView);
}

public void UpdateCell(NativeCell cell)


{
HeadingLabel.Text = cell.Name;
SubheadingLabel.Text = cell.Category;
CellImageView.Image = GetImage(cell.ImageFilename);
}

public UIImage GetImage(string filename)


{
return (!string.IsNullOrWhiteSpace(filename)) ? UIImage.FromFile("Images/" + filename + ".jpg") : null;
}

public override void LayoutSubviews()


{
base.LayoutSubviews();

HeadingLabel.Frame = new CGRect(5, 4, ContentView.Bounds.Width - 63, 25);


SubheadingLabel.Frame = new CGRect(100, 18, 100, 20);
CellImageView.Frame = new CGRect(ContentView.Bounds.Width - 63, 5, 33, 33);
}
}

Esta clase define los controles utilizados para representar el contenido de la celda y su diseño. La clase
implementa la interfaz INativeElementView , que es necesaria cuando ListView usa estrategia de almacenamiento
en caché RecycleElement . Esta interfaz especifica que la clase debe implementar la propiedad Element , que debe
devolver los datos de celda personalizada para las celdas recicladas.
El constructor NativeiOSCell inicializa la apariencia de las propiedades HeadingLabel , SubheadingLabel y
CellImageView . Estas propiedades se utilizan para mostrar los datos almacenados en la instancia NativeCell ,
siendo llamado el método UpdateCell para establecer el valor de cada propiedad. Además, cuando ListView usa
la estrategia de almacenamiento en caché RecycleElement , el método OnNativeCellPropertyChanged puede
actualizar los datos mostrados por las propiedades HeadingLabel , SubheadingLabel y CellImageView en el
representador personalizado.
El diseño de la celda se realiza mediante la invalidación de LayoutSubviews , que establece las coordenadas de
HeadingLabel , SubheadingLabel y CellImageView dentro de la celda.

Creación del representador personalizado en Android


En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:

[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeAndroidCellRenderer))]


namespace CustomRenderer.Droid
{
public class NativeAndroidCellRenderer : ViewCellRenderer
{
NativeAndroidCell cell;

protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,


ViewGroup parent, Context context)
{
var nativeCell = (NativeCell)item;
Console.WriteLine("\t\t" + nativeCell.Name);

cell = convertView as NativeAndroidCell;


if (cell == null)
{
cell = new NativeAndroidCell(context, nativeCell);
}
else
{
cell.NativeCell.PropertyChanged -= OnNativeCellPropertyChanged;
}

nativeCell.PropertyChanged += OnNativeCellPropertyChanged;

cell.UpdateCell(nativeCell);
return cell;
}
...
}
}

Se llama al método para crear cada celda que se mostrará. Cada celda es una instancia de
GetCellCore
NativeAndroidCell que define el diseño de la celda y sus datos. La operación del método GetCellCore depende
de la estrategia de almacenamiento en caché ListView :
Cuando la estrategia de almacenamiento en caché ListView sea RetainElement , se invocará el método
GetCellCore para cada celda. Se creará una NativeAndroidCell para cada instancia de NativeCell que se
muestre inicialmente en la pantalla. Cuando el usuario se desplace a través de la ListView , se volverán a
usar las instancias de NativeAndroidCell . Para obtener más información sobre la reutilización de celdas en
Android, vea Reutilización de vista fila.

NOTE
Tenga en cuenta que este código de representador personalizado llevará a cabo cierta reutilización de celda cuando
la ListView se establezca para conservar las celdas.
El método UpdateCell actualizará los datos mostrados por cada instancia de NativeAndroidCell , ya sean
recién creados o se vuelvan a utilizar, con los datos de cada instancia de NativeCell .

NOTE
Tenga en cuenta que el método OnNativeCellPropertyChanged se invocará cuando ListView se configure para
conservar las celdas, pero no se actualizarán los valores de propiedad de NativeAndroidCell .

Cuando la estrategia de almacenamiento en caché ListView sea RecycleElement , se invocará el método


GetCellCore para cada celda que se muestra inicialmente en la pantalla. Se creará una instancia de
NativeAndroidCell para cada instancia de NativeCell que se muestre inicialmente en la pantalla. El
método UpdateCell actualizará los datos mostrados por cada instancia de NativeAndroidCell con los
datos de cada instancia de NativeCell . Empero, el método GetCellCore no se invoca cuando el usuario se
desplaza por la ListView . En su lugar, se reutilizarán las instancias de NativeAndroidCell . Los eventos de
PropertyChanged se generarán en la instancia de NativeCell cuando cambien sus datos y el controlador de
eventos OnNativeCellPropertyChanged actualizará los datos en cada instancia de NativeAndroidCell
reutilizada.
El siguiente ejemplo de código muestra el método OnNativeCellPropertyChanged que se invoca cuando se provoca
un evento PropertyChanged :

namespace CustomRenderer.Droid
{
public class NativeAndroidCellRenderer : ViewCellRenderer
{
...

void OnNativeCellPropertyChanged(object sender, PropertyChangedEventArgs e)


{
var nativeCell = (NativeCell)sender;
if (e.PropertyName == NativeCell.NameProperty.PropertyName)
{
cell.HeadingTextView.Text = nativeCell.Name;
}
else if (e.PropertyName == NativeCell.CategoryProperty.PropertyName)
{
cell.SubheadingTextView.Text = nativeCell.Category;
}
else if (e.PropertyName == NativeCell.ImageFilenameProperty.PropertyName)
{
cell.SetImage(nativeCell.ImageFilename);
}
}
}
}

Este método actualiza los datos que muestran las instancias de NativeAndroidCell reutilizadas. Se realiza una
comprobación para la propiedad que se ha modificado, ya que el método puede llamarse varias veces.
La clase NativeAndroidCell define el diseño de cada celda y se muestra en el siguiente ejemplo de código:
internal class NativeAndroidCell : LinearLayout, INativeElementView
{
public TextView HeadingTextView { get; set; }
public TextView SubheadingTextView { get; set; }
public ImageView ImageView { get; set; }

public NativeCell NativeCell { get; private set; }


public Element Element => NativeCell;

public NativeAndroidCell(Context context, NativeCell cell) : base(context)


{
NativeCell = cell;

var view = (context as Activity).LayoutInflater.Inflate(Resource.Layout.NativeAndroidCell, null);


HeadingTextView = view.FindViewById<TextView>(Resource.Id.HeadingText);
SubheadingTextView = view.FindViewById<TextView>(Resource.Id.SubheadingText);
ImageView = view.FindViewById<ImageView>(Resource.Id.Image);

AddView(view);
}

public void UpdateCell(NativeCell cell)


{
HeadingTextView.Text = cell.Name;
SubheadingTextView.Text = cell.Category;

// Dispose of the old image


if (ImageView.Drawable != null)
{
using (var image = ImageView.Drawable as BitmapDrawable)
{
if (image != null)
{
if (image.Bitmap != null)
{
image.Bitmap.Dispose();
}
}
}
}

SetImage(cell.ImageFilename);
}

public void SetImage(string filename)


{
if (!string.IsNullOrWhiteSpace(filename))
{
// Display new image
Context.Resources.GetBitmapAsync(filename).ContinueWith((t) =>
{
var bitmap = t.Result;
if (bitmap != null)
{
ImageView.SetImageBitmap(bitmap);
bitmap.Dispose();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
// Clear the image
ImageView.SetImageBitmap(null);
}
}
}
Esta clase define los controles utilizados para representar el contenido de la celda y su diseño. La clase
implementa la interfaz INativeElementView , que es necesaria cuando ListView usa estrategia de almacenamiento
en caché RecycleElement . Esta interfaz especifica que la clase debe implementar la propiedad Element , que debe
devolver los datos de celda personalizada para las celdas recicladas.
El constructor NativeAndroidCell aumenta el diseño de NativeAndroidCell e inicializa las propiedades
HeadingTextView , SubheadingTextView y ImageView a los controles en el diseño aumentado. Estas propiedades se
utilizan para mostrar los datos almacenados en la instancia NativeCell , siendo llamado el método UpdateCell
para establecer el valor de cada propiedad. Además, cuando la ListView usa la estrategia de almacenamiento en
caché RecycleElement , el método OnNativeCellPropertyChanged puede actualizar los datos mostrados por las
propiedades HeadingTextView , SubheadingTextView y ImageView en el representador personalizado.
El siguiente ejemplo de código muestra la definición de diseño para el archivo de diseño de
NativeAndroidCell.axml :

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/CustomSelector">
<LinearLayout
android:id="@+id/Text"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip">
<TextView
android:id="@+id/HeadingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF7F3300"
android:textSize="20dip"
android:textStyle="italic" />
<TextView
android:id="@+id/SubheadingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dip"
android:textColor="#FF267F00"
android:paddingLeft="100dip" />
</LinearLayout>
<ImageView
android:id="@+id/Image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="5dp"
android:src="@drawable/icon"
android:layout_alignParentRight="true" />
</RelativeLayout>

Este diseño especifica que dos controles de TextView y un control de ImageView se usan para mostrar el
contenido de la celda. Los dos controles de TextView están orientados verticalmente dentro de un control de
LinearLayout , con todos los controles contenidos en un RelativeLayout .

Creación del representador personalizado en UWP


El siguiente ejemplo de código muestra el representador personalizado para UWP:
[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeUWPCellRenderer))]
namespace CustomRenderer.UWP
{
public class NativeUWPCellRenderer : ViewCellRenderer
{
public override Windows.UI.Xaml.DataTemplate GetTemplate(Cell cell)
{
return App.Current.Resources["ListViewItemTemplate"] as Windows.UI.Xaml.DataTemplate;
}
}
}

Se llama al método GetTemplate para devolver la celda que se va a representar para cada fila de datos en la lista.
Crea un DataTemplate para cada instancia de NativeCell que se mostrará en la pantalla, con el DataTemplate
definiendo la apariencia y el contenido de la celda.
DataTemplate se almacena en el diccionario de recursos de nivel de aplicación y se muestra en el siguiente
ejemplo de código:

<DataTemplate x:Key="ListViewItemTemplate">
<Grid Background="LightYellow">
<Grid.Resources>
<local:ConcatImageExtensionConverter x:Name="ConcatImageExtensionConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.40*" />
<ColumnDefinition Width="0.40*"/>
<ColumnDefinition Width="0.20*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="2" Foreground="#7F3300" FontStyle="Italic" FontSize="22"
VerticalAlignment="Top" Text="{Binding Name}" />
<TextBlock Grid.RowSpan="2" Grid.Column="1" Foreground="#267F00" FontWeight="Bold" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Category}" />
<Image Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Center"
Source="{Binding ImageFilename, Converter={StaticResource ConcatImageExtensionConverter}}" Width="50"
Height="50" />
<Line Grid.Row="1" Grid.ColumnSpan="3" X1="0" X2="1" Margin="30,20,0,0" StrokeThickness="1"
Stroke="LightGray" Stretch="Fill" VerticalAlignment="Bottom" />
</Grid>
</DataTemplate>

DataTemplate especifica los controles utilizados para mostrar el contenido de la celda y su diseño y apariencia.
Dos controles de TextBlock y un control de Image se usan para mostrar el contenido de la celda mediante el
enlace de datos. Además, una instancia de ConcatImageExtensionConverter se utiliza para concatenar la extensión
de archivo .jpg para cada nombre de archivo de imagen. Esto garantiza que el control Image puede cargar y
representar la imagen cuando se establece su propiedad Source .

Resumen
En este artículo se ha mostrado cómo crear un representador personalizado para un ViewCell que se hospeda
dentro de un control :::no-loc(Xamarin.Forms)::: ListView de . Esto impide que se llame varias veces a los cálculos
de diseño de :::no-loc(Xamarin.Forms)::: durante el desplazamiento de ListView .

Vínculos relacionados
Rendimiento de ListView
CustomRendererViewCell (sample) (CustomRendererViewCell [ejemplo])
Personalización de WebView
18/12/2020 • 27 minutes to read • Edit Online

Descargar el ejemplo
Un objeto WebView de :::no-loc(Xamarin.Forms)::: es una vista que muestra contenido web y HTML en la aplicación.
En este artículo se explica cómo crear un representador personalizado que extienda WebView para permitir la
invocación de código de C# desde JavaScript.
Todas las vistas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y
que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
WebView en iOS, se crea una instancia de la clase WkWebViewRenderer , que a su vez crea una instancia del control
WkWebView nativo. En la plataforma de Android, la clase WebViewRenderer crea una instancia de un control WebView
nativo. En Plataforma universal de Windows (UWP), la clase WebViewRenderer crea una instancia de un control
WebView nativo. Para obtener más información sobre el representador y las clases de control nativo a las que se
asignan los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.
El siguiente diagrama muestra la relación entre la clase View y los controles nativos correspondientes que la
implementan:

El proceso de representación se puede usar para implementar personalizaciones de plataforma mediante la


creación de un representador personalizado para un objeto WebView en cada plataforma. Para hacerlo, siga este
procedimiento:
1. Cree el control HybridWebView personalizado.
2. Consuma el elemento HybridWebView de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para el elemento HybridWebView en cada plataforma.
Ahora se describirá cada elemento para implementar un representador de HybridWebView que mejore los objetos
WebView de :::no-loc(Xamarin.Forms)::: para permitir la invocación de código de C# desde JavaScript. Se usa la
instancia de HybridWebView para mostrar una página HTML que pide al usuario que escriba su nombre. Luego,
cuando el usuario hace clic en un botón HTML, una función de JavaScript invoca a un elemento Action de C# que
muestra una ventana emergente que contiene el nombre de los usuarios.
Para más información sobre el proceso de invocación de C# desde JavaScript, vea Invocación de C# desde
JavaScript. Para más información sobre la página HTML, vea Creación de la página web.
NOTE
Un objeto WebView puede invocar una función de JavaScript desde C# y devolver cualquier resultado al código de C# que
realiza la llamada. Para más información, vea Invocación de JavaScript.

Creación de HybridWebView
El control personalizado HybridWebView se puede crear mediante la creación de subclases de la clase WebView :

public class HybridWebView : WebView


{
Action<string> action;

public static readonly BindableProperty UriProperty = BindableProperty.Create(


propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));

public string Uri


{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}

public void RegisterAction(Action<string> callback)


{
action = callback;
}

public void Cleanup()


{
action = null;
}

public void InvokeAction(string data)


{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}

El control HybridWebView personalizado se crea en el proyecto de biblioteca de .NET Standard y define la siguiente
API para el control:
Una propiedad Uri que especifica la dirección de la página web que se va a cargar.
Un método RegisterAction que registra un elemento Action con el control. Se invoca a la acción registrada
desde el código de JavaScript incluido en el archivo HTML al que hace referencia la propiedad Uri .
Un método CleanUp que quita la referencia al elemento registrado Action .
Un método InvokeAction que invoca al elemento registrado Action . Este método se llamará desde un
representador personalizado en cada proyecto de plataforma.

Uso de HybridWebView
En XAML, se puede hacer referencia al control personalizado HybridWebView en el proyecto de biblioteca de .NET
Standard al declarar un espacio de nombres para su ubicación y usar el prefijo del espacio de nombres en el
control personalizado. El siguiente ejemplo de código muestra cómo se puede usar el control personalizado
HybridWebView en una página XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
x:Class="CustomRenderer.HybridWebViewPage"
Padding="0,40,0,0">
<local:HybridWebView x:Name="hybridWebView"
Uri="index.html" />
</ContentPage>

El prefijo de espacio de nombres local puede tener cualquier nombre. Pero los valores clr-namespace y
assembly tienen que coincidir con los detalles del control personalizado. Una vez que se declara el espacio de
nombres, el prefijo se usa para hacer referencia al control personalizado.
El siguiente ejemplo de código muestra cómo se puede usar el control personalizado HybridWebView en una página
C#:

public HybridWebViewPageCS()
{
var hybridWebView = new HybridWebView
{
Uri = "index.html"
};
// ...
Padding = new Thickness(0, 40, 0, 0);
Content = hybridWebView;
}

La instancia de HybridWebView se usa para mostrar un control web nativo en cada plataforma. Su propiedad Uri
se establece en un archivo HTML que se almacena en cada proyecto de plataforma y que se mostrará mediante el
control web nativo. El HTML representado pide al usuario que escriba su nombre, con una función de JavaScript
que invoca a un elemento Action de C# en respuesta a un clic de botón HTML.
HybridWebViewPage registra la acción que se va a invocar desde JavaScript, como se muestra en el ejemplo de
código siguiente:

public partial class HybridWebViewPage : ContentPage


{
public HybridWebViewPage()
{
// ...
hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
}
}

Esta acción llama al método DisplayAlert para mostrar un elemento emergente modal que presenta el nombre
especificado en la página HTML que muestra la instancia de HybridWebView .
Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para mejorar los controles
web de la plataforma si se permite la invocación de código de C# desde JavaScript.

Creación del representador personalizado en cada plataforma


El proceso para crear la clase del representador personalizado es el siguiente:
1. Cree una subclase de la clase WkWebViewRenderer en iOS y de la clase WebViewRenderer en Android y UWP, que
representa el control personalizado.
2. Invalide el método OnElementChanged que representa el objeto WebView y escriba lógica para personalizarlo.
Este método se llama cuando se crea un objeto HybridWebView .
3. Agregue un atributo ExportRenderer a la clase del representador personalizado o AssemblyInfo.cs , para
especificar que se va a usar para representar el control personalizado de :::no-loc(Xamarin.Forms):::. Este atributo
se usa para registrar al representador personalizado con :::no-loc(Xamarin.Forms):::.

NOTE
Para la mayoría de los elementos de :::no-loc(Xamarin.Forms):::, proporcionar un representador personalizado en cada
proyecto de la plataforma es un paso opcional. Si no se registra un representador personalizado, se usará el representador
predeterminado de la clase base del control. Pero los representadores personalizados son necesarios en cada proyecto de
plataforma al representar un elemento View.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

El control personalizado HybridWebView se representa mediante clases de representador de la plataforma, que se


derivan de la clase WkWebViewRenderer en iOS y de la clase WebViewRenderer en Android y UWP. Esto da lugar a que
cada control personalizado HybridWebView se represente con controles web, como se muestra en las capturas de
pantalla siguientes:

Las clases WkWebViewRenderer y WebViewRenderer exponen el método OnElementChanged , al que se llama cuando se
crea el control personalizado de :::no-loc(Xamarin.Forms)::: para representar el control web nativo correspondiente.
Este método toma un parámetro VisualElementChangedEventArgs que contiene propiedades OldElement y
NewElement . Estas propiedades representan al elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el
representador y al elemento de :::no-loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente.
En la aplicación de ejemplo, la propiedad OldElement es null y la propiedad NewElement contiene una referencia
a la instancia de HybridWebView .
El lugar para realizar la personalización del control nativo es una versión reemplazada del método
OnElementChanged , en cada clase de representador de la plataforma. Mediante la propiedad :::no-
loc(Xamarin.Forms)::: se puede obtener una referencia al control de Element que se representa.
Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control
personalizado de :::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador
personalizado. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.
En las secciones siguientes se describe la estructura de la página web cargada por cada control web nativo, el
proceso para invocar C# desde JavaScript y su implementación en cada clase de representador personalizado de la
plataforma.
Creación de la página web
El ejemplo de código siguiente muestra la página web que va a mostrar el control personalizado HybridWebView :

<html>
<body>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<h1>HybridWebView Test</h1>
<br />
Enter name: <input type="text" id="name">
<br />
<br />
<button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
<br />
<p id="result">Result:</p>
<script type="text/javascript">function log(str) {
$('#result').text($('#result').text() + " " + str);
}

function invokeCSCode(data) {
try {
log("Sending Data:" + data);
invokeCSharpAction(data);
}
catch (err) {
log(err);
}
}</script>
</body>
</html>

La página web permite que un usuario escriba su nombre en un elemento input y proporciona un elemento
button que va a invocar a código de C# cuando se haga clic sobre él. El proceso para lograrlo es el siguiente:

Cuando el usuario hace clic en el elemento button , se llama a la función de JavaScript invokeCSCode y el valor
del elemento input se pasa a la función.
La función invokeCSCode llama a la función log para mostrar los datos que está enviando al elemento Action
de C#. Luego llama al método invokeCSharpAction para invocar al elemento Action de C#, pasando el
parámetro recibido desde el elemento input .

La función de JavaScript invokeCSharpAction no está definida en la página web, así que es cada representador
personalizado el que la inserta en ella.
En iOS, este archivo HTML se encuentra en la carpeta de contenido del proyecto de la plataforma e incluye una
acción de compilación de BundleResource . En Android, este archivo HTML se encuentra en la carpeta de
contenido o recursos del proyecto de la plataforma e incluye una acción de compilación de AndroidAsset .
Invocación de C# desde JavaScript
El proceso para invocar a C# desde JavaScript es idéntico en cada plataforma:
El representador personalizado crea un control web nativo y carga el archivo HTML especificado por la
propiedad HybridWebView.Uri .
Una vez que se ha cargado la página web, el representador personalizado inserta la función de JavaScript
invokeCSharpAction en la página web.
Cuando el usuario escribe su nombre y hace clic en el elemento HTML button , se invoca a la función
invokeCSCode , que a su vez invoca a la función invokeCSharpAction .
La función invokeCSharpAction invoca a un método del representador personalizado, que a su vez invoca al
método HybridWebView.InvokeAction .
El método HybridWebView.InvokeAction invoca al elemento registrado Action .
En las secciones siguientes se habla de cómo se implementa este proceso en cada plataforma.
Creación del representador personalizado en iOS
El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data)
{window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;

public HybridWebViewRenderer() : this(new WKWebViewConfiguration())


{
}

public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)


{
userController = config.UserContentController;
var script = new WKUserScript(new NSString(JavaScriptFunction),
WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
}

protected override void OnElementChanged(VisualElementChangedEventArgs e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction");
HybridWebView hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}

if (e.NewElement != null)
{
string filename = Path.Combine(NSBundle.MainBundle.BundlePath,
$"Content/{((HybridWebView)Element).Uri}");
LoadRequest(new NSUrlRequest(new NSUrl(filename, false)));
}
}

public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage


message)
{
((HybridWebView)Element).InvokeAction(message.Body.ToString());
}

protected override void Dispose(bool disposing)


{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control


nativo WKWebView y la función de JavaScript invokeCSharpAction se inserta en la página web. Una vez que el
usuario escribe su nombre y hace clic en el elemento HTML button , se ejecuta la función de JavaScript
invokeCSharpAction y se llama al método DidReceiveScriptMessage después de que se reciba un mensaje de la
página web. A su vez, este método invoca al método HybridWebView.InvokeAction , que invoca a la acción registrada
para mostrar la ventana emergente.
Esta funcionalidad se logra del siguiente modo:
El constructor del representador crea un objeto WkWebViewConfiguration y recupera su objeto
WKUserContentController . El objeto WkUserContentController permite publicar mensajes e insertar scripts de
usuario en una página web.
El constructor del representador crea un WKUserScript objeto, que inserta la función invokeCSharpAction de
JavaScript en la página web después de cargarla.
El constructor del representador llama al método WKUserContentController.AddUserScript para agregar el objeto
WKUserScript al controlador de contenido.
El constructor del representador llama al método WKUserContentController.AddScriptMessageHandler para
agregar un controlador de mensajes de script denominado invokeAction al objeto WKUserContentController , lo
que hace que la función window.webkit.messageHandlers.invokeAction.postMessage(data) de JavaScript se defina
en todos los marcos de todas las instancias de WebView en las que se usa el objeto WKUserContentController .
Siempre que el representador personalizado está asociado a un nuevo elemento de :::no-loc(Xamarin.Forms)::::
El método WKWebView.LoadRequest carga el archivo HTML especificado por la propiedad
HybridWebView.Uri . El código especifica que el archivo se almacena en la carpeta Content del proyecto.
Una vez que se muestra la página web, la función de JavaScript invokeCSharpAction se inserta en la
página web.
Los recursos se liberan cuando cambia el elemento al que está asociado el representador.
El elemento de :::no-loc(Xamarin.Forms)::: se limpia cuando se desecha el representador.

NOTE
La clase WKWebView solo se admite en iOS 8 y versiones posteriores.

Además, Info.plist debe actualizarse para que incluya los siguientes valores:

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

Creación del representador personalizado en Android


En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;

public HybridWebViewRenderer(Context context) : base(context)


{
_context = context;
}

protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript:
{JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
}
}

protected override void Dispose(bool disposing)


{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control


nativo WebView y la función de JavaScript invokeCSharpAction se inserta en la página web, una vez que la página
web ha terminado de cargarse, con la invalidación OnPageFinished en la clase JavascriptWebViewClient :

public class JavascriptWebViewClient : FormsWebViewClient


{
string _javascript;

public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)


{
_javascript = javascript;
}

public override void OnPageFinished(WebView view, string url)


{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}

Una vez que el usuario escribe su nombre y hace clic en el elemento HTML button , se ejecuta la función de
JavaScript invokeCSharpAction . Esta funcionalidad se logra del siguiente modo:
Siempre que el representador personalizado está asociado a un nuevo elemento de :::no-loc(Xamarin.Forms)::::
El método SetWebViewClient establece un nuevo objeto JavascriptWebViewClient como la
implementación de WebViewClient .
El método WebView.AddJavascriptInterface inserta una nueva instancia de JSBridge en el marco
principal del contexto de JavaScript de WebView y le asigna el nombre jsBridge . Esto permite acceder a
los métodos de la clase JSBridge desde JavaScript.
El método WebView.LoadUrl carga el archivo HTML especificado por la propiedad HybridWebView.Uri . El
código especifica que el archivo se almacena en la carpeta Content del proyecto.
En la clase JavascriptWebViewClient , la función de JavaScript invokeCSharpAction se inserta en la página
web una vez que esta termina de cargarse.
Los recursos se liberan cuando cambia el elemento al que está asociado el representador.
El elemento de :::no-loc(Xamarin.Forms)::: se limpia cuando se desecha el representador.
Cuando se ejecuta la función de JavaScript invokeCSharpAction , a su vez invoca al método JSBridge.InvokeAction ,
que se muestra en el ejemplo de código siguiente:

public class JSBridge : Java.Lang.Object


{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;

public JSBridge(HybridWebViewRenderer hybridRenderer)


{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}

[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;

if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))


{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}

La clase debe derivar de Java.Lang.Object y los métodos que se exponen a JavaScript deben decorarse con los
atributos [JavascriptInterface] y [Export] . Por lo tanto, cuando se inserta la función de JavaScript
invokeCSharpAction en la página web y se ejecuta, llama al método JSBridge.InvokeAction , puesto que está
decorado con los atributos [JavascriptInterface] y [Export("invokeAction")] . A su vez, el método InvokeAction
invoca al método HybridWebView.InvokeAction , que invocará a la acción registrada para mostrar la ventana
emergente.

IMPORTANT
En los proyectos de Android en los que se use el atributo [Export] se debe incluir una referencia a Mono.Android.Export ,
o bien se producirá un error del compilador.

Tenga en cuenta que la clase JSBridge mantiene un elemento WeakReference para la clase HybridWebViewRenderer .
Esto es para evitar la creación de una referencia circular entre las dos clases. Para más información, vea Referencias
débiles.
Creación del representador personalizado en UWP
En el siguiente ejemplo de código se muestra el representador personalizado para UWP:

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]


namespace CustomRenderer.UWP
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}";

protected override void OnElementChanged(ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.WebView>


e)
{
base.OnElementChanged(e);

if (e.OldElement != null)
{
Control.NavigationCompleted -= OnWebViewNavigationCompleted;
Control.ScriptNotify -= OnWebViewScriptNotify;
}
if (e.NewElement != null)
{
Control.NavigationCompleted += OnWebViewNavigationCompleted;
Control.ScriptNotify += OnWebViewScriptNotify;
Control.Source = new Uri($"ms-appx-web:///Content//{((HybridWebView)Element).Uri}");
}
}

async void OnWebViewNavigationCompleted(Windows.UI.Xaml.Controls.WebView sender,


WebViewNavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// Inject JS script
await Control.InvokeScriptAsync("eval", new[] { JavaScriptFunction });
}
}

void OnWebViewScriptNotify(object sender, NotifyEventArgs e)


{
((HybridWebView)Element).InvokeAction(e.Value);
}

protected override void Dispose(bool disposing)


{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control


nativo WebView y la función de JavaScript invokeCSharpAction se inserta en la página web, una vez cargada, con el
método WebView.InvokeScriptAsync . Una vez que el usuario escribe su nombre y hace clic en el elemento HTML
button , se ejecuta la función de JavaScript invokeCSharpAction y se llama al método OnWebViewScriptNotify
después de que se reciba una notificación de la página web. A su vez, este método invoca al método
HybridWebView.InvokeAction , que invoca a la acción registrada para mostrar la ventana emergente.

Esta funcionalidad se logra del siguiente modo:


Siempre que el representador personalizado está asociado a un nuevo elemento de :::no-loc(Xamarin.Forms)::::
Se registran controladores de eventos para los eventos NavigationCompleted y ScriptNotify . El evento
NavigationCompleted se desencadena cuando el control nativo WebView ha terminado de cargar el
contenido actual o si se ha producido un error de navegación. El evento ScriptNotify se desencadena
cuando el contenido del control nativo WebView usa JavaScript para pasar una cadena a la aplicación. La
página web desencadena el evento ScriptNotify mediante una llamada a window.external.notify al
pasar un parámetro string .
La propiedad WebView.Source está establecida en el URI del archivo HTML especificado por la propiedad
HybridWebView.Uri . El código da por supuesto que el archivo está almacenado en la carpeta Content del
proyecto. Una vez que se muestra la página web, se desencadena el evento NavigationCompleted y se
invoca al método OnWebViewNavigationCompleted . La función de JavaScript invokeCSharpAction se inserta
en la página web con el método WebView.InvokeScriptAsync , siempre que la navegación se haya realizado
correctamente.
La suscripción de los eventos se cancela cuando cambia el representador al que está adjunto el elemento.
El elemento de :::no-loc(Xamarin.Forms)::: se limpia cuando se desecha el representador.

Vínculos relacionados
HybridWebView (ejemplo)
Implementación de una vista
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
Los controles de interfaces de usuario personalizadas de :::no-loc(Xamarin.Forms)::: deben derivarse de la clase
View, que se usa para colocar los diseños y los controles en la pantalla. En este artículo se muestra cómo crear un
representador personalizado para un control personalizado de :::no-loc(Xamarin.Forms)::: que se usa para mostrar
una secuencia de vídeo de vista previa de la cámara del dispositivo.
Todas las vistas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y
que crea una instancia de un control nativo. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un
View en iOS, se crea la instancia de la clase ViewRenderer , que a su vez crea una instancia del control UIView
nativo. En la plataforma de Android, la clase ViewRenderer crea una instancia de un control View nativo. En
Plataforma universal de Windows (UWP), la clase ViewRenderer crea una instancia de un control FrameworkElement
nativo. Para obtener más información sobre el representador y las clases de control nativo a las que se asignan los
controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.

NOTE
Algunos controles de Android usan representadores rápidos, que no consumen la clase ViewRenderer . Para obtener más
información sobre los representadores rápidos, consulte Representadores rápidos de :::no-loc(Xamarin.Forms):::.

El siguiente diagrama muestra la relación entre la clase View y los controles nativos correspondientes que la
implementan:

El proceso de representación puede usarse para implementar personalizaciones específicas de plataforma al crear
un representador personalizado para una clase View en cada plataforma. Para hacerlo, siga este procedimiento:
1. Cree un control personalizado de :::no-loc(Xamarin.Forms):::.
2. Consuma el control personalizado de :::no-loc(Xamarin.Forms):::.
3. Cree el representador personalizado para el control en cada plataforma.
Ahora se analizará en detalle cada elemento, para implementar un representador CameraPreview que muestre una
secuencia de vídeo de vista previa de la cámara del dispositivo. Pulsar en la secuencia de vídeo la detendrá e
iniciará.
Creación de un control personalizado
Se puede crear un control personalizado mediante la creación de subclases de la clase View , como se muestra en
el siguiente ejemplo de código:

public class CameraPreview : View


{
public static readonly BindableProperty CameraProperty = BindableProperty.Create (
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraPreview),
defaultValue: CameraOptions.Rear);

public CameraOptions Camera


{
get { return (CameraOptions)GetValue (CameraProperty); }
set { SetValue (CameraProperty, value); }
}
}

El control personalizado CameraPreview se crea en el proyecto de biblioteca de .NET Standard y define la API del
control. El control personalizado expone una propiedad Camera que se usa para controlar si se debe mostrar la
secuencia de vídeo desde la cámara delantera o trasera del dispositivo. Si no se especifica un valor para la
propiedad Camera cuando se crea el control, el valor predeterminado es especificar la cámara trasera.

Uso del control personalizado


En XAML, se puede hacer referencia al control personalizado CameraPreview en el proyecto de biblioteca de .NET
Standard mediante la declaración de un espacio de nombres para su ubicación y el uso del prefijo del espacio de
nombres en el elemento de control personalizado. El siguiente ejemplo de código muestra cómo se puede usar el
control personalizado CameraPreview en una página XAML:

<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
<StackLayout>
<Label Text="Camera Preview:" />
<local:CameraPreview Camera="Rear"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
</StackLayout>
</ContentPage>

El prefijo de espacio de nombres local puede tener cualquier nombre. Pero los valores clr-namespace y
assembly tienen que coincidir con los detalles del control personalizado. Una vez que se declara el espacio de
nombres, el prefijo se usa para hacer referencia al control personalizado.
El siguiente ejemplo de código muestra cómo se puede usar el control personalizado CameraPreview en una página
C#:
public class MainPageCS : ContentPage
{
public MainPageCS ()
{
...
Content = new StackLayout
{
Children =
{
new Label { Text = "Camera Preview:" },
new CameraPreview
{
Camera = CameraOptions.Rear,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
}
}
};
}
}

Se usará una instancia del control personalizado CameraPreview para mostrar la secuencia de vídeo de vista previa
de la cámara del dispositivo. Aparte de especificar opcionalmente un valor para la propiedad Camera , la
personalización del control se llevará a cabo en el representador personalizado.
Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para crear controles de
vista previa de cámara específicos de la plataforma.

Creación del representador personalizado en cada plataforma


El proceso para crear la clase del representador personalizado en iOS y UWP es el siguiente:
1. Cree una subclase de la clase ViewRenderer<T1,T2> que representa el control personalizado. El primer
argumento de tipo debe ser el control personalizado para el que es el representador, en este caso
CameraPreview . El segundo argumento de tipo debe ser el control nativo que va a implementar el control
personalizado.
2. Invalide el método OnElementChanged que representa el control personalizado y escriba lógica para
personalizarlo. Se llama a este método cuando se crea el correspondiente control de :::no-loc(Xamarin.Forms):::.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el control personalizado de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al
representador personalizado con :::no-loc(Xamarin.Forms):::.
El proceso para crear la clase del representador personalizado en Android, como un representador rápido, es el
siguiente:
1. Cree una subclase del control de Android que representa el control personalizado. Además, especifique que la
subclase implementará las interfaces IVisualElementRenderer y IViewRenderer .
2. Implemente las interfaces IVisualElementRenderer y IViewRenderer en la clase de representación rápida.
3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará
para representar el control personalizado de :::no-loc(Xamarin.Forms):::. Este atributo se usa para registrar al
representador personalizado con :::no-loc(Xamarin.Forms):::.
NOTE
Para la mayoría de los elementos de :::no-loc(Xamarin.Forms):::, proporcionar un representador personalizado en cada
proyecto de la plataforma es un paso opcional. Si no se registra un representador personalizado, se usará el representador
predeterminado de la clase base del control. Pero los representadores personalizados son necesarios en cada proyecto de
plataforma al representar un elemento View.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las
relaciones entre ellos:

El control personalizado CameraPreview se representa mediante clases de representador específicas de la


plataforma, que se derivan de la clase ViewRenderer en iOS y UWP y de la clase FrameLayout en Android. Esto da
lugar a que cada control personalizado CameraPreview se represente con controles específicos de la plataforma,
como se muestra en las capturas de pantalla siguientes:

La clase ViewRenderer expone el método OnElementChanged , al que se llama cuando se crea el control
personalizado de :::no-loc(Xamarin.Forms)::: para representar el control nativo correspondiente. Este método toma
un parámetro ElementChangedEventArgs que contiene propiedades OldElement y NewElement . Estas propiedades
representan al elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de
:::no-loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente. En la aplicación de ejemplo, la
propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de
CameraPreview .

Una versión invalidada del método OnElementChanged , en cada clase de representador específica de la plataforma,
es el lugar en el que realizar la personalización y la creación de instancias del control nativo. Se debe usar el
método SetNativeControl para crear instancias del control nativo; además, este método también asigna la
referencia del control a la propiedad Control . Además, mediante la propiedad :::no-loc(Xamarin.Forms)::: se puede
obtener una referencia al control de Element que se representa.
En algunas circunstancias, se puede llamar al método OnElementChanged varias veces. Por lo tanto, para evitar
pérdidas de memoria, se debe tener cuidado a la hora de crear instancias de un nuevo control nativo. El enfoque
que usar al crear instancias de un nuevo control nativo en un presentador personalizado se muestra en el ejemplo
de código siguiente:

protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e)


{
base.OnElementChanged (e);

if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}

if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
}
// Configure the control and subscribe to event handlers
}
}

Solo se debe crear una instancia de un nuevo control nativo una vez, cuando la propiedad Control es null .
Además, solo se debe crear el control, configurarlo y suscribir los controladores de eventos cuando se adjunta el
presentador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::. De forma similar, solo se debe
cancelar la suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que está
asociado el representador. La adopción de este enfoque ayuda a crear un representador personalizado eficaz que
no sufra pérdidas de memoria.

IMPORTANT
El método SetNativeControl solo se debe llamar si e.NewElement no es null .

Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el
representador con :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control
personalizado de :::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador
personalizado. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.
En las secciones siguientes se describe la implementación de cada clase de representador personalizado específico
de plataforma.
Creación del representador personalizado en iOS
El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:
[assembly: ExportRenderer (typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.iOS
{
public class CameraPreviewRenderer : ViewRenderer<CameraPreview, UICameraPreview>
{
UICameraPreview uiCameraPreview;

protected override void OnElementChanged (ElementChangedEventArgs<CameraPreview> e)


{
base.OnElementChanged (e);

if (e.OldElement != null) {
// Unsubscribe
uiCameraPreview.Tapped -= OnCameraPreviewTapped;
}
if (e.NewElement != null) {
if (Control == null) {
uiCameraPreview = new UICameraPreview (e.NewElement.Camera);
SetNativeControl (uiCameraPreview);
}
// Subscribe
uiCameraPreview.Tapped += OnCameraPreviewTapped;
}
}

void OnCameraPreviewTapped (object sender, EventArgs e)


{
if (uiCameraPreview.IsPreviewing) {
uiCameraPreview.CaptureSession.StopRunning ();
uiCameraPreview.IsPreviewing = false;
} else {
uiCameraPreview.CaptureSession.StartRunning ();
uiCameraPreview.IsPreviewing = true;
}
}
...
}
}

Siempre que la propiedad Control sea null , se llama al método SetNativeControl para crear instancias de un
nuevo control UICameraPreview y asignar una referencia a él para la propiedad Control . El control
UICameraPreview es un control personalizado específico de la plataforma que utiliza las API de AVCapture para
proporcionar la secuencia de vista previa de la cámara. Expone un evento Tapped que controla el método
OnCameraPreviewTapped para detener e iniciar la vista previa de vídeo cuando se pulsa. Se establece una suscripción
al evento Tapped cuando el representador personalizado se adjunta a un nuevo elemento de :::no-
loc(Xamarin.Forms)::: y solo se cancela la suscripción cuando el elemento al que está adjunto el representador
cambia.
Creación del representador personalizado en Android
En el ejemplo de código siguiente se muestra el representador rápido de la plataforma Android:

[assembly: ExportRenderer(typeof(CustomRenderer.CameraPreview), typeof(CameraPreviewRenderer))]


namespace CustomRenderer.Droid
{
public class CameraPreviewRenderer : FrameLayout, IVisualElementRenderer, IViewRenderer
{
// ...
CameraPreview element;
VisualElementTracker visualElementTracker;
VisualElementRenderer visualElementRenderer;
FragmentManager fragmentManager;
CameraFragment cameraFragment;
FragmentManager FragmentManager => fragmentManager ??= Context.GetFragmentManager();

public event EventHandler<VisualElementChangedEventArgs> ElementChanged;


public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;

CameraPreview Element
{
get => element;
set
{
if (element == value)
{
return;
}

var oldElement = element;


element = value;
OnElementChanged(new ElementChangedEventArgs<CameraPreview>(oldElement, element));
}
}

public CameraPreviewRenderer(Context context) : base(context)


{
visualElementRenderer = new VisualElementRenderer(this);
}

void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
{
CameraFragment newFragment = null;

if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
cameraFragment.Dispose();
}
if (e.NewElement != null)
{
this.EnsureId();

e.NewElement.PropertyChanged += OnElementPropertyChanged;

ElevationHelper.SetElevation(this, e.NewElement);
newFragment = new CameraFragment { Element = element };
}

FragmentManager.BeginTransaction()
.Replace(Id, cameraFragment = newFragment, "camera")
.Commit();
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
}

async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)


{
ElementPropertyChanged?.Invoke(this, e);

switch (e.PropertyName)
{
case "Width":
await cameraFragment.RetrieveCameraDevice();
break;
}
}
// ...
}
}

En este ejemplo, el método OnElementChanged crea un objeto CameraFragment , siempre que se asocie el
representador personalizado a un nuevo elemento :::no-loc(Xamarin.Forms):::. El tipo CameraFragment es una clase
personalizada que usa la API Camera2 para proporcionar la secuencia de vista previa de la cámara. El objeto
CameraFragment se desecha cuando el elemento :::no-loc(Xamarin.Forms)::: al que se adjunta el representador se
asocia a los cambios.
Creación del representador personalizado en UWP
En el siguiente ejemplo de código se muestra el representador personalizado para UWP:

[assembly: ExportRenderer(typeof(CameraPreview), typeof(CameraPreviewRenderer))]


namespace CustomRenderer.UWP
{
public class CameraPreviewRenderer : ViewRenderer<CameraPreview, Windows.UI.Xaml.Controls.CaptureElement>
{
...
CaptureElement _captureElement;
bool _isPreviewing;

protected override void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
// Unsubscribe
Tapped -= OnCameraPreviewTapped;
...
}
if (e.NewElement != null)
{
if (Control == null)
{
...
_captureElement = new CaptureElement();
_captureElement.Stretch = Stretch.UniformToFill;

SetupCamera();
SetNativeControl(_captureElement);
}
// Subscribe
Tapped += OnCameraPreviewTapped;
}
}

async void OnCameraPreviewTapped(object sender, TappedRoutedEventArgs e)


{
if (_isPreviewing)
{
await StopPreviewAsync();
}
else
{
await StartPreviewAsync();
}
}
...
}
}

Siempre que la propiedad Control sea null , se crea una instancia de un nuevo CaptureElement y se llama al
método SetupCamera , que usa la API de MediaCapture para proporcionar la secuencia de vista previa de la cámara.
Después se llama al método SetNativeControl para asignar una referencia a la instancia de CaptureElement para la
propiedad Control . El control CaptureElement expone un evento Tapped que controla el método
OnCameraPreviewTapped para detener e iniciar la vista previa de vídeo cuando se pulsa. Se establece una suscripción
al evento Tapped cuando el representador personalizado se adjunta a un nuevo elemento de :::no-
loc(Xamarin.Forms)::: y solo se cancela la suscripción cuando el elemento al que está adjunto el representador
cambia.

NOTE
Es importante detener y eliminar los objetos que proporcionan acceso a la cámara en una aplicación de UWP. Si no lo hace
puede interferir con otras aplicaciones que intentan acceder a la cámara del dispositivo. Para obtener más información, vea
Display the camera preview (Mostar la vista previa de la cámara).

Resumen
En este artículo se ha mostrado cómo crear un representador personalizado para un control personalizado de :::no-
loc(Xamarin.Forms)::: que se usa para mostrar una secuencia de vídeo de vista previa de la cámara del dispositivo.
Los controles de interfaces de usuario personalizadas de :::no-loc(Xamarin.Forms)::: deben derivar de la clase View ,
que se usa para colocar los diseños y los controles en la pantalla.

Vínculos relacionados
CustomRendererView (sample) (CustomRendererView [ejemplo])
Implementación de un reproductor de vídeo
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
A veces es conveniente reproducir archivos de vídeo en una aplicación de :::no-loc(Xamarin.Forms):::. En esta serie
de artículos se explica cómo escribir representadores personalizados para iOS, Android y la Plataforma universal
de Windows (UWP) para una clase de :::no-loc(Xamarin.Forms)::: denominada VideoPlayer .
En el ejemplo VideoPlayerDemos , todos los archivos que implementan y admiten VideoPlayer están en
carpetas denominadas FormsVideoLibrary y se identifican con el espacio de nombres FormsVideoLibrary o los
espacios de nombres que empiezan por FormsVideoLibrary . Esta organización y nomenclatura debería hacer que
resulte más fácil copiar los archivos del reproductor de vídeo en su propia solución de :::no-loc(Xamarin.Forms):::.
VideoPlayer puede reproducir archivos de vídeo de tres tipos de orígenes:
Internet mediante una dirección URL
Un recurso insertado en la aplicación de plataforma
La biblioteca de vídeos del dispositivo
Los reproductores de vídeo necesitan controles de transporte , que son botones para reproducir y pausar el vídeo,
y una barra de posición que muestra el progreso a través del vídeo y permite al usuario ir rápidamente a una
ubicación diferente. VideoPlayer puede usar los controles de transporte y la barra de posición proporcionados
por la plataforma (como se muestra más adelante), o puede proporcionar controles de transporte personalizados
y una barra de posición. Este es el programa que se ejecuta en iOS, Android y la Plataforma universal de Windows:

Por supuesto, puede colocar el teléfono en horizontal para tener una vista más grande.
Un reproductor de vídeo más sofisticado tendría algunas características adicionales, como un control de volumen,
un mecanismo para interrumpir el vídeo cuando entra una llamada telefónica y una manera de mantener la
pantalla activa durante la reproducción.
En los siguientes artículos se muestra progresivamente cómo se compilan los representadores de plataforma y las
clases auxiliares:
Creación de reproductores de vídeo de la plataforma
Cada plataforma necesita una clase VideoPlayerRenderer que crea y mantiene un control de reproductor de vídeo
compatible con la plataforma. En este artículo se muestra la estructura de las clases del representador y cómo se
crean los reproductores.

Reproducir vídeo web


Probablemente el origen más común de vídeos para cualquier reproductor de vídeo es Internet. En este artículo se
describe cómo se puede hacer referencia a un vídeo web y cómo usarlo como origen para el reproductor de vídeo.

Enlazar orígenes de vídeo con el reproductor


En este artículo se usa ListView para presentar una colección de vídeos para reproducirlos. Un programa muestra
cómo el archivo de código subyacente puede establecer el origen de vídeo del reproductor de vídeo, pero un
segundo programa muestra cómo puede usar el enlace de datos entre ListView y el reproductor de vídeo.

Cargar vídeos de recursos de aplicación


Los vídeos se pueden insertar como recursos en los proyectos de plataforma. En este artículo se muestra cómo
almacenar esos recursos y después cargarlos en el programa para reproducirlos en el reproductor de vídeo.

Acceder a la biblioteca de vídeos del dispositivo


Cuando se crea un vídeo con la cámara del dispositivo, el archivo de vídeo se almacena en la biblioteca de
imágenes del dispositivo. En este artículo se muestra cómo acceder al selector de imágenes del dispositivo para
seleccionar el vídeo y, luego, reproducirlo con el reproductor de vídeo.

Controles de transporte de vídeo personalizados


Aunque los reproductores de vídeo en cada plataforma proporcionan sus propios controles de transporte en
forma de botones para reproducir y pausar , puede suprimir la presentación de estos botones y proporcionar los
suyos propios. En este artículo se muestra cómo hacerlo.

Barra de posición de vídeo personalizada


Todos los reproductores de vídeo de la plataforma tienen una barra de posición que muestra el progreso del vídeo
y permite avanzar o retroceder a una posición determinada. En este artículo se muestra cómo sustituir esa barra
de posición por un control personalizado.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Creación de reproductores de vídeo de plataforma
18/12/2020 • 13 minutes to read • Edit Online

Descargar el ejemplo
La solución VideoPlayerDemos contiene todo el código necesario para implementar un reproductor de vídeo
para :::no-loc(Xamarin.Forms):::. También incluye una serie de páginas en las que se muestra cómo usar el
reproductor de vídeo dentro de una aplicación. Todo el código VideoPlayer y sus representadores de plataforma
residen en carpetas de proyecto denominadas FormsVideoLibrary que además usan el espacio de nombres
FormsVideoLibrary . Esto debería facilitar la copia de los archivos en la propia aplicación y la referencia a las clases.

Reproductor de vídeo
La clase VideoPlayer forma parte de la biblioteca de .NET Standard VideoPlayerDemos compartida entre las
plataformas. Deriva de View :

using System;
using :::no-loc(Xamarin.Forms):::;

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
}
}

Los miembros de esta clase (y la interfaz IVideoPlayerController ) se describen en los artículos siguientes.
Cada una de las plataformas contiene una clase denominada VideoPlayerRenderer que contiene el código
específico de la plataforma en la que se va a implementar un reproductor de vídeo. La tarea principal de este
representador es crear un reproductor de vídeo para esa plataforma.
Controlador de vistas del reproductor de iOS
Hay varias clases involucradas en la implementación de un reproductor de vídeo en iOS. La aplicación primero crea
un elemento AVPlayerViewController y luego establece la propiedad Player en un objeto de tipo AVPlayer . Se
necesitan otras clases cuando se asigna un origen de vídeo al reproductor.
Como todos los representadores, el elemento VideoPlayerRenderer de iOS contiene un atributo ExportRenderer
que identifica la vista VideoPlayer con el representador:
using System;
using System.ComponentModel;
using System.IO;

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using UIKit;

using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.iOS;

[assembly: ExportRenderer(typeof(FormsVideoLibrary.VideoPlayer),
typeof(FormsVideoLibrary.iOS.VideoPlayerRenderer))]

namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
}
}

Por lo general, un representador que establece un control de plataforma deriva de la clase


ViewRenderer<View, NativeView> , donde View es el derivado :::no-loc(Xamarin.Forms)::: de View (en este caso,
VideoPlayer ) y NativeView es un derivado UIView de iOS para la clase de representador. Para este representador,
ese argumento genérico simplemente se establece en UIView , por motivos que va a entender en breve.
Cuando un representador se basa en un derivado UIViewController (como este), la clase debe reemplazar la
propiedad ViewController y devolver el controlador de vistas, en este caso AVPlayerViewController . Ese es el
propósito del campo _playerViewController :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
AVPlayer player;
AVPlayerItem playerItem;
AVPlayerViewController _playerViewController; // solely for ViewController property

public override UIViewController ViewController => _playerViewController;

protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)


{
base.OnElementChanged(args);

if (args.NewElement != null)
{
if (Control == null)
{
// Create AVPlayerViewController
_playerViewController = new AVPlayerViewController();

// Set Player property to AVPlayer


player = new AVPlayer();
_playerViewController.Player = player;

// Use the View from the controller as the native control


SetNativeControl(_playerViewController.View);
}
···
}
}
···
}
}

La responsabilidad principal de la invalidación OnElementChanged es comprobar si la propiedad Control es null y,


si es así, crear un control de plataforma y pasarlo al método SetNativeControl . En este caso, ese objeto solo está
disponible desde la propiedad View de AVPlayerViewController . Ese derivado UIView es una clase privada
denominada AVPlayerView , pero dado que es privada, no puede especificarse explícitamente como segundo
argumento genérico de ViewRenderer .
Por lo general, la propiedad Control de la clase de representador hace referencia al elemento UIView usado para
implementar el representador, pero en este caso, la propiedad Control no se usa en ningún otro lugar.
Vista de vídeo de Android
El representador de Android para VideoPlayer se basa en la clase VideoView de Android. Pero, si se usa VideoView
por sí solo para reproducir un vídeo en una aplicación de :::no-loc(Xamarin.Forms):::, el vídeo llena el área asignada
para VideoPlayer sin mantener la relación de aspecto correcta. Por este motivo (como verá en breve), VideoView
se convierte en elemento secundario de un elemento RelativeLayout de Android. Una directiva using define
ARelativeLayout para distinguirlo del elemento :::no-loc(Xamarin.Forms)::: de RelativeLayout , y ese es el segundo
argumento genérico de ViewRenderer :
using System;
using System.ComponentModel;
using System.IO;

using Android.Content;
using Android.Media;
using Android.Widget;
using ARelativeLayout = Android.Widget.RelativeLayout;

using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.Android;

[assembly: ExportRenderer(typeof(FormsVideoLibrary.VideoPlayer),
typeof(FormsVideoLibrary.Droid.VideoPlayerRenderer))]

namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
public VideoPlayerRenderer(Context context) : base(context)
{
}
···
}
}

A partir de :::no-loc(Xamarin.Forms)::: 2.5, los representadores de Android deben incluir un constructor con un
argumento Context .
La invalidación OnElementChanged crea VideoView y RelativeLayout y establece los parámetros de diseño de
VideoView para centrar dentro de RelativeLayout .
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
VideoView videoView;
MediaController mediaController; // Used to display transport controls
bool isPrepared;
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
base.OnElementChanged(args);

if (args.NewElement != null)
{
if (Control == null)
{
// Save the VideoView for future reference
videoView = new VideoView(Context);

// Put the VideoView in a RelativeLayout


ARelativeLayout relativeLayout = new ARelativeLayout(Context);
relativeLayout.AddView(videoView);

// Center the VideoView in the RelativeLayout


ARelativeLayout.LayoutParams layoutParams =
new ARelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
layoutParams.AddRule(LayoutRules.CenterInParent);
videoView.LayoutParameters = layoutParams;

// Handle a VideoView event


videoView.Prepared += OnVideoViewPrepared;

// Use the RelativeLayout as the native control


SetNativeControl(relativeLayout);
}
···
}
···
}

protected override void Dispose(bool disposing)


{
if (Control != null && videoView != null)
{
videoView.Prepared -= OnVideoViewPrepared;
}
base.Dispose(disposing);
}

void OnVideoViewPrepared(object sender, EventArgs args)


{
isPrepared = true;
···
}
···
}
}

Un controlador del evento Prepared se asocia en este método y se desasocia en el método Dispose . Este evento
se desencadena cuando VideoView tiene suficiente información para empezar a reproducir un archivo de vídeo.
Elemento multimedia de UWP
En Plataforma universal de Windows (UWP), el reproductor de vídeo más común es MediaElement . La
documentación de MediaElement indica que se debe usar MediaPlayerElement en su lugar cuando solo sea
necesario admitir versiones de Windows 10 a partir de la compilación 1607.
La invalidación OnElementChanged debe crear un elemento MediaElement , establecer un par de controladores de
eventos y pasar el objeto MediaElement a SetNativeControl :

using System;
using System.ComponentModel;

using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.UWP;

[assembly: ExportRenderer(typeof(FormsVideoLibrary.VideoPlayer),
typeof(FormsVideoLibrary.UWP.VideoPlayerRenderer))]

namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
base.OnElementChanged(args);

if (args.NewElement != null)
{
if (Control == null)
{
MediaElement mediaElement = new MediaElement();
SetNativeControl(mediaElement);

mediaElement.MediaOpened += OnMediaElementMediaOpened;
mediaElement.CurrentStateChanged += OnMediaElementCurrentStateChanged;
}
···
}
···
}

protected override void Dispose(bool disposing)


{
if (Control != null)
{
Control.MediaOpened -= OnMediaElementMediaOpened;
Control.CurrentStateChanged -= OnMediaElementCurrentStateChanged;
}

base.Dispose(disposing);
}
···
}
}

Los dos controladores de eventos se desasocian en el evento Dispose del representador.

Mostrar los controles de transporte


Todos los reproductores de vídeo incluidos en las plataformas admiten un conjunto predeterminado de controles
de transporte que incluyen botones para reproducir y detener y una barra para indicar la posición actual dentro del
vídeo, así como para ir a otra posición.
La clase VideoPlayer define una propiedad denominada AreTransportControlsEnabled y establece el valor
predeterminado en true :
namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
// AreTransportControlsEnabled property
public static readonly BindableProperty AreTransportControlsEnabledProperty =
BindableProperty.Create(nameof(AreTransportControlsEnabled), typeof(bool), typeof(VideoPlayer),
true);

public bool AreTransportControlsEnabled


{
set { SetValue(AreTransportControlsEnabledProperty, value); }
get { return (bool)GetValue(AreTransportControlsEnabledProperty); }
}
···
}
}

Aunque esta propiedad tiene descriptores de acceso set y get , el representador solo tiene que controlar los
casos en que la propiedad esté establecida. El descriptor de acceso get simplemente devuelve el valor actual de la
propiedad.
Propiedades como AreTransportControlsEnabled se controlan en los representadores de plataforma de dos
maneras:
La primera es cuando :::no-loc(Xamarin.Forms)::: crea un elemento VideoPlayer . Esto se indica en la
invalidación OnElementChanged del representador si la propiedad NewElement no es null . En este momento,
el representador puede establecer su propio reproductor de vídeo de plataforma a partir del valor inicial de
la propiedad, como se ha definido en VideoPlayer .
Si la propiedad de VideoPlayer cambia más adelante, se llama al método OnElementPropertyChanged del
representador. Esto permite al representador actualizar el reproductor de vídeo de plataforma en función del
nuevo valor de la propiedad.
En las secciones siguientes se explica cómo se controla la propiedad AreTransportControlsEnabled en cada
plataforma.
Controles de reproducción de iOS
La propiedad del elemento AVPlayerViewController de iOS que controla la presentación de controles de transporte
es ShowsPlaybackControls . Así es como esa propiedad se establece en el elemento VideoViewRenderer de iOS:
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
AVPlayerViewController _playerViewController; // solely for ViewController property

public override UIViewController ViewController => _playerViewController;

protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)


{
···
if (args.NewElement != null)
{
···
SetAreTransportControlsEnabled();
···
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(sender, args);

if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
···
}

void SetAreTransportControlsEnabled()
{
((AVPlayerViewController)ViewController).ShowsPlaybackControls =
Element.AreTransportControlsEnabled;
}
···
}
}

La propiedad Element del representador hace referencia a la clase VideoPlayer .


Controlador de elementos multimedia de Android
En Android, la presentación de los controles de transporte exige crear un objeto MediaController y asociarlo al
objeto VideoView . La mecánica se muestra en el método SetAreTransportControlsEnabled :
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
VideoView videoView;
MediaController mediaController; // Used to display transport controls
bool isPrepared;

protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)


{
···
if (args.NewElement != null)
{
···
SetAreTransportControlsEnabled();
···
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(sender, args);

if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
···
}

void SetAreTransportControlsEnabled()
{
if (Element.AreTransportControlsEnabled)
{
mediaController = new MediaController(Context);
mediaController.SetMediaPlayer(videoView);
videoView.SetMediaController(mediaController);
}
else
{
videoView.SetMediaController(null);

if (mediaController != null)
{
mediaController.SetMediaPlayer(null);
mediaController = null;
}
}
}
···
}
}

Propiedad de controles de transporte de UWP


El elemento MediaElement de UWP define una propiedad denominada AreTransportControlsEnabled , de modo que
esa propiedad se establece desde la propiedad VideoPlayer del mismo nombre:
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
SetAreTransportControlsEnabled();
···
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(sender, args);

if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
···
}

void SetAreTransportControlsEnabled()
{
Control.AreTransportControlsEnabled = Element.AreTransportControlsEnabled;
}
···
}
}

Una propiedad más es necesaria para empezar a reproducir un vídeo: se trata de la propiedad fundamental
Source que hace referencia a un archivo de vídeo. La implementación de la propiedad Source se describe en el
siguiente artículo, Reproducción de un vídeo de web.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Reproducción de un vídeo web
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
La clase VideoPlayer define una propiedad Source que se usa para especificar el origen del archivo de vídeo, así
como una propiedad AutoPlay . El valor predeterminado de AutoPlay es true , lo que significa que el vídeo se
debería comenzar a reproducir de forma automática después de establecer Source :

using System;
using :::no-loc(Xamarin.Forms):::;

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
// Source property
public static readonly BindableProperty SourceProperty =
BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(VideoPlayer), null);

[TypeConverter(typeof(VideoSourceConverter))]
public VideoSource Source
{
set { SetValue(SourceProperty, value); }
get { return (VideoSource)GetValue(SourceProperty); }
}

// AutoPlay property
public static readonly BindableProperty AutoPlayProperty =
BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(VideoPlayer), true);

public bool AutoPlay


{
set { SetValue(AutoPlayProperty, value); }
get { return (bool)GetValue(AutoPlayProperty); }
}
···
}
}

La propiedad Source es de tipo VideoSource , que se modela a partir de la clase abstracta ImageSource de :::no-
loc(Xamarin.Forms)::: y sus tres derivadas: UriImageSource , FileImageSource y StreamImageSource . Pero no hay
ninguna opción de hacer streaming disponible para VideoPlayer , porque iOS y Android no admiten la
reproducción de un vídeo desde una secuencia.

Orígenes de vídeo
La clase abstracta VideoSource consta únicamente de tres métodos estáticos que crean las instancias de las tres
clases que se derivan de VideoSource :
namespace FormsVideoLibrary
{
[TypeConverter(typeof(VideoSourceConverter))]
public abstract class VideoSource : Element
{
public static VideoSource FromUri(string uri)
{
return new UriVideoSource() { Uri = uri };
}

public static VideoSource FromFile(string file)


{
return new FileVideoSource() { File = file };
}

public static VideoSource FromResource(string path)


{
return new ResourceVideoSource() { Path = path };
}
}
}

La clase UriVideoSource se usa para especificar un archivo de vídeo descargable con un URI. Define una única
propiedad de tipo string :

namespace FormsVideoLibrary
{
public class UriVideoSource : VideoSource
{
public static readonly BindableProperty UriProperty =
BindableProperty.Create(nameof(Uri), typeof(string), typeof(UriVideoSource));

public string Uri


{
set { SetValue(UriProperty, value); }
get { return (string)GetValue(UriProperty); }
}
}
}

El control de los objetos de tipo UriVideoSource se describe a continuación.


La clase ResourceVideoSource se usa para acceder a los archivos de vídeo que se almacenan como recursos
insertados en la aplicación de plataforma, que también se especifica con una propiedad string :

namespace FormsVideoLibrary
{
public class ResourceVideoSource : VideoSource
{
public static readonly BindableProperty PathProperty =
BindableProperty.Create(nameof(Path), typeof(string), typeof(ResourceVideoSource));

public string Path


{
set { SetValue(PathProperty, value); }
get { return (string)GetValue(PathProperty); }
}
}
}

El control de los objetos de tipo ResourceVideoSource se describe en el artículo Carga de vídeos de recursos de
aplicación. La clase VideoPlayer no tiene ninguna función para cargar un archivo de vídeo almacenado como un
recurso en la biblioteca de .NET Standard.
La clase FileVideoSource se usa para acceder a los archivos de vídeo desde la biblioteca de vídeos del dispositivo.
La única propiedad también es de tipo string :

namespace FormsVideoLibrary
{
public class FileVideoSource : VideoSource
{
public static readonly BindableProperty FileProperty =
BindableProperty.Create(nameof(File), typeof(string), typeof(FileVideoSource));

public string File


{
set { SetValue(FileProperty, value); }
get { return (string)GetValue(FileProperty); }
}
}
}

El control de los objetos de tipo FileVideoSource se describe en el artículo Acceso a la biblioteca de vídeos del
dispositivo.
La clase VideoSource incluye un atributo TypeConverter que hace referencia a VideoSourceConverter :

namespace FormsVideoLibrary
{
[TypeConverter(typeof(VideoSourceConverter))]
public abstract class VideoSource : Element
{
···
}
}

Este convertidor de tipos se invoca cuando la propiedad Source se establece en una cadena en XAML. Esta es la
clase VideoSourceConverter :

namespace FormsVideoLibrary
{
public class VideoSourceConverter : TypeConverter
{
public override object ConvertFromInvariantString(string value)
{
if (!String.IsNullOrWhiteSpace(value))
{
Uri uri;
return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ?
VideoSource.FromUri(value) : VideoSource.FromResource(value);
}

throw new InvalidOperationException("Cannot convert null or whitespace to ImageSource");


}
}
}

El método ConvertFromInvariantString intenta convertir la cadena en un objeto Uri . Si lo consigue, y el esquema


no es file: , el método devuelve un elemento UriVideoSource . De lo contrario, devuelve un elemento
ResourceVideoSource .
Establecimiento del origen de vídeo
El resto de la lógica que implica orígenes de vídeo se implementa en los representadores de cada plataforma. En
las secciones siguientes se muestra cómo los representadores de plataforma reproducen vídeos cuando la
propiedad Source se establece en un objeto UriVideoSource .
Origen de vídeo de iOS
Dos secciones de VideoPlayerRenderer están implicadas en la configuración del origen de vídeo del reproductor
de vídeo. Cuando :::no-loc(Xamarin.Forms)::: crea por primera vez un objeto de tipo VideoPlayer , se llama al
método OnElementChanged con la propiedad NewElement del objeto de argumentos establecida en ese objeto
VideoPlayer . El método OnElementChanged llama a SetSource :

namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
SetSource();
···
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
···
else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
···
}
···
}
}

Más adelante, cuando se cambia la propiedad Source , se llama al método OnElementPropertyChanged con una
propiedad PropertyName de "Source" (Origen), y se vuelve a llamar a SetSource .
Para reproducir un archivo de vídeo en iOS, primero se crea un objeto de tipo AVAsset para encapsular el archivo
de vídeo, que se usa para crear un elemento AVPlayerItem , que después se pasa al objeto AVPlayer . Esta es la
forma en la que el método SetSource controla la propiedad Source cuando es de tipo UriVideoSource :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
AVPlayer player;
AVPlayerItem playerItem;
···
void SetSource()
{
AVAsset asset = null;

if (Element.Source is UriVideoSource)
{
string uri = (Element.Source as UriVideoSource).Uri;

if (!String.IsNullOrWhiteSpace(uri))
{
asset = AVAsset.FromUrl(new NSUrl(uri));
}
}
···
if (asset != null)
{
playerItem = new AVPlayerItem(asset);
}
else
{
playerItem = null;
}

player.ReplaceCurrentItemWithPlayerItem(playerItem);

if (playerItem != null && Element.AutoPlay)


{
player.Play();
}
}
···
}
}

La propiedad AutoPlay no cuenta con ninguna análoga en las clases de vídeo de iOS, por lo que se examina al
final del método SetSource para llamar al método Play en el objeto AVPlayer .
En algunos casos, los vídeos se siguen reproduciendo después de que la página con el elemento VideoPlayer
haya vuelto a la página principal. Para detener el vídeo, ReplaceCurrentItemWithPlayerItem también se establece en
la invalidación de Dispose :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (player != null)
{
player.ReplaceCurrentItemWithPlayerItem(null);
}
}
···
}
}

Origen de vídeo de Android


El objeto VideoPlayerRenderer de Android debe establecer el origen del reproductor de vídeo al crear VideoPlayer
por primera vez y posteriormente cuando cambia la propiedad Source :

namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
SetSource();
···
}
}
···
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
···
else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
···
}
···
}
}

El método SetSource controla los objetos de tipo UriVideoSource mediante una llamada a SetVideoUri en
VideoView con un objeto Uri de Android creado a partir de la cadena de URI. Aquí la clase Uri es completa para
distinguirla de la clase Uri de .NET:
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
void SetSource()
{
isPrepared = false;
bool hasSetSource = false;

if (Element.Source is UriVideoSource)
{
string uri = (Element.Source as UriVideoSource).Uri;

if (!String.IsNullOrWhiteSpace(uri))
{
videoView.SetVideoURI(Android.Net.Uri.Parse(uri));
hasSetSource = true;
}
}
···

if (hasSetSource && Element.AutoPlay)


{
videoView.Start();
}
}
···
}
}

El objeto VideoView de Android no tiene una propiedad AutoPlay correspondiente, por lo que se llama al método
Start si se ha establecido un vídeo nuevo.

Hay una diferencia entre el comportamiento de los representadores de iOS y Android si la propiedad Source de
VideoPlayer se establece en null , o bien si la propiedad Uri de UriVideoSource se establece en null o en una
cadena vacía. Si en el reproductor de vídeo de iOS se está reproduciendo un vídeo, y Source está establecido en
null (o la cadena es null o está en blanco), se llama a ReplaceCurrentItemWithPlayerItem con el valor null . Se
reemplaza el vídeo actual y se detiene la reproducción.
Android no admite una función similar. Si la propiedad Source se establece en null , el método SetSource
simplemente la ignora y se sigue reproduciendo el vídeo actual.
Origen de vídeo de UWP
El objeto MediaElement de UWP define una propiedad AutoPlay , que se controla en el representador como
cualquier otra propiedad:
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
SetSource();
SetAutoPlay();
···
}
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)


{
···
else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
else if (args.PropertyName == VideoPlayer.AutoPlayProperty.PropertyName)
{
SetAutoPlay();
}
···
}
···
}
}

La propiedad SetSource controla un objeto UriVideoSource mediante el establecimiento de la propiedad Source


de MediaElement en un valor Uri de .NET, o bien en null si la propiedad Source de VideoPlayer se establece
en null :
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
···
async void SetSource()
{
bool hasSetSource = false;

if (Element.Source is UriVideoSource)
{
string uri = (Element.Source as UriVideoSource).Uri;

if (!String.IsNullOrWhiteSpace(uri))
{
Control.Source = new Uri(uri);
hasSetSource = true;
}
}
···
if (!hasSetSource)
{
Control.Source = null;
}
}

void SetAutoPlay()
{
Control.AutoPlay = Element.AutoPlay;
}
···
}
}

Establecimiento de un origen de dirección URL


Con la implementación de estas propiedades en los tres representadores, es posible reproducir un vídeo desde un
origen de dirección URL. La página Play Web Video (Reproducir vídeo web) del programa VideoPlayDemos se
define con el siguiente archivo XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.PlayWebVideoPage"
Title="Play Web Video">

<video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />

</ContentPage>

La clase VideoSourceConverter convierte la cadena en un elemento UriVideoSource . Cuando se desplaza a la


página Play Web Video , se empieza a cargar el vídeo y se inicia la reproducción cuando se ha descargado y
almacenado en búfer una cantidad de datos suficiente. El vídeo tiene aproximadamente 10 minutos de duración:
En cada una de las plataformas, los controles de transporte se atenúan si no se usan, pero puede pulsar en el
vídeo para restaurarlos y verlos.
Para impedir que el vídeo se inicie de forma automática, establezca la propiedad AutoPlay en false :

<video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="false" />

Tendrá que presionar el botón Reproducir para iniciar el vídeo.


De forma similar, puede suprimir la presentación de los controles de transporte si establece la propiedad
AreTransportControlsEnabled en false :

<video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />

Si establece las dos propiedades en false , el vídeo no empezará a reproducirse y no habrá ninguna manera de
iniciarlo. Tendría que llamar a Play desde el archivo de código subyacente, o bien crear controles de transporte
propios como se describe en el artículo Implementación de controles de transporte de vídeo personalizados.
En el archivo App.xaml se incluyen recursos para dos vídeos adicionales:
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.App">
<Application.Resources>
<ResourceDictionary>

<video:UriVideoSource x:Key="ElephantsDream"
Uri="https://archive.org/download/ElephantsDream/ed_hd_512kb.mp4" />

<video:UriVideoSource x:Key="BigBuckBunny"
Uri="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
/>

<video:UriVideoSource x:Key="Sintel"
Uri="https://archive.org/download/Sintel/sintel-2048-stereo_512kb.mp4" />

</ResourceDictionary>
</Application.Resources>
</Application>

Para hacer referencia a una de estas películas, puede reemplazar la dirección URL explícita en el archivo
PlayWebVideo.xaml con una extensión de marcado StaticResource , en cuyo caso no se necesitará
VideoSourceConverter para crear el objeto UriVideoSource :

<video:VideoPlayer Source="{StaticResource ElephantsDream}" />

Como alternativa, puede establecer la propiedad Source de un archivo de vídeo en un elemento ListView , como
se describe en el artículo siguiente, Enlace de orígenes de vídeo al reproductor.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Enlazar orígenes de vídeo con el reproductor
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
Cuando la propiedad Source de la vista VideoPlayer se establece en un nuevo archivo de vídeo, el vídeo existente
deja de reproducirse y se inicia el nuevo vídeo. Esto se demuestra mediante la página Seleccionar vídeo web del
ejemplo VideoPlayerDemos . En esta página, se incluye un elemento ListView con los títulos de los tres vídeos a
los que se hace referencia en el archivo App.xaml :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.SelectWebVideoPage"
Title="Select Web Video">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<video:VideoPlayer x:Name="videoPlayer"
Grid.Row="0" />

<ListView Grid.Row="1"
ItemSelected="OnListViewItemSelected">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Elephant's Dream</x:String>
<x:String>Big Buck Bunny</x:String>
<x:String>Sintel</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</ContentPage>

Al seleccionar un vídeo, se ejecuta el controlador de eventos ItemSelected del archivo de código subyacente. El
controlador elimina los espacios en blanco y los apóstrofos del título y usa el resultado como una clave para
obtener uno de los recursos definidos en el archivo App.xaml . Después, el objeto UriVideoSource se establece en
la propiedad Source del elemento VideoPlayer .
namespace VideoPlayerDemos
{
public partial class SelectWebVideoPage : ContentPage
{
public SelectWebVideoPage()
{
InitializeComponent();
}

void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)


{
if (args.SelectedItem != null)
{
string key = ((string)args.SelectedItem).Replace(" ", "").Replace("'", "");
videoPlayer.Source = (UriVideoSource)Application.Current.Resources[key];
}
}
}
}

Cuando se carga la primera página, no se selecciona ningún elemento en ListView , por lo que tendrá que
seleccionar uno para que el vídeo empiece a reproducirse:

La propiedad Source de VideoPlayer se complementa con una propiedad enlazable, lo que quiere decir que
puede ser el objetivo de un enlace de datos. Esto se demuestra mediante la página Enlazar a VideoPlayer . El
marcado del archivo es compatible con la clase siguiente BindToVideoPlayer.xaml , que encapsula un título para
un vídeo y un objeto correspondiente VideoSource :

namespace VideoPlayerDemos
{
public class VideoInfo
{
public string DisplayName { set; get; }

public VideoSource VideoSource { set; get; }

public override string ToString()


{
return DisplayName;
}
}
}
El elemento ListView del archivo BindToVideoPlayer.xaml contiene una matriz de estos objetos VideoInfo y
cada uno se inicializa con un título del vídeo y el objeto UriVideoSource del diccionario de recursos en App.xaml :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:VideoPlayerDemos"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.BindToVideoPlayerPage"
Title="Bind to VideoPlayer">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<video:VideoPlayer x:Name="videoPlayer"
Grid.Row="0"
Source="{Binding Source={x:Reference listView},
Path=SelectedItem.VideoSource}" />

<ListView x:Name="listView"
Grid.Row="1">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:VideoInfo}">
<local:VideoInfo DisplayName="Elephant's Dream"
VideoSource="{StaticResource ElephantsDream}" />

<local:VideoInfo DisplayName="Big Buck Bunny"


VideoSource="{StaticResource BigBuckBunny}" />

<local:VideoInfo DisplayName="Sintel"
VideoSource="{StaticResource Sintel}" />
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</ContentPage>

La propiedad Source del elemento VideoPlayer se enlaza al elemento ListView . El elemento Path del enlace se
especifica como SelectedItem.VideoSource , que es un trazado compuesto formado por dos propiedades:
SelectedItem es una propiedad de ListView . El elemento seleccionado es del tipo VideoInfo , que tiene una
propiedad VideoSource .
Como con la primera página Seleccionar vídeo web , de manera inicial no se selecciona ningún elemento desde
ListView , por lo que necesita seleccionar uno de los vídeos para que empiece a reproducirse.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Carga de vídeos de recursos de aplicación
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Los representadores personalizados para la vista VideoPlayer son capaces de reproducir archivos de vídeo que se
han insertado en los proyectos de cada plataforma como recursos de la aplicación. Pero la versión actual de
VideoPlayer no puede acceder a los recursos insertados en una biblioteca de .NET Standard.

Para cargar estos recursos, cree una instancia de ResourceVideoSource estableciendo la propiedad Path en el
nombre de archivo (o la carpeta y el nombre de archivo) del recurso. Como alternativa, puede llamar al método
VideoSource.FromResource estático para hacer referencia al recurso. Después, establezca el objeto
ResourceVideoSource en la propiedad Source de VideoPlayer .

Almacenamiento de los archivos de vídeo


El almacenamiento de un archivo de vídeo en el proyecto de la plataforma es diferente para cada plataforma.
Recursos de vídeo de iOS
En un proyecto de iOS, puede almacenar un vídeo en la carpeta Recursos , o bien en una subcarpeta de la carpeta
Recursos . El archivo de vídeo debe tener un elemento Build Action de tipo BundleResource . Establezca la
propiedad Path de ResourceVideoSource en el nombre de archivo, por ejemplo MiArchivo.mp4 para un archivo
en la carpeta Recursos , o bien MiCarpeta/MiArchivo.mp4 , donde MiCarpeta es una subcarpeta de
Recursos .
En la solución VideoPlayerDemos , el proyecto VideoPlayerDemos.iOS contiene una subcarpeta de Recursos
denominada Videos que contiene un archivo denominado iOSApiVideo.mp4 . Se trata de un breve vídeo en el
que se muestra cómo usar el sitio web de Xamarin para encontrar documentación para la clase
AVPlayerViewController de iOS.

Recursos de vídeo de Android


En un proyecto de Android, los vídeos se deben almacenar en una subcarpeta de Recursos denominada raw . La
carpeta raw no puede contener subcarpetas. Asigne al archivo de vídeo un elemento Build Action de tipo
AndroidResource . Establezca la propiedad Path de ResourceVideoSource en el nombre de archivo, por ejemplo,
MiArchivo.mp4 .
El proyecto VideoPlayerDemos.Android contiene una subcarpeta de Recursos denominada raw , que contiene
un archivo denominado AndroidApiVideo.mp4 .
Recursos de vídeo de UWP
En un proyecto para Plataforma Universal de Windows, puede almacenar vídeos en cualquier carpeta del proyecto.
Asigne al archivo un elemento Build Action de tipo Content . Establezca la propiedad Path de
ResourceVideoSource en la carpeta y el nombre de archivo, por ejemplo, MiCarpeta/MiVideo.mp4 .

El proyecto VideoPlayerDemos.UWP contiene una carpeta denominada Videos con el archivo


UWPApiVideo.mp4 .

Carga de los archivos de vídeo


Cada una de las clases de representador de plataforma contiene código en su método SetSource para cargar los
archivos de vídeo almacenados como recursos.
Carga de recursos de iOS
La versión de iOS de VideoPlayerRenderer usa el método GetUrlForResource de NSBundle para cargar el recurso.
La ruta de acceso completa se debe dividir en un nombre de archivo, una extensión y un directorio. En el código se
usa la clase Path del espacio de nombres System.IO de .NET para dividir la ruta de acceso en estos componentes:

namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
void SetSource()
{
AVAsset asset = null;
···
else if (Element.Source is ResourceVideoSource)
{
string path = (Element.Source as ResourceVideoSource).Path;

if (!String.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
···
}
···
}
}

Carga de recursos de Android


El elemento VideoPlayerRenderer de Android usa el nombre de paquete y el nombre de archivo para construir un
objeto Uri . El nombre del paquete es el nombre de la aplicación, en este caso VideoPlayerDemos.Android ,
que se puede obtener de la propiedad estática Context.PackageName . Después, el objeto Uri resultante se pasa al
método SetVideoURI de VideoView :
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
void SetSource()
{
isPrepared = false;
bool hasSetSource = false;
···
else if (Element.Source is ResourceVideoSource)
{
string package = Context.PackageName;
string path = (Element.Source as ResourceVideoSource).Path;

if (!String.IsNullOrWhiteSpace(path))
{
string filename = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
string uri = "android.resource://" + package + "/raw/" + filename;
videoView.SetVideoURI(Android.Net.Uri.Parse(uri));
hasSetSource = true;
}
}
···
}
···
}
}

Carga de recursos de UWP


El elemento VideoPlayerRenderer de UWP crea un objeto Uri para la ruta de acceso y lo establece en la
propiedad Source de MediaElement :

namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
···
async void SetSource()
{
bool hasSetSource = false;
···
else if (Element.Source is ResourceVideoSource)
{
string path = "ms-appx:///" + (Element.Source as ResourceVideoSource).Path;

if (!String.IsNullOrWhiteSpace(path))
{
Control.Source = new Uri(path);
hasSetSource = true;
}
}
}
···
}
}

Reproducción del archivo de recursos


En la página Reproducir recurso de vídeo de la solución VideoPlayerDemos se usa la clase OnPlatform para
especificar el archivo de vídeo para cada plataforma:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.PlayVideoResourcePage"
Title="Play Video Resource">
<video:VideoPlayer>
<video:VideoPlayer.Source>
<video:ResourceVideoSource>
<video:ResourceVideoSource.Path>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="Videos/iOSApiVideo.mp4" />
<On Platform="Android" Value="AndroidApiVideo.mp4" />
<On Platform="UWP" Value="Videos/UWPApiVideo.mp4" />
</OnPlatform>
</video:ResourceVideoSource.Path>
</video:ResourceVideoSource>
</video:VideoPlayer.Source>
</video:VideoPlayer>
</ContentPage>

Si el recurso de iOS se almacena en la carpeta Recursos , y si el recurso de UWP se almacena en la carpeta raíz del
proyecto, puede usar el mismo nombre de archivo para cada plataforma. En ese caso, puede establecer ese
nombre directamente en la propiedad Source de VideoPlayer .
Esta es la ejecución de la página:

Ya ha visto cómo cargar vídeos desde un URI web y cómo reproducir recursos insertados. Además, puede cargar
vídeos desde la biblioteca de vídeos del dispositivo.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Acceder a la biblioteca de vídeos del dispositivo
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Los dispositivos móviles y los equipos de escritorio más modernos tienen la capacidad de grabar vídeos mediante
la cámara que llevan incorporada. Los vídeos que un usuario crea se almacenan como archivos en el dispositivo.
Estos archivos se pueden recuperar de la biblioteca de imágenes, y la clase VideoPlayer puede reproducirlos igual
que cualquier otro vídeo.

El servicio de dependencia del selector de fotos


Cada una de las plataformas incluye una utilidad que permite al usuario seleccionar una foto o un vídeo de la
biblioteca de imágenes del dispositivo. El primer paso para reproducir un vídeo de la biblioteca de imágenes del
dispositivo consiste en crear un servicio de dependencia que invoque al selector de imagen en cada plataforma. El
servicio de dependencia descrito anteriormente es muy similar al que se define en el artículo Seleccionar una
foto de la biblioteca de imágenes , salvo que el selector de vídeo devuelve un nombre de archivo en lugar de
un objeto Stream .
El proyecto de la biblioteca de .NET Standard define una interfaz denominada IVideoPicker para el servicio de
dependencia:

namespace FormsVideoLibrary
{
public interface IVideoPicker
{
Task<string> GetVideoFileAsync();
}
}

Cada una de las plataformas contiene una clase denominada VideoPicker que implementa esta interfaz.
El selector de vídeo de iOS
VideoPicker de iOS utiliza UIImagePickerController para acceder a la biblioteca de imágenes y especifica que
dicho acceso debe restringirse a los vídeos (denominados "películas") de la propiedad MediaType de iOS. Tenga en
cuenta que VideoPicker implementa explícitamente la interfaz de IVideoPicker . Tenga en cuenta también el
atributo Dependency que identifica esta clase como un servicio de dependencia. Estos son los dos requisitos que
permiten a :::no-loc(Xamarin.Forms)::: encontrar el servicio de dependencia en el proyecto de la plataforma:
using System;
using System.Threading.Tasks;
using UIKit;
using :::no-loc(Xamarin.Forms):::;

[assembly: Dependency(typeof(FormsVideoLibrary.iOS.VideoPicker))]

namespace FormsVideoLibrary.iOS
{
public class VideoPicker : IVideoPicker
{
TaskCompletionSource<string> taskCompletionSource;
UIImagePickerController imagePicker;

public Task<string> GetVideoFileAsync()


{
// Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.SavedPhotosAlbum,
MediaTypes = new string[] { "public.movie" }
};

// Set event handlers


imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;

// Present UIImagePickerController;
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);

// Return Task object


taskCompletionSource = new TaskCompletionSource<string>();
return taskCompletionSource.Task;
}

void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)


{
if (args.MediaType == "public.movie")
{
taskCompletionSource.SetResult(args.MediaUrl.AbsoluteString);
}
else
{
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}

void OnImagePickerCancelled(object sender, EventArgs args)


{
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
}
}

El selector de vídeo de Android


La implementación de Android de IVideoPicker requiere un método de devolución de llamada que forma parte
de la actividad de la aplicación. Por ese motivo, la clase MainActivity define dos propiedades, un campo y un
método de devolución de llamada:
namespace VideoPlayerDemos.Droid
{
···
public class MainActivity : global:::::no-loc(Xamarin.Forms):::.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Current = this;
···
}

// Field, properties, and method for Video Picker


public static MainActivity Current { private set; get; }

public static readonly int PickImageId = 1000;

public TaskCompletionSource<string> PickImageTaskCompletionSource { set; get; }

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)


{
base.OnActivityResult(requestCode, resultCode, data);

if (requestCode == PickImageId)
{
if ((resultCode == Result.Ok) && (data != null))
{
// Set the filename as the completion of the Task
PickImageTaskCompletionSource.SetResult(data.DataString);
}
else
{
PickImageTaskCompletionSource.SetResult(null);
}
}
}
}
}

El método OnCreate en MainActivity almacena su propia instancia en la propiedad Current estática. Esto
permite a la implementación de IVideoPicker obtener la instancia de MainActivity para iniciar el selector
Seleccione vídeo :
using System;
using System.Threading.Tasks;
using Android.Content;
using :::no-loc(Xamarin.Forms):::;

// Need application's MainActivity


using VideoPlayerDemos.Droid;

[assembly: Dependency(typeof(FormsVideoLibrary.Droid.VideoPicker))]

namespace FormsVideoLibrary.Droid
{
public class VideoPicker : IVideoPicker
{
public Task<string> GetVideoFileAsync()
{
// Define the Intent for getting images
Intent intent = new Intent();
intent.SetType("video/*");
intent.SetAction(Intent.ActionGetContent);

// Get the MainActivity instance


MainActivity activity = MainActivity.Current;

// Start the picture-picker activity (resumes in MainActivity.cs)


activity.StartActivityForResult(
Intent.CreateChooser(intent, "Select Video"),
MainActivity.PickImageId);

// Save the TaskCompletionSource object as a MainActivity property


activity.PickImageTaskCompletionSource = new TaskCompletionSource<string>();

// Return Task object


return activity.PickImageTaskCompletionSource.Task;
}
}
}

Las adiciones al objeto MainActivity son el único código de la solución VideoPlayerDemos en que el código de
aplicación normal debe modificarse para admitir las clases FormsVideoLibrary .
El selector de vídeo de UWP
La implementación de UWP de la interfaz de IVideoPicker utiliza el FileOpenPicker de UWP. Inicia la búsqueda
de archivos con la biblioteca de imágenes y restringe los tipos de archivo a MP4 y WMV (vídeo de Windows
Media):
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Pickers;
using :::no-loc(Xamarin.Forms):::;

[assembly: Dependency(typeof(FormsVideoLibrary.UWP.VideoPicker))]

namespace FormsVideoLibrary.UWP
{
public class VideoPicker : IVideoPicker
{
public async Task<string> GetVideoFileAsync()
{
// Create and initialize the FileOpenPicker
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.PicturesLibrary
};

openPicker.FileTypeFilter.Add(".wmv");
openPicker.FileTypeFilter.Add(".mp4");

// Get a file and return the path


StorageFile storageFile = await openPicker.PickSingleFileAsync();
return storageFile?.Path;
}
}
}

Invocar el servicio de dependencia


En la página Reproducir vídeo de la biblioteca del programa VideoPlayerDemos se muestra cómo utilizar el
servicio de dependencia del selector de vídeo. El archivo XAML contiene una instancia de VideoPlayer y un
Button con la etiqueta Mostrar la biblioteca de vídeos :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.PlayLibraryVideoPage"
Title="Play Library Video">
<StackLayout>
<video:VideoPlayer x:Name="videoPlayer"
VerticalOptions="FillAndExpand" />

<Button Text="Show Video Library"


Margin="10"
HorizontalOptions="Center"
Clicked="OnShowVideoLibraryClicked" />
</StackLayout>
</ContentPage>

El archivo de código subyacente contiene el controlador de Clicked para el Button . Para invocar el servicio de
dependencia es necesario llamar a DependencyService.Get para obtener la implementación de una interfaz de
IVideoPicker en el proyecto de plataforma. A continuación, se llama al método GetVideoFileAsync en esa
instancia:
namespace VideoPlayerDemos
{
public partial class PlayLibraryVideoPage : ContentPage
{
public PlayLibraryVideoPage()
{
InitializeComponent();
}

async void OnShowVideoLibraryClicked(object sender, EventArgs args)


{
Button btn = (Button)sender;
btn.IsEnabled = false;

string filename = await DependencyService.Get<IVideoPicker>().GetVideoFileAsync();

if (!String.IsNullOrWhiteSpace(filename))
{
videoPlayer.Source = new FileVideoSource
{
File = filename
};
}

btn.IsEnabled = true;
}
}
}

Posteriormente, el controlador de Clicked utiliza ese nombre de archivo para crear un objeto FileVideoSource y
establecerlo en la propiedad Source del VideoPlayer .
Cada una de las clases VideoPlayerRenderer contiene código en su método SetSource para objetos de tipo
FileVideoSource . Se muestran a continuación:

Control de archivos de iOS


La versión de iOS de VideoPlayerRenderer procesa los objetos FileVideoSource mediante el uso del método
Asset.FromUrl estático con el nombre de archivo. Esto crea un objeto AVAsset que representa el archivo de la
biblioteca de imágenes del dispositivo:
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
void SetSource()
{
AVAsset asset = null;
···
else if (Element.Source is FileVideoSource)
{
string uri = (Element.Source as FileVideoSource).File;

if (!String.IsNullOrWhiteSpace(uri))
{
asset = AVAsset.FromUrl(new NSUrl(uri));
}
}
···
}
···
}
}

Control de archivos de Android


Al procesar los objetos de tipo FileVideoSource , la implementación de Android de VideoPlayerRenderer utiliza el
método SetVideoPath de VideoView para especificar el archivo de biblioteca de imágenes del dispositivo:

namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
void SetSource()
{
isPrepared = false;
bool hasSetSource = false;
···
else if (Element.Source is FileVideoSource)
{
string filename = (Element.Source as FileVideoSource).File;

if (!String.IsNullOrWhiteSpace(filename))
{
videoView.SetVideoPath(filename);
hasSetSource = true;
}
}
···
}
···
}
}

Control de archivos de UWP


Al controlar los objetos de tipo FileVideoSource , la implementación de UWP del método SetSource debe crear
un objeto StorageFile , abrir ese archivo para leerlo y pasar el objeto de secuencia al método SetSource del
MediaElement :
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
async void SetSource()
{
bool hasSetSource = false;
···
else if (Element.Source is FileVideoSource)
{
// Code requires Pictures Library in Package.appxmanifest Capabilities to be enabled
string filename = (Element.Source as FileVideoSource).File;

if (!String.IsNullOrWhiteSpace(filename))
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync();
Control.SetSource(stream, storageFile.ContentType);
hasSetSource = true;
}
}
···
}
···
}
}

En todas las plataformas, el vídeo empieza a reproducirse casi inmediatamente después de establecer el origen de
vídeo porque el archivo se encuentra en el dispositivo y no debe descargarse.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Seleccionar una foto de la biblioteca de imágenes
Controles de transporte de vídeo personalizados
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
Los controles de transporte de un reproductor de vídeo son los botones que realizan las funciones Reproducir ,
Pausa y Detener . Estos botones suelen identificarse con iconos conocidos en lugar de texto, y las funciones
Reproducir y Pausa suelen combinarse en un mismo botón.
De forma predeterminada, el elemento VideoPlayer muestra controles de transporte compatibles con cada
plataforma. Al establecer la propiedad AreTransportControlsEnabled en false , se eliminan estos controles.
Después, puede controlar el elemento VideoPlayer mediante programación, o bien puede eliminar sus propios
controles de transporte.

Métodos Reproducir, Pausa y Detener


La clase VideoPlayer define tres métodos (llamados Play , Pause y Stop ) que se implementan mediante la
activación de eventos:

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
public event EventHandler PlayRequested;

public void Play()


{
PlayRequested?.Invoke(this, EventArgs.Empty);
}

public event EventHandler PauseRequested;

public void Pause()


{
PauseRequested?.Invoke(this, EventArgs.Empty);
}

public event EventHandler StopRequested;

public void Stop()


{
StopRequested?.Invoke(this, EventArgs.Empty);
}
}
}

Los controladores de eventos para estos eventos se establecen mediante la clase VideoPlayerRenderer en cada
plataforma, como se muestra a continuación:
Implementaciones de transporte de iOS
La versión de iOS de VideoPlayerRenderer usa el método OnElementChanged para establecer controladores para
estos tres eventos cuando la propiedad NewElement no es null y elimina la asociación de los controladores de
eventos cuando OldElement no es null :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
AVPlayer player;
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
args.NewElement.PlayRequested += OnPlayRequested;
args.NewElement.PauseRequested += OnPauseRequested;
args.NewElement.StopRequested += OnStopRequested;
}

if (args.OldElement != null)
{
···
args.OldElement.PlayRequested -= OnPlayRequested;
args.OldElement.PauseRequested -= OnPauseRequested;
args.OldElement.StopRequested -= OnStopRequested;
}
}
···
// Event handlers to implement methods
void OnPlayRequested(object sender, EventArgs args)
{
player.Play();
}

void OnPauseRequested(object sender, EventArgs args)


{
player.Pause();
}

void OnStopRequested(object sender, EventArgs args)


{
player.Pause();
player.Seek(new CMTime(0, 1));
}
}
}

Para implementar los controladores de eventos, se llama a métodos en el objeto AVPlayer . Como no hay ningún
método Stop para AVPlayer , para simularlo, se pausa el vídeo y se mueve la posición al principio.
Implementaciones de transporte de Android
La implementación de Android es similar a la implementación de iOS. Los controladores para las tres funciones se
establecen cuando NewElement no es null y, cuando OldElement no es null , se elimina la asociación:
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
VideoView videoView;
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
args.NewElement.PlayRequested += OnPlayRequested;
args.NewElement.PauseRequested += OnPauseRequested;
args.NewElement.StopRequested += OnStopRequested;
}

if (args.OldElement != null)
{
···
args.OldElement.PlayRequested -= OnPlayRequested;
args.OldElement.PauseRequested -= OnPauseRequested;
args.OldElement.StopRequested -= OnStopRequested;
}
}
···
void OnPlayRequested(object sender, EventArgs args)
{
videoView.Start();
}

void OnPauseRequested(object sender, EventArgs args)


{
videoView.Pause();
}

void OnStopRequested(object sender, EventArgs args)


{
videoView.StopPlayback();
}
}
}

Estas tres funciones llaman a métodos definidos por VideoView .


Implementaciones de transporte de UWP
La implementación de UWP de las tres funciones de transporte es muy similar a las implementaciones de iOS y
Android:
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
args.NewElement.PlayRequested += OnPlayRequested;
args.NewElement.PauseRequested += OnPauseRequested;
args.NewElement.StopRequested += OnStopRequested;
}

if (args.OldElement != null)
{
···
args.OldElement.PlayRequested -= OnPlayRequested;
args.OldElement.PauseRequested -= OnPauseRequested;
args.OldElement.StopRequested -= OnStopRequested;
}
}
···
// Event handlers to implement methods
void OnPlayRequested(object sender, EventArgs args)
{
Control.Play();
}

void OnPauseRequested(object sender, EventArgs args)


{
Control.Pause();
}

void OnStopRequested(object sender, EventArgs args)


{
Control.Stop();
}
}
}

Estado del reproductor de vídeo


Implementar las funciones Reproducir , Pausa y Detener no es suficiente para admitir los controles de
transporte. Con frecuencia, los comandos Reproducir y Pausa se implementan con el mismo botón, que cambia
su apariencia para indicar si el vídeo está reproduciéndose o en pausa en ese momento. Además, el botón no tiene
que habilitarse si el vídeo aún no se ha cargado.
Estos requisitos implican que el reproductor de vídeo necesita mostrar un estado actual que indique si está
reproduciéndose o en pausa, o bien si aún no está preparado para reproducir un vídeo. (Cada plataforma también
admite propiedades que indican si el vídeo se puede pausar o se puede mover a una nueva posición, pero estas
propiedades pueden aplicarse para la transmisión por streaming de vídeo, en lugar de aplicarlas en archivos de
vídeo, por lo que no se admiten en el elemento VideoPlayer que se describe aquí).
En el proyecto VideoPlayerDemos , se incluye una enumeración VideoStatus con tres miembros:
namespace FormsVideoLibrary
{
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
}

La clase VideoPlayer define una propiedad enlazable de solo lectura denominada Status del tipo VideoStatus .
Esta propiedad se define como de solo lectura porque únicamente tiene que establecerse desde el representador
de la plataforma:

using System;
using :::no-loc(Xamarin.Forms):::;

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
// Status read-only property
private static readonly BindablePropertyKey StatusPropertyKey =
BindableProperty.CreateReadOnly(nameof(Status), typeof(VideoStatus), typeof(VideoPlayer),
VideoStatus.NotReady);

public static readonly BindableProperty StatusProperty = StatusPropertyKey.BindableProperty;

public VideoStatus Status


{
get { return (VideoStatus)GetValue(StatusProperty); }
}

VideoStatus IVideoPlayerController.Status
{
set { SetValue(StatusPropertyKey, value); }
get { return Status; }
}
···
}
}

Normalmente, una propiedad enlazable de solo lectura tendría un descriptor de acceso set privado en la
propiedad Status para permitirle establecerlo en la clase. Pero, para un elemento View derivado admitido por
representadores, la propiedad tiene que establecerse desde fuera de la clase, pero solo por el representador de la
plataforma.
Por este motivo, se define otra propiedad con el nombre IVideoPlayerController.Status . Esta es una
implementación de interfaz explícita y es posible mediante la interfaz IVideoPlayerController que implementa la
clase VideoPlayer :
namespace FormsVideoLibrary
{
public interface IVideoPlayerController
{
VideoStatus Status { set; get; }

TimeSpan Duration { set; get; }


}
}

Esto es similar a la forma en que el control WebView usa la interfaz IWebViewController para implementar las
propiedades CanGoBack y CanGoForward . (Para obtener más información, vea el código fuente de WebView y sus
representadores).
Esto permite que una clase externa a VideoPlayer pueda establecer la propiedad Status al hacer referencia a la
interfaz IVideoPlayerController . (Verá el código en breve). La propiedad también se puede establecer desde otras
clases, pero es poco probable que se establezca por error. Aún más importante, la propiedad Status no se puede
establecer mediante un enlace de datos.
Para ayudar a los representadores a mantener actualizada la propiedad Status , la clase VideoPlayer define un
evento UpdateStatus que se desencadena cada décima de segundo:

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
public event EventHandler UpdateStatus;

public VideoPlayer()
{
Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
{
UpdateStatus?.Invoke(this, EventArgs.Empty);
return true;
});
}
···
}
}

Ajuste de estado de iOS


El elemento VideoPlayerRenderer de iOS establece un controlador para el evento UpdateStatus (y anula la
asociación de ese controlador cuando el elemento VideoPlayer subyacente está ausente) y usa el controlador
para establecer la propiedad Status :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
···
if (args.NewElement != null)
{
···
args.NewElement.UpdateStatus += OnUpdateStatus;
···
}

if (args.OldElement != null)
{
args.OldElement.UpdateStatus -= OnUpdateStatus;
···
}
}
···
void OnUpdateStatus(object sender, EventArgs args)
{
VideoStatus videoStatus = VideoStatus.NotReady;

switch (player.Status)
{
case AVPlayerStatus.ReadyToPlay:
switch (player.TimeControlStatus)
{
case AVPlayerTimeControlStatus.Playing:
videoStatus = VideoStatus.Playing;
break;

case AVPlayerTimeControlStatus.Paused:
videoStatus = VideoStatus.Paused;
break;
}
break;
}
}

((IVideoPlayerController)Element).Status = videoStatus;
···
}
···
}
}

Se debe acceder a dos propiedades de AVPlayer : la propiedad Status de tipo AVPlayerStatus y la propiedad
TimeControlStatus de tipo AVPlayerTimeControlStatus . Tenga en cuenta que la propiedad Element (que es el
objeto VideoPlayer ) tiene que transmitir a IVideoPlayerController para establecer la propiedad Status .
Ajuste de estado de Android
La propiedad IsPlaying del elemento VideoView de Android es un operador booleano que solo indica si el vídeo
está reproduciéndose o en pausa. Para determinar si el elemento VideoView aún no puede reproducir ni pausar el
vídeo, es necesario controlar el evento Prepared de VideoView . Estos dos controladores se establecen en el
método OnElementChanged y se anula la asociación durante el reemplazo Dispose :
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
VideoView videoView;
···
bool isPrepared;

protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)


{
···
if (args.NewElement != null)
{
if (Control == null)
{
···
videoView.Prepared += OnVideoViewPrepared;
···
}
···
args.NewElement.UpdateStatus += OnUpdateStatus;
···
}

if (args.OldElement != null)
{
args.OldElement.UpdateStatus -= OnUpdateStatus;
···
}

protected override void Dispose(bool disposing)


{
if (Control != null && videoView != null)
{
videoView.Prepared -= OnVideoViewPrepared;
}
if (Element != null)
{
Element.UpdateStatus -= OnUpdateStatus;
}

base.Dispose(disposing);
}
···
}
}

El controlador UpdateStatus usa el campo isPrepared (establecido en el controlador Prepared ) y la propiedad


IsPlaying para establecer la propiedad Status :
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
VideoView videoView;
···
bool isPrepared;
···
void OnVideoViewPrepared(object sender, EventArgs args)
{
isPrepared = true;
···
}
···
void OnUpdateStatus(object sender, EventArgs args)
{
VideoStatus status = VideoStatus.NotReady;

if (isPrepared)
{
status = videoView.IsPlaying ? VideoStatus.Playing : VideoStatus.Paused;
}
···
}
···
}
}

Ajuste de estado de UWP


El elemento VideoPlayerRenderer de UWP usa el evento UpdateStatus , pero no lo necesita para establecer la
propiedad Status . El elemento MediaElement define un evento CurrentStateChanged que permite notificar al
representador si se cambia la propiedad CurrentState . En el reemplazo Dispose , se anula la asociación de la
propiedad:
namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
base.OnElementChanged(args);

if (args.NewElement != null)
{
if (Control == null)
{
···
mediaElement.CurrentStateChanged += OnMediaElementCurrentStateChanged;
};
···
}
···
}

protected override void Dispose(bool disposing)


{
if (Control != null)
{
···
Control.CurrentStateChanged -= OnMediaElementCurrentStateChanged;
}

base.Dispose(disposing);
}
···
}
}

La propiedad CurrentState es el tipo MediaElementState y se asigna fácilmente a VideoStatus :

namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
···
void OnMediaElementCurrentStateChanged(object sender, RoutedEventArgs args)
{
VideoStatus videoStatus = VideoStatus.NotReady;

switch (Control.CurrentState)
{
case MediaElementState.Playing:
videoStatus = VideoStatus.Playing;
break;

case MediaElementState.Paused:
case MediaElementState.Stopped:
videoStatus = VideoStatus.Paused;
break;
}

((IVideoPlayerController)Element).Status = videoStatus;
}
···
}
}
Botones Reproducir, Pausa y Detener
Usar caracteres Unicode para imágenes simbólicas de Reproducir , Pausar y Detener es problemático. En la
sección Aspectos técnicos varios del estándar Unicode, se definen tres caracteres de símbolos que parecen
apropiados para esta finalidad. Estos son:
0x23F5 (triángulo que apunta hacia la derecha de tamaño medio y color negro) o para Reproducir
0x23F8 (barra doble vertical) o ⏸ para Pausa
0x23F9 (cuadrado negro) o ⏹ para Detener
Independientemente de cómo se muestren estos símbolos en el explorador (ya que en cada explorador se
controlan de formas distintas), no se muestran de forma coherente en las plataformas que admite :::no-
loc(Xamarin.Forms):::. En dispositivos con iOS y UWP, los caracteres Pausa y Detener tienen una apariencia
gráfica, pero con un fondo 3D azul y un primer plano blanco. Esto no ocurre en Android, donde el símbolo es
simplemente azul. Pero el punto de código 0x23F5 de Reproducir no tiene la misma apariencia en UWP y,
además, ni siquiera se admite en iOS y Android.
Por ese motivo, el punto de código 0x23F5 no se puede usar para Reproducir . Un sustituto adecuado es:
0x25B6 (triángulo que apunta hacia la derecha negro) o ▶ para Reproducir
Esto se admite en todas las plataformas, pero se trata de un triángulo negro sin formato que no tiene la apariencia
3D de Pausa y Detener . Una posibilidad es seguir el punto de código 0x25B6 con un código de variante:
0x25B6 seguido de 0xFE0F (variante 16) o ▶
️ para Reproducir
Esto es lo que se usa en el marcado que se muestra a continuación. En iOS, proporciona al símbolo de
Reproducir la misma apariencia 3D que los botones Pausa y Detener , pero la variante no funciona en Android
ni en UWP.
La página Transpor te personalizado establece la propiedad AreTranspor tControlsEnabled en false e
incluye un elemento ActivityIndicator mostrado cuando el vídeo está cargándose, además de dos botones. Los
objetos DataTrigger se usan para habilitar y deshabilitar el elemento ActivityIndicator y los botones, así como
para cambiar el primer botón entre Reproducir y Pausa :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.CustomTransportPage"
Title="Custom Transport">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<video:VideoPlayer x:Name="videoPlayer"
Grid.Row="0"
AutoPlay="False"
AreTransportControlsEnabled="False"
Source="{StaticResource BigBuckBunny}" />

<ActivityIndicator Grid.Row="0"
Color="Gray"
IsVisible="False">
<ActivityIndicator.Triggers>
<DataTrigger TargetType="ActivityIndicator"
Binding="{Binding Source={x:Reference videoPlayer},
Path=Status}"
Value="{x:Static video:VideoStatus.NotReady}">
<Setter Property="IsVisible" Value="True" />
<Setter Property="IsRunning" Value="True" />
<Setter Property="IsRunning" Value="True" />
</DataTrigger>
</ActivityIndicator.Triggers>
</ActivityIndicator>

<StackLayout Grid.Row="1"
Orientation="Horizontal"
Margin="0, 10"
BindingContext="{x:Reference videoPlayer}">

<Button Text="&#x25B6;&#xFE0F; Play"


HorizontalOptions="CenterAndExpand"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static video:VideoStatus.Playing}">
<Setter Property="Text" Value="&#x23F8; Pause" />
</DataTrigger>

<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static video:VideoStatus.NotReady}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>

<Button Text="&#x23F9; Stop"


HorizontalOptions="CenterAndExpand"
Clicked="OnStopButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static video:VideoStatus.NotReady}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
</StackLayout>
</Grid>
</ContentPage>

Los desencadenadores de datos se describen con detalle en el artículo Desencadenadores de datos.


El archivo de código subyacente contiene los controladores para los eventos Clicked del botón:
namespace VideoPlayerDemos
{
public partial class CustomTransportPage : ContentPage
{
public CustomTransportPage()
{
InitializeComponent();
}

void OnPlayPauseButtonClicked(object sender, EventArgs args)


{
if (videoPlayer.Status == VideoStatus.Playing)
{
videoPlayer.Pause();
}
else if (videoPlayer.Status == VideoStatus.Paused)
{
videoPlayer.Play();
}
}

void OnStopButtonClicked(object sender, EventArgs args)


{
videoPlayer.Stop();
}
}
}

Como AutoPlay se establece en false en el archivo CustomTranspor t.xaml , necesita pulsar el botón
Reproducir cuando este se habilite para iniciar el vídeo. Los botones se definen de forma que los caracteres
Unicode indicados anteriormente se muestren con sus equivalentes de texto. Los botones tienen una apariencia
coherente en todas las plataformas cuando el vídeo está reproduciéndose:

Pero, en Android y UWP, el botón Reproducir tiene una apariencia muy distinta cuando el vídeo está pausado:
En una aplicación de producción, es probable que quiera usar sus propias imágenes de mapa de bits para los
botones con el fin de conseguir una uniformidad visual.

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Barra de posición de vídeo personalizada
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
Los controles de transporte que cada plataforma implementa incluyen una barra de posición. Esta barra es similar
a un control deslizante o una barra de desplazamiento y muestra la ubicación actual del vídeo dentro de su
duración total. Además, el usuario puede manipular la barra de posición para avanzar o retroceder a una nueva
posición en el vídeo.
En este artículo se muestra cómo puede implementar su propia barra de posición personalizada.

La propiedad Duration
Un elemento de información que VideoPlayer necesita para admitir una barra de posición personalizada es la
duración del vídeo. VideoPlayer define una propiedad Duration de solo lectura de tipo TimeSpan :

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
// Duration read-only property
private static readonly BindablePropertyKey DurationPropertyKey =
BindableProperty.CreateReadOnly(nameof(Duration), typeof(TimeSpan), typeof(VideoPlayer), new
TimeSpan(),
propertyChanged: (bindable, oldValue, newValue) => ((VideoPlayer)bindable).SetTimeToEnd());

public static readonly BindableProperty DurationProperty = DurationPropertyKey.BindableProperty;

public TimeSpan Duration


{
get { return (TimeSpan)GetValue(DurationProperty); }
}

TimeSpan IVideoPlayerController.Duration
{
set { SetValue(DurationPropertyKey, value); }
get { return Duration; }
}
···
}
}

Al igual que la propiedad Status descrita en el artículo anterior, esta propiedad Duration es de solo lectura. Se
define con una clave BindablePropertyKey privada y solo se puede establecer mediante una referencia a la interfaz
de IVideoPlayerController , que incluye esta propiedad Duration :
namespace FormsVideoLibrary
{
public interface IVideoPlayerController
{
VideoStatus Status { set; get; }

TimeSpan Duration { set; get; }


}
}

Tenga en cuenta también el controlador de cambio de propiedad que llama a un método denominado
SetTimeToEnd que se describe más adelante en este artículo.

La duración de un vídeo no está disponible inmediatamente después de que se establezca la propiedad Source de
VideoPlayer . El archivo de vídeo debe descargarse parcialmente antes de que el reproductor de vídeo subyacente
pueda determinar su duración.
Aquí le mostramos cómo cada uno de los representadores de la plataforma obtiene la duración del vídeo:
Duración del vídeo en iOS
En iOS, la duración de un vídeo se obtiene a partir de la propiedad Duration de AVPlayerItem , pero no
inmediatamente después de que se haya creado AVPlayerItem . Es posible establecer un observador de iOS para la
propiedad Duration , pero VideoPlayerRenderer obtiene la duración en el método UpdateStatus , al que se llama 10
veces por segundo:

namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
void OnUpdateStatus(object sender, EventArgs args)
{
···
if (playerItem != null)
{
((IVideoPlayerController)Element).Duration = ConvertTime(playerItem.Duration);
···
}
}

TimeSpan ConvertTime(CMTime cmTime)


{
return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);

}
···
}
}

El método ConvertTime convierte un objeto CMTime en un valor TimeSpan .


Duración del vídeo en Android
La propiedad Duration de la VideoView de Android notifica una duración válida en milisegundos cuando se activa
el evento Prepared de VideoView . La clase VideoPlayerRenderer de Android utiliza ese controlador para obtener la
propiedad Duration :
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
void OnVideoViewPrepared(object sender, EventArgs args)
{
···
((IVideoPlayerController)Element).Duration = TimeSpan.FromMilliseconds(videoView.Duration);
}
···
}
}

Duración del vídeo en UWP


La propiedad NaturalDuration de MediaElement es un valor TimeSpan y entra en vigor cuando MediaElement
activa el evento MediaOpened :

namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
···
void OnMediaElementMediaOpened(object sender, RoutedEventArgs args)
{
((IVideoPlayerController)Element).Duration = Control.NaturalDuration.TimeSpan;
}
···
}
}

La propiedad Position
VideoPlayer también necesita una propiedad Position que aumente de cero a Duration mientras se reproduce
el vídeo. VideoPlayer implementa esta propiedad como la propiedad Position del MediaElement de UWP, que es
una propiedad enlazable normal con descriptores de acceso set y get públicos:

namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
// Position property
public static readonly BindableProperty PositionProperty =
BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(VideoPlayer), new TimeSpan(),
propertyChanged: (bindable, oldValue, newValue) => ((VideoPlayer)bindable).SetTimeToEnd());

public TimeSpan Position


{
set { SetValue(PositionProperty, value); }
get { return (TimeSpan)GetValue(PositionProperty); }
}
···
}
}

El descriptor de acceso get devuelve la posición actual del vídeo mientras se reproduce, pero el descriptor de
acceso set está pensado para responder a la manipulación del usuario de la barra de posición para mover la
posición de vídeo hacia adelante o hacia atrás.
En iOS y Android, la propiedad que obtiene la posición actual solo tiene un descriptor de acceso get , y hay
disponible un método Seek para realizar esta segunda tarea. Si se piensa bien, un método Seek por separado
parece ser un enfoque más sensato que una sola propiedad Position . Una sola propiedad Position tiene un
problema inherente: mientras se reproduce el vídeo, la propiedad Position debe actualizarse continuamente para
reflejar la nueva posición. Pero no es recomendable que la mayoría de los cambios en la propiedad Position
hagan que el reproductor de vídeo se mueva a una nueva posición en el vídeo. Si eso ocurriera, el reproductor de
vídeo, como respuesta, buscaría el último valor de la propiedad Position y el vídeo no avanzaría.
A pesar de las dificultades de la implementación de una propiedad Position con los descriptores de acceso set y
get , se ha elegido este enfoque porque es coherente con UWP MediaElement , y tiene una gran ventaja con el
enlace de datos: La propiedad Position de VideoPlayer pueden estar enlazada al control deslizante que se usa
para mostrar la posición y para buscar una nueva posición. Sin embargo, se deben tomar varias precauciones al
implementar esta propiedad Position para evitar bucles de retroalimentación.
Establecer y obtener la posición en iOS
En iOS, la propiedad CurrentTime del objeto AVPlayerItem indica la posición actual del vídeo en reproducción.
VideoPlayerRenderer de iOS establece la propiedad Position en el controlador UpdateStatus al mismo tiempo
que establece la propiedad Duration :

namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
void OnUpdateStatus(object sender, EventArgs args)
{
···
if (playerItem != null)
{
···
((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty,
ConvertTime(playerItem.CurrentTime));
}
}
···
}
}

El representador detecta cuándo el conjunto de propiedades Position de VideoPlayer ha cambiado en la


invalidación de OnElementPropertyChanged y utiliza ese valor nuevo para llamar a un método Seek en el objeto
AVPlayer :
namespace FormsVideoLibrary.iOS
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
···
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
···
else if (args.PropertyName == VideoPlayer.PositionProperty.PropertyName)
{
TimeSpan controlPosition = ConvertTime(player.CurrentTime);

if (Math.Abs((controlPosition - Element.Position).TotalSeconds) > 1)


{
player.Seek(CMTime.FromSeconds(Element.Position.TotalSeconds, 1));
}
}
}
···
}
}

Tenga en cuenta que cada vez que la propiedad Position en VideoPlayer se establece desde el controlador
OnUpdateStatus , la propiedad Position activa un evento PropertyChanged , que se detecta en la invalidación de
OnElementPropertyChanged . En la mayoría de estos cambios, el método OnElementPropertyChanged no debe hacer
nada. En caso contrario, ante cada cambio en la posición del vídeo, se movería a la misma posición a la que ha
llegado.
Para evitar este bucle de comentarios, el método OnElementPropertyChanged solo llama a Seek cuando la diferencia
entre la propiedad Position y la posición actual del AVPlayer es mayor que un segundo.
Establecer y obtener la posición en Android
Al igual que en el representador de iOS, el VideoPlayerRenderer de Android establece un valor nuevo para la
propiedad Position en el controlador OnUpdateStatus . La propiedad CurrentPosition de VideoView contiene la
posición nueva en unidades de milisegundos:

namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
void OnUpdateStatus(object sender, EventArgs args)
{
···
TimeSpan timeSpan = TimeSpan.FromMilliseconds(videoView.CurrentPosition);
((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty, timeSpan);
}
···
}
}

Además, al igual que en el representador de iOS, el representador de Android llama al método SeekTo de
VideoView cuando la propiedad Position ha cambiado, pero solo cuando el cambio y el valor CurrentPosition de
VideoView difieren en más de un segundo:
namespace FormsVideoLibrary.Droid
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
{
···
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
···
else if (args.PropertyName == VideoPlayer.PositionProperty.PropertyName)
{
if (Math.Abs(videoView.CurrentPosition - Element.Position.TotalMilliseconds) > 1000)
{
videoView.SeekTo((int)Element.Position.TotalMilliseconds);
}
}
}
···
}
}

Establecer y obtener la posición en UWP


VideoPlayerRenderer de UWP controla el valor Position de la misma manera que los representadores de iOS y
Android; sin embargo, dado que la propiedad Position del MediaElement de UWP también es un valor TimeSpan ,
no es necesario realizar ninguna conversión:

namespace FormsVideoLibrary.UWP
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
{
···
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
···
else if (args.PropertyName == VideoPlayer.PositionProperty.PropertyName)
{
if (Math.Abs((Control.Position - Element.Position).TotalSeconds) > 1)
{
Control.Position = Element.Position;
}
}
}
···
void OnUpdateStatus(object sender, EventArgs args)
{
((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty,
Control.Position);
}
···
}
}

Calcular una propiedad TimeToEnd


A veces, los reproductores de vídeo muestran el tiempo restante en el vídeo. Este valor comienza en la duración del
vídeo cuando el vídeo empieza y disminuye a cero cuando el vídeo finaliza.
VideoPlayer incluye una propiedad TimeToEnd de solo lectura que se controla por completo dentro de la clase
según los cambios en las propiedades Duration y Position :
namespace FormsVideoLibrary
{
public class VideoPlayer : View, IVideoPlayerController
{
···
private static readonly BindablePropertyKey TimeToEndPropertyKey =
BindableProperty.CreateReadOnly(nameof(TimeToEnd), typeof(TimeSpan), typeof(VideoPlayer), new
TimeSpan());

public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;

public TimeSpan TimeToEnd


{
private set { SetValue(TimeToEndPropertyKey, value); }
get { return (TimeSpan)GetValue(TimeToEndProperty); }
}

void SetTimeToEnd()
{
TimeToEnd = Duration - Position;
}
···
}
}

El método SetTimeToEnd se llama desde los controladores de cambio de propiedad de Duration y Position .

Un control deslizante personalizado para vídeo


Es posible escribir un control personalizado para una barra de posición o para usar el :::no-loc(Xamarin.Forms)::: de
Slider o una clase que derive de Slider , como la siguiente clase PositionSlider . La clase define dos
propiedades nuevas denominadas Duration y Position de tipo TimeSpan que están diseñadas para enlazarse a
las dos propiedades del mismo nombre en VideoPlayer . Tenga en cuenta que el modo de enlace predeterminado
de la propiedad Position es bidireccional:
namespace FormsVideoLibrary
{
public class PositionSlider : Slider
{
public static readonly BindableProperty DurationProperty =
BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new
TimeSpan(1),
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((PositionSlider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
});

public TimeSpan Duration


{
set { SetValue(DurationProperty, value); }
get { return (TimeSpan)GetValue(DurationProperty); }
}

public static readonly BindableProperty PositionProperty =


BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new
TimeSpan(0),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((PositionSlider)bindable).Value = seconds;
});

public TimeSpan Position


{
set { SetValue(PositionProperty, value); }
get { return (TimeSpan)GetValue(PositionProperty); }
}

public PositionSlider()
{
PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "Value")
{
TimeSpan newPosition = TimeSpan.FromSeconds(Value);

if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds >


0.01)
{
Position = newPosition;
}
}
};
}
}
}

El controlador de cambio de propiedad para la propiedad Duration establece la propiedad Maximum del Slider
subyacente en la propiedad TotalSeconds del valor TimeSpan . De forma similar, el controlador de cambio de
propiedad para Position establece la propiedad Value del Slider . De esta manera, el Slider subyacente realiza
un seguimiento de la posición del PositionSlider .
PositionSlider se actualiza desde el Slider subyacente en una única instancia: cuando el usuario manipula el
Slider para indicar que el vídeo debe avanzar o retroceder a una posición nueva. Esto se detecta en el controlador
PropertyChanged en el constructor del PositionSlider . El controlador comprueba si hay algún cambio en la
propiedad Value y, si es diferente de la propiedad Position , la propiedad Position se establece desde la
propiedad Value .
En teoría, la instrucción if interna podría escribirse así:

if (newPosition.Seconds != Position.Seconds)
{
Position = newPosition;
}

Sin embargo, la implementación de Slider en Android solo tiene 1000 pasos discretos, independientemente de la
configuración de Minimum y Maximum . Si la longitud de un vídeo fuera mayor que 1000 segundos, dos valores
Position diferentes se corresponderían a la misma configuración de Value del Slider , y esta instrucción if
desencadenaría un falso positivo de una manipulación de usuario del Slider . En su lugar, es más seguro
comprobar que las posiciones nueva y existente son mayores que una centésima de la duración total.

Utilizar el PositionSlider
La documentación del MediaElement de UWP advierte sobre el enlace a la propiedad Position porque la
propiedad se actualiza con frecuencia. La documentación recomienda que se utilice un temporizador para consultar
la propiedad Position .
Esa es una buena recomendación, pero las tres clases VideoPlayerRenderer ya utilizan indirectamente un
temporizador para actualizar la propiedad Position . La propiedad Position se cambia en un controlador para el
evento UpdateStatus , que se activa solo 10 veces por segundo.
Por tanto, la propiedad Position del VideoPlayer puede enlazarse a la propiedad Position del PositionSlider
sin problemas de rendimiento, como se muestra en la página Barra de posición personalizada :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
x:Class="VideoPlayerDemos.CustomPositionBarPage"
Title="Custom Position Bar">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<video:VideoPlayer x:Name="videoPlayer"
Grid.Row="0"
AreTransportControlsEnabled="False"
Source="{StaticResource ElephantsDream}" />

···

<StackLayout Grid.Row="1"
Orientation="Horizontal"
Margin="10, 0"
BindingContext="{x:Reference videoPlayer}">

<Label Text="{Binding Path=Position,


StringFormat='{0:hh\\:mm\\:ss}'}"
VerticalOptions="Center"/>

···

<Label Text="{Binding Path=TimeToEnd,


StringFormat='{0:hh\\:mm\\:ss}'}"
VerticalOptions="Center" />
</StackLayout>

<video:PositionSlider Grid.Row="2"
Margin="10, 0, 10, 10"
BindingContext="{x:Reference videoPlayer}"
Duration="{Binding Duration}"
Position="{Binding Position}">
<video:PositionSlider.Triggers>
<DataTrigger TargetType="video:PositionSlider"
Binding="{Binding Status}"
Value="{x:Static video:VideoStatus.NotReady}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</video:PositionSlider.Triggers>
</video:PositionSlider>
</Grid>
</ContentPage>

Los primeros puntos suspensivos (···) ocultan el ActivityIndicator ; es el mismo que en la página anterior
Transpor te personalizado . Tenga en cuenta los dos elementos Label que muestran las propiedades Position y
TimeToEnd . Los puntos suspensivos entre esos dos elementos Label ocultan los dos elementos Button que
aparecen en la página Transpor te personalizado para reproducir, hacer pausa y detener. La lógica de código
subyacente también es la misma que la de la página Transpor te personalizado .
Con esto concluye la explicación de VideoPlayer .

Vínculos relacionados
Demostraciones de reproductor de vídeo (ejemplo)
Enlace de datos de :::no-loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
El enlace de datos es la técnica que consiste en vincular las propiedades de dos objetos para que los cambios
en una propiedad se reflejen automáticamente en la otra propiedad. El enlace de datos es una parte integral
de la arquitectura de aplicación Model-View-ViewModel (MVVM).

El problema de vinculación de datos


Una aplicación de :::no-loc(Xamarin.Forms)::: consta de una o varias páginas, cada una de las cuales
generalmente contiene varios objetos de interfaz de usuario denominados vistas. Una de las tareas
principales del programa consiste en mantener estas vistas sincronizadas y realizar un seguimiento de los
distintos valores o selecciones que representan. A menudo las vistas representan valores de un origen de
datos subyacente y el usuario manipula estas vistas para cambiar esos datos. Cuando la vista cambia, los
datos subyacentes deben reflejar ese cambio y, de forma similar, cuando los datos subyacentes cambian, ese
cambio debe reflejarse en la vista.
Para controlar este trabajo correctamente, el programa debe recibir una notificación de cambios en estas
vistas o en los datos subyacentes. La solución habitual consiste en definir eventos que indican cuándo se
produce un cambio. Después, se puede instalar un controlador de eventos que recibe la notificación de estos
cambios, y responde transfiriendo datos de un objeto a otro. Pero cuando hay muchas vistas, también es
necesario que haya muchos controladores de eventos y se involucra una gran cantidad de código.

La solución de enlace de datos


El enlace de datos automatiza este trabajo y vuelve innecesarios los controladores de eventos. Los enlaces de
datos se pueden implementar en el código o en XAML, pero son mucho más comunes en XAML, ya que así
es más fácil reducir el tamaño del archivo de código subyacente. Al reemplazar el código de procedimientos
en los controladores de eventos con código declarativo o marcado, se simplifica y se aclara la aplicación.
Uno de los dos objetos implicados en un enlace de datos es casi siempre un elemento que se deriva de
View y forma parte de la interfaz visual de una página. El otro objeto puede ser:

Otro derivado de View , normalmente en la misma página.


Un objeto en un archivo de código.
En los programas de demo, como los del ejemplo DataBindingDemos , los enlaces de datos entre dos
derivados de View a menudo se muestran con fines de claridad y simplicidad. Pero se pueden aplicar los
mismos principios a los enlaces de datos entre un View y otros objetos. Cuando se compila una aplicación
con la arquitectura Model-View-ViewModel (MVVM), la clase con los datos subyacentes a menudo se
denomina ViewModel.
En los siguientes artículos se abordan los enlaces de datos:

Enlaces básicos
Conozca la diferencia entre el origen y el destino del enlace de datos y vea enlaces de datos sencillos en
código y en XAML.
Modo de enlace
Descubra cómo el modo de enlace puede controlar el flujo de datos entre los dos objetos.

Formato de cadena
Use un enlace de datos para dar formato y mostrar objetos como cadenas.

Enlace de ruta de acceso


Profundice en la propiedad Path del enlace de datos para acceder a las subpropiedades y los miembros de
la colección.

Enlace de convertidores de valores


Use convertidores de valor de enlace para modificar valores en el enlace de datos.

Enlaces relativos
Use enlaces relativos para establecer el origen de enlace en relación con la posición del destino de enlace.

Conmutación por recuperación de enlaces


Fortalezca los enlaces de datos mediante la definición de valores de reserva para usarlos si se produce un
error en el proceso de enlace.

Enlaces múltiples
Adjunte una colección de objetos Binding asociados a una única propiedad de destino de enlace.

Interfaz de comandos
Implemente la propiedad Command con los enlaces de datos.

Enlaces compilados
Use enlaces compilados para mejorar el rendimiento del enlace de datos.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Extensiones de marcado XAML
Enlaces básicos de :::no-loc(Xamarin.Forms):::
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
Un enlace de datos de :::no-loc(Xamarin.Forms)::: enlaza un par de propiedades entre dos objetos, del que al menos
uno suele ser un objeto de interfaz de usuario. Estos dos objetos se denominan el destino y el origen :
El destino es el objeto (y la propiedad) en la que se establece el enlace de datos.
El origen es el objeto (y la propiedad) al que hace referencia el enlace de datos.
Esta distinción a veces puede ser un poco confusa: en el caso más simple, los datos fluyen desde el origen al
destino, lo que significa que el valor de la propiedad de destino se establece en el valor de la propiedad de origen.
Pero en algunos casos, los datos también pueden fluir desde el destino al origen, o en ambas direcciones. Para
evitar confusiones, tenga en cuenta que el destino siempre es el objeto en el que se establece el enlace de datos,
incluso si ofrece datos en lugar de recibirlos.

Enlaces con un contexto de enlace


Aunque los enlaces de datos normalmente se especifican totalmente en XAML, es útil verlos en el código. La
página Enlace básico de código contiene un archivo XAML con un elemento Label y un elemento Slider :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicCodeBindingPage"
Title="Basic Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="48"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

El elemento Slider se establece para un intervalo de 0 a 360. El propósito de este programa es girar el elemento
Label mediante la manipulación de Slider .

Sin los enlaces de datos, se establecería el evento ValueChanged del elemento Slider en un controlador de
eventos que accede a la propiedad Value del elemento Slider y establece ese valor en la propiedad Rotation
del elemento Label . El enlace de datos automatiza ese trabajo; el controlador de eventos y el código que contiene
ya no son necesarios.
Puede establecer un enlace en una instancia de cualquier clase que se derive de BindableObject , lo que incluye las
derivadas Element , VisualElement , View y View . El enlace siempre se establece en el objeto de destino. El enlace
hace referencia al objeto de origen. Para establecer el enlace de datos, use los dos miembros siguientes de la clase
de destino:
La propiedad BindingContext especifica el objeto de origen.
El método SetBinding especifica la propiedad de destino y la de origen.
En este ejemplo, Label es el destino de enlace y Slider es el origen de enlace. Los cambios en el origen de
Slider afectan a la rotación del destino de Label . Los datos fluyen del origen al destino.

El método SetBinding definido por BindableObject tiene un argumento de tipo BindingBase del que se deriva la
clase Binding , pero existen otros métodos SetBinding definidos por la clase BindableObjectExtensions . En el
archivo de código subyacente del ejemplo Enlace de código básico se usa un método de extensión SetBinding
más sencillo de esta clase.

public partial class BasicCodeBindingPage : ContentPage


{
public BasicCodeBindingPage()
{
InitializeComponent();

label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
}
}

El objeto Label es el destino de enlace, por lo que es el objeto en el que se establece esta propiedad y en el que se
llama al método. La propiedad BindingContext indica el origen de enlace, que es el elemento Slider .
El método SetBinding se llama en el destino de enlace, pero especifica tanto la propiedad de destino como la de
origen. La propiedad de destino se especifica como un objeto BindableProperty : Label.RotationProperty . La
propiedad de origen se especifica como una cadena e indica la propiedad Value de Slider .
El método SetBinding revela una de las reglas de enlaces de datos más importantes:
La propiedad de destino debe estar respaldada por una propiedad enlazable.
Esta regla implica que el objeto de destino debe ser una instancia de una clase que se derive de BindableObject .
Vea el artículo Propiedades enlazables para obtener información general sobre los objetos y las propiedades
enlazables.
No hay ninguna regla de este tipo para la propiedad de origen, que se especifica como una cadena. De forma
interna, se usa la reflexión para acceder a la propiedad real. Pero en este caso concreto, la propiedad Value
también está respaldada por una propiedad enlazable.
El código se puede simplificar ligeramente: la propiedad enlazable RotationProperty se define mediante
VisualElement , y también la heredan Label y ContentPage , por lo que no es necesario el nombre de clase en la
llamada a SetBinding :

label.SetBinding(RotationProperty, "Value");

Pero la inclusión del nombre de clase es un buen recordatorio del objeto de destino.
Al manipular el elemento Slider , el elemento Label gira según corresponda:
La página Enlace Xaml básico es idéntica a la página Enlace de código básico , con la excepción de que define
el enlace de datos completo en XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicXamlBindingPage"
Title="Basic XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}" />

<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

Al igual que en el código, el enlace de datos se establece en el objeto de destino, que es el elemento Label .
Intervienen dos extensiones de marcado XAML. Son reconocibles al instante por los delimitadores de llave:
La extensión de marcado x:Reference es necesaria para hacer referencia al objeto de origen, que es el
elemento Slider denominado slider .
La extensión de marcado Binding enlaza la propiedad Rotation de Label a la propiedad Value de Slider .
Vea el artículo Extensiones de marcado XAML para obtener más información sobre las extensiones de marcado
XAML. La extensión de marcado x:Reference es compatible con la clase ReferenceExtension ; Binding es
compatible con la clase BindingExtension . Como indican los prefijos de espacio de nombres XML, x:Reference
forma parte de la especificación 2009 de XAML, mientras que Binding forma parte de :::no-loc(Xamarin.Forms):::.
Observe que no aparecen comillas entre las llaves.
Es fácil olvidar la extensión de marcado x:Reference cuando se establece el valor BindingContext . Un error
habitual es establecer la propiedad directamente en el nombre del origen de enlace, como en este caso:

BindingContext="slider"
Pero eso no es correcto. Ese marcado establece la propiedad BindingContext en un objeto string cuyos
caracteres deletrean "slider".
Observe que la propiedad de origen se especifica con la propiedad Path de BindingExtension , que se
corresponde con la propiedad Path de la clase Binding .
El marcado que se muestra en la página Basic XAML Binding (Enlace XAML básico) se puede simplificar: en las
extensiones de marcado XAML como x:Reference y Binding se pueden definir atributos de propiedad de
contenido , lo que para las extensiones de marcado XAML significa que no es necesario que aparezca el nombre de
propiedad. La propiedad Name es la propiedad de contenido de x:Reference y la propiedad Path es la propiedad
de contenido de Binding , lo que significa que se pueden eliminar de las expresiones:

<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference slider}"
Rotation="{Binding Value}" />

Enlaces sin un contexto de enlace


La propiedad BindingContext es un componente importante de los enlaces de datos, pero no siempre es
necesaria. En su lugar, el objeto de origen se puede especificar en la llamada a SetBinding o en la extensión de
marcado Binding .
Esto se muestra en el ejemplo Enlace de código alternativo . El archivo XAML es similar al ejemplo Enlace de
código básico , salvo que el elemento Slider se define para controlar la propiedad Scale del elemento Label .
Por ese motivo, el elemento Slider se establece para un intervalo de –2 a 2:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeCodeBindingPage"
Title="Alternative Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

El archivo de código subyacente establece el enlace con el método SetBinding definido por el objeto
BindableObject . El argumento es un constructor para la clase Binding :
public partial class AlternativeCodeBindingPage : ContentPage
{
public AlternativeCodeBindingPage()
{
InitializeComponent();

label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));


}
}

El constructor Binding tiene seis parámetros, por lo que el parámetro source se especifica con un argumento con
nombre. El argumento es el objeto slider .
La ejecución de este programa podría ser un poco sorprendente:

En la pantalla de iOS de la izquierda se muestra el aspecto de la pantalla cuando aparece la página por primera vez.
¿Dónde está el elemento Label ?
El problema es que el elemento Slider tiene un valor inicial de 0. Esto hace que la propiedad Scale de Label
también se establezca en 0, y se reemplace su valor predeterminado de 1. Como resultado, el elemento Label es
invisible inicialmente. Como se muestra en la captura de pantalla de Android, el elemento Slider se puede
manipular para que Label aparezca de nuevo, pero su desaparición inicial es desconcertante.
En el artículo siguiente verá cómo evitar este problema si inicializa el elemento Slider a partir del valor
predeterminado de la propiedad Scale .

NOTE
La clase VisualElement también define las propiedades ScaleX y ScaleY , que pueden escalar el elemento
VisualElement de forma diferente en dirección horizontal y vertical.

En la página Enlace XAML alternativo se muestra el enlace equivalente completamente en XAML:


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeXamlBindingPage"
Title="Alternative XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value}" />

<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

Ahora la extensión de marcado Binding tiene dos propiedades establecidas, Source y Path , separadas por una
coma. Si lo prefiere, pueden aparecer en la misma línea:

Scale="{Binding Source={x:Reference slider}, Path=Value}" />

La propiedad Source se establece en una extensión de marcado x:Reference insertada que, en caso contrario,
tiene la misma sintaxis que la configuración de BindingContext . Observe que no aparecen comillas dentro de las
llaves, y que las dos propiedades se deben separar por una coma.
La propiedad de contenido de la extensión de marcado Binding es Path , pero la parte Path= de la extensión de
marcado solo se puede eliminar si es la primera propiedad en la expresión. Para eliminar la parte Path= , es
necesario intercambiar las dos propiedades:

Scale="{Binding Value, Source={x:Reference slider}}" />

Aunque las extensiones de marcado XAML suelen estar delimitadas por llaves, también se pueden expresar como
elementos de objeto:

<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Source="{x:Reference slider}"
Path="Value" />
</Label.Scale>
</Label>

Ahora las propiedades Source y Path son atributos XAML normales: los valores aparecen entre comillas y los
atributos no están separados por una coma. La extensión de marcado x:Reference también se puede convertir en
un elemento de objeto:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Path="Value">
<Binding.Source>
<x:Reference Name="slider" />
</Binding.Source>
</Binding>
</Label.Scale>
</Label>

Esta sintaxis no es común, pero a veces es necesaria cuando hay objetos complejos implicados.
En los ejemplos mostrados hasta ahora se establecen las propiedades BindingContext y Source de Binding en
una extensión de marcado x:Reference para hacer referencia a otra vista en la página. Estas dos propiedades son
de tipo Object , y se pueden establecer en cualquier objeto que incluya propiedades adecuadas para los orígenes
de enlace.
En los artículos siguientes, descubrirá que puede establecer la propiedad BindingContext o Source en una
extensión de marcado x:Static para hacer referencia al valor de una propiedad estática o un campo, o bien en
una extensión de marcado StaticResource para hacer referencia a un objeto almacenado en un diccionario de
recursos, o directamente en un objeto, que suele ser (pero no siempre) una instancia de una clase ViewModel.
La propiedad BindingContext se puede establecer en un objeto Binding para que las propiedades Source y Path
de Binding definan el contexto de enlace.

Herencia del contexto de enlace


En este artículo, ha visto que puede especificar el objeto de origen mediante las propiedades BindingContext o
Source del objeto Binding . Si se establecen las dos, la propiedad Source de Binding tiene prioridad sobre
BindingContext .

La propiedad BindingContext tiene una característica muy importante:


La configuración de la propiedad BindingContext se hereda a través del árbol visual.
Como verá, esto puede ser muy útil para simplificar las expresiones de enlace y, en algunos casos, especialmente
en escenarios de Model-View-ViewModel (MVVM), es esencial.
El ejemplo Herencia de contexto de enlace es una simple demostración de la herencia de contexto de enlace:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BindingContextInheritancePage"
Title="BindingContext Inheritance">
<StackLayout Padding="10">

<StackLayout VerticalOptions="FillAndExpand"
BindingContext="{x:Reference slider}">

<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="EndAndExpand"
Rotation="{Binding Value}" />

<BoxView Color="#800000FF"
WidthRequest="180"
HeightRequest="40"
HorizontalOptions="Center"
VerticalOptions="StartAndExpand"
Rotation="{Binding Value}" />
</StackLayout>

<Slider x:Name="slider"
Maximum="360" />

</StackLayout>
</ContentPage>

La propiedad BindingContext de StackLayout se establece en el objeto slider . Label y BoxView heredan este
contexto de enlace, y en los dos sus propiedades Rotation se establecen en la propiedad Value del elemento
Slider :

En el artículo siguiente, verá cómo el modo de enlace puede cambiar el flujo de datos entre los objetos de origen y
destino.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Modo de enlace de :::no-loc(Xamarin.Forms):::
18/12/2020 • 28 minutes to read • Edit Online

Descargar el ejemplo
En el artículo anterior, las páginas Enlace de código alternativo y Enlace XAML alternativo contenían un
objeto Label con su propiedad Scale enlazada a la propiedad Value de un elemento Slider . Como el valor
inicial de Slider es 0, la propiedad Scale de Label se establece en 0 en lugar de 1, y el elemento Label
desaparece.
En el ejemplo DataBindingDemos , la página Enlace inverso es similar a los programas del artículo anterior,
salvo que el enlace de datos se define en Slider en lugar de Label :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.ReverseBindingPage"
Title="Reverse Binding">
<StackLayout Padding="10, 0">

<Label x:Name="label"
Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider x:Name="slider"
VerticalOptions="CenterAndExpand"
Value="{Binding Source={x:Reference label},
Path=Opacity}" />
</StackLayout>
</ContentPage>

A primera vista, esto podría parecer al contrario: ahora Label es el origen de enlace de datos y Slider es el
destino. El enlace hace referencia a la propiedad Opacity de Label , que tiene un valor predeterminado de 1.
Como cabría esperar, Slider se inicializa en el valor 1 a partir del valor Opacity inicial de Label . Esto se
muestra en la captura de pantalla de iOS de la izquierda:
Pero es posible que le sorprenda que Slider siga funcionando, como se muestra en la captura de pantalla de
Android. Esto parece sugerir que el enlace de datos funciona mejor cuando Slider es el destino de enlace en
lugar de Label , porque la inicialización funciona de la forma esperada.
La diferencia entre el ejemplo Enlace inverso y los ejemplos anteriores implica el modo de enlace.

El modo de enlace predeterminado


El modo de enlace se especifica con un miembro de la enumeración BindingMode :
Default
TwoWay – los datos van en ambas direcciones entre el origen y el destino
OneWay – los datos van del origen al destino
OneWayToSource – los datos van del destino al origen
OneTime – los datos van del origen al destino, pero solo cuando cambia BindingContext (novedad de :::no-
loc(Xamarin.Forms)::: 3.0)
Todas las propiedades enlazables tienen un modo de enlace predeterminado que se establece cuando se crea la
propiedad enlazable, y que está disponible en la propiedad DefaultBindingMode del objeto BindableProperty . Este
modo de enlace predeterminado indica el modo en vigor cuando esa propiedad es un destino de enlace de datos.
El modo de enlace predeterminado para la mayoría de las propiedades como Rotation , Scale y Opacity es
OneWay . Cuando estas propiedades son destinos de enlace de datos, la propiedad de destino se establece desde
el origen.
Pero el modo de enlace predeterminado para la propiedad Value de Slider es TwoWay . Esto significa que
cuando la propiedad Value es un destino de enlace de datos, el destino se establece desde el origen (como es
habitual), pero el origen también se establece desde el destino. Esto es lo que permite que Slider se establezca
en el valor Opacity inicial.
Es posible que parezca que este enlace bidireccional crea un bucle infinito, pero no es lo que sucede. Las
propiedades enlazables no señalan un cambio de propiedad a menos que la propiedad cambie realmente. Esto
evita un bucle infinito.
Enlaces bidireccionales
La mayoría de las propiedades enlazables tienen un modo de enlace predeterminado de OneWay , pero las
propiedades siguientes tienen un modo de enlace predeterminado de TwoWay :
Propiedad Date de DatePicker
Propiedad Text de Editor , Entry , SearchBar y EntryCell
Propiedad IsRefreshing de ListView
Propiedad SelectedItem de MultiPage
Propiedades SelectedIndex y SelectedItem de Picker
Propiedad Value de Slider y Stepper
Propiedad IsToggled de Switch
Propiedad On de SwitchCell
Propiedad Time de TimePicker

Estas propiedades concretas se definen como TwoWay por una muy buena razón:
Cuando los enlaces de datos se usan con la arquitectura de aplicación Model-View-ViewModel (MVVM), la clase
ViewModel es el origen del enlace de datos y la vista, que consta de vistas como Slider , son destinos de enlace
de datos. Los enlaces de MVVM son más similares al ejemplo Enlace inverso que los enlaces de los ejemplos
anteriores. Es muy probable que quiera que cada vista de la página se inicialice con el valor de la propiedad
correspondiente en el modelo de vista, pero los cambios en la vista también deberían afectar a la propiedad
ViewModel.
Las propiedades con modos de enlace predeterminados de TwoWay son las que probablemente se usen más en
escenarios de MVVM.
Enlaces unidireccionales al origen
Las propiedades enlazables de solo lectura tienen un modo de enlace predeterminado de OneWayToSource . Solo
hay una propiedad enlazable de lectura y escritura que tiene un modo de enlace predeterminado de
OneWayToSource :

Propiedad SelectedItem de ListView

La razón es que el resultado de un enlace en la propiedad SelectedItem debe ser el establecimiento del origen de
enlace. En un ejemplo posterior de este artículo se invalida este comportamiento.
Enlaces de un solo uso
Varias propiedades enlazables tienen un modo de enlace predeterminado de OneTime , que incluye la propiedad
IsTextPredictionEnabled de Entry .

Las propiedades de destino con un modo de enlace de OneTime solo se actualizan cuando cambia el contexto de
enlace. Para los enlaces en estas propiedades de destino, esto simplifica la infraestructura de enlace ya que no es
necesario supervisar los cambios en las propiedades de origen.

Modelos de vista y notificaciones de cambio de propiedad


En la página Selector de colores simple se muestra el uso de un modelo de vista simple. Los enlaces de datos
permiten al usuario seleccionar un color mediante tres elementos Slider para el matiz, la saturación y la
luminosidad.
El modelo de vista es el origen del enlace de datos. El modelo de vista no define propiedades enlazables, pero
implementa un mecanismo de notificación que permite que la infraestructura de enlace reciba una notificación
cuando cambia el valor de una propiedad. Este mecanismo de notificación es la interfaz de
INotifyPropertyChanged , que define un único evento denominado PropertyChanged . Una clase que implemente
esta interfaz normalmente desencadena el evento cuando cambia el valor de una de sus propiedades públicas.
No es necesario desencadenar el evento si la propiedad no cambia nunca. (La interfaz INotifyPropertyChanged
también se implementa mediante BindableObject y se desencadena un evento PropertyChanged cada vez que
una propiedad enlazable cambia de valor).
La HslColorViewModel clase define cinco propiedades: las propiedades Hue , Saturation , Luminosity y Color
están interrelacionadas. Cuando cambia el valor de cualquiera de los tres componentes de color, se vuelve a
calcular la propiedad Color y se desencadenan eventos PropertyChanged para las cuatro propiedades:

public class HslColorViewModel : INotifyPropertyChanged


{
Color color;
string name;

public event PropertyChangedEventHandler PropertyChanged;

public double Hue


{
set
{
if (color.Hue != value)
{
Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
}
}
}
get
{
return color.Hue;
}
}

public double Saturation


{
set
{
if (color.Saturation != value)
{
Color = Color.FromHsla(color.Hue, value, color.Luminosity);
}
}
get
{
return color.Saturation;
}
}

public double Luminosity


{
set
{
if (color.Luminosity != value)
{
Color = Color.FromHsla(color.Hue, color.Saturation, value);
}
}
get
{
return color.Luminosity;
}
}

public Color Color


{
set
{
if (color != value)
{
color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

Name = NamedColor.GetNearestColorName(color);
}
}
get
{
return color;
}
}

public string Name


{
private set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
get
{
return name;
}
}
}

Cuando cambia la propiedad Color , el método estático GetNearestColorName de la clase NamedColor (incluido
también en la solución DataBindingDemos )obtiene el color con nombre más cercano y establece la propiedad
Name . Esta propiedad Name tiene un descriptor de acceso set privado, por lo que no se puede establecer desde
fuera de la clase.
Cuando se establece una clase ViewModel como un origen de enlace, la infraestructura de enlace adjunta un
controlador al evento PropertyChanged . De esta manera, el enlace puede recibir una notificación de cambios en
las propiedades y, después, puede establecer las propiedades de destino a partir de los valores cambiados.
Pero cuando una propiedad de destino (o la definición Binding en una propiedad de destino) tiene un elemento
BindingMode de tipo OneTime , no es necesario que la infraestructura de enlace adjunte un controlador al evento
PropertyChanged . La propiedad de destino solo se actualiza cuando cambia BindingContext y no cuando cambia
la propiedad de origen.
El archivo XAML Selector de colores simple crea una instancia de HslColorViewModel en el diccionario de
recursos de la página e inicializa la propiedad Color . La propiedad BindingContext de Grid se establece en una
extensión de enlace StaticResource para hacer referencia a ese recurso:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.SimpleColorSelectorPage">

<ContentPage.Resources>
<ResourceDictionary>
<local:HslColorViewModel x:Key="viewModel"
Color="MediumTurquoise" />

<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<Grid BindingContext="{StaticResource viewModel}">


<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<BoxView Color="{Binding Color}"


Grid.Row="0" />

<StackLayout Grid.Row="1"
Margin="10, 0">

<Label Text="{Binding Name}"


HorizontalTextAlignment="Center" />

<Slider Value="{Binding Hue}" />

<Slider Value="{Binding Saturation}" />

<Slider Value="{Binding Luminosity}" />


</StackLayout>
</Grid>
</ContentPage>

Los elementos BoxView , Label y tres vistas Slider heredan el contexto de enlace del elemento Grid . Todas
estas vistas son destinos de enlace que hacen referencia a las propiedades de origen en el modelo de vista. Para
la propiedad Color de BoxView y la propiedad Text de Label , los enlaces de datos son OneWay : Las
propiedades de la vista se establecen de las propiedades de ViewModel.
Pero la propiedad Value de Slider es TwoWay . Esto permite que cada elemento Slider se establezca a partir
del modelo de vista, y también que el modelo de vista se establezca a partir de cada elemento Slider .
Cuando se ejecuta por primera vez el programa, BoxView , Label y los tres elementos Slider están establecidos
a partir del modelo de vista en función de la propiedad Color inicial establecida cuando se creó la instancia del
modelo de vista. Esto se muestra en la captura de pantalla de iOS de la izquierda:
Al manipular los controles deslizantes, se actualizan BoxView y Label en consecuencia, como se muestra en la
captura de pantalla de Android.
Un enfoque común consiste en crear instancias de ViewModel en el diccionario de recursos. También se pueden
crear instancias de ViewModel dentro de etiquetas de elemento de propiedad para la propiedad BindingContext .
En el archivo XAML Selector de colores simple , intente quitar HslColorViewModel del diccionario de recursos y
establézcalo en la propiedad BindingContext de Grid de esta forma:

<Grid>
<Grid.BindingContext>
<local:HslColorViewModel Color="MediumTurquoise" />
</Grid.BindingContext>

···

</Grid>

El contexto de enlace se puede establecer de varias formas. En ocasiones, el archivo de código subyacente crea
una instancia de ViewModel y la establece en la propiedad BindingContext de la página. Todos estos enfoques
son válidos.

Reemplazo del modo de enlace


Si el modo de enlace predeterminado en la propiedad de destino no es adecuado para un enlace de datos
concreto, es posible invalidarlo si se establece la propiedad Mode de Binding (o la propiedad Mode de la
extensión de marcado Binding ) en uno de los miembros de la enumeración BindingMode .
Pero establecer la propiedad Mode en TwoWay no siempre funciona como cabría esperar. Por ejemplo, intente
modificar el archivo XAML Enlace XAML alternativo para incluir TwoWay en la definición del enlace:

<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value,
Mode=TwoWay}" />

Se podría esperar que Slider se inicializara en el valor inicial de la propiedad Scale , que es 1, pero no es lo que
sucede. Cuando se inicializa un enlace TwoWay , primero se establece el destino a partir del origen, lo que significa
que la propiedad Scale se establece en el valor predeterminado de Slider de 0. Cuando se configura el enlace
TwoWay en el elemento Slider , el elemento Slider se establece inicialmente a partir del origen.

Puede establecer el modo de enlace en OneWayToSource en el ejemplo Enlace XAML alternativo :

<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value,
Mode=OneWayToSource}" />

Ahora se inicializa Slider en 1 (el valor predeterminado de Scale ) pero la manipulación de Slider no afecta a
la propiedad Scale , por lo que esto no es muy útil.

NOTE
La clase VisualElement también define las propiedades ScaleX y ScaleY , que pueden escalar el elemento
VisualElement de forma diferente en dirección horizontal y vertical.

Una aplicación muy útil de la invalidación del modo de enlace predeterminado con TwoWay implica la propiedad
SelectedItem de ListView . El modo de enlace predeterminado es OneWayToSource . Cuando se establece un
enlace de datos en la propiedad SelectedItem para hacer referencia a una propiedad de origen en un modelo de
vista, esa propiedad de origen se establece a partir de la selección ListView . Pero en algunas circunstancias, es
posible que le interese que también se inicialice ListView a partir del modelo de vista.
En la página Configuración de ejemplo se muestra esta técnica. Esta página representa una implementación
simple de la configuración de la aplicación, que a menudo se define en un modelo de vista, como en este archivo
SampleSettingsViewModel :

public class SampleSettingsViewModel : INotifyPropertyChanged


{
string name;
DateTime birthDate;
bool codesInCSharp;
double numberOfCopies;
NamedColor backgroundNamedColor;

public event PropertyChangedEventHandler PropertyChanged;

public SampleSettingsViewModel(IDictionary<string, object> dictionary)


{
Name = GetDictionaryEntry<string>(dictionary, "Name");
BirthDate = GetDictionaryEntry(dictionary, "BirthDate", new DateTime(1980, 1, 1));
CodesInCSharp = GetDictionaryEntry<bool>(dictionary, "CodesInCSharp");
NumberOfCopies = GetDictionaryEntry(dictionary, "NumberOfCopies", 1.0);
BackgroundNamedColor = NamedColor.Find(GetDictionaryEntry(dictionary, "BackgroundNamedColor",
"White"));
}

public string Name


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

public DateTime BirthDate


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

public bool CodesInCSharp


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

public double NumberOfCopies


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

public NamedColor BackgroundNamedColor


{
set
{
if (SetProperty(ref backgroundNamedColor, value))
{
OnPropertyChanged("BackgroundColor");
}
}
get { return backgroundNamedColor; }
}

public Color BackgroundColor


{
get { return BackgroundNamedColor?.Color ?? Color.White; }
}

public void SaveState(IDictionary<string, object> dictionary)


{
dictionary["Name"] = Name;
dictionary["BirthDate"] = BirthDate;
dictionary["CodesInCSharp"] = CodesInCSharp;
dictionary["NumberOfCopies"] = NumberOfCopies;
dictionary["BackgroundNamedColor"] = BackgroundNamedColor.Name;
}

T GetDictionaryEntry<T>(IDictionary<string, object> dictionary, string key, T defaultValue = default(T))


{
return dictionary.ContainsKey(key) ? (T)dictionary[key] : defaultValue;
}

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)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Cada configuración de la aplicación es una propiedad que se guarda en el diccionario de propiedades de :::no-
loc(Xamarin.Forms)::: en un método denominado SaveState y se carga desde ese diccionario al constructor. Hacia
la parte inferior de la clase hay dos métodos que ayudan a simplificar los modelos de vista y hacer que sean
menos propensos a los errores. El método OnPropertyChanged de la parte inferior tiene un parámetro opcional
que se establece en la propiedad que realiza la llamada. Esto evita errores ortográficos al especificar el nombre de
la propiedad como una cadena.
El método SetProperty de la clase hace incluso más: compara el valor que se establece en la propiedad con el
valor almacenado como un campo, y solo llama a OnPropertyChanged cuando los dos valores no son iguales.
En la clase se definen dos propiedades para el color de fondo: la propiedad
SampleSettingsViewModel
BackgroundNamedColores de tipo NamedColor , que es una clase que también se incluye en la solución
DataBindingDemos . La propiedad BackgroundColor es de tipo Color y se obtiene de la propiedad Color del
objeto NamedColor .
La clase NamedColor usa la fijación de .NET para enumerar todos los campos públicos estáticos en la estructura
Color de :::no-loc(Xamarin.Forms)::: y almacenarlos con sus nombres en una colección que sea accesible desde la
propiedad All estática:

public class NamedColor : IEquatable<NamedColor>, IComparable<NamedColor>


{
// Instance members
private NamedColor()
{
}

public string Name { private set; get; }

public string FriendlyName { private set; get; }

public Color Color { private set; get; }

public string RgbDisplay { private set; get; }

public bool Equals(NamedColor other)


{
return Name.Equals(other.Name);
}

public int CompareTo(NamedColor other)


{
return Name.CompareTo(other.Name);
}

// Static members
static NamedColor()
{
List<NamedColor> all = new List<NamedColor>();
StringBuilder stringBuilder = new StringBuilder();

// Loop through the public static fields of the Color structure.


foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields())
{
if (fieldInfo.IsPublic &&
fieldInfo.IsStatic &&
fieldInfo.FieldType == typeof(Color))
{
// Convert the name to a friendly name.
string name = fieldInfo.Name;
stringBuilder.Clear();
int index = 0;

foreach (char ch in name)


{
if (index != 0 && Char.IsUpper(ch))
{
stringBuilder.Append(' ');
}
}
stringBuilder.Append(ch);
index++;
}

// Instantiate a NamedColor object.


Color color = (Color)fieldInfo.GetValue(null);

NamedColor namedColor = new NamedColor


{
Name = name,
FriendlyName = stringBuilder.ToString(),
Color = color,
RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
(int)(255 * color.R),
(int)(255 * color.G),
(int)(255 * color.B))
};

// Add it to the collection.


all.Add(namedColor);
}
}
all.TrimExcess();
all.Sort();
All = all;
}

public static IList<NamedColor> All { private set; get; }

public static NamedColor Find(string name)


{
return ((List<NamedColor>)All).Find(nc => nc.Name == name);
}

public static string GetNearestColorName(Color color)


{
double shortestDistance = 1000;
NamedColor closestColor = null;

foreach (NamedColor namedColor in NamedColor.All)


{
double distance = Math.Sqrt(Math.Pow(color.R - namedColor.Color.R, 2) +
Math.Pow(color.G - namedColor.Color.G, 2) +
Math.Pow(color.B - namedColor.Color.B, 2));

if (distance < shortestDistance)


{
shortestDistance = distance;
closestColor = namedColor;
}
}
return closestColor.Name;
}
}

En la clase del proyecto DataBindingDemos se define una propiedad denominada Settings de tipo
App
SampleSettingsViewModel . Esta propiedad se inicializa cuando se crea una instancia de la clase App y el método
SaveState se llama cuando se llama al método OnSleep :
public partial class App : Application
{
public App()
{
InitializeComponent();

Settings = new SampleSettingsViewModel(Current.Properties);

MainPage = new NavigationPage(new MainPage());


}

public SampleSettingsViewModel Settings { private set; get; }

protected override void OnStart()


{
// Handle when your app starts
}

protected override void OnSleep()


{
// Handle when your app sleeps
Settings.SaveState(Current.Properties);
}

protected override void OnResume()


{
// Handle when your app resumes
}
}

Para obtener más información sobre los métodos de ciclo de vida de aplicación, vea el artículo Ciclo de vida de
aplicación .
Prácticamente todo lo demás se controla en el archivo SampleSettingsPage.xaml . El elemento BindingContext
de la página se establece mediante una extensión de marcado Binding : el origen de enlace es la propiedad
estática Application.Current , que es la instancia de la clase App del proyecto, y Path se establece en la
propiedad Settings , que es el objeto SampleSettingsViewModel :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.SampleSettingsPage"
Title="Sample Settings"
BindingContext="{Binding Source={x:Static Application.Current},
Path=Settings}">

<StackLayout BackgroundColor="{Binding BackgroundColor}"


Padding="10"
Spacing="10">

<StackLayout Orientation="Horizontal">
<Label Text="Name: "
VerticalOptions="Center" />

<Entry Text="{Binding Name}"


Placeholder="your name"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center" />
</StackLayout>

<StackLayout Orientation="Horizontal">
<Label Text="Birth Date: "
VerticalOptions="Center" />

<DatePicker Date="{Binding BirthDate}"


<DatePicker Date="{Binding BirthDate}"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center" />
</StackLayout>

<StackLayout Orientation="Horizontal">
<Label Text="Do you code in C#? "
VerticalOptions="Center" />

<Switch IsToggled="{Binding CodesInCSharp}"


VerticalOptions="Center" />
</StackLayout>

<StackLayout Orientation="Horizontal">
<Label Text="Number of Copies: "
VerticalOptions="Center" />

<Stepper Value="{Binding NumberOfCopies}"


VerticalOptions="Center" />

<Label Text="{Binding NumberOfCopies}"


VerticalOptions="Center" />
</StackLayout>

<Label Text="Background Color:" />

<ListView x:Name="colorListView"
ItemsSource="{x:Static local:NamedColor.All}"
SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"
VerticalOptions="FillAndExpand"
RowHeight="40">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
HeightRequest="32"
WidthRequest="32"
VerticalOptions="Center" />

<Label Text="{Binding FriendlyName}"


FontSize="24"
VerticalOptions="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

Todos los elementos secundarios de la página heredan el contexto de enlace. La mayoría de los demás enlaces de
esta página son para las propiedades de SampleSettingsViewModel . La propiedad BackgroundColor se usa para
establecer la propiedad BackgroundColor de StackLayout , y las propiedades Entry , DatePicker , Switch y
Stepper se enlazan a otras propiedades del modelo de vista.

La propiedad ItemsSource de ListView se establece en la propiedad estática NamedColor.All . Esto rellena


ListView con todas las instancias de NamedColor . Para cada elemento de ListView , el contexto de enlace para el
elemento se establece en un objeto NamedColor . Los elementos BoxView y Label de ViewCell están enlazados a
propiedades de NamedColor .
La propiedad SelectedItem de ListView es de tipo NamedColor y se enlaza a la propiedad BackgroundNamedColor
de SampleSettingsViewModel :
SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"

El modo de enlace predeterminado para SelectedItem es OneWayToSource , que establece la propiedad ViewModel
a partir del elemento seleccionado. El modo TwoWay permite que se inicialice SelectedItem a partir del modelo
de vista.
Pero cuando SelectedItem se establece de esta forma, ListView no se desplaza automáticamente para mostrar
el elemento seleccionado. Se necesita un poco de código en el archivo de código subyacente:

public partial class SampleSettingsPage : ContentPage


{
public SampleSettingsPage()
{
InitializeComponent();

if (colorListView.SelectedItem != null)
{
colorListView.ScrollTo(colorListView.SelectedItem,
ScrollToPosition.MakeVisible,
false);
}
}
}

En la captura de pantalla de iOS de la izquierda se muestra el programa al ejecutarlo por primera vez. El
constructor de SampleSettingsViewModel inicializa el color de fondo en blanco, y eso es lo que está seleccionado
en ListView :

La otra captura de pantalla muestra la configuración modificada. Al experimentar con esta página, recuerde
colocar el programa en modo de suspensión o finalícelo en el dispositivo o emulador que se está ejecutando. La
finalización del programa desde el depurador de Visual Studio no provocará que se llame a la invalidación de
OnSleep en la clase App .

En el artículo siguiente verá cómo especificar los formatos de cadena de los enlaces de datos que se establecen
en la propiedad Text de Label .

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Formato de cadena de :::no-loc(Xamarin.Forms):::
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
A veces es conveniente usar enlaces de datos para mostrar la representación de cadena de un objeto o valor. Por
ejemplo, es posible que quiera utilizar una Label para mostrar el valor actual de un Slider . En este enlace de
datos, Slider es el origen y el destino es la propiedad Text de Label .
Cuando se muestran las cadenas en el código, la herramienta más eficaz es el método String.Format estático. La
cadena de formato incluye códigos específicos para distintos tipos de objetos de formato, y puede incluir otros
textos junto con los valores a los que se va a aplicar formato. Para obtener más información sobre el formato de
cadena, vea Aplicar formato a tipos en .NET.

La propiedad StringFormat
Este recurso se transmite a los enlaces de datos: usted establece la propiedad StringFormat de Binding (o la
propiedad StringFormat de la extensión de marcado Binding ) a una cadena de formato .NET estándar con un
marcador de posición:

<Slider x:Name="slider" />


<Label Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='The slider value is {0:F2}'}" />

Tenga en cuenta que la cadena de formato está delimitada por caracteres de comillas simples (apóstrofo) para
ayudar al analizador XAML a evitar que trate las llaves como otra extensión de marcado XAML. En caso contrario,
esa cadena sin el carácter de comillas simples es la misma cadena que utilizaría para mostrar un valor de punto
flotante en una llamada a String.Format . Una especificación de formato de F2 hace que el valor se muestre con
dos posiciones decimales.
La propiedad StringFormat sólo tiene sentido cuando la propiedad de destino es de tipo string , y el modo de
enlace es OneWay o TwoWay . Para los enlaces bidireccionales, el StringFormat solo es aplicable para los valores
que se pasan desde el origen al destino.
Como verá en el artículo Xamarin.Forms Binding Path (Ruta de acceso de Xamarin.Forms), los enlaces de datos
pueden llegar a ser bastante complejos y enrevesados. Al depurar estos enlaces de datos, puede agregar una
Label en el archivo XAML con un StringFormat para mostrar algunos resultados intermedios. Puede resultar útil
incluso si se usa solo para mostrar un tipo de objeto.
La página Formato de cadena muestra varios usos de la propiedad StringFormat :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="DataBindingDemos.StringFormattingPage"
Title="String Formatting">

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>

<Style TargetType="BoxView">
<Setter Property="Color" Value="Blue" />
<Setter Property="HeightRequest" Value="2" />
<Setter Property="Margin" Value="0, 5" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Margin="10">
<Slider x:Name="slider" />
<Label Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='The slider value is {0:F2}'}" />

<BoxView />

<TimePicker x:Name="timePicker" />


<Label Text="{Binding Source={x:Reference timePicker},
Path=Time,
StringFormat='The TimeSpan is {0:c}'}" />

<BoxView />

<Entry x:Name="entry" />


<Label Text="{Binding Source={x:Reference entry},
Path=Text,
StringFormat='The Entry text is &quot;{0}&quot;'}" />

<BoxView />

<StackLayout BindingContext="{x:Static sys:DateTime.Now}">


<Label Text="{Binding}" />
<Label Text="{Binding Path=Ticks,
StringFormat='{0:N0} ticks since 1/1/1'}" />
<Label Text="{Binding StringFormat='The {{0:MMMM}} specifier produces {0:MMMM}'}" />
<Label Text="{Binding StringFormat='The long date is {0:D}'}" />
</StackLayout>

<BoxView />

<StackLayout BindingContext="{x:Static sys:Math.PI}">


<Label Text="{Binding}" />
<Label Text="{Binding StringFormat='PI to 4 decimal points = {0:F4}'}" />
<Label Text="{Binding StringFormat='PI in scientific notation = {0:E7}'}" />
</StackLayout>
</StackLayout>
</ContentPage>

Los enlaces en el Slider y el TimePicker muestran el uso de las especificaciones de formato específicas de los
tipos de datos double y TimeSpan . El StringFormat que muestra el texto de la vista Entry muestra cómo
especificar las comillas dobles en la cadena de formato con el uso de la entidad HTML &quot; .
La siguiente sección en el archivo XAML es un StackLayout con un BindingContext establecido en una extensión
de marcado de x:Static que hace referencia a la propiedad DateTime.Now estática. El primer enlace no tiene
propiedades:

<Label Text="{Binding}" />

Esto muestra simplemente el valor DateTime de BindingContext con el formato predeterminado. El segundo
enlace muestra la propiedad Ticks de DateTime , mientras que los otros dos enlaces muestran el propio
DateTime con un formato específico. Observe este StringFormat :

<Label Text="{Binding StringFormat='The {{0:MMMM}} specifier produces {0:MMMM}'}" />

Si necesita mostrar llaves a la izquierda o la derecha en la cadena de formato, simplemente use un par de ellas.
Los conjuntos de la última sección establecen el BindingContext al valor de Math.PI y lo muestran con formato
de forma predeterminada y dos tipos diferentes de formato numérico.
Esta es la ejecución del programa:

ViewModels y formato de cadena


Cuando se usa Label y StringFormat para mostrar el valor de una vista que también es el destino de una clase
ViewModel, puede definir el enlace de la vista para Label o desde el ViewModel a Label . En general, el segundo
enfoque es mejor porque comprueba que funcionan los enlaces entre View y ViewModel.
Este método se muestra en el ejemplo Mejor selector de colores , que usa el mismo ViewModel que el
programa Selector de colores simple que aparece en el artículo Xamarin.Forms Binding Mode (Modo de
enlace de Xamarin.Forms):
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.BetterColorSelectorPage"
Title="Better Color Selector">

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>

<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout>
<StackLayout.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</StackLayout.BindingContext>

<BoxView Color="{Binding Color}"


VerticalOptions="FillAndExpand" />

<StackLayout Margin="10, 0">


<Label Text="{Binding Name}" />

<Slider Value="{Binding Hue}" />


<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />

<Slider Value="{Binding Saturation}" />


<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />

<Slider Value="{Binding Luminosity}" />


<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</ContentPage>

Ahora hay tres pares de elementos Slider y Label que están enlazados a la misma propiedad de origen en el
objeto HslColorViewModel . La única diferencia es que Label tiene una propiedad StringFormat para mostrar
cada valor Slider .
Tal vez se pregunte cómo podría mostrar sus valores RGB (rojos, verdes y azules) en formato hexadecimal de dos
dígitos tradicional. Los valores enteros no están directamente disponibles desde la estructura de Color . Una
solución sería calcular valores enteros de los componentes de color dentro de ViewModel y exponerlos como
propiedades. Después puede aplicarles formato utilizando la especificación de formato X2 .
Otro enfoque es más general: puede escribir un convertidor de valores de enlace como se describe en el siguiente
artículo, Conver tidor de valores de enlace de Xamarin.Forms .
Empero, el siguiente artículo explora la Ruta de enlace con más detalle y muestra cómo se puede utilizar para
hacer referencia a elementos de las colecciones y subpropiedades.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Enlace de ruta de acceso de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
En todos los ejemplos de enlace de datos anteriores, la propiedad Path de la clase Binding (o la propiedad Path
de la extensión de marcado Binding ) se había establecido en una sola propiedad. En realidad es posible
establecer Path en una subpropiedad (una propiedad de una propiedad), o bien en un miembro de una
colección.
Por ejemplo, suponga que la página contiene un control TimePicker :

<TimePicker x:Name="timePicker">

La propiedad Time de TimePicker es de tipo TimeSpan , pero es posible que quiera crear un enlace de datos que
haga referencia a la propiedad TotalSeconds de ese valor TimeSpan . Este es el enlace de datos:

{Binding Source={x:Reference timePicker},


Path=Time.TotalSeconds}

La propiedad Time es de tipo TimeSpan , que tiene una propiedad TotalSeconds . Las propiedades Time y
TotalSeconds se conectan simplemente con un punto. Los elementos de la cadena Path siempre hacen
referencia a propiedades y no a los tipos de estas propiedades.
Ese y otros muchos ejemplos se muestran en la página Variaciones de la ruta de acceso :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:globe="clr-namespace:System.Globalization;assembly=netstandard"
x:Class="DataBindingDemos.PathVariationsPage"
Title="Path Variations"
x:Name="page">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Margin="10, 0">


<TimePicker x:Name="timePicker" />

<Label Text="{Binding Source={x:Reference timePicker},


Path=Time.TotalSeconds,
StringFormat='{0} total seconds'}" />

<Label Text="{Binding Source={x:Reference page},


Path=Content.Children.Count,
StringFormat='There are {0} children in this StackLayout'}" />

<Label Text="{Binding Source={x:Static globe:CultureInfo.CurrentCulture},


Path=DateTimeFormat.DayNames[3],
StringFormat='The middle day of the week is {0}'}" />

<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
StringFormat="The middle day of the week in France is {0}">
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>

<Label Text="{Binding Source={x:Reference page},


Path=Content.Children[1].Text.Length,
StringFormat='The second Label has {0} characters'}" />
</StackLayout>
</ContentPage>

En el segundo control Label , el origen de enlace es la propia página. La propiedad Content es de tipo
StackLayout , que tiene una propiedad Children de tipo IList<View> , que a su vez tiene una propiedad Count
que indica el número de elementos secundarios.

Rutas de acceso con indizadores


El enlace en el tercer control Label de las páginas Variaciones de la ruta de acceso hace referencia a la clase
CultureInfo del espacio de nombres System.Globalization :
<Label Text="{Binding Source={x:Static globe:CultureInfo.CurrentCulture},
Path=DateTimeFormat.DayNames[3],
StringFormat='The middle day of the week is {0}'}" />

El origen se establece en la propiedad estática CultureInfo.CurrentCulture , que es un objeto de tipo CultureInfo .


Esa clase define una propiedad denominada DateTimeFormat de tipo DateTimeFormatInfo que contiene una
colección DayNames . El índice selecciona el cuarto elemento.
El cuarto control Label hace algo similar pero para la referencia cultural asociada con Francia. La propiedad
Source del enlace se establece en el objeto CultureInfo con un constructor:

<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
StringFormat="The middle day of the week in France is {0}">
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>

Vea Pasar argumentos de constructor para obtener más información sobre cómo especificar argumentos de
constructor en XAML.
El último ejemplo es similar al segundo, salvo que hace referencia a uno de los elementos secundarios del
elemento StackLayout :

<Label Text="{Binding Source={x:Reference page},


Path=Content.Children[1].Text.Length,
StringFormat='The first Label has {0} characters'}" />

Ese elemento secundario es un control Label , que tiene una propiedad Text de tipo String , que a su vez tiene
una propiedad Length . El primer objeto Label notifica el valor TimeSpan establecido en el elemento TimePicker ,
de modo que cuando se cambia el texto, el último objeto Label también cambia.
Esta es la ejecución del programa:
Depuración de rutas de acceso complejas
Las definiciones de ruta de acceso complejas pueden ser difíciles de construir: necesita saber el tipo de cada
subpropiedad o el tipo de los elementos de la colección para agregar correctamente la subpropiedad siguiente,
pero los propios tipos no aparecen en la ruta de acceso. Una técnica adecuada consiste en crear la ruta de acceso
de forma incremental y observar los resultados intermedios. Para ese último ejemplo, se podría empezar sin
ninguna definición de Path :

<Label Text="{Binding Source={x:Reference page},


StringFormat='{0}'}" />

Eso muestra el tipo del origen de enlace, o DataBindingDemos.PathVariationsPage . Ya sabe que PathVariationsPage
se deriva de ContentPage , por lo que tiene una propiedad Content :

<Label Text="{Binding Source={x:Reference page},


Path=Content,
StringFormat='{0}'}" />

Ahora, se revela que el tipo de la propiedad Content es :::no-loc(Xamarin.Forms):::.StackLayout . Agregue la


propiedad Children a Path y el tipo será
:::no-loc(Xamarin.Forms):::.ElementCollection'1[:::no-loc(Xamarin.Forms):::.View] , que es una clase interna para
:::no-loc(Xamarin.Forms):::, pero obviamente un tipo de colección. Agregue un índice a lo anterior y el tipo será
:::no-loc(Xamarin.Forms):::.Label . Continúe de esta manera.

A medida que :::no-loc(Xamarin.Forms)::: procesa la ruta de acceso de enlace, instala un controlador


PropertyChanged en todos los objetos de la ruta de acceso que implementen la interfaz INotifyPropertyChanged .
Por ejemplo, el enlace final reacciona ante un cambio en el primer objeto Label porque cambia la propiedad
Text .

Si una propiedad en la ruta de acceso de enlace no implementa INotifyPropertyChanged , se omitirán todos los
cambios a esa propiedad. Algunos cambios podrían invalidar completamente la ruta de acceso de enlace, por lo
que solo debe usar esta técnica cuando la cadena de propiedades y subpropiedades nunca sea no válida.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Enlace de convertidores de valores de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
Los enlaces de datos normalmente transfieren datos desde una propiedad de origen a una propiedad de destino y,
en algunos casos, desde la propiedad de destino a la propiedad de origen. Esta transferencia es sencilla cuando las
propiedades de origen y destino son del mismo tipo, o cuando un tipo se puede convertir al otro mediante una
conversión implícita. Cuando no es así, debe realizarse una conversión de tipos.
En el artículo String Formatting (Formato de cadena), vio cómo puede usar la propiedad StringFormat de un
enlace de datos para convertir cualquier tipo en una cadena. Para otros tipos de conversiones, deberá escribir
código especializado en una clase que implementa la interfaz de IValueConverter . (La Plataforma universal de
Windows contiene una clase similar denominada IValueConverter en el espacio de nombres
Windows.UI.Xaml.Data , pero este IValueConverter está en el espacio de nombres :::no-loc(Xamarin.Forms)::: .)
Las clases que implementan IValueConverter se denominan convertidores de valores , pero también se
denominan a menudo convertidores de enlaces o convertidores de valores de enlace.

La interfaz de IValueConverter
Suponga que desea definir un enlace de datos cuya propiedad de origen es del tipo int pero la propiedad de
destino es un bool . Desea que este enlace de datos genere un valor false cuando el origen del entero es igual a
0 y true en caso contrario.
Puede hacerlo con una clase que implementa la interfaz IValueConverter :

public class IntToBoolConverter : IValueConverter


{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 0;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}

Establezca una instancia de esta clase en la propiedad Converter de la clase Binding o en la propiedad
Converter de la extensión de marcado Binding . Esta clase se convierte en parte del enlace de datos.

Se llama al método Convert cuando los datos se mueven desde el origen al destino en los enlaces OneWay o
TwoWay . El parámetro value es el objeto o el valor del origen de enlace de datos. El método debe devolver un
valor del tipo del destino de enlace de datos. El método que se muestra aquí convierte el parámetro value a un
int y después lo compara con 0 para un valor devuelto bool .

Se llama al método ConvertBack cuando los datos se mueven desde el destino al origen en los enlaces TwoWay o
OneWayToSource . ConvertBack realiza la conversión opuesta: supone que el parámetro value es un bool desde el
destino y lo convierte en un valor devuelto int para el origen.
Si el enlace de datos también incluye una configuración StringFormat , se invoca el convertidor de valores antes
de que se le dé formato de cadena al resultado.
La página Enable Buttons (Habilitar botones) en el ejemplo de Data Binding Demos (Demostraciones de
enlace de datos) muestra cómo utilizar este convertidor de valores en un enlace de datos. Se crea una instancia de
IntToBoolConverter en el diccionario de recursos de la página. Después se le hace referencia con una extensión de
marcado StaticResource para establecer la propiedad Converter en dos enlaces de datos. Es muy común
compartir los convertidores de tipos de datos entre varios enlaces de datos en la página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.EnableButtonsPage"
Title="Enable Buttons">
<ContentPage.Resources>
<ResourceDictionary>
<local:IntToBoolConverter x:Key="intToBool" />
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Padding="10, 0">


<Entry x:Name="entry1"
Text=""
Placeholder="enter search term"
VerticalOptions="CenterAndExpand" />

<Button Text="Search"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />

<Entry x:Name="entry2"
Text=""
Placeholder="enter destination"
VerticalOptions="CenterAndExpand" />

<Button Text="Submit"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry2},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
</StackLayout>
</ContentPage>

Si se usa un convertidor de valores en varias páginas de la aplicación, puede crear una instancia de él en el
diccionario de recursos en el archivo App.xaml .
La página Enable Buttons (Habilitar botones) muestra una necesidad común cuando un Button realiza una
operación basada en texto que el usuario escribe en un vista Entry . Si no se ha escrito nada en el Entry , el
Button debe deshabilitarse. Cada Button contiene un enlace de datos en su propiedad IsEnabled . El origen de
enlace de datos es la propiedad Length de la propiedad Text de la Entry correspondiente. Si esa propiedad
Length no es 0, el convertidor de valores devuelve true y se habilita el Button :
Tenga en cuenta que la propiedad Text en cada Entry se inicializa en una cadena vacía. La propiedad Text es
null de forma predeterminada, y los datos de enlace no funcionarán en ese caso.

Algunos convertidores de valores se escriben específicamente para determinadas aplicaciones, mientras que otros
están generalizados. Si sabe que un convertidor de valores solo se usará en los enlaces OneWay , el método
ConvertBack puede devolver simplemente null .

El método Convert mostrado anteriormente supone implícitamente que el argumento value es de tipo int y el
valor devuelto debe ser de tipo bool . De forma similar, el método ConvertBack supone que el argumento value
es de tipo bool y el valor devuelto es int . Si no es así, se producirá una excepción en tiempo de ejecución.
Puede escribir los convertidores de valores para que sean más generalizados y acepten diferentes tipos de datos.
Los métodos Convert y ConvertBack pueden usar los operadores as o is con el parámetro value , o pueden
llamar a GetType en ese parámetro para determinar su tipo y después realizar algo adecuado. El tipo esperado del
valor devuelto de cada método viene dado por el parámetro targetType . A veces, los convertidores de valores se
utilizan con los enlaces de datos de diferentes tipos de destino; el convertidor de valores puede usar el argumento
targetType para realizar una conversión del tipo correcto.

Si la conversión que se realiza es diferente para distintas referencias culturales, utilice el parámetro culture para
este propósito. El argumento parameter para Convert y ConvertBack se explica más adelante en este artículo.

Propiedades de convertidor de tipos de enlace


Las clases de convertidor de valores pueden tener propiedades y parámetros genéricos. Este convertidor de
valores determinado convierte un bool desde el origen a un objeto de tipo T para el destino:
public class BoolToObjectConverter<T> : IValueConverter
{
public T TrueObject { set; get; }

public T FalseObject { set; get; }

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? TrueObject : FalseObject;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((T)value).Equals(TrueObject);
}
}

La página Switch Indicators (Indicadores de conmutador) muestra cómo puede usarse para mostrar el valor de
una vista Switch . Aunque es común crear instancias de los convertidores de valores como recursos en un
diccionario de recursos, esta página muestra una alternativa: se crea una instancia de cada convertidor de valores
entre etiquetas de elemento de propiedad Binding.Converter . El x:TypeArguments indica el argumento genérico, y
TrueObject y FalseObject se establecen en objetos de ese tipo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.SwitchIndicatorsPage"
Title="Switch Indicators">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>

<Style TargetType="Switch">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Padding="10, 0">


<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Subscribe?" />
<Switch x:Name="switch1" />
<Label>
<Label.Text>
<Binding Source="{x:Reference switch1}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Of course!"
FalseObject="No way!" />
</Binding.Converter>
</Binding>
</Label.Text>
</Label>
</StackLayout>

<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Allow popups?" />
<Switch x:Name="switch2" />
<Label>
<Label>
<Label.Text>
<Binding Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Yes"
FalseObject="No" />
</Binding.Converter>
</Binding>
</Label.Text>
<Label.TextColor>
<Binding Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Color"
TrueObject="Green"
FalseObject="Red" />
</Binding.Converter>
</Binding>
</Label.TextColor>
</Label>
</StackLayout>

<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Learn more?" />
<Switch x:Name="switch3" />
<Label FontSize="18"
VerticalOptions="Center">
<Label.Style>
<Binding Source="{x:Reference switch3}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Style">
<local:BoolToObjectConverter.TrueObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Indubitably!" />
<Setter Property="FontAttributes" Value="Italic, Bold" />
<Setter Property="TextColor" Value="Green" />
</Style>
</local:BoolToObjectConverter.TrueObject>

<local:BoolToObjectConverter.FalseObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Maybe later" />
<Setter Property="FontAttributes" Value="None" />
<Setter Property="TextColor" Value="Red" />
</Style>
</local:BoolToObjectConverter.FalseObject>
</local:BoolToObjectConverter>
</Binding.Converter>
</Binding>
</Label.Style>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>

En el último de los tres pares Switch y Label , el argumento genérico se establece en Style y se proporcionan
objetos Style completos para los valores de TrueObject y FalseObject . Esto ignora el estilo implícito para
Label que se establece en el diccionario de recursos, por lo que las propiedades de ese estilo están asignadas
explícitamente a la Label . Activar o desactivar el Switch hace que el correspondiente Label refleje el cambio:
También es posible usar Triggers para implementar cambios similares en la interfaz de usuario en función de
otras vistas.

Parámetros de convertidor de tipos de enlace


La clase Binding define una propiedad ConverterParameter y la extensión de marcado Binding también define
una propiedad ConverterParameter . Si se establece esta propiedad, el valor se pasa a los métodos Convert y
ConvertBack como el argumento parameter . Incluso si la instancia del convertidor se comparte entre varios
enlaces de datos, el ConverterParameter puede ser diferente para realizar conversiones algo diferentes.
El uso de ConverterParameter se muestra con un programa de selección de color. En este caso, el
RgbColorViewModel tiene tres propiedades de tipo double denominadas Red , Green y Blue que usa para
construir un valor Color :

public class RgbColorViewModel : INotifyPropertyChanged


{
Color color;
string name;

public event PropertyChangedEventHandler PropertyChanged;

public double Red


{
set
{
if (color.R != value)
{
Color = new Color(value, color.G, color.B);
}
}
get
{
return color.R;
}
}

public double Green


{
set
{
if (color.G != value)
{
Color = new Color(color.R, value, color.B);
}
}
get
{
return color.G;
}
}

public double Blue


{
set
{
if (color.B != value)
{
Color = new Color(color.R, color.G, value);
}
}
get
{
return color.B;
}
}

public Color Color


{
set
{
if (color != value)
{
color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Red"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Green"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Blue"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

Name = NamedColor.GetNearestColorName(color);
}
}
get
{
return color;
}
}

public string Name


{
private set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
get
{
return name;
}
}
}

Las propiedades Red , Green y Blue oscilan entre 0 y 1. Con todo, es preferible que los componentes se
muestren como valores hexadecimales de dos dígitos.
Para mostrar estos elementos como valores hexadecimales en XAML, deben multiplicarse por 255, convertirse en
un entero y después debe aplicárseles formato con la especificación de "X2" en la propiedad StringFormat . Las
dos primeras tareas (multiplicar por 255 y convertir en un entero) pueden controlarse mediante el convertidor de
valores. Para hacer el convertidor de valores tan generalizado como sea posible, puede especificarse el factor de
multiplicación con la propiedad ConverterParameter , lo que significa que introduce los métodos Convert y
ConvertBack como el argumento parameter :

public class DoubleToIntConverter : IValueConverter


{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Round((double)value * GetParameter(parameter));
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value / GetParameter(parameter);
}

double GetParameter(object parameter)


{
if (parameter is double)
return (double)parameter;

else if (parameter is int)


return (int)parameter;

else if (parameter is string)


return double.Parse((string)parameter);

return 1;
}
}

El Convert convierte de un double a int al multiplicar por el valor parameter ; el ConvertBack divide el
argumento value entero entre parameter y devuelve un resultado double . (En el programa que se muestra
debajo, el convertidor de valores se usa solo en relación con el formato de cadenas, por lo que ConvertBack no se
usa.)
Es probable que el tipo del argumento parameter sea diferente en función de si se ha definido el enlace de datos
en el código o en XAML. Si la propiedad ConverterParameter de Binding se establece en código, es probable que
se establezca en un valor numérico:

binding.ConverterParameter = 255;

La propiedad ConverterParameter es de tipo Object , por lo que el compilador C# interpreta el literal 255 como un
entero y establece la propiedad en ese valor.
Pero en XAML es probable que el ConverterParameter se establezca de este modo:

<Label Text="{Binding Red,


Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />

El 255 parece un número, pero dado que ConverterParameter es de tipo Object , el analizador XAML trata el 255
como una cadena.
Por ese motivo, el convertidor de valores mostrado anteriormente incluye un método GetParameter
independiente que controla los casos para parameter de tipo double , int o string .
La página RGB Color Selector (Selector de colores RGB) crea una instancia de la página DoubleToIntConverter
en su diccionario de recursos siguiendo la definición de dos estilos implícitos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.RgbColorSelectorPage"
Title="RGB Color Selector">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>

<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>

<local:DoubleToIntConverter x:Key="doubleToInt" />


</ResourceDictionary>
</ContentPage.Resources>

<StackLayout>
<StackLayout.BindingContext>
<local:RgbColorViewModel Color="Gray" />
</StackLayout.BindingContext>

<BoxView Color="{Binding Color}"


VerticalOptions="FillAndExpand" />

<StackLayout Margin="10, 0">


<Label Text="{Binding Name}" />

<Slider Value="{Binding Red}" />


<Label Text="{Binding Red,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />

<Slider Value="{Binding Green}" />


<Label Text="{Binding Green,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Green = {0:X2}'}" />

<Slider Value="{Binding Blue}" />


<Label>
<Label.Text>
<Binding Path="Blue"
StringFormat="Blue = {0:X2}"
Converter="{StaticResource doubleToInt}">
<Binding.ConverterParameter>
<x:Double>255</x:Double>
</Binding.ConverterParameter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>

Los valores de las propiedades Red y Green se muestran con una extensión de marcado Binding . Empero, la
propiedad Blue crea una instancia de la clase Binding para demostrar cómo un valor double explícito puede
establecerse en la propiedad ConverterParameter .
Este es el resultado:

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Enlaces relativos de :::no-loc(Xamarin.Forms):::
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
Los enlaces relativos proporcionan la capacidad de establecer el origen de enlace en relación con la posición del
destino de enlace. Se crean con la extensión de marcado RelativeSource y se establecen como la propiedad
Source de una expresión de enlace.

La clase RelativeSourceExtension admite la extensión de marcado RelativeSource , que define las siguientes
propiedades:
Mode , del tipo RelativeBindingSourceMode , describe la ubicación del origen de enlace en relación con la
posición del destino de enlace.
AncestorLevel , del tipo int , un nivel de antecesor opcional que se va a buscar, cuando la propiedad Mode es
FindAncestor . Un AncestorLevel de n omite n-1 instancias del AncestorType .
AncestorType , del tipo Type , el tipo de antecesor que se va a buscar, cuando la propiedad Mode es
FindAncestor .

NOTE
El analizador de XAML permite abreviar la clase RelativeSourceExtension como RelativeSource .

La propiedad Mode se debe establecer en uno de los miembros de enumeración RelativeBindingSourceMode .


TemplatedParent indica el elemento al que se aplica la plantilla, en el que existe el elemento enlazado. Para
más información, vea Enlace a un elemento primario con plantilla.
Self indica el elemento en el que se está estableciendo el enlace, lo cual le permite enlazar una propiedad de
ese elemento a otra propiedad del mismo elemento. Para más información, vea Enlace a sí mismo.
FindAncestor indica el antecesor en el árbol visual del elemento enlazado. Este modo se debe utilizar para
enlazar a un control de antecesor representado por la propiedad AncestorType . Para más información, vea
Enlace a un antecesor.
FindAncestorBindingContext indica el BindingContext del antecesor en el árbol visual del elemento enlazado.
Este modo se debe utilizar para enlazar al BindingContext de un antecesor representado por la propiedad
AncestorType . Para más información, vea Enlace a un antecesor.

La propiedad Mode es la propiedad de contenido de la clase RelativeSourceExtension . Por lo tanto, para las
expresiones de marcado XAML expresadas con llaves, puede eliminar la parte Mode= de la expresión.
Para más información sobre las extensiones de marcado de :::no-loc(Xamarin.Forms):::, vea Extensiones de
marcado para el lenguaje XAML.

Enlazar a sí mismo
El modo de enlace relativo Self se utiliza para enlazar una propiedad de un elemento a otra propiedad del
mismo elemento:
<BoxView Color="Red"
WidthRequest="200"
HeightRequest="{Binding Source={RelativeSource Self}, Path=WidthRequest}"
HorizontalOptions="Center" />

En este ejemplo, BoxView establece su propiedad WidthRequest en un tamaño fijo y la propiedad HeightRequest
se enlaza a la propiedad WidthRequest . Por lo tanto, ambas propiedades son iguales y, de este modo, se dibuja
un cuadrado:

IMPORTANT
Al enlazar una propiedad de un elemento a otra propiedad del mismo elemento, las propiedades deben ser del mismo tipo.
También puede especificar un convertidor en el enlace para convertir el valor.

Un uso común de este modo de enlace es establecer la BindingContext de un objeto en una propiedad en sí
misma. El código siguiente muestra un ejemplo de esto:

<ContentPage ...
BindingContext="{Binding Source={RelativeSource Self}, Path=DefaultViewModel}">
<StackLayout>
<ListView ItemsSource="{Binding Employees}">
...
</ListView>
</StackLayout>
</ContentPage>

En este ejemplo, el BindingContext de la página se establece en la propiedad DefaultViewModel de sí mismo. Esta


propiedad se define en el archivo de código subyacente de la página y proporciona una instancia de ViewModel.
El ListView se enlaza a la propiedad Employees de ViewModel.

Enlazar a un antecesor
Los modos de enlace relativo FindAncestor y FindAncestorBindingContext se usan para enlazar a elementos
primarios de un tipo determinado en el árbol visual. El modo FindAncestor se utiliza para enlazar a un elemento
primario, que se deriva del tipo Element . El modo FindAncestorBindingContext se utiliza para enlazar a la
BindingContext de un elemento primario.

WARNING
La propiedad se debe establecer en un Type al utilizar los modos de enlace relativo
AncestorType FindAncestor y
FindAncestorBindingContext , de lo contrario se produce una XamlParseException .

Si no se establece explícitamente la propiedad Mode , al establecer la propiedad AncestorType en un tipo que se


deriva de Element se establecerá implícitamente la propiedad Mode en FindAncestor . Del mismo modo, si se
establece la propiedad AncestorType en un tipo que no se deriva de Element , se establecerá implícitamente la
propiedad Mode en FindAncestorBindingContext .

NOTE
Los enlaces relativos que usan el modo de FindAncestorBindingContext se volverán a aplicar cuando cambie la
BindingContext de cualquier antecesor.

En el código XAML siguiente se muestra un ejemplo en el que la propiedad Mode se establecerá implícitamente
en FindAncestorBindingContext :

<ContentPage ...
BindingContext="{Binding Source={RelativeSource Self}, Path=DefaultViewModel}">
<StackLayout>
<ListView ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Fullname}"
VerticalOptions="Center" />
<Button Text="Delete"
Command="{Binding Source={RelativeSource AncestorType={x:Type
local:PeopleViewModel}}, Path=DeleteEmployeeCommand}"
CommandParameter="{Binding}"
HorizontalOptions="EndAndExpand" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

En este ejemplo, el BindingContext de la página se establece en la propiedad DefaultViewModel de sí mismo. Esta


propiedad se define en el archivo de código subyacente de la página y proporciona una instancia de ViewModel.
El ListView se enlaza a la propiedad Employees de ViewModel. El DataTemplate , que define la apariencia de
cada elemento del ListView , contiene una Button . La propiedad Command del botón está enlazada al
DeleteEmployeeCommand en el ViewModel de su elemento primario. Al pulsar un Button se elimina un empleado:

Además, la propiedad AncestorLevel opcional puede ayudar a eliminar la ambigüedad en la búsqueda del
antecesor en aquellos escenarios en que sea posible que haya más de un antecesor de ese tipo en el árbol visual.

<Label Text="{Binding Source={RelativeSource AncestorType={x:Type Entry}, AncestorLevel=2}, Path=Text}" />

En este ejemplo, la propiedad Label.Text se enlaza a la propiedad Text de la segunda Entry que se encuentra
en la ruta de acceso ascendente, comenzando en el elemento de destino del enlace.
NOTE
La propiedad AncestorLevel debe establecerse en 1 para buscar el antecesor más próximo al elemento de destino de
enlace.

Enlazar a un elemento primario con plantilla


El modo de enlace relativo TemplatedParent se utiliza para enlazar desde una plantilla de control a la instancia de
objeto de tiempo de ejecución a la que se aplica la plantilla (conocida como el elemento primario con plantilla).
Este modo solo es aplicable si el enlace relativo está dentro de una plantilla de control y es similar a establecer
un TemplateBinding .
El siguiente XAML muestra un ejemplo de un modo de enlace relativo TemplatedParent :

<ContentPage ...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
<Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
...>
<Grid>
...
<Label Text="{Binding CardTitle}"
... />
<BoxView BackgroundColor="{Binding BorderColor}"
... />
<Label Text="{Binding CardDescription}"
... />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
<StackLayout>
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla
elit dolor, convallis non interdum."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim
fermentum. Morbi ut lacus vitae eros lacinia."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Xamarin Monkey"
CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat
scelerisque erat, quis aliquet arcu."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
</StackLayout>
</ContentPage>

En este ejemplo, la Frame , que es el elemento raíz del ControlTemplate , tiene su BindingContext establecido en
la instancia de objeto de tiempo de ejecución a la que se aplica la plantilla. Por lo tanto, Frame y sus elementos
secundarios resuelven sus expresiones de enlace con las propiedades de cada objeto CardView :
Para obtener más información sobre las plantillas de control, consulte Plantillas de control de :::no-
loc(Xamarin.Forms):::.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Extensiones de marcado XAML
Plantillas de control de :::no-loc(Xamarin.Forms):::
Conmutación por recuperación de enlaces de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
En ocasiones, los enlaces de datos producen errores, porque no se puede resolver el origen de enlace o porque el
enlace se realiza correctamente pero devuelve un valor null . Si bien estos escenarios se pueden controlar con los
convertidores de valor, u otro código adicional, los enlaces de datos pueden hacerse más sólidos mediante la
definición de valores de reserva para su uso si se produce un error en el proceso de enlace. Esto puede realizarse
mediante la definición de las propiedades FallbackValue y TargetNullValue en una expresión de enlace. Dado que
estas propiedades residen en la clase BindingBase , pueden usarse con enlaces, enlaces múltiples, enlaces
compilados y la extensión de marcado Binding .

NOTE
El uso de las propiedades FallbackValue y TargetNullValue en una expresión de enlace es opcional.

Definición de un valor de reserva


La propiedad FallbackValue permite que se pueda definir un valor de reserva que se usará cuando el origen del
enlace no se pueda resolver. Un escenario común para establecer esta propiedad es cuando se realiza el enlace con
propiedades de origen que podrían no existir en todos los objetos en una colección enlazada de tipos
heterogéneos.
La página MonkeyDetail ilustra el establecimiento de la propiedad FallbackValue :

<Label Text="{Binding Population, FallbackValue='Population size unknown'}"


... />

El enlace en Label define un valor de FallbackValue que se establecerá en el destino si no se puede resolver el
origen del enlace. Por lo tanto, el valor definido por la propiedad FallbackValue se mostrará si la propiedad
Population no existe en el objeto enlazado. Tenga en cuenta que aquí, el valor de la propiedad FallbackValue está
delimitado por caracteres de comillas simples (apóstrofo).
En lugar de definir los valores de la propiedad FallbackValue en línea, se recomienda definirlos como recursos en
ResourceDictionary . La ventaja de este enfoque es que estos valores se definen una vez en una sola ubicación y
son localizables más fácilmente. Posteriormente, se pueden recuperar los recursos mediante la extensión de
marcado StaticResource :

<Label Text="{Binding Population, FallbackValue={StaticResource populationUnknown}}"


... />

NOTE
No es posible establecer la propiedad FallbackValue con una expresión de enlace.
Esta es la ejecución del programa:

Cuando el FallbackValue propiedad no está establecida en una expresión de enlace y la ruta de acceso del enlace
o parte de la ruta de acceso no se resuelve, BindableProperty.DefaultValue se establece en el destino. Sin
embargo, cuando la propiedad FallbackValue está establecida y la ruta de acceso del enlace o parte de la ruta de
acceso no se resuelve, el valor de la propiedad del valor FallbackValue se establece en el destino. Por lo tanto, en
la página MonkeyDetail , Label muestra el mensaje "Population size unknown" ("Tamaño de la población
desconocido"), porque al elemento enlazado le falta una propiedad Population .

IMPORTANT
Un convertidor de valores definido no se ejecuta en una expresión de enlace cuando la propiedad FallbackValue está
establecida.

Definición de un valor de reemplazo nulo


La propiedad TargetNullValue permite que se pueda definir un valor de reemplazo que se usará cuando el origen
del enlace se resuelva pero el valor sea null . Un escenario común para establecer esta propiedad es cuando se
realiza el enlace con propiedades de origen que podrían ser null en una colección enlazada.
La página Monkeys ilustra el establecimiento de la propiedad TargetNullValue :

<ListView ItemsSource="{Binding Monkeys}"


...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
...
<Image Source="{Binding ImageUrl,
TargetNullValue='https://upload.wikimedia.org/wikipedia/commons/2/20/Point_d_interrogation.jpg'}"
... />
...
<Label Text="{Binding Location, TargetNullValue='Location unknown'}"
... />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Los enlaces en Image y Label definen valores TargetNullValue que se aplicará si la ruta de acceso de enlace
devuelve null . Por lo tanto, los valores definidos por las propiedades TargetNullValue se mostrarán para los
objetos de la colección donde las propiedades ImageUrl y Location no estén definidas. Tenga en cuenta que aquí,
los valores de la propiedad TargetNullValue están delimitados por caracteres de comillas simples (apóstrofo).
En lugar de definir los valores de la propiedad TargetNullValue en línea, se recomienda definirlos como recursos
en ResourceDictionary . La ventaja de este enfoque es que estos valores se definen una vez en una sola ubicación y
son localizables más fácilmente. Posteriormente, se pueden recuperar los recursos mediante la extensión de
marcado StaticResource :

<Image Source="{Binding ImageUrl, TargetNullValue={StaticResource fallbackImageUrl}}"


... />
<Label Text="{Binding Location, TargetNullValue={StaticResource locationUnknown}}"
... />

NOTE
No es posible establecer la propiedad TargetNullValue con una expresión de enlace.

Esta es la ejecución del programa:

Cuando la propiedad TargetNullValue no está establecida en una expresión de enlace, un valor de origen de null
se convertirá si se define un convertidor de valores, se le aplicará formato si se define StringFormat y, a
continuación, se establecerá el resultado en el destino. Sin embargo, cuando la propiedad TargetNullValue está
establecida, un valor de origen de null se convertirá si se ha definido un convertidor de valores, y si sigue siendo
null después de la conversión, el valor de la propiedad TargetNullValue está establecido en el destino.

IMPORTANT
No se aplicará formato a la cadena en una expresión de enlace cuando la propiedad TargetNullValue esté establecida.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Enlaces múltiples de Xamarin.Forms
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
Los enlaces múltiples proporcionan la capacidad de asociar una colección de objetos Binding a una única
propiedad de destino de enlace. Se crean con la clase MultiBinding , que evalúa todos sus objetos Binding y
devuelve un valor único a través de una instancia de IMultiValueConverter proporcionada por la aplicación.
Además, MultiBinding vuelve a evaluar todos sus objetos Binding cuando cambia alguno de los datos enlazados.
La clase MultiBinding define las propiedades siguientes:
Bindings , de tipo IList<BindingBase> , que representa la colección de objetos Binding dentro de la instancia
de MultiBinding .
Converter , de tipo IMultiValueConverter , que representa el convertidor que se utilizará para convertir los
valores al valor de destino (o desde este).
ConverterParameter , de tipo object , que representa un parámetro opcional que pasar a Converter .

La propiedad Bindings es la propiedad de contenido de la clase MultiBinding y, por tanto, no es necesario


establecerla explícitamente desde XAML.
Además, la clase MultiBinding hereda las siguientes propiedades de la clase BindingBase :
FallbackValue , de tipo object , que representa el valor que se va a usar cuando el enlace múltiple no puede
devolver un valor.
Mode , de tipo BindingMode , que indica la dirección del flujo de datos del enlace múltiple.
StringFormat , de tipo string , que especifica cómo dar formato al resultado del enlace múltiple si se muestra
como una cadena.
TargetNullValue , de tipo object , que representa el valor que se usa en el destino cuando el valor del origen es
null .

Un elemento MultiBinding debe usar IMultiValueConverter para generar un valor para el destino de enlace, en
función del valor de los enlaces de la colección de Bindings . Por ejemplo, Color podría calcularse a partir de los
valores de rojo, azul y verde, que pueden ser valores de los mismos objetos de origen del enlace (o diferentes).
Cuando un valor se mueve del destino a los orígenes, el valor de la propiedad de destino se convierte en un
conjunto de valores que se devuelven en los enlaces.

IMPORTANT
Los enlaces individuales de la colección Bindings pueden tener sus propios convertidores de valores.

El valor de la propiedad Mode determina la funcionalidad de MultiBinding y se utiliza como modo de enlace para
todos los enlaces de la colección, a menos que un enlace individual invalide la propiedad. Por ejemplo, si la
propiedad Mode de un objeto MultiBinding está establecida en TwoWay , se considerarán todos los enlaces de la
colección TwoWay , a menos que establezca explícitamente un valor Mode diferente en uno de los enlaces.

Definición de IMultiValueConverter
La interfaz IMultiValueConverter permite aplicar la lógica personalizada a un elemento MultiBinding . Para asociar
un convertidor a MultiBinding , cree una clase que implemente la interfaz IMultiValueConverter y, a continuación,
implemente los métodos Convert y ConvertBack :

public class AllTrueMultiConverter : IMultiValueConverter


{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
{
return false;
// Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
}

foreach (var value in values)


{
if (!(value is bool b))
{
return false;
// Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
}
else if (!b)
{
return false;
}
}
return true;
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
{
// Return null to indicate conversion back is not possible
return null;
}

if (b)
{
return targetTypes.Select(t => (object)true).ToArray();
}
else
{
// Can't convert back from false because of ambiguity
return null;
}
}
}

El método Convert convierte valores de origen a un valor para el destino de enlace. Xamarin.Forms llama a este
método cuando propaga los valores de los enlaces de origen al destino de enlace. Este método acepta cuatro
argumentos:
object[] de tipo MultiBinding , es una matriz de valores que generan los enlaces de origen en values .
targetType , de tipo Type , es el tipo de la propiedad del destino de enlace.
parameter , de tipo object , es el parámetro de convertidor que se va a usar.
culture , de tipo CultureInfo , es la referencia cultural que se va a usar en el convertidor.

El método Convert devuelve un elemento object que representa un valor convertido. Este método debe
devolver:
BindableProperty.UnsetValue para indicar que el convertidor no generó un valor y que el enlace usará
FallbackValue .
Binding.DoNothing para indicar a Xamarin.Forms que no realice ninguna acción. Por ejemplo, para indicar a
Xamarin.Forms que no transfiera un valor al destino de enlace, o no use FallbackValue .
null para indicar que el convertidor no puede realizar la conversión y que el enlace usará TargetNullValue .

IMPORTANT
Un objeto MultiBinding que recibe BindableProperty.UnsetValue de un método Convert debe definir su propiedad
FallbackValue . De forma similar, un objeto MultiBinding que recibe null de un método Convert debe definir su
propiedad TargetNullValue .

El método ConvertBack convierte un valor de destino de enlace en valores de enlace de origen. Este método acepta
cuatro argumentos:
value , de tipo object , es el valor que genera el destino de enlace.
targetTypes , de tipo Type[] , es la matriz de tipos a los que se convertirá. La longitud de la matriz indica el
número y los tipos de valores que se sugiere que el método devuelva.
parameter , de tipo object , es el parámetro de convertidor que se va a usar.
culture , de tipo CultureInfo , es la referencia cultural que se va a usar en el convertidor.

El método ConvertBack devuelve una matriz de valores, de tipo object[] , que se han convertido del valor de
destino de nuevo a los valores de origen. Este método debe devolver:
BindableProperty.UnsetValue en la posición i para indicar que el convertidor no puede proporcionar un valor
para el enlace de origen en el índice i , y que no se establecerá ningún valor en él.
Binding.DoNothing en la posición i para indicar que no se va a establecer ningún valor en el enlace de origen
en el índice i .
null para indicar que el convertidor no puede realizar la conversión o que no admite la conversión en esta
dirección.

Consumo de un elemento IMultiValueConverter


Un elemento IMultiValueConverter se consume creando una instancia de él en un diccionario de recursos y, a
continuación, haciendo referencia a él mediante la extensión de marcado StaticResource para establecer la
propiedad MultiBinding.Converter :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.MultiBindingConverterPage"
Title="MultiBinding Converter demo">

<ContentPage.Resources>
<local:AllTrueMultiConverter x:Key="AllTrueConverter" />
<local:InverterConverter x:Key="InverterConverter" />
</ContentPage.Resources>

<CheckBox>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource AllTrueConverter}">
<Binding Path="Employee.IsOver16" />
<Binding Path="Employee.HasPassedTest" />
<Binding Path="Employee.IsSuspended"
Converter="{StaticResource InverterConverter}" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ContentPage>
En este ejemplo, el objeto MultiBinding usa la instancia de AllTrueMultiConverter para establecer la propiedad
CheckBox.IsChecked en true , siempre que los tres objetos Binding se evalúen como true . De lo contrario, la
propiedad CheckBox.IsChecked se establece en false .
De forma predeterminada, la propiedad CheckBox.IsChecked utiliza un enlace TwoWay . Por lo tanto, el método
ConvertBack de la instancia de AllTrueMultiConverter se ejecuta cuando el usuario no ha desactivado CheckBox ,
que establece los valores de enlace de origen en el valor de la propiedad CheckBox.IsChecked .
El código de C# equivalente se muestra a continuación:

public class MultiBindingConverterCodePage : ContentPage


{
public MultiBindingConverterCodePage()
{
BindingContext = new GroupViewModel();

CheckBox checkBox = new CheckBox();


checkBox.SetBinding(CheckBox.IsCheckedProperty, new MultiBinding
{
Bindings = new Collection<BindingBase>
{
new Binding("Employee1.IsOver16"),
new Binding("Employee1.HasPassedTest"),
new Binding("Employee1.IsSuspended", converter: new InverterConverter())
},
Converter = new AllTrueMultiConverter()
});

Title = "MultiBinding converter demo";


Content = checkBox;
}
}

Cadenas de formato
Un elemento MultiBinding puede dar formato a cualquier resultado de enlace múltiple que se muestra como una
cadena, con la propiedad StringFormat . Esta propiedad se puede establecer en una cadena de formato estándar de
.NET, con marcadores de posición, que especifica cómo dar formato al resultado de enlace múltiple:

<Label>
<Label.Text>
<MultiBinding StringFormat="{}{0} {1} {2}">
<Binding Path="Employee1.Forename" />
<Binding Path="Employee1.MiddleName" />
<Binding Path="Employee1.Surname" />
</MultiBinding>
</Label.Text>
</Label>

En este ejemplo, la propiedad StringFormat combina los tres valores enlazados en una sola cadena que se muestra
por Label .
El código de C# equivalente se muestra a continuación:
Label label = new Label();
label.SetBinding(Label.TextProperty, new MultiBinding
{
Bindings = new Collection<BindingBase>
{
new Binding("Employee1.Forename"),
new Binding("Employee1.MiddleName"),
new Binding("Employee1.Surname")
},
StringFormat = "{0} {1} {2}"
});

IMPORTANT
El número de parámetros en un formato de cadena compuesto no puede superar el número de objetos Binding
secundarios de MultiBinding .

Al establecer las propiedades Converter y StringFormat , el convertidor se aplica primero al valor de los datos y, a
continuación, se aplica StringFormat .
Para obtener más información sobre el formato de cadena en Xamarin.Forms, vea Formato de cadena de
Xamarin.Forms.

Provisión de valores de reserva


Los enlaces de datos se pueden fortalecer mediante la definición de valores de reserva para usarlos si se produce
un error en el proceso de enlace. Esto puede realizarse opcionalmente mediante la definición de las propiedades
FallbackValue y TargetNullValue en un objeto MultiBinding .

Un objeto MultiBindingusará su valor FallbackValue cuando el método Convert de una instancia de


IMultiValueConverter devuelva BindableProperty.UnsetValue , lo que indica que el convertidor no generó un valor.
Un objeto MultiBinding usará su valor TargetNullValue cuando el método Convert de una instancia de
IMultiValueConverter devuelva null , lo que indica que el convertidor no puede realizar la conversión.

Para obtener más información sobre las reservas de enlace, consulte Reservas de enlace de Xamarin.Forms.

Anidación de objetos MultiBinding


Los objetos MultiBinding se pueden anidar para que se evalúen varios objetos MultiBinding a fin de devolver un
valor a través de una instancia de IMultiValueConverter :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.NestedMultiBindingPage"
Title="Nested MultiBinding demo">

<ContentPage.Resources>
<local:AllTrueMultiConverter x:Key="AllTrueConverter" />
<local:AnyTrueMultiConverter x:Key="AnyTrueConverter" />
<local:InverterConverter x:Key="InverterConverter" />
</ContentPage.Resources>

<CheckBox>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource AnyTrueConverter}">
<MultiBinding Converter="{StaticResource AllTrueConverter}">
<Binding Path="Employee.IsOver16" />
<Binding Path="Employee.HasPassedTest" />
<Binding Path="Employee.IsSuspended" Converter="{StaticResource InverterConverter}" />
</MultiBinding>
<Binding Path="Employee.IsMonarch" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ContentPage>

En este ejemplo, el objeto MultiBinding usa su instancia de AnyTrueMultiConverter para establecer la propiedad
CheckBox.IsChecked en true , siempre que todos los objetos Binding del objeto MultiBinding interno se evalúen
como true o siempre que el objeto Binding del objeto MultiBinding externo se evalúe como true . De lo
contrario, la propiedad CheckBox.IsChecked se establece en false .

Uso de un enlace RelativeSource en un elemento MultiBinding


Los objetos MultiBinding admiten enlaces relativos, lo cual proporciona la capacidad de establecer el origen de
enlace en relación con la posición del destino de enlace:
<ContentPage ...
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<local:AllTrueMultiConverter x:Key="AllTrueConverter" />

<ControlTemplate x:Key="CardViewExpanderControlTemplate">
<Expander BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
IsExpanded="{Binding IsExpanded, Source={RelativeSource TemplatedParent}}"
BackgroundColor="{Binding CardColor}">
<Expander.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueConverter}">
<Binding Path="IsExpanded" />
<Binding Path="IsEnabled" />
</MultiBinding>
</Expander.IsVisible>
<Expander.Header>
<Grid>
<!-- XAML that defines Expander header goes here -->
</Grid>
</Expander.Header>
<Grid>
<!-- XAML that defines Expander content goes here -->
</Grid>
</Expander>
</ControlTemplate>
</ContentPage.Resources>

<StackLayout>
<controls:CardViewExpander BorderColor="DarkGray"
CardTitle="John Doe"
CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nulla elit dolor, convallis non interdum."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewExpanderControlTemplate}"
IsEnabled="True"
IsExpanded="True" />
</StackLayout>
</ContentPage>

En este ejemplo, el modo de enlace relativo TemplatedParent se utiliza para enlazar desde una plantilla de control a
la instancia de objeto de tiempo de ejecución a la que se aplica la plantilla. El elemento Expander , que es el
elemento raíz de ControlTemplate , tiene su BindingContext establecido en la instancia de objeto de tiempo de
ejecución a la que se aplica la plantilla. Por lo tanto, Expander y sus elementos secundarios resuelven sus
expresiones de enlace, y los objetos Binding , con las propiedades del objeto CardViewExpander . El elemento
MultiBinding usa la instancia de AllTrueMultiConverter para establecer la propiedad Expander.IsVisible en
true , siempre que los dos objetos Binding se evalúen como true . De lo contrario, la propiedad
Expander.IsVisible se establece en false .

Para más información sobre los enlaces relativos, consulte Enlaces relativos de Xamarin.Forms. Para obtener más
información sobre las plantillas de control, consulte Plantillas de control de Xamarin.Forms.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Formato de cadena de Xamarin.Forms
Reservas de enlace de Xamarin.Forms
Enlaces relativos de Xamarin.Forms
Plantillas de control de Xamarin.Forms
Interfaz de comandos de :::no-loc(Xamarin.Forms):::
18/12/2020 • 31 minutes to read • Edit Online

Descargar el ejemplo
En la arquitectura Model-View-ViewModel (MVVM), los enlaces de datos se definen entre las propiedades de
ViewModel, que suele ser una clase que se deriva de INotifyPropertyChanged , y las propiedades en la vista, que
suele ser el archivo XAML. A veces, una aplicación tiene necesidades que van más allá de estos enlaces de
propiedad y solicita al usuario que inicie comandos que influyen en el modelo de vista. Por lo general, estos
comandos se señalizan mediante clics de botón o pulsaciones con el dedo, y tradicionalmente se procesan en el
archivo de código subyacente en un controlador para el evento Clicked del elemento Button o el evento
Tapped de un elemento TapGestureRecognizer .

La interfaz de comandos proporciona un enfoque alternativo para implementar comandos que se adapta mucho
mejor a la arquitectura MVVM. El propio modelo de vista puede contener comandos, que son métodos que se
ejecutan en respuesta a una actividad específica en la vista como un clic de Button . Los enlaces de datos se
definen entre estos comandos y el objeto Button .
Para permitir un enlace de datos entre un objeto Button y un ViewModel, el objeto Button define dos
propiedades:
Command de tipo System.Windows.Input.ICommand
CommandParameter de tipo Object

Para usar la interfaz de comandos, defina un enlace de datos que tenga como destino la propiedad Command del
objeto Button donde el origen sea una propiedad en el modelo de vista de tipo ICommand . El modelo de vista
contiene código asociado con la propiedad ICommand que se ejecuta cuando se hace clic en el botón. Puede
establecer CommandParameter en datos arbitrarios para distinguir entre varios botones si todos se enlazan a la
misma propiedad ICommand en el modelo de vista.
Las propiedades Command y CommandParameter también se definen mediante las clases siguientes:
MenuItem y, por tanto, ToolbarItem , que se deriva de MenuItem
TextCell y, por tanto, ImageCell , que se deriva de TextCell
TapGestureRecognizer

SearchBar define una propiedad SearchCommand de tipo ICommand y una propiedad SearchCommandParameter . La
propiedad RefreshCommand de ListView también es de tipo ICommand .
Todos estos comandos se pueden controlar en un modelo de vista de forma que no dependa del objeto de
interfaz de usuario concreto de la vista.

La interfaz ICommand
La interfaz System.Windows.Input.ICommand no forma parte de :::no-loc(Xamarin.Forms):::. En su lugar, se define en
el espacio de nombres System.Windows.Input y consta de dos métodos y un evento:
public interface ICommand
{
public void Execute (Object parameter);

public bool CanExecute (Object parameter);

public event EventHandler CanExecuteChanged;


}

Para usar la interfaz de comandos, el modelo de vista contiene propiedades de tipo ICommand :

public ICommand MyCommand { private set; get; }

El modelo de vista también debe hacer referencia a una clase que implemente la interfaz ICommand . Esta clase se
describirá en breve. En la vista, la propiedad Command de un objeto Button está enlazada a esa propiedad:

<Button Text="Execute command"


Command="{Binding MyCommand}" />

Cuando el usuario presiona el elemento Button , Button llama al método Execute del objeto ICommand
enlazado a su propiedad Command . Es la parte más sencilla de la interfaz de comandos.
El método CanExecute es más complejo. Cuando se define por primera vez el enlace en la propiedad Command del
objeto Button , y cuando cambia de algún modo el enlace de datos, el objeto Button llama al método
CanExecute del objeto ICommand . Si CanExecute devuelve false , el objeto Button se deshabilita a sí mismo.
Esto indica que el comando concreto no está disponible actualmente o no es válido.
El objeto Button también adjunta un controlador al evento CanExecuteChanged de ICommand . El evento se
desencadena desde dentro del modelo de vista. Cuando se desencadena ese evento, el objeto Button vuelve a
llamar a CanExecute . El objeto Button se habilita a sí mismo si CanExecute devuelve true y se deshabilita si
CanExecute devuelve false .

IMPORTANT
No use la propiedad IsEnabled de Button si usa la interfaz de comandos.

La clase Command
Cuando en el modelo de vista se define una propiedad de tipo ICommand , el modelo de vista también debe
contener o hacer referencia a una clase que implemente la interfaz ICommand . Esta clase debe contener o hacer
referencia a los métodos Execute y CanExecute , y desencadenar el evento CanExecuteChanged cada vez que el
método CanExecute pueda devolver otro valor.
Puede escribir este tipo de clase, o bien puede usar una escrita por otra persona. Como ICommand forma parte de
Microsoft Windows, se ha usado durante años con las aplicaciones MVVM de Windows. El uso de una clase de
Windows que implementa ICommand permite compartir los modelos de vista entre las aplicaciones de Windows y
las de :::no-loc(Xamarin.Forms):::.
Si el uso compartido de modelos de vista entre Windows y :::no-loc(Xamarin.Forms)::: no constituye un problema,
puede usar la clase Command o Command<T> incluida en :::no-loc(Xamarin.Forms)::: para implementar la interfaz
ICommand . Estas clases permiten especificar los cuerpos de los métodos Execute y CanExecute en los
constructores de clase. Use Command<T> cuando use la propiedad CommandParameter para distinguir entre varias
vistas enlazadas a la misma propiedad ICommand , y la clase Command más sencilla cuando no sea un requisito.
Comandos básicos
En la página Entrada de personas del programa Demostraciones de enlace de datos se muestran algunos
comandos sencillos implementados en un modelo de vista.
PersonViewModel define tres propiedades denominadas Name , Age y Skills que definen una persona. Esta
clase no contiene ninguna propiedad ICommand :

public class PersonViewModel : INotifyPropertyChanged


{
string name;
double age;
string skills;

public event PropertyChangedEventHandler PropertyChanged;

public string Name


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

public double Age


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

public string Skills


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

public override string ToString()


{
return Name + ", age " + Age;
}

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)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

El elemento PersonCollectionViewModel que se muestra a continuación crea objetos de tipo PersonViewModel y


permite al usuario rellenar los datos. Para ello, la clase define las propiedades IsEditing de tipo bool y
PersonEdit de tipo PersonViewModel . Además, la clase define tres propiedades de tipo ICommand y una propiedad
denominada Persons de tipo IList<PersonViewModel> :
public class PersonCollectionViewModel : INotifyPropertyChanged
{
PersonViewModel personEdit;
bool isEditing;

public event PropertyChangedEventHandler PropertyChanged;

···

public bool IsEditing


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

public PersonViewModel PersonEdit


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

public ICommand NewCommand { private set; get; }

public ICommand SubmitCommand { private set; get; }

public ICommand CancelCommand { private set; get; }

public IList<PersonViewModel> Persons { get; } = new ObservableCollection<PersonViewModel>();

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)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Esta lista abreviada no incluye el constructor de la clase, que es donde se definen las tres propiedades de tipo
ICommand , que se mostrará en breve. Tenga en cuenta que los cambios en las tres propiedades de tipo ICommand
y la propiedad Persons no hacen que se desencadenen eventos PropertyChanged . Estas propiedades se
establecen al crear la clase por primera vez y no cambian a partir de entonces.
Antes de examinar el constructor de la clase PersonCollectionViewModel , veremos los archivos XAML para el
programa Entrada de personas . Este archivo contiene un elemento Grid con su propiedad BindingContext
establecida en el elemento PersonCollectionViewModel . El objeto Grid contiene un objeto Button con el texto
New con su propiedad Command enlazada a la propiedad NewCommand del modelo de vista, un formulario de
entrada con propiedades enlazadas a la propiedad IsEditing , así como las propiedades de PersonViewModel , y
dos botones más enlazados a las propiedades SubmitCommand y CancelCommand de la clase ViewModel. El objeto
ListView final muestra la colección de las personas que ya se han introducido:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.PersonEntryPage"
Title="Person Entry">
Title="Person Entry">
<Grid Margin="10">
<Grid.BindingContext>
<local:PersonCollectionViewModel />
</Grid.BindingContext>

<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- New Button -->


<Button Text="New"
Grid.Row="0"
Command="{Binding NewCommand}"
HorizontalOptions="Start" />

<!-- Entry Form -->


<Grid Grid.Row="1"
IsEnabled="{Binding IsEditing}">

<Grid BindingContext="{Binding PersonEdit}">


<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Label Text="Name: " Grid.Row="0" Grid.Column="0" />


<Entry Text="{Binding Name}"
Grid.Row="0" Grid.Column="1" />

<Label Text="Age: " Grid.Row="1" Grid.Column="0" />


<StackLayout Orientation="Horizontal"
Grid.Row="1" Grid.Column="1">
<Stepper Value="{Binding Age}"
Maximum="100" />
<Label Text="{Binding Age, StringFormat='{0} years old'}"
VerticalOptions="Center" />
</StackLayout>

<Label Text="Skills: " Grid.Row="2" Grid.Column="0" />


<Entry Text="{Binding Skills}"
Grid.Row="2" Grid.Column="1" />

</Grid>
</Grid>

<!-- Submit and Cancel Buttons -->


<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Button Text="Submit"
Grid.Column="0"
Command="{Binding SubmitCommand}"
VerticalOptions="CenterAndExpand" />

<Button Text="Cancel"
Grid.Column="1"
Command="{Binding CancelCommand}"
Command="{Binding CancelCommand}"
VerticalOptions="CenterAndExpand" />
</Grid>

<!-- List of Persons -->


<ListView Grid.Row="3"
ItemsSource="{Binding Persons}" />
</Grid>
</ContentPage>

Este es el funcionamiento: el usuario presiona primero el botón New (Nuevo). Esto habilita el formulario de
entrada, pero deshabilita el botón New . Después, el usuario escribe un nombre, la edad y las habilidades. En
cualquier momento durante la edición, el usuario puede presionar el botón Cancel (Cancelar) para volver a
empezar. El botón Submit (Enviar) solo se habilita cuando se ha escrito un nombre y una edad válida. Al
presionar este botón Submit , se transfiere a la persona a la colección mostrada por ListView . Después de
presionar el botón Cancel o Submit , se borra el formulario de entrada y se vuelve a habilitar el botón New .
En la pantalla de iOS de la izquierda se muestra el diseño antes de escribir una vigencia válida. En la pantallas de
Android se muestra el botón Enviar habilitado después de haber establecido una edad:

El programa no tiene ninguna función para editar las entradas existentes y no guarda las entradas al salir de la
página.
Toda la lógica para los botones New , Submit y Cancel se controla en PersonCollectionViewModel a través de
definiciones de las propiedades NewCommand , SubmitCommand y CancelCommand . El constructor de
PersonCollectionViewModel establece estas tres propiedades en objetos de tipo Command .

Un constructor de la clase Command permite pasar argumentos de tipo Action y Func<bool> correspondientes a
los métodos Execute y CanExecute . Es más fácil definir estas acciones y funciones como funciones lambda
directamente en el constructor de Command . Esta es la definición del objeto Command para la propiedad
NewCommand :
public class PersonCollectionViewModel : INotifyPropertyChanged
{

···

public PersonCollectionViewModel()
{
NewCommand = new Command(
execute: () =>
{
PersonEdit = new PersonViewModel();
PersonEdit.PropertyChanged += OnPersonEditPropertyChanged;
IsEditing = true;
RefreshCanExecutes();
},
canExecute: () =>
{
return !IsEditing;
});

···

void OnPersonEditPropertyChanged(object sender, PropertyChangedEventArgs args)


{
(SubmitCommand as Command).ChangeCanExecute();
}

void RefreshCanExecutes()
{
(NewCommand as Command).ChangeCanExecute();
(SubmitCommand as Command).ChangeCanExecute();
(CancelCommand as Command).ChangeCanExecute();
}

···

Cuando el usuario hace clic en el botón New , se ejecuta la función execute pasada al constructor de Command .
Esto crea un objeto PersonViewModel , establece un controlador en el evento PropertyChanged de ese objeto,
establece IsEditing en true , y llama al método RefreshCanExecutes definido después del constructor.
Además de implementar la interfaz ICommand , la clase Command también define un método denominado
ChangeCanExecute . El modelo de vista debe llamar a ChangeCanExecute para una propiedad ICommand cada vez
que suceda algo que pueda cambiar el valor devuelto del método CanExecute . Una llamada a ChangeCanExecute
hace que la clase Command desencadene el método CanExecuteChanged . El objeto Button ha adjuntado un
controlador para ese evento y responde mediante una nueva llamada a CanExecute y, después, se habilita a sí
mismo en función del valor devuelto de ese método.
Cuando el método execute de NewCommand llama a RefreshCanExecutes , la propiedad NewCommand recibe una
llamada a ChangeCanExecute , y Button llama al método canExecute , que ahora devuelve false porque la
propiedad IsEditing es true .
El controlador para el nuevo objeto PersonViewModel llama al método
PropertyChanged ChangeCanExecute de
SubmitCommand . Aquí se muestra la implementación de esa propiedad de comando:
public class PersonCollectionViewModel : INotifyPropertyChanged
{

···

public PersonCollectionViewModel()
{

···

SubmitCommand = new Command(


execute: () =>
{
Persons.Add(PersonEdit);
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return PersonEdit != null &&
PersonEdit.Name != null &&
PersonEdit.Name.Length > 1 &&
PersonEdit.Age > 0;
});

···
}

···

La función canExecute para SubmitCommand se llama cada vez que cambia una propiedad en el objeto
PersonViewModel que se está editando. Solo devuelve true cuando la propiedad Name tiene al menos un
carácter de longitud, y Age es mayor que 0. En ese momento, se habilita el botón Submit .
La función execute para Submit quita el controlador de cambio de propiedad de PersonViewModel , agrega el
objeto a la colección Persons y devuelve todo a las condiciones iniciales.
La función execute para el botón Cancel hace lo mismo que el botón Submit excepto agregar el objeto a la
colección:
public class PersonCollectionViewModel : INotifyPropertyChanged
{

···

public PersonCollectionViewModel()
{

···

CancelCommand = new Command(


execute: () =>
{
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return IsEditing;
});
}

···

El método canExecute devuelve true en cualquier momento que se modifique un elemento PersonViewModel .
Estas técnicas se podrían adaptar para escenarios más complejos: una propiedad de PersonCollectionViewModel
se podría enlazar a la propiedad SelectedItem del objeto ListView para editar los elementos existentes, y se
podría agregar un botón Delete (Eliminar) para eliminar esos elementos.
No es necesario definir los métodos execute y canExecute como funciones lambda. Puede escribirlos como
métodos privados estándar en el modelo de vista y hacer referencia a ellos en constructores de Command . Pero
este enfoque tiende a crear una gran cantidad de métodos a los que solo se hace referencia una vez en el modelo
de vista.

Uso de parámetros de comando


A veces es conveniente que uno o varios botones (u otros objetos de interfaz de usuario) compartan la misma
propiedad ICommand en el modelo de vista. En este caso, se usa la propiedad CommandParameter para distinguir los
botones.
Se puede seguir usando la clase Command para estas propiedades ICommand compartidas. La clase define un
constructor alternativo que acepta métodos execute y canExecute con parámetros de tipo Object . Esta es la
forma de pasar CommandParameter a estos métodos.
Pero cuando se usa CommandParameter , resulta más fácil utilizar la clase Command<T> genérica para especificar el
tipo del objeto establecido en CommandParameter . Los métodos execute y canExecute que especifique tienen
parámetros de ese tipo.
En la página Teclado decimal se ilustra esta técnica y se muestra cómo implementar un teclado numérico para
escribir números decimales. El elemento BindingContext para el objeto Grid es un elemento
DecimalKeypadViewModel . La propiedad Entry de este modelo de vista se enlaza a la propiedad Text de un
elemento Label . Todos los objetos Button están enlazados a varios comandos del modelo de vista:
ClearCommand , BackspaceCommand y DigitCommand :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.DecimalKeypadPage"
Title="Decimal Keyboard">

<Grid WidthRequest="240"
HeightRequest="480"
ColumnSpacing="2"
RowSpacing="2"
HorizontalOptions="Center"
VerticalOptions="Center">

<Grid.BindingContext>
<local:DecimalKeypadViewModel />
</Grid.BindingContext>

<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Button">
<Setter Property="FontSize" Value="32" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Black" />
</Style>
</ResourceDictionary>
</Grid.Resources>

<Label Text="{Binding Entry}"


Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
FontSize="32"
LineBreakMode="HeadTruncation"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End" />

<Button Text="CLEAR"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding ClearCommand}" />

<Button Text="&#x21E6;"
Grid.Row="1" Grid.Column="2"
Command="{Binding BackspaceCommand}" />

<Button Text="7"
Grid.Row="2" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="7" />

<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="8" />

<Button Text="9"
Grid.Row="2" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="9" />

<Button Text="4"
Grid.Row="3" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="4" />

<Button Text="5"
Grid.Row="3" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="5" />

<Button Text="6"
Grid.Row="3" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="6" />

<Button Text="1"
Grid.Row="4" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="1" />

<Button Text="2"
Grid.Row="4" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="2" />

<Button Text="3"
Grid.Row="4" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="3" />

<Button Text="0"
Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding DigitCommand}"
CommandParameter="0" />

<Button Text="&#x00B7;"
Grid.Row="5" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="." />
</Grid>
</ContentPage>

Los 11 botones para los 10 dígitos y el separador decimal comparten un enlace a DigitCommand .
CommandParameter distingue entre estos botones. El valor establecido en CommandParameter suele ser el mismo que
el texto que se muestra en el botón, excepto por el separador decimal que, para una mayor claridad, se muestra
con un carácter de punto central.
Este es el programa en acción:

Observe que el botón para el separador decimal en las tres capturas de pantalla está deshabilitado porque el
número introducido ya contiene un separador decimal.
DecimalKeypadViewModel define una propiedad Entry de tipo string (que es la única que desencadena un
evento PropertyChanged ) y tres propiedades de tipo ICommand :
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
string entry = "0";

public event PropertyChangedEventHandler PropertyChanged;

···

public string Entry


{
private set
{
if (entry != value)
{
entry = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Entry"));
}
}
get
{
return entry;
}
}

public ICommand ClearCommand { private set; get; }

public ICommand BackspaceCommand { private set; get; }

public ICommand DigitCommand { private set; get; }


}

El botón correspondiente a ClearCommand siempre está habilitado y simplemente establece la entrada en "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged


{

···

public DecimalKeypadViewModel()
{
ClearCommand = new Command(
execute: () =>
{
Entry = "0";
RefreshCanExecutes();
});

···

void RefreshCanExecutes()
{
((Command)BackspaceCommand).ChangeCanExecute();
((Command)DigitCommand).ChangeCanExecute();
}

···

Como el botón siempre está habilitado, no es necesario especificar un argumento canExecute en el constructor
de Command .
La lógica para escribir números y el retroceso es un poco complicada, porque si no se ha especificado ningún
dígito, la propiedad Entry es la cadena "0". Si el usuario escribe más ceros, Entry todavía contiene un solo cero.
Si el usuario escribe cualquier otro dígito, ese dígito reemplaza al cero. Pero si el usuario escribe un separador
decimal antes de cualquier otro dígito, Entry es la cadena "0.".
El botón Backspace (Retroceso) solo se habilita cuando la longitud de la entrada es mayor que 1, o bien si Entry
no es igual a la cadena "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged


{

···

public DecimalKeypadViewModel()
{

···

BackspaceCommand = new Command(


execute: () =>
{
Entry = Entry.Substring(0, Entry.Length - 1);
if (Entry == "")
{
Entry = "0";
}
RefreshCanExecutes();
},
canExecute: () =>
{
return Entry.Length > 1 || Entry != "0";
});

···

···

La lógica para la función execute para el botón Backspace garantiza que Entry es al menos una cadena de "0".
La propiedad está enlazada a 11 botones, que se identifican a sí mismos con la propiedad
DigitCommand
CommandParameter de forma individual. Se podría establecer DigitCommand en una instancia de la clase Command
normal, pero es más fácil usar la clase Command<T> genérica. Cuando se usa la interfaz de comandos con XAML,
las propiedades CommandParameter suelen ser cadenas, y ese es el tipo del argumento genérico. Las funciones
execute y canExecute tienen argumentos de tipo string :
public class DecimalKeypadViewModel : INotifyPropertyChanged
{

···

public DecimalKeypadViewModel()
{

···

DigitCommand = new Command<string>(


execute: (string arg) =>
{
Entry += arg;
if (Entry.StartsWith("0") && !Entry.StartsWith("0."))
{
Entry = Entry.Substring(1);
}
RefreshCanExecutes();
},
canExecute: (string arg) =>
{
return !(arg == "." && Entry.Contains("."));
});
}

···

El método execute anexa el argumento de cadena a la propiedad Entry . Pero si el resultado comienza con un
cero (pero no un cero y un separador decimal), ese cero inicial se debe quitar mediante la función Substring .
El método canExecute devuelve false solo si el argumento es el separador decimal (lo que indica que se
presiona el separador decimal) y Entry ya contiene un separador decimal.
Todos los métodos execute llaman a RefreshCanExecutes , que llama a ChangeCanExecute para DigitCommand y
ClearCommand . Esto garantiza que los botones de retroceso y separador decimal se habilitan o deshabilitan en
función de la secuencia actual de dígitos especificados.

Comandos asincrónicos para menús de navegación


Los comandos son útiles para la implementación de menús de navegación, como los del propio programa
Demostraciones de enlace de datos . Este es un fragmento de MainPage.xaml :
<?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:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.MainPage"
Title="Data Binding Demos"
Padding="10">
<TableView Intent="Menu">
<TableRoot>
<TableSection Title="Basic Bindings">

<TextCell Text="Basic Code Binding"


Detail="Define a data-binding in code"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:BasicCodeBindingPage}" />

<TextCell Text="Basic XAML Binding"


Detail="Define a data-binding in XAML"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:BasicXamlBindingPage}" />

<TextCell Text="Alternative Code Binding"


Detail="Define a data-binding in code without a BindingContext"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:AlternativeCodeBindingPage}" />

···

</TableSection>
</TableRoot>
</TableView>
</ContentPage>

Al usar comandos con XAML, las propiedades CommandParameter normalmente se establecen en cadenas. Pero en
este caso, se usa una extensión de marcado XAML para que CommandParameter sea de tipo System.Type .
Cada propiedad Command se enlaza a una propiedad denominada NavigateCommand . Esa propiedad se define en el
archivo de código subyacente, MainPage.xaml.cs :

public partial class MainPage : ContentPage


{
public MainPage()
{
InitializeComponent();

NavigateCommand = new Command<Type>(


async (Type pageType) =>
{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
});

BindingContext = this;
}

public ICommand NavigateCommand { private set; get; }


}

El constructor establece la propiedad NavigateCommand en un método execute que crea una instancia del
parámetro System.Type y, después, navega hasta ella. Como la llamada a PushAsync requiere un operador
await , el método execute debe estar marcado como asincrónico. Esto se consigue con la palabra clave async
por delante de la lista de parámetros.
El constructor establece también el elemento BindingContext de la página en sí mismo para que los enlaces
hagan referencia a NavigateCommand en esta clase.
El orden del código de este constructor establece una diferencia: la llamada a InitializeComponent hace que se
analice el código XAML, pero en ese momento, el enlace a una propiedad denominada NavigateCommand no se
puede resolver porque BindingContext está establecido en null . Si se establece BindingContext en el
constructor antes de establecer NavigateCommand , el enlace se puede resolver cuando se establezca
BindingContext , pero en ese momento, NavigateCommand sigue siendo null . Establecer NavigateCommand
después de BindingContext no tendrá ningún efecto en el enlace porque un cambio en NavigateCommand no
desencadena un evento PropertyChanged y el enlace no sabe que NavigateCommand ahora es válido.
El establecimiento de y BindingContext (en cualquier orden) antes de llamar a
NavigateCommand
InitializeComponent funcionará porque los dos componentes del enlace se establecen cuando el analizador
XAML encuentra la definición de enlace.
En ocasiones, los enlaces de datos pueden resultar complicados, pero como ha visto en esta serie de artículos,
son eficaces y versátiles, y ayudan considerablemente a organizar el código mediante la separación de la lógica
subyacente de la interfaz de usuario.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
Capítulo sobre el enlace de datos del libro :::no-loc(Xamarin.Forms):::
Enlaces compilados de :::no-loc(Xamarin.Forms):::
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
Los enlaces compilados se resuelven más rápidamente que los enlaces clásicos, lo cual mejora el rendimiento del
enlace de datos en las aplicaciones de :::no-loc(Xamarin.Forms):::.
Los enlaces de datos tienen dos problemas principales:
1. No hay ninguna validación de las expresiones de enlace en tiempo de compilación. Alternativamente, los
enlaces se resuelven en tiempo de ejecución. Por lo tanto, los enlaces no válidos no se detectan hasta el
tiempo de ejecución, cuando la aplicación no se comporta según lo esperado o aparecen mensajes de error.
2. No son rentables. Los enlaces se resuelven en tiempo de ejecución mediante la inspección de objetos de uso
general (reflejo); el trabajo adicional que supone llevarlo a cabo varía en función de la plataforma.
Los enlaces compilados mejoran el rendimiento de enlace de datos en las aplicaciones de :::no-
loc(Xamarin.Forms)::: mediante la resolución de expresiones de enlace en tiempo de compilación, en lugar de en
tiempo de ejecución. Además, esta validación en tiempo de compilación de expresiones de enlace permite una
mejor experiencia de solución de problemas, porque los enlaces no válidos se notifican como errores de
compilación.
El proceso para usar enlaces compilados es el siguiente:
1. Habilite la compilación XAML. Para obtener más información acerca de la compilación XAML, consulte XAML
Compilation (Compilación XAML).
2. Establezca un atributo x:DataType de un elemento VisualElement para el tipo del objeto al cual
VisualElement y sus elementos secundarios se enlazará.

NOTE
Se recomienda establecer el atributo x:DataType en el mismo nivel de la jerarquía de vistas en que está establecido
BindingContext . Sin embargo, este atributo puede volver a definirse en cualquier ubicación en una jerarquía de vistas.

Para usar enlaces compilados, el atributo x:DataType debe establecerse en un literal de cadena o un tipo con la
extensión de marcado x:Type . En tiempo de compilación XAML, las expresiones de enlace no válidas se
notificarán como errores de compilación. Sin embargo, el compilador XAML solo notificará un error de
compilación para la primera expresión de enlace no válida que encuentre. Las expresiones de enlace válidas que
se definen en VisualElement o en sus elementos secundarios se compilarán, independientemente de si
BindingContext está establecido en XAML o en código. La compilación de una expresión de enlace genera un
código compilado que obtendrá un valor de una propiedad en el origen y lo establecerá en la propiedad en el
destino que se especifica en el marcado. Además, dependiendo de la expresión de enlace, el código generado
puede observar cambios en el valor de la propiedad de origen y actualizar la propiedad de destino , y puede
insertar los cambios desde el destino de nuevo al origen.

IMPORTANT
Los enlaces compilados actualmente están deshabilitados para las expresiones de enlace que definen la propiedad Source
. Esto es así porque la propiedad Source siempre se establece mediante la extensión de marcado x:Reference , que no
se puede resolver en tiempo de compilación.
Uso de enlaces compilados
En la página Compiled Color Selector (Selector de colores compilados) se muestra el uso de enlaces
compilados entre las vistas de :::no-loc(Xamarin.Forms)::: y las propiedades de modelo de vista:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorSelectorPage"
Title="Compiled Color Selector">
...
<StackLayout x:DataType="local:HslColorViewModel">
<StackLayout.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</StackLayout.BindingContext>
<BoxView Color="{Binding Color}"
... />
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</ContentPage>

El elemento StackLayout raíz crea una instancia de HslColorViewModel e inicializa la propiedad Color dentro de
las etiquetas de elemento de propiedad para la propiedad BindingContext . El elemento StackLayout raíz
también define el atributo x:DataType como el tipo ViewModel, lo cual indica que todas las expresiones de
enlace en la jerarquía de vistas StackLayout raíz se compilarán. Esto se puede comprobar cambiando cualquiera
de las expresiones de enlace para enlazar a una propiedad ViewModel inexistente, lo cual generará a un error de
compilación. Aunque en este ejemplo se establece el atributo x:DataType en un literal de cadena, también se
puede establecer en un tipo con la extensión de marcado x:Type . Para obtener más información acerca de la
extensión de marcado x:Type , consulte x:Type Markup Extension (Extensión de marcado x:Type).

IMPORTANT
El atributo x:DataType puede volver a definirse en cualquier punto de una jerarquía de vistas.

Los elementos BoxView , Label y las vistas Slider heredan el contexto de enlace del elemento StackLayout .
Todas estas vistas son destinos de enlace que hacen referencia a las propiedades de origen en el modelo de vista.
Para la propiedad BoxView.Color y la propiedad Label.Text , los enlaces de datos son OneWay ; las propiedades
de las vista se establecen a partir de las propiedades en ViewModel. Sin embargo, la propiedad Slider.Value
utiliza un enlace TwoWay . Esto permite que cada elemento Slider se establezca a partir del modelo de vista, y
también que el modelo de vista se establezca a partir de cada elemento Slider .
Cuando la aplicación se ejecuta por primera vez, los elementos BoxView , Label , y los elementos Slider están
establecidos a partir de ViewModel en base a la propiedad Color inicial establecida cuando se creó una
instancia de ViewModel. Esto se muestra en las capturas de pantalla siguientes:
A medida que se manipulan los controles deslizantes, los elementos BoxView y Label se actualizan del modo
correspondiente.
Para obtener más información acerca de este selector de colores, consulte ViewModels and Property-Change
Notifications (ViewModels y las notificaciones de cambio de propiedad).

Uso de enlaces compilados en DataTemplate


Los enlaces en DataTemplate se interpretan en el contexto del objeto del cual se crea la plantilla. Por lo tanto,
cuando utilice enlaces de compilación en DataTemplate , DataTemplate debe declarar el tipo de su objeto de datos
mediante el atributo x:DataType .
En la página Compiled Color List (Lista de colores compilados) se muestra el uso de enlaces compilados en
DataTemplate :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorListPage"
Title="Compiled Color List">
<Grid>
...
<ListView x:Name="colorListView"
ItemsSource="{x:Static local:NamedColor.All}"
... >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<ViewCell>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
... />
<Label Text="{Binding FriendlyName}"
... />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- The BoxView doesn't use compiled bindings -->
<BoxView Color="{Binding Source={x:Reference colorListView}, Path=SelectedItem.Color}"
... />
</Grid>
</ContentPage>
La propiedad ListView.ItemsSource está establecida en la propiedad NamedColor.All estática. La clase
NamedColor usa la fijación de .NET para enumerar todos los campos públicos estáticos en la estructura de Color
y almacenarlos con sus nombres en una colección que sea accesible desde la propiedad All estática. Por lo
tanto, ListView se rellena con todas las instancias de NamedColor . Para cada elemento de ListView , el contexto
de enlace para el elemento está establecido en un objeto NamedColor . Los elementos BoxView y Label de
ViewCell están enlazados a propiedades NamedColor .

Tenga en cuenta que x:DataType``NamedColor define el atributo DataTemplate para ser del tipo DataTemplate , lo
cual indica que todas las expresiones de enlace en la jerarquía de vistas se compilarán. Esto se puede comprobar
cambiando cualquiera de las expresiones de enlace para enlazar a una propiedad NamedColor inexistente, lo cual
generará a un error de compilación. Aunque en este ejemplo se establece el atributo x:DataType en un literal de
cadena, también se puede establecer en un tipo con la extensión de marcado x:Type . Para obtener más
información acerca de la extensión de marcado x:Type , consulte x:Type Markup Extension (Extensión de
marcado x:Type).
Cuando la aplicación se ejecuta por primera vez, ListView se rellena con instancias de NamedColor . Cuando un
elemento de ListView está seleccionado, la propiedad BoxView.Color se establece en el color del elemento
seleccionado en ListView :

Al seleccionar otros elementos de ListView se actualiza el color de BoxView .

Combinación de enlaces compilados con enlaces clásicos


Las expresiones de enlace solo se compilan para la jerarquía de vistas en la cual está definido el atributo
x:DataType . Por contra, todas las vistas en una jerarquía en la cual el atributo x:DataType no esté definido
utilizarán enlaces clásicos. Por lo tanto, es posible combinar enlaces compilados y enlaces clásicos en una página.
Por ejemplo, en la sección anterior, las vistas dentro de DataTemplate utilizan enlaces compilados, mientras que
el elemento BoxView que se establece en el color seleccionado en ListView , no lo hace.
Una estructuración cuidadosa de atributos x:DataType , por lo tanto, puede conseguir que una página utilice
enlaces compilados y clásicos. De forma alternativa, el atributo x:DataType se puede volver a definir en cualquier
punto en una jerarquía de vistas para null utilizando la extensión de marcado x:Null . Esto indica que las
expresiones de enlace dentro de la jerarquía de vistas utilizarán enlaces clásicos. La página Mixed Bindings
(Enlaces mixtos) muestra este enfoque:
<StackLayout x:DataType="local:HslColorViewModel">
<StackLayout.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</StackLayout.BindingContext>
<BoxView Color="{Binding Color}"
VerticalOptions="FillAndExpand" />
<StackLayout x:DataType="{x:Null}"
Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>

El elemento StackLayout raíz establece el atributo x:DataType para ser del tipo HslColorViewModel , lo cual indica
que todas las expresiones de enlace en la jerarquía de vistas StackLayout se compilarán. Sin embargo, el
StackLayout interno redefine el atributo x:DataType en null con la expresión de marcado x:Null . Por lo tanto,
las expresiones de enlace dentro del StackLayout interno usan enlaces clásicos. Solo BoxView , dentro de la
jerarquía de vistas StackLayout raíz, utiliza enlaces compilados.
Para obtener más información acerca de la expresión de marcado x:Null , consulte x:Null Markup Extension
(Extensión de marcado x:Null).

Rendimiento
Los enlaces compilados mejoran el rendimiento del enlace de datos, con unas ventajas de rendimiento variables.
Las pruebas de unidades muestran lo siguiente:
Un enlace compilado que utiliza la notificación de cambio de propiedad (es decir, un enlace OneWay ,
OneWayToSource o TwoWay ) se resuelve aproximadamente 8 veces más rápidamente que un enlace clásico.
Un enlace compilado que no utiliza la notificación de cambio de propiedad (es decir, un enlace OneTime ) se
resuelve aproximadamente 20 veces más rápidamente que un enlace clásico.
El establecimiento de BindingContext en un enlace compilado que utiliza la notificación de cambio de
propiedad (es decir, un enlace OneWay , OneWayToSource o TwoWay ) se resuelve aproximadamente 5 veces más
rápidamente que el establecimiento de BindingContext en un enlace clásico.
El establecimiento de BindingContext en un enlace compilado que no utiliza la notificación de cambio de
propiedad (es decir, un enlace OneTime ) se resuelve aproximadamente 7 veces más rápidamente que el
establecimiento de BindingContext en un enlace clásico.

Estas diferencias de rendimiento se pueden ampliar en dispositivos móviles, dependiendo de la plataforma que
se utilice, la versión del sistema operativo que se utilice y el dispositivo en el que se ejecute la aplicación.

Vínculos relacionados
Data Binding Demos (sample) (Demos de enlace de datos [ejemplo])
DependencyService de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Introducción
La clase DependencyService es un localizador de servicios que habilita las aplicaciones de Xamarin.Forms para
invocar la funcionalidad nativa de la plataforma desde código compartido.

Registro y resolución
Las implementaciones de la plataforma deben registrarse con DependencyService y, después, deben resolverse
desde código compartido para poder invocarse.

Selección de una foto de la biblioteca


En este artículo, se explica cómo usar la clase DependencyService de Xamarin.Forms para seleccionar una foto de
la biblioteca de imágenes del teléfono.
Introducción a DependencyService de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
La clase DependencyService es un localizador de servicios que habilita las aplicaciones de :::no-
loc(Xamarin.Forms)::: para invocar la funcionalidad nativa de la plataforma desde código compartido.
El proceso para usar DependencyService para invocar la funcionalidad nativa de la plataforma es el siguiente:
1. Cree una interfaz para la funcionalidad de la plataforma nativa en el código compartido. Para más información,
vea Creación de una interfaz.
2. Implemente la interfaz en los proyectos de la plataforma requeridos. Para obtener más información, vea
Implementación de la interfaz en cada plataforma.
3. Registro de las implementaciones de la plataforma con DependencyService . Esto permite que :::no-
loc(Xamarin.Forms)::: localice las implementaciones de la plataforma en tiempo de ejecución. Para obtener más
información, vea Registro de las implementaciones de la plataforma.
4. Resuelva las implementaciones de la plataforma desde el código compartido e invóquelas. Para obtener más
información, vea Resolución de las implementaciones de la plataforma.
En el siguiente diagrama, se muestra cómo se invoca la funcionalidad nativa de la plataforma en una aplicación de
:::no-loc(Xamarin.Forms)::::

Creación de una interfaz


El primer paso para poder invocar la funcionalidad nativa de la plataforma desde código compartido es crear una
interfaz que defina la API para interactuar con la funcionalidad nativa de la plataforma. Esta interfaz debe colocarse
en su proyecto de código compartido.
En el ejemplo siguiente, se muestra una interfaz para una API que puede usarse para recuperar la orientación de
un dispositivo:

public interface IDeviceOrientationService


{
DeviceOrientation GetOrientation();
}
Implementación de la interfaz en cada plataforma
Después de crear la interfaz que define la API para interactuar con la funcionalidad nativa de la plataforma, la
interfaz deberá implementarse en cada proyecto de la plataforma.
iOS
En el siguiente ejemplo de código, se muestra la implementación de la interfaz de IDeviceOrientationService en
iOS:

namespace DependencyServiceDemos.iOS
{
public class DeviceOrientationService : IDeviceOrientationService
{
public DeviceOrientation GetOrientation()
{
UIInterfaceOrientation orientation = UIApplication.SharedApplication.StatusBarOrientation;

bool isPortrait = orientation == UIInterfaceOrientation.Portrait ||


orientation == UIInterfaceOrientation.PortraitUpsideDown;
return isPortrait ? DeviceOrientation.Portrait : DeviceOrientation.Landscape;
}
}
}

Android
En el siguiente ejemplo de código, se muestra la implementación de la interfaz de IDeviceOrientationService en
Android:

namespace DependencyServiceDemos.Droid
{
public class DeviceOrientationService : IDeviceOrientationService
{
public DeviceOrientation GetOrientation()
{
IWindowManager windowManager =
Android.App.Application.Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();

SurfaceOrientation orientation = windowManager.DefaultDisplay.Rotation;


bool isLandscape = orientation == SurfaceOrientation.Rotation90 ||
orientation == SurfaceOrientation.Rotation270;
return isLandscape ? DeviceOrientation.Landscape : DeviceOrientation.Portrait;
}
}
}

Plataforma universal de Windows


En el siguiente ejemplo de código, se muestra la implementación de la interfaz de IDeviceOrientationService en la
Plataforma universal de Windows (UWP):
namespace DependencyServiceDemos.UWP
{
public class DeviceOrientationService : IDeviceOrientationService
{
public DeviceOrientation GetOrientation()
{
ApplicationViewOrientation orientation = ApplicationView.GetForCurrentView().Orientation;
return orientation == ApplicationViewOrientation.Landscape ? DeviceOrientation.Landscape :
DeviceOrientation.Portrait;
}
}
}

Registro de las implementaciones de la plataforma


Después de implementar la interfaz en cada proyecto de la plataforma, las implementaciones de la plataforma
deberán registrarse con DependencyService para que :::no-loc(Xamarin.Forms)::: pueda ubicarlas en tiempo de
ejecución. Normalmente, esto se realiza con DependencyAttribute , lo que indica que el tipo especificado
proporciona una implementación de la interfaz.
En el siguiente ejemplo se muestra el uso de DependencyAttribute para registrar la implementación en iOS de la
interfaz de IDeviceOrientationService :

using :::no-loc(Xamarin.Forms):::;

[assembly: Dependency(typeof(DependencyServiceDemos.iOS.DeviceOrientationService))]
namespace DependencyServiceDemos.iOS
{
public class DeviceOrientationService : IDeviceOrientationService
{
public DeviceOrientation GetOrientation()
{
...
}
}
}

En este ejemplo, DependencyAttribute registra DeviceOrientationService con DependencyService . De forma similar,


las implementaciones de la interfaz de IDeviceOrientationService en otras plataformas se debe registrar con
DependencyAttribute .

Para obtener más información sobre el registro de implementaciones de plataforma con DependencyService , vea
Registro y resolución de DependencyService de :::no-loc(Xamarin.Forms):::.

Resolución de las implementaciones de la plataforma


Tras el registro de las implementaciones de la plataforma con DependencyService , las implementaciones deberán
resolverse antes de invocarse. Normalmente, esto se realiza en código compartido mediante el método
DependencyService.Get<T> .

En el código siguiente, se muestra un ejemplo de llamada al método Get<T> para resolver la interfaz de
IDeviceOrientationService y, después, invocar su método GetOrientation :

IDeviceOrientationService service = DependencyService.Get<IDeviceOrientationService>();


DeviceOrientation orientation = service.GetOrientation();

Como alternativa, este código se puede comprimir en una sola línea:


DeviceOrientation orientation = DependencyService.Get<IDeviceOrientationService>().GetOrientation();

Para obtener más información sobre la resolución de implementaciones de plataforma con DependencyService , vea
Registro y resolución de DependencyService de :::no-loc(Xamarin.Forms):::.

Vínculos relacionados
Demostraciones de DependencyService (ejemplo)
Registro y resolución de DependencyService de :::no-loc(Xamarin.Forms):::
Registro y resolución de DependencyService de
:::no-loc(Xamarin.Forms):::
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
Al usar DependencyService de :::no-loc(Xamarin.Forms)::: para invocar la funcionalidad de la plataforma nativa, las
implementaciones de la plataforma deben estar registradas con DependencyService y, después, resolverse desde
código compartido para poder invocarse.

Registro de las implementaciones de la plataforma


Las implementaciones de la plataforma deben estar registradas con DependencyService para que :::no-
loc(Xamarin.Forms)::: pueda localizarlas en tiempo de ejecución.
El registro se puede realizar con DependencyAttribute o con los métodos Register y RegisterSingleton .

IMPORTANT
Las compilaciones de versión de los proyectos de UWP que usan la compilación nativa de .NET deben registrar las
implementaciones de plataforma con los métodos Register .

Registro por atributo


DependencyAttribute se puede usar para registrar una implementación de la plataforma con DependencyService .
El atributo indica que el tipo especificado proporciona una implementación concreta de la interfaz.
En el siguiente ejemplo se usa DependencyAttribute para registrar la implementación de iOS de la interfaz
IDeviceOrientationService :

using :::no-loc(Xamarin.Forms):::;

[assembly: Dependency(typeof(DeviceOrientationService))]
namespace DependencyServiceDemos.iOS
{
public class DeviceOrientationService : IDeviceOrientationService
{
public DeviceOrientation GetOrientation()
{
...
}
}
}

En este ejemplo, DependencyAttribute registra DeviceOrientationService con DependencyService . Esto da como


resultado el tipo concreto que se va a registrar en la interfaz que implementará.
De forma similar, las implementaciones de la interfaz de IDeviceOrientationService en otras plataformas se debe
registrar con DependencyAttribute .
NOTE
El registro con DependencyAttribute se realiza en el nivel de espacio de nombres.

Registro por método


Los métodos DependencyService.Register , y el método RegisterSingleton , se pueden usar para registrar una
implementación de la plataforma con DependencyService .
En el siguiente ejemplo se usa el método Register para registrar la implementación de iOS de la interfaz
IDeviceOrientationService :

[Register("AppDelegate")]
public partial class AppDelegate : global:::::no-loc(Xamarin.Forms):::.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global:::::no-loc(Xamarin.Forms):::.Forms.Init();
LoadApplication(new App());
DependencyService.Register<IDeviceOrientationService, DeviceOrientationService>();
return base.FinishedLaunching(app, options);
}
}

En este ejemplo, el método registra el tipo concreto, DeviceOrientationService , en la interfaz de


Register
IDeviceOrientationService . De forma alternativa, se puede usar una sobrecarga del método Register para
registrar una implementación de plataforma con DependencyService :

DependencyService.Register<DeviceOrientationService>();

En este ejemplo, el método Register registra DeviceOrientationService con DependencyService . Esto da como
resultado el tipo concreto que se va a registrar en la interfaz que implementará.
Como alternativa, se puede registrar una instancia de objeto existente como singleton con el método
RegisterSingleton :

var service = new DeviceOrientationService();


DependencyService.RegisterSingleton<IDeviceOrientationService>(service);

En este ejemplo, el método RegisterSingleton registra la instancia del objeto DeviceOrientationService en la


interfaz IDeviceOrientationService , como singleton.
De forma similar, las implementaciones de la interfaz IDeviceOrientationService en otras plataformas se puede
registrar con los métodos Register , o el método RegisterSingleton .

IMPORTANT
El registro con los métodos Register y RegisterSingleton se debe realizar en los proyectos de la plataforma antes de
que se invoque la funcionalidad que ha proporcionado la implementación de la plataforma mediante el código compartido.

Resolución de las implementaciones de la plataforma


Las implementaciones de la plataforma deben resolverse antes de invocarse. Normalmente, esto se realiza en
código compartido mediante el método DependencyService.Get<T> . Sin embargo, también se puede lograr con el
método DependencyService.Resolve<T> .
De forma predeterminada, DependencyService solo resuelve implementaciones de plataforma con constructores
sin parámetros. Aun así, se puede incorporar a :::no-loc(Xamarin.Forms)::: un método de resolución de
dependencias que use un contenedor de inserción de dependencias o métodos de generador para resolver
implementaciones de plataforma. Este enfoque puede usarse para resolver las implementaciones de plataforma
que tienen constructores con parámetros. Para obtener más información, vea Inserción de dependencias en :::no-
loc(Xamarin.Forms):::.

IMPORTANT
La invocación de una implementación de la plataforma que no se haya registrado con DependencyService tendrá como
resultado la generación de NullReferenceException .

Resolución mediante el método Get<T>


El método Get<T> recupera la implementación de la plataforma de la interfaz de T en tiempo de ejecución y
luego hace una de estas acciones:
Crea una instancia de ella como singleton.
Devuelve una instancia existente como singleton, que se registró con DependencyService por el método
RegisterSingleton .

En ambos casos, esta instancia estará activa durante la vigencia de la aplicación, y las llamadas subsiguientes para
resolver la misma implementación de la plataforma recuperarán la misma instancia.
En el código siguiente, se muestra un ejemplo de llamada al método Get<T> para resolver la interfaz de
IDeviceOrientationService y, después, invocar su método GetOrientation :

IDeviceOrientationService service = DependencyService.Get<IDeviceOrientationService>();


DeviceOrientation orientation = service.GetOrientation();

Como alternativa, este código se puede comprimir en una sola línea:

DeviceOrientation orientation = DependencyService.Get<IDeviceOrientationService>().GetOrientation();

NOTE
El método Get<T> devuelve una instancia de la implementación de la plataforma de la interfaz de T como singleton de
forma predeterminada. No obstante, se puede modificar este comportamiento. Para obtener más información, vea
Administración de la vigencia de los objetos resueltos.

Resolución mediante el método Resolve<T>


El método Resolve<T> recupera la implementación de la plataforma de la interfaz de T en tiempo de ejecución
mediante un método de resolución de dependencias que se ha insertado en :::no-loc(Xamarin.Forms)::: con la
clase DependencyResolver . Si un método de resolución de dependencias no se ha insertado en :::no-
loc(Xamarin.Forms):::, el método Resolve<T> llamará al método Get<T> como solución alternativa para recuperar
la implementación de la plataforma. Para obtener más información sobre la inserción de métodos de resolución
de dependencias en :::no-loc(Xamarin.Forms):::, vea Resolución de dependencias en :::no-loc(Xamarin.Forms):::.
En el código siguiente, se muestra un ejemplo de llamada al método Resolve<T> para resolver la interfaz de
IDeviceOrientationService y, después, invocar su método GetOrientation :
IDeviceOrientationService service = DependencyService.Resolve<IDeviceOrientationService>();
DeviceOrientation orientation = service.GetOrientation();

Como alternativa, este código se puede comprimir en una sola línea:

DeviceOrientation orientation = DependencyService.Resolve<IDeviceOrientationService>().GetOrientation();

NOTE
Cuando el método Resolve<T> llama al método Get<T> como solución alternativa, devuelve una instancia de la
implementación de la plataforma de la interfaz de T como singleton de forma predeterminada. No obstante, se puede
modificar este comportamiento. Para obtener más información, vea Administración de la vigencia de los objetos resueltos.

Administración de la vigencia de los objetos resueltos


El comportamiento predeterminado de la clase DependencyService es resolver las implementaciones de la
plataforma como elementos singleton. Por lo tanto, las implementaciones de la plataforma permanecerán activas
durante la vigencia de la aplicación en cuestión.
Este comportamiento se especifica con el argumento opcional DependencyFetchTarget en los métodos Get<T> y
Resolve<T> . La enumeración DependencyFetchTarget define dos miembros:

GlobalInstance , que devuelve la implementación de la plataforma como singleton.


NewInstance , que devuelve una nueva instancia de la implementación de la plataforma. La aplicación es
responsable de administrar la vigencia de la instancia de la implementación de la plataforma.
Los métodos Get<T> y establecen sus argumentos opcionales en
Resolve<T>
DependencyFetchTarget.GlobalInstance , por lo que las implementaciones de la plataforma siempre se resuelven
como elementos singleton. Este comportamiento se puede cambiar especificando
DependencyFetchTarget.NewInstance como argumentos para los métodos Get<T> y Resolve<T> con el fin de que
crear las instancias de las implementaciones de la plataforma:

ITextToSpeechService service = DependencyService.Get<ITextToSpeechService>


(DependencyFetchTarget.NewInstance);

En este ejemplo, DependencyService crea una instancia de la implementación de la plataforma para la interfaz de
ITextToSpeechService . Las llamadas subsiguientes para resolver ITextToSpeechService también crearán nuevas
instancias.
La consecuencia de crear siempre una nueva instancia de una implementación de la plataforma es que la
aplicación adquiere la responsabilidad de administrar la vigencia de las instancias. Esto significa que, si se
suscribe a un evento definido en una implementación de la plataforma, deberá cancelar la suscripción al evento
cuando ya no se requiera la implementación de la plataforma. Además, significa que puede ser necesario que las
implementaciones de la plataforma implementen IDisposable y realicen una limpieza de sus recursos en los
métodos Dispose . La aplicación de ejemplo muestra este escenario en sus implementaciones de la plataforma de
TextToSpeechService .

Cuando una aplicación termina de utilizar una implementación de la plataforma que implementa IDisposable ,
debe llamar a la implementación del objeto Dispose . Una manera de realizar esta acción es usar una instrucción
using :
ITextToSpeechService service = DependencyService.Get<ITextToSpeechService>
(DependencyFetchTarget.NewInstance);
using (service as IDisposable)
{
await service.SpeakAsync("Hello world");
}

En este ejemplo, una vez que se ha invocado el método SpeakAsync , la instrucción using desecha
automáticamente el objeto de la implementación de la plataforma. Esto da como resultado la invocación del
método Dispose del objeto, que realiza la limpieza requerida.
Para obtener más información sobre cómo llamar al método Dispose de un objeto, vea Uso de objetos que
implementan IDisposable.

Vínculos relacionados
Demostraciones de DependencyService (ejemplo)
Resolución de dependencias :::no-loc(Xamarin.Forms):::
Seleccionar una foto de la biblioteca de imágenes
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
En este artículo, se explica cómo crear una aplicación que permita al usuario seleccionar una foto de la biblioteca
de imágenes del teléfono. Como en :::no-loc(Xamarin.Forms)::: no se incluye esta función, es necesario usar
DependencyService para acceder a las API nativas en cada plataforma.

Creación de la interfaz
Primero, cree una interfaz en el código compartido que exprese la función deseada. En el caso de una aplicación
de selección de fotos, solo se necesita un método. Esto se define en la interfaz IPhotoPickerService de la
biblioteca de .NET Standard del código de ejemplo:

namespace DependencyServiceDemos
{
public interface IPhotoPickerService
{
Task<Stream> GetImageStreamAsync();
}
}

El método GetImageStreamAsync se define como asincrónico porque tiene que devolver resultados rápidamente,
pero no puede devolver un objeto Stream para la foto seleccionada hasta que el usuario haya abierto la
biblioteca de imágenes y haya seleccionado una.
Esta interfaz se implementa en todas las plataformas que usan código específico de plataforma.

Implementación en iOS
La implementación de iOS de la interfaz IPhotoPickerService usa el objeto UIImagePickerController , como se
describe en la receta Seleccionar una foto de la galería y en el código de ejemplo.
La implementación de iOS se contiene en la clase PhotoPickerService del proyecto de iOS del código de ejemplo.
Para que el administrador DependencyService pueda ver esta clase, tiene que identificarse con un atributo [
assembly ] del tipo Dependency y, además, tiene que ser pública e implementar explícitamente la interfaz
IPhotoPickerService :
[assembly: Dependency (typeof (PhotoPickerService))]
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;

public Task<Stream> GetImageStreamAsync()


{
// Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes =
UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};

// Set event handlers


imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;

// Present UIImagePickerController;
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);

// Return Task object


taskCompletionSource = new TaskCompletionSource<Stream>();
return taskCompletionSource.Task;
}
...
}
}

El método GetImageStreamAsync crea un elemento UIImagePickerController y lo inicializa para seleccionar


imágenes desde la biblioteca de fotos. Se necesitan dos controladores de eventos: uno para cuando el usuario
seleccione una foto y otro para cuando cancele la visualización de la biblioteca de fotos. Después, el método
PresentViewController muestra la biblioteca de fotos al usuario.

En este momento, el método GetImageStreamAsync tiene que devolver un objeto Task<Stream> al código que
realiza la llamada. Esta tarea solo se completa cuando el usuario termina de interactuar con la biblioteca de fotos
y se llama a uno de los controladores de eventos. Para situaciones como esta, la clase TaskCompletionSource es
esencial. La clase proporciona un objeto Task al tipo genérico adecuado para devolver desde el método
GetImageStreamAsync y, después, se puede enviar una señal a la clase cuando se complete la tarea.

Se llama al controlador de eventos FinishedPickingMedia cuando el usuario ha seleccionado una imagen. Pero el
controlador proporciona un objeto UIImage y el elemento Task tiene que devolver un objeto Stream de .NET.
Esto se realiza en dos pasos: Primero, el objeto UIImage se convierte a un archivo PNG o JPEG en memoria
almacenado en un objeto NSData y, después, el objeto NSData se convierte a un objeto Stream de .NET. Una
llamada al método SetResult del objeto TaskCompletionSource completa la tarea al proporcionar el objeto
Stream :
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
...
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
UIImage image = args.EditedImage ?? args.OriginalImage;

if (image != null)
{
// Convert UIImage to .NET Stream object
NSData data;
if (args.ReferenceUrl.PathExtension.Equals("PNG") ||
args.ReferenceUrl.PathExtension.Equals("png"))
{
data = image.AsPNG();
}
else
{
data = image.AsJPEG(1);
}
Stream stream = data.AsStream();

UnregisterEventHandlers();

// Set the Stream as the completion of the Task


taskCompletionSource.SetResult(stream);
}
else
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}

void OnImagePickerCancelled(object sender, EventArgs args)


{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}

void UnregisterEventHandlers()
{
imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
imagePicker.Canceled -= OnImagePickerCancelled;
}
}
}

Una aplicación de iOS necesita el permiso del usuario para acceder a la biblioteca de fotos del teléfono. Agregue
el código siguiente a la sección dict del archivo Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>

Implementación en Android
La implementación de Android usa la técnica descrita en la receta Seleccionar una imagen y en el código de
ejemplo. Pero el método al que se llama cuando el usuario ha seleccionado una imagen desde la biblioteca de
imágenes es un reemplazo de OnActivityResult en una clase derivada de Activity . Por este motivo, la clase
MainActivity normal del proyecto de Android se ha complementado con un campo, una propiedad y un
reemplazo del método OnActivityResult :

public class MainActivity : FormsAppCompatActivity


{
internal static MainActivity Instance { get; private set; }

protected override void OnCreate(Bundle savedInstanceState)


{
// ...
Instance = this;
}
// ...
// Field, property, and method for Picture Picker
public static readonly int PickImageId = 1000;

public TaskCompletionSource<Stream> PickImageTaskCompletionSource { set; get; }

protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)


{
base.OnActivityResult(requestCode, resultCode, intent);

if (requestCode == PickImageId)
{
if ((resultCode == Result.Ok) && (intent != null))
{
Android.Net.Uri uri = intent.Data;
Stream stream = ContentResolver.OpenInputStream(uri);

// Set the Stream as the completion of the Task


PickImageTaskCompletionSource.SetResult(stream);
}
else
{
PickImageTaskCompletionSource.SetResult(null);
}
}
}
}

El reemplazo OnActivityResult indica el archivo de imagen seleccionado con un objeto Uri de Android, pero
esto se puede convertir a un objeto Stream de .NET si se llama al método OpenInputStream del objeto
ContentResolver obtenido desde la propiedad ContentResolver de la actividad.

Al igual que la implementación de iOS, en la implementación de Android se usa un elemento


TaskCompletionSource para indicar que se ha completado la tarea. Este objeto TaskCompletionSource se define
como una propiedad pública de la clase MainActivity . Esto permite hace referencia a la propiedad en la clase
PhotoPickerService del proyecto de Android. Esta es la clase con el método GetImageStreamAsync :
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.Droid
{
public class PhotoPickerService : IPhotoPickerService
{
public Task<Stream> GetImageStreamAsync()
{
// Define the Intent for getting images
Intent intent = new Intent();
intent.SetType("image/*");
intent.SetAction(Intent.ActionGetContent);

// Start the picture-picker activity (resumes in MainActivity.cs)


MainActivity.Instance.StartActivityForResult(
Intent.CreateChooser(intent, "Select Picture"),
MainActivity.PickImageId);

// Save the TaskCompletionSource object as a MainActivity property


MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Stream>();

// Return Task object


return MainActivity.Instance.PickImageTaskCompletionSource.Task;
}
}
}

Este método accede a la clase MainActivity por varios motivos: para la propiedad Instance , para el campo
PickImageId , para la propiedad TaskCompletionSource y para llamar a StartActivityForResult . Este método se
define mediante la clase FormsAppCompatActivity , que es la clase base de MainActivity .

Implementación en UWP
Al contrario que en las implementaciones de iOS y Android, la implementación del selector de fotos para la
Plataforma universal de Windows no necesita la clase TaskCompletionSource . La clase PhotoPickerService usa la
clase FileOpenPicker para acceder a la biblioteca de fotos. Como el método PickSingleFileAsync de
FileOpenPicker es en sí asincrónico, el método GetImageStreamAsync puede simplemente usar await con ese
método (y otros métodos asincrónicos) y devolver un objeto Stream :
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.UWP
{
public class PhotoPickerService : IPhotoPickerService
{
public async Task<Stream> GetImageStreamAsync()
{
// Create and initialize the FileOpenPicker
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
};

openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");

// Get a file and return a Stream


StorageFile storageFile = await openPicker.PickSingleFileAsync();

if (storageFile == null)
{
return null;
}

IRandomAccessStreamWithContentType raStream = await storageFile.OpenReadAsync();


return raStream.AsStreamForRead();
}
}
}

Implementación en código compartido


Después de implementar la interfaz para cada plataforma, el código compartido de la biblioteca de .NET Standard
puede usarla.
La interfaz de usuario incluye un Button en el que se puede hacer clic para elegir una foto:

<Button Text="Pick Photo"


Clicked="OnPickPhotoButtonClicked" />

El controlador de eventos Clicked usa la clase DependencyService para llamar a GetImageStreamAsync . Como
resultado, se produce una llamada al proyecto de la plataforma. Si el método devuelve un objeto Stream , el
controlador establece la propiedad Source del objeto image en los datos Stream :

async void OnPickPhotoButtonClicked(object sender, EventArgs e)


{
(sender as Button).IsEnabled = false;

Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();


if (stream != null)
{
image.Source = ImageSource.FromStream(() => stream);
}

(sender as Button).IsEnabled = true;


}
Vínculos relacionados
DependencyService (sample) (DependencyService [ejemplo])
Seleccionar una foto de la galería (iOS)
Seleccionar una imagen (Android)
Doble pantalla de Xamarin.Forms
18/12/2020 • 3 minutes to read • Edit Online

Los dispositivos de doble pantalla como Microsoft Surface Duo ofrecen nuevas posibilidades de experiencia del
usuario para las aplicaciones. Xamarin.Forms incluye las clases TwoPaneView y DualScreenInfo para que pueda
desarrollar aplicaciones para dispositivos de doble pantalla.

Primeros pasos
Siga estos pasos para agregar funcionalidades de doble pantalla a una aplicación Xamarin.Forms:
1. Abra el cuadro de diálogo Administrador de paquetes de NuGet para su solución.
2. En la pestaña Examinar , busque Xamarin.Forms.DualScreen .
3. Instale el paquete de Xamarin.Forms.DualScreen en la solución.
4. Agregue la siguiente llamada al método de inicialización a la clase MainActivity del proyecto de Android,
en el evento OnCreate :

Xamarin.Forms.DualScreen.DualScreenService.Init(this);

Este método es necesario para que la aplicación pueda detectar cambios en el estado de la aplicación, como
el hecho de abarcar un espacio de dos pantallas.
5. Actualice el atributo Activity en la clase MainActivity del proyecto de Android, de modo que incluya
todas estas opciones de ConfigurationChanges :

ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation


| ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.UiMode

Estos valores son necesarios para que los cambios de configuración y el espacio abarcado se puedan
notificar de forma más confiable. De forma predeterminada, solo dos se agregan a los proyectos de
Xamarin.Forms, por lo que no olvide agregar el resto para ofrecer compatibilidad de doble pantalla
confiable.

Solución de problemas
Si la clase DualScreenInfo o el diseño TwoPaneView no funcionan del modo esperado, revise de nuevo las
instrucciones de configuración de esta página. La omisión o la incorrecta configuración del método Init o de los
valores de atributo de ConfigurationChanges son causas comunes de error.
Revise los ejemplos de doble pantalla de Xamarin.Forms para obtener orientación adicional y una implementación
de referencia.

Pasos siguientes
Una vez que haya agregado NuGet, agregue características de doble pantalla a la aplicación con las siguientes
instrucciones:
Modelos de diseño de doble pantalla: al considerar el uso óptimo de varias pantallas en un dispositivo de doble
pantalla, consulte estas instrucciones de patrones para encontrar la mejor opción para la interfaz de su
aplicación.
Diseño TwoPaneView: la clase TwoPaneView de Xamarin.Forms, inspirada en el control UWP del mismo nombre,
es un diseño multiplataforma optimizado para los dispositivos de pantalla doble.
Clase auxiliar DualScreenInfo: la clase DualScreenInfo permite determinar en qué panel se encuentra la vista,
qué tamaño tiene, en qué posición está el dispositivo, el ángulo de la bisagra y mucho más.
Desencadenadores de pantalla doble: el espacio de nombres de Xamarin.Forms.DualScreen incluye dos
desencadenadores de estado que desencadenan un cambio de VisualState cuando varía el modo de
visualización del diseño adjunto o de la ventana.
Para obtener más información, consulte la documentación sobre la doble pantalla para desarrolladores.
Modelos de diseño de doble pantalla de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
En esta guía se presentan nuestros patrones de diseño recomendados para los dispositivos de doble pantalla, con
código y ejemplos que le ayudarán a crear interfaces que proporcionan experiencias de usuario atractivas y útiles.

Patrón de lienzo extendido


El patrón de lienzo extendido trata las dos pantallas como un gran lienzo para mostrar un mapa, una imagen, una
hoja de cálculo u otro tipo de contenido que se beneficie de la distribución para consumir el espacio máximo:
<ContentPage xmlns:local="clr-namespace:Xamarin.Duo.Forms.Samples"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Duo.Forms.Samples.ExtendCanvas">
<Grid>
<WebView x:Name="webView"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
<SearchBar x:Name="searchBar"
Placeholder="Find a place..."
BackgroundColor="DarkGray"
Opacity="0.8"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start" />
</Grid>
</ContentPage>

En este ejemplo, Grid y el contenido interno se expandirán para consumir toda la pantalla disponible, con
independencia de que se muestren en una sola pantalla o se distribuyan en dos.

Patrón Maestro y detalle


El patrón Maestro y detalle se usa cuando la vista maestra, normalmente una lista en la parte izquierda,
proporciona el contenido que el usuario selecciona para ver detalles sobre ese elemento en la parte derecha:
<ContentPage xmlns:local="clr-namespace:Xamarin.Duo.Forms.Samples"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dualScreen="clr-namespace::::no-loc(Xamarin.Forms):::.DualScreen;assembly=:::no-
loc(Xamarin.Forms):::.DualScreen"
x:Class="Xamarin.Duo.Forms.Samples.MasterDetail">
<dualScreen:TwoPaneView MinWideModeWidth="4000"
MinTallModeHeight="4000">
<dualScreen:TwoPaneView.Pane1>
<local:Master x:Name="masterPage" />
</dualScreen:TwoPaneView.Pane1>
<dualScreen:TwoPaneView.Pane2>
<local:Details x:Name="detailsPage" />
</dualScreen:TwoPaneView.Pane2>
</dualScreen:TwoPaneView>
</ContentPage>

En este ejemplo, puede usar TwoPaneView para establecer una lista en un panel y una vista de detalle en el otro.

Patrón de dos páginas


El patrón de dos páginas es idóneo para el contenido que se presta a un diseño de dos páginas, como un lector de
documentos, notas o una mesa de trabajo:
<Grid x:Name="layout">
<CollectionView x:Name="cv"
BackgroundColor="LightGray">
<CollectionView.ItemsLayout>
<GridItemsLayout SnapPointsAlignment="Start"
SnapPointsType="MandatorySingle"
Orientation="Horizontal"
HorizontalItemSpacing="{Binding Source={x:Reference mainPage}, Path=HingeWidth}"
/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="LightGray"
Padding="0"
Margin="0"
WidthRequest="{Binding Source={x:Reference mainPage}, Path=ContentWidth}"
HeightRequest="{Binding Source={x:Reference mainPage}, Path=ContentHeight}">
<Frame Margin="20"
BackgroundColor="White">
<Label FontSize="Large"
Text="{Binding .}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Frame>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

El objeto CollectionView , con un diseño de cuadrícula que se divide en el ancho de la bisagra, es el enfoque
idóneo para ofrecer esta experiencia de doble pantalla.

Patrón de vista doble


El patrón de vista doble puede ser similar a la vista de "dos páginas", pero se diferencia en el contenido y el
usuario. En este patrón, el contenido se compara en paralelo, posiblemente para editar un documento o una foto,
comparar distintos menús de un restaurante o diferenciar un conflicto de fusión mediante combinación de
archivos de código:
<ContentPage xmlns:local="clr-namespace:Xamarin.Duo.Forms.Samples"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dualScreen="clr-namespace::::no-loc(Xamarin.Forms):::.DualScreen;assembly=:::no-
loc(Xamarin.Forms):::.DualScreen"
x:Class="Xamarin.Duo.Forms.Samples.DualViewListPage">
<dualScreen:TwoPaneView>
<dualScreen:TwoPaneView.Pane1>
<CollectionView x:Name="mapList"
SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10,5,10,5">
<Frame Visual="Material"
BorderColor="LightGray">
<StackLayout Padding="5">
<Label FontSize="Title"
Text="{Binding Title}" />
</StackLayout>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</dualScreen:TwoPaneView.Pane1>
<dualScreen:TwoPaneView.Pane2>
<local:DualViewMap x:Name="mapPage" />
</dualScreen:TwoPaneView.Pane2>
</dualScreen:TwoPaneView>
</ContentPage>

Patrón complementario
El patrón complementario muestra cómo se podría usar la segunda pantalla para proporcionar un segundo nivel
de contenido relacionado con la vista principal, como en el caso de una aplicación de dibujo, un juego o la edición
de elementos multimedia:
<ContentPage xmlns:local="clr-namespace:Xamarin.Duo.Forms.Samples"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dualscreen="clr-namespace::::no-loc(Xamarin.Forms):::.DualScreen;assembly=:::no-
loc(Xamarin.Forms):::.DualScreen"
x:Name="mainPage"
x:Class="Xamarin.Duo.Forms.Samples.CompanionPane"
BackgroundColor="LightGray"
Visual="Material">
<dualscreen:TwoPaneView x:Name="twoPaneView"
MinWideModeWidth="4000"
MinTallModeHeight="4000">
<dualscreen:TwoPaneView.Pane1>
<CarouselView x:Name="cv"
BackgroundColor="LightGray"
IsScrollAnimated="False" >
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="LightGray"
Padding="0"
Margin="0"
WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane1.Width}"
HeightRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane1.Height}">
<Frame Margin="20"
BackgroundColor="White">
<Label FontSize="Large"
Text="{Binding ., StringFormat='Slide Content {0}'}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Frame>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</dualscreen:TwoPaneView.Pane1>
<dualscreen:TwoPaneView.Pane2>
<CollectionView x:Name="indicators"
SelectionMode="Single"
Margin="20, 20, 20, 20"
BackgroundColor="LightGray"
BackgroundColor="LightGray"
WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane2.Width}"
ItemsSource="{Binding Source={x:Reference cv}, Path=ItemsSource}">
<CollectionView.Resources>
<ResourceDictionary>
<Style TargetType="Frame">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Padding"
Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BorderColor"
Value="Green" />
<Setter Property="Padding"
Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ResourceDictionary>
</CollectionView.Resources>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane2.Width}"
CornerRadius="10"
HeightRequest="60"
BackgroundColor="White"
Margin="0">
<StackLayout HorizontalOptions="Fill"
VerticalOptions="Fill"
Orientation="Horizontal">
<Label FontSize="Micro"
Padding="20,0,20,0"
VerticalTextAlignment="Center"
WidthRequest="140" Text="{Binding ., StringFormat='Slide Content {0}'}"
/>
<Label FontSize="Small"
Padding="20,0,20,0"
VerticalTextAlignment="Center"
HorizontalOptions="FillAndExpand"
BackgroundColor="DarkGray"
Grid.Column="1"
Text="{Binding ., StringFormat='Slide {0}'}" />
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</dualscreen:TwoPaneView.Pane2>
</dualscreen:TwoPaneView>
</ContentPage>

Vínculos relacionados
DualScreen (ejemplo)
Creación de aplicaciones para dispositivos de doble pantalla
Diseño TwoPaneView de :::no-loc(Xamarin.Forms):::
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
La clase TwoPaneView representa un contenedor con dos vistas que ajustan el tamaño y la posición del contenido
en el espacio disponible, ya sea en paralelo o de arriba a abajo. TwoPaneView hereda de Grid , por lo que la manera
más sencilla de pensar en estas propiedades es como si se aplicaran a una cuadrícula.

Configuración de TwoPaneView
Siga estas instrucciones para crear un diseño de doble pantalla en la aplicación:
1. Siga las instrucciones de la sección Primeros pasos para agregar NuGet y configurar la clase MainActivity
de Android.
2. Comience con una clase TwoPaneView básica con el código XAML siguiente:

<ContentPage
xmlns:dualScreen="clr-namespace::::no-loc(Xamarin.Forms):::.DualScreen;assembly=:::no-
loc(Xamarin.Forms):::.DualScreen">
<dualScreen:TwoPaneView>
<dualScreen:TwoPaneView.Pane1>
<StackLayout>
<Label Text="Pane1 Content" />
</StackLayout>
</dualScreen:TwoPaneView.Pane1>
<dualScreen:TwoPaneView.Pane2>
<StackLayout>
<Label Text="Pane2 Content" />
</StackLayout>
</dualScreen:TwoPaneView.Pane2>
</dualScreen:TwoPaneView>
</ContentPage>

TIP
El XAML anterior omite muchos atributos comunes del elemento ContentPage . Al agregar una clase TwoPaneView a la
aplicación, no olvide declarar el espacio de nombres xmlns:dualScreen como se muestra.

Descripción de los modos de TwoPaneView


Solo puede estar activo uno de estos modos:
SinglePane actualmente solo hay un panel visible.
Wide los dos paneles se disponen en horizontal. Un panel se sitúa a la izquierda y el otro a la derecha. Cuando
se muestra en dos pantallas, en este modo el dispositivo está en vertical.
Tall los dos paneles se disponen en vertical. Un panel está en la parte superior y el otro en la parte inferior.
Cuando se muestra en dos pantallas, en este modo el dispositivo está en horizontal.

Control de TwoPaneView cuando solo está en una pantalla


Las siguientes propiedades se aplican cuando TwoPaneView ocupa una sola pantalla:
MinTallModeHeight indica el alto mínimo que debe tener el control para entrar en modo Tall.
MinWideModeWidth indica el ancho mínimo que debe tener el control para entrar en modo Wide.
Pane1Length establece el ancho de Panel1 en el modo Wide, el alto de Panel1 en modo Tall y no tiene ningún
efecto en el modo SinglePane.
Pane2Length establece el ancho de Panel2 en el modo Wide, el alto de Panel2 en modo Tall y no tiene ningún
efecto en el modo SinglePane.

IMPORTANT
Si TwoPaneView se distribuye en dos pantallas, estas propiedades no tienen ningún efecto.

Propiedades que se aplican cuando se está en una pantalla o en dos


Las siguientes propiedades se aplican cuando TwoPaneView ocupa una sola pantalla o dos pantallas:
TallModeConfiguration cuando está en modo Tall (alto) indica la disposición de arriba a abajo, o bien si solo
quiere un panel visible según lo que se haya definido mediante TwoPaneViewPriority.
WideModeConfiguration cuando está en modo Wide (ancho), la disposición de izquierda a derecha, o bien si solo
quiere un panel visible según lo que se haya definido mediante TwoPaneViewPriority.
PanePriority determina si se muestra Panel1 o Panel2 en el modo SinglePane.

Vínculos relacionados
DualScreen (ejemplo)
Clase auxiliar DualScreenInfo de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
La clase DualScreenInfo permite determinar en qué panel se encuentra la vista, qué tamaño tiene, en qué posición
está el dispositivo, el ángulo de la bisagra y mucho más.

Configuración de DualScreenInfo
Siga estas instrucciones para crear un diseño de doble pantalla en la aplicación:
1. Siga las instrucciones de la sección Primeros pasos para agregar NuGet y configurar la clase MainActivity de
Android.
2. Agregue using :::no-loc(Xamarin.Forms):::.DualScreen; al archivo de clase.
3. Use la clase DualScreenInfo.Current en la aplicación.

Propiedades
Cuando se distribuye en dos pantallas, SpanningBounds devuelve dos rectángulos que indican los límites de
cada área visible. Si la ventana no se distribuye, se devolverá una matriz vacía.
HingeBounds indica la posición de la bisagra en la pantalla.
IsLandscape indica si el dispositivo está en posición horizontal. Esto resulta útil porque las API de orientación
nativas no notifican correctamente la orientación cuando una aplicación se distribuye en dos pantallas.
SpanMode indica si el diseño está en modo de alto, de ancho o de un solo panel.

Además, el evento PropertyChangedse desencadena cuando cambian las propiedades, y el evento


HingeAngleChanged se desencadena cuando cambia el ángulo de la bisagra.

Sondeo del angulo de bisagra en Android y UWP


El método siguiente está disponible cuando se tiene acceso a DualScreenInfo desde proyectos de la plataforma
Android y UWP:
GetHingeAngleAsync recupera el ángulo actual de la bisagra del dispositivo. Al usar el simulador, se puede
establecer el valor de HingeAngle si se modifica el sensor de presión.
Este método se puede invocar desde los representadores personalizados en Android y UWP. En el código siguiente
se muestra un ejemplo de representador personalizado de Android:
public class HingeAngleLabelRenderer : :::no-
loc(Xamarin.Forms):::.Platform.Android.FastRenderers.LabelRenderer
{
System.Timers.Timer _hingeTimer;
public HingeAngleLabelRenderer(Context context) : base(context)
{
}

async void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)


{
if (_hingeTimer == null)
return;

_hingeTimer.Stop();
var hingeAngle = await DualScreenInfo.Current.GetHingeAngleAsync();

Device.BeginInvokeOnMainThread(() =>
{
if (_hingeTimer != null)
Element.Text = hingeAngle.ToString();
});

if (_hingeTimer != null)
_hingeTimer.Start();
}

protected override void OnElementChanged(ElementChangedEventArgs<Label> e)


{
base.OnElementChanged(e);

if (_hingeTimer == null)
{
_hingeTimer = new System.Timers.Timer(100);
_hingeTimer.Elapsed += OnTimerElapsed;
_hingeTimer.Start();
}
}

protected override void Dispose(bool disposing)


{
if (_hingeTimer != null)
{
_hingeTimer.Elapsed -= OnTimerElapsed;
_hingeTimer.Stop();
_hingeTimer = null;
}

base.Dispose(disposing);
}
}

Acceso a DualScreenInfo en la ventana de la aplicación


En el código siguiente se muestra cómo acceder a DualScreenInfo en la ventana de la aplicación:
DualScreenInfo currentWindow = DualScreenInfo.Current;

// Retrieve absolute position of the hinge on the screen


var hingeBounds = currentWindow.HingeBounds;

// check if app window is spanned across two screens


if(currentWindow.SpanMode == TwoPaneViewMode.SinglePane)
{
// window is only on one screen
}
else if(currentWindow.SpanMode == TwoPaneViewMode.Tall)
{
// window is spanned across two screens and oriented top-bottom
}
else if(currentWindow.SpanMode == TwoPaneViewMode.Wide)
{
// window is spanned across two screens and oriented side-by-side
}

// Detect if any of the properties on DualScreenInfo change.


// This is useful to detect if the app window gets spanned
// across two screens or put on only one
currentWindow.PropertyChanged += OnDualScreenInfoChanged;

Aplicación de DualScreenInfo a los diseños


La clase DualScreenInfo tiene un constructor que puede tomar un diseño y proporcionar información sobre él con
respecto a las dos pantallas del dispositivo:

<Grid x:Name="grid" ColumnSpacing="0">


<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Column1Width}" />
<ColumnDefinition Width="{Binding Column2Width}" />
<ColumnDefinition Width="{Binding Column3Width}" />
</Grid.ColumnDefinitions>
<Label FontSize="Large"
VerticalOptions="Center"
HorizontalOptions="End"
Text="I should be on the left side of the hinge" />
<Label FontSize="Large"
VerticalOptions="Center"
HorizontalOptions="Start"
Grid.Column="2"
Text="I should be on the right side of the hinge" />
</Grid>
public partial class GridUsingDualScreenInfo : ContentPage
{
public DualScreenInfo DualScreenInfo { get; }
public double Column1Width { get; set; }
public double Column2Width { get; set; }
public double Column3Width { get; set; }

public GridUsingDualScreenInfo()
{
InitializeComponent();
DualScreenInfo = new DualScreenInfo(grid);
BindingContext = this;
}

protected override void OnAppearing()


{
base.OnAppearing();
DualScreenInfo.PropertyChanged += OnInfoPropertyChanged;
UpdateColumns();
}

protected override void OnDisappearing()


{
base.OnDisappearing();
DualScreenInfo.PropertyChanged -= OnInfoPropertyChanged;
}

void UpdateColumns()
{
// Check if grid is on two screens
if (DualScreenInfo.SpanningBounds.Length > 0)
{
// set the width of the first column to the width of the layout
// that's on the left screen
Column1Width = DualScreenInfo.SpanningBounds[0].Width;

// set the middle column to the width of the hinge


Column2Width = DualScreenInfo.HingeBounds.Width;

// set the width of the third column to the width of the layout
// that's on the right screen
Column3Width = DualScreenInfo.SpanningBounds[1].Width;
}
else
{
Column1Width = 100;
Column2Width = 0;
Column3Width = 100;
}

OnPropertyChanged(nameof(Column1Width));
OnPropertyChanged(nameof(Column2Width));
OnPropertyChanged(nameof(Column3Width));

void OnInfoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)


{
UpdateColumns();
}
}

En la captura de pantalla siguiente se muestra el diseño resultante:


Vínculos relacionados
DualScreen (ejemplo)
Desencadenadores de doble pantalla de
Xamarin.Forms
18/12/2020 • 3 minutes to read • Edit Online

El espacio de nombres de Xamarin.Forms.DualScreen incluye dos desencadenadores de estado:


SpanModeStateTrigger desencadena un cambio en VisualState cuando se modifica el modo de vista del diseño
adjunto.
WindowSpanModeStateTrigger desencadena un cambio en VisualState cuando se modifica el modo de vista de
la ventana.
Para obtener más información sobre desencadenadores de estado, vea Desencadenadores de estado.

Desencadenador de estado de modo de intervalo


Un elemento SpanModeStateTrigger desencadena un cambio en VisualState cuando se modifica el modo de
intervalo del diseño adjunto. Este desencadenador tiene una única propiedad enlazable:
SpanMode , de tipo TwoPaneViewMode , que indica el modo de intervalo al que debe aplicarse VisualState .

NOTE
SpanModeStateTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un controlador de eventos al
evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Grid que incluye objetos SpanModeStateTrigger :
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="GridSingle">
<VisualState.StateTriggers>
<dualScreen:SpanModeStateTrigger SpanMode="SinglePane"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Green" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="GridWide">
<VisualState.StateTriggers>
<dualScreen:SpanModeStateTrigger SpanMode="Wide" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Red" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="GridTall">
<VisualState.StateTriggers>
<dualScreen:SpanModeStateTrigger SpanMode="Tall" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Purple" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
</Grid>

En este ejemplo, los estados visuales se establecen en un objeto Grid . El color de fondo del elemento Grid es
verde cuando solo se muestra un panel, rojo cuando se muestran los paneles en paralelo y púrpura cuando los
paneles se muestran de arriba a abajo.

Desencadenador de estado de modo de intervalo de ventana


Un elemento WindowSpanModeStateTrigger desencadena un cambio en VisualState cuando se modifica el modo de
intervalo de la ventana. Este desencadenador tiene una única propiedad enlazable:
SpanMode , de tipo TwoPaneViewMode , que indica el modo de intervalo al que debe aplicarse VisualState .

NOTE
El elemento WindowSpanModeStateTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un
controlador de eventos al evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Grid que incluye objetos WindowSpanModeStateTrigger :
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="NotSpanned">
<VisualState.StateTriggers>
<dualScreen:WindowSpanModeStateTrigger SpanMode="SinglePane"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Red" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Spanned">
<VisualState.StateTriggers>
<dualScreen:WindowSpanModeStateTrigger SpanMode="Wide" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Green" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Tall">
<VisualState.StateTriggers>
<dualScreen:WindowSpanModeStateTrigger SpanMode="Tall" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Yellow" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
</Grid>

En este ejemplo, los estados visuales se establecen en un objeto Grid . El color de fondo del elemento Grid es
rojo cuando solo se muestra un panel, verde cuando se muestran los paneles en paralelo y amarillo cuando los
paneles se muestran de arriba a abajo.

Vínculos relacionados
Desencadenadores de Xamarin.Forms
Administrador de estado visual de Xamarin.Forms
Efectos de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Las interfaces de usuario de Xamarin.Forms se representan mediante los controles nativos de la plataforma de
destino, lo que permite que las aplicaciones de Xamarin.Forms conserven la apariencia adecuada para cada
plataforma. Con los efectos se pueden personalizar los controles nativos de cada plataforma sin tener que
recurrir a la implementación de un representador personalizado.

Introducción a los efectos


Con los efectos se pueden personalizar los controles nativos de cada plataforma y normalmente se usan para
pequeños cambios de estilo. En este artículo se proporciona una introducción a los efectos, se describe el límite
entre los efectos y los representadores personalizados y se describe la clase PlatformEffect .

Crear un efecto
Los efectos simplifican la personalización de un control. En este artículo se muestra cómo crear un efecto que
cambia el color de fondo del control Entry cuando el control recibe el foco.

Pasar parámetros a un efecto


La creación de un efecto que se configura a través de parámetros permite que el efecto se pueda volver a usar.
En estos artículos se muestra cómo usar las propiedades para pasar parámetros a un efecto y cambiar un
parámetro en tiempo de ejecución.

Invocación de eventos desde un efecto


Los efectos pueden invocar eventos. En este artículo se muestra cómo crear un evento que implementa el
seguimiento de dedo multitoque de bajo nivel y señala una aplicación para presiones de toque, movimientos y
liberaciones.

RoundEffect reutilizable
RoundEffect es un efecto reutilizable que se puede aplicar a cualquier control derivado de VisualElement para
representar el control como un círculo. Este efecto se puede usar para crear imágenes circulares, botones
circulares u otros controles circulares.
Introducción a los efectos
18/12/2020 • 6 minutes to read • Edit Online

Con los efectos se pueden personalizar los controles nativos de cada plataforma y normalmente se usan para
pequeños cambios de estilo. En este artículo se proporciona una introducción a los efectos, se describe el límite
entre los efectos y los representadores personalizados, y se describe la clase PlatformEffect.
En Páginas, diseños y controles de Xamarin.Forms, se presenta una API común para describir interfaces de usuario
móviles multiplataforma. Cada página, diseño y control se representa de forma distinta en cada plataforma
mediante una clase Renderer que, a su vez, crea un control nativo (correspondiente a la representación de
Xamarin.Forms), lo coloca en la pantalla y agrega el comportamiento especificado en el código compartido.
Los desarrolladores pueden implementar sus propias clases Renderer personalizadas para personalizar la
apariencia o el comportamiento de un control. Pero la implementación de una clase de representador
personalizado para llevar a cabo una personalización de controles simples suele ser una respuesta compleja. Los
efectos simplifican este proceso y permiten que los controles nativos de cada plataforma se puedan personalizar
más fácilmente.
Los efectos se crean en proyectos específicos de la plataforma mediante la creación de subclases del control
PlatformEffect y, después, se consumen al asociarlos a un control adecuado en una biblioteca de .NET Standard
de Xamarin.Forms o un proyecto de biblioteca compartida.

¿Por qué usar un efecto en lugar de un representador personalizado?


Los efectos simplifican la personalización de un control, son reutilizables y se pueden parametrizar para aumentar
todavía más la reutilización.
Todo lo que se puede lograr con un efecto también se puede lograr con un representador personalizado. Pero los
representadores personalizados ofrecen más flexibilidad y personalización que los efectos. En las instrucciones
siguientes se enumeran las circunstancias bajo las que elegir un efecto en lugar de un representador
personalizado:
Un efecto se recomienda cuando el cambio de las propiedades de un control específico de la plataforma
proporcionará el resultado deseado.
Un representador personalizado es necesario cuando hay que invalidar los métodos de un control específico de
la plataforma.
Un representador personalizado es necesario cuando hay que reemplazar el control específico de la plataforma
que implementa un control de Xamarin.Forms.

Creación de subclases de la clase PlatformEffect


En la tabla siguiente se muestra el espacio de nombres para la clase PlatformEffect en cada plataforma y los tipos
de sus propiedades:

P L ATA F O RM A ESPA C IO DE N O M B RES C O N T EN EDO R C O N T RO L

iOS Xamarin.Forms.Platform.iOS UIView UIView

Android Xamarin.Forms.Platform.And ViewGroup Ver


roid
P L ATA F O RM A ESPA C IO DE N O M B RES C O N T EN EDO R C O N T RO L

Plataforma universal de Xamarin.Forms.Platform.UW FrameworkElement FrameworkElement


Windows (UWP) P

Cada una de las clases PlatformEffect específicas de la plataforma expone las propiedades siguientes:
Container : hace referencia al control específico de la plataforma que se usa para implementar el diseño.
Control : hace referencia al control específico de la plataforma que se usa para implementar el control de
Xamarin.Forms.
Element : hace referencia al control de Xamarin.Forms que se va a representar.

Los efectos no tienen información de tipo sobre el contenedor, el control o el elemento al que se adjuntan, ya que
se pueden adjuntar a cualquier elemento. Por tanto, cuando se adjunta un efecto a un elemento que no es
compatible, se debe degradar correctamente o iniciar una excepción. Pero las propiedades Container , Control y
Element se pueden convertir a su tipo de implementación. Para obtener más información sobre estos tipos, vea
Clases base y controles nativos del representador.
Cada clase PlatformEffect específica de la plataforma expone los métodos siguientes, que se deben invalidar para
implementar un efecto:
OnAttached : se llama cuando se adjunta un efecto a un control de Xamarin.Forms. Una versión invalidada de
este método, en cada clase de efecto específica de la plataforma, es el lugar para realizar la personalización del
control, junto con el control de excepciones en caso de que no se pueda aplicar el efecto al control de
Xamarin.Forms especificado.
OnDetached : se llama cuando se desasocia un efecto de un control de Xamarin.Forms. Una versión invalidada de
este método, en cada clase de efecto específica de la plataforma, es el lugar para realizar cualquier limpieza de
efectos, como anular el registro de un controlador de eventos.
Además, PlatformEffect expone el método OnElementPropertyChanged , que también se puede invalidar. Este
método se llama cuando ha cambiado una propiedad del elemento. Una versión invalidada de este método, en
cada clase de efecto específica de la plataforma, es el lugar para responder a los cambios de propiedad enlazable
en el control de Xamarin.Forms. Siempre se debe realizar una comprobación de la propiedad que ha modificado,
ya que esta invalidación se puede llamar varias veces.

Vínculos relacionados
Representadores personalizados
Creación de un efecto
18/12/2020 • 13 minutes to read • Edit Online

Descargar el ejemplo
Los efectos simplifican la personalización de un control. En este artículo se muestra cómo crear un efecto que
cambia el color de fondo del control Entry cuando recibe el foco.
El proceso para crear un efecto de cada proyecto específico de la plataforma es el siguiente:
1. Se crea una subclase de la clase PlatformEffect .
2. Se invalida el método OnAttached y se escribe lógica para personalizar el control.
3. Se invalida el método OnDetached y se escribe lógica para limpiar la personalización del control, si es
necesario.
4. Se agrega un atributo ResolutionGroupName a la clase de efecto. Este atributo establece un espacio de nombres
para los efectos para toda la empresa, lo que evita conflictos con otros efectos con el mismo nombre. Tenga en
cuenta que este atributo solo se puede aplicar una vez por proyecto.
5. Agregue un atributo ExportEffect a la clase de efecto. Este atributo registra el efecto con un identificador
único que :::no-loc(Xamarin.Forms)::: usa junto con el nombre del grupo para buscar el efecto antes de aplicarlo
a un control. El atributo toma dos parámetros: el nombre de tipo del efecto y una cadena única que se usará
para buscar el efecto antes de aplicarlo a un control.
Después, el efecto se puede consumir si se adjunta al control adecuado.

NOTE
Proporcionar un efecto en cada proyecto de la plataforma es un paso opcional. Al intentar usar un efecto cuando no se ha
registrado uno, se devolverá un valor distinto de NULL que no hace nada.

En la aplicación de ejemplo se muestra un elemento FocusEffect que cambia el color de fondo de un control
cuando recibe el foco. El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de
ejemplo, junto con las relaciones entre ellos:

La clase FocusEffect personaliza un control Entry en el elemento HomePage en cada proyecto específico de la
plataforma. Cada clase FocusEffect se deriva de la clase PlatformEffect para cada plataforma. Como resultado,
se representa el control Entry con un color de fondo específico de la plataforma, que cambia cuando el control
recibe el foco, como se muestra en las capturas de pantalla siguientes:
Creación del efecto en cada plataforma
En las secciones siguientes se describe la implementación específica de la plataforma de la clase FocusEffect .

Proyecto de iOS
En el ejemplo de código siguiente se muestra la implementación FocusEffect para el proyecto de iOS:
using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.iOS;

[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(EffectsDemo.iOS.FocusEffect), nameof(EffectsDemo.iOS.FocusEffect))]
namespace EffectsDemo.iOS
{
public class FocusEffect : PlatformEffect
{
UIColor backgroundColor;

protected override void OnAttached ()


{
try {
Control.BackgroundColor = backgroundColor = UIColor.FromRGB (204, 153, 255);
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}

protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged (args);

try {
if (args.PropertyName == "IsFocused") {
if (Control.BackgroundColor == backgroundColor) {
Control.BackgroundColor = UIColor.White;
} else {
Control.BackgroundColor = backgroundColor;
}
}
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}
}
}

El método establece la propiedad BackgroundColor del control en color morado claro con el método
OnAttached
UIColor.FromRGB y también almacena este color en un campo. Esta funcionalidad se encapsula en un bloque try /
catch en caso de que el control al que está asociado el efecto no tenga una propiedad BackgroundColor . El
método OnDetached no proporciona ninguna implementación porque no se necesita limpieza.
La invalidación de OnElementPropertyChanged responde a los cambios de propiedad enlazable en el control de :::no-
loc(Xamarin.Forms):::. Cuando cambia la propiedad IsFocused , la propiedad BackgroundColor del control se
cambia a color blanco si el control tiene el foco; en caso contrario, se cambia a color morado claro. Esta
funcionalidad se encapsula en un bloque try / catch en caso de que el control al que está asociado el efecto no
tenga una propiedad BackgroundColor .

Proyecto de Android
En el ejemplo de código siguiente se muestra la implementación FocusEffect para el proyecto de Android:
using System;
using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.Android;

[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(EffectsDemo.Droid.FocusEffect), nameof(EffectsDemo.Droid.FocusEffect))]
namespace EffectsDemo.Droid
{
public class FocusEffect : PlatformEffect
{
Android.Graphics.Color originalBackgroundColor = new Android.Graphics.Color(0, 0, 0, 0);
Android.Graphics.Color backgroundColor;

protected override void OnAttached()


{
try
{
backgroundColor = Android.Graphics.Color.LightGreen;
Control.SetBackgroundColor(backgroundColor);
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached()


{
}

protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(args);
try
{
if (args.PropertyName == "IsFocused")
{
if (((Android.Graphics.Drawables.ColorDrawable)Control.Background).Color ==
backgroundColor)
{
Control.SetBackgroundColor(originalBackgroundColor);
}
else
{
Control.SetBackgroundColor(backgroundColor);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
}
}

El método OnAttached llama al método SetBackgroundColor para establecer el color de fondo del control en verde
claro y también almacena este color en un campo. Esta funcionalidad se encapsula en un bloque try / catch en
caso de que el control al que está asociado el efecto no tenga una propiedad SetBackgroundColor . El método
OnDetached no proporciona ninguna implementación porque no se necesita limpieza.

La invalidación de OnElementPropertyChanged responde a los cambios de propiedad enlazable en el control de :::no-


loc(Xamarin.Forms):::. Cuando cambia la propiedad IsFocused , el color de fondo del control se cambia a color
blanco si el control tiene el foco; en caso contrario, se cambia a color verde claro. Esta funcionalidad se encapsula
en un bloque try / catch en caso de que el control al que está asociado el efecto no tenga una propiedad
BackgroundColor .

Proyectos de la Plataforma universal de Windows


En el ejemplo de código siguiente se muestra la implementación FocusEffect para los proyectos de la Plataforma
universal de Windows (UWP):

using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Platform.UWP;

[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(EffectsDemo.UWP.FocusEffect), nameof(EffectsDemo.UWP.FocusEffect))]
namespace EffectsDemo.UWP
{
public class FocusEffect : PlatformEffect
{
protected override void OnAttached()
{
try
{
(Control as Windows.UI.Xaml.Controls.Control).Background = new SolidColorBrush(Colors.Cyan);
(Control as FormsTextBox).BackgroundFocusBrush = new SolidColorBrush(Colors.White);
}
catch (Exception ex)
{
Debug.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached()


{
}
}
}

El método OnAttached establece la propiedad Background del control en cian y la propiedad


BackgroundFocusBrush en blanco. Esta funcionalidad se encapsula en un bloque try / catch en caso de que el
control al que está asociado el efecto carezca de estas propiedades. El método OnDetached no proporciona
ninguna implementación porque no se necesita limpieza.

Consumo del efecto


El proceso para consumir un efecto desde un proyecto de biblioteca de .NET Standard o de biblioteca compartida
de :::no-loc(Xamarin.Forms)::: es el siguiente:
1. Se declara un control que el efecto va a personalizar.
2. Se adjunta el efecto al control agregándolo a la colección Effects del control.

NOTE
Una instancia de efecto solo se puede adjuntar a un único control. Por tanto, un efecto se debe resolver dos veces para
usarlo en dos controles.

Consumo del efecto en XAML


En el ejemplo de código XAML siguiente se muestra un control Entry al que se adjunta el elemento FocusEffect :
<Entry Text="Effect attached to an Entry" ...>
<Entry.Effects>
<local:FocusEffect />
</Entry.Effects>
...
</Entry>

La clase FocusEffect de la biblioteca de .NET Standard admite el consumo de efectos en XAML, y se muestra en el
ejemplo de código siguiente:

public class FocusEffect : RoutingEffect


{
public FocusEffect () : base ($"MyCompany.{nameof(FocusEffect)}")
{
}
}

La clase FocusEffect crea subclases de la clase RoutingEffect , que representa un efecto independiente de la
plataforma que encapsula un efecto interno que suele ser específico de la plataforma. La clase FocusEffect llama
al constructor de clase base, y se pasa un parámetro que consiste en la concatenación del nombre del grupo de
resolución (que se especifica con el atributo ResolutionGroupName en la clase de efecto), y el identificador único
que se ha especificado con el atributo ExportEffect en la clase de efecto. Por tanto, cuando se inicializa Entry en
tiempo de ejecución, se agrega una nueva instancia de MyCompany.FocusEffect a la colección Effects del control.
Los efectos también se pueden adjuntar a los controles mediante un comportamiento, o bien mediante
propiedades adjuntas. Para obtener más información sobre cómo adjuntar un efecto a un control mediante un
comportamiento, vea EffectBehavior reutilizable. Para obtener más información sobre cómo adjuntar un efecto a
un control mediante propiedades adjuntas, vea Pasar parámetros a un efecto.

Consumo del efecto en C#


En el siguiente ejemplo de código, se muestra el control Entry equivalente en C#:

var entry = new Entry {


Text = "Effect attached to an Entry",
...
};

FocusEffectse adjunta a la instancia de Entry mediante la adición del efecto a la colección Effects del control,
como se muestra en el ejemplo de código siguiente:

public HomePageCS ()
{
...
entry.Effects.Add (Effect.Resolve ($"MyCompany.{nameof(FocusEffect)}"));
...
}

Effect.Resolve devuelve un elemento Effect para el nombre especificado, que es una concatenación del
nombre del grupo de resolución (que se especifica con el atributo ResolutionGroupName en la clase de efecto), y el
identificador único que se ha especificado con el atributo ExportEffect en la clase de efecto. Si una plataforma no
proporciona el efecto, el método Effect.Resolve devolverá un valor que no es null .

Resumen
En este artículo se ha mostrado cómo crear un efecto que cambia el color de fondo del control Entry cuando el
control recibe el foco.

Vínculos relacionados
Representadores personalizados
Efecto
PlatformEffect
Efecto de color de fondo (ejemplo)
Efecto de enfoque (ejemplo)
Paso de parámetros a un efecto
18/12/2020 • 2 minutes to read • Edit Online

Los parámetros de efecto pueden definirse mediante propiedades, lo que permite volver a usar el efecto. Después,
se pueden pasar parámetros al efecto mediante la especificación de valores para cada propiedad al crear una
instancia del efecto.

Pasar parámetros de efecto como propiedades de Common Language


Runtime
Las propiedades de Common Language Runtime (CLR) se pueden usar para definir parámetros de efecto que no
responden a los cambios de propiedades en tiempo de ejecución. En este artículo se muestra cómo usar
propiedades CLR para pasar parámetros a un efecto.

Pasar parámetros de efecto como propiedades adjuntas


Las propiedades adjuntas se pueden usar para definir los parámetros de efecto que responden a los cambios de
propiedades en tiempo de ejecución. En este artículo se muestra cómo usar las propiedades adjuntas para pasar
parámetros a un efecto y cambiar un parámetro en tiempo de ejecución.
Pasar parámetros de efecto como propiedades de
Common Language Runtime
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
Las propiedades de Common Language Runtime (CLR) se pueden usar para definir parámetros de efecto que no
responden a los cambios de propiedades en tiempo de ejecución. En este artículo se muestra cómo usar
propiedades CLR para pasar parámetros a un efecto.
El proceso para crear parámetros de efecto que no respondan a los cambios de propiedades en tiempo de
ejecución es el siguiente:
1. Cree una clase public que genere subclases de la clase RoutingEffect . La clase RoutingEffect representa un
efecto independiente de la plataforma que encapsula un efecto interno, que suele ser específico de la
plataforma.
2. Cree un constructor que llame al constructor de clase base, y pase una concatenación del nombre del grupo de
resolución y el identificador único que se ha especificado en cada clase de efecto específica de la plataforma.
3. Agregue propiedades a la clase para cada parámetro que se va a pasar al efecto.
Después, se pueden pasar parámetros al efecto mediante la especificación de valores para cada propiedad al crear
una instancia del efecto.
En la aplicación de ejemplo se muestra un elemento ShadowEffect que agrega una sombra al texto mostrado por
un control Label . El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de
ejemplo, junto con las relaciones entre ellos:

LabelShadowEffect personaliza un control Label en el elemento HomePage en cada proyecto específico de la


plataforma. Los parámetros se pasan a cada elemento LabelShadowEffect a través de las propiedades de la clase
ShadowEffect . Cada clase LabelShadowEffect se deriva de la clase PlatformEffect para cada plataforma. Como
resultado, se agrega una sombra al texto mostrado por el control Label , como se muestra en las capturas de
pantalla siguientes:
Creación de parámetros de efecto
Se debe crear una clase public que genere subclases de la clase RoutingEffect para representar los parámetros
efecto, como se muestra en el ejemplo de código siguiente:

public class ShadowEffect : RoutingEffect


{
public float Radius { get; set; }

public Color Color { get; set; }

public float DistanceX { get; set; }

public float DistanceY { get; set; }

public ShadowEffect () : base ("MyCompany.LabelShadowEffect")


{
}
}

El elemento ShadowEffect contiene cuatro propiedades que representan los parámetros que se van a pasar a cada
elemento LabelShadowEffect específico de la plataforma. El constructor de la clase llama al constructor de clase
base, y se pasa un parámetro que consiste en la concatenación del nombre del grupo de resolución y el
identificador único que se ha especificado en cada clase de efecto específica de la plataforma. Por tanto, cuando se
crea una instancia de ShadowEffect , se agrega una nueva instancia de MyCompany.LabelShadowEffect a la colección
Effects de un control.

Consumo del efecto


En el ejemplo de código XAML siguiente se muestra un control Label al que se adjunta el elemento ShadowEffect :

<Label Text="Label Shadow Effect" ...>


<Label.Effects>
<local:ShadowEffect Radius="5" DistanceX="5" DistanceY="5">
<local:ShadowEffect.Color>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="Black" />
<On Platform="Android" Value="White" />
<On Platform="UWP" Value="Red" />
</OnPlatform>
</local:ShadowEffect.Color>
</local:ShadowEffect>
</Label.Effects>
</Label>

En el siguiente ejemplo de código, se muestra el control Label equivalente en C#:


var label = new Label {
Text = "Label Shadow Effect",
...
};

Color color = Color.Default;


switch (Device.RuntimePlatform)
{
case Device.iOS:
color = Color.Black;
break;
case Device.Android:
color = Color.White;
break;
case Device.UWP:
color = Color.Red;
break;
}

label.Effects.Add (new ShadowEffect {


Radius = 5,
Color = color,
DistanceX = 5,
DistanceY = 5
});

En ambos ejemplos de código, se crea una instancia de la clase ShadowEffect con valores especificados para cada
propiedad, antes de agregarse a la colección Effects del control. Observe que la propiedad ShadowEffect.Color
usa valores de color específicos de la plataforma. Para obtener más información, vea Clase Device.

Creación del efecto en cada plataforma


En las secciones siguientes se describe la implementación específica de la plataforma de la clase LabelShadowEffect
.
Proyecto de iOS
En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de iOS:
[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.iOS
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
try {
var effect = (ShadowEffect)Element.Effects.FirstOrDefault (e => e is ShadowEffect);
if (effect != null) {
Control.Layer.ShadowRadius = effect.Radius;
Control.Layer.ShadowColor = effect.Color.ToCGColor ();
Control.Layer.ShadowOffset = new CGSize (effect.DistanceX, effect.DistanceY);
Control.Layer.ShadowOpacity = 1.0f;
}
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
}
}

El método OnAttached recupera la instancia de ShadowEffect y, después, establece las propiedades Control.Layer
en los valores de propiedad especificados para crear la sombra. Esta funcionalidad se encapsula en un bloque try
/ catch en caso de que el control al que está asociado el efecto no tenga las propiedades Control.Layer . El
método OnDetached no proporciona ninguna implementación porque no se necesita limpieza.
Proyecto de Android
En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de Android:

[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.Droid
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
try {
var control = Control as Android.Widget.TextView;
var effect = (ShadowEffect)Element.Effects.FirstOrDefault (e => e is ShadowEffect);
if (effect != null) {
float radius = effect.Radius;
float distanceX = effect.DistanceX;
float distanceY = effect.DistanceY;
Android.Graphics.Color color = effect.Color.ToAndroid ();
control.SetShadowLayer (radius, distanceX, distanceY, color);
}
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
}
}
El método OnAttached recupera la instancia de ShadowEffect y llama al método TextView.SetShadowLayer para
crear una sombra con los valores de propiedad especificados. Esta funcionalidad se encapsula en un bloque try /
catch en caso de que el control al que está asociado el efecto no tenga las propiedades Control.Layer . El método
OnDetached no proporciona ninguna implementación porque no se necesita limpieza.

Proyecto de la Plataforma universal de Windows


En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de
Plataforma universal de Windows (UWP):

[assembly: ResolutionGroupName ("Xamarin")]


[assembly: ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.UWP
{
public class LabelShadowEffect : PlatformEffect
{
bool shadowAdded = false;

protected override void OnAttached ()


{
try {
if (!shadowAdded) {
var effect = (ShadowEffect)Element.Effects.FirstOrDefault (e => e is ShadowEffect);
if (effect != null) {
var textBlock = Control as Windows.UI.Xaml.Controls.TextBlock;
var shadowLabel = new Label ();
shadowLabel.Text = textBlock.Text;
shadowLabel.FontAttributes = FontAttributes.Bold;
shadowLabel.HorizontalOptions = LayoutOptions.Center;
shadowLabel.VerticalOptions = LayoutOptions.CenterAndExpand;
shadowLabel.TextColor = effect.Color;
shadowLabel.TranslationX = effect.DistanceX;
shadowLabel.TranslationY = effect.DistanceY;

((Grid)Element.Parent).Children.Insert (0, shadowLabel);


shadowAdded = true;
}
}
} catch (Exception ex) {
Debug.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
}
}

La Plataforma Universal de Windows no proporciona un efecto de sombra, por lo que la implementación de


LabelShadowEffect en ambas plataformas simula una mediante la adición de un segundo control Label de
desplazamiento detrás del control Label principal. El método OnAttached recupera la instancia de ShadowEffect ,
crea el objeto Label y establece algunas propiedades de diseño en el objeto Label . Después, para crear la
sombra, se establecen las propiedades TextColor , TranslationX y TranslationY para controlar el color y la
ubicación del objeto Label . Después, el elemento shadowLabel se inserta mediante desplazamiento detrás del
elemento Label principal. Esta funcionalidad se encapsula en un bloque try / catch en caso de que el control al
que está asociado el efecto no tenga las propiedades Control.Layer . El método OnDetached no proporciona
ninguna implementación porque no se necesita limpieza.

Resumen
En este artículo se ha mostrado cómo usar propiedades CLR para pasar parámetros a un efecto. Las propiedades
CLR se pueden usar para definir parámetros de efecto que no responden a los cambios de propiedades en tiempo
de ejecución.

Vínculos relacionados
Representadores personalizados
Efecto
PlatformEffect
RoutingEffect
Efecto de sombra (ejemplo)
Pasar parámetros de efecto como propiedades
adjuntas
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
Las propiedades adjuntas se pueden usar para definir los parámetros de efecto que responden a los cambios de
propiedades en tiempo de ejecución. En este artículo se muestra cómo usar las propiedades adjuntas para pasar
parámetros a un efecto y cambiar un parámetro en tiempo de ejecución.
El proceso para crear parámetros de efecto que respondan a los cambios de propiedades en tiempo de ejecución
es el siguiente:
1. Se crea una clase static que contiene una propiedad adjunta para cada parámetro que se va a pasar al efecto.
2. Se agrega una propiedad adjunta adicional a la clase que se va a usar para controlar la adición o eliminación del
efecto del control al que se va a conectar la clase. Se asegura que esta propiedad adjunta registra un delegado
propertyChanged que se ejecutará cuando cambie el valor de la propiedad.
3. Se crean captadores y establecedores static para cada propiedad adjunta.
4. Se implementa la lógica en el delegado propertyChanged para agregar y quitar el efecto.
5. Se implementa una clase anidada dentro de la clase static , con el nombre del efecto, que crea subclases de la
clase RoutingEffect . Para el constructor, se llama al constructor de clase base, y se pasa una concatenación del
nombre del grupo de resolución y el identificador único que se ha especificado en cada clase de efecto
específica de la plataforma.
Después, se pueden pasar parámetros al efecto mediante la adición de las propiedades adjuntas y los valores de
propiedad al control adecuado. Además, los parámetros se pueden cambiar en tiempo de ejecución mediante la
especificación de un nuevo valor de propiedad adjunta.

NOTE
Una propiedad adjunta es un tipo especial de propiedad enlazable, definida en una clase, pero adjunta a otros objetos, y
reconocible en XAML como atributos que contienen una clase y un nombre de propiedad separados por un punto. Para
obtener más información, vea Propiedades adjuntas.

En la aplicación de ejemplo se muestra un elemento ShadowEffect que agrega una sombra al texto mostrado por
un control Label . Además, el color de la sombra se puede cambiar en tiempo de ejecución. El siguiente diagrama
muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las relaciones entre ellos:

LabelShadowEffect personaliza un control Label en el elemento HomePage en cada proyecto específico de la


plataforma. Los parámetros se pasan a cada elemento LabelShadowEffect a través de las propiedades adjuntas de
la clase ShadowEffect . Cada clase LabelShadowEffect se deriva de la clase PlatformEffect para cada plataforma.
Como resultado, se agrega una sombra al texto mostrado por el control Label , como se muestra en las capturas
de pantalla siguientes:

Creación de parámetros de efecto


Se debe crear una clase static para representar los parámetros efecto, como se muestra en el ejemplo de código
siguiente:
public static class ShadowEffect
{
public static readonly BindableProperty HasShadowProperty =
BindableProperty.CreateAttached ("HasShadow", typeof(bool), typeof(ShadowEffect), false, propertyChanged:
OnHasShadowChanged);
public static readonly BindableProperty ColorProperty =
BindableProperty.CreateAttached ("Color", typeof(Color), typeof(ShadowEffect), Color.Default);
public static readonly BindableProperty RadiusProperty =
BindableProperty.CreateAttached ("Radius", typeof(double), typeof(ShadowEffect), 1.0);
public static readonly BindableProperty DistanceXProperty =
BindableProperty.CreateAttached ("DistanceX", typeof(double), typeof(ShadowEffect), 0.0);
public static readonly BindableProperty DistanceYProperty =
BindableProperty.CreateAttached ("DistanceY", typeof(double), typeof(ShadowEffect), 0.0);

public static bool GetHasShadow (BindableObject view)


{
return (bool)view.GetValue (HasShadowProperty);
}

public static void SetHasShadow (BindableObject view, bool value)


{
view.SetValue (HasShadowProperty, value);
}
...

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


{
var view = bindable as View;
if (view == null) {
return;
}

bool hasShadow = (bool)newValue;


if (hasShadow) {
view.Effects.Add (new LabelShadowEffect ());
} else {
var toRemove = view.Effects.FirstOrDefault (e => e is LabelShadowEffect);
if (toRemove != null) {
view.Effects.Remove (toRemove);
}
}
}

class LabelShadowEffect : RoutingEffect


{
public LabelShadowEffect () : base ("MyCompany.LabelShadowEffect")
{
}
}
}

ShadowEffect contiene cinco propiedades adjuntas, con captadores y establecedores static para cada propiedad
adjunta. Cuatro de estas propiedades representan los parámetros que se van a pasar a cada elemento
LabelShadowEffect específico de la plataforma. La clase ShadowEffect también define una propiedad adjunta
HasShadow que se usa para controlar la adición o eliminación del efecto del control al que se conecta la clase
ShadowEffect . Esta propiedad adjunta registra el método OnHasShadowChanged que se ejecutará cuando cambie el
valor de la propiedad. Este método agrega o quita el efecto en función del valor de la propiedad adjunta
HasShadow .

La clase LabelShadowEffect anidada, que crea subclases de la clase RoutingEffect , admite la adición y eliminación
de efectos. La clase RoutingEffect representa un efecto independiente de la plataforma que encapsula un efecto
interno, que suele ser específico de la plataforma. Esto simplifica el proceso de eliminación del efecto, ya que no
hay ningún acceso en tiempo de compilación a la información de tipo para un efecto específico de la plataforma. El
constructor LabelShadowEffect llama al constructor de clase base, y se pasa un parámetro que consiste en la
concatenación del nombre del grupo de resolución y el identificador único que se ha especificado en cada clase de
efecto específica de la plataforma. Esto habilita la adición y eliminación del efecto en el método
OnHasShadowChanged , como se indica a continuación:

Adición de efectos : se agrega una nueva instancia de LabelShadowEffect a la colección Effects del control.
Esto reemplaza al uso del método Effect.Resolve para agregar el efecto.
Eliminación de efectos : se recupera y se quita la primera instancia de LabelShadowEffect en la colección
Effects del control.

Consumo del efecto


Cada elemento LabelShadowEffect específico de la plataforma se puede usar mediante la adición de las
propiedades adjuntas a un control Label , como se muestra en el ejemplo de código XAML siguiente:

<Label Text="Label Shadow Effect" ...


local:ShadowEffect.HasShadow="true" local:ShadowEffect.Radius="5"
local:ShadowEffect.DistanceX="5" local:ShadowEffect.DistanceY="5">
<local:ShadowEffect.Color>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="Black" />
<On Platform="Android" Value="White" />
<On Platform="UWP" Value="Red" />
</OnPlatform>
</local:ShadowEffect.Color>
</Label>

En el siguiente ejemplo de código, se muestra el control Label equivalente en C#:

var label = new Label {


Text = "Label Shadow Effect",
...
};

Color color = Color.Default;


switch (Device.RuntimePlatform)
{
case Device.iOS:
color = Color.Black;
break;
case Device.Android:
color = Color.White;
break;
case Device.UWP:
color = Color.Red;
break;
}

ShadowEffect.SetHasShadow (label, true);


ShadowEffect.SetRadius (label, 5);
ShadowEffect.SetDistanceX (label, 5);
ShadowEffect.SetDistanceY (label, 5);
ShadowEffect.SetColor (label, color));

Al establecer la propiedad adjunta ShadowEffect.HasShadow en true se ejecuta el método


ShadowEffect.OnHasShadowChanged que agrega o quita LabelShadowEffect del control Label . En ambos ejemplos de
código, la propiedad adjunta ShadowEffect.Color proporciona valores de color específicos de la plataforma. Para
obtener más información, vea Clase Device.
Además, Button permite que se pueda cambiar el color de la sombra en tiempo de ejecución. Cuando se hace clic
en Button , el código siguiente cambia el color de la sombra estableciendo la propiedad adjunta
ShadowEffect.Color :

ShadowEffect.SetColor (label, Color.Teal);

Consumo del efecto con un estilo


Los efectos que se pueden consumir mediante la adición de propiedades adjuntas a un control también se pueden
consumir con un estilo. En el ejemplo de código XAML siguiente se muestra un estilo explícito para el efecto de
sombra, que se puede aplicar a controles Label :

<Style x:Key="ShadowEffectStyle" TargetType="Label">


<Style.Setters>
<Setter Property="local:ShadowEffect.HasShadow" Value="True" />
<Setter Property="local:ShadowEffect.Radius" Value="5" />
<Setter Property="local:ShadowEffect.DistanceX" Value="5" />
<Setter Property="local:ShadowEffect.DistanceY" Value="5" />
</Style.Setters>
</Style>

Style se puede aplicar a un control Label si se establece su propiedad Style en la instancia de Style mediante
la extensión de marcado StaticResource , como se muestra en el ejemplo de código siguiente:

<Label Text="Label Shadow Effect" ... Style="{StaticResource ShadowEffectStyle}" />

Para obtener más información sobre los estilos, vea Estilos.

Creación del efecto en cada plataforma


En las secciones siguientes se describe la implementación específica de la plataforma de la clase
LabelShadowEffect .

Proyecto de iOS
En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de iOS:
[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.iOS
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
try {
UpdateRadius ();
UpdateColor ();
UpdateOffset ();
Control.Layer.ShadowOpacity = 1.0f;
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
...

void UpdateRadius ()
{
Control.Layer.ShadowRadius = (nfloat)ShadowEffect.GetRadius (Element);
}

void UpdateColor ()
{
Control.Layer.ShadowColor = ShadowEffect.GetColor (Element).ToCGColor ();
}

void UpdateOffset ()
{
Control.Layer.ShadowOffset = new CGSize (
(double)ShadowEffect.GetDistanceX (Element),
(double)ShadowEffect.GetDistanceY (Element));
}
}

El método OnAttached llama a métodos que recuperan los valores de propiedad adjunta mediante los captadores
ShadowEffect , y que establecen propiedades Control.Layer en los valores de propiedad para crear la sombra. Esta
funcionalidad se encapsula en un bloque try / catch en caso de que el control al que está asociado el efecto no
tenga las propiedades Control.Layer . El método OnDetached no proporciona ninguna implementación porque no
se necesita limpieza.
Respuesta a los cambios de propiedad
Si alguno de los valores de las propiedades adjuntas ShadowEffect cambia en tiempo de ejecución, el efecto debe
responder mostrando los cambios. Una versión invalidada del método OnElementPropertyChanged en la clase de
efecto específica de la plataforma es el lugar para responder a los cambios de propiedad enlazable, como se
muestra en el ejemplo de código siguiente:
public class LabelShadowEffect : PlatformEffect
{
...
protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
{
if (args.PropertyName == ShadowEffect.RadiusProperty.PropertyName) {
UpdateRadius ();
} else if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
UpdateColor ();
} else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
UpdateOffset ();
}
}
...
}

El método OnElementPropertyChanged actualiza el radio, el color o el desplazamiento de la sombra, siempre que


haya cambiado el valor de la propiedad adjunta ShadowEffect correspondiente. Siempre se debe realizar una
comprobación de la propiedad que ha modificado, ya que esta invalidación se puede llamar varias veces.
Proyecto de Android
En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de Android:
[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.Droid
{
public class LabelShadowEffect : PlatformEffect
{
Android.Widget.TextView control;
Android.Graphics.Color color;
float radius, distanceX, distanceY;

protected override void OnAttached ()


{
try {
control = Control as Android.Widget.TextView;
UpdateRadius ();
UpdateColor ();
UpdateOffset ();
UpdateControl ();
} catch (Exception ex) {
Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
...

void UpdateControl ()
{
if (control != null) {
control.SetShadowLayer (radius, distanceX, distanceY, color);
}
}

void UpdateRadius ()
{
radius = (float)ShadowEffect.GetRadius (Element);
}

void UpdateColor ()
{
color = ShadowEffect.GetColor (Element).ToAndroid ();
}

void UpdateOffset ()
{
distanceX = (float)ShadowEffect.GetDistanceX (Element);
distanceY = (float)ShadowEffect.GetDistanceY (Element);
}
}

El método OnAttached llama a métodos que recuperan los valores de propiedad adjunta mediante los captadores
ShadowEffect , y llama a un método que llama al método TextView.SetShadowLayer para crear una sombra con los
valores de propiedad. Esta funcionalidad se encapsula en un bloque try / catch en caso de que el control al que
está asociado el efecto no tenga las propiedades Control.Layer . El método OnDetached no proporciona ninguna
implementación porque no se necesita limpieza.
Respuesta a los cambios de propiedad
Si alguno de los valores de las propiedades adjuntas ShadowEffect cambia en tiempo de ejecución, el efecto debe
responder mostrando los cambios. Una versión invalidada del método OnElementPropertyChanged en la clase de
efecto específica de la plataforma es el lugar para responder a los cambios de propiedad enlazable, como se
muestra en el ejemplo de código siguiente:
public class LabelShadowEffect : PlatformEffect
{
...
protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
{
if (args.PropertyName == ShadowEffect.RadiusProperty.PropertyName) {
UpdateRadius ();
UpdateControl ();
} else if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
UpdateColor ();
UpdateControl ();
} else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
UpdateOffset ();
UpdateControl ();
}
}
...
}

El método OnElementPropertyChanged actualiza el radio, el color o el desplazamiento de la sombra, siempre que


haya cambiado el valor de la propiedad adjunta ShadowEffect correspondiente. Siempre se debe realizar una
comprobación de la propiedad que ha modificado, ya que esta invalidación se puede llamar varias veces.
Proyecto de la Plataforma universal de Windows
En el ejemplo de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de
Plataforma universal de Windows (UWP):
[assembly: ResolutionGroupName ("MyCompany")]
[assembly: ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.UWP
{
public class LabelShadowEffect : PlatformEffect
{
Label shadowLabel;
bool shadowAdded = false;

protected override void OnAttached ()


{
try {
if (!shadowAdded) {
var textBlock = Control as Windows.UI.Xaml.Controls.TextBlock;

shadowLabel = new Label ();


shadowLabel.Text = textBlock.Text;
shadowLabel.FontAttributes = FontAttributes.Bold;
shadowLabel.HorizontalOptions = LayoutOptions.Center;
shadowLabel.VerticalOptions = LayoutOptions.CenterAndExpand;

UpdateColor ();
UpdateOffset ();

((Grid)Element.Parent).Children.Insert (0, shadowLabel);


shadowAdded = true;
}
} catch (Exception ex) {
Debug.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
}
}

protected override void OnDetached ()


{
}
...

void UpdateColor ()
{
shadowLabel.TextColor = ShadowEffect.GetColor (Element);
}

void UpdateOffset ()
{
shadowLabel.TranslationX = ShadowEffect.GetDistanceX (Element);
shadowLabel.TranslationY = ShadowEffect.GetDistanceY (Element);
}
}
}

La Plataforma Universal de Windows no proporciona un efecto de sombra, por lo que la implementación de


LabelShadowEffect en ambas plataformas simula una mediante la adición de un segundo control Label de
desplazamiento detrás del control Label principal. El método OnAttached crea el objeto Label y establece
algunas propiedades de diseño en el objeto Label . Después, llama a métodos que recuperan los valores de
propiedad adjunta mediante los captadores ShadowEffect , y crea la sombra mediante el establecimiento de las
propiedades TextColor , TranslationX y TranslationY para controlar el color y la ubicación de Label . Después, el
elemento shadowLabel se inserta mediante desplazamiento detrás del elemento Label principal. Esta
funcionalidad se encapsula en un bloque try / catch en caso de que el control al que está asociado el efecto no
tenga las propiedades Control.Layer . El método OnDetached no proporciona ninguna implementación porque no
se necesita limpieza.
Respuesta a los cambios de propiedad
Si alguno de los valores de las propiedades adjuntas ShadowEffect cambia en tiempo de ejecución, el efecto debe
responder mostrando los cambios. Una versión invalidada del método OnElementPropertyChanged en la clase de
efecto específica de la plataforma es el lugar para responder a los cambios de propiedad enlazable, como se
muestra en el ejemplo de código siguiente:

public class LabelShadowEffect : PlatformEffect


{
...
protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
{
if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
UpdateColor ();
} else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
UpdateOffset ();
}
}
...
}

El método OnElementPropertyChanged actualiza el color o el desplazamiento de la sombra, siempre que haya


cambiado el valor de la propiedad adjunta ShadowEffect correspondiente. Siempre se debe realizar una
comprobación de la propiedad que ha modificado, ya que esta invalidación se puede llamar varias veces.

Resumen
En este artículo se ha mostrado cómo usar propiedades adjuntas para pasar parámetros a un efecto y cambiar un
parámetro en tiempo de ejecución. Las propiedades adjuntas se pueden usar para definir los parámetros de efecto
que responden a los cambios de propiedades en tiempo de ejecución.

Vínculos relacionados
Representadores personalizados
Efecto
PlatformEffect
RoutingEffect
Efecto de sombra (ejemplo)
Invocación de eventos desde efectos
18/12/2020 • 38 minutes to read • Edit Online

Descargar el ejemplo
Un efecto puede definir e invocar un evento, señalando cambios en la vista nativa subyacente. En este artículo se
muestra cómo implementar seguimiento multitáctil de bajo nivel y cómo se generan eventos que indican actividad
táctil.
El efecto que se describe en este artículo proporciona acceso a eventos de función táctil de bajo nivel. Estos
eventos de bajo nivel no están disponibles a través de las clases GestureRecognizer existentes, pero son vitales
para algunos tipos de aplicaciones. Por ejemplo, una aplicación de dibujo táctil necesita realizar un seguimiento de
los dedos individuales cuando se mueven en la pantalla. Un teclado musical debe detectar cuándo se pulsan y
sueltan teclas individuales, así como un deslizamiento de dedos de una clave a otra en un glissando.
Un efecto es ideal para el seguimiento multitáctil, ya que puede asociarse a cualquier elemento de :::no-
loc(Xamarin.Forms):::.

Eventos táctiles de plataforma


iOS, Android y la Plataforma universal de Windows incluyen una API de bajo nivel que permite a las aplicaciones
detectar la actividad táctil. Todas estas plataformas distinguen entre tres tipos básicos de eventos táctiles:
Presionado , cuando un dedo toca la pantalla
Movido , cuando se mueve un dedo tocando la pantalla
Soltado , cuando se separa el dedo de la pantalla
En un entorno multitáctil, varios dedos pueden tocar la pantalla al mismo tiempo. Las diferentes plataformas
incluyen un número de identificación (ID) que las aplicaciones pueden usar para distinguir entre varios dedos.
En iOS, la clase UIView define tres métodos reemplazables, TouchesBegan , TouchesMoved y TouchesEnded
correspondientes a estos tres eventos básicos. En el artículo Multi-Touch Finger Tracking (Seguimiento multitáctil)
se describe cómo usar estos métodos. Sin embargo, un programa de iOS no necesita invalidar una clase que
derive de UIView para usar estos métodos. UIGestureRecognizer de iOS también define estos mismos tres
métodos y se puede adjuntar a una instancia de una clase que deriva de UIGestureRecognizer a cualquier objeto
UIView .

En Android, la clase View define un método reemplazable denominado OnTouchEvent para procesar toda la
actividad táctil. El tipo de la actividad táctil se define por miembros de la enumeración Down , PointerDown , Move ,
Up y PointerUp tal como se describe en el artículo Multi-Touch Finger Tracking (Seguimiento multitáctil). View
de Android también define un evento denominado Touch que permite que un controlador de eventos se adjunte a
cualquier objeto View .
En la Plataforma universal de Windows (UWP), la clase UIElement define eventos denominados PointerPressed ,
PointerMoved y PointerReleased . Estos se describen en el artículo Handle pointer input (Controlar la entrada del
puntero) de MSDN y en la documentación de API para la clase UIElement .
La API Pointer en la Plataforma universal de Windows está diseñada para unificar la entrada de mouse, táctil y
manuscrita. Por ese motivo, el evento PointerMoved se invoca cuando el mouse se mueve a través de un elemento,
incluso cuando no se ha presionado ningún botón del mouse. El objeto PointerRoutedEventArgs que acompaña a
estos eventos tiene una propiedad denominada Pointer , que tiene una propiedad denominada IsInContact , que
indica si se presiona un botón del mouse o un dedo está en contacto con la pantalla.
Además, la UWP define dos eventos más denominados PointerEntered y PointerExited . Estos indican si un dedo
o el mouse se mueven de un elemento a otro. Por ejemplo, imagine dos elementos adyacentes denominados A y B.
Ambos elementos tienen controladores instalados para los eventos de puntero. Cuando se presiona un dedo en A,
el evento PointerPressed se invoca. Cuando se mueve el dedo, A invoca eventos PointerMoved . Si el dedo se
mueve de A a B, A invoca un evento PointerExited y B invoca un evento PointerEntered . Si después se suelta el
dedo, B invoca un evento PointerReleased .
Las plataformas iOS y Android son diferentes de UWP: la vista que obtiene primero la llamada a TouchesBegan o
OnTouchEvent cuando un dedo toca la vista continúa obteniendo toda la actividad de interacción, incluso si se
mueve el dedo a distintas vistas. UWP puede comportarse de forma similar si la aplicación captura el puntero: en
el controlador de eventos PointerEntered , el elemento llama a CapturePointer y después obtiene toda la actividad
táctil de ese dedo.
El enfoque de UWP resulta muy útil para algunos tipos de aplicaciones, por ejemplo, un teclado musical. Cada
clave puede controlar los eventos táctiles para esa clave y detectar cuando un dedo se deslizó de una tecla a otra
mediante los eventos PointerEntered y PointerExited .
Por ese motivo, el efecto de seguimiento táctil que se describe en este artículo implementa el enfoque de UWP.

La API de efecto de seguimiento táctil


El ejemplo de Touch Tracking Effect Demos (Demostraciones de efecto de seguimiento táctil) contiene las clases
(y una enumeración) que implementan el seguimiento táctil de bajo nivel. Estos tipos pertenecen al espacio de
nombres TouchTracking y comienzan con la palabra Touch . El proyecto de biblioteca de .NET Standard
TouchTrackingEffectDemos incluye la enumeración TouchActionType para el tipo de eventos táctiles:

public enum TouchActionType


{
Entered,
Pressed,
Moved,
Released,
Exited,
Cancelled
}

Todas las plataformas incluyen también un evento que indica que se ha cancelado el evento táctil.
La clase TouchEffectde la biblioteca .NET Standard deriva de RoutingEffect y define un evento denominado
TouchAction y un método denominado OnTouchAction que invoca al evento TouchAction :

public class TouchEffect : RoutingEffect


{
public event TouchActionEventHandler TouchAction;

public TouchEffect() : base("XamarinDocs.TouchEffect")


{
}

public bool Capture { set; get; }

public void OnTouchAction(Element element, TouchActionEventArgs args)


{
TouchAction?.Invoke(element, args);
}
}
Observe también la propiedad Capture . Para capturar eventos táctiles, una aplicación debe establecer esta
propiedad en true antes de un evento Pressed . En caso contrario, los eventos táctiles se comportan como los de
la Plataforma universal de Windows.
La clase TouchActionEventArgs en la biblioteca de .NET Standard contiene toda la información que acompaña a
cada evento:

public class TouchActionEventArgs : EventArgs


{
public TouchActionEventArgs(long id, TouchActionType type, Point location, bool isInContact)
{
Id = id;
Type = type;
Location = location;
IsInContact = isInContact;
}

public long Id { private set; get; }

public TouchActionType Type { private set; get; }

public Point Location { private set; get; }

public bool IsInContact { private set; get; }


}

Una aplicación puede utilizar la propiedad Id para el seguimiento de dedos individuales. Observe la propiedad
IsInContact . Esta propiedad es siempre true para eventos Pressed y false para eventos Released . También es
siempre true para eventos Moved en iOS y Android. La propiedad IsInContact podría ser false para Moved
eventos en la Plataforma universal de Windows cuando se ejecuta el programa en el escritorio y se mueve el
puntero del mouse sin un botón presionado.
Puede usar la clase TouchEffect en sus propias aplicaciones si incluye el archivo en el proyecto de biblioteca de
.NET Standard de la solución y si agrega una instancia a la colección de Effects de cualquier elemento de :::no-
loc(Xamarin.Forms):::. Adjunte un controlador al evento TouchAction para obtener los eventos táctiles.
Para usar TouchEffect en su propia aplicación, también necesitará las implementaciones de plataforma incluidas
en la solución TouchTrackingEffectDemos .

Las implementaciones de efecto de seguimiento táctil


Las implementaciones de iOS, Android y UWP de TouchEffect se describen aquí comenzando con la
implementación más sencilla (UWP) y terminando con la implementación de iOS porque es estructuralmente más
compleja que las demás.
La implementación de UWP
La implementación de UWP de TouchEffect es la más sencilla. Como de costumbre, la clase se deriva de
PlatformEffect e incluye dos atributos de ensamblado:
[assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.UWP.TouchEffect), "TouchEffect")]

namespace TouchTracking.UWP
{
public class TouchEffect : PlatformEffect
{
...
}
}

La invalidación OnAttached guarda información, como los campos, y adjunta los controladores a todos los eventos
de puntero:

public class TouchEffect : PlatformEffect


{
FrameworkElement frameworkElement;
TouchTracking.TouchEffect effect;
Action<Element, TouchActionEventArgs> onTouchAction;

protected override void OnAttached()


{
// Get the Windows FrameworkElement corresponding to the Element that the effect is attached to
frameworkElement = Control == null ? Container : Control;

// Get access to the TouchEffect class in the .NET Standard library


effect = (TouchTracking.TouchEffect)Element.Effects.
FirstOrDefault(e => e is TouchTracking.TouchEffect);

if (effect != null && frameworkElement != null)


{
// Save the method to call on touch events
onTouchAction = effect.OnTouchAction;

// Set event handlers on FrameworkElement


frameworkElement.PointerEntered += OnPointerEntered;
frameworkElement.PointerPressed += OnPointerPressed;
frameworkElement.PointerMoved += OnPointerMoved;
frameworkElement.PointerReleased += OnPointerReleased;
frameworkElement.PointerExited += OnPointerExited;
frameworkElement.PointerCanceled += OnPointerCancelled;
}
}
...
}

El controlador OnPointerPressed invoca el evento de efecto llamando al campo onTouchAction en el método


CommonHandler :
public class TouchEffect : PlatformEffect
{
...
void OnPointerPressed(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Pressed, args);

// Check setting of Capture property


if (effect.Capture)
{
(sender as FrameworkElement).CapturePointer(args.Pointer);
}
}
...
void CommonHandler(object sender, TouchActionType touchActionType, PointerRoutedEventArgs args)
{
PointerPoint pointerPoint = args.GetCurrentPoint(sender as UIElement);
Windows.Foundation.Point windowsPoint = pointerPoint.Position;

onTouchAction(Element, new TouchActionEventArgs(args.Pointer.PointerId,


touchActionType,
new Point(windowsPoint.X, windowsPoint.Y),
args.Pointer.IsInContact));
}
}

OnPointerPressed también comprueba el valor de la propiedad Capture en la clase efecto en la biblioteca de .NET
Standard y llama a CapturePointer si es true .

Los otros controladores de eventos de UWP son incluso más sencillos:

public class TouchEffect : PlatformEffect


{
...
void OnPointerEntered(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Entered, args);
}
...
}

La implementación de Android
Las implementaciones de Android y iOS son necesariamente más complejas porque deben implementar los
eventos Exited y Entered cuando un dedo se mueve de un elemento a otro. Ambas implementaciones tienen
una estructura similar.
La clase TouchEffect de Android instala un controlador para el evento Touch :

view = Control == null ? Container : Control;


...
view.Touch += OnTouch;

La clase también define dos diccionarios estáticos:


public class TouchEffect : PlatformEffect
{
...
static Dictionary<Android.Views.View, TouchEffect> viewDictionary =
new Dictionary<Android.Views.View, TouchEffect>();

static Dictionary<int, TouchEffect> idToEffectDictionary =


new Dictionary<int, TouchEffect>();
...

viewDictionary obtiene una nueva entrada cada vez que se llama a la invalidación OnAttached :

viewDictionary.Add(view, this);

La entrada se quita del diccionario en OnDetached . Todas las instancias de TouchEffect están asociadas a una vista
concreta a la que está conectada el efecto. El diccionario estático permite cualquier instancia de TouchEffect para
enumerar todas las demás vistas y sus correspondientes instancias de TouchEffect . Esto es necesario para
permitir la transferencia de los eventos de una vista a otra.
Android asigna un código de identificador a los eventos táctiles que permite que una aplicación realice un
seguimiento de los dedos individuales. El idToEffectDictionary asocia este código de identificador con una
instancia de TouchEffect . Se agrega un elemento a este diccionario cuando se llama al controlador Touch para
una pulsación de dedo:

void OnTouch(object sender, Android.Views.View.TouchEventArgs args)


{
...
switch (args.Event.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
FireEvent(this, id, TouchActionType.Pressed, screenPointerCoords, true);

idToEffectDictionary.Add(id, this);

capture = libTouchEffect.Capture;
break;

El elemento se quita de idToEffectDictionary cuando se separa el dedo de la pantalla. El método FireEvent


simplemente acumula toda la información necesaria para llamar al método OnTouchAction :

void FireEvent(TouchEffect touchEffect, int id, TouchActionType actionType, Point pointerLocation, bool
isInContact)
{
// Get the method to call for firing events
Action<Element, TouchActionEventArgs> onTouchAction = touchEffect.libTouchEffect.OnTouchAction;

// Get the location of the pointer within the view


touchEffect.view.GetLocationOnScreen(twoIntArray);
double x = pointerLocation.X - twoIntArray[0];
double y = pointerLocation.Y - twoIntArray[1];
Point point = new Point(fromPixels(x), fromPixels(y));

// Call the method


onTouchAction(touchEffect.formsElement,
new TouchActionEventArgs(id, actionType, point, isInContact));
}
Se procesan todos los demás tipos de interacción de dos maneras diferentes: si la propiedad Capture es true , el
evento táctil es una traducción bastante simple para la información de TouchEffect . Se complica más cuando
Capture es false , porque los eventos táctiles quizás tengan que moverse de una vista a otra. Esta es la
responsabilidad del método CheckForBoundaryHop , al que se llama durante los eventos de movimiento. Este
método usa ambos diccionarios estáticos. Enumera a través de viewDictionary para determinar la vista que
actualmente esté tocando el dedo y usa idToEffectDictionary para almacenar la instancia de TouchEffect actual
(y por lo tanto, la vista actual) asociada con un identificador determinado:

void CheckForBoundaryHop(int id, Point pointerLocation)


{
TouchEffect touchEffectHit = null;

foreach (Android.Views.View view in viewDictionary.Keys)


{
// Get the view rectangle
try
{
view.GetLocationOnScreen(twoIntArray);
}
catch // System.ObjectDisposedException: Cannot access a disposed object.
{
continue;
}
Rectangle viewRect = new Rectangle(twoIntArray[0], twoIntArray[1], view.Width, view.Height);

if (viewRect.Contains(pointerLocation))
{
touchEffectHit = viewDictionary[view];
}
}

if (touchEffectHit != idToEffectDictionary[id])
{
if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Exited, pointerLocation, true);
}
if (touchEffectHit != null)
{
FireEvent(touchEffectHit, id, TouchActionType.Entered, pointerLocation, true);
}
idToEffectDictionary[id] = touchEffectHit;
}
}

Si ha habido un cambio en el idToEffectDictionary , el método potencialmente llama a FireEvent para Exited y


Entered para transferir de una vista a otra. Con todo, es posible que el dedo se moviese a un área ocupada por
una vista sin un TouchEffect adjunto o desde esa área a una vista con el efecto adjunto.
Tenga en cuenta el bloqueo try y catch cuando se accede a la vista. En una página a la que se navega que
después navega a la página principal, no se llama al método OnDetached y los elementos permanecen en el
viewDictionary pero Android los considera eliminados.

La implementación de iOS
La implementación de iOS es similar a la implementación de Android, salvo que la clase TouchEffect de iOS debe
crear una instancia de un derivado de UIGestureRecognizer . Se trata de una clase en el proyecto de iOS
denominado TouchRecognizer . Esta clase mantiene dos diccionarios estáticos que almacenan instancias de
TouchRecognizer :
static Dictionary<UIView, TouchRecognizer> viewDictionary =
new Dictionary<UIView, TouchRecognizer>();

static Dictionary<long, TouchRecognizer> idToTouchDictionary =


new Dictionary<long, TouchRecognizer>();

Gran parte de la estructura de esta clase TouchRecognizer es similar a la de la clase TouchEffect de Android.

IMPORTANT
Muchas de las vistas de UIKit no tienen la funcionalidad táctil habilitada de forma predeterminada. La funcionalidad táctil
se puede habilitar agregando view.UserInteractionEnabled = true; a la invalidación OnAttached en la clase
TouchEffect en el proyecto de iOS. Esto debe ocurrir después obtener UIView , que se corresponde con el elemento al
que está asociado el efecto.

Poner el efecto táctil en funcionamiento


El programa TouchTrackingEffectDemos contiene cinco páginas que prueban el efecto de seguimiento táctil
para tareas comunes.
La página Arrastre de BoxView le permite agregar elementos BoxView a un AbsoluteLayout y después
arrastrarlos en la pantalla. El archivo XAML crea instancias de dos vistas de Button para agregar elementos
BoxView a la AbsoluteLayout y borrar el AbsoluteLayout .

El método en el archivo de código subyacente que agrega un nuevo BoxView a AbsoluteLayout también agrega
un objeto TouchEffect para BoxView y adjunta un controlador de eventos para el efecto:

void AddBoxViewToLayout()
{
BoxView boxView = new BoxView
{
WidthRequest = 100,
HeightRequest = 100,
Color = new Color(random.NextDouble(),
random.NextDouble(),
random.NextDouble())
};

TouchEffect touchEffect = new TouchEffect();


touchEffect.TouchAction += OnTouchEffectAction;
boxView.Effects.Add(touchEffect);
absoluteLayout.Children.Add(boxView);
}

El controlador de eventos TouchAction procesa todos los eventos táctiles para todos los elementos de BoxView ,
pero debe usarse con cuidado: no puede permitir dos dedos en una sola BoxView porque el programa solo
implementa el arrastre y los dos dedos podrían interferir entre sí. Por este motivo, la página define una clase
incrustada para cada dedo del que está realizando el seguimiento:
class DragInfo
{
public DragInfo(long id, Point pressPoint)
{
Id = id;
PressPoint = pressPoint;
}

public long Id { private set; get; }

public Point PressPoint { private set; get; }


}

Dictionary<BoxView, DragInfo> dragDictionary = new Dictionary<BoxView, DragInfo>();

El dragDictionary contiene una entrada para cada BoxView que se arrastra actualmente.
La acción táctil Pressed agrega un elemento a este diccionario y la acción Released lo quita. La lógica Pressed
debe comprobar si ya hay un elemento en el diccionario para esa BoxView . Si es así, BoxView ya se está
arrastrando y el nuevo evento es un segundo dedo en esa misma BoxView . Para las acciones Moved y Released , el
controlador de eventos debe comprobar si el diccionario tiene una entrada para esa BoxView y que la propiedad
Id táctil para la BoxView arrastrada coincide con el que aparece en la entrada del diccionario:

void OnTouchEffectAction(object sender, TouchActionEventArgs args)


{
BoxView boxView = sender as BoxView;

switch (args.Type)
{
case TouchActionType.Pressed:
// Don't allow a second touch on an already touched BoxView
if (!dragDictionary.ContainsKey(boxView))
{
dragDictionary.Add(boxView, new DragInfo(args.Id, args.Location));

// Set Capture property to true


TouchEffect touchEffect = (TouchEffect)boxView.Effects.FirstOrDefault(e => e is TouchEffect);
touchEffect.Capture = true;
}
break;

case TouchActionType.Moved:
if (dragDictionary.ContainsKey(boxView) && dragDictionary[boxView].Id == args.Id)
{
Rectangle rect = AbsoluteLayout.GetLayoutBounds(boxView);
Point initialLocation = dragDictionary[boxView].PressPoint;
rect.X += args.Location.X - initialLocation.X;
rect.Y += args.Location.Y - initialLocation.Y;
AbsoluteLayout.SetLayoutBounds(boxView, rect);
}
break;

case TouchActionType.Released:
if (dragDictionary.ContainsKey(boxView) && dragDictionary[boxView].Id == args.Id)
{
dragDictionary.Remove(boxView);
}
break;
}
}

La lógica Pressed establece la propiedad Capture del objeto TouchEffect en true . Esto tiene el efecto de
entregar todos los eventos subsiguientes para ese dedo en el mismo controlador de eventos.
La lógica Moved mueve la BoxView modificando la propiedad adjunta LayoutBounds . La propiedad Location de
los argumentos de evento siempre es relativa a la BoxView que se está arrastrando y si el BoxView se está
arrastrando a una velocidad constante, las propiedades Location de los eventos consecutivos serán
aproximadamente las mismas. Por ejemplo, si un dedo presiona el BoxView en su centro, la acción Pressed
almacena una propiedad PressPoint de (50, 50), que sigue siendo la misma para los eventos posteriores. Si la
BoxView se arrastra en diagonal a una velocidad constante, las propiedades Location subsiguientes durante la
acción Moved podrían ser valores de (55, 55), en cuyo caso la lógica Moved agrega 5 a la posición horizontal y
vertical de la BoxView . Esto mueve la BoxView de forma que su centro está de nuevo directamente bajo el dedo.
Puede mover varios elementos BoxView al mismo tiempo usando diferentes dedos.

Creación de subclases de la vista


A menudo, resulta más fácil para un elemento de :::no-loc(Xamarin.Forms)::: controlar sus propios eventos táctiles.
La página Arrastre de BoxView arrastrable funciona igual que la página Arrastre de BoxView , pero los
elementos que el usuario arrastra son instancias de una clase DraggableBoxView que se deriva de BoxView :
class DraggableBoxView : BoxView
{
bool isBeingDragged;
long touchId;
Point pressPoint;

public DraggableBoxView()
{
TouchEffect touchEffect = new TouchEffect
{
Capture = true
};
touchEffect.TouchAction += OnTouchEffectAction;
Effects.Add(touchEffect);
}

void OnTouchEffectAction(object sender, TouchActionEventArgs args)


{
switch (args.Type)
{
case TouchActionType.Pressed:
if (!isBeingDragged)
{
isBeingDragged = true;
touchId = args.Id;
pressPoint = args.Location;
}
break;

case TouchActionType.Moved:
if (isBeingDragged && touchId == args.Id)
{
TranslationX += args.Location.X - pressPoint.X;
TranslationY += args.Location.Y - pressPoint.Y;
}
break;

case TouchActionType.Released:
if (isBeingDragged && touchId == args.Id)
{
isBeingDragged = false;
}
break;
}
}
}

El constructor crea y adjunta el TouchEffect y establece la propiedad Capture cuando se crea una instancia de ese
objeto por primera vez. No se necesita ningún diccionario porque la propia clase almacena valores
isBeingDragged , pressPoint y touchId asociados con cada dedo. El control Moved modifica las propiedades
TranslationX y TranslationY , por lo que la lógica funcionará incluso si el elemento primario de la
DraggableBoxView no es un AbsoluteLayout .

Integración con SkiaSharp


Las siguientes dos demostraciones requieren gráficos y usan SkiaSharp para este propósito. Es posible que le
interese obtener información sobre el uso de SkiaSharp en :::no-loc(Xamarin.Forms)::: antes de estudiar estos
ejemplos. Los dos primeros artículos ("Conceptos básicos de dibujo de SkiaSharp" y "Rutas y líneas de acceso de
SkiaSharp") incluyen todo lo que necesitará aquí.
La página Ellipse Drawing (Dibujo de elipse) le permite dibujar una elipse deslizando el dedo en la pantalla.
Dependiendo de cómo mueva el dedo, puede dibujar la elipse desde la esquina superior izquierda a la esquina
inferior derecha, o desde cualquier otra esquina hasta la esquina opuesta. La elipse se dibuja con un color aleatorio
y una opacidad.

Si después toca uno de los puntos suspensivos, puede arrastrarla a otra ubicación. Esto requiere una técnica
conocida como "prueba de posicionamiento," lo que implica buscar el objeto gráfico en un momento determinado.
Los puntos suspensivos de SkiaSharp no son elementos de :::no-loc(Xamarin.Forms):::, por lo que no pueden
realizar su propio procesamiento de TouchEffect . El TouchEffect debe aplicarse a todo el objeto SKCanvasView .
El archivo EllipseDrawPage.xaml crea una instancia de SKCanvasView en un Grid de una sola celda. El objeto
TouchEffect se asocia a ese Grid :

<Grid x:Name="canvasViewGrid"
Grid.Row="1"
BackgroundColor="White">

<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>

En la Plataforma universal de Windows y en Android el TouchEffect se puede conectar directamente a la


SKCanvasView , pero en iOS no es posible. Tenga en cuenta que la propiedad Capture está establecida en true .

Cada elipse que representa SkiaSharp se representa mediante un objeto de tipo EllipseDrawingFigure :
class EllipseDrawingFigure
{
SKPoint pt1, pt2;

public EllipseDrawingFigure()
{
}

public SKColor Color { set; get; }

public SKPoint StartPoint


{
set
{
pt1 = value;
MakeRectangle();
}
}

public SKPoint EndPoint


{
set
{
pt2 = value;
MakeRectangle();
}
}

void MakeRectangle()
{
Rectangle = new SKRect(pt1.X, pt1.Y, pt2.X, pt2.Y).Standardized;
}

public SKRect Rectangle { set; get; }

// For dragging operations


public Point LastFingerLocation { set; get; }

// For the dragging hit-test


public bool IsInEllipse(SKPoint pt)
{
SKRect rect = Rectangle;

return (Math.Pow(pt.X - rect.MidX, 2) / Math.Pow(rect.Width / 2, 2) +


Math.Pow(pt.Y - rect.MidY, 2) / Math.Pow(rect.Height / 2, 2)) < 1;
}
}

Las propiedades StartPoint y EndPoint se utilizan cuando el programa está procesando la entrada táctil; la
propiedad Rectangle se utiliza para dibujar la elipse. La propiedad LastFingerLocation entra en juego cuando se
arrastra la elipse y el método IsInEllipse ayuda en la prueba de posicionamiento. El método devuelve true si el
punto está dentro de la elipse.
El archivo de código subyacente mantiene tres colecciones:

Dictionary<long, EllipseDrawingFigure> inProgressFigures = new Dictionary<long, EllipseDrawingFigure>();


List<EllipseDrawingFigure> completedFigures = new List<EllipseDrawingFigure>();
Dictionary<long, EllipseDrawingFigure> draggingFigures = new Dictionary<long, EllipseDrawingFigure>();

El diccionario draggingFigure contiene un subconjunto de la colección de completedFigures . El controlador de


eventos PaintSurface de SkiaSharp representa simplemente los objetos en las colecciones completedFigures y
inProgressFigures :
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Fill
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();

foreach (EllipseDrawingFigure figure in completedFigures)


{
paint.Color = figure.Color;
canvas.DrawOval(figure.Rectangle, paint);
}
foreach (EllipseDrawingFigure figure in inProgressFigures.Values)
{
paint.Color = figure.Color;
canvas.DrawOval(figure.Rectangle, paint);
}
}

La parte más complicada del procesamiento táctil es el control Pressed . Aquí es donde se realiza la prueba de
posicionamiento, pero si el código detecta una elipse bajo el dedo del usuario, dicha elipse solo se puede arrastrar
si actualmente no la está arrastrando otro dedo. Si no hay ninguna elipse bajo el dedo del usuario, el código
comienza el proceso de dibujar una elipse nueva:
case TouchActionType.Pressed:
bool isDragOperation = false;

// Loop through the completed figures


foreach (EllipseDrawingFigure fig in completedFigures.Reverse<EllipseDrawingFigure>())
{
// Check if the finger is touching one of the ellipses
if (fig.IsInEllipse(ConvertToPixel(args.Location)))
{
// Tentatively assume this is a dragging operation
isDragOperation = true;

// Loop through all the figures currently being dragged


foreach (EllipseDrawingFigure draggedFigure in draggingFigures.Values)
{
// If there's a match, we'll need to dig deeper
if (fig == draggedFigure)
{
isDragOperation = false;
break;
}
}

if (isDragOperation)
{
fig.LastFingerLocation = args.Location;
draggingFigures.Add(args.Id, fig);
break;
}
}
}

if (isDragOperation)
{
// Move the dragged ellipse to the end of completedFigures so it's drawn on top
EllipseDrawingFigure fig = draggingFigures[args.Id];
completedFigures.Remove(fig);
completedFigures.Add(fig);
}
else // start making a new ellipse
{
// Random bytes for random color
byte[] buffer = new byte[4];
random.NextBytes(buffer);

EllipseDrawingFigure figure = new EllipseDrawingFigure


{
Color = new SKColor(buffer[0], buffer[1], buffer[2], buffer[3]),
StartPoint = ConvertToPixel(args.Location),
EndPoint = ConvertToPixel(args.Location)
};
inProgressFigures.Add(args.Id, figure);
}
canvasView.InvalidateSurface();
break;

Otro ejemplo de SkiaSharp es la página Finger Paint (Dibujo con los dedos). Puede seleccionar un color de trazo
y el ancho del trazo desde dos vistas Picker y después dibujar con uno o más dedos:
Este ejemplo también requiere una clase independiente para representar cada línea dibujada en la pantalla:

class FingerPaintPolyline
{
public FingerPaintPolyline()
{
Path = new SKPath();
}

public SKPath Path { set; get; }

public Color StrokeColor { set; get; }

public float StrokeWidth { set; get; }


}

Un objeto SKPath se usa para representar cada línea. El archivo FingerPaint.xaml.cs mantiene dos colecciones de
estos objetos, una para aquellas polilíneas que se están dibujando actualmente y otra para las polilíneas
completadas:

Dictionary<long, FingerPaintPolyline> inProgressPolylines = new Dictionary<long, FingerPaintPolyline>();


List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

El procesamiento de Pressed crea una nueva FingerPaintPolyline , llama a MoveTo en el objeto de ruta de acceso
para almacenar el punto inicial y agrega ese objeto al diccionario de inProgressPolylines . El procesamiento de
Moved llama a LineTo en el objeto de ruta con la nueva posición del dedo y el procesamiento de Released
transfiere la polilínea completada desde inProgressPolylines a completedPolylines . Una vez más, el código de
dibujo de SkiaSharp real es relativamente sencillo:
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();

foreach (FingerPaintPolyline polyline in completedPolylines)


{
paint.Color = polyline.StrokeColor.ToSKColor();
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}

foreach (FingerPaintPolyline polyline in inProgressPolylines.Values)


{
paint.Color = polyline.StrokeColor.ToSKColor();
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
}

Seguimiento táctil de vista a vista


Todos los ejemplos anteriores han establecido la propiedad Capture del TouchEffect en true , ya sea cuando el
TouchEffect se creó o cuando se produjo el evento Pressed . Esto garantiza que el mismo elemento recibe todos
los eventos asociados con el dedo que presionó la vista primero. El ejemplo final no establece Capture en true .
Esto provoca un comportamiento diferente cuando se mueve un dedo en contacto con la pantalla de un elemento
a otro. El elemento desde el que se mueve el dedo recibe un evento con una propiedad Type establecida en
TouchActionType.Exited y el segundo elemento recibe un evento con una configuración Type de
TouchActionType.Entered .

Este tipo de procesamiento táctil es muy útil para un teclado de música. Una tecla debe ser capaz de detectar
cuándo se pulsa, pero también cuándo un dedo pasa de una tecla a otra.
La página Silent Keyboard teclado silencioso define clases WhiteKey y BlackKey pequeñas que derivan de Key ,
que se deriva de BoxView .
La clase Keyestá lista para usarse en un programa de música real. Define propiedades públicas denominadas
IsPressed y KeyNumber , que están pensadas para establecerse en el código de teclas que establece el estándar
MIDI. La clase Key también define un evento denominado StatusChanged , que se invoca cuando la propiedad
IsPressed cambia.

Se permiten varios dedos en cada tecla. Por este motivo, la clase Key mantiene una List de los números de Id.
táctil de todos los dedos que tocan actualmente esa tecla:

List<long> ids = new List<long>();

El controlador de eventos TouchAction agrega un identificador para la lista ids para un tipo de evento Pressed y
un tipo Entered , pero solo cuando la propiedad IsInContact es true para el evento Entered . Se quita el
identificador de la List para un evento Released o Exited :
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
AddToList(args.Id);
break;

case TouchActionType.Entered:
if (args.IsInContact)
{
AddToList(args.Id);
}
break;

case TouchActionType.Moved:
break;

case TouchActionType.Released:
case TouchActionType.Exited:
RemoveFromList(args.Id);
break;
}
}

Los métodos AddToList y RemoveFromList comprueban si la List ha cambiado entre vacía y no vacía y si es así,
invocan el evento StatusChanged .
Los distintos elementos WhiteKey y BlackKey se organizan en el archivo XAML de la página, que tiene mejor
aspecto cuando se mantiene el teléfono en un modo horizontal:

Si pasa los dedos por las teclas, podrá ver por los pequeños cambios en el color que los eventos táctiles se
transfieren de una tecla a otra.

Resumen
En este artículo se mostró cómo invocar eventos en un efecto, y cómo escribir y usar un efecto que implementa el
procesamiento multitáctil de bajo nivel.

Vínculos relacionados
Multi-Touch Finger Tracking in iOS (Seguimiento de dedos multitáctil en iOS)
Multi-Touch Finger Tracking in Android (Seguimiento de dedos multitáctil en Android)
Touch Tracking Effect (sample) (Efecto de seguimiento táctil [ejemplo])
RoundEffect reutilizable de :::no-loc(Xamarin.Forms):::
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo

IMPORTANT
Ya no es necesario usar RoundEffect para representar un control como un círculo. El último enfoque recomendado es
recortar el control mediante EllipseGeometry . Para obtener más información, vea Recorte con una geometría.

RoundEffect simplifica la representación de cualquier control que se derive de VisualElement como un círculo. Este
efecto se puede usar para crear imágenes circulares, botones u otros controles:

Creación de un objeto RoutingEffect compartido


Para crear un efecto multiplataforma se debe crear una clase de efecto en el proyecto compartido. La aplicación de
ejemplo crea una clase RoundEffect vacía que se deriva de la clase RoutingEffect :

public class RoundEffect : RoutingEffect


{
public RoundEffect() : base($"Xamarin.{nameof(RoundEffect)}")
{
}
}

Esta clase permite al proyecto compartido resolver las referencias al efecto en código o XAML, pero no proporciona
ninguna funcionalidad. El efecto debe tener implementaciones para cada plataforma.

Implementación del efecto de Android


El proyecto de la plataforma Android define una clase RoundEffect que se deriva de PlatformEffect . Esta clase se
etiqueta con atributos assembly que permiten que :::no-loc(Xamarin.Forms)::: resuelva la clase de efecto:
[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(RoundEffectDemo.Droid.RoundEffect), nameof(RoundEffectDemo.Droid.RoundEffect))]
namespace RoundEffectDemo.Droid
{
public class RoundEffect : PlatformEffect
{
// ...
}
}

En la plataforma Android se usa el concepto de OutlineProvider para definir los bordes de un control. En el
proyecto de ejemplo se incluye una clase CornerRadiusProvider que se deriva de la clase ViewOutlineProvider :

class CornerRadiusOutlineProvider : ViewOutlineProvider


{
Element element;

public CornerRadiusOutlineProvider(Element formsElement)


{
element = formsElement;
}

public override void GetOutline(Android.Views.View view, Outline outline)


{
float scale = view.Resources.DisplayMetrics.Density;
double width = (double)element.GetValue(VisualElement.WidthProperty) * scale;
double height = (double)element.GetValue(VisualElement.HeightProperty) * scale;
float minDimension = (float)Math.Min(height, width);
float radius = minDimension / 2f;
Rect rect = new Rect(0, 0, (int)width, (int)height);
outline.SetRoundRect(rect, radius);
}
}

Esta clase usa las propiedades Width y Height de la instancia de Element de :::no-loc(Xamarin.Forms)::: para
calcular un radio que es la mitad de la dimensión más corta.
Una vez que se ha definido un proveedor de esquemas, la clase RoundEffect puede consumirlo para implementar
el efecto:
public class RoundEffect : PlatformEffect
{
ViewOutlineProvider originalProvider;
Android.Views.View effectTarget;

protected override void OnAttached()


{
try
{
effectTarget = Control ?? Container;
originalProvider = effectTarget.OutlineProvider;
effectTarget.OutlineProvider = new CornerRadiusOutlineProvider(Element);
effectTarget.ClipToOutline = true;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to set corner radius: {ex.Message}");
}
}

protected override void OnDetached()


{
if(effectTarget != null)
{
effectTarget.OutlineProvider = originalProvider;
effectTarget.ClipToOutline = false;
}
}
}

Se llama al método OnAttached cuando el efecto se adjunta a un elemento. El objeto OutlineProvider existente se
guarda para que se pueda restaurar cuando se desasocie el efecto. Se usa una nueva instancia de
CornerRadiusOutlineProvider como OutlineProvider y ClipToOutline se establece en true para recortar los
elementos que se desbordan en los bordes del contorno.
Se llama al método OnDetatched cuando el efecto se quita de un elemento y restaura el valor de OutlineProvider
original.

NOTE
En función del tipo de elemento, la propiedad Control puede ser NULL o no. Si la propiedad Control no es NULL, las
esquinas redondeadas se pueden aplicar directamente al control. Pero si es NULL, las esquinas redondeadas se deben aplicar
al objeto Container . El campo effectTarget permite aplicar el efecto al objeto adecuado.

Implementación del efecto de iOS


El proyecto de la plataforma iOS define una clase RoundEffect que se deriva de PlatformEffect . Esta clase se
etiqueta con atributos assembly que permiten que :::no-loc(Xamarin.Forms)::: resuelva la clase de efecto:

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(RoundEffectDemo.iOS.RoundEffect), nameof(RoundEffectDemo.iOS.RoundEffect))]
namespace RoundEffectDemo.iOS
{
public class RoundEffect : PlatformEffect
{
// ...
}

En iOS, los controles tienen una propiedad Layer , que tiene una propiedad CornerRadius . La implementación de
la clase RoundEffect en iOS calcula el radio de redondeo adecuado y actualiza la propiedad CornerRadius de la
capa:

public class RoundEffect : PlatformEffect


{
nfloat originalRadius;
UIKit.UIView effectTarget;

protected override void OnAttached()


{
try
{
effectTarget = Control ?? Container;
originalRadius = effectTarget.Layer.CornerRadius;
effectTarget.ClipsToBounds = true;
effectTarget.Layer.CornerRadius = CalculateRadius();
}
catch (Exception ex)
{
Console.WriteLine($"Failed to set corner radius: {ex.Message}");
}
}

protected override void OnDetached()


{
if (effectTarget != null)
{
effectTarget.ClipsToBounds = false;
if (effectTarget.Layer != null)
{
effectTarget.Layer.CornerRadius = originalRadius;
}
}
}

float CalculateRadius()
{
double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
float minDimension = (float)Math.Min(height, width);
float radius = minDimension / 2f;

return radius;
}
}

El método CalculateRadius calcula un radio en función en la dimensión mínima de Element de :::no-


loc(Xamarin.Forms):::. Se llama al método OnAttached cuando el efecto se adjunta a un control y actualiza la
propiedad CornerRadius de la capa. Establece la propiedad ClipToBounds en true de modo que el
desbordamiento de los elementos se recorta a los bordes del control. Se llama al método OnDetatched cuando el
efecto se quita de un control e invierte estos cambios, lo que restaura el radio de esquina original.

NOTE
En función del tipo de elemento, la propiedad Control puede ser NULL o no. Si la propiedad Control no es NULL, las
esquinas redondeadas se pueden aplicar directamente al control. Pero si es NULL, las esquinas redondeadas se deben aplicar
al objeto Container . El campo effectTarget permite aplicar el efecto al objeto adecuado.

Consumo del efecto


Una vez que el efecto se ha implementado entre plataformas, los controles de :::no-loc(Xamarin.Forms)::: pueden
usarlo. Una aplicación común de RoundEffect consiste en convertir un objeto Image en circular. En el código
XAML siguiente se muestra el efecto aplicado a una instancia de Image :

<Image Source=outdoors"
HeightRequest="100"
WidthRequest="100">
<Image.Effects>
<local:RoundEffect />
</Image.Effects>
</Image>

El efecto también se puede aplicar en el código:

var image = new Image


{
Source = ImageSource.FromFile("outdoors"),
HeightRequest = 100,
WidthRequest = 100
};
image.Effects.Add(new RoundEffect());

La clase RoundEffect se puede aplicar a cualquier control que se derive de VisualElement .

NOTE
Para que el efecto calcule el radio correcto, el control al que se aplica debe tener un tamaño explícito. Por tanto, se deben
definir las propiedades HeightRequest y WidthRequest . Si el control afectado aparece en un elemento StackLayout , su
propiedad HorizontalOptions no debe usar uno de los valores Expand como LayoutOptions.CenterAndExpand , o no
tendrá dimensiones precisas.

Vínculos relacionados
Aplicación de ejemplo de RoundEffect
Introducción a los efectos
Crear un efecto
Gestos de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Se pueden usar reconocedores de gestos para detectar la interacción del usuario con las vistas en una aplicación de
Xamarin.Forms.
La clase GestureRecognizer de Xamarin.Forms admite los gestos de pulsar, reducir, desplazar lateralmente, deslizar
rápidamente y arrastrar y colocar en instancias de View .

Incorporación de un reconocedor de gesto de pulsar


Se usa un gesto de pulsar para la detección de pulsación y se reconoce con la clase TapGestureRecognizer .

Incorporación de un reconocedor de gesto de reducir


Se usa un gesto de reducir para realizar un zoom interactivo y se reconoce con la clase PinchGestureRecognizer .

Incorporación de un reconocedor de gesto de desplazamiento lateral


Se usa un gesto de desplazar lateralmente para detectar el movimiento de los dedos alrededor de la pantalla y
aplicar ese movimiento al contenido; se reconoce con la clase PanGestureRecognizer .

Incorporación de un reconocedor de gesto de deslizar rápidamente


Un gesto de deslizar rápidamente se produce cuando un dedo se mueve a través de la pantalla en dirección
horizontal o vertical y, a menudo, se usa para iniciar la navegación a través del contenido. Los gestos de deslizar
rápidamente se reconocen con la clase SwipeGestureRecognizer .

Incorporación de un reconocedor de gesto de arrastrar y colocar


Un gesto de arrastrar y colocar permite arrastrar elementos y sus paquetes de datos asociados desde una
ubicación en pantalla a otra ubicación mediante un gesto continuo. Los gestos de arrastre y colocación se
reconocen con las clases DragGestureRecognizer y DropGestureRecognizer , respectivamente.
Incorporación de un reconocedor de gesto de pulsar
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
El gesto de pulsar se usa para la detección de pulsaciones y se implementa con la clase TapGestureRecognizer.
Para que se pueda hacer clic en un elemento de interfaz de usuario con el gesto de pulsar, cree una instancia de
TapGestureRecognizer , controle el evento Tapped , y agregue el nuevo reconocedor de gestos a la colección
GestureRecognizers en el elemento de interfaz de usuario. En el ejemplo de código siguiente se muestra un
elemento TapGestureRecognizer adjunto a un elemento Image :

var tapGestureRecognizer = new TapGestureRecognizer();


tapGestureRecognizer.Tapped += (s, e) => {
// handle the tap
};
image.GestureRecognizers.Add(tapGestureRecognizer);

De forma predeterminada, la imagen responderá a pulsaciones simples. Establezca la propiedad


NumberOfTapsRequired para que espere una pulsación doble (o más pulsaciones si es necesario).

tapGestureRecognizer.NumberOfTapsRequired = 2; // double-tap

Cuando se establece NumberOfTapsRequired por encima de uno, el controlador de eventos solo se ejecuta si las
pulsaciones se producen dentro de un período de tiempo concreto (que no se puede configurar). Si la segunda
pulsación (o las posteriores) no se producen dentro de ese período, se omiten y se reinicia el "recuento de
pulsaciones".

Uso de Xaml
Un reconocedor de gestos se puede agregar a un control en Xaml mediante propiedades adjuntas. La sintaxis para
agregar un elemento TapGestureRecognizer a una imagen se muestra a continuación (en este caso se define un
evento de pulsación doble ):

<Image Source="tapped.jpg">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnTapGestureRecognizerTapped"
NumberOfTapsRequired="2" />
</Image.GestureRecognizers>
</Image>

El código para el controlador de eventos (en el ejemplo) incrementa un contador y cambia la imagen de color a
blanco y negro.
void OnTapGestureRecognizerTapped(object sender, EventArgs args)
{
tapCount++;
var imageSender = (Image)sender;
// watch the monkey go from color to black&white!
if (tapCount % 2 == 0) {
imageSender.Source = "tapped.jpg";
} else {
imageSender.Source = "tapped_bw.jpg";
}
}

Uso de ICommand
Las aplicaciones que usan el patrón Model-View-ViewModel (MVVM) suelen usar ICommand en lugar de conectar
controladores de eventos directamente. El elemento TapGestureRecognizer puede admitir fácilmente ICommand
estableciendo el enlace en el código:

var tapGestureRecognizer = new TapGestureRecognizer();


tapGestureRecognizer.SetBinding (TapGestureRecognizer.CommandProperty, "TapCommand");
image.GestureRecognizers.Add(tapGestureRecognizer);

o con Xaml:

<Image Source="tapped.jpg">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}"
CommandParameter="Image1" />
</Image.GestureRecognizers>
</Image>

El código completo para este modelo de vista se puede encontrar en el ejemplo. A continuación se muestran los
detalles de implementación de Command relevantes:

public class TapViewModel : INotifyPropertyChanged


{
int taps = 0;
ICommand tapCommand;
public TapViewModel () {
// configure the TapCommand with a method
tapCommand = new Command (OnTapped);
}
public ICommand TapCommand {
get { return tapCommand; }
}
void OnTapped (object s) {
taps++;
Debug.WriteLine ("parameter: " + s);
}
//region INotifyPropertyChanged code omitted
}

Vínculos relacionados
TapGesture (ejemplo)
GestureRecognizer
TapGestureRecognizer
Incorporación de un reconocedor de gesto de reducir
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
El gesto de reducir se usa para realizar un zoom interactivo y se implementa con la clase PinchGestureRecognizer.
Un escenario común para el gesto de reducir es realizar un zoom interactivo de una imagen en la ubicación donde
se realice el gesto. Esto se logra al escalar el contenido de la ventanilla, como se demuestra en este artículo.
Para que un elemento de interfaz de usuario se pueda mover con el gesto de reducir, cree una instancia de
PinchGestureRecognizer , controle el evento PinchUpdated y agregue el nuevo reconocedor de gestos a la colección
GestureRecognizers en el elemento de interfaz de usuario. En el siguiente ejemplo de código, se muestra un
elemento PinchGestureRecognizer asociado a un elemento Image :

var pinchGesture = new PinchGestureRecognizer();


pinchGesture.PinchUpdated += (s, e) => {
// Handle the pinch
};
image.GestureRecognizers.Add(pinchGesture);

Esto también se puede lograr en XAML, como se muestra en el ejemplo de código siguiente:

<Image Source="waterfront.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer PinchUpdated="OnPinchUpdated" />
</Image.GestureRecognizers>
</Image>

Después, se agrega el código del controlador de eventos OnPinchUpdated al archivo de código subyacente:

void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)


{
// Handle the pinch
}

Crear un contenedor de PinchToZoom


Para controlar el gesto de reducir y realizar esta operación de zoom, se necesita un cálculo matemático para
transformar la interfaz de usuario. Esta sección contiene una clase auxiliar generalizada para realizar la operación
matemática, que puede usarse para realizar un zoom interactivo en cualquier elemento de la interfaz de usuario. En
el ejemplo de código siguiente se muestra la clase PinchToZoomContainer :
public class PinchToZoomContainer : ContentView
{
...

public PinchToZoomContainer ()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add (pinchGesture);
}

void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)


{
...
}
}

Esta clase se puede encapsular en un elemento de interfaz de usuario para que el gesto de reducir amplíe o
reduzca el elemento de interfaz de usuario encapsulado. En el siguiente ejemplo de código de XAML, se muestra
cómo encapsular PinchToZoomContainer en un elemento Image :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PinchGesture;assembly=PinchGesture"
x:Class="PinchGesture.HomePage">
<ContentPage.Content>
<Grid Padding="20">
<local:PinchToZoomContainer>
<local:PinchToZoomContainer.Content>
<Image Source="waterfront.jpg" />
</local:PinchToZoomContainer.Content>
</local:PinchToZoomContainer>
</Grid>
</ContentPage.Content>
</ContentPage>

En el siguiente ejemplo de código, se muestra cómo el elemento PinchToZoomContainer encapsula un elemento


Image en una página de C#:

public class HomePageCS : ContentPage


{
public HomePageCS ()
{
Content = new Grid {
Padding = new Thickness (20),
Children = {
new PinchToZoomContainer {
Content = new Image { Source = ImageSource.FromFile ("waterfront.jpg") }
}
}
};
}
}

Cuando el elemento Image recibe un gesto de reducir, la imagen mostrada se ampliará o reducirá. La acción de
zoom se realiza mediante el método PinchZoomContainer.OnPinchUpdated , que se muestra en el siguiente ejemplo de
código:
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);

// The ScaleOrigin is in relative coordinates to the wrapped user interface element,


// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

// The ScaleOrigin is in relative coordinates to the wrapped user interface element,


// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

// Calculate the transformed element pixel coordinates.


double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);

// Apply translation based on the change in origin.


Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);

// Apply scale factor.


Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}

Este método actualiza el nivel de zoom del elemento de la interfaz de usuario encapsulada basándose en el gesto
de reducir del usuario. Para lograr esto, se usan los valores de las propiedades Scale , ScaleOrigin y Status de la
instancia de PinchGestureUpdatedEventArgs para calcular el factor de escala que se aplicará en el origen del gesto de
reducir. El elemento del usuario encapsulado se amplía o reduce en el origen del gesto de reducir al establecer las
propiedades TranslationX , TranslationY y Scale en los valores calculados.

Vínculos relacionados
PinchGesture (ejemplo)
GestureRecognizer
PinchGestureRecognizer
Incorporación de un reconocedor de gesto de
desplazamiento lateral
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
El gesto de desplazamiento lateral se usa para detectar el movimiento de los dedos alrededor de la pantalla y
aplicar ese movimiento al contenido, y se implementa con la clase PanGestureRecognizer . Un escenario habitual
para el gesto de desplazamiento lateral consiste en desplazar una imagen de forma horizontal y vertical, para
poder ver todo el contenido de imagen cuando se muestre en una ventanilla más pequeña que las dimensiones de
la imagen. Esto se logra moviendo la imagen dentro de la ventanilla, y se muestra en este artículo.
Para que un elemento de interfaz de usuario se pueda mover con el gesto de desplazamiento lateral, cree una
instancia de PanGestureRecognizer , controle el evento PanUpdated y agregue el nuevo reconocedor de gestos a la
colección GestureRecognizers en el elemento de interfaz de usuario. En el siguiente ejemplo de código, se muestra
un elemento PanGestureRecognizer asociado a un elemento Image :

var panGesture = new PanGestureRecognizer();


panGesture.PanUpdated += (s, e) => {
// Handle the pan
};
image.GestureRecognizers.Add(panGesture);

Esto también se puede lograr en XAML, como se muestra en el ejemplo de código siguiente:

<Image Source="MonoMonkey.jpg">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated" />
</Image.GestureRecognizers>
</Image>

Después, se agrega el código del controlador de eventos OnPanUpdated al archivo de código subyacente:

void OnPanUpdated (object sender, PanUpdatedEventArgs e)


{
// Handle the pan
}

Creación de un contenedor de desplazamiento lateral


Esta sección contiene una clase auxiliar generalizada que realiza el desplazamiento lateral de forma libre, que
normalmente es adecuado para navegar dentro de imágenes o mapas. El control del gesto de desplazamiento
lateral para realizar esta operación requiere un cálculo matemático para transformar la interfaz de usuario. Este
cálculo matemático se usa para el desplazamiento lateral solo dentro de los límites del elemento de interfaz de
usuario ajustado. En el ejemplo de código siguiente se muestra la clase PanContainer :
public class PanContainer : ContentView
{
double x, y;

public PanContainer ()
{
// Set PanGestureRecognizer.TouchPoints to control the
// number of touch points needed to pan
var panGesture = new PanGestureRecognizer ();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add (panGesture);
}

void OnPanUpdated (object sender, PanUpdatedEventArgs e)


{
...
}
}

Esta clase se puede encapsular en un elemento de interfaz de usuario para que el gesto desplace el elemento de
interfaz de usuario encapsulado. En el ejemplo de código XAML siguiente se muestra la encapsulación de
PanContainer en un elemento Image :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PanGesture"
x:Class="PanGesture.HomePage">
<ContentPage.Content>
<AbsoluteLayout>
<local:PanContainer>
<Image Source="MonoMonkey.jpg" WidthRequest="1024" HeightRequest="768" />
</local:PanContainer>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>

En el ejemplo de código siguiente se muestra cómo el elemento PanContainer encapsula un elemento Image en
una página de C#:

public class HomePageCS : ContentPage


{
public HomePageCS ()
{
Content = new AbsoluteLayout {
Padding = new Thickness (20),
Children = {
new PanContainer {
Content = new Image {
Source = ImageSource.FromFile ("MonoMonkey.jpg"),
WidthRequest = 1024,
HeightRequest = 768
}
}
}
};
}
}

En los dos ejemplos, las propiedades WidthRequest y HeightRequest se establecen en los valores de alto y ancho
de la imagen que se va a mostrar.
Cuando el elemento Image recibe un gesto de desplazamiento lateral, se desplaza lateralmente la imagen
mostrada. El desplazamiento lateral se realiza mediante el método PanContainer.OnPanUpdated , que se muestra en
el ejemplo de código siguiente:

void OnPanUpdated (object sender, PanUpdatedEventArgs e)


{
switch (e.StatusType) {
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX =
Math.Max (Math.Min (0, x + e.TotalX), -Math.Abs (Content.Width - App.ScreenWidth));
Content.TranslationY =
Math.Max (Math.Min (0, y + e.TotalY), -Math.Abs (Content.Height - App.ScreenHeight));
break;

case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}

Este método actualiza el contenido visible del elemento de interfaz de usuario encapsulado, en función del gesto de
desplazamiento lateral del usuario. Esto se logra mediante el uso de los valores de las propiedades TotalX y
TotalY de la instancia de PanUpdatedEventArgs para calcular la dirección y la distancia del desplazamiento lateral.
Las propiedades App.ScreenWidth y App.ScreenHeight proporcionan el alto y ancho de la ventanilla, y se establecen
en los valores de ancho y alto de la pantalla del dispositivo por los proyectos específicos de la plataforma
correspondiente. Después, se realiza el desplazamiento lateral del elemento de usuario encapsulado, mediante el
establecimiento de sus propiedades TranslationX y TranslationY en los valores calculados.
Al desplazar lateralmente el contenido de un elemento que no ocupa toda la pantalla, el alto y ancho de la
ventanilla se pueden obtener de las propiedades Height y Width del elemento.

NOTE
Mostrar imágenes de alta resolución puede aumentar considerablemente la superficie de memoria de una aplicación. Por
tanto, solo se deberían crear cuando sea necesario y deberían liberarse en cuanto la aplicación ya no las necesite. Para más
información, vea Optimizar los recursos de imagen.

Vínculos relacionados
PanGesture (ejemplo)
GestureRecognizer
PanGestureRecognizer
Incorporación de un reconocedor de gesto de
deslizar rápidamente
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Un gesto de deslizar rápidamente se produce cuando un dedo se mueve a través de la pantalla en dirección
horizontal o vertical y, a menudo, se usa para iniciar la navegación a través del contenido. Los ejemplos de código
en este artículo están sacados del ejemplo Swipe Gesture (Gesto de deslizar rápidamente).
Para hacer que View reconozca un gesto de deslizar rápidamente, cree una instancia de SwipeGestureRecognizer ,
establezca la propiedad Direction en un valor de enumeración de SwipeDirection ( Left , Right , Up o Down ),
opcionalmente establezca la propiedad Threshold , controle el evento Swiped y agregue el reconocedor de gestos
nuevo a la colección GestureRecognizers de la vista. En el ejemplo de código siguiente se muestra un elemento
SwipeGestureRecognizer adjunto a BoxView :

<BoxView Color="Teal" ...>


<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="OnSwiped"/>
</BoxView.GestureRecognizers>
</BoxView>

El código equivalente en C# es el siguiente:

var boxView = new BoxView { Color = Color.Teal, ... };


var leftSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Left };
leftSwipeGesture.Swiped += OnSwiped;

boxView.GestureRecognizers.Add(leftSwipeGesture);

La clase SwipeGestureRecognizer también incluye una propiedad Threshold , que opcionalmente se puede
establecer en un valor uint que representa la distancia mínima que debe deslizarse un dedo para que se
reconozca el deslizamiento, en unidades independientes del dispositivo. El valor predeterminado de esta propiedad
es 100, lo cual significa que cualquier deslizamiento por debajo de 100 unidades independientes del dispositivo se
ignorará.

Reconocimiento de la dirección del deslizamiento


En los ejemplos anteriores, la propiedad Direction está establecida en un valor único de la enumeración de
SwipeDirection . Sin embargo, también es posible establecer esta propiedad en varios valores desde la
enumeración de SwipeDirection , por lo que el evento Swiped se desencadena en respuesta a un deslizamiento en
más de una dirección. Sin embargo, la restricción es que un único elemento SwipeGestureRecognizer solo puede
reconocer los deslizamientos que se produzcan en el mismo eje. Por lo tanto, se pueden reconocer los
deslizamientos que se producen en el eje horizontal estableciendo la propiedad Direction en Left y Right :

<SwipeGestureRecognizer Direction="Left,Right" Swiped="OnSwiped"/>

De forma similar, se pueden reconocer los deslizamientos que se producen en el eje horizontal estableciendo la
propiedad Direction en Up y Down :
var swipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Up | SwipeDirection.Down };

Como alternativa, puede crearse un SwipeGestureRecognizer para cada dirección de deslizamiento para que
reconozca los deslizamientos en cada dirección:

<BoxView Color="Teal" ...>


<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="OnSwiped"/>
<SwipeGestureRecognizer Direction="Right" Swiped="OnSwiped"/>
<SwipeGestureRecognizer Direction="Up" Swiped="OnSwiped"/>
<SwipeGestureRecognizer Direction="Down" Swiped="OnSwiped"/>
</BoxView.GestureRecognizers>
</BoxView>

El código equivalente en C# es el siguiente:

var boxView = new BoxView { Color = Color.Teal, ... };


var leftSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Left };
leftSwipeGesture.Swiped += OnSwiped;
var rightSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Right };
rightSwipeGesture.Swiped += OnSwiped;
var upSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Up };
upSwipeGesture.Swiped += OnSwiped;
var downSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Down };
downSwipeGesture.Swiped += OnSwiped;

boxView.GestureRecognizers.Add(leftSwipeGesture);
boxView.GestureRecognizers.Add(rightSwipeGesture);
boxView.GestureRecognizers.Add(upSwipeGesture);
boxView.GestureRecognizers.Add(downSwipeGesture);

NOTE
En los ejemplos anteriores, el mismo controlador de eventos responde a la activación del evento Swiped . Sin embargo, cada
instancia de SwipeGestureRecognizer puede usar un controlador de eventos distinto, si es necesario.

Respuesta al deslizamiento
En el ejemplo siguiente, se muestra un controlador de eventos para el evento Swiped :
void OnSwiped(object sender, SwipedEventArgs e)
{
switch (e.Direction)
{
case SwipeDirection.Left:
// Handle the swipe
break;
case SwipeDirection.Right:
// Handle the swipe
break;
case SwipeDirection.Up:
// Handle the swipe
break;
case SwipeDirection.Down:
// Handle the swipe
break;
}
}

SwipedEventArgs puede examinarse para determinar la dirección del deslizamiento, con lógica personalizada como
respuesta al deslizamiento, según sea necesario. Se puede obtener la dirección del deslizamiento desde la
propiedad Direction de los argumentos de evento, que se establecerán en uno de los valores de la enumeración
de SwipeDirection . Además, los argumentos de evento también tienen una propiedad Parameter que se
establecerá en el valor de la propiedad CommandParameter , si se ha definido.

Uso de comandos
La clase SwipeGestureRecognizer también incluye las propiedades Command y CommandParameter . Estas propiedades
se utilizan normalmente en aplicaciones que usan el patrón Model-View-ViewModel (MVVM). La propiedad
Command define el elemento ICommand que se invocará cuando se reconoce un gesto de deslizar rápidamente, con
la propiedad CommandParameter que define un objeto que se pasará al elemento ICommand. . El ejemplo de código
siguiente muestra cómo enlazar la propiedad Command a un elemento ICommand definido en el modelo de vista
cuya instancia se ha establecido como la página BindingContext :

var boxView = new BoxView { Color = Color.Teal, ... };


var leftSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Left, CommandParameter = "Left"
};
leftSwipeGesture.SetBinding(SwipeGestureRecognizer.CommandProperty, "SwipeCommand");
boxView.GestureRecognizers.Add(leftSwipeGesture);

El código XAML equivalente es:

<BoxView Color="Teal" ...>


<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Command="{Binding SwipeCommand}" CommandParameter="Left" />
</BoxView.GestureRecognizers>
</BoxView>

SwipeCommand es una propiedad de tipo ICommand definida en la instancia de modelo de vista que se establece
como la página BindingContext . Cuando se reconoce un gesto de deslizar rápidamente, se ejecuta el método
Execute del objeto SwipeCommand . El argumento para el método Execute es el valor de la propiedad
CommandParameter . Para obtener más información sobre los comandos, consulte The Command Interface (La
interfaz de comandos).

Creación de un contenedor de deslizamiento


La clase SwipeContainer , que se muestra en el siguiente ejemplo de código, es una clase de reconocimiento de
deslizamiento que se ha ajustado alrededor de un elemento View para realizar el reconocimiento de gestos de
deslizar rápidamente:

public class SwipeContainer : ContentView


{
public event EventHandler<SwipedEventArgs> Swipe;

public SwipeContainer()
{
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Left));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Right));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Up));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Down));
}

SwipeGestureRecognizer GetSwipeGestureRecognizer(SwipeDirection direction)


{
var swipe = new SwipeGestureRecognizer { Direction = direction };
swipe.Swiped += (sender, e) => Swipe?.Invoke(this, e);
return swipe;
}
}

La clase crea objetos SwipeGestureRecognizer para las cuatro direcciones de deslizamiento y


SwipeContainer
adjunta Swipe controladores de eventos. Estos controladores de eventos invocan el evento Swipe definido por
SwipeContainer .

En el ejemplo de código XAML siguiente se muestra la clase SwipeContainer que realiza el ajuste de BoxView :

<ContentPage ...>
<StackLayout>
<local:SwipeContainer Swipe="OnSwiped" ...>
<BoxView Color="Teal" ... />
</local:SwipeContainer>
</StackLayout>
</ContentPage>

En el ejemplo de código siguiente se muestra cómo SwipeContainer realiza el ajuste de BoxView en una página de
C#:

public class SwipeContainerPageCS : ContentPage


{
public SwipeContainerPageCS()
{
var boxView = new BoxView { Color = Color.Teal, ... };
var swipeContainer = new SwipeContainer { Content = boxView, ... };
swipeContainer.Swipe += (sender, e) =>
{
// Handle the swipe
};

Content = new StackLayout


{
Children = { swipeContainer }
};
}
}

Cuando BoxView recibe un gesto de deslizar rápidamente, el evento Swiped en SwipeGestureRecognizer se activa.
Esto se controla mediante la clase SwipeContainer , que activa su propio evento Swipe . Este evento Swipe se
controla en la página. Posteriormente, SwipedEventArgs puede examinarse para determinar la dirección del
deslizamiento, con lógica personalizada como respuesta al deslizamiento, según sea necesario.

Vínculos relacionados
Gesto de deslizar rápidamente (ejemplo)
GestureRecognizer
SwipeGestureRecognizer
Incorporación de reconocedores de gesto de
arrastrar y colocar
18/12/2020 • 22 minutes to read • Edit Online

Descargar el ejemplo
Un gesto de arrastrar y colocar permite arrastrar elementos y sus paquetes de datos asociados desde una
ubicación en pantalla a otra ubicación mediante un gesto continuo. Arrastrar y colocar puede tener lugar en una
sola aplicación o puede iniciarse en una aplicación y finalizar en otra.

IMPORTANT
El reconocedor de gesto de arrastrar y colocar :::no-loc(Xamarin.Forms)::: es actualmente experimental y solo se puede usar
estableciendo la marca DragAndDrop_Experimental . Para más información, vea Marcas experimentales.
El reconocimiento de los gestos de arrastrar y colocar se admite en iOS, Android y la Plataforma universal de Windows
(UWP). Sin embargo, en iOS se requiere una plataforma mínima de iOS 11.

El origen de arrastre , que es el elemento en el que se inicia el gesto de arrastrar, puede proporcionar los datos que
se van a transferir rellenando un objeto de paquete de datos. Cuando el origen de arrastre se libera, se produce la
colocación. Es entonces cuando el destino de colocación , que es el elemento bajo el origen de arrastre, procesa el
paquete de datos.
El proceso para habilitar la función de arrastrar y colocar en una aplicación es el siguiente:
1. Habilite la función de arrastrar en un elemento agregando un objeto DragGestureRecognizer a su colección
GestureRecognizers y estableciendo la propiedad DragGestureRecognizer.CanDrag en true . Para más
información, consulte Habilitación del arrastre.
2. [opcional] Cree un paquete de datos. :::no-loc(Xamarin.Forms)::: rellena automáticamente el paquete de datos
para los controles de imagen y texto pero, para el resto de contenido, deberá crear su propio paquete de datos.
Para obtener más información, consulte Creación de un paquete de datos.
3. Habilite la función de colocar en un elemento agregando un objeto DropGestureRecognizer a su colección
GestureRecognizers y estableciendo la propiedad DropGestureRecognizer.AllowDrop en true . Para más
información, consulte Habilitación de la colocación.
4. [opcional] Controle el evento DropGestureRecognizer.DragOver para indicar el tipo de operación que el destino
de colocación permite. Para más información, consulte Controlar el evento DragOver.
5. [opcional] Procese el paquete de datos para recibir el contenido colocado. :::no-loc(Xamarin.Forms)::: recuperará
automáticamente los datos de texto e imagen del paquete de datos pero, para el resto de contenido, deberá
procesar el paquete de datos. Para más información, consulte Proceso del paquete de datos.

NOTE
Actualmente no se admite el arrastre de elementos hacia y desde CollectionView .
Habilitación del arrastre
En :::no-loc(Xamarin.Forms):::, la clase DragGestureRecognizer proporciona el reconocimiento del gesto de arrastrar.
Esta clase define las propiedades siguientes:
CanDrag , de tipo bool , que indica si el elemento al que está asociado el reconocedor de gestos puede ser un
origen de arrastre. El valor predeterminado de esta propiedad es false .
DragStartingCommand , de tipo ICommand , que se ejecuta cuando un gesto de arrastrar se reconoce por primera
vez.
DragStartingCommandParameter , de tipo object , que es el parámetro que se pasa a DragStartingCommand .
DropCompletedCommand , de tipo ICommand , que se ejecuta cuando el origen de arrastre se coloca.
DropCompletedCommandParameter , de tipo object , que es el parámetro que se pasa a DropCompletedCommand .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.
La clase DragGestureRecognizertambién define los eventos DragStarting y DropCompleted . Cuando un objeto
DragGestureRecognizer detecta un gesto de arrastrar, ejecuta DragStartingCommand e invoca el evento DragStarting
. Después, cuando el objeto DragGestureRecognizer detecta la finalización de un gesto de colocar, ejecuta
DropCompletedCommand e invoca el evento DropCompleted .

El objeto DragStartingEventArgs que acompaña al evento DragStarting define las siguientes propiedades:
Handled , de tipo bool , indica si el controlador de eventos ha controlado el evento o si :::no-
loc(Xamarin.Forms)::: debe continuar su propio procesamiento.
Cancel , de tipo bool , indica si se debe cancelar el evento.
Data , de tipo DataPackage , indica el paquete de datos que acompaña al origen de arrastre. Se trata de una
propiedad de solo lectura.
El objeto DropCompletedEventArgs que acompaña al evento DropCompleted tiene una propiedad DropResult de solo
lectura, de tipo DataPackageOperation . Para más información sobre la enumeración DataPackageOperation , consulte
Controlar el evento DragOver.
En el ejemplo de XAML siguiente se muestra DragGestureRecognizer , que se adjunta a Image :

<Image Source="monkeyface.png">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>

En este ejemplo, se puede iniciar un gesto de arrastrar en Image .

TIP
En iOS, Android y UWP, un gesto de arrastrar se inicia con una pulsación larga seguida de un arrastre.

Para obtener un ejemplo de uso de comandos DragGestureRecognizer , vea el ejemplo.

Creación de un paquete de datos


:::no-loc(Xamarin.Forms)::: creará automáticamente un paquete de datos, cuando se inicie una arrastre, para los
siguientes controles:
Controles de texto. Los valores de texto se pueden arrastrar desde los objetos CheckBox , DatePicker , Editor ,
Entry , Label , RadioButton , Switch y TimePicker .
Controles de imagen. Las imágenes se pueden arrastrar desde los controles Button , Image y ImageButton .

En la tabla siguiente se muestran las propiedades que se leen, y cualquier conversión que se intenta, cuando se
inicia una operación de arrastrar en un control de texto:

C O N T RO L P RO P IEDA D C O N VERSIÓ N

CheckBox IsChecked bool convertido en string .

DatePicker Date DateTime convertido en string .

Editor Text

Entry Text

Label Text

RadioButton IsChecked bool convertido en string .

Switch IsToggled bool convertido en string .

TimePicker Time TimeSpan convertido en string .

En el caso de contenido que no sea texto ni imágenes, deberá crear un paquete de datos usted mismo.
La clase DataPackage representa los paquetes de datos, que define las siguientes propiedades:
Properties , de tipo DataPackagePropertySet , que es una colección de propiedades que comprenden los datos
contenidos en DataPackage . Esta propiedad es de solo lectura.
Image , de tipo ImageSource , que es la imagen contenida en DataPackage .
Text , de tipo string , que es el texto contenido en DataPackage .
View , de tipo DataPackageView , que es una versión de solo lectura de DataPackage .

La clase DataPackagePropertySet representa una bolsa de propiedades almacenada en Dictionary<string,object> .


Para información sobre la clase DataPackageView , vea Proceso del paquete de datos.
Almacenamiento de datos de imagen o texto
Los datos de imagen o texto se pueden asociar a un origen de arrastre mediante el almacenamiento de los datos
en la propiedad DataPackage.Image o DataPackage.Text . Esto puede realizarse en el controlador del evento
DragStarting .

En el ejemplo de XAML siguiente se muestra DragGestureRecognizer , que registra un controlador para el evento
DragStarting :
<Path Stroke="Black"
StrokeThickness="4">
<Path.GestureRecognizers>
<DragGestureRecognizer CanDrag="True"
DragStarting="OnDragStarting" />
</Path.GestureRecognizers>
<Path.Data>
<!-- PathGeometry goes here -->
</Path.Data>
</Path>

En este ejemplo, DragGestureRecognizer se adjunta a un objeto Path . El evento DragStarting se desencadena


cuando se detecta un gesto de arrastrar en Path , que ejecuta el controlador de eventos OnDragStarting :

void OnDragStarting(object sender, DragStartingEventArgs e)


{
e.Data.Text = "My text data goes here";
}

El objeto DragStartingEventArgs que acompaña al evento DragStarting tiene una propiedad Data , de tipo
DataPackage . En este ejemplo, la propiedad Text del objeto DataPackage se establece en string . Después, se
puede acceder a DataPackage en la colocación para recuperar string .
Almacenamiento de datos en la bolsa de propiedades
Todos los datos, incluidas las imágenes y el texto, se pueden asociar a un origen de arrastre almacenando los datos
en la colección DataPackage.Properties . Esto puede realizarse en el controlador del evento DragStarting .
En el ejemplo de XAML siguiente se muestra DragGestureRecognizer , que registra un controlador para el evento
DragStarting :

<Rectangle Stroke="Red"
Fill="DarkBlue"
StrokeThickness="4"
HeightRequest="200"
WidthRequest="200">
<Rectangle.GestureRecognizers>
<DragGestureRecognizer CanDrag="True"
DragStarting="OnDragStarting" />
</Rectangle.GestureRecognizers>
</Rectangle>

En este ejemplo, DragGestureRecognizer se adjunta a un objeto Rectangle . El evento DragStarting se desencadena


cuando se detecta un gesto de arrastrar en Rectangle , que ejecuta el controlador de eventos OnDragStarting :

void OnDragStarting(object sender, DragStartingEventArgs e)


{
Shape shape = (sender as Element).Parent as Shape;
e.Data.Properties.Add("Square", new Square(shape.Width, shape.Height));
}

El objeto DragStartingEventArgsque acompaña al evento DragStarting tiene una propiedad Data , de tipo
DataPackage . La colección Properties del objeto DataPackage , que es una colección Dictionary<string, object> ,
puede modificarse para almacenar los datos necesarios. En este ejemplo, el diccionario Properties se modifica
para almacenar un objeto Square , que representa el tamaño de Rectangle , con respecto a una clave "Square".

Habilitación de la colocación
En :::no-loc(Xamarin.Forms):::, la clase DropGestureRecognizer proporciona el reconocimiento del gesto de colocar.
Esta clase define las propiedades siguientes:
AllowDrop , de tipo bool , que indica si el elemento al que está asociado el reconocedor de gestos puede ser un
destino de colocación. El valor predeterminado de esta propiedad es false .
DragOverCommand , de tipo ICommand , que se ejecuta cuando el origen de arrastre se arrastra sobre el destino de
colocación.
DragOverCommandParameter , de tipo object , que es el parámetro que se pasa a DragOverCommand .
DropCommand , de tipo ICommand , que se ejecuta cuando el origen de arrastre se coloca sobre el destino de
colocación.
DropCommandParameter , de tipo object , que es el parámetro que se pasa a DropCommand .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.
La clase DropGestureRecognizer también define los eventos DragOver y Drop . Cuando DropGestureRecognizer
reconoce un origen de arrastre sobre el destino de colocación, ejecuta DragOverCommand e invoca el evento
DragOver . Después, cuando DropGestureRecognizer reconoce un gesto de colocación sobre el destino de
colocación, ejecuta DropCommand e invoca el evento Drop .
La clase DragEventArgs que acompaña al evento DragOver define las siguientes propiedades:
Data , de tipo DataPackage , que contiene los datos asociados al origen de arrastre. Esta propiedad es de solo
lectura.
AcceptedOperation , de tipo DataPackageOperation , que especifica las operaciones que el destino de colocación
permite.
Para más información sobre la enumeración DataPackageOperation , consulte Controlar el evento DragOver.
La clase DropEventArgs que acompaña al evento Drop define las siguientes propiedades:
Data , de tipo DataPackageView , que es una versión de solo lectura del paquete de datos.
Handled , de tipo bool , indica si el controlador de eventos ha controlado el evento o si :::no-
loc(Xamarin.Forms)::: debe continuar su propio procesamiento.
En el ejemplo de XAML siguiente se muestra DropGestureRecognizer , que se adjunta a Image :

<Image BackgroundColor="Silver"
HeightRequest="300"
WidthRequest="250">
<Image.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True" />
</Image.GestureRecognizers>
</Image>

En este ejemplo, cuando un origen de arrastre se coloca en el destino de colocación Image , el origen de arrastre se
copia en el destino de colocación, siempre que dicho origen de arrastre sea una clase ImageSource . Esto se produce
porque :::no-loc(Xamarin.Forms)::: copia automáticamente las imágenes arrastradas, y el texto, en destinos de
colocación compatibles.
Para obtener un ejemplo de uso de comandos DropGestureRecognizer , vea el ejemplo.

Controlar el evento DragOver


El evento DropGestureRecognizer.DragOver se puede controlar opcionalmente para indicar qué tipo de operaciones
permite el destino de colocación. Esto se puede lograr estableciendo la propiedad AcceptedOperation , de tipo
DataPackageOperation , del objeto DragEventArgs que acompaña al evento DragOver .
La enumeración DataPackageOperation define los miembros siguientes:
None , indica que no se realizará ninguna acción.
Copy , indica que el contenido de origen de arrastre se copiará en el destino de colocación.

IMPORTANT
Cuando se crea un objeto de DragEventArgs , el valor predeterminado de la propiedad AcceptedOperation es
DataPackageOperation.Copy .

En el ejemplo de XAML siguiente se muestra DropGestureRecognizer , que registra un controlador para el evento
DragOver :

<Image BackgroundColor="Silver"
HeightRequest="300"
WidthRequest="250">
<Image.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True"
DragOver="OnDragOver" />
</Image.GestureRecognizers>
</Image>

En este ejemplo, DropGestureRecognizer se adjunta a un objeto Image . El evento DragOver se desencadena cuando
se arrastra un origen de arrastre sobre el destino de colocación, pero sin colocarse, lo que ejecuta el controlador de
eventos OnDragOver :

void OnDragOver(object sender, DragEventArgs e)


{
e.AcceptedOperation = DataPackageOperation.None;
}

En este ejemplo, la propiedad AcceptedOperation del objeto DragEventArgs se establece en


DataPackageOperation.None . Esto garantiza que no se realiza ninguna acción cuando se coloca un origen de arrastre
sobre el destino de colocación.

Proceso del paquete de datos


El evento Drop se desencadena cuando se libera un origen de arrastre sobre un destino de colocación. Cuando
esto ocurre, :::no-loc(Xamarin.Forms)::: intentará recuperar automáticamente datos del paquete de datos cuando se
coloca un origen de arrastre en los controles siguientes:
Controles de texto. Los valores de texto se pueden colocar en los objetos CheckBox , DatePicker , Editor ,
Entry , Label , RadioButton , Switch y TimePicker .
Controles de imagen. Las imágenes se pueden colocar en los controles Button , Image y ImageButton .
En la tabla siguiente se muestran las propiedades que se establecen, y cualquier conversión que se intenta, cuando
un origen de arrastre basado en texto se coloca en un control de texto:

C O N T RO L P RO P IEDA D C O N VERSIÓ N

CheckBox IsChecked string se convierte en bool .


C O N T RO L P RO P IEDA D C O N VERSIÓ N

DatePicker Date string se convierte en DateTime .

Editor Text

Entry Text

Label Text

RadioButton IsChecked string se convierte en bool .

Switch IsToggled string se convierte en bool .

TimePicker Time string se convierte en TimeSpan .

En el caso de contenido que no sea texto ni imágenes, deberá procesar el paquete de datos usted mismo.
La clase DropEventArgs que acompaña al evento Drop define una propiedad Data , de tipo DataPackageView . Esta
propiedad representa una versión de solo lectura del paquete de datos.
Recuperación de datos de imagen o texto
Los datos de imagen o texto se pueden recuperar de un paquete de datos en el controlador para el evento Drop ,
mediante métodos definidos en la clase DataPackageView .
La clase DataPackageView incluye los métodos GetImageAsync y GetTextAsync . El método GetImageAsync recupera
una imagen del paquete de datos, que estaba almacenada en la propiedad DataPackage.Image , y devuelve
Task<ImageSource> . De forma similar, el método GetTextAsync recupera texto del paquete de datos, que estaba
almacenada en la propiedad DataPackage.Text , y devuelve Task<string> .
En el ejemplo siguiente se muestra un controlador de eventos Drop que recupera texto del paquete de datos para
Path :

async void OnDrop(object sender, DropEventArgs e)


{
string text = await e.Data.GetTextAsync();

// Perform logic to take action based on the text value.


}

En este ejemplo, los datos de texto se recuperan del paquete de datos mediante el método GetTextAsync . Después,
se puede tomar una acción basada en el valor de texto.
Recuperación de datos de la bolsa de propiedades
Todos los datos se pueden recuperar de un paquete de datos en el controlador del evento Drop mediante el acceso
a la colección Properties del paquete de datos.
La clase define una propiedad Properties , de tipo DataPackagePropertySetView . La clase
DataPackageView
DataPackagePropertySetView representa una bolsa de propiedades de solo lectura almacenada en
Dictionary<string, object> .

En el ejemplo siguiente se muestra un controlador de eventos Drop que recupera datos de la bolsa de
propiedades de un paquete de datos para Rectangle :
void OnDrop(object sender, DropEventArgs e)
{
Square square = (Square)e.Data.Properties["Square"];

// Perform logic to take action based on retrieved value.


}

En este ejemplo, el objeto de Square se recupera de la bolsa de propiedades del paquete de datos mediante la
especificación de la clave de diccionario "Square". Después, se puede tomar una acción basada en el valor
recuperado.

Vínculos relacionados
Gesto de arrastrar y colocar (ejemplo)
Notificaciones locales de Xamarin.Forms
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
Las notificaciones locales son alertas que envían las aplicaciones instaladas en un dispositivo móvil. Se suelen usar
a menudo en características como las siguientes:
Eventos de calendario
Recordatorios
Desencadenadores basados en la ubicación
Cada plataforma controla la creación, la visualización y el consumo de notificaciones locales de manera diferente.
En este artículo se explica cómo crear una abstracción multiplataforma para enviar, programar y recibir
notificaciones locales con Xamarin.Forms.

Creación de una interfaz multiplataforma


La aplicación de Xamarin.Forms debe crear y consumir notificaciones sin preocuparse por las implementaciones
subyacentes de la plataforma. La siguiente interfaz de INotificationManager se implementa en la biblioteca de
código compartido y define una API multiplataforma que la aplicación puede usar para interactuar con las
notificaciones:

public interface INotificationManager


{
event EventHandler NotificationReceived;
void Initialize();
void SendNotification(string title, string message, DateTime? notifyTime = null);
void ReceiveNotification(string title, string message);
}

Esta interfaz se implementará en cada proyecto de plataforma. El evento NotificationReceived permite que la
aplicación controle las notificaciones entrantes. El método Initialize debe ejecutar alguna lógica de plataforma
nativa necesaria para preparar el sistema de notificación. El método SendNotification debe enviar una notificación,
en un elemento DateTime opcional. La plataforma subyacente debe llamar al método ReceiveNotification cuando
se reciba un mensaje.
Uso de la interfaz en Xamarin.Forms
Una vez creada una interfaz, se puede usar en el proyecto compartido de Xamarin.Forms aunque aún no se hayan
creado las implementaciones de la plataforma. La aplicación de ejemplo contiene un elemento ContentPage
denominado MainPage.xaml con el siguiente contenido:

<StackLayout Margin="0,35,0,0"
x:Name="stackLayout">
<Label Text="Click the button below to create a local notification."
TextColor="Red"
HorizontalOptions="Center"
VerticalOptions="Start" />
<Button Text="Create Notification"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="OnSendClick" />
<Label Text="Click the button below to schedule a local notification for in 10 seconds time."
TextColor="Red"
HorizontalOptions="Center"
VerticalOptions="Start" />
<Button Text="Create Notification"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="OnScheduleClick" />
</StackLayout>

El diseño contiene elementos Label que explican las instrucciones y elementos Button que envían o programan
una notificación cuando se pulsan.
El código subyacente de la clase MainPage controla el envío y la recepción de notificaciones:
public partial class MainPage : ContentPage
{
INotificationManager notificationManager;
int notificationNumber = 0;

public MainPage()
{
InitializeComponent();

notificationManager = DependencyService.Get<INotificationManager>();
notificationManager.NotificationReceived += (sender, eventArgs) =>
{
var evtData = (NotificationEventArgs)eventArgs;
ShowNotification(evtData.Title, evtData.Message);
};
}

void OnSendClick(object sender, EventArgs e)


{
notificationNumber++;
string title = $"Local Notification #{notificationNumber}";
string message = $"You have now received {notificationNumber} notifications!";
notificationManager.SendNotification(title, message);
}

void OnScheduleClick(object sender, EventArgs e)


{
notificationNumber++;
string title = $"Local Notification #{notificationNumber}";
string message = $"You have now received {notificationNumber} notifications!";
notificationManager.SendNotification(title, message, DateTime.Now.AddSeconds(10));
}

void ShowNotification(string title, string message)


{
Device.BeginInvokeOnMainThread(() =>
{
var msg = new Label()
{
Text = $"Notification Received:\nTitle: {title}\nMessage: {message}"
};
stackLayout.Children.Add(msg);
});
}
}

El constructor de clase MainPage usa el elemento DependencyService de Xamarin.Forms para recuperar una
instancia específica de la plataforma de INotificationManager . Los métodos OnSendClick y OnScheduleClicked
utilizan la instancia INotificationManager para enviar y programar notificaciones nuevas. Se llama al método
ShowNotification desde el controlador de eventos asociado al evento NotificationReceived y se inserta un nuevo
elemento Label en la página cuando se invoca el evento.
El controlador de eventos NotificationReceived convierte sus argumentos de evento en NotificationEventArgs .
Este tipo se define en el proyecto compartido de Xamarin.Forms:

public class NotificationEventArgs : EventArgs


{
public string Title { get; set; }
public string Message { get; set; }
}

Para obtener más información sobre DependencyService de Xamarin.Forms, vea DependencyService de


Xamarin.Forms.

Creación de la implementación de la interfaz de Android


Para que la aplicación de Xamarin.Forms envíe y reciba notificaciones en Android, esta debe proporcionar una
implementación de la interfaz INotificationManager .
Creación de la clase AndroidNotificationManager
La clase AndroidNotificationManager implementa la interfaz INotificationManager :

using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Support.V4.App;
using Xamarin.Forms;
using AndroidApp = Android.App.Application;

[assembly: Dependency(typeof(LocalNotifications.Droid.AndroidNotificationManager))]
namespace LocalNotifications.Droid
{
public class AndroidNotificationManager : INotificationManager
{
const string channelId = "default";
const string channelName = "Default";
const string channelDescription = "The default channel for notifications.";

public const string TitleKey = "title";


public const string MessageKey = "message";

bool channelInitialized = false;


int messageId = 0;
int pendingIntentId = 0;

NotificationManager manager;

public event EventHandler NotificationReceived;

public static AndroidNotificationManager Instance { get; private set; }

public void Initialize()


{
CreateNotificationChannel();
Instance = this;
}

public void SendNotification(string title, string message, DateTime? notifyTime = null)


{
if (!channelInitialized)
{
CreateNotificationChannel();
}

if (notifyTime != null)
{
Intent intent = new Intent(AndroidApp.Context, typeof(AlarmHandler));
intent.PutExtra(TitleKey, title);
intent.PutExtra(MessageKey, message);

PendingIntent pendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, pendingIntentId++,


intent, PendingIntentFlags.CancelCurrent);
long triggerTime = GetNotifyTime(notifyTime.Value);
AlarmManager alarmManager = AndroidApp.Context.GetSystemService(Context.AlarmService) as
AlarmManager;
alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
}
else
{
Show(title, message);
}
}

public void ReceiveNotification(string title, string message)


{
var args = new NotificationEventArgs()
{
Title = title,
Message = message,
};
NotificationReceived?.Invoke(null, args);
}

public void Show(string title, string message)


{
Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
intent.PutExtra(TitleKey, title);
intent.PutExtra(MessageKey, message);

PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, pendingIntentId++,


intent, PendingIntentFlags.UpdateCurrent);

NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, channelId)


.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources,
Resource.Drawable.xamagonBlue))
.SetSmallIcon(Resource.Drawable.xamagonBlue)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);

Notification notification = builder.Build();


manager.Notify(messageId++, notification);
}

void CreateNotificationChannel()
{
manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);

if (Build.VERSION.SdkInt >= BuildVersionCodes.O)


{
var channelNameJava = new Java.Lang.String(channelName);
var channel = new NotificationChannel(channelId, channelNameJava,
NotificationImportance.Default)
{
Description = channelDescription
};
manager.CreateNotificationChannel(channel);
}

channelInitialized = true;
}

long GetNotifyTime(DateTime notifyTime)


{
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(notifyTime);
double epochDiff = (new DateTime(1970, 1, 1) - DateTime.MinValue).TotalSeconds;
long utcAlarmTime = utcTime.AddSeconds(-epochDiff).Ticks / 10000;
return utcAlarmTime; // milliseconds
}
}
}
El atributo situado encima del espacio de nombres registra la implementación de la interfaz de
assembly
INotificationManager con DependencyService .

Android permite que las aplicaciones definan varios canales para las notificaciones. El método Initialize crea un
canal básico que usa la aplicación de ejemplo para enviar notificaciones. El método SendNotification define la
lógica específica de la plataforma necesaria para crear y enviar una notificación. El sistema operativo Android llama
al método ReceiveNotification cuando se recibe un mensaje, y se invoca el controlador de eventos.
El método SendNotification crea inmediatamente una notificación local, o en un elemento DateTime exacto. Se
puede programar una notificación para un elemento DateTime exacto mediante la clase AlarmManager , y la
notificación la recibirá un objeto derivado de la clase BroadcastReceiver :

[BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]


public class AlarmHandler : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent?.Extras != null)
{
string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);

AndroidNotificationManager.Instance.Show(title, message);
}
}
}

IMPORTANT
De forma predeterminada, las notificaciones programadas con la clase AlarmManager no sobrevivirán al reinicio del
dispositivo. Sin embargo, puede diseñar la aplicación para que vuelva a programar notificaciones automáticamente si el
dispositivo se reinicia. Para más información, vea Activación de una alarma cuando el dispositivo se reinicia en Programación
de alarmas repetidas en developer.android.com. Para obtener información sobre el procesamiento en segundo plano en
Android, vea la guía sobre el procesamiento en segundo plano en developer.android.com.

Para más información sobre los receptores de difusión, vea Receptores de difusión en Xamarin.Android.
Administración de las notificaciones entrantes en Android
La clase MainActivity debe detectar las notificaciones entrantes y notificar la instancia de
AndroidNotificationManager . El atributo Activity de la clase MainActivity debe especificar un valor LaunchMode
de LaunchMode.SingleTop :

[Activity(
//...
LaunchMode = LaunchMode.SingleTop]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
// ...
}

El modo SingleTop impide que se inicien varias instancias de un elemento Activity mientras la aplicación está en
primer plano. Es posible que este valor de LaunchMode no sea adecuado para las aplicaciones que inician varias
actividades en escenarios de notificación más complejos. Para más información sobre los valores de enumeración
de LaunchMode , consulte la actividad LaunchMode de Android.
En la clase MainActivity se modifica para recibir notificaciones entrantes:
protected override void OnCreate(Bundle savedInstanceState)
{
// ...

global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
CreateNotificationFromIntent(Intent);
}

protected override void OnNewIntent(Intent intent)


{
CreateNotificationFromIntent(intent);
}

void CreateNotificationFromIntent(Intent intent)


{
if (intent?.Extras != null)
{
string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);
DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
}
}

El método CreateNotificationFromIntent extrae los datos de notificación del argumento intent y los proporciona
a AndroidNotificationManager mediante el método ReceiveNotification . Se llama al método
CreateNotificationFromIntent desde el método OnCreate y el método OnNewIntent :

Cuando la aplicación se inicia mediante los datos de notificación, los datos de Intent se pasan al método
OnCreate .
Si la aplicación ya está en primer plano, los datos de Intent se pasan al método OnNewIntent .

Android ofrece muchas opciones avanzadas para las notificaciones. Para más información, consulte Notificaciones
en Xamarin.Android .

Creación de la implementación de la interfaz de iOS


Para que la aplicación de Xamarin.Forms envíe y reciba notificaciones en iOS, esta debe proporcionar una
implementación de INotificationManager .
Creación de la clase iOSNotificationManager
La clase iOSNotificationManager implementa la interfaz INotificationManager :

using System;
using Foundation;
using UserNotifications;
using Xamarin.Forms;

[assembly: Dependency(typeof(LocalNotifications.iOS.iOSNotificationManager))]
namespace LocalNotifications.iOS
{
public class iOSNotificationManager : INotificationManager
{
int messageId = 0;
bool hasNotificationsPermission;
public event EventHandler NotificationReceived;

public void Initialize()


{
// request the permission to use local notifications
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err)
=>
=>
{
hasNotificationsPermission = approved;
});
}

public void SendNotification(string title, string message, DateTime? notifyTime = null)


{
// EARLY OUT: app doesn't have permissions
if (!hasNotificationsPermission)
{
return;
}

messageId++;

var content = new UNMutableNotificationContent()


{
Title = title,
Subtitle = "",
Body = message,
Badge = 1
};

UNNotificationTrigger trigger;
if (notifyTime != null)
{
// Create a calendar-based trigger.
trigger = UNCalendarNotificationTrigger.CreateTrigger(GetNSDateComponents(notifyTime.Value),
false);
}
else
{
// Create a time-based trigger, interval is in seconds and must be greater than 0.
trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(0.25, false);
}

var request = UNNotificationRequest.FromIdentifier(messageId.ToString(), content, trigger);


UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
throw new Exception($"Failed to schedule notification: {err}");
}
});
}

public void ReceiveNotification(string title, string message)


{
var args = new NotificationEventArgs()
{
Title = title,
Message = message
};
NotificationReceived?.Invoke(null, args);
}

NSDateComponents GetNSDateComponents(DateTime dateTime)


{
return new NSDateComponents
{
Month = dateTime.Month,
Day = dateTime.Day,
Year = dateTime.Year,
Hour = dateTime.Hour,
Minute = dateTime.Minute,
Second = dateTime.Second
};
}
}
}
}

El atributo situado encima del espacio de nombres registra la implementación de la interfaz de


assembly
INotificationManager con DependencyService .

En iOS, debe solicitar permiso para usar notificaciones antes de intentar programar una notificación. El método
Initialize solicita autorización para usar notificaciones locales. El método SendNotification define la lógica
necesaria para crear y enviar una notificación. iOS llamará al método ReceiveNotification cuando se reciba un
mensaje, y se invocará el controlador de eventos.

NOTE
El método crea inmediatamente una notificación local mediante un objeto
SendNotification
UNTimeIntervalNotificationTrigger , o en un elemento DateTime exacto con un objeto
UNCalendarNotificationTrigger .

Control de las notificaciones entrantes en iOS


En iOS, debe crear un delegado que cree subclases UNUserNotificationCenterDelegate para administrar los
mensajes entrantes. La aplicación de ejemplo define una clase iOSNotificationReceiver :

public class iOSNotificationReceiver : UNUserNotificationCenterDelegate


{
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification,
Action<UNNotificationPresentationOptions> completionHandler)
{
DependencyService.Get<INotificationManager>().ReceiveNotification(notification.Request.Content.Title,
notification.Request.Content.Body);

// alerts are always shown for demonstration but this can be set to "None"
// to avoid showing alerts if the app is in the foreground
completionHandler(UNNotificationPresentationOptions.Alert);
}
}

Esta clase usa DependencyService para obtener una instancia de la clase iOSNotificationManager y proporciona
datos de notificación entrantes al método ReceiveNotification .
La clase AppDelegate debe especificar el delegado personalizado durante el inicio de la aplicación. La clase
AppDelegate debe especificar un objeto iOSNotificationReceiver como delegado UNUserNotificationCenter
durante el inicio de la aplicación. Esto sucede en el método FinishedLaunching :

public override bool FinishedLaunching(UIApplication app, NSDictionary options)


{
global::Xamarin.Forms.Forms.Init();

UNUserNotificationCenter.Current.Delegate = new iOSNotificationReceiver();

LoadApplication(new App());
return base.FinishedLaunching(app, options);
}

iOS ofrece muchas opciones avanzadas para las notificaciones. Para más información, consulte Notificaciones en
Xamarin.iOS.

Probar la aplicación
Una vez que los proyectos de plataforma contienen una implementación registrada de la interfaz
INotificationManager , la aplicación se puede probar en ambas plataformas. Ejecute la aplicación y haga clic en
cualquiera de los botones Crear notificación para crear una notificación.
En Android, las notificaciones aparecerán en el área de notificación. Cuando se pulsa la notificación, la aplicación
recibe la notificación y muestra un mensaje:

En iOS, la aplicación recibe automáticamente las notificaciones entrantes sin necesidad de la intervención del
usuario. La aplicación recibe la notificación y muestra un mensaje:

Vínculos relacionados
Proyecto de ejemplo
Notificaciones en Xamarin.Android
Receptores de difusión en Xamarin.Android
Notificaciones en Xamarin.iOS
DependencyService de Xamarin.Forms
Localización de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

El marco de localización integrado de .NET puede usarse para compilar aplicaciones multilingües multiplataforma
con Xamarin.Forms.

Localización de cadenas e imágenes de Xamarin.Forms


El mecanismo integrado para la localización de aplicaciones .NET usa archivos RESX y las clases de los espacios de
nombres System.Resources y System.Globalization . Los archivos RESX que contienen las cadenas traducidas se
insertan en el ensamblado de Xamarin.Forms, junto con una clase generada por el compilador que proporciona
acceso fuertemente tipado a las traducciones. Después, se puede recuperar el texto traducido en el código.

Localización de derecha a izquierda


La dirección de flujo es la dirección en la que el ojo humano lee los elementos de la interfaz de usuario en la página.
La localización de derecha a izquierda agrega compatibilidad para la dirección de flujo de derecha a izquierda para
las aplicaciones de Xamarin.Forms.
Localización de cadenas e imágenes de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
La localización es el proceso de adaptación de una aplicación para cumplir los requisitos lingüísticos o culturales
específicos de un mercado objetivo. Para llevar a cabo la localización, es posible que haya que traducir el texto y las
imágenes de una aplicación a varios idiomas. Una aplicación localizada muestra automáticamente el texto
traducido en función de la configuración de la referencia cultural del dispositivo móvil:

.NET Framework incluye un mecanismo integrado para localizar aplicaciones con archivos de recursos resx. Un
archivo de recursos almacena texto y otro contenido como pares de nombre/valor que permiten a la aplicación
recuperar el contenido de una clave proporcionada. Los archivos de recursos permiten separar el contenido
localizado del código de la aplicación.
El uso de archivos de recursos para localizar aplicaciones de :::no-loc(Xamarin.Forms)::: requiere que se realicen los
pasos siguientes:
1. Creación de archivos resx que contengan texto traducido.
2. Especificación de la referencia cultural predeterminada en el proyecto compartido.
3. Localización de texto en :::no-loc(Xamarin.Forms):::.
4. Localización de imágenes en función de la configuración de la referencia cultural para cada plataforma.
5. Localización del nombre de la aplicación en cada plataforma.
6. Prueba de la localización en cada plataforma.

Creación de archivos resx


Los archivos de recursos son archivos XML con una extensión .resx que se compilan en archivos de recursos
binarios (.resources) durante el proceso de compilación. Visual Studio 2019 genera una clase que proporciona una
API que permite recuperar recursos. Normalmente, una aplicación localizada contiene un archivo de recursos
predeterminado con todas las cadenas usadas en la aplicación, así como archivos de recursos para cada idioma
admitido. La aplicación de ejemplo tiene una carpeta Resx en el proyecto compartido que contiene los archivos de
recursos y su archivo de recursos predeterminado se denomina AppResources.resx .
Los archivos de recursos contienen la siguiente información para cada elemento:
Nombre especifica la clave que se usa para acceder al texto en el código.
Valor especifica el texto traducido.
Comentario es un campo opcional que contiene información adicional.
Para agregar un archivo de recursos se debe usar el cuadro de diálogo Agregar nuevo elemento en
Visual Studio 2019:

Una vez agregado el archivo, se pueden agregar filas para cada recurso de texto:

La opción desplegable Modificador de acceso determina el modo en que Visual Studio genera la clase utilizada
para acceder a los recursos. Al establecer el modificador de acceso en Público o Interno , se genera una clase
generada con el nivel de accesibilidad especificado. Si el modificador de acceso se establece en Sin generación
de código , no se genera un archivo de clase. El archivo de recursos predeterminado debe configurarse para
generar un archivo de clase, lo que se traduce en que se agrega un archivo con la extensión .designer.cs al
proyecto.
Una vez creado el archivo de recursos predeterminado, se pueden crear archivos adicionales para cada referencia
cultural que admita la aplicación. Cada archivo de recursos adicional debe incluir la referencia cultural de la
traducción en el nombre del archivo y debe tener el Modificador de acceso establecido en Sin generación de
código .
Al iniciarse, la aplicación intenta resolver una solicitud de recursos por orden de especificidad. Por ejemplo, si la
referencia cultural del dispositivo es en-US la aplicación busca archivos de recursos por este orden:
1. AppResources.en-US.resx
2. AppResources.en.resx
3. AppResources.resx (predeterminado)
En la captura de pantalla siguiente se muestra un archivo de traducción al español denominado
AppResources.es.cs :

El archivo de traducción utiliza los mismos valores de Nombre especificados en el archivo predeterminado, pero
contiene cadenas de idioma español en la columna Value . Además, el Modificador de acceso se establece en
Sin generación de código .
Se agrega un archivo de recursos con el cuadro de diálogo Agregar nuevo archivo en Visual Studio 2019 para
Mac:

Una vez creado un archivo de recursos predeterminado, se puede agregar texto creando elementos data dentro
del elemento root en el archivo de recursos:
<?xml version="1.0" encoding="utf-8"?>
<root>
...
<data name="AddButton" xml:space="preserve">
<value>Add Note</value>
</data>
<data name="NotesLabel" xml:space="preserve">
<value>Notes:</value>
</data>
<data name="NotesPlaceholder" xml:space="preserve">
<value>e.g. Get Milk</value>
</data>
</root>

Se puede crear un archivo de clase .designer.cs estableciendo una propiedad Herramienta personalizada en
las opciones del archivo de recursos:

Al establecer Herramienta personalizada en PublicResXFileCodeGenerator se producirá una clase generada


con acceso public . Al establecer Herramienta personalizada en InternalResXFileCodeGenerator se
producirá una clase generada con acceso internal . Un valor vacío de Herramienta personalizada no generará
ninguna clase. El nombre de la clase generada coincidirá con el nombre del archivo de recursos. Por ejemplo, el
archivo AppResources.resx se traducirá en la creación de una clase AppResources en un archivo denominado
AppResources.designer.cs .
Se pueden crear archivos de recursos adicionales para cada referencia cultural admitida. Cada archivo de idioma
debe incluir la referencia cultural de traducción en el nombre de archivo, por lo que un archivo destinado a es-MX
debe denominarse AppResources.es-MX.resx .
Al iniciarse, la aplicación intenta resolver una solicitud de recursos por orden de especificidad. Por ejemplo, si la
referencia cultural del dispositivo es en-US la aplicación busca archivos de recursos por este orden:
1. AppResources.en-US.resx
2. AppResources.en.resx
3. AppResources.resx (predeterminado)
Los archivos de traducción de idioma deben tener los mismos valores de Nombre especificados como archivo
predeterminado. En el XML siguiente se muestra un archivo de traducción al español denominado
AppResources.es.resx :
<?xml version="1.0" encoding="utf-8"?>
<root>
...
<data name="NotesLabel" xml:space="preserve">
<value>Notas:</value>
</data>
<data name="NotesPlaceholder" xml:space="preserve">
<value>por ejemplo . comprar leche</value>
</data>
<data name="AddButton" xml:space="preserve">
<value>Agregar nuevo elemento</value>
</data>
</root>

Especificación de la referencia cultural predeterminada


Para que los archivos de recursos funcionen correctamente, la aplicación debe tener un valor de
NeutralResourcesLanguage especificado. En el proyecto compartido, hay que personalizar el archivo
AssemblyInfo.cs para especificar la referencia cultural predeterminada. En el código siguiente se muestra cómo
establecer el valor de NeutralResourcesLanguage en en-US en el archivo AssemblyInfo.cs :

using System.Resources;

// The resources from the neutral language .resx file are stored directly
// within the library assembly. For that reason, changing en-US to a different
// language in this line will not by itself change the language shown in the
// app. See the discussion of UltimateResourceFallbackLocation in the
// documentation for additional information:
// https://docs.microsoft.com/dotnet/api/system.resources.neutralresourceslanguageattribute
[assembly: NeutralResourcesLanguage("en-US")]

WARNING
Si no especifica el atributo NeutralResourcesLanguage , la clase ResourceManager devuelve valores null para las
referencias culturales que no incluyan un archivo de recursos específico. Cuando se especifica la referencia cultural
predeterminada, la clase ResourceManager devuelve los resultados del archivo resx predeterminado para las referencias
culturales no admitidas. Por lo tanto, se recomienda especificar siempre el valor de NeutralResourcesLanguage para que se
muestre el texto de las referencias culturales no admitidas.

Una vez que se ha creado un archivo de recursos predeterminado y se ha especificado la referencia cultural
predeterminada en el archivo AssemblyInfo.cs , la aplicación puede recuperar cadenas localizadas durante el
inicio.
Para obtener más información sobre los archivos de recursos, vea Creación de archivos de recursos para
aplicaciones .NET.

Especificación de los idiomas admitidos en iOS


En iOS, debe declarar todos los idiomas admitidos en el archivo Info.plist del proyecto. En el archivo Info.plist ,
use la vista Origen para establecer una matriz para la clave CFBundleLocalizations y proporcione valores que se
correspondan a los archivos Resx. Además, asegúrese de establecer un idioma esperado a través de la clave
CFBundleDevelopmentRegion :
Como alternativa, abra el archivo Info.plist en un editor XML y agregue lo siguiente:

<key>CFBundleLocalizations</key>
<array>
<string>de</string>
<string>es</string>
<string>fr</string>
<string>ja</string>
<string>pt</string> <!-- Brazil -->
<string>pt-PT</string> <!-- Portugal -->
<string>ru</string>
<string>zh-Hans</string>
<string>zh-Hant</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>

NOTE
Apple trata el idioma portugués de un modo ligeramente distinto de lo que cabría esperar. Para obtener más información,
vea Adición de idiomas en developer.apple.com.

Para obtener más información, vea Especificación de los idiomas predeterminados y admitidos en Info.plist.

Localización de texto en :::no-loc(Xamarin.Forms):::


El texto se localiza en :::no-loc(Xamarin.Forms)::: mediante la clase AppResources generada. Esta clase se denomina
según el nombre del archivo de recursos predeterminado. Dado que el archivo de recursos del proyecto de
ejemplo se denomina AppResources.cs , Visual Studio genera una clase coincidente denominada AppResources .
Las propiedades estáticas se generan en la clase AppResources para cada fila del archivo de recursos. Las siguientes
propiedades estáticas se generan en la clase AppResources de la aplicación de ejemplo:
AddButton
NotesLabel
NotesPlaceholder
El acceso a estos valores como propiedades x:Static permite mostrar texto localizado en XAML:
<ContentPage ...
xmlns:resources="clr-namespace:LocalizationDemo.Resx">
<Label Text="{x:Static resources:AppResources.NotesLabel}" />
<Entry Placeholder="{x:Static resources:AppResources.NotesPlaceholder}" />
<Button Text="{x:Static resources:AppResources.AddButton}" />
</ContentPage>

El texto localizado también se puede recuperar con código:

public LocalizedCodePage()
{
Label notesLabel = new Label
{
Text = AppResources.NotesLabel,
// ...
};

Entry notesEntry = new Entry


{
Placeholder = AppResources.NotesPlaceholder,
//...
};

Button addButton = new Button


{
Text = AppResources.AddButton,
// ...
};

Content = new StackLayout


{
Children = {
notesLabel,
notesEntry,
addButton
}
};
}

Las propiedades de la clase AppResourcesusan el valor actual de


System.Globalization.CultureInfo.CurrentUICulture para determinar de qué archivo de recursos de referencia
cultural se deben recuperar los valores.

Localización de imágenes
Además de almacenar texto, los archivos resx pueden almacenar algo más que texto, también pueden almacenar
imágenes y datos binarios. Ahora bien, los dispositivos móviles tienen una gama de tamaños y densidades de
pantalla y cada plataforma móvil tiene funcionalidad para mostrar imágenes dependientes de la densidad. Por lo
tanto, se debe usar la funcionalidad de localización de imágenes de plataforma en lugar de almacenar imágenes en
archivos de recursos.
Localización de imágenes en Android
En Android, los recursos Drawable localizados (imágenes) se almacenan con una convención de nomenclatura para
carpetas en el directorio Resources . Las carpetas se denominan Drawable con un sufijo para el idioma de
destino. Por ejemplo, la carpeta de idioma español se denomina drawable-es .
Cuando se requiere un código de configuración regional de cuatro letras, Android requiere un carácter r adicional
después del guión. Por ejemplo, la carpeta de configuración regional de México (es-MX) debe denominarse
drawable-es-rMX . Los nombres de archivo de imagen de cada carpeta de configuración regional deben ser
idénticos:

Para obtener más información, vea Localización de Android.


Localización de imágenes en iOS
En iOS, las imágenes localizadas se almacenan con una convención de nomenclatura para carpetas en el directorio
Resources . La carpeta predeterminada se denomina Base.lproj . Las carpetas específicas del idioma se denominan
con el nombre de idioma o de configuración regional, seguido de .lproj . Por ejemplo, la carpeta de idioma español
se denomina es.lproj .
Los códigos locales de cuatro letras funcionan igual que los códigos de idioma de dos letras. Por ejemplo, la
carpeta de configuración regional de México (es-MX) debe denominarse es-MX.lproj . Los nombres de archivo de
imagen de cada carpeta de configuración regional deben ser idénticos:

NOTE
iOS admite la creación de un catálogo de recursos localizado en lugar de usar la estructura de carpetas. lproj. Aunque se debe
crear y administrar en Xcode.

Para obtener más información, vea Localización de iOS.


Localización de imágenes en UWP
En UWP, las imágenes localizadas se almacenan con una convención de nomenclatura para carpetas en el directorio
Assets/Images . Las carpetas se denominan con el idioma o la configuración regional. Por ejemplo, la carpeta de
idioma español se denomina es y la carpeta de configuración regional de México debe tener el nombre es-MX . Los
nombres de archivo de imagen de cada carpeta de configuración regional deben ser idénticos:

Para obtener más información, consulte Localización de UWP.


Consumo de imágenes localizadas
Dado que cada plataforma almacena las imágenes con una estructura de archivos única, el código XAML usa la
clase OnPlatform para establecer la propiedad ImageSource según la plataforma actual:

<Image>
<Image.Source>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="flag.png" />
<On Platform="UWP" Value="Assets/Images/flag.png" />
</OnPlatform>
</Image.Source>
</Image>

NOTE
La extensión de marcado OnPlatform ofrece una forma más concisa de especificar valores específicos de la plataforma. Para
obtener más información, vea Extensión de marcado OnPlatform.

El origen de la imagen se puede establecer en función de la propiedad Device.RuntimePlatform en el código:

string imgSrc = Device.RuntimePlatform == Device.UWP ? "Assets/Images/flag.png" : "flag.png";


Image flag = new Image
{
Source = ImageSource.FromFile(imgSrc),
WidthRequest = 100
};

Localización del nombre de la aplicación


El nombre de la aplicación se especifica por plataforma y no utiliza archivos de recursos resx. Para localizar el
nombre de la aplicación en Android, vea Localización del nombre de la aplicación en Android. Para localizar el
nombre de la aplicación en iOS, vea Localización del nombre de la aplicación en iOS. Para localizar el nombre de la
aplicación en UWP, vea Localización de cadenas en el manifiesto de paquete de UWP.
Prueba de la localización
La mejor manera de probar la localización consiste en cambiar el idioma del dispositivo. Es posible establecer el
valor de System.Globalization.CultureInfo.CurrentUICulture en el código, pero el comportamiento es incoherente
entre las plataformas, por lo que no se recomienda para las pruebas.
En iOS, en la aplicación de configuración, puede establecer el idioma de cada aplicación específicamente sin
cambiar el idioma del dispositivo.
En Android, la configuración de idioma se detecta y se almacena en caché cuando se inicia la aplicación. Si cambia
de idioma, es posible que haya que salir de la aplicación y reiniciarla para ver los cambios aplicados.

Vínculos relacionados
Proyecto de ejemplo de localización
Creación de archivos de recursos para aplicaciones .NET
Localización multiplataforma
Utilizar la clase CultureInfo (MSDN)
Localización de Android
Localización de iOS
Localización de UWP
Buscar y utilizar recursos para una referencia cultural específica (MSDN)
Localización de derecha a izquierda
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
La localización de derecha a izquierda agrega compatibilidad para la dirección de flujo de derecha a izquierda para
las aplicaciones de Xamarin.Forms.

NOTE
La localización de derecha a izquierda requiere el uso de iOS 9 o versiones posteriores; en Android, la API 17 o versiones
posteriores.

La dirección de flujo es la dirección en la que el ojo humano lee los elementos de la interfaz de usuario en la
página. Algunos lenguajes, como el árabe y hebreo, requieren que los elementos de interfaz de usuario se
distribuyan en una dirección de flujo de derecha a izquierda. Esto también puede lograrse estableciendo la
propiedad VisualElement.FlowDirection . Esta propiedad obtiene o establece la dirección en que fluyen los
elementos de interfaz de usuario dentro de cualquier elemento primario que controle su diseño, y debe
establecerse en uno de los valores de enumeración de FlowDirection :
LeftToRight
RightToLeft
MatchParent

Al establecer la propiedad FlowDirection como RightToLeft en un elemento, generalmente se establece la


alineación a la derecha, el orden de lectura de derecha a izquierda y el diseño del control para que fluya de
derecha a izquierda:

TIP
Solo debe establecerse la propiedad FlowDirection en el diseño inicial. El cambio de este valor en tiempo de ejecución
hace que el proceso de diseño sea difícil y que ello afecte al rendimiento.
El valor de la propiedad FlowDirection predeterminado para un elemento sin un elemento primario es
LeftToRight , mientras que el valor predeterminado de FlowDirection para un elemento con un elemento
primario es MatchParent . Por lo tanto, un elemento hereda el valor de la propiedad FlowDirection de su
elemento primario en el árbol visual, y cualquier elemento puede invalidar el valor que obtiene de su elemento
primario.

TIP
Al localizar una aplicación para idiomas con flujo de derecha a izquierda, establezca la propiedad FlowDirection en una
página o un diseño raíz. Esto hace que todos los elementos contenidos dentro de la página, o el diseño raíz, respondan
adecuadamente a la dirección del flujo.

Respetar la dirección del flujo del dispositivo


Respetar la dirección del flujo del dispositivo según el idioma y la región seleccionados es una opción explícita del
desarrollador y no se realiza automáticamente. Se consigue mediante el establecimiento de la propiedad
FlowDirection de una página, o diseño raíz, en el valor static Device.FlowDirection :

<ContentPage ... FlowDirection="{x:Static Device.FlowDirection}"> />

this.FlowDirection = Device.FlowDirection;

Posteriormente, todos los elementos secundarios de la página, o el diseño raíz, de forma predeterminada
heredarán el valor Device.FlowDirection .

Configuración de la plataforma
Se requiere una configuración de la plataforma específica para habilitar las configuraciones regionales de derecha
a izquierda.
iOS
La configuración regional de derecha a izquierda necesaria debe agregarse como un idioma compatible con los
elementos de matriz para la clave CFBundleLocalizations Info.plist . En el ejemplo siguiente se muestra el idioma
árabe agregado a la matriz para la clave CFBundleLocalizations :

<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
</array>

Para obtener más información, consulte Localization Basics in iOS (Introducción a la localización en iOS).
A continuación, se puede probar la localización de derecha a izquierda cambiando el idioma y la región del
dispositivo o el simulador a una configuración regional de derecha a izquierda que se haya especificado en
Info.plist .
WARNING
Tenga en cuenta que al cambiar el idioma y la región para una configuración regional de derecha a izquierda en iOS, todas
las vistas DatePicker emitirán una excepción si no se incluyen los recursos necesarios para la configuración regional. Por
ejemplo, al probar una aplicación en árabe que tenga un DatePicker , asegúrese de que mideast (Oriente Medio) esté
seleccionado en la sección Internationalization (Internacionalización) del panel iOS Build (Compilación de iOS).

Android
El archivo AndroidManifest.xml de la aplicación debe actualizarse para que el nodo <uses-sdk> establezca el
atributo android:minSdkVersion en 17 y el nodo <application> establezca el atributo android:supportsRtl en
true :

<?xml version="1.0" encoding="utf-8"?>


<manifest ... >
<uses-sdk android:minSdkVersion="17" ... />
<application ... android:supportsRtl="true">
</application>
</manifest>

A continuación, se puede probar la localización de derecha a izquierda cambiando el dispositivo o el emulador


para que use el idioma de derecha a izquierda o habilitando la opción Forzar dirección diseño RTL en Ajustes
> Opciones del desarrollador .
Plataforma universal de Windows (UWP)
Se deben especificar los recursos de idioma necesarios en el nodo <Resources> del archivo
Package.appxmanifest . En el ejemplo siguiente se muestra el idioma árabe agregado al nodo <Resources> :

<Resources>
<Resource Language="x-generate"/>
<Resource Language="en" />
<Resource Language="ar" />
</Resources>

Además, UWP requiere que la referencia cultural predeterminada de la aplicación se defina explícitamente en la
biblioteca .NET Standard. Esto puede realizarse estableciendo el atributo NeutralResourcesLanguage en
AssemblyInfo.cs , o en otra clase, en la referencia cultural predeterminada:

using System.Resources;

[assembly: NeutralResourcesLanguage("en")]

A continuación, se puede probar la localización de derecha a izquierda cambiando el idioma y la región del
dispositivo a la configuración regional de derecha a izquierda adecuada.

Limitaciones
La localización de derecha a izquierda de Xamarin.Forms actualmente tiene una serie de limitaciones:
El control de la ubicación del botón NavigationPage , la ubicación de elementos de barra de herramientas y la
animación de transición lo lleva a cabo la configuración regional del dispositivo, en lugar de la propiedad
FlowDirection .
La dirección de deslizamiento de CarouselPage no realiza la acción.
El contenido visual de Image no se invierte.
El control de la orientación de DisplayAlert y DisplayActionSheet lo lleva a cabo la configuración regional del
dispositivo, en lugar de la propiedad FlowDirection .
El contenido de WebView no respeta la propiedad FlowDirection .
Debe agregarse una propiedad TextDirection , para controlar la alineación del texto.
iOS
El control de la orientación de Stepper lo lleva a cabo la configuración regional del dispositivo, en lugar de la
propiedad FlowDirection .
El control de la alineación del texto de EntryCell lo lleva a cabo la configuración regional del dispositivo, en
lugar de la propiedad FlowDirection .
La alineación y los gestos de ContextActions no se invierten.
Android
El control de la orientación de SearchBar lo lleva a cabo la configuración regional del dispositivo, en lugar de
la propiedad FlowDirection .
El control de la colocación de ContextActions lo lleva a cabo la configuración regional del dispositivo, en lugar
de la propiedad FlowDirection .
UWP
El control de la alineación del texto de Editor lo lleva a cabo la configuración regional del dispositivo, en lugar
de la propiedad FlowDirection .
Los elementos secundarios MasterDetailPage no heredan la propiedad FlowDirection .
El control de la alineación del texto de ContextActions lo lleva a cabo la configuración regional del dispositivo,
en lugar de la propiedad FlowDirection .

Forzar el diseño de derecha a izquierda


Las aplicaciones de Xamarin.iOS y Xamarin.Android se pueden forzar para que siempre usen un diseño de
derecha a izquierda, independientemente de la configuración del dispositivo, modificando los proyectos de
plataforma respectivos.
iOS
Las aplicaciones de Xamarin.iOS se pueden forzar para que siempre usen un diseño de derecha a izquierda
modificando la clase AppDelegate de la siguiente manera:
1. Declare la función IntPtr_objc_msgSend como la primera línea de la clase AppDelegate :

[System.Runtime.InteropServices.DllImport(ObjCRuntime.Constants.ObjectiveCLibrary, EntryPoint =
"objc_msgSend")]
internal extern static IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector,
UISemanticContentAttribute arg1);

2. Llame a la función IntPtr_objc_msgSend desde el método FinishedLaunching , antes de volver del método
FinshedLaunching :

bool result = base.FinishedLaunching(app, options);

ObjCRuntime.Selector selector = new ObjCRuntime.Selector("setSemanticContentAttribute:");


IntPtr_objc_msgSend(UIView.Appearance.Handle, selector.Handle,
UISemanticContentAttribute.ForceRightToLeft);

return result;
Este enfoque es útil para las aplicaciones que siempre requieren un diseño de derecha a izquierda y elimina el
requisito para establecer la propiedad FlowDirection .
Para obtener más información sobre el método IntrPtr_objc_msgSend , consulte los selectores de Objective-C en
Xamarin.iOS.
Android
Las aplicaciones de Xamarin.Android se pueden forzar para que siempre usen un diseño de derecha a izquierda
modificando la clase MainActivity para que incluya la siguiente línea:

Window.DecorView.LayoutDirection = LayoutDirection.Rtl;

NOTE
Este enfoque requiere que la aplicación esté configurada para admitir el diseño de derecha a izquierda. Para obtener más
información, vea Configuración de la plataforma Android.

Este enfoque es útil para las aplicaciones que siempre requieren un diseño de derecha a izquierda y elimina el
requisito para establecer la propiedad FlowDirection de la mayoría de los controles. Sin embargo, algunos
controles, como CollectionView , no respetan la propiedad LayoutDirection y requieren que se establezca la
propiedad FlowDirection .

Vídeo de Xamarin.University sobre la compatibilidad de idiomas de


derecha a izquierda
Vídeo de compatibilidad de derecha a izquierda de Xamarin.Forms 3.0

Vínculos relacionados
Aplicación de ejemplo TodoLocalizedRTL
MessagingCenter de :::no-loc(Xamarin.Forms):::
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
El patrón de publicación y suscripción es un patrón de mensajería en el que los publicadores envían mensajes sin
tener conocimiento de los destinatarios, que se conocen como suscriptores. Del mismo modo, los suscriptores
escuchan mensajes específicos, sin tener conocimiento de ningún publicador.
Los eventos de .NET implementan el patrón de publicación y suscripción, y son el enfoque más sencillo para una
capa de comunicación entre los componentes si no se requiere el acoplamiento flexible, como sucede con un
control y la página que lo contiene. Pero las duraciones del publicador y del suscriptor están acopladas por
referencias a objetos, y el tipo de suscriptor debe tener una referencia al tipo de publicador. Esto puede generar
problemas de administración de memoria, en especial cuando hay objetos de corta duración que se suscriben a un
evento de un objeto estático o de larga duración. Si no se quita el controlador de eventos, el suscriptor se
mantendrá activo mediante la referencia a él en el publicador y esto impedirá o retrasará la recolección de
elementos no utilizados del suscriptor.
La clase MessagingCenter de :::no-loc(Xamarin.Forms)::: implementa el patrón de publicación y suscripción, lo que
permite la comunicación basada en mensajes entre los componentes que no se recomienda vincular mediante
referencias de tipo y objeto. Este mecanismo permite a los publicadores y suscriptores comunicarse sin tener
ninguna referencia mutua, lo que ayuda a reducir su interdependencia.
La clase MessagingCenter proporciona la funcionalidad de publicación y suscripción de multidifusión. Esto significa
que puede haber varios publicadores que publican un único mensaje, y puede haber varios suscriptores que
escuchen el mismo mensaje:

Los publicadores envían mensajes con el método MessagingCenter.Send , mientras que los suscriptores escuchan
mensajes con el método MessagingCenter.Subscribe . Además, en caso necesario, los suscriptores también pueden
cancelar la suscripción a mensajes con el método MessagingCenter.Unsubscribe .

IMPORTANT
Internamente, la clase MessagingCenter utiliza referencias débiles. Esto significa que no mantendrá los objetos activos y
permitirá la recolección de elementos no utilizados. Por lo tanto, solo debería ser necesario cancelar la suscripción a un
mensaje cuando una clase ya no quiere recibir el mensaje.

Publicación de un mensaje
Los mensajes de MessagingCenter son cadenas. Los publicadores informan a los suscriptores de un mensaje con
una de las sobrecargas de MessagingCenter.Send . En el ejemplo de código siguiente, se publica un mensaje de Hi :

MessagingCenter.Send<MainPage>(this, "Hi");

En este ejemplo, el método Send especifica un argumento genérico que representa al emisor. Para recibir el
mensaje, un suscriptor también debe especificar el mismo argumento genérico, lo que indica que está escuchando
un mensaje de ese emisor. Además, en este ejemplo se especifican dos argumentos de método:
El primer argumento especifica la instancia del emisor.
El segundo argumento especifica el mensaje.
Los datos de carga también se pueden enviar con un mensaje:

MessagingCenter.Send<MainPage, string>(this, "Hi", "John");

En este ejemplo, el método Send especifica dos argumentos genéricos. El primero es el tipo que envía el mensaje,
y el segundo es el tipo de los datos de carga que se envían. Para recibir el mensaje, un suscriptor también debe
especificar los mismos argumentos genéricos. Esto habilita varios mensajes que comparten una identidad de
mensaje, pero que envían diferentes tipos de datos de carga para que los reciban los distintos suscriptores.
Además, en este ejemplo se especifica un tercer argumento de método que representa los datos de carga que se
van a enviar al suscriptor. En este caso, los datos de carga son un elemento string .
El método Send publicará el mensaje y cualquier dato de carga mediante un enfoque del tipo "dispara y olvida".
Por lo tanto, el mensaje se envía incluso aunque no haya ningún suscriptor registrado para recibir el mensaje. En
esta situación, se omite el mensaje enviado.

Suscripción a un mensaje
Los suscriptores pueden registrarse para recibir un mensaje mediante una de las sobrecargas de
MessagingCenter.Subscribe . El ejemplo de código siguiente muestra un ejemplo:

MessagingCenter.Subscribe<MainPage> (this, "Hi", (sender) =>


{
// Do something whenever the "Hi" message is received
});

En este ejemplo, el método Subscribe suscribe el objeto this a los mensajes de Hi enviados por el tipo
MainPage y ejecuta un delegado de devolución de llamada en respuesta a la recepción del mensaje. El delegado de
devolución de llamada, especificado como una expresión lambda, podría ser código que actualiza la IU, guarda
algunos datos o desencadena alguna otra operación.

NOTE
Es posible que un suscriptor no necesite controlar cada instancia de un mensaje publicado, lo cual se puede controlar
mediante los argumentos de tipo genérico que se especifican en el método Subscribe .

En el ejemplo siguiente se muestra cómo suscribirse a un mensaje que contiene datos de carga:

MessagingCenter.Subscribe<MainPage, string>(this, "Hi", async (sender, arg) =>


{
await DisplayAlert("Message received", "arg=" + arg, "OK");
});
En este ejemplo, el método Subscribe se suscribe a los mensajes de Hi enviados por el tipo MainPage , cuyos
datos de carga son un elemento string . Un delegado de devolución de llamada se ejecuta como respuesta a la
recepción de este tipo de mensaje, que muestra los datos de carga en una alerta.

IMPORTANT
El delegado que ejecuta el método Subscribe se ejecutará en el mismo subproceso que publica el mensaje mediante el
método Send .

Cancelación de la suscripción a un mensaje


Los suscriptores pueden cancelar la suscripción a los mensajes que ya no quieren recibir. Para ello, hay que usar
una de las sobrecargas de MessagingCenter.Unsubscribe .

MessagingCenter.Unsubscribe<MainPage>(this, "Hi");

En este ejemplo, el método Unsubscribe cancela la suscripción del objeto de this al mensaje Hi enviado por el
tipo MainPage .
Se debe cancelar la suscripción a los mensajes que contienen datos de la sobrecarga Unsubscribe y que
especifican dos argumentos genéricos:

MessagingCenter.Unsubscribe<MainPage, string>(this, "Hi");

En este ejemplo, el método Unsubscribe cancela la suscripción del objeto this al mensaje Hi enviado por el tipo
MainPage , cuyos datos de carga son un elemento string .

Vínculos relacionados
Ejemplo de MessagingCenter
Navegación en Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Xamarin.Forms proporciona una serie de experiencias de navegación de páginas diferente, en función del tipo de
página que se use.
Tipos de página de

Como alternativa, las aplicaciones de Xamarin.Forms Shell usan una experiencia de navegación basada en URI que
no obliga a una jerarquía de navegación del conjunto. Para obtener más información, consulte Navegación en
Xamarin.Forms Shell.

Navegación jerárquica
La clase NavigationPage proporciona una experiencia de navegación jerárquica en la que el usuario puede
navegar por las páginas hacia delante y hacia atrás, si quiere. La clase implementa la navegación como una pila de
objetos Page en la que el último en entrar es el primero en salir (LIFO).

TabbedPage
TabbedPage de Xamarin.Forms consta de una lista de pestañas y un área de detalles mayor. Cada pestaña carga
contenido en el área de detalles.

CarouselPage
CarouselPage de Xamarin.Forms es una página que los usuarios pueden deslizar de lado a lado para navegar por
páginas de contenido, como una galería.

MasterDetailPage
MasterDetailPage de Xamarin.Forms es una página que administra dos páginas de información relacionada: una
página maestra que presenta elementos y una página de detalles que muestra detalles sobre elementos de la
página maestra.

Páginas modales
Xamarin.Forms también es compatible con las páginas modales. Una página modal anima a los usuarios a
completar una tarea autocontenida que no se puede abandonar mientras no se complete o se cancele la tarea.
Navegación jerárquica
18/12/2020 • 20 minutes to read • Edit Online

Descargar el ejemplo
La clase NavigationPage proporciona una experiencia de navegación jerárquica en la que el usuario puede
navegar por las páginas hacia adelante y hacia atrás, como quiera. La clase implementa la navegación como una
pila de objetos de página en la que el último en entrar es el primero en salir (LIFO). En este artículo se muestra
cómo utilizar la clase NavigationPage para realizar la navegación en una pila de páginas.
Para pasar de una página a otra, una aplicación insertará una página nueva en la pila de navegación, donde se
convertirá en la página activa, tal como se muestra en el diagrama siguiente.

Para volver a la página anterior, la aplicación mostrará la página actual de la pila de navegación y la nueva página
de nivel superior se convertirá en la página activa, tal como se muestra en el diagrama siguiente:

La propiedad Navigation expone los métodos de navegación en cualquiera de los tipos derivados de Page .
Estos métodos proporcionan la capacidad para insertar páginas en la pila de navegación, sacar páginas de la pila
de navegación y manipular la pila.

Realizar la navegación
En la navegación jerárquica, se usa la clase NavigationPage para navegar por una pila de objetos ContentPage . En
las siguientes capturas de pantalla, se muestran los componentes principales de la NavigationPage en cada
plataforma:

El diseño de una NavigationPage depende de la plataforma:


En iOS, en la parte superior de la página, hay una barra de navegación que muestra un título y presenta un
botón Atrás que vuelve a la página anterior.
En Android, en la parte superior de la página, hay una barra de navegación que muestra un título, un icono y
un botón Atrás que vuelve a la página anterior. El icono se define en el atributo [Activity] que decora la
clase MainActivity en el proyecto específico de la plataforma Android.
En la Plataforma universal de Windows, en la parte superior de la página, hay una barra de navegación que
muestra un título.
En todas las plataformas, el valor de la propiedad Page.Title se mostrará como el título de la página. Además, la
propiedad IconColor se puede establecer en un elemento Color que se aplica al icono de la barra de
navegación.

NOTE
Se recomienda que una NavigationPage debe rellenarse únicamente con instancias de ContentPage .

Creación de la página raíz


La primera página que se agrega a una pila de navegación se denomina página raíz de la aplicación. En el
ejemplo de código siguiente se muestra cómo se consigue:

public App ()
{
MainPage = new NavigationPage (new Page1Xaml ());
}

Esto hace que la instancia ContentPage de Page1Xaml se inserte en la pila de navegación, donde se convertirá en
la página activa y en la página raíz de la aplicación. Esto se muestra en las capturas de pantalla siguientes:

NOTE
La propiedad RootPage de una instancia de NavigationPage permite acceder a la primera página de la pila de
navegación.

Inserción de páginas en la pila de navegación


Para navegar a Page2Xaml , es necesario invocar el método PushAsync en la propiedad Navigation de la página
actual, como se muestra en el ejemplo de código siguiente:
async void OnNextPageButtonClicked (object sender, EventArgs e)
{
await Navigation.PushAsync (new Page2Xaml ());
}

Esto hace que la instancia de Page2Xaml se inserte en la pila de navegación, donde se convertirá en la página
activa. Esto se muestra en las capturas de pantalla siguientes:

Al invocar el método PushAsync , ocurre lo siguiente:


Se invoca la invalidación de OnDisappearing de la página que llama a PushAsync .
Se invoca la invalidación de OnAppearing de la página a la que se ha navegado.
La tarea PushAsync finaliza.

Sin embargo, el orden exacto en el que se producen estos eventos depende de la plataforma. Para obtener más
información, consulte el capítulo 24 del libro sobre :::no-loc(Xamarin.Forms)::: de Charles Petzold.

NOTE
Las llamadas a OnDisappearing y las invalidaciones de OnAppearing no se pueden tratar como indicaciones
garantizadas de navegación de páginas. Por ejemplo, en iOS, la invalidación de OnDisappearing se llama en la página
activa cuando la aplicación finaliza.

Sacar páginas de la pila de navegación


La página activa se puede extraer de la pila de navegación. Para ello, pulse el botón Atrás del dispositivo,
independientemente de si se trata de un botón físico en el dispositivo o de un botón en la pantalla.
Para volver mediante programación a la página original, la instancia Page2Xaml debe invocar el método
PopAsync , como se muestra en el ejemplo de código siguiente:

async void OnPreviousPageButtonClicked (object sender, EventArgs e)


{
await Navigation.PopAsync ();
}

Esto hace que la instancia de Page2Xaml se quite de la pila de navegación y que la nueva página de nivel superior
se convierta en la página activa. Al invocar el método PopAsync , ocurre lo siguiente:
Se invoca la invalidación de OnDisappearing de la página que llama a PopAsync .
Se invoca la invalidación de OnAppearing de la página a la que se ha regresado.
La tarea PopAsync vuelve.

Sin embargo, el orden exacto en el que se producen estos eventos depende de la plataforma. Para obtener más
información, consulte el capítulo 24 del libro sobre :::no-loc(Xamarin.Forms)::: de Charles Petzold.
Al igual que los métodos PushAsync y PopAsync , la propiedad Navigation de cada página también proporciona
un método PopToRootAsync , que se muestra en el ejemplo de código siguiente:

async void OnRootPageButtonClicked (object sender, EventArgs e)


{
await Navigation.PopToRootAsync ();
}

Este método saca todas las páginas de la pila de navegación, excepto la Page raíz, de manera que la página raíz
de la aplicación se convierte en la página activa.
Animación de transiciones de página
La propiedad Navigation de cada página también proporciona métodos de inserción y extracción invalidados
que incluyen un parámetro boolean que controla si se debe mostrar una animación de página durante la
navegación, como se muestra en el ejemplo de código siguiente:

async void OnNextPageButtonClicked (object sender, EventArgs e)


{
// Page appearance not animated
await Navigation.PushAsync (new Page2Xaml (), false);
}

async void OnPreviousPageButtonClicked (object sender, EventArgs e)


{
// Page appearance not animated
await Navigation.PopAsync (false);
}

async void OnRootPageButtonClicked (object sender, EventArgs e)


{
// Page appearance not animated
await Navigation.PopToRootAsync (false);
}

Al establecer el parámetro boolean en false , la animación de transición de página se deshabilita, mientras que
al establecer el parámetro en true , la animación de transición de página se habilita, siempre que la plataforma
subyacente lo admita. Sin embargo, los métodos de inserción y extracción que carecen de este parámetro
habilitan la animación de manera predeterminada.

Pasar datos al navegar


A veces es necesario que una página pase datos a otra durante la navegación. Dos técnicas para llevar a cabo esto
son pasar datos a través de un constructor de página y establecer el BindingContext de la página nueva en los
datos. A continuación, se explicarán de uno en uno.
Pasar datos a través de un constructor de página
La técnica más sencilla para pasar datos a otra página durante la navegación es hacerlo a través de un parámetro
de constructor de página, que se muestra en el ejemplo de código siguiente:
public App ()
{
MainPage = new NavigationPage (new MainPage (DateTime.Now.ToString ("u")));
}

Este código crea una instancia de MainPage y pasa la fecha y la hora actuales en formato ISO8601, que se
encapsula en una instancia de NavigationPage .
La instancia de MainPage recibe los datos a través de un parámetro de constructor, tal como se muestra en el
ejemplo de código siguiente:

public MainPage (string date)


{
InitializeComponent ();
dateLabel.Text = date;
}

A continuación, se establece la propiedad Label.Text para que los datos se muestren en la página, como se
muestra en las capturas de pantalla siguientes:

Pasar datos a través de un objeto BindingContext


Un enfoque alternativo para pasar datos a otra página durante la navegación es establecer el BindingContext de
la página nueva en los datos, como se muestra en el siguiente ejemplo de código:

async void OnNavigateButtonClicked (object sender, EventArgs e)


{
var contact = new Contact {
Name = "Jane Doe",
Age = 30,
Occupation = "Developer",
Country = "USA"
};

var secondPage = new SecondPage ();


secondPage.BindingContext = contact;
await Navigation.PushAsync (secondPage);
}

Este código establece el BindingContext de la instancia de SecondPage en la instancia de Contact y después


navega a la SecondPage .
Después, la SecondPage utiliza el enlace de datos para mostrar los datos de la instancia de Contact , como se
muestra en el ejemplo de código XAML siguiente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PassingData.SecondPage"
Title="Second Page">
<ContentPage.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<Label Text="Name:" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" />
</StackLayout>
...
<Button x:Name="navigateButton" Text="Previous Page" Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

En el ejemplo de código siguiente se muestra cómo se puede realizar el enlace de datos en C#:

public class SecondPageCS : ContentPage


{
public SecondPageCS ()
{
var nameLabel = new Label {
FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
FontAttributes = FontAttributes.Bold
};
nameLabel.SetBinding (Label.TextProperty, "Name");
...
var navigateButton = new Button { Text = "Previous Page" };
navigateButton.Clicked += OnNavigateButtonClicked;

Content = new StackLayout {


HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children = {
new StackLayout {
Orientation = StackOrientation.Horizontal,
Children = {
new Label{ Text = "Name:", FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.FillAndExpand },
nameLabel
}
},
...
navigateButton
}
};
}

async void OnNavigateButtonClicked (object sender, EventArgs e)


{
await Navigation.PopAsync ();
}
}

A continuación, una serie de controles Label muestran los datos en la página, como se muestra en las capturas
de pantalla siguientes:
Para más información sobre el enlace de datos, consulte Data Binding Basics (Aspectos básicos del enlace de
datos).

Manipulación de la pila de navegación


La propiedad Navigation expone una propiedad NavigationStack desde la que se pueden obtener las páginas de
la pila de navegación. Mientras que :::no-loc(Xamarin.Forms)::: mantiene el acceso a la pila de navegación, la
propiedad Navigation proporciona los métodos InsertPageBefore y RemovePage para manipular la pila
mediante la inserción o la eliminación de páginas.
El método InsertPageBefore inserta una página especificada en la pila de navegación antes de una página
existente especificada, como se muestra en el diagrama siguiente:

El método RemovePage quita la página especificada de la pila de navegación, como se muestra en el diagrama
siguiente:

Estos métodos permiten una experiencia de navegación personalizada, como sustituir una página de inicio de
sesión por una página nueva, después de iniciar sesión correctamente. El ejemplo de código siguiente muestra
esta situación:

async void OnLoginButtonClicked (object sender, EventArgs e)


{
...
var isValid = AreCredentialsCorrect (user);
if (isValid) {
App.IsUserLoggedIn = true;
Navigation.InsertPageBefore (new MainPage (), this);
await Navigation.PopAsync ();
} else {
// Login failed
}
}
Siempre que las credenciales del usuario sean correctas, la instancia de MainPage se inserta en la pila de
navegación antes de la página actual. A continuación, el método PopAsync quita la página actual de la pila de
navegación y la instancia de MainPage se convierte en la página activa.

Mostrar vistas en la barra de navegación


Cualquier View de :::no-loc(Xamarin.Forms)::: se puede mostrar en la barra de navegación de una
NavigationPage . Para ello, establezca la propiedad adjunta NavigationPage.TitleView en una View . Esta
propiedad adjunta se puede establecer en cualquier Page y, cuando la Page se inserte en una NavigationPage ,
la NavigationPage respetará el valor de la propiedad.
En el ejemplo siguiente, tomado del Ejemplo de vista de título, se muestra cómo establecer la propiedad adjunta
NavigationPage.TitleView de XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NavigationPageTitleView.TitleViewPage">
<NavigationPage.TitleView>
<Slider HeightRequest="44" WidthRequest="300" />
</NavigationPage.TitleView>
...
</ContentPage>

El código equivalente en C# es el siguiente:

public class TitleViewPage : ContentPage


{
public TitleViewPage()
{
var titleView = new Slider { HeightRequest = 44, WidthRequest = 300 };
NavigationPage.SetTitleView(this, titleView);
...
}
}

Esto da como resultado un Slider que se muestra en la barra de navegación en la NavigationPage :

IMPORTANT
Muchas vistas no aparecen en la barra de navegación, salvo que el tamaño de la vista se especifique con las propiedades
WidthRequest y HeightRequest . Como alternativa, la vista puede encapsularse en un StackLayout con las
propiedades HorizontalOptions y VerticalOptions establecidas en los valores adecuados.

Tenga en cuenta que, dado que la clase Layout deriva de la clase View , la propiedad adjunta TitleView se
puede establecer para mostrar una clase de diseño que contiene varias vistas. En iOS y la Plataforma universal de
Windows (UWP), la altura de la barra de navegación no se puede cambiar y, por tanto, se recortará si la vista que
se muestra en la barra de navegación es mayor que el tamaño predeterminado de la barra de navegación. Sin
embargo, en Android, la altura de la barra de navegación se puede cambiar estableciendo la propiedad enlazable
NavigationPage.BarHeight en un double que represente la altura nueva. Para obtener más información, consulte
Establecer la altura de la barra de navegación en una NavigationPage.
Como alternativa, para sugerir una barra de exploración extendida, se puede colocar una parte del contenido en
la barra de navegación y otra en una vista en la parte superior del contenido de la página que coincida con el
color de la barra de navegación. Además, en iOS, la línea y la sombra del separador que se encuentran en la parte
inferior de la barra de navegación se pueden quitar estableciendo la propiedad enlazable
NavigationPage.HideNavigationBarSeparator en true . Para obtener más información, consulte Ocultar el
separador de la barra de navegación en una NavigationPage.

NOTE
Las propiedades BackButtonTitle , Title , TitleIcon y TitleView pueden definir valores que ocupan espacio en la
barra de navegación. Mientras que el tamaño de la barra de navegación varía según la plataforma y el tamaño de pantalla,
al establecer todas estas propiedades se producirán conflictos a causa del espacio limitado disponible. En lugar de intentar
utilizar una combinación de estas propiedades, es posible que, si solo establece la propiedad TitleView , pueda elaborar
mejor el diseño de la barra de navegación deseado.

Limitaciones
Al mostrar una View en la barra de navegación de una NavigationPage , hay una serie de limitaciones que debe
conocer:
En iOS, las vistas colocadas en la barra de navegación de una NavigationPage se muestran en una posición
diferente según si están habilitados los títulos de gran tamaño. Para obtener más información sobre cómo
habilitar títulos grandes, consulte Mostrar títulos grandes.
En Android, colocar las vistas en la barra de navegación de una NavigationPage solo puede realizarse en las
aplicaciones que utilizan la compatibilidad de aplicaciones.
No se recomienda colocar vistas grandes y complejas, como ListView y TableView , en la barra de
navegación de una NavigationPage .

Vínculos relacionados
Navegación de páginas
Jerárquica (ejemplo)
PassingData (ejemplo)
LoginFlow (ejemplo)
TitleView (ejemplo)
Vídeo sobre cómo crear un flujo de pantalla de inicio de sesión en :::no-loc(Xamarin.Forms):::
NavigationPage
TabbedPage de Xamarin.Forms
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
TabbedPage de Xamarin.Forms consta de una lista de pestañas y un área de detalles mayor. Cada pestaña carga
contenido en el área de detalles. En las capturas de pantalla siguientes se muestra un elemento TabbedPage en iOS
y Android:

En iOS, la lista de pestañas aparece en la parte inferior de la pantalla y el área de detalles está arriba. Cada pestaña
está formada por un título y un icono, que debe ser un archivo PNG con un canal alfa. En orientación vertical, los
iconos de la barra de pestañas aparecen encima de los títulos de pestañas. En orientación horizontal, los iconos y
los títulos aparecen unos al lado de otros. Además, se puede mostrar una barra de pestañas normal o compacta,
dependiendo del dispositivo y la orientación. Si hay más de cinco pestañas, aparece una pestaña Más que puede
usarse para acceder a las demás pestañas. Para información sobre los requisitos de los iconos, consulte Tamaño de
los iconos de la barra de pestañas en developer.apple.com.
En Android, la lista de pestañas aparece en la parte superior de la pantalla y el área de detalles está debajo. Cada
pestaña está formada por un título y un icono, que debe ser un archivo PNG con un canal alfa. Sin embargo, se
pueden mover las pestañas a la parte inferior de la pantalla con una plataforma específica. Si hay más de cinco
pestañas, y la lista de pestañas está en la parte inferior de la pantalla, aparece una pestaña Más que puede usarse
para acceder a las demás pestañas. Para información sobre los requisitos de los iconos, consulte Pestañas en
material.io y Compatibilidad con distintas densidades de píxeles en developer.android.com. Para información sobre
cómo mover las pestañas a la parte inferior de la pantalla, consulte Establecimiento de la posición y el color de la
barra de herramientas de TabbedPage.
En la Plataforma universal de Windows (UWP), la lista de pestañas aparece en la parte superior de la pantalla y el
área de detalles se muestra debajo. Cada pestaña está formada por un título. Sin embargo, se pueden agregar
iconos a cada pestaña con una plataforma específica. Para más información, consulte Iconos de TabbedPage en
Windows.
TIP
Los archivos de Gráficos vectoriales escalables (SVG) se pueden mostrar como iconos de pestaña en un elemento
TabbedPage :

La clase TabbedRenderer para iOS tiene un método reemplazable GetIcon que se puede usar para cargar iconos de
pestaña desde un origen especificado. Además, en caso necesario se pueden proporcionar versiones seleccionadas y sin
seleccionar de un icono.
La clase TabbedPageRenderer para Android AppCompat tiene un método reemplazable SetTabIconImageSource que
se puede usar para cargar iconos de pestaña desde un elemento Drawable personalizado. Como alternativa, los
archivos SVG se pueden convertir en recursos Drawable vectoriales, que Xamarin.Forms puede mostrar de forma
automática. Para obtener más información sobre cómo convertir archivos SVG en recursos Drawable vectoriales, vea
Cómo agregar gráficos vectoriales de varias densidades en developer.android.com.
Para obtener más información, vea TabbedPage de Xamarin.Forms con iconos de pestaña de SVG.

Creación de TabbedPage
Para crear una instancia de TabbedPage se pueden usar dos métodos:
Rellene el elemento TabbedPage con una colección de objetos Page secundarios, por ejemplo, objetos
ContentPage . Para más información, consulte Relleno de un elemento TabbedPage con una colección de
páginas.
Asigne una colección a la propiedad ItemsSource y asigne un elemento DataTemplate a la propiedad
ItemTemplate para devolver páginas para los objetos de la colección. Para más información, consulte Relleno
de un elemento TabbedPage con una plantilla.
Con ambos métodos, TabbedPage muestra cada página cuando el usuario selecciona cada pestaña.

IMPORTANT
Se recomienda que una instancia de TabbedPage se rellene únicamente con instancias de NavigationPage y
ContentPage . Esto ayuda a garantizar una experiencia de usuario coherente en todas las plataformas.

Además, TabbedPage define las propiedades siguientes:


BarBackgroundColor de tipo Color , el color de fondo de la barra de pestañas.
BarTextColor de tipo Color , el color del texto de la barra de pestañas.
SelectedTabColor de tipo Color , el color de la pestaña cuando está seleccionada.
UnselectedTabColor de tipo Color , el color de la pestaña cuando no está seleccionada.

Todas estas propiedades están respaldadas por objetos BindableProperty , lo que significa que se les pueden
aplicar estilos y que las propiedades pueden ser los destinos de los enlaces de datos.

WARNING
En un elemento TabbedPage , cada objeto Page se crea cuando se construye TabbedPage . Esto puede dar lugar a una
experiencia de usuario deficiente, especialmente si TabbedPage es la página raíz de la aplicación. Aun así, Xamarin.Forms
Shell permite que las páginas a las que se accede mediante una barra de pestañas se creen a petición, en respuesta a la
navegación. Para obtener más información, consulte Xamarin.Forms Shell.

Relleno de un elemento TabbedPage con una colección de páginas


Un elemento TabbedPage se puede rellenar con una colección de objetos Page secundarios, por ejemplo, objetos
ContentPage . Para ello, se agregan los objetos Page a la colección TabbedPage.Children . Esto se puede lograr en
XAML de la siguiente manera:

<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageWithNavigationPage;assembly=TabbedPageWithNavigationPage"
x:Class="TabbedPageWithNavigationPage.MainPage">
<local:TodayPage />
<NavigationPage Title="Schedule" IconImageSource="schedule.png">
<x:Arguments>
<local:SchedulePage />
</x:Arguments>
</NavigationPage>
</TabbedPage>

NOTE
La propiedad Children de la clase MultiPage<T> , de la que se deriva TabbedPage , es la propiedad ContentProperty
de MultiPage<T> . Por lo tanto, en XAML no es necesario asignar explícitamente los objetos Page a la propiedad
Children .

El código de C# equivalente es el siguiente:

public class MainPageCS : TabbedPage


{
public MainPageCS ()
{
NavigationPage navigationPage = new NavigationPage (new SchedulePageCS ());
navigationPage.IconImageSource = "schedule.png";
navigationPage.Title = "Schedule";

Children.Add (new TodayPageCS ());


Children.Add (navigationPage);
}
}

En este ejemplo, el elemento TabbedPage se rellena con dos objetos Page . El primer elemento secundario es un
objeto ContentPage y el segundo es un objeto NavigationPage que contiene un objeto ContentPage .
En las capturas de pantalla siguientes se muestra un objeto ContentPage de un elemento TabbedPage :
Al seleccionar otra pestaña se muestra el objeto ContentPage que representa la pestaña:

En la pestaña Programar , el objeto ContentPage está encapsulado en un objeto NavigationPage .

WARNING
Aunque se puede colocar un objeto NavigationPage en un elemento TabbedPage , no se recomienda colocar un elemento
TabbedPage en un objeto NavigationPage . Esto se debe a que, en iOS, un elemento UITabBarController siempre
actúa como contenedor de UINavigationController . Para obtener más información, vea Combined View Controller
Interfaces (Interfaces combinadas del controlador de vistas) en la biblioteca para desarrolladores de iOS.

Navegación en una pestaña


La navegación se puede realizar dentro de una pestaña, siempre y cuando el objeto ContentPage esté encapsulado
en un objeto NavigationPage . Para ello, se invoca el método PushAsync en la propiedad Navigation del objeto
ContentPage :

await Navigation.PushAsync (new UpcomingAppointmentsPage ());

La página a la que se navega se especifica como argumento para el método PushAsync . En este ejemplo, la página
UpcomingAppointmentsPage se inserta en la pila de navegación, donde se convierte en la página activa:

Para obtener más información sobre la navegación mediante la clase NavigationPage , vea Navegación jerárquica.

Relleno de un elemento TabbedPage con una plantilla


Un elemento TabbedPage se puede rellenar con páginas mediante la asignación de una colección de datos a la
propiedad ItemsSource y la asignación de DataTemplate a la propiedad ItemTemplate que crea una plantilla de los
datos como objetos Page . Esto se puede lograr en XAML de la siguiente manera:
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabbedPageDemo;assembly=TabbedPageDemo"
x:Class="TabbedPageDemo.TabbedPageDemoPage"
ItemsSource="{x:Static local:MonkeyDataModel.All}">
<TabbedPage.Resources>
<ResourceDictionary>
<local:NonNullToBooleanConverter x:Key="booleanConverter" />
</ResourceDictionary>
</TabbedPage.Resources>
<TabbedPage.ItemTemplate>
<DataTemplate>
<ContentPage Title="{Binding Name}" IconImageSource="monkeyicon.png">
<StackLayout Padding="5, 25">
<Label Text="{Binding Name}" Font="Bold,Large" HorizontalOptions="Center" />
<Image Source="{Binding PhotoUrl}" WidthRequest="200" HeightRequest="200" />
<StackLayout Padding="50, 10">
<StackLayout Orientation="Horizontal">
<Label Text="Family:" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding Family}" Font="Bold,Medium" />
</StackLayout>
...
</StackLayout>
</StackLayout>
</ContentPage>
</DataTemplate>
</TabbedPage.ItemTemplate>
</TabbedPage>

El código de C# equivalente es el siguiente:


public class TabbedPageDemoPageCS : TabbedPage
{
public TabbedPageDemoPageCS ()
{
var booleanConverter = new NonNullToBooleanConverter ();

ItemTemplate = new DataTemplate (() =>


{
var nameLabel = new Label
{
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)),
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center
};
nameLabel.SetBinding (Label.TextProperty, "Name");

var image = new Image { WidthRequest = 200, HeightRequest = 200 };


image.SetBinding (Image.SourceProperty, "PhotoUrl");

var familyLabel = new Label


{
FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
FontAttributes = FontAttributes.Bold
};
familyLabel.SetBinding (Label.TextProperty, "Family");
...

var contentPage = new ContentPage


{
IconImageSource = "monkeyicon.png",
Content = new StackLayout {
Padding = new Thickness (5, 25),
Children =
{
nameLabel,
image,
new StackLayout
{
Padding = new Thickness (50, 10),
Children =
{
new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children =
{
new Label { Text = "Family:", HorizontalOptions = LayoutOptions.FillAndExpand },
familyLabel
}
},
// ...
}
}
}
}
};
contentPage.SetBinding (TitleProperty, "Name");
return contentPage;
});
ItemsSource = MonkeyDataModel.All;
}
}

En este ejemplo, cada pestaña consta de un objeto ContentPage que usa objetos Image y Label para mostrar los
datos de la pestaña:
Al seleccionar otra pestaña se muestra el objeto ContentPage que representa la pestaña.

Vínculos relacionados
TabbedPageWithNavigationPage (ejemplo)
TabbedPage (ejemplo)
TabbedPage con iconos de pestaña de SVG
Navegación jerárquica
Páginas de Xamarin.Forms
TabbedPage API
Página de carrusel de :::no-loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
CarouselPage de :::no-loc(Xamarin.Forms)::: es una página que los usuarios pueden deslizar de lado a lado para
navegar por páginas de contenido, a modo de galería. En este artículo se muestra cómo usar CarouselPage para
navegar por una colección de páginas.

IMPORTANT
CarouselPage se ha reemplazado por CarouselView , que proporciona un diseño desplazable en el que los usuarios
pueden deslizarse para desplazarse por una colección de elementos. Para obtener más información sobre CarouselView ,
vea CarouselView de :::no-loc(Xamarin.Forms):::.

En las capturas de pantalla siguientes se muestra un elemento CarouselPage en cada plataforma:

El diseño de un elemento CarouselPage es idéntico en todas las plataformas. Para navegar por las páginas, hay que
deslizar de derecha a izquierda para avanzar en la colección y de izquierda a derecha para retroceder en la
colección. Las capturas de pantalla siguientes muestran la primera página de una instancia de CarouselPage :
Al deslizar de derecha a izquierda se pasa a la segunda página, como se muestra en las capturas de pantalla
siguientes:

Al volver a deslizar de derecha a izquierda se pasa a la tercera página, mientras que al deslizar de izquierda a
derecha se vuelve a la página anterior.

NOTE
La instancia de CarouselPage no admite la virtualización de la interfaz de usuario. Por lo tanto, el rendimiento puede verse
afectado si CarouselPage contiene demasiados elementos secundarios.

Si hay una instancia de CarouselPage insertada en la página Detail de MasterDetailPage , la propiedad


MasterDetailPage.IsGestureEnabled debe establecerse en false para evitar conflictos entre CarouselPage y
MasterDetailPage .
Para obtener más información sobre la instancia de CarouselPage , vea el capítulo 25 del libro sobre :::no-
loc(Xamarin.Forms)::: de Charles Petzold.

Creación de CarouselPage
Para crear una instancia de CarouselPage se pueden usar dos métodos:
Rellenar la instancia de CarouselPage con una colección de instancias secundarias de ContentPage .
Asignar una colección a la propiedad ItemsSource y asignar un elemento DataTemplate a la propiedad
ItemTemplate para devolver instancias de ContentPage para objetos de la colección.

Con ambos métodos, CarouselPage muestra a su vez cada página, con una interacción de deslizamiento que pasa
a la página siguiente que se va a mostrar.

NOTE
Una instancia de CarouselPage solo se puede rellenar con instancias de ContentPage , o derivados de ContentPage .

Relleno de CarouselPage con una colección de páginas


El ejemplo de código XAML siguiente muestra una instancia de CarouselPage con tres instancias de ContentPage :
<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CarouselPageNavigation.MainPage">
<ContentPage>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS, Android" Value="0,40,0,0" />
</OnPlatform>
</ContentPage.Padding>
<StackLayout>
<Label Text="Red" FontSize="Medium" HorizontalOptions="Center" />
<BoxView Color="Red" WidthRequest="200" HeightRequest="200" HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
<ContentPage>
...
</ContentPage>
<ContentPage>
...
</ContentPage>
</CarouselPage>

En el ejemplo de código siguiente se muestra la interfaz de usuario equivalente en C#:


public class MainPageCS : CarouselPage
{
public MainPageCS ()
{
Thickness padding;
switch (Device.RuntimePlatform)
{
case Device.iOS:
case Device.Android:
padding = new Thickness(0, 40, 0, 0);
break;
default:
padding = new Thickness();
break;
}

var redContentPage = new ContentPage {


Padding = padding,
Content = new StackLayout {
Children = {
new Label {
Text = "Red",
FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.Center
},
new BoxView {
Color = Color.Red,
WidthRequest = 200,
HeightRequest = 200,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
}
}
}
};
var greenContentPage = new ContentPage {
Padding = padding,
Content = new StackLayout {
...
}
};
var blueContentPage = new ContentPage {
Padding = padding,
Content = new StackLayout {
...
}
};

Children.Add (redContentPage);
Children.Add (greenContentPage);
Children.Add (blueContentPage);
}
}

Cada ContentPage muestra simplemente un elemento Label para un color determinado y un elemento BoxView
de ese color.
Relleno de CarouselPage con una plantilla
El ejemplo de código XAML siguiente muestra una instancia de CarouselPage construida mediante la asignación
de DataTemplate a la propiedad ItemTemplate para devolver páginas para objetos de la colección:
<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CarouselPageNavigation.MainPage">
<CarouselPage.ItemTemplate>
<DataTemplate>
<ContentPage>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS, Android" Value="0,40,0,0" />
</OnPlatform>
</ContentPage.Padding>
<StackLayout>
<Label Text="{Binding Name}" FontSize="Medium" HorizontalOptions="Center" />
<BoxView Color="{Binding Color}" WidthRequest="200" HeightRequest="200"
HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
</DataTemplate>
</CarouselPage.ItemTemplate>
</CarouselPage>

CarouselPage se rellena con datos al establecer la propiedad ItemsSource en el constructor para el archivo de
código subyacente:

public MainPage ()
{
...
ItemsSource = ColorsDataModel.All;
}

En el ejemplo de código siguiente se muestra la instancia de CarouselPage equivalente creada en C#:


public class MainPageCS : CarouselPage
{
public MainPageCS ()
{
Thickness padding;
switch (Device.RuntimePlatform)
{
case Device.iOS:
case Device.Android:
padding = new Thickness(0, 40, 0, 0);
break;
default:
padding = new Thickness();
break;
}

ItemTemplate = new DataTemplate (() => {


var nameLabel = new Label {
FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.Center
};
nameLabel.SetBinding (Label.TextProperty, "Name");

var colorBoxView = new BoxView {


WidthRequest = 200,
HeightRequest = 200,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};
colorBoxView.SetBinding (BoxView.ColorProperty, "Color");

return new ContentPage {


Padding = padding,
Content = new StackLayout {
Children = {
nameLabel,
colorBoxView
}
}
};
});

ItemsSource = ColorsDataModel.All;
}
}

Cada ContentPage muestra simplemente un elemento Label para un color determinado y un elemento BoxView
de ese color.

Vínculos relacionados
Páginas de Xamarin.Forms
CarouselPage (ejemplo)
CarouselPageTemplate (ejemplo)
CarouselPage
Página maestra y de detalles de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
MasterDetailPage de :::no-loc(Xamarin.Forms)::: es una página que administra dos páginas relacionadas de
información: una página maestra que presenta elementos y una página de detalles que presenta detalles sobre los
elementos de la página maestra. En este artículo se explica cómo usar una instancia de MasterDetailPage y cómo
navegar entre sus páginas de información.

Información general
Normalmente, una página maestra presenta una lista de elementos, como se muestra en las siguientes capturas de
pantalla:

La ubicación de la lista de elementos es idéntica en cada plataforma; al seleccionar uno de los elementos, se le lleva
a la página de detalles correspondiente. Además, la página maestra también incluye una barra de navegación que
contiene un botón que se puede usar para ir a la página de detalles activa:
En iOS, la barra de navegación se encuentra en la parte superior de la página y tiene un botón que lleva a la
página de detalles. Además, se puede ir a la página de detalles activa si se desliza la página maestra hacia la
izquierda.
En Android, la barra de navegación se encuentra en la parte superior de la página y presenta un título, un icono
y un botón que lleva a la página de detalles. El icono se define en el atributo [Activity] que decora la clase
MainActivity en el proyecto específico de la plataforma Android. Además, se puede ir a la página de detalles
activa si se desliza la página maestra hacia la izquierda, si se puntea en el extremo derecho de la pantalla en la
página de detalles y si se pulsa el botón Atrás situado en la parte inferior de la pantalla.
En Plataforma universal de Windows (UWP), la barra de navegación se encuentra en la parte superior de la
página y tiene un botón que lleva a la página de detalles.
Una página de detalles presenta datos correspondientes al elemento seleccionado en la página maestra; los
componentes principales de la página de detalles se muestran en las capturas de pantalla siguientes:
La página de detalles contiene una barra de navegación cuyo contenido depende de la plataforma:
En iOS, la barra de navegación se encuentra en la parte superior de la página, muestra un título y tiene un
botón que devuelve a la página maestra, siempre que la instancia de la página de detalles esté incluida en la
instancia de NavigationPage . Además, se puede volver a la página maestra si se desliza la página de detalles
hacia la derecha.
En Android, hay una barra de navegación en la parte superior de la página que muestra un título, un icono y un
botón que devuelve a la página maestra. El icono se define en el atributo [Activity] que decora la clase
MainActivity en el proyecto específico de la plataforma Android.
En UWP, la barra de navegación se encuentra en la parte superior de la página, muestra un título y tiene un
botón que devuelve a la página maestra.
Comportamiento de navegación
El comportamiento de la experiencia de navegación entre las páginas maestra y de detalles depende de la
plataforma:
En iOS, la página de detalles se desliza hacia la derecha cuando la página maestra se desliza desde la izquierda,
y la parte izquierda de la página de detalles sigue siendo visible.
En Android, las páginas maestra y de detalles se superponen.
En UWP, la página maestra se desplaza desde el lateral izquierdo de la página de detalles, siempre que la
propiedad MasterBehavior esté establecida en Popover . Para obtener más información, consulte Control del
comportamiento de presentación de la página de detalles.
En el modo horizontal se observa un comportamiento similar, salvo que la página maestra en iOS y Android tiene
un ancho similar al de la página maestra en modo vertical, así que se ve más superficie de la página de detalles.
Para obtener información sobre cómo controlar el comportamiento de navegación, vea Control del
comportamiento de presentación de la página de detalles.

Creación de MasterDetailPage
Una instancia de MasterDetailPage contiene propiedades Master y Detail que son de tipo Page y se usan para
obtener y establecer las páginas maestra y de detalles, respectivamente.

IMPORTANT
Una instancia de MasterDetailPage está diseñada para ser una página raíz. Su empleo como página secundaria en otros
tipos de páginas podría dar lugar a un comportamiento inesperado e incoherente. Además, se recomienda que la página
maestra de una instancia de MasterDetailPage siempre sea una instancia de ContentPage y que la página de detalles
solo se rellene con instancias de TabbedPage , NavigationPage y ContentPage . Esto ayuda a garantizar una experiencia
de usuario coherente en todas las plataformas.

El ejemplo de código XAML siguiente muestra una instancia de MasterDetailPage que establece las propiedades
Master y Detail :
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MasterDetailPageNavigation;assembly=MasterDetailPageNavigation"
x:Class="MasterDetailPageNavigation.MainPage">
<MasterDetailPage.Master>
<local:MasterPage x:Name="masterPage" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:ContactsPage />
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>

En el ejemplo de código siguiente se muestra la instancia de MasterDetailPage equivalente creada en C#:

public class MainPageCS : MasterDetailPage


{
MasterPageCS masterPage;

public MainPageCS ()
{
masterPage = new MasterPageCS ();
Master = masterPage;
Detail = new NavigationPage (new ContactsPageCS ());
...
}
...
}

La propiedad MasterDetailPage.Master está establecida en una instancia de ContentPage . La propiedad


MasterDetailPage.Detail está establecida en una instancia de NavigationPage que contiene una instancia de
ContentPage .
Creación de la página maestra
El ejemplo de código XAML siguiente muestra la declaración del objeto MasterPage , al que se hace referencia
mediante la propiedad MasterDetailPage.Master :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="using:MasterDetailPageNavigation"
x:Class="MasterDetailPageNavigation.MasterPage"
Padding="0,40,0,0"
IconImageSource="hamburger.png"
Title="Personal Organiser">
<StackLayout>
<ListView x:Name="listView" x:FieldModifier="public">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:MasterPageItem}">
<local:MasterPageItem Title="Contacts" IconSource="contacts.png" TargetType="{x:Type
local:ContactsPage}" />
<local:MasterPageItem Title="TodoList" IconSource="todo.png" TargetType="{x:Type
local:TodoListPage}" />
<local:MasterPageItem Title="Reminders" IconSource="reminders.png" TargetType="{x:Type
local:ReminderPage}" />
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding IconSource}" />
<Label Grid.Column="1" Text="{Binding Title}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

La página consta de un elemento ListView que se rellena con datos de XAML al establecer su propiedad
ItemsSource en una matriz de instancias de MasterPageItem . Cada MasterPageItem define propiedades Title ,
IconSource y TargetType .

Se asigna un elemento DataTemplate a la propiedad ListView.ItemTemplate para mostrar cada MasterPageItem .


DataTemplate contiene un elemento ViewCell que consta de un elemento Image y un Label . Image muestra el
valor de la propiedad IconSource y Label muestra el valor de la propiedad Title , para cada MasterPageItem .
La página tiene establecidas sus propiedades Title y IconImageSource . El icono aparece en la página de detalles,
siempre que esta tenga una barra de título. Esta debe habilitarse en iOS al incluir la instancia de la página de
detalles en una instancia de NavigationPage .

NOTE
La página MasterDetailPage.Master debe tener su propiedad Title establecida, o se produce una excepción.

En el ejemplo de código siguiente se muestra la página equivalente creada en C#:


public class MasterPageCS : ContentPage
{
public ListView ListView { get { return listView; } }

ListView listView;

public MasterPageCS ()
{
var masterPageItems = new List<MasterPageItem> ();
masterPageItems.Add (new MasterPageItem {
Title = "Contacts",
IconSource = "contacts.png",
TargetType = typeof(ContactsPageCS)
});
masterPageItems.Add (new MasterPageItem {
Title = "TodoList",
IconSource = "todo.png",
TargetType = typeof(TodoListPageCS)
});
masterPageItems.Add (new MasterPageItem {
Title = "Reminders",
IconSource = "reminders.png",
TargetType = typeof(ReminderPageCS)
});

listView = new ListView {


ItemsSource = masterPageItems,
ItemTemplate = new DataTemplate (() => {
var grid = new Grid { Padding = new Thickness(5, 10) };
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });

var image = new Image();


image.SetBinding(Image.SourceProperty, "IconSource");
var label = new Label { VerticalOptions = LayoutOptions.FillAndExpand };
label.SetBinding(Label.TextProperty, "Title");

grid.Children.Add(image);
grid.Children.Add(label, 1, 0);

return new ViewCell { View = grid };


}),
SeparatorVisibility = SeparatorVisibility.None
};

IconImageSource = "hamburger.png";
Title = "Personal Organiser";
Content = new StackLayout
{
Children = { listView }
};
}
}

En las capturas de pantalla siguientes se muestra la página maestra en cada plataforma:


Creación y presentación de la página de detalles
La instancia de MasterPage contiene una propiedad ListView que expone su instancia de ListView para que el
elemento MainPage de la instancia de MasterDetailPage pueda registrar un controlador de eventos para controlar
el evento ItemSelected . Esto permite a la instancia de MainPage establecer la propiedad Detail en la página que
representa al elemento seleccionado ListView . En el ejemplo de código siguiente se muestra el controlador de
eventos:

public partial class MainPage : MasterDetailPage


{
public MainPage ()
{
...
masterPage.listView.ItemSelected += OnItemSelected;
}

void OnItemSelected (object sender, SelectedItemChangedEventArgs e)


{
var item = e.SelectedItem as MasterPageItem;
if (item != null) {
Detail = new NavigationPage ((Page)Activator.CreateInstance (item.TargetType));
masterPage.listView.SelectedItem = null;
IsPresented = false;
}
}
}

El método OnItemSelected realiza las siguientes acciones:


Recupera el elemento SelectedItem de la instancia de ListView y, siempre que no sea null , establece la
página de detalles en una nueva instancia del tipo de página almacenado en la propiedad TargetType de
MasterPageItem . El tipo de página se incluye en una instancia de NavigationPage para asegurarse de que el
icono al que se hace referencia mediante la propiedad IconImageSource en MasterPage se muestre en la página
de detalles en iOS.
El elemento seleccionado en ListView se estable en null para asegurarse de que ninguno de los elementos
ListView se seleccione la próxima vez que el elemento MasterPage esté presente.
La página de detalles se presenta al usuario al establecer la propiedad MasterDetailPage.IsPresented en false .
Esta propiedad controla si se presenta la página maestra o de detalles. Se debe establecer en true para
mostrar la página maestra y en false para mostrar la página de detalles.

Las capturas de pantalla siguientes muestran la página de detalles ContactPage , que se presenta después de
haberse seleccionado en la página maestra:

Control del comportamiento de presentación de la página de detalles


La forma en que MasterDetailPage administre las páginas maestra y de detalles depende de si la aplicación se
ejecuta en un teléfono o tableta, de la orientación del dispositivo y del valor de la propiedad MasterBehavior . Esta
propiedad determina cómo se muestra la página de detalles. Sus posibles valores son:
Default : las páginas se muestran con el valor predeterminado de la plataforma.
Popover : la página de detalles cubre, o cubre parcialmente, la página maestra.
Split : la página maestra se muestra a la izquierda y la página de detalles a la derecha.
SplitOnLandscape : se usa una pantalla dividida cuando el dispositivo está en orientación horizontal.
SplitOnPor trait : se usa una pantalla dividida cuando el dispositivo está en orientación vertical.
En el siguiente ejemplo de código XAML se muestra cómo establecer la propiedad MasterBehavior en una
instancia de MasterDetailPage :
<?xml version="1.0" encoding="UTF-8"?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MasterDetailPageNavigation.MainPage"
MasterBehavior="Popover">
...
</MasterDetailPage>

En el ejemplo de código siguiente se muestra la instancia de MasterDetailPage equivalente creada en C#:

public class MainPageCS : MasterDetailPage


{
MasterPageCS masterPage;

public MainPageCS ()
{
MasterBehavior = MasterBehavior.Popover;
...
}
}

Pero el valor de la propiedad MasterBehavior solo afecta a las aplicaciones que se ejecutan en el escritorio o en
tabletas. Las aplicaciones que se ejecutan en teléfonos siempre tienen el comportamiento Popover.

Resumen
En este artículo se ha explicado cómo usar una instancia de MasterDetailPage y cómo navegar entre sus páginas
de información. MasterDetailPage de :::no-loc(Xamarin.Forms)::: es una página que administra dos páginas de
información relacionada: una página maestra que presenta elementos y una página de detalles que muestra
detalles sobre elementos de la página maestra.

Vínculos relacionados
Páginas de Xamarin.Forms
MasterDetailPage (ejemplo)
MasterDetailPage
Páginas modales de :::no-loc(Xamarin.Forms):::
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: es compatible con las páginas modales. Una página modal anima a los usuarios a
completar una tarea autocontenida que no se puede abandonar mientras no se complete o se cancele la tarea. En
este artículo se muestra cómo navegar a páginas modales.
En este artículo se tratan los siguientes temas:
Realizar la navegación: insertar páginas en la pila modal, sacar páginas de la pila modal, deshabilitar el botón
Atrás y animar las transiciones de página.
Pasar datos al navegar: pasar datos a través de un constructor de página y de un BindingContext .

Información general
Una página modal puede ser cualquiera de los tipos Página compatibles con :::no-loc(Xamarin.Forms):::. Para
mostrar una página modal, la aplicación la insertará en la pila modal, donde se convertirá en la página activa,
como se muestra en el siguiente diagrama:

Para volver a la página anterior, la aplicación mostrará la página actual de la pila modal y la nueva página de nivel
superior se convertirá en la página activa, tal como se muestra en el siguiente diagrama:

Realizar la navegación
Los métodos de navegación modal se exponen mediante la propiedad Navigation en cualquier tipo Page
derivado. Estos métodos proporcionan la capacidad de insertar páginas modales en la pila modal, y sacar páginas
modales de la pila modal.
La propiedad Navigation también expone una propiedad ModalStack desde la que se pueden obtener las
páginas modales de la pila modal. Pero no existe el concepto de realizar una manipulación de pila modal o extraer
la página raíz de la navegación modal. Esto se debe a que estas operaciones no se admiten de forma universal en
las plataformas subyacentes.

NOTE
No es necesaria una instancia de NavigationPage para realizar la navegación de páginas modal.

Inserción de páginas en la pila modal


Para navegar a ModalPage , es necesario invocar el método PushModalAsync en la propiedad Navigation de la
página actual, como se muestra en el ejemplo de código siguiente:

async void OnItemSelected (object sender, SelectedItemChangedEventArgs e)


{
if (listView.SelectedItem != null) {
var detailPage = new DetailPage ();
...
await Navigation.PushModalAsync (detailPage);
}
}

Esto hace que la instancia de ModalPage se inserte en la pila modal, donde se convertirá en la página activa,
siempre que se haya seleccionado un elemento en la ListView en la instancia de MainPage . La instancia de
ModalPage se muestra en la siguiente captura de pantalla:

Al invocar PushModalAsync , ocurre lo siguiente:


La página que llama a PushModalAsync invoca su invalidación OnDisappearing , siempre que la plataforma
subyacente no sea Android.
Se invoca la invalidación de OnAppearing de la página a la que se ha navegado.
La tarea PushAsync finaliza.

Con todo, el orden exacto en el que se producen estos eventos depende de la plataforma. Para obtener más
información, consulte el capítulo 24 del libro sobre :::no-loc(Xamarin.Forms)::: de Charles Petzold.

NOTE
Las llamadas a OnDisappearing y las invalidaciones de OnAppearing no se pueden tratar como indicaciones garantizadas
de navegación de páginas. Por ejemplo, en iOS, la invalidación de OnDisappearing se llama en la página activa cuando la
aplicación finaliza.

Sacar páginas de la pila modal


La página activa se puede extraer de la pila modal. Para ello, pulse el botón Atrás del dispositivo,
independientemente de si se trata de un botón físico en el dispositivo o de un botón en la pantalla.
Para volver mediante programación a la página original, la instancia ModalPage debe invocar el método
PopModalAsync , como se muestra en el ejemplo de código siguiente:
async void OnDismissButtonClicked (object sender, EventArgs args)
{
await Navigation.PopModalAsync ();
}

Esto hace que la instancia de ModalPage se quite de la pila modal y que la nueva página de nivel superior se
convierta en la página activa. Al invocar PopModalAsync , ocurre lo siguiente:
Se invoca la invalidación de OnDisappearing de la página que llama a PopModalAsync .
Se invoca la invalidación de OnAppearing de la página a la que se vuelve, siempre que la plataforma
subyacente no sea Android.
Se devuelve la tarea PopModalAsync .
Con todo, el orden exacto en el que se producen estos eventos depende de la plataforma. Para obtener más
información, consulte el capítulo 24 del libro sobre :::no-loc(Xamarin.Forms)::: de Charles Petzold.
Deshabilitación del botón Atrás
En Android, el usuario siempre puede volver a la página anterior presionando el botón Atrás estándar en el
dispositivo. Si la página modal requiere que el usuario complete una tarea independiente antes de salir de la
página, la aplicación debe deshabilitar el botón Atrás. Esto puede realizarse invalidando el método
Page.OnBackButtonPressed en la página modal. Para obtener más información, consulte el capítulo 24 del libro
sobre :::no-loc(Xamarin.Forms)::: de Charles Petzold.
Animación de transiciones de página
La propiedad Navigation de cada página también proporciona métodos de inserción y extracción invalidados que
incluyen un parámetro boolean que controla si se debe mostrar una animación de página durante la navegación,
como se muestra en el ejemplo de código siguiente:

async void OnNextPageButtonClicked (object sender, EventArgs e)


{
// Page appearance not animated
await Navigation.PushModalAsync (new DetailPage (), false);
}

async void OnDismissButtonClicked (object sender, EventArgs args)


{
// Page appearance not animated
await Navigation.PopModalAsync (false);
}

Al establecer el parámetro boolean en false , la animación de transición de página se deshabilita, mientras que
al establecer el parámetro en true , la animación de transición de página se habilita, siempre que la plataforma
subyacente lo admita. Sin embargo, los métodos de inserción y extracción que carecen de este parámetro
habilitan la animación de manera predeterminada.

Pasar datos al navegar


A veces es necesario que una página pase datos a otra durante la navegación. Dos técnicas para llevar a cabo esto
son pasar datos a través de un constructor de página y establecer el BindingContext de la página nueva en los
datos. En las siguientes secciones se explicarán de uno en uno.
Pasar datos a través de un constructor de página
La técnica más sencilla para pasar datos a otra página durante la navegación es hacerlo a través de un parámetro
de constructor de página, que se muestra en el siguiente ejemplo de código:
public App ()
{
MainPage = new MainPage (DateTime.Now.ToString ("u")));
}

Este código crea una instancia de MainPage y pasa la fecha y la hora actuales en formato ISO8601.
La instancia de MainPage recibe los datos a través de un parámetro de constructor, tal como se muestra en el
ejemplo de código siguiente:

public MainPage (string date)


{
InitializeComponent ();
dateLabel.Text = date;
}

Después, se establece la propiedad Label.Text para que los datos se muestren en la página.
Pasar datos a través de un objeto BindingContext
Un enfoque alternativo para pasar datos a otra página durante la navegación es establecer el BindingContext de
la página nueva en los datos, como se muestra en el siguiente ejemplo de código:

async void OnItemSelected (object sender, SelectedItemChangedEventArgs e)


{
if (listView.SelectedItem != null) {
var detailPage = new DetailPage ();
detailPage.BindingContext = e.SelectedItem as Contact;
listView.SelectedItem = null;
await Navigation.PushModalAsync (detailPage);
}
}

Este código establece el BindingContext de la instancia de DetailPage en la instancia de Contact y después


navega a la DetailPage .
Después, la DetailPage utiliza el enlace de datos para mostrar los datos de la instancia de Contact , como se
muestra en el ejemplo de código XAML siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ModalNavigation.DetailPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,40,0,0" />
</OnPlatform>
</ContentPage.Padding>
<ContentPage.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<Label Text="Name:" FontSize="Medium" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" />
</StackLayout>
...
<Button x:Name="dismissButton" Text="Dismiss" Clicked="OnDismissButtonClicked" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

En el ejemplo de código siguiente se muestra cómo se puede realizar el enlace de datos en C#:
public class DetailPageCS : ContentPage
{
public DetailPageCS ()
{
var nameLabel = new Label {
FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
FontAttributes = FontAttributes.Bold
};
nameLabel.SetBinding (Label.TextProperty, "Name");
...
var dismissButton = new Button { Text = "Dismiss" };
dismissButton.Clicked += OnDismissButtonClicked;

Thickness padding;
switch (Device.RuntimePlatform)
{
case Device.iOS:
padding = new Thickness(0, 40, 0, 0);
break;
default:
padding = new Thickness();
break;
}

Padding = padding;
Content = new StackLayout {
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children = {
new StackLayout {
Orientation = StackOrientation.Horizontal,
Children = {
new Label{ Text = "Name:", FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.FillAndExpand },
nameLabel
}
},
...
dismissButton
}
};
}

async void OnDismissButtonClicked (object sender, EventArgs args)


{
await Navigation.PopModalAsync ();
}
}

Después, una serie de controles Label muestran los datos en la página.


Para más información sobre el enlace de datos, consulte Data Binding Basics (Aspectos básicos del enlace de
datos).

Resumen
En este artículo se mostró cómo navegar a páginas modales. Una página modal anima a los usuarios a completar
una tarea autocontenida que no se puede abandonar mientras no se complete o se cancele la tarea.

Vínculos relacionados
Page Navigation (Navegación de páginas)
Modal (sample) (Modal [ejemplo])
PassingData (sample) (PassingData [ejemplo])
Xamarin.Forms Shell
18/12/2020 • 3 minutes to read • Edit Online

Introducción
Xamarin.Forms Shell reduce la complejidad del desarrollo de aplicaciones móviles al proporcionar las
características fundamentales que requieren la mayoría de las aplicaciones móviles. Esto incluye una experiencia
de usuario de navegación común, un esquema de navegación basado en URI y un controlador de búsqueda
integrada.

Creación de una aplicación de Xamarin.Forms Shell


El proceso para crear una aplicación de Xamarin.Forms Shell consiste en crear un archivo XAML que sirva de
subclase de la clase Shell , establecer la propiedad MainPage de la clase App de la aplicación en el objeto
Shell con subclases y, después, describir la jerarquía visual de la aplicación en la clase Shell con subclases.

Control flotante
El control flotante es el menú raíz de una aplicación de Shell y es accesible por medio de un icono o al deslizar el
dedo desde el lado de la pantalla. El control flotante consta de un encabezado opcional, elementos de control
flotante y elementos de menú opcionales.

Pestañas
Después de un control flotante, el siguiente nivel de navegación en una aplicación de Shell es la barra de
pestañas de la parte inferior. Como alternativa, el modelo de navegación para una aplicación puede comenzar
con pestañas en la parte inferior y no usar un control flotante. En ambos casos, cuando una pestaña inferior
contiene más de una página, las páginas son navegables mediante las pestañas principales.

Configuración de la página
La clase Shell define las propiedades adjuntas que se pueden usar para configurar la apariencia de las páginas
en las aplicaciones de Xamarin.Forms Shell. Esto incluye establecer los colores de la página, deshabilitar la barra
de navegación y la barra de pestañas y mostrar las vistas en la barra de navegación.

Navegación
Las aplicaciones de Shell pueden usar un esquema de navegación basado en URI que emplea rutas para navegar
a cualquier página de la aplicación, sin tener que seguir una jerarquía de navegación establecida.

Búsqueda
Las aplicaciones de Shell pueden usar la funcionalidad de búsqueda integrada que se proporciona en un cuadro
de búsqueda que se puede agregar a la parte superior de cada página.

Ciclo de vida
Las aplicaciones del shell respetan el ciclo de vida de Xamarin.Forms; se genera un evento Appearing si una
página está a punto de aparecer en la pantalla y se genera un evento Disappearing si una página está a punto
de desaparecer de la pantalla.
Representadores personalizados
Las aplicaciones de Shell son muy personalizables mediante las propiedades y los métodos que exponen las
distintas clases de Shell. Sin embargo, también es posible crear a un representador personalizado de Shell
cuando se requieren personalizaciones más sofisticadas específicas de la plataforma.
Introducción a Xamarin.Forms Shell
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
Xamarin.Forms Shell reduce la complejidad del desarrollo de aplicaciones móviles al proporcionar las
características fundamentales que requieren la mayoría de aplicaciones móviles, como por ejemplo:
Un único lugar para describir la jerarquía visual de una aplicación.
Una interfaz de usuario de navegación común.
Un esquema de navegación basado en URI que permite la navegación a cualquier página de la aplicación.
Un controlador de búsqueda integrado.
Además, las aplicaciones de Shell se benefician de una mayor velocidad de representación y un consumo reducido
de memoria.

IMPORTANT
Las aplicaciones existentes pueden adoptar Shell y aprovechar inmediatamente las mejoras de navegación, rendimiento y
extensibilidad.

Compatibilidad con la plataforma


Xamarin.Forms Shell está totalmente disponible en iOS y Android, pero solo parcialmente disponible en la
Plataforma universal de Windows (UWP). Además, Shell es actualmente experimental en UWP y solo se puede usar
agregando la siguiente línea de código a la clase App en el proyecto de UWP, antes de llamar a Forms.Init :

global::Xamarin.Forms.Forms.SetFlags("Shell_UWP_Experimental");

Para obtener más información sobre el estado de Shell en UWP, vea el Panel de proyectos de Shell de
Xamarin.Forms en github.com.

Experiencia de navegación de Shell


Shell ofrece una experiencia de navegación bien fundamentada basada en controles flotantes y pestañas. El nivel
superior del panel de navegación en una aplicación de Shell es un control flotante o una barra de pestañas inferior,
según los requisitos de navegación de la aplicación. El ejemplo siguiente muestra una aplicación donde el nivel
superior de navegación es un control flotante:
Al seleccionar un elemento de control flotante, se selecciona y muestra la pestaña inferior que representa el
elemento:

NOTE
Cuando el control flotante no está abierto, la barra de pestañas inferior se puede considerar el nivel superior de navegación
en la aplicación.

Cada pestaña muestra un objeto ContentPage . Sin embargo, si una pestaña inferior contiene más de una página,
las páginas son navegables mediante la barra de pestañas superior:
Dentro de cada pestaña, se puede navegar a objetos adicionales ContentPage :

Vínculos relacionados
Xaminals (ejemplo)
Creación de una aplicación de :::no-
loc(Xamarin.Forms)::: Shell
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
El proceso para crear una aplicación de :::no-loc(Xamarin.Forms)::: Shell es el siguiente:
1. Cree una aplicación de :::no-loc(Xamarin.Forms)::: o cargue una aplicación existente que quiera convertir en una
aplicación de Shell.
2. Agregue un archivo XAML al proyecto de código compartido que sirva de subclase de la clase Shell . Para
obtener más información, consulte Subclass the Shell class (Establecimiento de subclases en la clase Shell).
3. Establezca la propiedad MainPage de la clase App de la aplicación en el objeto Shell con subclases. Para
obtener más información, consulte Bootstrap the Shell application (Arranque de la aplicación Shell).
4. Describa la jerarquía visual de la aplicación en la clase Shell con subclases. Para obtener más información,
consulte Describe the visual hierarchy of the application (Descripción de la jerarquía visual de la aplicación).

Creación de subclases de la clase de Shell


El primer paso para crear una aplicación de :::no-loc(Xamarin.Forms)::: Shell es agregar un archivo XAML al
proyecto de código compartido que crea subclases en la clase Shell . Este archivo puede tener cualquier nombre,
pero se recomienda AppShell . El siguiente código de ejemplo muestra un archivo AppShell.xaml recién creado:

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xaminals.AppShell">

</Shell>

En el ejemplo siguiente se muestra el código subyacente, AppShell.xaml.cs :

using :::no-loc(Xamarin.Forms):::;

namespace Xaminals
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}

Arranque de una aplicación de Shell


Después de crear el archivo XAML que crea subclases en el objeto Shell , la propiedad MainPage de la clase App
debe establecerse en el objeto Shell con subclases:
namespace Xaminals
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
...
}
}

En este ejemplo, la clase AppShell es el archivo XAML que se deriva de la clase Shell .

NOTE
Mientras se compila una aplicación de Shell en blanco, su ejecución dará como resultado la generación de
InvalidOperationException .

Descripción de la jerarquía visual de la aplicación


El último paso para crear una aplicación de :::no-loc(Xamarin.Forms)::: Shell consiste en describir la jerarquía visual
de la aplicación en la clase Shell con subclases. Una clase Shell con subclases consta de tres objetos jerárquicos
principales:
FlyoutItem o TabBar . Un FlyoutItem representa uno o varios elementos en el control flotante y debe usarse
cuando el modelo de navegación de la aplicación incluye un control flotante. Un TabBar representa la barra de
pestañas de la parte inferior y debe usarse cuando el patrón de navegación de la aplicación comienza con
pestañas en la parte inferior. Cada objeto FlyoutItem o TabBar es un elemento secundario del objeto Shell .
Tab , que representa contenido agrupado, navegable mediante las pestañas inferiores. Cada objeto Tab es un
elemento secundario de un objeto FlyoutItem o un objeto TabBar .
ShellContent , que representa el objeto ContentPage en la aplicación. Cada objeto ShellContent es un
elemento secundario de un objeto Tab . Cuando hay más de un objeto ShellContent en un objeto Tab , los
objetos serán navegables mediante las pestañas superiores.
Ninguno de estos objetos representa ninguna interfaz de usuario, sino más bien la organización de la jerarquía
visual de la aplicación. Shell tomará estos elementos y generará la interfaz de usuario de navegación del contenido.
El siguiente XAML muestra un ejemplo de una clase Shell con subclases:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
...
<FlyoutItem Title="Animals"
FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png">
<views:CatsPage />
</ShellContent>
<ShellContent Title="Dogs"
Icon="dog.png">
<views:DogsPage />
</ShellContent>
</Tab>
<ShellContent Title="Monkeys"
Icon="monkey.png">
<views:MonkeysPage />
</ShellContent>
<ShellContent Title="Elephants"
Icon="elephant.png">
<views:ElephantsPage />
</ShellContent>
<ShellContent Title="Bears"
Icon="bear.png">
<views:BearsPage />
</ShellContent>
</FlyoutItem>
...
</Shell>

Cuando se ejecuta, este XAML muestra el objeto CatsPage , porque es el primer elemento de contenido declarado
en la clase Shell con subclases:

Al presionar el icono de tres barras, o al deslizar el dedo desde la izquierda, se muestra el control flotante:
IMPORTANT
En una aplicación de Shell, cada ContentPage que es un elemento secundario de un objeto ShellContent se crea durante
el inicio de la aplicación. Agregar objetos ShellContent adicionales mediante esta estrategia dará lugar a la creación de
páginas adicionales durante el inicio de la aplicación, lo que puede conducir a una experiencia de inicio deficiente. Sin
embargo, Shell también es capaz de crear páginas a petición, en respuesta a la navegación. Para más información, consulte
Carga eficiente de páginas en la guía Pestañas de :::no-loc(Xamarin.Forms)::: Shell.

Vínculos relacionados
Xaminals (ejemplo)
Control flotante de :::no-loc(Xamarin.Forms)::: Shell
18/12/2020 • 30 minutes to read • Edit Online

Descargar el ejemplo
El control flotante es el menú raíz de una aplicación de Shell y es accesible por medio de un icono o al deslizar el
dedo desde el lado de la pantalla. El control flotante consta de un encabezado opcional, elementos de control
flotante y elementos de menú opcionales:

Si es necesario, el color de fondo del control flotante se puede establecer Color mediante la propiedad enlazable
Shell.FlyoutBackgroundColor . Esta propiedad también se puede establecer con una hoja de estilo CSS. Para obtener
más información, consulte Propiedades específicas de :::no-loc(Xamarin.Forms)::: Shell.

Icono de control flotante


De forma predeterminada, las aplicaciones de Shell tienen un icono de tres barras que, cuando se presiona, abre el
control flotante. Este icono se puede cambiar si se establece la propiedad enlazable Shell.FlyoutIcon , de tipo
ImageSource , en un icono adecuado:

<Shell ...
FlyoutIcon="flyouticon.png">
...
</Shell>

Comportamiento del control flotante


Al control flotante se tiene acceso mediante el icono de tres barras o al deslizar el dedo desde el lado de la pantalla.
Sin embargo, este comportamiento se puede cambiar si se establece la propiedad adjunta Shell.FlyoutBehavior
en uno de los miembros de la enumeración FlyoutBehavior :
Disabled : indica que el usuario no puede abrir el control flotante.
Flyout : indica que el usuario puede abrir y cerrar el control flotante. Este es el valor predeterminado de la
propiedad FlyoutBehavior .
Locked : indica que el usuario no puede cerrar el control flotante, y que este no solapa el contenido.

En el siguiente ejemplo se muestra cómo deshabilitar el control flotante.

<Shell ...
FlyoutBehavior="Disabled">
...
</Shell>

NOTE
La propiedad adjunta FlyoutBehavior se puede establecer en los objetos Shell , FlyoutItem , ShellContent y en
objetos de página, para invalidar el comportamiento predeterminado del control flotante.

Además, el control flotante se puede abrir y cerrar mediante programación si se establece la propiedad enlazable
Shell.FlyoutIsPresented en un valor boolean que indica si el control flotante es visible actualmente:

Shell.Current.FlyoutIsPresented = false;

Encabezado de control flotante


El encabezado de control flotante es el contenido que aparece opcionalmente en la parte superior del control
flotante y su apariencia se define mediante un elemento object que se puede establecer mediante el valor de la
propiedad Shell.FlyoutHeader :

<Shell.FlyoutHeader>
<controls:FlyoutHeader />
</Shell.FlyoutHeader>

El tipo FlyoutHeader se muestra en el ejemplo siguiente:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xaminals.Controls.FlyoutHeader"
HeightRequest="200">
<Grid BackgroundColor="Black">
<Image Aspect="AspectFill"
Source="xamarinstore.jpg"
Opacity="0.6" />
<Label Text="Animals"
TextColor="White"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</ContentView>

El resultado es el siguiente encabezado de control flotante:


Como alternativa, la apariencia del encabezado de control flotante se puede definir mediante el establecimiento de
la propiedad Shell.FlyoutHeaderTemplate en un objeto DataTemplate :

<Shell.FlyoutHeaderTemplate>
<DataTemplate>
<Grid BackgroundColor="Black"
HeightRequest="200">
<Image Aspect="AspectFill"
Source="xamarinstore.jpg"
Opacity="0.6" />
<Label Text="Animals"
TextColor="White"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.FlyoutHeaderTemplate>

De forma predeterminada, el encabezado de control flotante se corregirá en el control flotante mientras el


contenido que va a continuación se desplazará si hay suficientes elementos. Sin embargo, este comportamiento se
puede cambiar si se establece la propiedad enlazable Shell.FlyoutHeaderBehavior en uno de los miembros de la
enumeración FlyoutHeaderBehavior :
Default : indica que se usará el comportamiento predeterminado para la plataforma. Este es el valor
predeterminado de la propiedad FlyoutHeaderBehavior .
Fixed : indica que el encabezado de control flotante permanece sin cambios y visible en todo momento.
Scroll : indica que el encabezado de control flotante se desplaza fuera de la vista cuando el usuario desplaza
los elementos.
CollapseOnScroll : indica que el encabezado de control flotante se contrae solo en un título, a medida que el
usuario desplaza los elementos.
En el ejemplo siguiente se muestra cómo contraer el encabezado de control flotante a medida que el usuario se
desplaza:

<Shell ...
FlyoutHeaderBehavior="CollapseOnScroll">
...
</Shell>

Imagen de fondo del control flotante


El control flotante puede tener una imagen de fondo opcional, que aparece debajo del encabezado del control
flotante y detrás de los elementos del control flotante y los elementos del menú. La imagen de fondo se puede
especificar si se establece la propiedad enlazable FlyoutBackgroundImage , de tipo ImageSource , en un archivo, un
recurso incrustado, un URI o un flujo.
La relación de aspecto de la imagen de fondo se puede configurar si se establece la propiedad enlazable
FlyoutBackgroundImageAspect , de tipo Aspect , en uno de los miembros de enumeración de Aspect :
AspectFill : recorta la imagen para que rellene el área de visualización y conserve la relación de aspecto.
AspectFit : aplica el formato letterbox a la imagen si es necesario para que la imagen quepa en el área de
visualización, con un espacio en blanco agregado a la parte superior o inferior o a los laterales, en función de si
la imagen es ancha o alta.
Fill : ajusta la imagen para rellenar completa y exactamente el área de visualización. Esto puede producir que
la imagen se distorsione.
De forma predeterminada, la propiedad FlyoutBackgroundImageAspect se establecerá en AspectFit .
En el ejemplo siguiente se muestra cómo configurar estas propiedades:

<Shell ...
FlyoutBackgroundImage="photo.jpg"
FlyoutBackgroundImageAspect="AspectFill">
...
</Shell>

Esto da como resultado una imagen de fondo en el control flotante:

Elementos del control flotante


Cuando el modelo de navegación de una aplicación incluye un control flotante, el objeto Shell con subclases debe
contener uno o varios objetos FlyoutItem , donde cada objeto FlyoutItem representa un elemento del control
flotante. Cada objeto FlyoutItem debe ser un elemento secundario del objeto Shell .
En el ejemplo siguiente se crea un control flotante que contiene un encabezado y dos elementos:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<Shell.FlyoutHeader>
<controls:FlyoutHeader />
</Shell.FlyoutHeader>
<FlyoutItem Title="Cats"
Icon="cat.png">
<Tab>
<ShellContent>
<views:CatsPage />
</ShellContent>
</Tab>
</FlyoutItem>
<FlyoutItem Title="Dogs"
Icon="dog.png">
<Tab>
<ShellContent>
<views:DogsPage />
</ShellContent>
</Tab>
</FlyoutItem>
</Shell>

En este ejemplo, solo se puede acceder a cada objeto ContentPage mediante elementos de control flotante:

NOTE
Cuando no existe un encabezado de control flotante, aparecen elementos de control flotante en la parte superior del control
flotante. En caso contrario, aparecen debajo del encabezado de control flotante.

Shell tiene operadores de conversión implícita que permiten simplificar la jerarquía visual de Shell, sin introducir
vistas adicionales en el árbol visual. Esto es posible porque un objeto Shell con subclases solo puede contener
objetos FlyoutItem o un objeto TabBar , que solo pueden contener objetos Tab , que solo pueden contener
objetos ShellContent . Estos operadores de conversión implícita pueden usarse para quitar los objetos FlyoutItem ,
Tab y ShellContent del ejemplo anterior:

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<Shell.FlyoutHeader>
<controls:FlyoutHeader />
</Shell.FlyoutHeader>
<views:CatsPage IconImageSource="cat.png" />
<views:DogsPage IconImageSource="dog.png" />
</Shell>
Esta conversión implícita encapsula automáticamente cada objeto ContentPage en objetos ShellContent , que se
encapsulan en objetos Tab , que se encapsulan en objetos FlyoutItem .

IMPORTANT
En una aplicación de Shell, cada ContentPage que es un elemento secundario de un objeto ShellContent se crea durante
el inicio de la aplicación. Agregar objetos ShellContent adicionales mediante esta estrategia dará lugar a la creación de
páginas adicionales durante el inicio de la aplicación, lo que puede conducir a una experiencia de inicio deficiente. Sin
embargo, Shell también es capaz de crear páginas a petición, en respuesta a la navegación. Para más información, consulte
Carga eficiente de páginas en la guía Pestañas de :::no-loc(Xamarin.Forms)::: Shell.

Clase FlyoutItem
La clase FlyoutItem incluye las siguientes propiedades que controlan la apariencia y comportamiento del
elemento del control flotante:
FlyoutDisplayOptions , de tipo FlyoutDisplayOptions , define cómo se muestran el elemento y sus elementos
secundarios en el control flotante. El valor predeterminado es AsSingleItem .
CurrentItem , de tipo Tab , el elemento seleccionado.
Items , de tipo IList<Tab> , define todas las pestañas dentro de FlyoutItem .
FlyoutIcon , de tipo ImageSource , el icono que se usará para el elemento. Si esta propiedad no está establecida,
se volverá a usar el valor de la propiedad Icon .
Icon , de tipo ImageSource , define el icono que se mostrará en las partes del cromo que no son el control
flotante.
IsChecked , de tipo boolean , define si el elemento está actualmente resaltado en la ventana flotante.
IsEnabled , de tipo boolean , define si el elemento es seleccionable en el cromo.
IsTabStop , de tipo bool , indica si se incluye un objeto FlyoutItem en la navegación entre pestañas. Su valor
predeterminado es true , y cuando su valor es false , la infraestructura de navegación entre pestañas omite el
objeto FlyoutItem , independientemente de si se ha definido un objeto TabIndex .
IsVisible , de tipo bool , indica si FlyoutItem está oculto en el menú de control flotante. Su valor
predeterminado es true .
TabIndex , de tipo int , indica el orden en que los objetos FlyoutItem reciben el foco cuando el usuario navega
por los elementos presionando la tecla de tabulación. El valor predeterminado de la propiedad es 0.
Title , de tipo string , el título que se mostrará en la interfaz de usuario.
Route , de tipo string , la cadena usada para abordar el elemento.

Todas estas propiedades, excepto la propiedad Route , están respaldadas por objetos BindableProperty , lo que
significa que las propiedades pueden ser destinos de los enlaces de datos.

NOTE
Todos los objetos FlyoutItem del objeto Shell en subclase se agregan a la colección Shell.Items , que define la lista de
elementos que se mostrarán en el control flotante.

Además, la clase FlyoutItem expone los siguientes métodos reemplazables:


OnTabIndexPropertyChanged , que se llama cada vez que cambia la propiedad TabIndex .
OnTabStopPropertyChanged , que se llama cada vez que cambia la propiedad IsTabStop .
TabIndexDefaultValueCreator , devuelve un elemento int , y se llama para establecer el valor predeterminado
de la propiedad TabIndex .
TabStopDefaultValueCreator , devuelve un elemento bool , y se llama para establecer el valor predeterminado
de la propiedad TabStop .

Fondo del control flotante


El fondo del control flotante, que es la apariencia de la superposición del control flotante, se puede especificar
estableciendo la propiedad adjunta Shell.FlyoutBackdrop en Brush :

<Shell ...
FlyoutBackdrop="Silver">
...
</Shell>

En este ejemplo, el fondo del control flotante se pinta con SolidColorBrush plata.

IMPORTANT
La propiedad adjunta FlyoutBackdrop se puede establecer en cualquier elemento del shell, pero solo se aplicará cuando se
establezca en objetos Shell , FlyoutItem o TabBar .

En el ejemplo siguiente se muestra cómo establecer el fondo del control flotante en FlyoutItem en
LinearGradientBrush :

<Shell ...>
<FlyoutItem ...>
<Shell.FlyoutBackdrop>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<GradientStop Color="#8A2387"
Offset="0.1" />
<GradientStop Color="#E94057"
Offset="0.6" />
<GradientStop Color="#F27121"
Offset="1.0" />
</LinearGradientBrush>
</Shell.FlyoutBackdrop>
...
</FlyoutItem>
...
</Shell>

Para más información acerca de los pinceles, vea Pinceles de :::no-loc(Xamarin.Forms):::.

Desplazamiento vertical de control flotante


De forma predeterminada, un control flotante se puede desplazar verticalmente cuando los elementos del control
flotante no caben en este. Este comportamiento se puede cambiar si se establece la propiedad enlazable
Shell.FlyoutVerticalScrollMode en uno de los miembros de la enumeración ScrollMode :

Disabled : indica que se deshabilitará el desplazamiento vertical.


Enabled indica que se habilitará el desplazamiento vertical.
Auto : indica que se habilitará el desplazamiento vertical si los elementos de control flotante no caben en el
control flotante. Este es el valor predeterminado de la propiedad Shell.FlyoutVerticalScrollMode .

En el siguiente ejemplo se muestra cómo deshabilitar el desplazamiento vertical:


<Shell ...
FlyoutVerticalScrollMode="Disabled"
...
</Shell>

Opciones de presentación de control flotante


La enumeración FlyoutDisplayOptions define los miembros siguientes:
AsSingleItem , indica que el elemento será visible como un único elemento.
AsMultipleItems , indica que el elemento y sus elementos secundarios serán visibles en el control flotante como
un grupo de elementos.
Al establecer la propiedad FlyoutItem.FlyoutDisplayOptions en AsMultipleItems , se creará un elemento de control
flotante para cada objeto Tab dentro de un objeto FlyoutItem :

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
FlyoutHeaderBehavior="CollapseOnScroll"
x:Class="Xaminals.AppShell">

<Shell.FlyoutHeader>
<controls:FlyoutHeader />
</Shell.FlyoutHeader>

<FlyoutItem Title="Animals"
FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png"
ContentTemplate="{DataTemplate views:CatsPage}" />
<ShellContent Title="Dogs"
Icon="dog.png"
ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
<ShellContent Title="Monkeys"
Icon="monkey.png"
ContentTemplate="{DataTemplate views:MonkeysPage}" />
<ShellContent Title="Elephants"
Icon="elephant.png"
ContentTemplate="{DataTemplate views:ElephantsPage}" />
<ShellContent Title="Bears"
Icon="bear.png"
ContentTemplate="{DataTemplate views:BearsPage}" />
</FlyoutItem>

<ShellContent Title="About"
Icon="info.png"
ContentTemplate="{DataTemplate views:AboutPage}" />
</Shell>

En este ejemplo, se crean elementos de control flotante para el objeto Tab , que es secundario del objeto
FlyoutItem , y el objeto ShellContent , que son elementos secundarios del objeto FlyoutItem . Esto ocurre porque
cada objeto ShellContent que es secundario del objeto FlyoutItem se encapsula automáticamente en un objeto
Tab . Además, se crea un elemento de control flotante para el objeto ShellContent final, que se encapsula en un
objeto Tab y, luego, en un objeto FlyoutItem .
El resultado son los siguientes elementos de control flotante:

Definición de la apariencia de FlyoutItem


Se puede personalizar la apariencia de cada FlyoutItem mediante el establecimiento de la propiedad adjunta
Shell.ItemTemplate en un objeto DataTemplate :

<Shell ...>
...
<Shell.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.8*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding FlyoutIcon}"
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Title}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.ItemTemplate>
</Shell>

En este ejemplo se muestra el título de cada objeto FlyoutItem en cursiva:

Como Shell.ItemTemplate es una propiedad adjunta, se pueden asociar otras plantillas a objetos FlyoutItem
específicos.
NOTE
Shell proporciona las propiedades Title y FlyoutIcon para el objeto BindingContext de ItemTemplate .

Shell incluye también tres clases de estilo, que se aplican automáticamente a objetos FlyoutItem . Para más
información, vea Clases de estilo FlyoutItem y MenuItem.
Plantilla predeterminada para objetos FlyoutItem
Aquí se muestra la plantilla DataTemplate predeterminada usada con cada FlyoutItem :

<DataTemplate x:Key="FlyoutTemplate">
<Grid x:Name="FlyoutItemLayout"
HeightRequest="{x:OnPlatform Android=50}"
ColumnSpacing="{x:OnPlatform UWP=0}"
RowSpacing="{x:OnPlatform UWP=0}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{x:OnPlatform Android=#F2F2F2, iOS=#F2F2F2}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{x:OnPlatform Android=54, iOS=50, UWP=Auto}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="FlyoutItemImage"
Source="{Binding FlyoutIcon}"
VerticalOptions="Center"
HorizontalOptions="{x:OnPlatform Default=Center, UWP=Start}"
HeightRequest="{x:OnPlatform Android=24, iOS=22, UWP=16}"
WidthRequest="{x:OnPlatform Android=24, iOS=22, UWP=16}">
<Image.Margin>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="UWP"
Value="12,0,12,0" />
</OnPlatform.Platforms>
</OnPlatform>
</Image.Margin>
</Image>
<Label x:Name="FlyoutItemLabel"
Grid.Column="1"
Text="{Binding Title}"
FontSize="{x:OnPlatform Android=14, iOS=Small}"
HorizontalOptions="{x:OnPlatform UWP=Start}"
HorizontalTextAlignment="{x:OnPlatform UWP=Start}"
FontAttributes="{x:OnPlatform iOS=Bold}"
VerticalTextAlignment="Center">
<Label.TextColor>
<OnPlatform x:TypeArguments="Color">
<OnPlatform.Platforms>
<On Platform="Android"
Value="#D2000000" />
</OnPlatform.Platforms>
</OnPlatform>
</Label.TextColor>
<Label.Margin>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="Android"
Value="20, 0, 0, 0" />
</OnPlatform.Platforms>
</OnPlatform>
</Label.Margin>
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<OnPlatform.Platforms>
<On Platform="Android"
Value="sans-serif-medium" />
</OnPlatform.Platforms>
</OnPlatform>
</Label.FontFamily>
</Label>
</Grid>
</DataTemplate>

Esta plantilla se puede usar como base para realizar modificaciones en el diseño de control flotante existente y,
asimismo, muestra los estados visuales que se implementan para los elementos de control flotante.
Además, los elementos Grid , Image y Label tienen todos valores x:Name y, por tanto, pueden ser destinos en
Visual State Manager. Para más información, vea Establecer el estado en varios elementos.

NOTE
Esta misma plantilla también se puede usar con los objetos MenuItem .

Orden de tabulación de FlyoutItem


De forma predeterminada, el orden de tabulación de los objetos FlyoutItem es el mismo orden en que se
enumeran en XAML o se agregan mediante programación a una colección secundaria. Este es el orden en que se
navegará por los objetos FlyoutItem con un teclado y, con frecuencia, este orden predeterminado es el mejor
orden posible.
Se puede cambiar el orden de tabulación predeterminado mediante el establecimiento de la propiedad
FlyoutItem.TabIndex , que indica el orden en que los objetos FlyoutItem reciben el foco cuando el usuario navega
por los elementos presionando la tecla de tabulación. El valor predeterminado de la propiedad es 0, y se puede
establecer en cualquier valor int .
Las reglas siguientes aplican cuando se usa el orden de tabulación predeterminado, o cuando se establece la
propiedad TabIndex :
Los objetos FlyoutItem con un objeto TabIndex igual a 0 se agregan al orden de tabulación según su orden de
declaración en XAML o colecciones secundarias.
Los objetos FlyoutItem con un valor de TabIndex mayor que 0 se agregan al orden de tabulación en función
de su valor TabIndex .
Los objetos FlyoutItem con un valor de TabIndex menor que 0 se agregan al orden de tabulación y aparecen
antes que cualquier valor 0.
Los conflictos relacionados con TabIndex se resuelven por orden de declaración.

Tras definirse un orden de tabulación, al presionar la tecla TAB se recorrerá cíclicamente el foco a través de objetos
FlyoutItem en orden de TabIndex ascendente, con una encapsulación alrededor del principio una vez alcanzado el
control final.
Además de establecer el orden de tabulación de los objetos FlyoutItem , puede ser necesario excluir algunos
objetos de dicho orden. Para ello, se puede usar la propiedad FlyoutItem.IsTabStop , que indica si un objeto
FlyoutItem se incluye en la navegación entre pestañas. Su valor predeterminado es true , y cuando su valor es
false , la infraestructura de navegación entre pestañas omite el objeto FlyoutItem , independientemente de si se
ha definido un objeto TabIndex .

Establecimiento del objeto FlyoutItem actual


La clase Shell tiene una propiedad enlazable llamada CurrentItem , de tipo FlyoutItem , que representa
actualmente el objeto FlyoutItem seleccionado. La primera vez que se ejecuta una aplicación de Shell, esta
propiedad se establecerá en el primer objeto FlyoutItem del objeto Shell en subclase. Sin embargo, la propiedad
se puede establecer en otro objeto FlyoutItem , como se muestra en el ejemplo siguiente:

<Shell ...
CurrentItem="{x:Reference aboutItem}">
<FlyoutItem Title="Animals"
FlyoutDisplayOptions="AsMultipleItems">
...
</FlyoutItem>
<ShellContent x:Name="aboutItem"
Title="About"
Icon="info.png"
ContentTemplate="{DataTemplate views:AboutPage}" />
</Shell>

Este código establece el objeto ShellContent llamado aboutItem como la propiedad CurrentItem , que hace que se
muestre. En este ejemplo, se usa una conversión implícita para encapsular el objeto ShellContent en un objeto
Tab , que se encapsula en un objeto FlyoutItem .

El código C# equivalente, dado un objeto ShellContent denominado aboutItem , es el siguiente:

CurrentItem = aboutItem;

En este ejemplo, la propiedad CurrentItem está establecida en la clase Shell de la que se crean subclases. Como
alternativa, la propiedad CurrentItem se puede establecer en cualquier clase a través de la propiedad estática
Shell.Current :

Shell.Current.CurrentItem = aboutItem;

Elementos de menú
Los elementos del menú se pueden agregar opcionalmente a la ventana flotante, y cada elemento de menú se
representa mediante un objeto MenuItem . La posición de los objetos MenuItem en el control flotante depende de su
orden de declaración en la jerarquía visual del Shell. Por lo tanto, cualquier objeto MenuItem declarado antes de los
objetos FlyoutItem aparecerá en la parte superior del control flotante, y cualquier objeto MenuItem declarado
después de los objetos FlyoutItem aparecerá en la parte inferior del control flotante.

NOTE
La clase MenuItem tiene un evento Clicked y una propiedad Command . Por lo tanto, los objetos MenuItem permiten
escenarios que ejecutan una acción en respuesta al elemento MenuItem que se pulsa. Estos escenarios incluyen realizar la
navegación y abrir un explorador web en una página web específica.

Se pueden agregar objetos MenuItem al control flotante, tal como se muestra en el ejemplo siguiente:
<Shell ...>
...
<MenuItem Text="Random"
IconImageSource="random.png"
Command="{Binding RandomPageCommand}" />
<MenuItem Text="Help"
IconImageSource="help.png"
Command="{Binding HelpCommand}"
CommandParameter="https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell" />
</Shell>

Este código agrega dos objetos MenuItem al control flotante, debajo de todos los elementos del control flotante:

El primer objeto MenuItem ejecuta un elemento ICommand llamado RandomPageCommand , que lleva a una página
aleatoria de la aplicación. El segundo objeto MenuItem ejecuta un elemento ICommand llamado HelpCommand , que
abre la dirección URL especificada por la propiedad CommandParameter en un explorador web.

NOTE
El elemento BindingContext de cada MenuItem se hereda del objeto Shell en subclase.

Definición de la apariencia de MenuItem


Se puede personalizar la apariencia de cada MenuItem mediante el establecimiento de la propiedad adjunta
Shell.MenuItemTemplate en un objeto DataTemplate :
<Shell ...>
<Shell.MenuItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.8*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Icon}"
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Text}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.MenuItemTemplate>
...
<MenuItem Text="Random"
IconImageSource="random.png"
Command="{Binding RandomPageCommand}" />
<MenuItem Text="Help"
IconImageSource="help.png"
Command="{Binding HelpCommand}"
CommandParameter="https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell" />
</Shell>

Este ejemplo adjunta el nivel de Shell MenuItemTemplate a cada objeto MenuItem , mostrando el título de cada
objeto MenuItem en cursiva:

NOTE
Shell proporciona las propiedades Text y IconImageSource para el elemento BindingContext del objeto
MenuItemTemplate . También puede usar Title en lugar de Text y Icon en lugar de IconImageSource , lo que le
permitirá volver a usar la misma plantilla para los elementos de menú y los de control flotante

Dado que Shell.MenuItemTemplate es una propiedad adjunta, se pueden asociar diferentes plantillas a objetos
MenuItem específicos:
<Shell ...>
<Shell.MenuItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.8*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Icon}"
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Text}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.MenuItemTemplate>
...
<MenuItem Text="Random"
IconImageSource="random.png"
Command="{Binding RandomPageCommand}" />
<MenuItem Text="Help"
Icon="help.png"
Command="{Binding HelpCommand}"
CommandParameter="https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell">
<Shell.MenuItemTemplate>
<DataTemplate>
...
</DataTemplate>
</Shell.MenuItemTemplate>
</MenuItem>
</Shell>

Este ejemplo adjunta el MenuItemTemplate de nivel de Shell al primer objeto MenuItem y adjunta el
MenuItemTemplate alineado al segundo MenuItem .

NOTE
La plantilla predeterminada para objetos FlyoutItem también se puede usar con objetos MenuItem . Para más información,
vea Plantilla predeterminada para objetos FlyoutItem.

Clases de estilo de FlyoutItem y MenuItem


Shell incluye tres clases de estilo, que se aplican automáticamente a los objetos FlyoutItem y MenuItem . Los
nombres de esas clases de estilo son:
FlyoutItemLabelStyle
FlyoutItemImageStyle
FlyoutItemLayoutStyle

En el siguiente código XAML se muestra un ejemplo de definición de estilos para estas clases de estilo:
<Style TargetType="Label"
Class="FlyoutItemLabelStyle">
<Setter Property="TextColor"
Value="Black" />
<Setter Property="HeightRequest"
Value="100" />
</Style>

<Style TargetType="Image"
Class="FlyoutItemImageStyle">
<Setter Property="Aspect"
Value="Fill" />
</Style>

<Style TargetType="Layout"
Class="FlyoutItemLayoutStyle"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="Teal" />
</Style>

Estos estilos se aplicarán automáticamente a objetos FlyoutItem y MenuItem , sin tener que establecer sus
propiedades StyleClass en los nombres de clase de estilo.
Además, se pueden definir y aplicar clases de estilo personalizadas a objetos FlyoutItem y MenuItem . Para obtener
más información sobre las clases de estilo, vea Clases de estilo de :::no-loc(Xamarin.Forms):::.

Vínculos relacionados
Xaminals (ejemplo)
Clases de estilo de :::no-loc(Xamarin.Forms):::
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
Pinceles de :::no-loc(Xamarin.Forms):::
Pestañas de :::no-loc(Xamarin.Forms)::: Shell
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
Cuando el modelo de navegación para una aplicación incluye un control flotante, el siguiente nivel de navegación
de la aplicación es la barra de pestañas de la parte inferior. Además, cuando se cierra el control flotante, la barra
de pestañas inferior se puede considerar el nivel superior de navegación.
Como alternativa, el modelo de navegación para una aplicación puede comenzar con pestañas en la parte inferior
y no usar un control flotante. En este escenario, el elemento secundario del objeto Shell debe ser un objeto
TabBar , que representa la barra de pestañas de la parte inferior.

NOTE
El tipo TabBar deshabilita el control flotante.

Cada objeto FlyoutItem o TabBar puede contener uno o varios objetos Tab , donde cada objeto Tab representa
una pestaña en la barra de pestañas inferior. Cada objeto Tab puede contener uno o varios objetos ShellContent ,
y cada objeto ShellContent mostrará un único objeto ContentPage . Cuando hay más de un objeto ShellContent
en un objeto Tab , los objetos ContentPage serán navegables mediante las pestañas superiores.
Dentro de cada objeto ContentPage , se puede navegar a objetos ContentPage adicionales. Para obtener más
información sobre la navegación, consulte Navegación en :::no-loc(Xamarin.Forms)::: Shell.

Aplicación de página única


La aplicación más sencilla de Shell es una aplicación de página única, que se puede crear mediante la adición de
un único objeto Tab a un objeto TabBar . Dentro del objeto Tab , un objeto ShellContent se debe establecer en
un objeto ContentPage :

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab>
<ShellContent>
<views:CatsPage />
</ShellContent>
</Tab>
</TabBar>
</Shell>

Este ejemplo de código tiene como resultado la siguiente aplicación de página única:
NOTE
Si es necesario, la barra de navegación se puede ocultar mediante el establecimiento de la propiedad adjunta
Shell.NavBarIsVisible en false en el objeto ContentPage .

Shell tiene operadores de conversión implícita que permiten simplificar la jerarquía visual de Shell, sin introducir
vistas adicionales en el árbol visual. Esto es posible porque un objeto Shell con subclases solo puede contener
objetos FlyoutItem o un objeto TabBar , que solo pueden contener objetos Tab , que solo pueden contener
objetos ShellContent . Estos operadores de conversión implícita pueden usarse para quitar los objetos TabBar ,
Tab y ShellContent del ejemplo anterior:

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell"
FlyoutBehavior="Disabled">
<views:CatsPage />
</Shell>

Esta conversión implícita encapsula automáticamente el objeto ContentPage en un objeto ShellContent , que se
encapsula en un objeto Tab , que se encapsula en un objeto FlyoutItem . Un control flotante no es necesario en
una aplicación de página única y, por lo tanto, la propiedad Shell.FlyoutBehavior está establecida en Disabled .

IMPORTANT
En una aplicación de Shell, cada ContentPage que es un elemento secundario de un objeto ShellContent se crea
durante el inicio de la aplicación. Agregar objetos ShellContent adicionales mediante esta estrategia dará lugar a la
creación de páginas adicionales durante el inicio de la aplicación, lo que puede conducir a una experiencia de inicio deficiente.
Sin embargo, Shell también es capaz de crear páginas a petición, en respuesta a la navegación. Para más información,
consulte Carga eficiente de páginas.

Pestañas inferiores
Los objetos Tab se representan como pestañas inferiores, siempre y cuando haya varios objetos Tab en un
único objeto TabBar :
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Cats"
Icon="cat.png">
<ShellContent>
<views:CatsPage />
</ShellContent>
</Tab>
<Tab Title="Dogs"
Icon="dog.png">
<ShellContent>
<views:DogsPage />
</ShellContent>
</Tab>
</TabBar>
</Shell>

Los títulos e iconos de pestaña se establecen en cada objeto Tab , y se muestran en las pestañas inferiores:

Si hay más de cinco pestañas, aparece una pestaña Más que puede usarse para acceder a las demás pestañas:

Como alternativa, se pueden usar operadores de conversión implícita de Shell para quitar los objetos
ShellContent y Tab del ejemplo anterior:

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<views:CatsPage IconImageSource="cat.png" />
<views:DogsPage IconImageSource="dog.png" />
</TabBar>
</Shell>

Esta conversión implícita encapsula automáticamente cada objeto ContentPage en un objeto ShellContent ,
ambos de los cuales se encapsulan en un objeto Tab .

IMPORTANT
En una aplicación de Shell, cada ContentPage que es un elemento secundario de un objeto ShellContent se crea
durante el inicio de la aplicación. Agregar objetos ShellContent adicionales mediante esta estrategia dará lugar a la
creación de páginas adicionales durante el inicio de la aplicación, lo que puede conducir a una experiencia de inicio deficiente.
Sin embargo, Shell también es capaz de crear páginas a petición, en respuesta a la navegación. Para más información,
consulte Carga eficiente de páginas.

Clase Tab
La clase Tab incluye las siguientes propiedades que controlan la apariencia y el comportamiento de las pestañas:
CurrentItem , de tipo , el elemento seleccionado.
ShellContent
FlyoutDisplayOptions , de tipo FlyoutDisplayOptions , define cómo se muestran el elemento y sus elementos
secundarios en el control flotante. El valor predeterminado es AsSingleItem .
FlyoutIcon , de tipo ImageSource , define el icono que se mostrará en el control flotante.
Icon , de tipo ImageSource , define el icono que se mostrará en partes del cromo que no son la ventana
flotante.
IsChecked , de tipo boolean , define si el elemento está actualmente resaltado en la ventana flotante.
IsEnabled , de tipo boolean , define si el elemento es seleccionable en el cromo.
IsTabStop , de tipo bool , indica si se incluye un objeto Tab en la navegación entre pestañas. Su valor
predeterminado es true , y cuando su valor es false , la infraestructura de navegación entre pestañas omite
el objeto Tab , independientemente de si se ha definido un objeto TabIndex .
Items , de tipo IList<ShellContent> , define todo el contenido dentro de un objeto Tab .
TabIndex , de tipo int , indica el orden en que los objetos Tab reciben el foco cuando el usuario navega por
los elementos presionando la tecla de tabulación. El valor predeterminado de la propiedad es 0.
Title , de tipo string , el título que se mostrará en la pestaña en la interfaz de usuario.

Contenido de Shell
El elemento secundario de cada objeto Tab es un objeto ShellContent cuya propiedad Content está establecida
en ContentPage :

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Cats"
Icon="cat.png">
<ShellContent>
<views:CatsPage />
</ShellContent>
</Tab>
<Tab Title="Dogs"
Icon="dog.png">
<ShellContent>
<views:DogsPage />
</ShellContent>
</Tab>
</TabBar>
</Shell>

Dentro de cada objeto ContentPage , se puede navegar a objetos ContentPage adicionales. Para obtener más
información sobre la navegación, consulte Navegación en :::no-loc(Xamarin.Forms)::: Shell.

NOTE
El BindingContext de cada objeto ShellContent se hereda del objeto Tab primario.

Clase ShellContent
La clase ShellContent incluye las siguientes propiedades que controlan la apariencia del contenido de las
pestañas y su comportamiento:
Content , de tipo , el contenido de ShellContent .
object
ContentTemplate , de tipo DataTemplate , la plantilla usada para aumentar dinámicamente el contenido de
ShellContent .
FlyoutIcon , de tipo ImageSource , define el icono que se mostrará en el control flotante.
Icon , de tipo ImageSource , define el icono que se mostrará en partes del cromo que no son la ventana
flotante.
IsChecked , de tipo boolean , define si el elemento está actualmente resaltado en la ventana flotante.
IsEnabled , de tipo boolean , define si el elemento es seleccionable en el cromo.
IsVisible , de tipo bool , indica si ShellContent está oculto en todas las estructuras de la interfaz de usuario.
Su valor predeterminado es true .
MenuItems , de tipo MenuItemCollection , los elementos de menú para mostrar en el control flotante cuando
ShellContent es la página presentada.
Title , de tipo string , el título que se mostrará en la interfaz de usuario.

Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos.

Pestañas inferiores y superiores


Cuando hay más de un objeto ShellContent en un objeto Tab , se agrega una barra de pestañas principales a la
pestaña inferior, mediante las cuales se puede navegar por los objetos ContentPage :

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="domestic.png">
<ShellContent>
<views:CatsPage />
</ShellContent>
<ShellContent>
<views:DogsPage />
</ShellContent>
</Tab>
<Tab Title="Monkeys"
Icon="monkey.png">
<ShellContent>
<views:MonkeysPage />
</ShellContent>
</Tab>
</TabBar>
</Shell>

El resultado es el diseño que se muestra en las capturas de pantalla siguientes:


Como alternativa, se pueden usar operadores de conversión implícita de Shell para quitar los objetos
ShellContent y el segundo objeto Tab del ejemplo anterior:

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="domestic.png">
<views:CatsPage />
<views:DogsPage />
</Tab>
<views:MonkeysPage IconImageSource="monkey.png" />
</TabBar>
</Shell>

Esta conversión implícita encapsula automáticamente MonkeysPage en un objeto ShellContent , el cual se


encapsula en un objeto Tab . Además, CatsPage y DogsPage se encapsulan implícitamente en objetos
ShellContent .

Carga eficiente de páginas


En una aplicación de Shell, cada objeto ContentPage de un objeto ShellContent se crea durante el inicio de la
aplicación, lo que puede conducir a una experiencia de inicio deficiente. Sin embargo, Shell también permite que
las páginas se creen a petición, en respuesta a la navegación. Para ello, se puede usar la extensión de marcado
DataTemplate para convertir cada objeto ContentPage en un objeto DataTemplate y, luego, establecer el resultado
como el valor de la propiedad ShellContent.ContentTemplate :
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png"
ContentTemplate="{DataTemplate views:CatsPage}" />
<ShellContent Title="Dogs"
Icon="dog.png"
ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
<ShellContent Title="Monkeys"
Icon="monkey.png"
ContentTemplate="{DataTemplate views:MonkeysPage}" />
</TabBar>
</Shell>

Este XAML crea y muestra CatsPage , porque es el primer elemento del contenido declarado en el objeto Shell
en subclase. Se puede navegar por las páginas DogsPage y MonkeysPage mediante pestañas inferiores, y estas
páginas solo se crean cuando el usuario se desplaza hasta ellas. La ventaja de este enfoque es que se evita la
experiencia de inicio deficiente, ya que las páginas se crean a petición en respuesta a la navegación, y no cuando
se inicia la aplicación.

Apariencia de las pestañas


La clase Shell define las siguientes propiedades adjuntas que controlan la apariencia de las pestañas:
TabBarBackgroundColor , de tipo Color , que define el color de fondo de la barra de pestañas. Si la propiedad no
está establecida, se usa el valor de la propiedad BackgroundColor .
TabBarDisabledColor , de tipo Color , que define el color deshabilitado de la barra de pestañas. Si la propiedad
no está establecida, se usa el valor de la propiedad DisabledColor .
TabBarForegroundColor , de tipo Color , que define el color de primer plano de la barra de pestañas. Si la
propiedad no está establecida, se usa el valor de la propiedad ForegroundColor .
TabBarTitleColor , de tipo Color , que define el color de título de la barra de pestañas. Si la propiedad no está
establecida, se usa el valor de la propiedad TitleColor .
TabBarUnselectedColor , de tipo Color , que define el color no seleccionado de la barra de pestañas. Si la
propiedad no está establecida, se usa el valor de la propiedad UnselectedColor .

Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos, y se les puede aplicar estilos.
En el ejemplo siguiente se muestra un estilo XAML que establece diferentes propiedades de color de las pestañas:

<Style x:Key="BaseStyle"
TargetType="Element">
<Setter Property="Shell.TabBarBackgroundColor"
Value="#3498DB" />
<Setter Property="Shell.TabBarTitleColor"
Value="White" />
<Setter Property="Shell.TabBarUnselectedColor"
Value="#B4FFFFFF" />
</Style>

Además, también se puede aplicar estilo a las pestañas mediante Hojas de estilos CSS. Para obtener más
información, consulte Propiedades específicas de :::no-loc(Xamarin.Forms)::: Shell.

Vínculos relacionados
Xaminals (ejemplo)
Navegación de Shell:::no-loc(Xamarin.Forms):::
Propiedades específicas de :::no-loc(Xamarin.Forms)::: CSS Shell
Configuración de la página de :::no-
loc(Xamarin.Forms)::: Shell
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
La clase Shell define las propiedades adjuntas que se pueden usar para configurar la apariencia de las páginas en
las aplicaciones de :::no-loc(Xamarin.Forms)::: Shell. Esto incluye configurar los colores y el modo de presentación
de la página, deshabilitar la barra de navegación y la barra de pestañas y mostrar las vistas en la barra de
navegación.

Establecimiento de los colores de la página


La clase Shell define las siguientes propiedades adjuntas que pueden usarse para establecer los colores de la
página en una aplicación de Shell:
BackgroundColor , de tipo Color , que define el color de fondo en el cromo de Shell. El color no se rellena detrás
del contenido de Shell.
DisabledColor , de tipo Color , que define el color para sombrear el texto y los iconos que están deshabilitados.
ForegroundColor , de tipo Color , que define el color para sombrear el texto y los iconos.
TitleColor , de tipo Color , que define el color usado para el título de la página actual.
UnselectedColor , de tipo Color , que define el color usado para el texto y los iconos no seleccionados en el
cromo de Shell.
Todas estas propiedades están respaldadas por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos y se les pueden aplicar estilos mediante XAML. Además, estas
propiedades se pueden establecer mediante Hojas de estilo CSS. Para obtener más información, consulte
Propiedades específicas de :::no-loc(Xamarin.Forms)::: Shell.

NOTE
También hay propiedades que permiten la definición de los colores de las pestañas. Para obtener más información, vea
Apariencia de las pestañas.

El XAML siguiente muestra el establecimiento de las propiedades de color en una clase Shell con subclases:

<?xml version="1.0" encoding="UTF-8"?>


<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xaminals.AppShell"
BackgroundColor="#455A64"
ForegroundColor="White"
TitleColor="White"
DisabledColor="#B4FFFFFF"
UnselectedColor="#95FFFFFF">

</Shell>

En este ejemplo, los valores de color se aplicarán a todas las páginas de la aplicación de Shell, a menos que se
invaliden en el nivel de página.
Dado que las propiedades de color son las propiedades adjuntas, también pueden establecerse en páginas
individuales, para establecer los colores de esa página:

<ContentPage ...
Shell.BackgroundColor="Gray"
Shell.ForegroundColor="White"
Shell.TitleColor="Blue"
Shell.DisabledColor="#95FFFFFF"
Shell.UnselectedColor="#B4FFFFFF">

</ContentPage>

Como alternativa, se pueden establecer las propiedades de color con un estilo XAML:

<Style x:Key="DomesticShell"
TargetType="Element" >
<Setter Property="Shell.BackgroundColor"
Value="#039BE6" />
<Setter Property="Shell.ForegroundColor"
Value="White" />
<Setter Property="Shell.TitleColor"
Value="White" />
<Setter Property="Shell.DisabledColor"
Value="#B4FFFFFF" />
<Setter Property="Shell.UnselectedColor"
Value="#95FFFFFF" />
</Style>

Para obtener más información sobre los estilos XAML, vea Aplicación de estilo a aplicaciones :::no-
loc(Xamarin.Forms)::: con estilos XAML.

Configuración del modo de presentación de la página


De forma predeterminada, se produce una animación de navegación pequeña cuando se navega a una página con
el método GoToAsync . Sin embargo, este comportamiento se puede cambiar si se configura la propiedad adjunta
Shell.PresentationMode de una clase ContentPage en uno de los miembros de la enumeración PresentationMode :

NotAnimated indica que la página se mostrará sin una animación de navegación.


Animated indica que la página se mostrará con una animación de navegación. Se trata del valor
predeterminado de la propiedad adjunta Shell.PresentationMode .
Modal indica que la página se mostrará como una página modal.
ModalAnimated indica que la página se mostrará como una página modal, con una animación de navegación.
ModalNotAnimated indica que la página se mostrará como una página modal, sin una animación de navegación.

IMPORTANT
El tipo PresentationMode es una enumeración de marcas. Esto significa que se puede aplicar una combinación de miembros
de enumeración en el código. Sin embargo, para facilitar su uso en XAML, el miembro ModalAnimated es una combinación
de los miembros Animated y Modal , y el miembro ModalNotAnimated es una combinación de los miembros
NotAnimated y Modal . Para obtener más información sobre las enumeraciones de marcas, vea Tipos de enumeración como
marcas de bits.

En el siguiente ejemplo de XAML se establece la propiedad adjunta Shell.PresentationMode en una clase


ContentPage :
<ContentPage ...
Shell.PresentationMode="Modal">
...
</ContentPage>

En este ejemplo, la clase ContentPage se establece para mostrarse como una página modal, cuando se navega a la
página con el método GoToAsync .

Habilitación de sombra en la barra de navegación


La clase Shell define la propiedad adjunta NavBarHasShadow , de tipo bool , que controla si la barra de navegación
tiene una sombra. De forma predeterminada, el valor de la propiedad es false en iOS y true en Android.
Aunque esta propiedad se puede establecer en un objeto Shell con subclases, también se puede establecer en
cualquier página que desee habilitar la sombra de la barra de navegación. Por ejemplo, el siguiente XAML muestra
la habilitación de la sombra de la barra de navegación de ContentPage :

<ContentPage ...
Shell.NavBarHasShadow="true">
...
</ContentPage>

Esto hace que se habilite la sombra de la barra de navegación.

Deshabilitación de la barra de navegación


La clase Shell define la propiedad adjunta NavBarIsVisible , de tipo bool , que define si la barra de navegación
debe estar visible cuando se presenta una página. De forma predeterminada, el valor de la propiedad es true .
Aunque esta propiedad se puede establecer en un objeto Shell con subclases, normalmente se establece en todas
las páginas en las que se desea que no esté visible la barra de navegación. Por ejemplo, el siguiente XAML muestra
la deshabilitación de la barra de navegación de ContentPage :

<ContentPage ...
Shell.NavBarIsVisible="false">
...
</ContentPage>

Como resultado, la barra de navegación deja de ser visible cuando se presenta la página:
Deshabilitación de la barra de pestañas
La clase Shell define la propiedad adjunta TabBarIsVisible , de tipo bool , que define si la barra de pestañas debe
estar visible cuando se presenta una página. De forma predeterminada, el valor de la propiedad es true .
Aunque esta propiedad se puede establecer en un objeto Shell con subclases, normalmente se establece en todas
las páginas en las que se desea que no esté visible la barra de pestañas. Por ejemplo, el siguiente XAML muestra la
deshabilitación de la barra de pestañas de ContentPage :

<ContentPage ...
Shell.TabBarIsVisible="false">
...
</ContentPage>

Como resultado, la barra de pestañas deja de ser visible cuando se presenta la página:

Visualización de las vistas en la barra de navegación


La clase Shell define la propiedad adjunta TitleView , de tipo View , que permite que cualquier View de :::no-
loc(Xamarin.Forms)::: se muestre en la barra de navegación.
Aunque esta propiedad se puede establecer en un objeto Shell con subclases, también se puede establecer en
cualquier página en la que se desea que se muestre una vista en la barra de navegación. Por ejemplo, el siguiente
XAML muestra la visualización de Image en la barra de navegación de ContentPage :

<ContentPage ...>
<Shell.TitleView>
<Image Source="xamarin_logo.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Shell.TitleView>
...
</ContentPage>

Esto da como resultado una imagen que se muestra en la barra de navegación de la página:

IMPORTANT
Si se ha configurado la barra de navegación para que no sea visible, con la propiedad adjunta NavBarIsVisible , la vista de
título no se mostrará.

Muchas vistas no aparecen en la barra de navegación, salvo que el tamaño de la vista se especifique con las
propiedades WidthRequest y HeightRequest , o la ubicación de la vista se especifique con las propiedades
HorizontalOptions y VerticalOptions .

Dado que la clase Layout se deriva de la clase View , la propiedad adjunta TitleView se puede establecer para
mostrar una clase de diseño que contiene varias vistas. De igual manera, dado que la clase ContentView se deriva
en última instancia de la clase View , la propiedad adjunta TitleView se puede establecer para mostrar un
ContentView que contenga una sola vista.

Visibilidad de la página
Shell respeta la visibilidad de la página, establecida con la propiedad IsVisible . Por lo tanto, cuando la propiedad
IsVisible de una página se establece en false , no será visible en la aplicación Shell y no será posible navegar
hasta ella.

Vínculos relacionados
Xaminals (ejemplo)
Aplicación de estilo a aplicaciones :::no-loc(Xamarin.Forms)::: con estilos XAML
Propiedades específicas de :::no-loc(Xamarin.Forms)::: CSS Shell
Navegación en Xamarin.Forms Shell
18/12/2020 • 23 minutes to read • Edit Online

Descargar el ejemplo
Xamarin.Forms Shell incluye una experiencia de navegación basada en el URI que emplea rutas para navegar a
cualquier página de la aplicación, sin tener que seguir una jerarquía de navegación establecida. También ofrece la
posibilidad de navegar hacia atrás sin tener que visitar todas las páginas de la pila de navegación.
Shell define las siguientes propiedades relacionadas con la navegación:
BackButtonBehavior , de tipo BackButtonBehavior , una propiedad adjunta que define el comportamiento del
botón Atrás.
CurrentItem , de tipo FlyoutItem , el valor de FlyoutItem seleccionado actualmente.
CurrentState , de tipo ShellNavigationState , el estado de navegación actual de Shell .
Current , de tipo Shell , un alias con el tipo convertido para Application.Current.MainPage .

Las propiedades BackButtonBehavior , CurrentItem y CurrentState están respaldadas por objetos


BindableProperty , lo que significa que estas propiedades pueden ser destinos de los enlaces de datos.

La navegación se realiza mediante la invocación del método GoToAsync , desde la clase Shell . Cuando la
navegación está a punto de realizarse, se activa un evento Navigating y cuando finaliza, se activa un evento
Navigated .

NOTE
La navegación todavía se puede realizar en una aplicación de Xamarin.Forms Shell mediante la propiedad Navigation. Para
más información, consulte Navegación jerárquica.

Rutas
La navegación se realiza en una aplicación de Shell mediante la especificación de un URI al que navegar. Los URI
de navegación pueden tener tres componentes:
Una ruta, que define la ruta de acceso al contenido que existe como parte de la jerarquía visual de Shell.
Una página. Las páginas que no existen en la jerarquía visual de Shell se pueden insertar en la pila de
navegación desde cualquier lugar dentro de una aplicación de Shell. Por ejemplo, una página de detalles de
elementos no se definirá en la jerarquía visual de Shell, pero se puede insertar en la pila de navegación si es
necesario.
Uno o varios parámetros de consulta. Los parámetros de consulta son parámetros que se pueden pasar a la
página de destino durante la navegación.
Cuando un URI de navegación incluye los tres componentes, la estructura es: //route/page?queryParameters
Registro de rutas
Las rutas se pueden definir en objetos FlyoutItem , Tab y ShellContent mediante sus propiedades Route :
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
</Tab>
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>

NOTE
Todos los elementos de la jerarquía de Shell tienen asociada una ruta. Si el desarrollador no establece una ruta, esta se
genera en tiempo de ejecución. Sin embargo, no se garantiza que las rutas generadas sean coherentes entre distintas
sesiones de aplicación.

En este ejemplo se crea la siguiente jerarquía de ruta desde la misma aplicación, que se puede usar en la
navegación mediante programación:

animals
domestic
cats
dogs
monkeys
elephants
bears
about

Para desplazarse al objeto ShellContent de la ruta dogs , el URI de la ruta absoluta es //animals/domestic/dogs .
Igualmente, para desplazarse al objeto ShellContent de la ruta about , el URL de la ruta absoluta es //about .

IMPORTANT
Si se detecta una ruta duplicada, se produce una excepción ArgumentException al inicio de la aplicación. También se
producirá esta excepción si dos o más rutas del mismo nivel de la jerarquía comparten el nombre de ruta.

Registro de rutas de página


En el constructor de subclases de Shell, o en cualquier otra ubicación que se ejecute antes de invocar una ruta, se
pueden registrar explícitamente rutas adicionales para cualquier página que no esté representada en la jerarquía
visual de Shell:
Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));

En este ejemplo se registran como rutas páginas de detalles de elementos que no están definidas en la subclase
de Shell. Luego, es posible desplazarse por estas páginas desde cualquier lugar de la aplicación mediante la
navegación basada en el URI. Las rutas de estas páginas se conocen como rutas globales.

NOTE
Si es necesario, se puede cancelar el registro de las páginas cuyas rutas se han registrado con el método
Routing.RegisterRoute , con el método Routing.UnRegisterRoute .

Como alternativa, se pueden registrar las páginas en diferentes jerarquías de ruta:

Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));

En este ejemplo se habilita la navegación contextual por las páginas, donde la navegación a la ruta details
desde la página de la ruta monkeys muestra MonkeyDetailPage . De forma similar, al desplazarse a la ruta
details desde la página de la ruta elephants se muestra ElephantDetailPage .

IMPORTANT
Se producirá una excepción ArgumentException si el método Routing.RegisterRoute intenta registrar la misma ruta
en dos o más tipos diferentes.

Realización de la navegación
Para realizar la navegación, se debe obtener primero una referencia a la subclase Shell . Esta referencia se
puede obtener mediante la conversión de la propiedad App.Current.MainPage en un objeto Shell , por medio de
la propiedad Shell.Current . Luego, se puede realizar la navegación mediante la llamada al método GoToAsync
en el objeto Shell . Este método lleva a ShellNavigationState y devuelve un objeto Task que se completa una
vez completada la animación de navegación. El objeto ShellNavigationState se construye mediante el método
GoToAsync , desde una propiedad string o Uri , y tiene su propiedad Location establecida en el argumento
string o Uri .

Cuando se navega a una ruta de la jerarquía visual de Shell, no se crea una pila de navegación. Sin embargo,
cuando se navega a una página que no está en la jerarquía visual de Shell, se crea una pila de navegación.

NOTE
El estado actual de navegación de Shell se puede recuperar mediante la propiedad Shell.Current.CurrentState , que
incluye el URI de la ruta mostrada en la propiedad Location .

Rutas absolutas
Se puede realizar la navegación mediante la especificación de un URI absoluto válido como argumento del
método GoToAsync :

await Shell.Current.GoToAsync("//animals/monkeys");

En este ejemplo se navega a la página de la ruta monkeys , donde dicha ruta se define en un objeto ShellContent .
El objeto ShellContent que representa la ruta monkeys es un elemento secundario de un objeto FlyoutItem ,
cuya ruta es animals .
Rutas relativas
La navegación se puede realizar también mediante la especificación de un URI relativo válido como argumento
del método GoToAsync . El sistema de enrutamiento intenta hacer coincidir el URI con un objeto ShellContent .
Por lo tanto, si todas las rutas de una aplicación son únicas, la navegación se puede realizar con solo especificar
el nombre de ruta único como un URI relativo.
Se admiten los formatos siguientes de ruta relativa:

F O RM ATO DESC RIP C IÓ N

route En la jerarquía de ruta se buscará la ruta especificada, hacia


arriba desde la posición actual. La página que coincida se
insertará en la pila de navegación.

/route La jerarquía de ruta se buscará a partir de la ruta


especificada, hacia abajo desde la posición actual. La página
que coincida se insertará en la pila de navegación.

//route En la jerarquía de ruta se buscará la ruta especificada, hacia


arriba desde la posición actual. La página que coincida
reemplazará la pila de navegación.

///route En la jerarquía de ruta se buscará la ruta especificada, hacia


abajo desde la posición actual. La página que coincida
reemplazará la pila de navegación.

En el ejemplo siguiente se navega a la página de la ruta monkeydetails :

await Shell.Current.GoToAsync("monkeydetails");

En este ejemplo, la ruta monkeyDetails se busca hacia arriba en la jerarquía hasta que se encuentra la página que
coincide. Cuando se encuentra, se inserta en la pila de navegación.
Navegación contextual
Las rutas relativas permiten la navegación contextual. Por ejemplo, considere la siguiente jerarquía de ruta:

monkeys
details
bears
details

Cuando se muestra la página registrada para la ruta monkeys , al navegar a la ruta details se mostrará la página
registrada para la ruta monkeys/details . Igualmente, cuando se muestra la página registrada para la ruta bears ,
al navegar a la ruta details se mostrará la página registrada para la ruta bears/details . Para información
sobre cómo registrar las rutas en este ejemplo, consulte Registro de rutas de página.
Navegación hacia atrás
La navegación hacia atrás se puede llevar a cabo especificando ".." como argumento en el método GotoAsync :

await Shell.Current.GoToAsync("..");

La navegación hacia atrás con ".." también se puede combinar con una ruta:

await Shell.Current.GoToAsync("../route");

En este ejemplo, se realiza la navegación hacia atrás y, después, se navega a la ruta especificada.

IMPORTANT
Desplazarse hacia atrás y luego a una ruta especificada solo es posible si la navegación hacia atrás sitúa al usuario en la
ubicación actual en la jerarquía de rutas para navegar a la ruta especificada.

Del mismo modo, es posible desplazarse hacia atrás varias veces y, seguidamente, navegar a una ruta
especificada:

await Shell.Current.GoToAsync("../../route");

En este ejemplo, la navegación hacia atrás se realiza dos veces y, después, se navega a la ruta especificada.
Además, los datos se pueden pasar a través de las propiedades de consulta al navegar hacia atrás:

await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");

En este ejemplo, se realiza la navegación hacia atrás y el valor del parámetro de consulta se pasa al parámetro de
consulta en la página anterior.

NOTE
Los parámetros de consulta se pueden anexar a cualquier solicitud de navegación hacia atrás.

Para obtener más información sobre cómo pasar datos al navegar, vea Pasar datos.
Rutas no válidas
Los siguientes formatos de ruta no son válidos:

F O RM ATO EXP L IC A C IÓ N

route o /route Las rutas de la jerarquía visual no se pueden insertar en la


pila de navegación.

//page o ///page Actualmente, las rutas globales no pueden ser la única


página en la pila de navegación. Por lo tanto, no se admite el
enrutamiento absoluto a rutas globales.

El uso de cualquiera de estos formatos de ruta dará como resultado una excepción Exception .
IMPORTANT
Al intentar navegar a una ruta inexistente, se producirá una excepción ArgumentException .

Depuración de la navegación
Algunas de las clases de Shell se representan con DebuggerDisplayAttribute , que especifica cómo el depurador
muestra una clase o campo. Esto puede ayudar a depurar las solicitudes de navegación puesto que se muestran
los datos relacionados con la solicitud de navegación. Por ejemplo, en la captura de pantalla siguiente se
muestran las propiedades CurrentItem y CurrentState del objeto Shell.Current :

En este ejemplo, la propiedad CurrentItem , de tipo FlyoutItem , muestra el título y la ruta del objeto FlyoutItem .
Igualmente, la propiedad CurrentState , de tipo ShellNavigationState , muestra el URI de la ruta mostrada
dentro de la aplicación de Shell.
Clase Tab
La clase Tab define una propiedad Stack , de tipo IReadOnlyList<Page> , que representa la pila de navegación
actual insertada en Tab . La clase también proporciona los siguientes métodos de navegación reemplazables:
GetNavigationStack , devuelve IReadOnlyList<Page >, la pila de navegación actual.
OnInsertPageBefore , que se llama cuando se llama a INavigation.InsertPageBefore .
OnPopAsync , devuelve Task<Page> , y se llama cuando se llama a INavigation.PopAsync .
OnPopToRootAsync , devuelve Task , y se llama cuando se llama a INavigation.OnPopToRootAsync .
OnPushAsync , devuelve Task , y se llama cuando se llama a INavigation.PushAsync .
OnRemovePage , que se llama cuando se llama a INavigation.RemovePage .

Por ejemplo, en el código siguiente de ejemplo se indica cómo invalidar el método OnRemovePage :

public class MyTab : Tab


{
protected override void OnRemovePage(Page page)
{
base.OnRemovePage(page);

// Custom logic
}
}

Después, los objetos MyTab se pueden consumir en la jerarquía visual de Shell en lugar de los objetos Tab .

Eventos de navegación
La clase Shell define un evento Navigating , que se desencadena cuando está a punto de realizarse la
navegación, ya sea debido a la navegación mediante programación o a la interacción del usuario. El objeto
ShellNavigatingEventArgs que acompaña al evento Navigating proporciona las siguientes propiedades:

P RO P IEDA D. T IP O DESC RIP C IÓ N

Current ShellNavigationState Identificador URI de la página actual.


P RO P IEDA D. T IP O DESC RIP C IÓ N

Source ShellNavigationSource El tipo de navegación que se ha


producido.

Target ShellNavigationState Identificador URI que representa el


destino de la navegación.

CanCancel bool Valor que indica si es posible cancelar la


navegación.

Cancelled bool Valor que indica si la navegación se ha


cancelado.

Además, la clase ShellNavigatingEventArgs proporciona un método Cancel que se puede usar para cancelar la
navegación.
La clase Shell también define un evento Navigated , que se desencadena cuando se ha completado la
navegación. El objeto ShellNavigatedEventArgs que acompaña al evento Navigating proporciona las siguientes
propiedades:

P RO P IEDA D. T IP O DESC RIP C IÓ N

Current ShellNavigationState Identificador URI de la página actual.

Previous ShellNavigationState Identificador URI de la página anterior.

Source ShellNavigationSource El tipo de navegación que se ha


producido.

Las clases y ShellNavigatingEventArgs tienen ambas propiedades


ShellNavigatedEventArgs Source , de tipo
ShellNavigationSource . Esta enumeración proporciona los valores siguientes:

Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged

Por lo tanto, en un controlador del evento Navigating , es posible interceptar la navegación y realizar acciones en
función del origen de navegación. Por ejemplo, el código siguiente muestra cómo cancelar la navegación hacia
atrás si no se han guardado los datos de la página:
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
// Cancel back navigation if data is unsaved
if (e.Source == ShellNavigationSource.Pop && !dataSaved)
{
e.Cancel();
}
}

Pasar datos
Al realizar la navegación mediante programación, los datos pueden pasarse como parámetros de consulta. Por
ejemplo, el código siguiente se ejecuta en la aplicación de ejemplo cuando un usuario selecciona un elefante en
ElephantsPage :

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)


{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
await Shell.Current.GoToAsync($"//animals/elephants/elephantdetails?name={elephantName}");
}

En este ejemplo de código se recupera el elefante actualmente seleccionado en CollectionView y se navega a la


ruta elephantdetails ; además, se pasa elephantName como parámetro de consulta. Tenga en cuenta que los
parámetros de consulta van a ser codificados con URL para la navegación, por lo que "Indian Elephant" se
convertirá en "Indian%20Elephant".
Para recibir datos, la clase que representa la página a la que se navega, o la clase de BindingContext de la página,
se debe decorar con un elemento QueryPropertyAttribute para cada parámetro de consulta:

[QueryProperty("Name", "name")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
BindingContext = ElephantData.Elephants.FirstOrDefault(m => m.Name ==
Uri.UnescapeDataString(value));
}
}
...
}

El primer argumento de QueryPropertyAttribute especifica el nombre de la propiedad que recibirá los datos,
mientras que el segundo argumento especifica el identificador del parámetro de consulta. Por tanto, el elemento
QueryPropertyAttribute del ejemplo anterior especifica que la propiedad Name recibirá los datos pasados al
parámetro de consulta name del URI en la llamada al método GoToAsync . La propiedad Name decodifica
entonces el valor del parámetro de consulta en una dirección URL y lo usa para establecer el objeto
BindingContext de la página en el objeto que se mostrará.

NOTE
Una clase se puede representar con varios objetos QueryPropertyAttribute .

Comportamiento del botón Atrás


La clase BackButtonBehavior define las siguientes propiedades que controlan la apariencia y el comportamiento
del botón Atrás:
Command , de tipo , que se ejecuta cuando se presiona el botón Atrás.
ICommand
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .
IconOverride , de tipo ImageSource , el icono usado para el botón Atrás.
IsEnabled , de tipo boolean , indica si se ha habilitado el botón Atrás. El valor predeterminado es true .
TextOverride , de tipo string , el texto usado para el botón Atrás.

Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos.
La clase BackButtonBehavior se puede consumir mediante el establecimiento de la propiedad adjunta
Shell.BackButtonBehavior a un objeto BackButtonBehavior :

<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>

El código de C# equivalente es el siguiente:

Shell.SetBackButtonBehavior(this, new BackButtonBehavior


{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});

La propiedad Command se establece en ICommand para ejecutarse cuando se presiona el botón Atrás, y la
propiedad IconOverride se establece en el icono que se usa para el botón Atrás:

Vínculos relacionados
Xaminals (ejemplo)
Búsqueda en :::no-loc(Xamarin.Forms)::: Shell
18/12/2020 • 20 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: Shell incluye la funcionalidad de búsqueda integrada que proporciona la clase
SearchHandler . La funcionalidad de búsqueda se puede agregar a una página mediante el establecimiento de la
propiedad adjunta Shell.SearchHandler en un objeto SearchHandler en subclase. El resultado es un cuadro de
búsqueda que se agrega en la parte superior de la página:

Cuando se escribe una consulta en el cuadro de búsqueda, la propiedad Query se actualiza y, en cada actualización,
se ejecuta el método OnQueryChanged . Este método se puede invalidar para rellenar el área de sugerencias de
búsqueda con datos:

Luego, cuando se selecciona un resultado del área de sugerencias de búsqueda, se ejecuta el método
OnItemSelected . Este método se puede invalidar para responder de forma adecuada; por ejemplo, navegando a
una página de detalles.

Creación de una clase SearchHandler


La funcionalidad de búsqueda se puede agregar a una aplicación de Shell mediante la creación de subclases de la
clase SearchHandler y la invalidación de los métodos OnQueryChanged y OnItemSelected :
public class MonkeySearchHandler : SearchHandler
{
protected override void OnQueryChanged(string oldValue, string newValue)
{
base.OnQueryChanged(oldValue, newValue);

if (string.IsNullOrWhiteSpace(newValue))
{
ItemsSource = null;
}
else
{
ItemsSource = MonkeyData.Monkeys
.Where(monkey => monkey.Name.ToLower().Contains(newValue.ToLower()))
.ToList<Animal>();
}
}

protected override async void OnItemSelected(object item)


{
base.OnItemSelected(item);

// Note: strings will be URL encoded for navigation (e.g. "Blue Monkey" becomes "Blue%20Monkey").
Therefore, decode at the receiver.
await (App.Current.MainPage as :::no-loc(Xamarin.Forms):::.Shell).GoToAsync($"monkeydetails?name=
{((Animal)item).Name}");
}
}

La invalidación OnQueryChanged tiene dos argumentos: oldValue , que contiene la consulta de búsqueda anterior, y
newValue , que contiene la consulta de búsqueda actual. El área de sugerencias de búsqueda se puede actualizar
mediante el establecimiento de la propiedad SearchHandler.ItemsSource en una colección IEnumerable que
contiene elementos que coinciden con la consulta de búsqueda actual.
Cuando el usuario selecciona un resultado de búsqueda, se ejecuta la invalidación OnItemSelected y se establece la
propiedad SelectedItem . En este ejemplo, el método dirige a otra página que muestra datos sobre el elemento
Animal seleccionado. Para obtener más información sobre la navegación, consulte Navegación en :::no-
loc(Xamarin.Forms)::: Shell.

NOTE
Se pueden establecer propiedades SearchHandler adicionales para controlar la apariencia del cuadro de búsqueda.

Consumo de una clase SearchHandler


La clase SearchHandler en subclase se puede consumir mediante el establecimiento de la propiedad adjunta
Shell.SearchHandler en un objeto del tipo en subclase:

<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
<controls:MonkeySearchHandler Placeholder="Enter search term"
ShowsResults="true"
DisplayMemberName="Name" />
</Shell.SearchHandler>
...
</ContentPage>

El código de C# equivalente es el siguiente:


Shell.SetSearchHandler(this, new MonkeySearchHandler
{
Placeholder = "Enter search term",
ShowsResults = true,
DisplayMemberName = "Name"
});

El método MonkeySearchHandler.OnQueryChanged devuelve un elemento List de objetos Animal . La propiedad


DisplayMemberName se establece en la propiedad Name de cada objeto Animal , por lo que los datos mostrados en
el área de sugerencias serán el nombre de cada animal.
La propiedad ShowsResults está establecida en true , de modo que se muestran sugerencias de búsqueda cuando
el usuario escribe una consulta de búsqueda:

A medida que cambia la consulta de búsqueda, se actualiza el área de sugerencias de búsqueda:

Cuando se selecciona un resultado de búsqueda, hay un desplazamiento hasta MonkeyDetailPage y se muestran


datos sobre el mono seleccionado:

Definición de la apariencia de los elemento de los resultados de


búsqueda
Además de mostrar datos de string en los resultados de búsqueda, se puede definir la apariencia de cada uno de
los elementos de los resultados de búsqueda mediante el establecimiento de la propiedad
SearchHandler.ItemTemplate en DataTemplate :

<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
<controls:MonkeySearchHandler Placeholder="Enter search term"
ShowsResults="true">
<controls:MonkeySearchHandler.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.15*" />
<ColumnDefinition Width="0.85*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="40"
WidthRequest="40" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
</Grid>
</DataTemplate>
</controls:MonkeySearchHandler.ItemTemplate>
</controls:MonkeySearchHandler>
</Shell.SearchHandler>
...
</ContentPage>

El código de C# equivalente es el siguiente:

Shell.SetSearchHandler(this, new MonkeySearchHandler


{
Placeholder = "Enter search term",
ShowsResults = true,
DisplayMemberName = "Name",
ItemTemplate = new DataTemplate(() =>
{
Grid grid = new Grid { Padding = 10 };
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.15, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.85, GridUnitType.Star) });

Image image = new Image { Aspect = Aspect.AspectFill, HeightRequest = 40, WidthRequest = 40 };


image.SetBinding(Image.SourceProperty, "ImageUrl");
Label nameLabel = new Label { FontAttributes = FontAttributes.Bold };
nameLabel.SetBinding(Label.TextProperty, "Name");

grid.Children.Add(image);
grid.Children.Add(nameLabel, 1, 0);
return grid;
})
});

Los elementos especificados en DataTemplate definen la apariencia de cada elemento en el área de sugerencias. En
este ejemplo, el diseño dentro de DataTemplate se administra mediante un objeto Grid . El objeto Grid contiene
un objeto Image y un objeto Label , que enlazan ambos con las propiedades de cada objeto Monkey .
Las capturas de pantalla siguientes muestran el resultado de crear plantillas para cada elemento del área de
sugerencias:

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Visibilidad del cuadro de búsqueda


Cuando se agrega un SearchHandler en la parte superior de una página, de forma predeterminada el cuadro de
búsqueda está visible y totalmente expandido. Pero este comportamiento se puede cambiar estableciendo la
propiedad SearchHandler.SearchBoxVisibility en uno de los miembros de la enumeración SearchBoxVisibility :
Hidden : el cuadro de búsqueda no es visible ni accesible.
Collapsible : el cuadro de búsqueda está oculto hasta que el usuario realiza una acción para mostrarlo. En iOS,
el cuadro de búsqueda aparece rebotando verticalmente el contenido de la página y, en Android, el cuadro de
búsqueda aparece al pulsar el icono del signo de interrogación.
Expanded : el cuadro de búsqueda está visible y totalmente expandido. Este es el valor predeterminado de la
propiedad SearchHandler.SearchBoxVisibility .

IMPORTANT
En iOS, un cuadro de búsqueda contraíble requiere iOS 11 o superior.

En el ejemplo siguiente se muestra cómo ocultar el cuadro de búsqueda:

<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
<controls:AnimalSearchHandler SearchBoxVisibility="Hidden"
... />
</Shell.SearchHandler>
...
</ContentPage>

Enfoque en el cuadro de búsqueda


Al pulsar en un cuadro de búsqueda aparece el teclado en pantalla, y el cuadro de búsqueda adquiere el foco de
entrada. Esto también se consigue mediante programación llamando al método Focus , que intenta establecer el
foco de entrada en el cuadro de búsqueda, y devuelve true si se realiza correctamente. Cuando un cuadro de
búsqueda obtiene el foco, se desencadena el evento Focus y se llama al método OnFocused reemplazable.
Cuando un cuadro de búsqueda tiene el foco de entrada, al pulsar en otro lugar en la pantalla desaparece el teclado
en pantalla y el cuadro de búsqueda pierde el foco de entrada. Esto también se consigue mediante programación
llamando al método Unfocus . Cuando un cuadro de búsqueda pierde el foco, se desencadena el evento Unfocused
y se llama al método OnUnfocus reemplazable.
Se puede recuperar el estado del foco de un cuadro de búsqueda a través de la propiedad IsFocused , que
devuelve true si un SearchHandler tiene actualmente el foco de entrada.

Apariencia SearchHandler
La clase SearchHandler define las siguientes propiedades que afectan a su apariencia:
BackgroundColor , del tipo Color , es el color de fondo para el texto del cuadro de búsqueda.
CancelButtonColor , del tipo Color , es el color del botón Cancelar.
CharacterSpacing , del tipo double , es el espaciado entre los caracteres del texto de SearchHandler .
FontAttributes , del tipo FontAttributes , indica si el texto del cuadro de búsqueda está en negrita o cursiva.
FontFamily , del tipo string , es la familia de fuentes utilizada para el texto del cuadro de búsqueda.
FontSize , del tipo double , es el tamaño del texto del cuadro de búsqueda.
HorizontalTextAlignment , del tipo TextAlignment , es la alineación horizontal del texto del cuadro de búsqueda.
PlaceholderColor , del tipo Color , es el color del texto del cuadro de búsqueda del marcador de posición.
TextColor , del tipo Color , es el color del texto del cuadro de búsqueda.
TextTransform , del tipo TextTransform , determina las mayúsculas y minúsculas del texto del cuadro de
búsqueda.
VerticalTextAlignment , del tipo TextAlignment , es la alineación vertical del texto del cuadro de búsqueda.

Teclado SearchHandler
El teclado que aparece cuando los usuarios interactúan con un SearchHandler se puede establecer mediante
programación a través de la propiedad Keyboard en una de las siguientes propiedades desde la clase Keyboard :
Chat : se usa para el texto y los lugares donde los emoji son útiles.
Default : el teclado predeterminado.
Email : se usa al especificar direcciones de correo electrónico.
Numeric : se usa al escribir números.
Plain : se usa al escribir texto, sin ningún KeyboardFlags especificado.
Telephone : se usa al escribir números de teléfono.
Text : se usa al escribir texto.
Url : se usa para especificar las rutas de acceso de archivo y direcciones web.

Esto se puede lograr en XAML de la siguiente manera:

<SearchHandler Keyboard="Email" />

El código de C# equivalente es el siguiente:

SearchHandler searchHandler = new SearchHandler { Keyboard = Keyboard.Email };

La clase Keyboard tiene también un patrón de diseño Factory Method Create que puede usarse para personalizar
un teclado mediante la especificación del comportamiento de las mayúsculas y minúsculas, el corrector ortográfico
y las sugerencias. Los valores de enumeración KeyboardFlags se especifican como argumentos para el método,
con la devolución de un Keyboard personalizado. La enumeración KeyboardFlags contiene los valores siguientes:
None : no se agregan características al teclado.
CapitalizeSentence : indica que la primera letra de la primera palabra de cada frase se escribirá
automáticamente en mayúsculas.
Spellcheck : indica que se pasará el corrector ortográfico al texto especificado.
Suggestions : indica que se ofrecerán finalizaciones de palabra para el texto especificado.
CapitalizeWord : indica que las primeras letras de todas las palabras se escribirán automáticamente en
mayúsculas.
CapitalizeCharacter : indica que todos los caracteres se escribirán automáticamente en mayúsculas.
CapitalizeNone : indica que no se producirá ningún uso automático de mayúsculas.
All : indica que se pasará el corrector automático, se ofrecerán finalizaciones de palabras y las frases
empezarán en mayúsculas en el texto especificado.
El ejemplo de código XAML siguiente muestra cómo personalizar el Keyboard predeterminado para ofrecer
finalizaciones de palabras y poner en mayúsculas todos los caracteres especificados:

<SearchHandler Placeholder="Enter search terms">


<SearchHandler.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</SearchHandler.Keyboard>
</SearchHandler>

El código de C# equivalente es el siguiente:

SearchHandler searchHandler = new SearchHandler { Placeholder = "Enter search terms" };


searchHandler.Keyboard = Keyboard.Create(KeyboardFlags.Suggestions | KeyboardFlags.CapitalizeCharacter);

Referencia de SearchHandler
La clase SearchHandler define las siguientes propiedades que controlan su apariencia y comportamiento:
BackgroundColor , del tipo Color , es el color de fondo para el texto del cuadro de búsqueda.
CancelButtonColor , del tipo Color , es el color del botón Cancelar.
ClearIcon , de tipo ImageSource , el icono que aparece para borrar el contenido del cuadro de búsqueda.
ClearIconHelpText , de tipo string , el texto de ayuda accesible para el icono de borrar.
ClearIconName , de tipo string , el nombre del icono de borrar para usar con los lectores de pantalla.
ClearPlaceholderCommand , de tipo ICommand , que se ejecuta cuando se pulsa ClearPlaceholderIcon .
ClearPlaceholderCommandParameter , de tipo object , que es el parámetro que se pasa a ClearPlaceholderCommand .
ClearPlaceholderEnabled , de tipo bool , que determina si se puede ejecutar ClearPlaceholderCommand . El valor
predeterminado es true .
ClearPlaceholderHelpText , de tipo string , el texto de ayuda accesible para el icono de borrar marcador de
posición.
ClearPlaceholderIcon , de tipo ImageSource , el icono de borrar marcador de posición que se muestra cuando el
cuadro de búsqueda está vacío.
ClearPlaceholderName , de tipo string , el nombre del icono de borrar marcador de posición para su uso con los
lectores de pantalla.
Command , de tipo ICommand , que se ejecuta cuando se confirma la consulta de búsqueda.
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .
DisplayMemberName , de tipo string , que representa el nombre o la ruta de acceso de la propiedad que se
muestra para cada elemento de datos de la colección ItemsSource .
FontAttributes , del tipo FontAttributes , indica si el texto del cuadro de búsqueda está en negrita o cursiva.
FontFamily , del tipo string , es la familia de fuentes utilizada para el texto del cuadro de búsqueda.
FontSize , del tipo double , es el tamaño del texto del cuadro de búsqueda.
HorizontalTextAlignment , del tipo TextAlignment , es la alineación horizontal del texto del cuadro de búsqueda.
IsFocused , del tipo bool , que representa si un SearchHandler actualmente tiene foco de entrada.
IsSearchEnabled , de tipo bool , que representa el estado habilitado del cuadro de búsqueda. El valor
predeterminado es true .
ItemsSource , de tipo IEnumerable , especifica la colección de elementos que se mostrarán en el área de
sugerencias, y tiene un valor predeterminado de null .
ItemTemplate , de tipo DataTemplate , especifica la plantilla que se aplicará a cada elemento de la colección de
elementos que se mostrará en el área de sugerencias.
Keyboard , del tipo Keyboard , es el teclado para el SearchHandler .
Placeholder , de tipo string , el texto que se muestra cuando el cuadro de búsqueda está vacío.
PlaceholderColor , del tipo Color , es el color del texto del cuadro de búsqueda del marcador de posición.
Query , de tipo string , el texto especificado por el usuario en el cuadro de búsqueda.
QueryIcon , de tipo ImageSource , el icono utilizado para indicar al usuario que la búsqueda está disponible.
QueryIconHelpText , de tipo string , el texto de ayuda accesible para el icono de consulta.
QueryIconName , de tipo string , el nombre del icono de consulta para su uso con los lectores de pantalla.
SearchBoxVisibility , de tipo SearchBoxVisibility , la visibilidad del cuadro de búsqueda. De forma
predeterminada, el cuadro de búsqueda está visible y totalmente expandido.
SelectedItem , de tipo object , el elemento seleccionado en los resultados de búsqueda. Esta propiedad es de
solo lectura y tiene un valor predeterminado de null .
ShowsResults , de tipo bool , indica si se deben esperar resultados de búsqueda en el área de sugerencias, al
escribir texto. El valor predeterminado es false .
TextColor , del tipo Color , es el color del texto del cuadro de búsqueda.
TextTransform , del tipo TextTransform , determina las mayúsculas y minúsculas del texto del cuadro de
búsqueda.
Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos.
Además, la clase SearchHandler proporciona los métodos reemplazables siguientes:
OnClearPlaceholderClicked , que se llama cada vez que se pulsa ClearPlaceholderIcon .
OnItemSelected , que se llama cada vez que el usuario selecciona un resultado de búsqueda.
OnFocused , al que se llama cuando un SearchHandler adquiere el foco de entrada.
OnQueryChanged , que se llama cuando cambia la propiedad Query .
OnQueryConfirmed , que se llama cada vez que el usuario presiona Entrar o confirma su consulta en el cuadro de
búsqueda.
OnUnfocus , al que se llama cuando un SearchHandler pierde el foco de entrada.

Vínculos relacionados
Xaminals (ejemplo)
Navegación en :::no-loc(Xamarin.Forms)::: Shell
Ciclo de vida de :::no-loc(Xamarin.Forms)::: Shell
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Las aplicaciones del shell respetan el ciclo de vida de :::no-loc(Xamarin.Forms):::; se genera un evento Appearing si
una página está a punto de aparecer en la pantalla y se genera un evento Disappearing si una página está a punto
de desaparecer de la pantalla. Estos eventos se propagan a las páginas y se pueden controlar si se invalidan los
métodos OnAppearing o OnDisappearing en la página.

NOTE
En una aplicación del shell, los eventos Appearing y Disappearing se generan desde código multiplataforma, antes de
que el código de plataforma haga que una página sea visible o quite una página de la pantalla.

Para obtener más información sobre el ciclo de vida de la aplicación de :::no-loc(Xamarin.Forms):::, consulte Ciclo de
vida de la aplicación de :::no-loc(Xamarin.Forms):::.

Navegación jerárquica
En una aplicación del shell, al insertar una página en la pila de navegación, se obtendrá el objeto ShellContent
visible actualmente y el contenido de la página, lo que provocará el evento Disappearing . De forma similar, al sacar
la última página de la pila de navegación, se obtendrá el objeto ShellContent recién visible y el contenido de la
página, lo que provocará el evento Appearing .
Para obtener más información sobre la navegación jerárquica, consulte Navegación jerárquica de :::no-
loc(Xamarin.Forms):::.

Navegación modal
En una aplicación del shell, al insertar una página modal en la pila de navegación modal, todos los objetos del shell
estarán visibles y se provocará el evento Disappearing . De forma similar, al sacar la última página modal de la pila
de navegación modal, todos los objetos del shell estarán visibles y se provocará el evento Appearing .
Para obtener más información sobre la navegación modal, consulte Páginas modales de :::no-loc(Xamarin.Forms):::.

Vínculos relacionados
Xaminals (ejemplo)
Ciclo de vida de la aplicación de :::no-loc(Xamarin.Forms):::
Páginas modales de :::no-loc(Xamarin.Forms):::
Representadores personalizados de Xamarin.Forms
Shell
18/12/2020 • 6 minutes to read • Edit Online

Una de las ventajas de las aplicaciones de Xamarin.Forms Shell es que su apariencia y comportamiento es muy
personalizable mediante las propiedades y los métodos que exponen las distintas clases de Shell. Sin embargo,
también es posible crear a un representador personalizado de Shell cuando se requieren personalizaciones más
sofisticadas específicas de la plataforma. Al igual que con otros representadores personalizados, se puede agregar
un representador personalizado de Shell a solo un proyecto de plataforma para personalizar la apariencia y el
comportamiento, mientras se permite el comportamiento predeterminado en la otra plataforma; o se puede
agregar un representador personalizado de Shell diferente a cada proyecto de plataforma para personalizar la
apariencia y el comportamiento en iOS y Android.
Las aplicaciones de Shell se representan mediante la clase ShellRenderer en iOS y Android. En iOS, la clase
ShellRenderer se puede encontrar en el espacio de nombres Xamarin.Forms.Platform.iOS . En Android, la clase
ShellRenderer se puede encontrar en el espacio de nombres Xamarin.Forms.Platform.Android .

El proceso para crear un representador personalizado de Shell es el siguiente:


1. Cree la subclase de la clase Shell . Esta acción ya se realizará en la aplicación de Shell.
2. Consuma la clase Shell en subclase. Esta acción ya se realizará en la aplicación de Shell.
3. Cree una clase de representador personalizado que se derive de la clase ShellRenderer , en las plataformas
necesarias.

Creación de una clase de representador personalizado


El proceso para crear una clase de representador personalizado de Shell es el siguiente:
1. Se crea una subclase de la clase ShellRenderer .
2. Invalide los métodos necesarios para llevar a cabo la personalización necesaria.
3. Agregue un elemento ExportRendererAttribute a la subclase ShellRenderer para especificar que se usará para
representar la aplicación de Shell. Este atributo se usa para registrar al representador personalizado con
Xamarin.Forms.

NOTE
Es opcional para proporcionar un representador personalizado de Shell en cada proyecto de plataforma. Si no está
registrado un representador personalizado, se usará entonces la clase ShellRenderer predeterminada.

La clase ShellRenderer expone los siguientes métodos reemplazables:


IO S A N DRO ID UW P

SetElementSize CreateFragmentForPage CreateShellFlyoutTemplateSelector


CreateFlyoutRenderer CreateShellFlyoutContentRenderer CreateShellHeaderRenderer
CreateNavBarAppearanceTracker CreateShellFlyoutRenderer CreateShellItemRenderer
CreatePageRendererTracker CreateShellItemRenderer CreateShellSectionRenderer
CreateShellFlyoutContentRenderer CreateShellSectionRenderer OnElementPropertyChanged
CreateShellItemRenderer CreateTrackerForToolbar OnElementSet
CreateShellItemTransition CreateToolbarAppearanceTracker UpdateFlyoutBackdropColor
CreateShellSearchResultsRenderer CreateTabLayoutAppearanceTracker UpdateFlyoutBackgroundColor
CreateShellSectionRenderer CreateBottomNavViewAppearanceTracker
CreateTabBarAppearanceTracker OnElementPropertyChanged
Dispose OnElementSet
OnCurrentItemChanged SwitchFragment
OnElementPropertyChanged Dispose
OnElementSet
UpdateBackgroundColor

Las clases FlyoutItem y TabBar son alias de la clase ShellItem , y la clase Tab es un alias de la clase
ShellSection . Por lo tanto, el método CreateShellItemRenderer se debe invalidar al crear un representador
personalizado para objetos FlyoutItem , y el método CreateShellSectionRenderer se debe invalidar al crear un
representador personalizado para objetos Tab .

IMPORTANT
Hay clases de representador adicionales de Shell, como ShellSectionRenderer y ShellItemRenderer , en iOS, Android y
UWP. Sin embargo, estas clases de representador adicionales se crean mediante invalidaciones en la clase ShellRenderer .
Por lo tanto, para personalizar el comportamiento de estas clases de representador adicionales, puede crear una subclase de
ellas y una instancia de la subclase en la invalidación adecuada de la clase ShellRenderer en subclase.

Ejemplo de iOS
En el ejemplo de código siguiente se muestra una clase ShellRenderer en subclase para iOS, que establece una
imagen de fondo en la barra de navegación de la aplicación de Shell:

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(Xaminals.AppShell), typeof(Xaminals.iOS.MyShellRenderer))]


namespace Xaminals.iOS
{
public class MyShellRenderer : ShellRenderer
{
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
(renderer as
ShellSectionRenderer).NavigationBar.SetBackgroundImage(UIImage.FromFile("monkey.png"), UIBarMetrics.Default);
}
return renderer;
}
}
}

La clase MyShellRenderer invalida el método CreateShellSectionRenderer y recupera el representador creado por


la clase base. Luego, modifica al representador mediante el establecimiento de una imagen de fondo en la barra
de navegación, antes de volver al representador.
Ejemplo de Android
En el ejemplo de código siguiente se muestra una clase ShellRenderer en subclase para Android, que establece
una imagen de fondo en la barra de navegación de la aplicación de Shell:

using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(Xaminals.AppShell), typeof(Xaminals.Droid.MyShellRenderer))]


namespace Xaminals.Droid
{
public class MyShellRenderer : ShellRenderer
{
public MyShellRenderer(Context context) : base(context)
{
}

protected override IShellToolbarAppearanceTracker CreateToolbarAppearanceTracker()


{
return new MyShellToolbarAppearanceTracker(this);
}
}
}

La clase invalida el método CreateToolbarAppearanceTracker y devuelve una instancia de la clase


MyShellRenderer
MyShellToolbarAppearanceTracker . La clase MyShellToolbarAppearanceTracker , que se deriva de la clase
ShellToolbarAppearanceTracker , se muestra en el ejemplo siguiente:

using Android.Support.V7.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

namespace Xaminals.Droid
{
public class MyShellToolbarAppearanceTracker : ShellToolbarAppearanceTracker
{
public MyShellToolbarAppearanceTracker(IShellContext context) : base(context)
{
}

public override void SetAppearance(Toolbar toolbar, IShellToolbarTracker toolbarTracker,


ShellAppearance appearance)
{
base.SetAppearance(toolbar, toolbarTracker, appearance);
toolbar.SetBackgroundResource(Resource.Drawable.monkey);
}
}
}

La clase MyShellToolbarAppearanceTracker invalida el método SetAppearance y modifica la barra de herramientas


mediante el establecimiento de una imagen de fondo en ella.

IMPORTANT
Solo es necesario agregar el objeto ExportRendererAttribute a un representador personalizado que se deriva de la clase
ShellRenderer . Se crean clases de representador de Shell en subclase adicionales mediante la clase ShellRenderer en
subclase.
Vínculos relacionados
Representadores personalizados de Xamarin.Forms
Plantillas de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Plantillas de control
Las plantillas de control de Xamarin.Forms definen la estructura visual de los controles personalizados derivados
ContentView y las páginas derivadas ContentPage .

Plantillas de datos
Las plantillas de datos de Xamarin.Forms definen la presentación de los datos en los controles admitidos.
Plantillas de control de :::no-loc(Xamarin.Forms):::
18/12/2020 • 24 minutes to read • Edit Online

Descargar el ejemplo
Las plantillas de control de :::no-loc(Xamarin.Forms)::: permiten definir la estructura visual de los controles
personalizados derivados de ContentView y las páginas derivadas de ContentPage . Las plantillas de control
separan la interfaz de usuario (IU) de un control personalizado, o de una página, de la lógica que implementa el
control o la página. También se puede insertar contenido adicional en el control personalizado (o en la página)
con plantilla en una ubicación predefinida.
Por ejemplo, se puede crear una plantilla de control que redefina la interfaz de usuario proporcionada por un
control personalizado. La instancia de control personalizada requerida puede consumir entonces la plantilla de
control. Como alternativa, se puede crear una plantilla de control que defina cualquier interfaz de usuario
común que se usará en varias páginas de una aplicación. La plantilla de control se puede usar en varias
páginas, cada una de las cuales sigue mostrando su contenido único.

Creación de una clase ControlTemplate


En el ejemplo siguiente se muestra el código de un control personalizado CardView :

public class CardView : ContentView


{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle),
typeof(string), typeof(CardView), string.Empty);
public static readonly BindableProperty CardDescriptionProperty =
BindableProperty.Create(nameof(CardDescription), typeof(string), typeof(CardView), string.Empty);
// ...

public string CardTitle


{
get => (string)GetValue(CardTitleProperty);
set => SetValue(CardTitleProperty, value);
}

public string CardDescription


{
get => (string)GetValue(CardDescriptionProperty);
set => SetValue(CardDescriptionProperty, value);
}
// ...
}

La clase CardView , que se deriva de ContentView , representa un control personalizado que muestra los datos
en un diseño de tipo tarjeta. La clase contiene propiedades, que están respaldadas por propiedades enlazables,
de los datos que muestra. Sin embargo, la clase CardView no define ninguna interfaz de usuario. En su lugar, la
interfaz de usuario se definirá con una plantilla de control. Para más información sobre cómo crear controles
personalizados derivados de ContentView , vea ContentView de :::no-loc(Xamarin.Forms):::.
Se crea una plantilla de control con el tipo ControlTemplate . Cuando se crea una clase ControlTemplate , se
combinan los objetos View para compilar la interfaz de usuario de un control o página personalizados. Una
clase ControlTemplate solo debe tener un objeto View como elemento raíz. Sin embargo, el elemento raíz
normalmente contiene otros objetos View . La combinación de objetos constituye la estructura visual del
control.
Aunque se puede definir una clase ControlTemplate en línea, el enfoque habitual para declararla
ControlTemplate es como un recurso de un diccionario de recursos. Dado que las plantillas de control son
recursos, siguen las mismas reglas de ámbito que se aplican a todos los recursos. Por ejemplo, si se declara una
plantilla de control en el elemento raíz del archivo XAML de definición de la aplicación, se puede usar en
cualquier parte de esta. Si define la plantilla en una página, solo esa página puede usar la plantilla de control.
Para más información sobre los recursos, vea Diccionarios de recursos de :::no-loc(Xamarin.Forms):::.
En el siguiente ejemplo de XAML se muestra una clase ControlTemplate para objetos CardView :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
<Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{Binding BorderColor}"
BackgroundColor="{Binding IconBackgroundColor}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
<Image Source="{Binding IconImageSource}"
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
Text="{Binding CardTitle}"
FontAttributes="Bold"
FontSize="Large"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{Binding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="{Binding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
...
</ContentPage>

Cuando se declara una clase ControlTemplate como un recurso, debe tener una clave especificada con el
atributo x:Key para que se pueda identificar en el diccionario de recursos. En este ejemplo, el elemento raíz de
CardViewControlTemplate es un objeto Frame . El objeto Frame usa la extensión de marcado RelativeSource
para establecer su elemento BindingContext en la instancia del objeto en tiempo de ejecución a la que se
aplicará la plantilla, lo que se conoce como elemento primario con plantilla. El objeto Frame usa una
combinación de objetos Grid , Frame , Image , Label , and BoxView para definir la estructura visual de un
objeto CardView . Las expresiones de enlace de estos objetos se resuelven a partir de las propiedades CardView
, debido a la herencia de BindingContext del elemento Frame raíz. Para más información sobre la extensión de
marcado RelativeSource , consulte Enlaces relativos de :::no-loc(Xamarin.Forms):::.

Consumo de una clase ControlTemplate


Una clase ControlTemplate se puede aplicar a un control personalizado derivado ContentView mediante el
establecimiento de su propiedad ControlTemplate en el objeto de la plantilla de control. Igualmente, una clase
ControlTemplate se puede aplicar a una página derivada ContentPage mediante el establecimiento de su
propiedad ControlTemplate en el objeto de la plantilla de control. Cuando se aplica una clase ControlTemplate
en tiempo de ejecución, todos los controles que se definen en ella ControlTemplate se agregan al árbol visual
del control personalizado, o la página, con plantilla.
En el ejemplo siguiente se muestra que CardViewControlTemplate se asigna a la propiedad ControlTemplate de
cada objeto CardView :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<StackLayout Margin="30">
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla
elit dolor, convallis non interdum."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim
fermentum. Morbi ut lacus vitae eros lacinia."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Xamarin Monkey"
CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat
scelerisque erat, quis aliquet arcu."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
</StackLayout>
</ContentPage>

En este ejemplo, los controles de CardViewControlTemplate forman parte del árbol visual de cada objeto
CardView . Dado que el objeto Frame raíz de la plantilla de control establece su elemento BindingContext en el
elemento primario con plantilla, Frame y sus elementos secundarios resuelven su expresión de enlace a partir
de las propiedades de cada objeto CardView .
Las capturas de pantallas siguientes muestran que CardViewControlTemplate se ha aplicado a tres objetos
CardView :
IMPORTANT
El momento en el que se aplica ControlTemplate a una instancia de control se puede detectar si se invalida el método
OnApplyTemplate en el control personalizado, o la página, con plantilla. Para más información, consulte Obtención de
un elemento con nombre de una plantilla.

Paso de parámetros con TemplateBinding


La extensión de marcado TemplateBinding enlaza una propiedad de un elemento que se encuentra en una
clase ControlTemplate a una propiedad pública definida por el control personalizado, o la página, con plantilla.
Cuando se usa TemplateBinding , permite que las propiedades del control actúen como parámetros para la
plantilla. Por consiguiente, cuando se establece una propiedad de un control personalizado, o una página, con
plantilla, ese valor se pasa al elemento que contiene TemplateBinding .

IMPORTANT
La expresión de marcado TemplateBinding permite quitar el enlace de RelativeSource de la plantilla de control
anterior y reemplazar las expresiones de Binding .

La extensión de marcado TemplateBinding define las siguientes propiedades:


Path , de tipo string , la ruta de acceso a la propiedad.
Mode , de tipo BindingMode , la dirección en la que los cambios se propagan entre source y target.
Converter , de tipo IValueConverter , el convertidor de valores de enlace.
ConverterParameter , de tipo object , el parámetro para el convertidor de valores de enlace.
StringFormat , de tipo string , el formato de cadena del enlace.

El valor de ContentProperty para la extensión de marcado TemplateBinding es Path . Por lo tanto, la parte
"Path=" de la extensión de marcado se puede omitir si la ruta de acceso es el primer elemento de la expresión
TemplateBinding . Para más información sobre el uso de estas propiedades en una expresión de enlace,
consulte Enlace de datos de :::no-loc(Xamarin.Forms):::.

WARNING
La extensión de marcado TemplateBinding solo debe usarse dentro de una clase ControlTemplate . Sin embargo, al
intentar usar una expresión TemplateBinding fuera de una clase ControlTemplate , no se producirá un error de
compilación o se producirá una excepción.
En el siguiente ejemplo de XAML se muestra una clase ControlTemplate para objetos CardView , que usa la
extensión de marcado TemplateBinding :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
<Frame BackgroundColor="{TemplateBinding CardColor}"
BorderColor="{TemplateBinding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{TemplateBinding BorderColor}"
BackgroundColor="{TemplateBinding IconBackgroundColor}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
<Image Source="{TemplateBinding IconImageSource}"
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
Text="{TemplateBinding CardTitle}"
FontAttributes="Bold"
FontSize="Large"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{TemplateBinding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="{TemplateBinding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
...
</ContentPage>

En este ejemplo, la extensión de marcado TemplateBinding resuelve las expresiones de enlace a partir de las
propiedades de cada objeto CardView . Las capturas de pantallas siguientes muestran que
CardViewControlTemplate se ha aplicado a tres objetos CardView :

IMPORTANT
El uso de la extensión de marcado TemplateBinding es equivalente a establecer el elemento BindingContext del
elemento raíz de la plantilla en su elemento primario con plantilla con la extensión de marcado RelativeSource y,
luego, resolver los enlaces de objetos secundarios con la extensión de marcado Binding . De hecho, la extensión de
marcado TemplateBinding crea un elemento Binding cuyo valor de Source es
RelativeBindingSource.TemplatedParent .

Aplicación de una clase ControlTemplate con un estilo


También se pueden aplicar plantillas de control con estilos. Para ello se crea un estilo implicit o explicit que
consume ControlTemplate .
En el siguiente ejemplo de XAML se muestra un estilo implicit que consume el objeto CardViewControlTemplate :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
...
</ControlTemplate>

<Style TargetType="controls:CardView">
<Setter Property="ControlTemplate"
Value="{StaticResource CardViewControlTemplate}" />
</Style>
</ContentPage.Resources>
<StackLayout Margin="30">
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla
elit dolor, convallis non interdum."
IconBackgroundColor="SlateGray"
IconImageSource="user.png" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim
fermentum. Morbi ut lacus vitae eros lacinia."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"/>
<controls:CardView BorderColor="DarkGray"
CardTitle="Xamarin Monkey"
CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat
scelerisque erat, quis aliquet arcu."
IconBackgroundColor="SlateGray"
IconImageSource="user.png" />
</StackLayout>
</ContentPage>

En este ejemplo, el estilo implicit Style se aplica automáticamente a cada objeto CardView y se establece la
propiedad ControlTemplate de cada CardView en CardViewControlTemplate .
Para obtener más información sobre los estilos, vea Estilos de :::no-loc(Xamarin.Forms):::.

Redefinición de la interfaz de usuario de un control


Cuando se crea una instancia de ControlTemplate y se asigna a la propiedad ControlTemplate de un control
personalizado derivado ContentView o a una página derivada ContentPage , la estructura visual definida para el
control personalizado, o la página, se reemplaza por la estructura visual definida en la clase ControlTemplate .
Por ejemplo, el control personalizado CardViewUI define su interfaz de usuario mediante el código XAML
siguiente:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ControlTemplateDemos.Controls.CardViewUI"
x:Name="this">
<Frame BindingContext="{x:Reference this}"
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{Binding BorderColor, FallbackValue='Black'}"
BackgroundColor="{Binding IconBackgroundColor, FallbackValue='Gray'}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
<Image Source="{Binding IconImageSource}"
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
Text="{Binding CardTitle, FallbackValue='Card title'}"
FontAttributes="Bold"
FontSize="Large"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{Binding BorderColor, FallbackValue='Black'}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="{Binding CardDescription, FallbackValue='Card description'}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ContentView>

Sin embargo, los controles que componen esta interfaz de usuario se pueden reemplazar definiendo una nueva
estructura visual en una clase ControlTemplate y asignándola a la propiedad ControlTemplate de un objeto
CardViewUI :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewCompressed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{TemplateBinding IconImageSource}"
BackgroundColor="{TemplateBinding IconBackgroundColor}"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill"
HorizontalOptions="Center"
VerticalOptions="Center" />
<StackLayout Grid.Column="1">
<Label Text="{TemplateBinding CardTitle}"
FontAttributes="Bold" />
<Label Text="{TemplateBinding CardDescription}" />
</StackLayout>
</Grid>
</ControlTemplate>
</ContentPage.Resources>
<StackLayout Margin="30">
<controls:CardViewUI BorderColor="DarkGray"
CardTitle="John Doe"
CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nulla elit dolor, convallis non interdum."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewCompressed}" />
<controls:CardViewUI BorderColor="DarkGray"
CardTitle="Jane Doe"
CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim
fermentum. Morbi ut lacus vitae eros lacinia."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewCompressed}" />
<controls:CardViewUI BorderColor="DarkGray"
CardTitle="Xamarin Monkey"
CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat
scelerisque erat, quis aliquet arcu."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource CardViewCompressed}" />
</StackLayout>
</ContentPage>

En este ejemplo, la estructura visual del objeto CardViewUI se redefine en una clase ControlTemplate que
proporciona una estructura visual más compacta adecuada para una lista condensada:
Sustitución del contenido por un objeto ContentPresenter
Se puede colocar un objeto ContentPresenter en una plantilla de control para marcar dónde aparecerá el
contenido que se va a mostrar en el control personalizado, o la página, con plantilla. El control personalizado, o
la página, que consume la plantilla de control definirá entonces el contenido que se mostrará mediante
ContentPresenter . En el diagrama siguiente se ilustra un elemento ControlTemplate para una página que
contiene una serie controles, incluido un control ContentPresenter marcado por un rectángulo de color azul:

En el código XAML siguiente se muestra una plantilla de control denominada TealTemplate que contiene un
elemento ContentPresenter en su estructura visual:
<ControlTemplate x:Key="TealTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*" />
<RowDefinition Height="0.8*" />
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<BoxView Color="Teal" />
<Label Margin="20,0,0,0"
Text="{TemplateBinding HeaderText}"
TextColor="White"
FontSize="Title"
VerticalOptions="Center" />
<ContentPresenter Grid.Row="1" />
<BoxView Grid.Row="2"
Color="Teal" />
<Label x:Name="changeThemeLabel"
Grid.Row="2"
Margin="20,0,0,0"
Text="Change Theme"
TextColor="White"
HorizontalOptions="Start"
VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
</Label.GestureRecognizers>
</Label>
<controls:HyperlinkLabel Grid.Row="2"
Margin="0,0,20,0"
Text="Help"
TextColor="White"
Url="https://docs.microsoft.com/xamarin/xamarin-forms/"
HorizontalOptions="End"
VerticalOptions="Center" />
</Grid>
</ControlTemplate>

En el ejemplo siguiente se muestra que TealTemplate se ha asignado a la propiedad ControlTemplate de una


página derivada ContentPage :

<controls:HeaderFooterPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
ControlTemplate="{StaticResource TealTemplate}"
HeaderText="MyApp"
...>
<StackLayout Margin="10">
<Entry Placeholder="Enter username" />
<Entry Placeholder="Enter password"
IsPassword="True" />
<Button Text="Login" />
</StackLayout>
</controls:HeaderFooterPage>

Cuando se aplica TealTemplate a la página en tiempo de ejecución, el contenido de la página se sustituye por
el elemento ContentPresenter definido en la plantilla de control:
Obtención de un elemento con nombre desde una plantilla
Los elementos con nombre dentro de una plantilla de control se pueden recuperar del control personalizado, o
la página, con plantilla. Para ello se puede usar el método GetTemplateChild , que devuelve el elemento con
nombre en el árbol visual ControlTemplate con instancias, si se encuentra. De lo contrario, devuelve null .
Una vez que se crearon instancias de una plantilla de control, se llama al método OnApplyTemplate de la
plantilla. Por lo tanto, se debe llamar al método GetTemplateChild desde una invalidación de OnApplyTemplate
en el control, o la página, con plantilla.

IMPORTANT
Se debe llamar al método GetTemplateChild solo una vez que se ha llamado al método OnApplyTemplate .

En el siguiente código XAML se muestra una plantilla de control denominada TealTemplate que se puede
aplicar a páginas derivadas ContentPage :

<ControlTemplate x:Key="TealTemplate">
<Grid>
...
<Label x:Name="changeThemeLabel"
Grid.Row="2"
Margin="20,0,0,0"
Text="Change Theme"
TextColor="White"
HorizontalOptions="Start"
VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
</Label.GestureRecognizers>
</Label>
...
</Grid>
</ControlTemplate>

En este ejemplo, se designa el elemento Label , y se puede recuperar en el código de la página con plantilla.
Para ello, se llama al método GetTemplateChild desde la invalidación OnApplyTemplate de la página con
plantilla:
public partial class AccessTemplateElementPage : HeaderFooterPage
{
Label themeLabel;

public AccessTemplateElementPage()
{
InitializeComponent();
}

protected override void OnApplyTemplate()


{
base.OnApplyTemplate();
themeLabel = (Label)GetTemplateChild("changeThemeLabel");
themeLabel.Text = OriginalTemplate ? "Aqua Theme" : "Teal Theme";
}
}

En este ejemplo, se recupera el objeto Label llamado changeThemeLabel una vez que se ha creado una
instancia de ControlTemplate . Luego, la clase AccessTemplateElementPage puede acceder a changeThemeLabel y
manipularlo. Las capturas de pantalla siguientes muestran que se ha cambiado el texto mostrado por el Label :

Enlace a ViewModel
Una clase ControlTemplate puede enlazar datos a un objeto ViewModel, incluso cuando ControlTemplate
enlaza al elemento primario con plantilla (la instancia del objeto en tiempo de ejecución a la que se aplica la
plantilla).
En el siguiente ejemplo de XAML se muestra una página que consume un objeto ViewModel llamado
PeopleViewModel :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ControlTemplateDemos"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<ContentPage.BindingContext>
<local:PeopleViewModel />
</ContentPage.BindingContext>

<ContentPage.Resources>
<DataTemplate x:Key="PersonTemplate">
<controls:CardView BorderColor="DarkGray"
CardTitle="{Binding Name}"
CardDescription="{Binding Description}"
ControlTemplate="{StaticResource CardViewControlTemplate}" />
</DataTemplate>
</ContentPage.Resources>

<StackLayout Margin="10"
BindableLayout.ItemsSource="{Binding People}"
BindableLayout.ItemTemplate="{StaticResource PersonTemplate}" />
</ContentPage>

En este ejemplo, el elemento BindingContext de la página se establece en una instancia PeopleViewModel . Este
objeto ViewModel expone una colección People y un elemento ICommand llamado DeletePersonCommand . El
elemento StackLayout de la página un diseño enlazable para enlazar datos a la colección People y el
elemento ItemTemplate del diseño enlazable está establecido en el recurso PersonTemplate . Este elemento
DataTemplate especifica que cada elemento de la colección People se mostrará mediante un objeto CardView .
La estructura visual del objeto CardView se define mediante un elemento ControlTemplate denominado
CardViewControlTemplate :
<ControlTemplate x:Key="CardViewControlTemplate">
<Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="{Binding CardTitle}"
FontAttributes="Bold"
FontSize="Large"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
BackgroundColor="{Binding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Text="{Binding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
<Button Text="Delete"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:PeopleViewModel}},
Path=DeletePersonCommand}"
CommandParameter="{Binding CardTitle}"
HorizontalOptions="End" />
</Grid>
</Frame>
</ControlTemplate>

En este ejemplo, el elemento raíz de ControlTemplate es un objeto Frame . El objeto Frame usa la extensión de
marcado RelativeSource para establecer su BindingContext en el elemento primario con plantilla. Las
expresiones de enlace del objeto Frame y sus elementos secundarios se resuelven a partir de propiedades
CardView , por la herencia de BindingContext del elemento Frame raíz. Las capturas de pantallas siguientes
muestran la página con la colección People , que consta de tres elementos:

Mientras que los objetos ControlTemplate enlazan a propiedades de su elemento primario con plantilla, el
elemento Button de la plantilla de control enlaza a su elemento primario con plantilla y al elemento
DeletePersonCommand de ViewModel. Esto se debe a que la propiedad Button.Command vuelve a definir su origen
de enlace como el contexto de enlace del antecesor cuyo tipo de contexto de enlace es PeopleViewModel , que es
el elemento StackLayout . Luego, la parte Path de las expresiones de enlace puede resolver la propiedad
DeletePersonCommand . Sin embargo, la propiedad Button.CommandParameter no modifica su origen de enlace,
sino que la hereda de su elemento primario en la clase ControlTemplate . Por lo tanto, la propiedad
CommandParameter enlaza a la propiedad CardTitle de CardView .

El efecto general de los enlaces Button es que cuando se pulsa el elemento Button , se ejecuta el elemento
DeletePersonCommand de la clase PeopleViewModel , y se pasa el valor de la propiedad CardName a
DeletePersonCommand . Como resultado, el objeto CardView especificado se quita del diseño enlazable:

Para más información sobre los enlaces relativos, consulte Enlaces relativos de :::no-loc(Xamarin.Forms):::.

Vínculos relacionados
ControlTemplateDemos (ejemplo)
ContentView de :::no-loc(Xamarin.Forms):::
Enlaces relativos de :::no-loc(Xamarin.Forms):::
Diccionarios de recursos de :::no-loc(Xamarin.Forms):::
Enlace de datos de :::no-loc(Xamarin.Forms):::
Estilos de :::no-loc(Xamarin.Forms):::
Plantillas de datos de :::no-loc(Xamarin.Forms):::
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Las plantillas de datos se usan para especificar el aspecto de los datos en los controles admitidos y
normalmente se enlaza a los datos que se van a mostrar.

Introducción
Las plantillas de datos de :::no-loc(Xamarin.Forms)::: permiten definir la presentación de los datos en los
controles admitidos. En este artículo se ofrece una introducción a las plantillas de datos y se analiza por qué
son necesarias.

Crear una plantilla de datos


Las plantillas de datos se pueden crear en línea, en ResourceDictionary , o a partir de un tipo personalizado o
un tipo de celda de :::no-loc(Xamarin.Forms)::: adecuado. Una plantilla insertada se debe usar si no es necesario
volver a utilizarla en otro lugar. Como alternativa, se puede reutilizar una plantilla de datos si se define como un
tipo personalizado, o como un recurso de nivel de página o el nivel de aplicación de nivel de control.

Crear un DataTemplateSelector
Un DataTemplateSelector se puede usar para elegir DataTemplate en tiempo de ejecución según el valor de
una propiedad enlazada a datos. Esto permite aplicar varias instancias de DataTemplate al mismo tipo de
objeto para personalizar la apariencia de objetos concretos. En este artículo se explica cómo crear y consumir
un DataTemplateSelector .

Vínculos relacionados
Plantillas de datos (ejemplo)
Introducción a las plantillas de datos de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Las plantillas de datos de :::no-loc(Xamarin.Forms)::: permiten definir la presentación de los datos en los controles
admitidos. En este artículo se ofrece una introducción a las plantillas de datos y se analiza por qué son necesarias.
Considere la posibilidad de un elemento ListView que muestra una colección de objetos Person . En el ejemplo de
código siguiente se muestra la definición de la clase Person :

public class Person


{
public string Name { get; set; }
public int Age { get; set; }
public string Location { get; set; }
}

La clase Person define las propiedades Name , Age y Location , que se pueden establecer al crear un objeto
Person . El objeto ListView se usa para mostrar la colección de objetos Person , como se muestra en el ejemplo
de código XAML siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataTemplates"
...>
<StackLayout Margin="20">
...
<ListView Margin="0,20,0,0">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:Person}">
<local:Person Name="Steve" Age="21" Location="USA" />
<local:Person Name="John" Age="37" Location="USA" />
<local:Person Name="Tom" Age="42" Location="UK" />
<local:Person Name="Lucas" Age="29" Location="Germany" />
<local:Person Name="Tariq" Age="39" Location="UK" />
<local:Person Name="Jane" Age="30" Location="USA" />
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
</ContentPage>

Se agregan elementos al objeto ListView en XAML mediante la inicialización de la propiedad ItemsSource a partir
de una matriz de instancias de Person .

NOTE
Tenga en cuenta que el elemento x:Array requiere un atributo Type que indica el tipo de los elementos de la matriz.

La página de C# equivalente se muestra en el ejemplo siguiente de código, en el que se inicializa la propiedad


ItemsSource en un elemento List de instancias de Person :
public WithoutDataTemplatePageCS()
{
...
var people = new List<Person>
{
new Person { Name = "Steve", Age = 21, Location = "USA" },
new Person { Name = "John", Age = 37, Location = "USA" },
new Person { Name = "Tom", Age = 42, Location = "UK" },
new Person { Name = "Lucas", Age = 29, Location = "Germany" },
new Person { Name = "Tariq", Age = 39, Location = "UK" },
new Person { Name = "Jane", Age = 30, Location = "USA" }
};

Content = new StackLayout


{
Margin = new Thickness(20),
Children = {
...
new ListView { ItemsSource = people, Margin = new Thickness(0, 20, 0, 0) }
}
};
}

ListView llama a ToString cuando se muestran los objetos de la colección. Como no hay ninguna invalidación de
Person.ToString , ToString devuelve el nombre de tipo de cada objeto, como se muestra en las capturas de
pantalla siguientes:

El objeto Person puede invalidar el método ToString para mostrar datos significativos, como se muestra en el
ejemplo de código siguiente:

public class Person


{
...
public override string ToString ()
{
return Name;
}
}

Como resultado, ListView muestra el valor de propiedad Person.Name para cada objeto de la colección, como se
muestra en las capturas de pantalla siguientes:
La invalidación de Person.ToString podría devolver una cadena con formato formada por las propiedades Name ,
Age y Location . Pero este enfoque solo ofrece un control limitado sobre la apariencia de cada elemento de datos.
Para obtener más flexibilidad, se puede crear un elemento DataTemplate que defina la apariencia de los datos.

Creación de una plantilla de datos


DataTemplate se usa para especificar la apariencia de los datos, y normalmente se utiliza el enlace de datos para
mostrar los datos. Un escenario de uso común es cuando se muestran datos de una colección de objetos en un
control ListView . Por ejemplo, cuando se enlaza un elemento ListView a una colección de objetos Person , la
propiedad ListView.ItemTemplate se establecerá en un elemento DataTemplate que define la apariencia de cada
objeto Person de ListView . DataTemplate contendrá los elementos que se enlazan a los valores de propiedad de
cada objeto Person . Para más información sobre el enlace de datos, consulte Data Binding Basics (Aspectos
básicos del enlace de datos).
Se puede usar un elemento DataTemplate como un valor para las propiedades siguientes:
ListView.HeaderTemplate
ListView.FooterTemplate
ListView.GroupHeaderTemplate
ItemsView.ItemTemplate , que es heredado por ListView .
MultiPage.ItemTemplate , que es heredado por CarouselPage , MasterDetailPage y TabbedPage .

NOTE
Tenga en cuenta que aunque TableView usa objetos Cell , no usa un elemento DataTemplate . Esto se debe a que los
enlaces de datos siempre se establecen directamente en objetos Cell .

Una instancia de DataTemplate que se coloca como un elemento secundario directo de las propiedades
enumeradas anteriormente se conoce como una plantilla insertada. Como alternativa, se puede definir un
elemento DataTemplate como un recurso de nivel de control, de página o de aplicación. La elección de dónde se
puede definir una instancia de DataTemplate afecta a dónde se puede usar:
Una instancia de DataTemplate definida en el nivel de control solo se puede aplicar al control.
Un instancia de DataTemplate definida en el nivel de página se puede aplicar a varios controles válidos de la
página.
Una instancia de DataTemplate definida en el nivel de aplicación se puede aplicar a los controles válidos de toda
la aplicación.
Las plantillas de datos situadas más abajo en la jerarquía de vistas tienen prioridad sobre las definidas más arriba
cuando comparten atributos x:Key . Por ejemplo, una plantilla de datos de nivel de aplicación se reemplazará por
una plantilla de datos de nivel de página, y una plantilla de datos de nivel de página se reemplazará por una
plantilla de datos de nivel de control, o bien una plantilla de datos insertada.

Vínculos relacionados
Apariencia de etiqueta
Plantillas de datos (ejemplo)
DataTemplate
Creación de una plantilla de datos de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Las plantillas de datos se pueden crear insertadas, en un objeto ResourceDictionary, o bien a partir de un tipo
personalizado o un tipo de celda de :::no-loc(Xamarin.Forms)::: adecuado. En este artículo se explora cada una de
las técnicas.
Un escenario de uso común para un elemento DataTemplate es mostrar datos de una colección de objetos en un
control ListView . La apariencia de los datos de cada celda del control ListView se puede administrar mediante el
establecimiento de la propiedad ListView.ItemTemplate en un elemento DataTemplate . Se pueden usar varias
técnicas para realizar esta acción:
Creación de una plantilla de datos insertada.
Creación de una plantilla de datos con un tipo.
Creación de una plantilla de datos como un recurso.
Independientemente de la técnica que se use, el resultado es que la apariencia de cada celda del control ListView
se define mediante un elemento DataTemplate , como se muestra en las capturas de pantalla siguientes:

Creación de una plantilla de datos insertada


La propiedad ListView.ItemTemplate se puede establecer en un elemento DataTemplate insertado. Una plantilla
insertada, que es la que se coloca como elemento secundario directo de una propiedad de control adecuada, se
debe usar si no hay ninguna necesidad de reutilizar la plantilla de datos en otros puntos. Los elementos
especificados en el elemento DataTemplate definen la apariencia de cada celda, como se muestra en el ejemplo de
código XAML siguiente:
<ListView Margin="0,20,0,0">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:Person}">
<local:Person Name="Steve" Age="21" Location="USA" />
<local:Person Name="John" Age="37" Location="USA" />
<local:Person Name="Tom" Age="42" Location="UK" />
<local:Person Name="Lucas" Age="29" Location="Germany" />
<local:Person Name="Tariq" Age="39" Location="UK" />
<local:Person Name="Jane" Age="30" Location="USA" />
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
...
<Label Text="{Binding Name}" FontAttributes="Bold" />
<Label Grid.Column="1" Text="{Binding Age}" />
<Label Grid.Column="2" Text="{Binding Location}" HorizontalTextAlignment="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

El elemento secundario de un elemento DataTemplate insertado debe ser de tipo Cell , o bien derivarse de él. En
este ejemplo se usa un ViewCell , que procede de Cell . Aquí, el diseño dentro de ViewCell se administra
mediante un control Grid . Grid contiene tres instancias de Label que enlazan sus propiedades Text a las
propiedades adecuadas de cada objeto Person de la colección.
El código de C# equivalente se muestra en el ejemplo de código siguiente:
public class WithDataTemplatePageCS : ContentPage
{
public WithDataTemplatePageCS()
{
...
var people = new List<Person>
{
new Person { Name = "Steve", Age = 21, Location = "USA" },
...
};

var personDataTemplate = new DataTemplate(() =>


{
var grid = new Grid();
...
var nameLabel = new Label { FontAttributes = FontAttributes.Bold };
var ageLabel = new Label();
var locationLabel = new Label { HorizontalTextAlignment = TextAlignment.End };

nameLabel.SetBinding(Label.TextProperty, "Name");
ageLabel.SetBinding(Label.TextProperty, "Age");
locationLabel.SetBinding(Label.TextProperty, "Location");

grid.Children.Add(nameLabel);
grid.Children.Add(ageLabel, 1, 0);
grid.Children.Add(locationLabel, 2, 0);

return new ViewCell { View = grid };


});

Content = new StackLayout


{
Margin = new Thickness(20),
Children = {
...
new ListView { ItemsSource = people, ItemTemplate = personDataTemplate, Margin = new
Thickness(0, 20, 0, 0) }
}
};
}
}

En C#, el elemento DataTemplate insertado se crea mediante una sobrecarga del constructor que especifica un
argumento Func .

Creación de una plantilla de datos con un tipo


La propiedad ListView.ItemTemplate también se puede establecer en un elemento DataTemplate creado a partir
de un tipo de celda. La ventaja de este enfoque es que la apariencia definida por el tipo de celda se puede reutilizar
en varias plantillas de datos en toda la aplicación. En el código XAML siguiente se muestra un ejemplo de este
enfoque:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataTemplates"
...>
<StackLayout Margin="20">
...
<ListView Margin="0,20,0,0">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:Person}">
<local:Person Name="Steve" Age="21" Location="USA" />
...
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<local:PersonCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

En este caso, la propiedad ListView.ItemTemplate se establece en un elemento DataTemplate creado a partir de un


tipo personalizado que define el aspecto de la celda. El tipo personalizado se debe derivar del tipo ViewCell , como
se muestra en el ejemplo de código siguiente:

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTemplates.PersonCell">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" FontAttributes="Bold" />
<Label Grid.Column="1" Text="{Binding Age}" />
<Label Grid.Column="2" Text="{Binding Location}" HorizontalTextAlignment="End" />
</Grid>
</ViewCell>

Dentro de ViewCell , el diseño se administra mediante un control Grid . Grid contiene tres instancias de Label
que enlazan sus propiedades Text a las propiedades adecuadas de cada objeto Person de la colección.
El código de C# equivalente se muestra en el ejemplo siguiente:
public class WithDataTemplatePageFromTypeCS : ContentPage
{
public WithDataTemplatePageFromTypeCS()
{
...
var people = new List<Person>
{
new Person { Name = "Steve", Age = 21, Location = "USA" },
...
};

Content = new StackLayout


{
Margin = new Thickness(20),
Children = {
...
new ListView { ItemTemplate = new DataTemplate(typeof(PersonCellCS)), ItemsSource = people,
Margin = new Thickness(0, 20, 0, 0) }
}
};
}
}

En C#, DataTemplate se crea mediante una sobrecarga del constructor que especifica el tipo de celda como
argumento. El tipo de celda se debe derivar del tipo ViewCell , como se muestra en el ejemplo de código siguiente:

public class PersonCellCS : ViewCell


{
public PersonCellCS()
{
var grid = new Grid();
...
var nameLabel = new Label { FontAttributes = FontAttributes.Bold };
var ageLabel = new Label();
var locationLabel = new Label { HorizontalTextAlignment = TextAlignment.End };

nameLabel.SetBinding(Label.TextProperty, "Name");
ageLabel.SetBinding(Label.TextProperty, "Age");
locationLabel.SetBinding(Label.TextProperty, "Location");

grid.Children.Add(nameLabel);
grid.Children.Add(ageLabel, 1, 0);
grid.Children.Add(locationLabel, 2, 0);

View = grid;
}
}

NOTE
Tenga en cuenta que :::no-loc(Xamarin.Forms)::: también incluye tipos de celda que se pueden usar para mostrar datos
simples en celdas ListView . Para obtener más información, vea Apariencia de una celda.

Creación de una plantilla de datos con un recurso


Las plantillas de datos también se pueden crear como objetos reutilizables en un objeto ResourceDictionary . Esto
se consigue mediante la asignación de un atributo x:Key único a cada declaración, para proporcionarle una clave
descriptiva en el objeto ResourceDictionary , como se muestra en el ejemplo de código XAML siguiente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="personTemplate">
<ViewCell>
<Grid>
...
</Grid>
</ViewCell>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="20">
...
<ListView ItemTemplate="{StaticResource personTemplate}" Margin="0,20,0,0">
<ListView.ItemsSource>
<x:Array Type="{x:Type local:Person}">
<local:Person Name="Steve" Age="21" Location="USA" />
...
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
</ContentPage>

El elemento se asigna a la propiedad ListView.ItemTemplate mediante la extensión de marcado


DataTemplate
StaticResource . Tenga en cuenta que aunque el elemento DataTemplate se defina en el objeto ResourceDictionary
de la página, también se puede definir en el nivel de control o aplicación.
En el ejemplo de código siguiente se muestra la página equivalente en C#:

public class WithDataTemplatePageCS : ContentPage


{
public WithDataTemplatePageCS ()
{
...
var personDataTemplate = new DataTemplate (() => {
var grid = new Grid ();
...
return new ViewCell { View = grid };
});

Resources = new ResourceDictionary ();


Resources.Add ("personTemplate", personDataTemplate);

Content = new StackLayout {


Margin = new Thickness(20),
Children = {
...
new ListView { ItemTemplate = (DataTemplate)Resources ["personTemplate"], ItemsSource = people };
}
};
}
}

El elemento DataTemplate se agrega a ResourceDictionary con el método Add , que especifica una cadena Key
que se usa para hacer referencia al elemento DataTemplate al recuperarlo.

Resumen
En este artículo se ha explicado cómo crear plantillas de datos, insertadas, a partir de un tipo personalizado, o bien
en un objeto ResourceDictionary . Una plantilla insertada se debe usar si no es necesario volver a utilizarla en otro
lugar. Como alternativa, se puede reutilizar una plantilla de datos si se define como un tipo personalizado, o bien
como un recurso de nivel de página, nivel de aplicación o de nivel de control.

Vínculos relacionados
Apariencia de etiqueta
Plantillas de datos (ejemplo)
DataTemplate
Creación de DataTemplateSelector de :::no-
loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Un elemento DataTemplateSelector se puede usar para elegir una plantilla de datos en tiempo de ejecución
según el valor de una propiedad enlazada a datos. Esto permite aplicar varias instancias de DataTemplate al
mismo tipo de objeto, para personalizar la apariencia de objetos concretos. En este artículo se explica cómo crear
y consumir una instancia de DataTemplateSelector.
Un selector de plantillas de datos habilita escenarios como el enlace de ListView a una colección de objetos,
donde la apariencia de cada objeto de ListView se puede elegir en tiempo de ejecución mediante el selector de
plantillas de datos devolviendo un elemento DataTemplate determinado.

Creación de DataTemplateSelector
Un selector de plantillas de datos se implementa mediante la creación de una clase que hereda de
DataTemplateSelector . Después, se reemplaza el método OnSelectTemplate para devolver un elemento
DataTemplate , como se muestra en el ejemplo de código siguiente:

public class PersonDataTemplateSelector : DataTemplateSelector


{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }

protected override DataTemplate OnSelectTemplate (object item, BindableObject container)


{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}

El método OnSelectTemplate devuelve la plantilla adecuada en función del valor de la propiedad DateOfBirth . La
plantilla que se devuelve es el valor de las propiedades ValidTemplate o InvalidTemplate , que se establecen
cuando se consume PersonDataTemplateSelector .
Después, se puede asignar una instancia de la clase de selector de plantilla de datos a propiedades de control de
:::no-loc(Xamarin.Forms):::, como ListView.ItemTemplate . Para obtener una lista de las propiedades válidas, vea
Creación de una plantilla de datos.
Limitaciones
Las instancias de DataTemplateSelector tienen las limitaciones siguientes:
La subclase DataTemplateSelector siempre debe devolver la misma plantilla para los mismos datos si se
consultan varias veces.
La subclase DataTemplateSelector no debe devolver otra subclase DataTemplateSelector .
La subclase DataTemplateSelector no debe devolver nuevas instancias de DataTemplate en cada llamada. En
su lugar, se debe devolver la misma instancia. De lo contrario, se creará una fuga de memoria y se
deshabilitará la virtualización.
En Android, no puede haber más de 20 plantillas de datos diferentes por ListView .
Consumo de una instancia de DataTemplateSelector en XAML
En XAML, se pueden crear instancias de PersonDataTemplateSelector si se declara como un recurso, como se
muestra en el ejemplo de código siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-
namespace:Selector;assembly=Selector" x:Class="Selector.HomePage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>

Este objeto ResourceDictionary de nivel de página define dos instancias de DataTemplate y una instancia de
PersonDataTemplateSelector . La instancia de PersonDataTemplateSelector establece sus propiedades
ValidTemplate y InvalidTemplate en las instancias de DataTemplate correspondientes mediante la extensión de
marcado StaticResource . Tenga en cuenta que aunque los recursos se definen en el objeto ResourceDictionary
de la página, también se pueden definir en el nivel de control o aplicación.
La instancia de PersonDataTemplateSelector se consume asignándola a la propiedad ListView.ItemTemplate ,
como se muestra en el ejemplo de código siguiente:

<ListView x:Name="listView" ItemTemplate="{StaticResource personDataTemplateSelector}" />

En tiempo de ejecución, ListView llama al método PersonDataTemplateSelector.OnSelectTemplate para cada uno


de los elementos de la colección subyacente, y la llamada pasa el objeto de datos como el parámetro item .
Después, el elemento DataTemplate devuelto por el método se aplica a ese objeto.
En las capturas de pantalla siguientes se muestra el resultado de la aplicación de PersonDataTemplateSelector por
parte de ListView a cada objeto de la colección subyacente:
Cualquier objeto Person que tenga un valor de propiedad DateOfBirth mayor o igual a 1980 se muestra en
color verde, y los demás objetos se muestran en color rojo.

Consumo de una instancia de DataTemplateSelector en C#


En C#, se pueden crear instancias de PersonDataTemplateSelector y asignarlas a la propiedad
ListView.ItemTemplate , como se muestra en el ejemplo de código siguiente:

public class HomePageCS : ContentPage


{
DataTemplate validTemplate;
DataTemplate invalidTemplate;

public HomePageCS ()
{
...
SetupDataTemplates ();
var listView = new ListView {
ItemsSource = people,
ItemTemplate = new PersonDataTemplateSelector {
ValidTemplate = validTemplate,
InvalidTemplate = invalidTemplate }
};

Content = new StackLayout {


Margin = new Thickness (20),
Children = {
...
listView
}
};
}
...
}

La instancia de PersonDataTemplateSelector establece sus propiedades ValidTemplate y InvalidTemplate en las


instancias de DataTemplate correspondientes creadas por el método SetupDataTemplates . En tiempo de
ejecución, ListView llama al método PersonDataTemplateSelector.OnSelectTemplate para cada uno de los
elementos de la colección subyacente, y la llamada pasa el objeto de datos como el parámetro item . Después, el
elemento DataTemplate devuelto por el método se aplica a ese objeto.
Resumen
En este artículo se ha explicado cómo crear y consumir un elemento DataTemplateSelector .
DataTemplateSelector se puede usar para elegir un elemento DataTemplate en tiempo de ejecución según el
valor de una propiedad enlazada a datos. Esto permite aplicar varias instancias de DataTemplate al mismo tipo
de objeto para personalizar la apariencia de objetos concretos.

Vínculos relacionados
Selector de plantillas de datos (ejemplo)
DataTemplateSelector
Desencadenadores de :::no-loc(Xamarin.Forms):::
18/12/2020 • 24 minutes to read • Edit Online

Descargar el ejemplo
Los desencadenadores permiten expresar acciones de forma declarativa en XAML que cambian la apariencia de
controles en función de eventos o cambios en propiedades. Además, los desencadenadores de estado, que son
un grupo especializado de desencadenadores, definen cuándo se debe aplicar la clase VisualState .
Puede asignar un desencadenador directamente a un control o agregarlo a un diccionario de recursos de nivel
de aplicación o página que se vaya a aplicar a varios controles.

Desencadenadores de propiedad
Un desencadenador simple se puede expresar puramente en XAML, mediante la incorporación de un elemento
Trigger a la colección de desencadenadores de un control. En este ejemplo se muestra un desencadenador que
cambia un color de fondo Entry cuando recibe el foco:

<Entry Placeholder="enter name">


<Entry.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
<!-- multiple Setters elements are allowed -->
</Trigger>
</Entry.Triggers>
</Entry>

Las partes importantes de la declaración del desencadenador son:


TargetType : tipo de control al que se aplica el desencadenador.
Proper ty : propiedad en el control que se supervisa.
Value : valor, cuando se produce para la propiedad supervisada, que hace que el desencadenador se
active.
Setter : colección de elementos Setter que se puede agregar cuando se cumple la condición del
desencadenador. Debe especificar los elementos Property y Value que se van a establecer.
EnterActions y ExitActions (no mostrados): se escriben en código y se pueden usar además de los
elementos Setter (o en su lugar). Se describen abajo.
Aplicar un desencadenador mediante un estilo
Los desencadenadores también se pueden agregar a una declaración Style en un control, en una página o una
aplicación ResourceDictionary . Este ejemplo declara un estilo implícito (es decir, no se establece Key ), lo que
significa que se aplica a todos los controles Entry en la página.
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
<!-- multiple Setters elements are allowed -->
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>

Desencadenadores de datos
Los desencadenadores de datos usan enlaces de datos para supervisar otro control a fin de que se llame a los
elementos Setter . En lugar del atributo Property de un desencadenador de propiedad, establezca el atributo
Binding para supervisar el valor especificado.

El ejemplo siguiente usa la sintaxis de enlace de datos {Binding Source={x:Reference entry}, Path=Text.Length} ,
que es como se hace referencia a las propiedades de otro control. Cuando la longitud de entry es cero, el
desencadenador se activa. En este ejemplo el desencadenador deshabilita el botón cuando la entrada está vacía.

<!-- the x:Name is referenced below in DataTrigger-->


<!-- tip: make sure to set the Text="" (or some other default) -->
<Entry x:Name="entry"
Text=""
Placeholder="required field" />

<Button x:Name="button" Text="Save"


FontSize="Large"
HorizontalOptions="Center">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference entry},
Path=Text.Length}"
Value="0">
<Setter Property="IsEnabled" Value="False" />
<!-- multiple Setters elements are allowed -->
</DataTrigger>
</Button.Triggers>
</Button>

TIP
Al evaluar Path=Text.Length , proporcione siempre un valor predeterminado para la propiedad de destino (p. ej.,
Text="" ) porque, de lo contrario, será null y el desencadenador no funcionará según lo esperado.

Además de especificar los elementos Setter , también puede proporcionar EnterActions y ExitActions .

Desencadenadores de eventos
El elemento EventTrigger solo requiere una propiedad Event , como "Clicked" , en el ejemplo siguiente.
<EventTrigger Event="Clicked">
<local:NumericValidationTriggerAction />
</EventTrigger>

Tenga en cuenta que no hay elementos Setter, sino una referencia a una clase definida por
local:NumericValidationTriggerAction que requiere que se declare xmlns:local en el código XAML de la
página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"

La propia clase implementa TriggerAction , lo que significa que debe proporcionar una invalidación para el
método Invoke al que se llama cada vez que se produce el evento desencadenador.
Una implementación de acción de desencadenador debe:
Implementar la clase genérica TriggerAction<T> , con el parámetro genérico correspondiente al tipo de
control al que se va a aplicar el desencadenador. Puede usar superclases como VisualElement para
escribir acciones de desencadenador que funcionen con una serie de controles, o especificar un tipo de
control como Entry .
Invalidar el método Invoke : se llama a este método cada vez que se cumplen los criterios del
desencadenador.
Opcionalmente, exponer propiedades que se pueden establecer en el código XAML cuando se declara el
desencadenador. Para obtener un ejemplo de esto, vea la clase VisualElementPopTriggerAction en la
aplicación de ejemplo que lo acompaña.

public class NumericValidationTriggerAction : TriggerAction<Entry>


{
protected override void Invoke (Entry entry)
{
double result;
bool isValid = Double.TryParse (entry.Text, out result);
entry.TextColor = isValid ? Color.Default : Color.Red;
}
}

Después, el desencadenador de eventos se puede usar desde XAML:

<EventTrigger Event="TextChanged">
<local:NumericValidationTriggerAction />
</EventTrigger>

Tenga cuidado al compartir desencadenadores en una instancia de ResourceDictionary , ya que una instancia se
comparte entre controles, con lo que cualquier estado que se configure una vez se va a aplicar a todos ellos.
Tenga en cuenta que los desencadenadores de eventos no admiten los elementos EnterActions y ExitActions
descritos abajo.

Multi-desencadenadores
Un elemento MultiTrigger se parece Trigger o DataTrigger , salvo que en él puede haber más de una
condición. Todas las condiciones deben cumplirse para que se desencadenen los elementos Setter .
Este es el ejemplo de un desencadenador de un botón que se enlaza a dos entradas diferentes ( email y phone ):

<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference email},
Path=Text.Length}"
Value="0" />
<BindingCondition Binding="{Binding Source={x:Reference phone},
Path=Text.Length}"
Value="0" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
<!-- multiple Setter elements are allowed -->
</MultiTrigger>

La colección Conditions también puede contener elementos PropertyCondition como este:

<PropertyCondition Property="Text" Value="OK" />

Compilar un multi-desencadenador "require all"


El multi-desencadenador solo actualiza su control cuando se cumplen todas las condiciones. Las pruebas con
"todas las longitudes de campo son cero" (como una página de inicio de sesión donde deben completarse todas
las entradas) son complicadas, porque se quiere una condición "where Text.Length > 0", pero esto no se puede
expresar en XAML.
Se puede hacer con un elemento IValueConverter . El código convertidor siguiente transforma el enlace
Text.Length en un bool que indica si un campo está vacío o no:

public class MultiTriggerConverter : IValueConverter


{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if ((int)value > 0) // length > 0 ?
return true; // some data has been entered
else
return false; // input is empty
}

public object ConvertBack(object value, Type targetType,


object parameter, CultureInfo culture)
{
throw new NotSupportedException ();
}
}

Para usar este convertidor en un multi-desencadenador, primero agréguelo al diccionario de recursos de la


página ( junto con una definición de espacio de nombres personalizada xmlns:local ):

<ResourceDictionary>
<local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
</ResourceDictionary>

A continuación se muestra el código XAML. Observe las siguientes diferencias con respecto al primer ejemplo
de multi-desencadenador:
El botón tiene IsEnabled="false" establecido de forma predeterminada.
Las condiciones del multi-desencadenador usan el convertidor para convertir el valor Text.Length en un
boolean .
Cuando todas las condiciones son true , el establecedor convierte en true la propiedad IsEnabled del
botón.

<Entry x:Name="user" Text="" Placeholder="user name" />

<Entry x:Name="pwd" Text="" Placeholder="password" />

<Button x:Name="loginButton" Text="Login"


FontSize="Large"
HorizontalOptions="Center"
IsEnabled="false">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference user},
Path=Text.Length,
Converter={StaticResource dataHasBeenEntered}}"
Value="true" />
<BindingCondition Binding="{Binding Source={x:Reference pwd},
Path=Text.Length,
Converter={StaticResource dataHasBeenEntered}}"
Value="true" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiTrigger>
</Button.Triggers>
</Button>

Estas capturas de pantalla muestran la diferencia entre los dos ejemplos de multi-desencadenadores anteriores.
En la parte superior de las pantallas, la entrada de texto con un solo elemento Entry basta para habilitar el
botón Guardar . En la parte inferior de las pantallas, el botón Iniciar sesión permanece inactivo hasta que
ambos campos contienen datos.
EnterActions y ExitActions
Otra forma de implementar cambios cuando se produce un desencadenador es mediante la incorporación de
colecciones EnterActions y ExitActions y la especificación de implementaciones TriggerAction<T> .
La colección EnterActions se usa para definir un elemento IList de objetos TriggerAction que se invocará
cuando se cumpla la condición del desencadenador. La colección ExitActions se usa para definir un elemento
IList de objetos TriggerAction que se invocará cuando ya no se cumpla la condición del desencadenador.

NOTE
La clase EventTrigger omite los objetos TriggerAction definidos en las colecciones EnterActions y ExitActions .

Puede proporcionar tanto EnterActions como ExitActions , así como Setter en un desencadenador, pero
tenga en cuenta que se llama a los elementos Setter de inmediato (no se espera a que se completen
EnterAction o ExitAction ). También puede hacer todo en el código y no usar elementos Setter en absoluto.

<Entry Placeholder="enter job title">


<Entry.Triggers>
<Trigger TargetType="Entry"
Property="Entry.IsFocused" Value="True">
<Trigger.EnterActions>
<local:FadeTriggerAction StartsFrom="0" />
</Trigger.EnterActions>

<Trigger.ExitActions>
<local:FadeTriggerAction StartsFrom="1" />
</Trigger.ExitActions>
<!-- You can use both Enter/Exit and Setter together if required -->
</Trigger>
</Entry.Triggers>
</Entry>

Como siempre, cuando se hace referencia a una clase en XAML, se debe declarar un espacio de nombres como
xmlns:local , como se muestra aquí:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"

El código FadeTriggerAction se muestra a continuación:

public class FadeTriggerAction : TriggerAction<VisualElement>


{
public int StartsFrom { set; get; }

protected override void Invoke(VisualElement sender)


{
sender.Animate("FadeTriggerAction", new Animation((d) =>
{
var val = StartsFrom == 1 ? d : 1 - d;
// so i was aiming for a different color, but then i liked the pink :)
sender.BackgroundColor = Color.FromRgb(1, val, 1);
}),
length: 1000, // milliseconds
easing: Easing.Linear);
}
}
Desencadenadores de estado
Los desencadenadores de estado son un conjunto especializado de desencadenadores que definen las
condiciones en las que se debe aplicar VisualState .
Los desencadenadores de estado se agregan a la colección StateTriggers de una clase VisualState . Esta
colección puede contener un único desencadenador de estado o varios desencadenadores de estado. Se aplicará
una clase VisualState cuando cualquier desencadenador de estado de la colección esté activo.
Al usar desencadenadores de estado para controlar los estados visuales, :::no-loc(Xamarin.Forms)::: usa las
siguientes reglas de prioridad para determinar qué desencadenador, y la clase VisualState correspondiente, se
activará:
1. Cualquier desencadenador derivado de StateTriggerBase .
2. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowWidth .
3. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowHeight .

Si hay varios desencadenadores activos simultáneamente (por ejemplo, dos desencadenadores personalizados),
tiene prioridad el primer desencadenador declarado en el marcado.

NOTE
Los desencadenadores de estado se pueden establecer en una clase Style o directamente en los elementos.

Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.
Desencadenador de estado
La clase StateTrigger , que se deriva de la clase StateTriggerBase , tiene una propiedad enlazable IsActive . Un
elemento StateTrigger desencadena un cambio de VisualState cuando cambia el valor de la propiedad
IsActive .

La clase StateTriggerBase, que es la clase base de todos los desencadenadores de estado, tiene una propiedad
IsActive y un evento IsActiveChanged . Este evento se desencadena cuando se produce un cambio de
VisualState . Además, la clase StateTriggerBase tiene métodos OnAttached y OnDetached que se pueden
invalidar.

IMPORTANT
La propiedad enlazable StateTrigger.IsActive oculta la propiedad StateTriggerBase.IsActive heredada.

En el siguiente ejemplo de XAML, se muestra una clase Style que incluye objetos StateTrigger :
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Checked">
<VisualState.StateTriggers>
<StateTrigger IsActive="{Binding IsToggled}"
IsActiveChanged="OnCheckedStateIsActiveChanged" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Black" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.StateTriggers>
<StateTrigger IsActive="{Binding IsToggled, Converter={StaticResource
inverseBooleanConverter}}"
IsActiveChanged="OnUncheckedStateIsActiveChanged" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

En este ejemplo, la clase Style implícita se destina a objetos Grid . Cuando la propiedad IsToggled del objeto
enlazado es true , el color de fondo de Grid se establece en negro. Cuando la propiedad IsToggled del objeto
enlazado se convierte en false , se desencadena un cambio de VisualState , y el color de fondo de Grid
cambia a blanco.
Además, cada vez que cambia una clase VisualState , se desencadena el evento IsActiveChanged para el
elemento VisualState . Cada VisualState registra un controlador de eventos para este evento:

void OnCheckedStateIsActiveChanged(object sender, EventArgs e)


{
StateTriggerBase stateTrigger = sender as StateTriggerBase;
Console.WriteLine($"Checked state active: {stateTrigger.IsActive}");
}

void OnUncheckedStateIsActiveChanged(object sender, EventArgs e)


{
StateTriggerBase stateTrigger = sender as StateTriggerBase;
Console.WriteLine($"Unchecked state active: {stateTrigger.IsActive}");
}

En este ejemplo, cuando se desencadena un controlador para el evento IsActiveChanged , el controlador indica si
la clase VisualState está activa o no. Por ejemplo, los mensajes siguientes aparecen en la ventana de la consola
cuando cambian del estado visual Checked al estado visual Unchecked :

Checked state active: False


Unchecked state active: True
NOTE
Para crear desencadenadores de estado personalizados, derive de la clase StateTriggerBase e invalide los métodos
OnAttached y OnDetached para realizar cualquier registro y limpieza necesarios.

Desencadenador adaptable
Una clase AdaptiveTrigger desencadena un cambio de VisualState cuando la ventana tiene un alto o un ancho
especificado. Este desencadenador tiene dos propiedades enlazables:
MinWindowHeight , de tipo double , que indica la altura mínima de la ventana a la que debe aplicarse
VisualState .
MinWindowWidth , de tipo double , que indica la anchura mínima de la ventana a la que debe aplicarse
VisualState .

NOTE
AdaptiveTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un controlador de eventos al
evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Style que incluye objetos AdaptiveTrigger :

<Style TargetType="StackLayout">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Vertical">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation"
Value="Vertical" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Horizontal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation"
Value="Horizontal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

En este ejemplo, la clase Style implícita se destina a objetos StackLayout . Cuando el ancho de la ventana tiene
entre 0 y 800 unidades independientes del dispositivo, los objetos StackLayout a los que se aplica la clase
Style tendrán una orientación vertical. Cuando el ancho de la ventana es menor o igual que 800 unidades
independientes del dispositivo, se desencadena el cambio de VisualState y la orientación de StackLayout
cambia a horizontal:
Las propiedades MinWindowHeight y MinWindowWidth se pueden utilizar de forma independiente o
combinándolas entre sí. En el siguiente ejemplo de XAML se muestra la configuración de ambas propiedades:

<AdaptiveTrigger MinWindowWidth="800"
MinWindowHeight="1200"/>

En este ejemplo, AdaptiveTrigger indica que la clase correspondiente VisualState se aplicará cuando el ancho
de la ventana actual es mayor o igual que 800 unidades independientes del dispositivo y el alto de la ventana
actual es mayor o igual que 1200 unidades independientes del dispositivo.
Comparación del desencadenador de estado
CompareStateTrigger desencadena un cambio de VisualState cuando una propiedad es igual a un valor
específico. Este desencadenador tiene dos propiedades enlazables:
Property , de tipo object , que indica la propiedad comparada por el desencadenador.
Value , de tipo object , que indica el valor al que debe aplicarse VisualState .

NOTE
CompareStateTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un controlador de eventos al
evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Style que incluye objetos CompareStateTrigger :
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Checked">
<VisualState.StateTriggers>
<CompareStateTrigger Property="{Binding Source={x:Reference checkBox},
Path=IsChecked}"
Value="True" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Black" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.StateTriggers>
<CompareStateTrigger Property="{Binding Source={x:Reference checkBox},
Path=IsChecked}"
Value="False" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
...
<Grid>
<Frame BackgroundColor="White"
CornerRadius="12"
Margin="24"
HorizontalOptions="Center"
VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<CheckBox x:Name="checkBox"
VerticalOptions="Center" />
<Label Text="Check the CheckBox to modify the Grid background color."
VerticalOptions="Center" />
</StackLayout>
</Frame>
</Grid>

En este ejemplo, la clase Style implícita se destina a objetos Grid . Cuando la propiedad IsChecked de
CheckBox es false , el color de fondo de Grid se establece en blanco. Cuando la propiedad
CheckBox.IsChecked se convierte en true , se desencadena un cambio de VisualState , y el color de fondo de
Grid cambia a negro:
Desencadenador de estado de dispositivos
DeviceStateTrigger desencadena un cambio de VisualState basado en la plataforma del dispositivo en que se
ejecuta la aplicación. Este desencadenador tiene una única propiedad enlazable:
Device , de tipo string , que indica la plataforma del dispositivo en la que debe aplicarse VisualState .

NOTE
DeviceStateTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un controlador de eventos al
evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Style que incluye objetos DeviceStateTrigger :
<Style x:Key="DeviceStateTriggerPageStyle"
TargetType="ContentPage">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="iOS">
<VisualState.StateTriggers>
<DeviceStateTrigger Device="iOS" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Silver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Android">
<VisualState.StateTriggers>
<DeviceStateTrigger Device="Android" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="#2196F3" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="UWP">
<VisualState.StateTriggers>
<DeviceStateTrigger Device="UWP" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Aquamarine" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

En este ejemplo, la clase Style explícita se destina a objetos ContentPage . Los objetos ContentPage que usan el
estilo definen su color de fondo en plateado en iOS, en azul pálido en Android y en aguamarina en UWP. En las
capturas de pantalla siguientes se muestran las páginas resultantes en iOS y Android:

Desencadenador de estado de orientación


OrientationStateTrigger desencadena un cambio de VisualState cuando varía la orientación del dispositivo.
Este desencadenador tiene una única propiedad enlazable:
Orientation , de tipo DeviceOrientation , que indica la orientación a la que debe aplicarse VisualState .

NOTE
OrientationStateTrigger deriva de la clase StateTriggerBase y, por tanto, puede adjuntar un controlador de
eventos al evento IsActiveChanged .

En el siguiente ejemplo de XAML, se muestra una clase Style que incluye objetos OrientationStateTrigger :

<Style x:Key="OrientationStateTriggerPageStyle"
TargetType="ContentPage">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Portrait">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Silver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

En este ejemplo, la clase Style explícita se destina a objetos ContentPage . Los objetos ContentPage que usan el
estilo definen su color de fondo en plateado cuando la orientación es vertical y en blanco cuando la orientación
es horizontal.

Vínculos relacionados
Ejemplo de desencadenadores
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
API de desencadenador de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Marcado de C#
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
El marcado de C# es un conjunto opcional de métodos y clases auxiliares fluidas para simplificar el proceso de
creación de interfaces de usuario declarativas :::no-loc(Xamarin.Forms)::: en C#. La API fluida proporcionada por el
marcado de C# está disponible en el :::no-loc(Xamarin.Forms):::.Markup espacio de nombres.
Al igual que con XAML, el marcado de C# permite una separación limpia entre el marcado de la interfaz de usuario
y la lógica de la interfaz de usuario. Esto se puede lograr separando el marcado de la interfaz de usuario y la lógica
de la interfaz de usuario en archivos de clase parcial distintos. Por ejemplo, para una página de inicio de sesión, el
marcado de la interfaz de usuario se encontraba en un archivo denominado LoginPage.CS , mientras que la lógica
de la interfaz de usuario se encontraba en un archivo denominado LoginPage.Logic.CS .
El marcado de C# está disponible en :::no-loc(Xamarin.Forms)::: 4,6. Sin embargo, actualmente es experimental y
solo se puede usar agregando la siguiente línea de código al archivo app.CS :

Device.SetFlags(new string[]{ "Markup_Experimental" });

NOTE
El marcado de C# está disponible en todas las plataformas admitidas por :::no-loc(Xamarin.Forms)::: .

Ejemplo básico
En el ejemplo siguiente se muestra cómo establecer el contenido de la página en un nuevo Grid que contiene un
Label y un Entry , en C#:
Grid grid = new Grid();

Label label = new Label { Text = "Code: " };


grid.Children.Add(label, 0, 1);

Entry entry = new Entry


{
Placeholder = "Enter number",
Keyboard = Keyboard.Numeric,
BackgroundColor = Color.AliceBlue,
TextColor = Color.Black,
FontSize = 15,
HeightRequest = 44,
Margin = fieldMargin
};
grid.Children.Add(entry, 0, 2);
Grid.SetColumnSpan(entry, 2);
entry.SetBinding(Entry.TextProperty, new Binding("RegistrationCode"));

Content = grid;

En este ejemplo se crea un Grid objeto, Label con Entry objetos secundarios y. Label Muestra el texto y los
Entry datos se enlazan a la RegistrationCode propiedad del ViewModel. Cada vista secundaria se establece para
que aparezca en una fila específica de Grid , y Entry abarca todas las columnas de Grid . Además, se establece el
alto de Entry , junto con el teclado, los colores, el tamaño de fuente del texto y su Margin . Por último, la
Page.Content propiedad se establece en el Grid objeto.

El marcado de C# permite que este código se vuelva a escribir mediante su API fluida:

using :::no-loc(Xamarin.Forms):::.Markup;
using static :::no-loc(Xamarin.Forms):::.Markup.GridRowsColumns;

Content = new Grid


{
Children =
{
new Label { Text = "Code:" }
.Row (BodyRow.CodeHeader) .Column (BodyCol.Header),

new Entry { Placeholder = "Enter number", Keyboard = Keyboard.Numeric, BackgroundColor = Color.AliceBlue,


TextColor = Color.Black } .Font (15)
.Row (BodyRow.CodeEntry) .ColumnSpan (All<BodyCol>()) .Margin (fieldMargin) .Height (44)
.Bind (nameof(vm.RegistrationCode))
}
}};

Este ejemplo es idéntico al ejemplo anterior, pero la API fluida de marcado en C# simplifica el proceso de creación
de la interfaz de usuario en C#.

NOTE
El marcado de C# incluye métodos de extensión que establecen propiedades específicas de la vista. Estos métodos de
extensión no están diseñados para reemplazar todos los establecedores de propiedad. En su lugar, están diseñados para
mejorar la legibilidad del código y se pueden usar en combinación con los establecedores de propiedad. Se recomienda usar
siempre un método de extensión cuando exista uno para una propiedad, pero puede elegir el saldo que prefiera.

Enlace de datos
El marcado de C# incluye un Bind método de extensión, junto con sobrecargas, que crea un enlace de datos entre
una propiedad de vista enlazable y una propiedad especificada. El Bind método conoce la propiedad enlazable
predeterminada para la mayoría de los controles que se incluyen en :::no-loc(Xamarin.Forms)::: . Por lo tanto,
normalmente no es necesario especificar la propiedad de destino al utilizar este método. Sin embargo, también
puede registrar la propiedad enlazable predeterminada para controles adicionales:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

DefaultBindableProperties.Register(HoverButton.CommandProperty, RadialGauge.ValueProperty);

El Bind método se puede utilizar para enlazar a cualquier propiedad enlazable:

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new Label { Text = "No data available" }


.Bind (Label.IsVisibleProperty, nameof(vm.Empty))

Además, el BindCommand método de extensión se puede enlazar al valor predeterminado Command y a las
propiedades de un control CommandParameter en una única llamada al método:

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new TextCell { Text = "Tap me" }


.BindCommand (nameof(vm.TapCommand))

De forma predeterminada, CommandParameter se enlaza al contexto de enlace. También puede especificar la ruta de
acceso de enlace y el origen de los Command CommandParameter enlaces y:

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new TextCell { Text = "Tap Me" }


.BindCommand (nameof(vm.TapCommand), vm, nameof(Item.Id))

En este ejemplo, el contexto de enlace es una Item instancia de, por lo que no es necesario especificar un origen
para el Id CommandParameter enlace.
Si solo necesita enlazar a Command , puede pasar null al parameterPath argumento del BindCommand método.
Como alternativa, use el Bind método.
También puede registrar las propiedades y predeterminadas Command CommandParameter para los controles
adicionales:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

DefaultBindableProperties.RegisterCommand(
(CustomViewA.CommandProperty, CustomViewA.CommandParameterProperty),
(CustomViewB.CommandProperty, CustomViewB.CommandParameterProperty)
);

El código de convertidor en línea se puede pasar al Bind método con convert los convertBack parámetros y:
using :::no-loc(Xamarin.Forms):::.Markup;
//...

new Label { Text = "Tree" }


.Bind (Label.MarginProperty, nameof(TreeNode.TreeDepth),
convert: (int depth) => new Thickness(depth * 20, 0, 0, 0))

También se admiten los parámetros de convertidor Safe Type:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

new Label { }
.Bind (nameof(viewModel.Text),
convert: (string text, int repeat) => string.Concat(Enumerable.Repeat(text, repeat)))

Además, el código y las instancias del convertidor se pueden volver a usar con la FuncConverter clase:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

FuncConverter<int, Thickness> treeMarginConverter = new FuncConverter<int, Thickness>(depth => new


Thickness(depth * 20, 0, 0, 0));
new Label { Text = "Tree" }
.Bind (Label.MarginProperty, nameof(TreeNode.TreeDepth), converter: treeMarginConverter),

La FuncConverter clase también admite CultureInfo objetos:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

cultureAwareConverter = new FuncConverter<DateTimeOffset, string, int>(


(date, daysToAdd, culture) => date.AddDays(daysToAdd).ToString(culture)
);

También es posible enlazar datos a Span objetos que se especifican con la FormattedText propiedad:

using :::no-loc(Xamarin.Forms):::.Markup;
//...

new Label { } .FormattedText (


new Span { Text = "Built with " },
new Span { TextColor = Color.Blue, TextDecorations = TextDecorations.Underline }
.BindTapGesture (nameof(vm.ContinueToCSharpForMarkupCommand))
.Bind (nameof(vm.Title))
)

Reconocedores de gestos
Command``CommandParameter las propiedades y pueden ser datos enlazados a GestureElement View tipos y mediante
los BindClickGesture métodos de BindSwipeGesture extensión, y BindTapGesture :

using :::no-loc(Xamarin.Forms):::.Markup;
//...

new Label { Text = "Tap Me" }


.BindTapGesture (nameof(vm.TapCommand))
En este ejemplo se crea un reconocedor de gestos del tipo especificado y se agrega a Label . Los Bind*Gesture
métodos de extensión ofrecen los mismos parámetros que los BindCommand métodos de extensión. Sin embargo,
de forma predeterminada no se Bind*Gesture enlaza CommandParameter , mientras que BindCommand sí.
Para inicializar un reconocedor de gestos con parámetros, use los ClickGesture métodos de extensión,,
PanGesture PinchGesture , SwipeGesture y TapGesture :

using :::no-loc(Xamarin.Forms):::.Markup;
//...

new Label { Text = "Tap Me" }


.TapGesture (g => g.Bind(nameof(vm.DoubleTapCommand)).NumberOfTapsRequired = 2)

Dado que un reconocedor de gestos es un BindableObject , puede utilizar los Bind BindCommand métodos de
extensión y al inicializarlo. También puede inicializar tipos de reconocedor de gestos personalizados con el
Gesture<TGestureElement, TGestureRecognizer> método de extensión.

Layout
El marcado de C# incluye una serie de métodos de extensión de diseño que admiten la colocación de vistas en
diseños y contenido en vistas:

T IP O M ÉTO DO S DE EXT EN SIÓ N

FlexLayout AlignSelf , Basis , Grow , Menu , Order , Shrink

Grid Row , Column , RowSpan , ColumnSpan

Label TextLeft , TextCenterHorizontal , TextRight


TextTop , TextCenterVertical , TextBottom
TextCenter

Layout Padding , Paddings

LayoutOptions Left , CenterHorizontal , FillHorizontal , Right


LeftExpand , CenterExpandHorizontal ,
FillExpandHorizontal , RightExpand
Top , Bottom , CenterVertical , FillVertical
TopExpand , BottomExpand , CenterExpandVertical ,
FillExpandVertical
Center , Fill , CenterExpand , FillExpand

View Margin , Margins

VisualElement Height , Width , MinHeight , MinWidth , Size , MinSize

Compatibilidad de izquierda a derecha y de derecha a izquierda


En el marcado de C# diseñado para admitir la dirección de flujo de izquierda a derecha (LTR) o de derecha a
izquierda (RTL), los métodos de extensión enumerados anteriormente ofrecen el conjunto de nombres más
intuitivo Left : Right , Top y Bottom .
Para que esté disponible el conjunto correcto de métodos de extensión izquierdo y derecho y, en el proceso, haga
que sea explícito en qué dirección de flujo está diseñada el marcado, incluya una de las dos using directivas
siguientes: using :::no-loc(Xamarin.Forms):::.Markup.LeftToRight; , o
using :::no-loc(Xamarin.Forms):::.Markup.RightToLeft; .
En el marcado de C# diseñado para admitir la dirección de flujo de izquierda a derecha y de derecha a izquierda, se
recomienda usar los métodos de extensión en la tabla siguiente, en lugar de uno de los espacios de nombres
anteriores:

T IP O M ÉTO DO S DE EXT EN SIÓ N

Label TextStart , TextEnd

LayoutOptions Start , End


StartExpand , EndExpand

Convención de línea de diseño


La Convención recomendada es colocar todos los métodos de extensión de diseño para una vista en una sola línea
en el orden siguiente:
1. La fila y la columna que contienen la vista.
2. Alineación dentro de la fila y la columna.
3. Márgenes alrededor de la vista.
4. Tamaño de la vista.
5. Relleno dentro de la vista.
6. Alineación del contenido dentro del relleno.
En el código siguiente se muestra un ejemplo de esta Convención:

new Label { }
.Row (BodyRow.Prompt) .ColumnSpan (All<BodyCol>()) .FillExpandHorizontal () .CenterVertical ()
.Margin (fieldNameMargin) .TextCenterHorizontal () // Layout line

De forma coherente, la Convención le permite leer rápidamente el marcado de C# y crear una asignación mental de
la ubicación en la que se encuentra el contenido de la vista en la interfaz de usuario.

Filas y columnas de cuadrícula


Las enumeraciones se pueden utilizar para definir Grid filas y columnas, en lugar de usar números. Esto ofrece la
ventaja de que la renumeración no es necesaria al agregar o quitar filas o columnas.

IMPORTANT
Grid La definición de filas y columnas mediante enumeraciones requiere la siguiente using Directiva:
using static :::no-loc(Xamarin.Forms):::.Markup.GridRowsColumns;

En el código siguiente se muestra un ejemplo de cómo definir y consumir Grid filas y columnas mediante
enumeraciones:
using :::no-loc(Xamarin.Forms):::.Markup;
using :::no-loc(Xamarin.Forms):::.Markup.LeftToRight;
using static :::no-loc(Xamarin.Forms):::.Markup.GridRowsColumns;
// ...

enum BodyRow
{
Prompt,
CodeHeader,
CodeEntry,
Button
}

enum BodyCol
{
FieldLabel,
FieldValidation
}

View Build() => new Grid


{
RowDefinitions = Rows.Define(
(BodyRow.Prompt , 170 ),
(BodyRow.CodeHeader, 75 ),
(BodyRow.CodeEntry , Auto),
(BodyRow.Button , Auto)
),

ColumnDefinitions = Columns.Define(
(BodyCol.FieldLabel , 160 ),
(BodyCol.FieldValidation, Star)
),

Children =
{
new Label { LineBreakMode = LineBreakMode.WordWrap } .Font (15) .Bold ()
.Row (BodyRow.Prompt) .ColumnSpan (All<BodyCol>()) .FillExpandHorizontal () .CenterVertical
() .Margin (fieldNameMargin) .TextCenterHorizontal ()
.Bind (nameof(vm.RegistrationPrompt)),

new Label { Text = "Registration code" } .Bold ()


.Row (BodyRow.CodeHeader) .Column(BodyCol.FieldLabel) .Bottom () .Margin (fieldNameMargin),

new Label { } .Italic ()


.Row (BodyRow.CodeHeader) .Column (BodyCol.FieldValidation) .Right () .Bottom () .Margin
(fieldNameMargin)
.Bind (nameof(vm.RegistrationCodeValidationMessage)),

new Entry { Placeholder = "E.g. 123456", Keyboard = Keyboard.Numeric, BackgroundColor =


Color.AliceBlue, TextColor = Color.Black } .Font (15)
.Row (BodyRow.CodeEntry) .ColumnSpan (All<BodyCol>()) .Margin (fieldMargin) .Height (44)
.Bind (nameof(vm.RegistrationCode), BindingMode.TwoWay),

new Button { Text = "Verify" } .Style (FilledButton)


.Row (BodyRow.Button) .ColumnSpan (All<BodyCol>()) .FillExpandHorizontal () .Margin
(PageMarginSize)
.Bind (Button.IsVisibleProperty, nameof(vm.CanVerifyRegistrationCode))
.Bind (nameof(vm.VerifyRegistrationCodeCommand)),
}
};

Además, puede definir de manera concisa filas y columnas sin enumeraciones:


new Grid
{
RowDefinitions = Rows.Define (Auto, Star, 20),
ColumnDefinitions = Columns.Define (Auto, Star, 20, 40)
// ...
}

Fuentes
Los controles de la lista siguiente pueden llamar a FontSize los Bold métodos de extensión,, Italic y Font para
establecer la apariencia del texto que muestra el control:
Button
DatePicker
Editor
Entry
Label
Picker
SearchBar
Span
TimePicker

Efectos
Los efectos se pueden adjuntar a los controles con el Effect método de extensión:

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new Button { Text = "Tap Me" }


.Effects (new ButtonMixedCaps())

Integración de lógica
El Invoke método de extensión se puede usar para ejecutar código insertado en el marcado de C#:

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new ListView { } .Invoke (l => l.ItemTapped += OnListViewItemTapped)

Además, puede usar el método de Assign extensión para tener acceso a un control desde fuera del marcado de la
interfaz de usuario (en el archivo de lógica de la interfaz de usuario):

using :::no-loc(Xamarin.Forms):::.Markup;
// ...

new ListView { } .Assign (out MyListView)

Estilos
En el ejemplo siguiente se muestra cómo crear estilos implícitos y explícitos mediante el marcado de C#:
using :::no-loc(Xamarin.Forms):::.Markup;
using :::no-loc(Xamarin.Forms):::;

namespace CSharpForMarkupDemos
{
public static class Styles
{
static Style<Button> buttons, filledButton;
static Style<Label> labels;
static Style<Span> link;

#region Implicit styles

public static ResourceDictionary Implicit => new ResourceDictionary { Buttons, Labels };

public static Style<Button> Buttons => buttons ?? (buttons = new Style<Button>(


(Button.HeightRequestProperty, 44),
(Button.FontSizeProperty, 13),
(Button.HorizontalOptionsProperty, LayoutOptions.Center),
(Button.VerticalOptionsProperty, LayoutOptions.Center)
));

public static Style<Label> Labels => labels ?? (labels = new Style<Label>(


(Label.FontSizeProperty, 13),
(Label.TextColorProperty, Color.Black)
));

#endregion Implicit styles

#region Explicit styles

public static Style<Button> FilledButton => filledButton ?? (filledButton = new Style<Button>(


(Button.TextColorProperty, Color.White),
(Button.BackgroundColorProperty, Color.FromHex("#1976D2")),
(Button.CornerRadiusProperty, 5)
)).BasedOn(Buttons);

public static Style<Span> Link => link ?? (link = new Style<Span>(


(Span.TextColorProperty, Color.Blue),
(Span.TextDecorationsProperty, TextDecorations.Underline)
));

#endregion Explicit styles


}
}

Los estilos implícitos se pueden consumir cargándolo en el Diccionario de recursos de la aplicación:

public App()
{
Resources = Styles.Implicit;
// ...
}

Los estilos explícitos se pueden usar con el Style método de extensión.

using static CSharpForMarkupExample.Styles;


// ...

new Button { Text = "Tap Me" } .Style (FilledButton),


NOTE
Además del método de Style extensión, también hay métodos de ApplyToDerivedTypes extensión,, BasedOn Add y
CanCascade .

Como alternativa, puede crear sus propios métodos de extensión de estilo:

public static TButton Filled<TButton>(this TButton button) where TButton : Button


{
button.Buttons(); // Equivalent to Style .BasedOn (Buttons)
button.TextColor = Color.White;
button.BackgroundColor = Color.Red;
return button;
}

El Filled método de extensión se puede consumir de la siguiente manera:

new Button { Text = "Tap Me" } .Filled ()

Características específicas de las plataformas


El Invoke método de extensión se puede usar para aplicar características específicas de la plataforma. Sin
embargo, para evitar errores de ambigüedad, no incluya using directivas para los
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.*Specific espacios de nombres directamente. En su lugar, cree
un alias de espacio de nombres y consuma los específicos de la plataforma mediante el alias:

using :::no-loc(Xamarin.Forms):::.Markup;
using PciOS = :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
// ...

new ListView { } .Invoke (l => PciOS.ListView.SetGroupHeaderStyle(l, PciOS.GroupHeaderStyle.Grouped))

Además, si usa ciertos específicos de la plataforma con frecuencia, puede crear métodos de extensión fluida para
ellos en su propia clase de extensiones:

public static T iOSGroupHeaderStyle<T>(this T listView, PciOS.GroupHeaderStyle style) where T : Forms.ListView


{
PciOS.ListView.SetGroupHeaderStyle(listView, style);
return listView;
}

El método de extensión se puede consumir de la siguiente manera:

new ListView { } .iOSGroupHeaderStyle(PciOS.GroupHeaderStyle.Grouped)

Para obtener más información sobre las características específicas de la plataforma, vea características de la
plataforma Android, características de la plataforma iOSy características de la plataforma Windows.

Convención recomendada
Un orden recomendado y una agrupación de propiedades y métodos auxiliares es:
Propósito : cualquier propiedad o método auxiliar cuyo valor identifique el propósito del control (por ejemplo
Text ,, Placeholder Assign ).
Otros : todas las propiedades o métodos auxiliares que no son de diseño ni de enlace, en la misma línea o en
varias líneas.
Diseño : el diseño se ordena de forma ascendente: filas y columnas, opciones de diseño, margen, tamaño,
relleno y alineación del contenido.
BIND : el enlace de datos se realiza al final de la cadena de métodos, con una propiedad enlazada por línea. Si la
propiedad enlazable predeterminada está enlazada, debe estar al final de la cadena de métodos.
En el código siguiente se muestra un ejemplo de la siguiente Convención:

new Button { Text = "Verify" /* purpose */ } .Style (FilledButton) // other


.Row (BodyRow.Button) .ColumnSpan (All<BodyCol>()) .FillExpandHorizontal () .Margin (10) // layout
.Bind (Button.IsVisibleProperty, nameof(vm.CanVerifyRegistrationCode)) // bind
.Bind (nameof(vm.VerifyRegistrationCodeCommand)), // bind default

new Label { }
.Assign (out animatedMessageLabel) // purpose
.Invoke (label => label.SizeChanged += MessageLabel_SizeChanged) // other
.Row (BodyRow.Message) .ColumnSpan (All<BodyCol>()) // layout
.Bind (nameof(vm.Message)), // bind default

Aplicar de forma coherente esta Convención permite examinar rápidamente el marcado de C# y compilar una
imagen mental del diseño de la interfaz de usuario.

Vínculos relacionados
CSharpForMarkupDemos (ejemplo)
Características de la plataforma Android
características de la plataforma iOS
Características de la plataforma Windows
Controls Reference (Referencia de controles)
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
La interfaz de usuario de una :::no-loc(Xamarin.Forms)::: aplicación se construye de objetos que se asignan a los
controles nativos de cada plataforma de destino. Esto permite a las aplicaciones específicas de la plataforma iOS,
Android y el Plataforma universal de Windows usar el :::no-loc(Xamarin.Forms)::: código contenido en una
biblioteca de .net Standard.
Los cuatro grupos de control principales que se usan para crear la interfaz de usuario de una :::no-
loc(Xamarin.Forms)::: aplicación son los siguientes:
Páginas
Diseños
Vistas
Celdas
Una :::no-loc(Xamarin.Forms)::: Página suele ocupar toda la pantalla. La página contiene normalmente un diseño,
que contiene vistas y posiblemente otros diseños. Las celdas son componentes especializados que se usan en la
conexión con TableView y ListView . En la :::no-loc(Xamarin.Forms)::: :::no-loc(Xamarin.Forms)::: jerarquía de
clases de controlesse puede encontrar un diagrama de clases que muestre la jerarquía de tipos que se suelen
usar para compilar una interfaz de usuario en.
En los cuatro artículos de páginas , diseños , vistas y celdas , cada tipo de control se describe con vínculos a la
documentación de la API, un artículo que describe su uso (si existe) y uno o varios programas de ejemplo (si
existen). Cada tipo de control también está acompañado por una captura de pantalla que muestra una página del
ejemplo FormsGaller y que se ejecuta en dispositivos iOS y Android. Debajo de cada captura de pantalla hay
vínculos al código fuente de la página de C#, la página XAML equivalente y, si procede, el archivo de código
subyacente de C# para la página XAML.

NOTE
Las páginas, los diseños y las vistas derivan de la VisualElement clase. La VisualElement clase proporciona diversas
propiedades, métodos y eventos que son útiles para derivar clases. Para obtener más información, vea propiedades,
métodos y eventos de VisualElement.

Además de los controles proporcionados con :::no-loc(Xamarin.Forms)::: , hay disponibles controles de terceros.
Para obtener más información, vea controles de terceros.

Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
:::no-loc(Xamarin.Forms)::: Jerarquía de clases de controles
Documentación de la API
:::no-loc(Xamarin.Forms)::: Pages
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: Las páginas representan pantallas de aplicaciones móviles multiplataforma.
Todos los tipos de página que se describen a continuación derivan de la :::no-loc(Xamarin.Forms)::: Page clase.
Estos elementos visuales ocupan toda la pantalla o la mayoría de ellas. Un Page objeto representa un
ViewController en iOS y un Page en el plataforma universal de Windows. En Android, cada página ocupa la
pantalla como Activity , pero :::no-loc(Xamarin.Forms)::: las páginas no son not Activity objetos.

Páginas
:::no-loc(Xamarin.Forms)::: admite los siguientes tipos de páginas:

T IP O DESC RIP C IÓ N A SP EC TO

ContentPage ContentPage es el tipo de página


más sencillo y más común. Establezca
la Content propiedad en un solo
View objeto, que suele ser, como
Layout StackLayout , Grid o
ScrollView .

Documentación de la API

Código C# para esta página / Página


XAML

MasterDetailPage Un MasterDetailPage administra dos


paneles de información. Establezca la
Master propiedad en una página que
normalmente muestra una lista o un
menú. Establezca la Detail
propiedad en una página que muestre
un elemento seleccionado en la página
maestra. La IsPresented propiedad
rige si está visible la página maestra o
de detalle.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML con código subyacente
T IP O DESC RIP C IÓ N A SP EC TO

NavigationPage NavigationPage Administra la


navegación entre otras páginas
mediante una arquitectura basada en
la pila. Cuando se usa la navegación de
páginas en la aplicación, se debe pasar
una instancia de la Página principal al
constructor de un NavigationPage
objeto.

Documentación / de API Guía / de


Ejemplo 1, 2y 3
Código C# para esta página / Página
XAML con código subyacente

TabbedPage TabbedPage deriva de la clase


abstracta MultiPage y permite la
navegación entre las páginas
secundarias mediante tabulaciones.
Establezca la Children propiedad en
una colección de páginas o establezca
la ItemsSource propiedad en una
colección de objetos de datos y la
ItemTemplate propiedad en, que
DataTemplate describe cómo se
representará visualmente cada objeto.
Código C# para esta página / Página
Documentación / de API Guía / de XAML
Ejemplo 1 y 2

CarouselPage CarouselPage deriva de la clase


abstracta MultiPage y permite la
navegación entre las páginas
secundarias a través del dedo
deslizante. Establezca la Children
propiedad en una colección de
ContentPage objetos o establezca la
ItemsSource propiedad en una
colección de objetos de datos y la
ItemTemplate propiedad en, que
DataTemplate describe cómo se
representará visualmente cada objeto. Código C# para esta página / Página
XAML
Documentación / de API Guía / de
Ejemplo 1 y 2

TemplatedPage TemplatedPage muestra el contenido


de pantalla completa con una plantilla
de control y es la clase base para
ContentPage .

Documentación / de API Guía de


Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
Ejemplos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Documentación de API
:::no-loc(Xamarin.Forms)::: Diseños
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: Los diseños se usan para crear controles de interfaz de usuario en estructuras
visuales.
Las Layout Layout<T> clases y de :::no-loc(Xamarin.Forms)::: son subtipos especializados de vistas que actúan
como contenedores para vistas y otros diseños. La Layout propia clase deriva de View . Layout Normalmente,
un derivado contiene lógica para establecer la posición y el tamaño de los elementos secundarios en :::no-
loc(Xamarin.Forms)::: las aplicaciones.

Las clases que derivan de Layout pueden dividirse en dos categorías:

Diseños con un solo contenido


Estas clases derivan de Layout , que Padding define IsClippedToBounds las propiedades y:

T IP O DESC RIP C IÓ N A SP EC TO

ContentView ContentView contiene un único


elemento secundario que se establece
con la Content propiedad. La
Content propiedad se puede
establecer en cualquier View
derivado, incluidos otros Layout
derivados. ContentView se utiliza
principalmente como elemento
estructural y sirve como clase base
para Frame .

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML
T IP O DESC RIP C IÓ N A SP EC TO

Frame La Frameclase se deriva de


ContentViewy muestra un borde, o
marco, alrededor de su elemento
secundario. La Frame clase tiene un
Padding valor predeterminado de 20
y también define BorderColor
CornerRadius las propiedades, y
HasShadow .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML

ScrollView ScrollView es capaz de desplazar su


contenido. Establezca la Content
propiedad en una vista o diseño
demasiado grande para caber en la
pantalla. (El contenido de una
ScrollView es muy frecuente
StackLayout ). Establezca la
Orientation propiedad para indicar
si el desplazamiento debe ser vertical,
horizontal o ambos.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML

TemplatedView TemplatedView muestra el contenido


con una plantilla de control y es la
clase base para ContentView .

Documentación / de API Guía de

ContentPresenter ContentPresenter es un
administrador de diseño para vistas
con plantilla, que se utiliza dentro de
ControlTemplate para marcar dónde
aparece el contenido que se va a
presentar.

Documentación / de API Guía de

Diseños con varios elementos secundarios


Estas clases se derivan de Layout<View> :
T IP O DESC RIP C IÓ N A SP EC TO

StackLayout StackLayout coloca los elementos


secundarios en una pila, horizontal o
verticalmente, en función de la
Orientation propiedad. La
Spacing propiedad rige el espaciado
entre los elementos secundarios y
tiene un valor predeterminado de 6.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

Grid Grid coloca los elementos


secundarios en una cuadrícula de filas
y columnas. La posición de un
elemento secundario se indica
mediante las propiedades adjuntas
Row ,, Column RowSpan y
ColumnSpan .

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

AbsoluteLayout AbsoluteLayout coloca los


elementos secundarios en ubicaciones
específicas en relación con su elemento
primario. La posición de un elemento
secundario se indica mediante las
propiedades adjuntas LayoutBounds
y LayoutFlags . AbsoluteLayout Es
útil para animar las posiciones de las
vistas.

Documentación / de API Guía / de


Ejemplo de Código C# para esta página / Página
XAML con código subyacente

RelativeLayout RelativeLayout coloca los


elementos secundarios en relación con
el RelativeLayout propio o con sus
elementos del mismo nivel. La posición
de un elemento secundario se indica
mediante las propiedades adjuntas que
se establecen en objetos de tipo
Constraint y BoundsConstraint .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML
T IP O DESC RIP C IÓ N A SP EC TO

FlexLayout FlexLayout se basa en el módulo de


diseño de caja flexiblede CSS, conocido
normalmente como diseño flexible o
caja flexible. FlexLayout define seis
propiedades enlazables y cinco
propiedades enlazables asociadas que
permiten apilar o ajustar los elementos
secundarios con muchas opciones de
alineación y orientación.

Documentación / de API Guía / de


Ejemplo de Código C# para esta página / Página
XAML

Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
Ejemplos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Documentación de API
:::no-loc(Xamarin.Forms)::: Vistas
18/12/2020 • 21 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: las vistas son los bloques de creación de interfaces de usuario móviles
multiplataforma.
Las vistas son objetos de la interfaz de usuario, como etiquetas, botones y controles deslizantes que
normalmente se conocen como controles o widgets en otros entornos de programación gráficos. Las vistas
admitidas por :::no-loc(Xamarin.Forms)::: todas derivan de la View clase. Se pueden dividir en varias categorías:

Vistas para presentación


T IP O DESC RIP C IÓ N A SP EC TO

BoxView BoxView muestra un rectángulo


sólido coloreado por la Color
propiedad. BoxView tiene una
solicitud de tamaño predeterminado
de 40 x 40. Para otros tamaños, asigne
las WidthRequest HeightRequest
propiedades y.

Documentación / de API Guía / de


Ejemplo 1, 2, 3, 4, 5y 6
Código C# para esta página / Página
XAML

Ellipse Ellipse muestra una elipse o un


círculo con el tamaño WidthRequest
x HeightRequest . Para pintar el
interior de la elipse, establezca su
Fill propiedad en Color . Para
asignar a la elipse un contorno,
establezca su Stroke propiedad en
Color .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML
T IP O DESC RIP C IÓ N A SP EC TO

Expander Expander proporciona un


contenedor expansible para hospedar
cualquier contenido y se compone de
un encabezado y contenido. Establezca
la Header propiedad en un View
que se mostrará como el encabezado y
la Content propiedad en un que se
View mostrará cuando el encabezado
se expanda mediante una derivación.

Documentación / de API Guía / de


Ejemplo de Código C# para esta página / Página
XAML

Label Label muestra cadenas de texto de


una sola línea o bloques de texto de
varias líneas, con un formato constante
o variable. Establezca la Text
propiedad en una cadena para el
formato constante o establezca la
FormattedText propiedad en un
FormattedString objeto para el
formato de variable.

Documentación / de API Guía / de


Ejemplo de Código C# para esta página / Página
XAML

Line Line muestra una línea desde un


punto inicial hasta un punto final. Las
propiedades y representan el punto
inicial X1 Y1 , mientras que el punto
final se representa mediante las X2
propiedades y Y2 . Para colorear la
línea, establezca su Stroke propiedad
en Color .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML

Image Image muestra un mapa de bits. Los


mapas de bits se pueden descargar a
través de Web, incrustados como
recursos en los proyectos comunes de
proyecto o plataforma, o bien crearse
mediante un Stream objeto .net.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML
T IP O DESC RIP C IÓ N A SP EC TO

Map Map muestra un mapa. El :::no-


loc(Xamarin.Forms)::: . El paquete
NuGet de Maps debe estar instalado.
Android y Plataforma universal de
Windows requieren una clave de
autorización de asignación.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

MediaElement MediaElement reproduce vídeo o


audio. Los elementos multimedia se
pueden reproducir desde una dirección
URL o desde un archivo local, en
función de si la Source propiedad
está establecida en UriMediaSource
o FileMediaSource .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML

OpenGLView OpenGLView muestra los gráficos


OpenGL en los proyectos de iOS y
Android. No se admite el Plataforma
universal de Windows. Los proyectos
de iOS y Android requieren una
referencia al ensamblado OpenTK-1,0
o al ensamblado de la versión 1.0.0.0
de OpenTK . OpenGLView es más
fácil de usar en un proyecto
compartido; Si se utiliza en una
biblioteca de .NET Standard, también
se requerirá un servicio de Código C# para esta página / Página
dependencia (como se muestra en el XAML con código subyacente
código de ejemplo).

Esta es la única instalación de gráficos


integrada en :::no-loc(Xamarin.Forms):::
, pero una :::no-loc(Xamarin.Forms):::
aplicación también puede representar
gráficos con SkiaSharp .

Documentación de la API
T IP O DESC RIP C IÓ N A SP EC TO

Path Path muestra curvas y formas


complejas. La Data propiedad
especifica la forma que se va a dibujar.
Para colorear la forma, establezca su
Stroke propiedad en Color .

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

Polygon Polygon muestra un polígono. La


Points propiedad especifica los
puntos de vértice del polígono,
mientras que la FillRule propiedad
especifica cómo se determina el relleno
interior del polígono. Para pintar el
interior del polígono, establezca su
Fill propiedad en Color . Para dar
un contorno al polígono, establezca su
Stroke propiedad en Color .

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML

Polyline Polyline muestra una serie de líneas


rectas conectadas. La Points
propiedad especifica los puntos de
vértice de la polilínea, mientras que la
FillRule propiedad especifica cómo
se determina el relleno interior de la
polilínea. Para pintar el interior de la
polilínea, establezca su Fill
propiedad en Color . Para dar a la
polilínea un contorno, establezca su
Stroke propiedad en Color .
Código C# para esta página / Página
Documentación / de API Guía / de XAML
Ejemplo de

Rectangle Rectangle muestra un rectángulo o


un cuadrado. Para pintar el interior del
rectángulo, establezca su Fill
propiedad en Color . Para dar al
rectángulo un contorno, establezca su
Stroke propiedad en Color .

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML
T IP O DESC RIP C IÓ N A SP EC TO

WebView WebView muestra páginas web o


contenido HTML, en función de si la
Source propiedad está establecida en
un UriWebViewSource
HtmlWebViewSource objeto o.

Documentación / de API Guía / de


Ejemplo 1 y 2

Código C# para esta página / Página


XAML

Vistas que inician comandos


T IP O DESC RIP C IÓ N A SP EC TO

Button Button es un objeto rectangular que


muestra texto y que activa un
Clicked evento cuando se presiona.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML con código subyacente

ImageButton ImageButton es un objeto


rectangular que muestra una imagen y
que activa un Clicked evento
cuando se presiona.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML con código subyacente
T IP O DESC RIP C IÓ N A SP EC TO

RadioButton RadioButton permite la selección de


una opción de un conjunto y activa un
CheckedChanged evento cuando se
produce la selección.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML con código subyacente

RefreshView RefreshView es un control


contenedor que proporciona
funcionalidad de extracción para
actualizar para el contenido
desplazable. El ICommand definido por
la Command propiedad se ejecuta
cuando se desencadena una
actualización, y la IsRefreshing
propiedad indica el estado actual del
control.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML con código subyacente

SearchBar SearchBar muestra un área para que


el usuario escriba una cadena de texto
y un botón (o una tecla del teclado)
que señale a la aplicación para realizar
una búsqueda. La Text propiedad
proporciona acceso al texto y el
SearchButtonPressed evento indica
que se ha presionado el botón.

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML con código subyacente

SwipeView SwipeView es un control contenedor


que se ajusta alrededor de un
elemento de contenido y proporciona
elementos de menú contextual que se
revelan mediante un gesto de deslizar
rápidamente. Cada elemento de menú
se representa mediante un
SwipeItem , que tiene una Command
propiedad que ejecuta ICommand
cuando se puntea el elemento.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML con código subyacente
Vistas para establecer valores
T IP O DESC RIP C IÓ N A SP EC TO

CheckBox CheckBox permite al usuario


seleccionar un valor booleano
mediante un tipo de botón que puede
estar activado o vacío. La IsChecked
propiedad es el estado de CheckBox ,
y el CheckedChanged evento se
desencadena cuando cambia el estado.

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML

Slider Slider permite al usuario seleccionar


un double valor de un intervalo
continuo especificado con las
Minimum propiedades y Maximum .

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

Stepper Stepper permite al usuario


seleccionar un double valor de un
intervalo de valores incrementales
especificado con las Minimum
propiedades, Maximum y Increment
.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML

Switch Switch toma la forma de un


modificador ON/OFF para permitir al
usuario seleccionar un valor booleano.
La IsToggled propiedad es el estado
del modificador y el Toggled evento
se desencadena cuando cambia el
estado.

Documentación / de API Guía / de


Ejemplo de

Código C# para esta página / Página


XAML
T IP O DESC RIP C IÓ N A SP EC TO

DatePicker DatePicker permite al usuario


seleccionar una fecha con el selector de
fecha de la plataforma. Establezca un
intervalo de fechas permitidas con las
MinimumDate MaximumDate
propiedades y. La Date propiedad es
la fecha seleccionada y el
DateSelected evento se
desencadena cuando cambia esa
propiedad.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML

TimePicker TimePicker permite al usuario


seleccionar una hora con el selector de
tiempo de plataforma. La Time
propiedad es la hora seleccionada. Una
aplicación puede supervisar los
cambios en la Time propiedad
mediante la instalación de un
controlador para el PropertyChanged
evento.

Documentación / de API Guía / de


Ejemplo de Código C# para esta página / Página
XAML

Vistas para editar texto


Estas dos clases se derivan de la InputView clase, que define la Keyboard propiedad:

T IP O DESC RIP C IÓ N A SP EC TO

Entry Entry permite al usuario escribir y


editar una sola línea de texto. El texto
está disponible como la Text
propiedad y los TextChanged eventos
y Completed se activan cuando
cambia el texto o el usuario envía una
señal de finalización pulsando la tecla
entrar.

Utilice Editor para escribir y editar


varias líneas de texto.
Código C# para esta página / Página
Documentación / de API Guía / de XAML
Ejemplo de
T IP O DESC RIP C IÓ N A SP EC TO

Editor Editor permite al usuario escribir y


editar varias líneas de texto. El texto
está disponible como la Text
propiedad y los TextChanged eventos
y Completed se activan cuando
cambia el texto o el usuario envía una
señal de finalización.

Use una Entry vista para escribir y


editar una sola línea de texto.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML

Vistas para indicar actividad


T IP O DESC RIP C IÓ N A SP EC TO

ActivityIndicator ActivityIndicator usa una


animación para mostrar que la
aplicación está ocupada en una
actividad prolongada sin ofrecer
ninguna indicación de progreso. La
IsRunning propiedad controla la
animación.

Si se conoce el progreso de la
actividad, use ProgressBar en su
lugar.
Código C# para esta página / Página
Documentación / de API Guía / de XAML
Ejemplo de

ProgressBar ProgressBar usa una animación para


mostrar que la aplicación progresa a
través de una actividad prolongada.
Establezca la Progress propiedad en
valores entre 0 y 1 para indicar el
progreso.

Si no se conoce el progreso de la
actividad, use ActivityIndicator en
su lugar.

Documentación / de API Guía / de Código C# para esta página / Página


Ejemplo de XAML con código subyacente

Vistas que muestran colecciones


T IP O DESC RIP C IÓ N A SP EC TO
T IP O DESC RIP C IÓ N A SP EC TO

CarouselView CarouselView muestra una lista


desplazable de elementos de datos.
Establezca la ItemsSource propiedad
en una colección de objetos y
establezca la ItemTemplate
propiedad en un DataTemplate
objeto que describa cómo se va a dar
formato a los elementos. El
CurrentItemChanged evento indica
que el elemento mostrado
actualmente ha cambiado, que está
disponible como la CurrentItem Código C# para esta página / Página
propiedad. XAML

Guía / de Ejemplo de

CollectionView CollectionView muestra una lista


desplazable de elementos de datos
seleccionables, con distintas
especificaciones de diseño. Pretende
proporcionar una alternativa más
flexible y de rendimiento a ListView .
Establezca la ItemsSource propiedad
en una colección de objetos y
establezca la ItemTemplate
propiedad en un DataTemplate
objeto que describa cómo se va a dar
formato a los elementos. El Código C# para esta página / Página
SelectionChanged evento indica que XAML
se ha realizado una selección, que está
disponible como la SelectedItem
propiedad.

Guía / de Ejemplo de

IndicatorView IndicatorView muestra indicadores


que representan el número de
elementos de un CarouselView .
Establezca la
CarouselView.IndicatorView
propiedad en el IndicatorView
objeto para mostrar indicadores para
CarouselView .

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML
T IP O DESC RIP C IÓ N A SP EC TO

ListView ListView deriva de ItemsView y


muestra una lista desplazable de
elementos de datos seleccionables.
Establezca la ItemsSource propiedad
en una colección de objetos y
establezca la ItemTemplate
propiedad en un DataTemplate
objeto que describa cómo se va a dar
formato a los elementos. El
ItemSelected evento indica que se
ha realizado una selección, que está
disponible como la SelectedItem Código C# para esta página / Página
propiedad. XAML

Documentación / de API Guía / de


Ejemplo de

Picker Picker muestra un elemento


seleccionado en una lista de cadenas
de texto y permite seleccionar ese
elemento cuando se puntea en la vista.
Establezca la Items propiedad en una
lista de cadenas o la ItemsSource
propiedad en una colección de objetos.
El SelectedIndexChanged evento se
desencadena cuando se selecciona un
elemento.

Picker Muestra la lista de elementos Código C# para esta página / Página


solo cuando está seleccionado. Use XAML con código subyacente
ListView o TableView para una
lista desplazable que permanezca en la
página.

Documentación / de API Guía / de


Ejemplo de

TableView TableView muestra una lista de filas


de tipo Cell con encabezados y
subencabezados opcionales. Establezca
la Root propiedad en un objeto de
tipo TableRoot y agregue
TableSection objetos a esa
TableRoot . Cada TableSection es
una colección de Cell objetos.

Documentación / de API Guía / de


Ejemplo de
Código C# para esta página / Página
XAML

Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
Ejemplos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Documentación de API
:::no-loc(Xamarin.Forms)::: Celdas
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: las celdas se pueden agregar a los controles ListView y TableViews.
Una celda es un elemento especializado que se usa para los elementos de una tabla y describe cómo se debe
representar cada elemento de una lista. La Cell clase se deriva de Element , de la que VisualElement también
deriva. Una celda no es en sí misma un elemento visual; en su lugar, se trata de una plantilla para crear un
elemento visual.
Cell se utiliza exclusivamente con ListView TableView controles y. Para obtener información sobre cómo usar
y personalizar las celdas, consulte ListView la TableView documentación de y.

Celdas
:::no-loc(Xamarin.Forms)::: admite los siguientes tipos de celda:

T IP O DESC RIP C IÓ N A SP EC TO

TextCell TextCell Muestra una o dos cadenas


de texto. Establezca la Text
propiedad y, opcionalmente, la
Detail propiedad en estas cadenas
de texto.

Documentación / de API Guía de

Código C# para esta página / Página


XAML

ImageCell ImageCell Muestra la misma


información que, TextCell pero
incluye un mapa de bits que se
establece con la Source propiedad.

Documentación / de API Guía de

Código C# para esta página / Página


XAML
T IP O DESC RIP C IÓ N A SP EC TO

SwitchCell El SwitchCell contiene el conjunto


de texto con la Text propiedad y un
modificador ON/OFF inicialmente
establecido con la On propiedad
booleana. Controle el OnChanged
evento que se va a notificar cuando
On cambie la propiedad.

Documentación / de API Guía de

Código C# para esta página / Página


XAML

EntryCell EntryCell Define una Label


propiedad que identifica la celda y una
sola línea de texto modificable en la
Text propiedad. Controle el
Completed evento que se va a
notificar cuando el usuario haya
completado la entrada de texto.

Documentación / de API Guía de

Código C# para esta página / Página


XAML

Vínculos relacionados
:::no-loc(Xamarin.Forms)::: Ejemplo de FormsGallery
Ejemplos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Documentación de API
:::no-loc(Xamarin.Forms)::: propiedades, métodos y
eventos de controles comunes
18/12/2020 • 25 minutes to read • Edit Online

La :::no-loc(Xamarin.Forms)::: VisualElement clase es la clase base para la mayoría de los controles utilizados en
una :::no-loc(Xamarin.Forms)::: aplicación. La VisualElement clase define muchas propiedades, métodosy eventos
que se usan en las clases derivadas.

Propiedades
Las siguientes propiedades están disponibles en los VisualElement objetos.
AnchorX

La AnchorX propiedad es un double valor que define el punto central en el eje X para las transformaciones como
la escala y la rotación. El valor predeterminado es 0,5.
AnchorY

La AnchorY propiedad es un double valor que define el punto central en el eje Y para las transformaciones como
la escala y la rotación. El valor predeterminado es 0,5.
Background

La Background propiedad es un Brush valor que permite usar pinceles como fondo en cualquier control. El valor
predeterminado es Brush.Default .
BackgroundColor

La BackgroundColor propiedad es un Color que determina el color de fondo del control. Si no se establece, el
fondo será el Color objeto predeterminado, que se representa como transparente.
Behaviors

La Behaviors propiedad es List de Behavior objetos. Los comportamientos permiten asociar la funcionalidad
reutilizable a los elementos agregándolos a la Behaviors lista. Para obtener más información sobre la Behavior
clase, vea :::no-loc(Xamarin.Forms)::: Behaviors.
Bounds

La Bounds propiedad es un objeto de solo lectura Rectangle que representa el espacio ocupado por el control. El
Bounds valor de la propiedad se asigna durante el ciclo de diseño. Rectangle struct Contiene propiedades y
métodos útiles para probar la intersección y la contención de rectángulos. Para obtener más información, consulte
la :::no-loc(Xamarin.Forms)::: API de rectángulo.
Clip

La Clip propiedad es un Geometry objeto que define el contorno del contenido de un elemento. Para definir un
clip, use un Geometry objeto como EllipseGeometry para establecer la propiedad del elemento Clip . Solo se
verá el área que está dentro de la región de la geometría. Para obtener más información, vea Recorte con una
geometría.
Effects

La Effects propiedad es List de Effect objetos, heredada de la Element clase. Los efectos permiten
personalizar los controles nativos y se suelen usar para pequeños cambios de estilo. Para obtener más información
sobre la Effect clase, vea :::no-loc(Xamarin.Forms)::: efectos.
FlowDirection

La FlowDirection propiedad es un FlowDirection valor de enumeración. La dirección del flujo se puede establecer
en MatchParent , LeftToRight o RightToLeft y determina el orden y la dirección del diseño. La FlowDirection
propiedad se utiliza normalmente para admitir idiomas que se leen de derecha a izquierda.
Height

La Height propiedad es un valor de solo lectura double que describe el alto representado del control. La Height
propiedad se calcula durante el ciclo de diseño y no se puede establecer directamente. El alto de un control se
puede solicitar mediante la propiedad HeightRequest.
HeightRequest

La HeightRequest propiedad es un double valor que determina el alto deseado del control. Es posible que el alto
absoluto del control no coincida con el valor solicitado. Para obtener más información, consulte propiedades de la
solicitud.
InputTransparent

La InputTransparent propiedad es un bool que determina si el control recibe datos proporcionados por el
usuario. El valor predeterminado es false , asegurándose de que el elemento recibe la entrada. Esta propiedad se
transfiere a los elementos secundarios cuando se establece. Si se establece la InputTransparent propiedad en
true en una clase de diseño, todos los elementos del diseño no recibirán una entrada.

IsEnabled

La IsEnabled propiedad es un bool valor que determina si el control reacciona a los datos proporcionados por el
usuario. El valor predeterminado es true . Si establece esta propiedad en false, impedirá que el control acepte
datos proporcionados por el usuario.
IsFocused

La IsFocused propiedad es un bool valor que describe si el control es actualmente el objeto que tiene el foco. Si
se llama al Focus método en el control, el IsFocused valor se establecerá en true. Al llamar al Unfocus método, se
establecerá esta propiedad en false.
IsTabStop

La IsTabStop propiedad es un bool valor que define si el control recibe el foco cuando el usuario avanza por los
controles con la tecla TAB. Si esta propiedad es false, la TabIndex propiedad no tendrá ningún efecto.
IsVisible

La IsVisible propiedad es un bool valor que determina si se representa el control. No se mostrarán los
controles con la IsVisible propiedad establecida en false, por lo que no se tendrán en cuenta para los cálculos de
espacio durante el ciclo de diseño y no podrán aceptar la entrada del usuario.
MinimumHeightRequest

La MinimumHeightRequest propiedad es un double valor que determina cómo se controla el desbordamiento


cuando dos elementos compiten por el espacio limitado. El establecimiento de la MinimumHeightRequest propiedad
permite que el proceso de diseño escale el elemento hacia abajo hasta la dimensión mínima solicitada. Si no
MinimumHeightRequest se especifica, el valor predeterminado es-1 y el proceso de diseño considerará
HeightRequest como el valor mínimo. Esto significa que los elementos sin MinimumHeightRequest valor no tendrán
un alto escalable.
Para obtener más información, vea propiedades de solicitud mínima.
MinimumWidthRequest
La MinimumWidthRequest propiedad es un double valor que determina cómo se controla el desbordamiento
cuando dos elementos compiten por el espacio limitado. El establecimiento de la MinimumWidthRequest propiedad
permite que el proceso de diseño escale el elemento hacia abajo hasta la dimensión mínima solicitada. Si no
MinimumWidthRequest se especifica, el valor predeterminado es-1 y el proceso de diseño considerará WidthRequest
como el valor mínimo. Esto significa que los elementos sin MinimumWidthRequest valor no tendrán un ancho
escalable.
Para obtener más información, vea propiedades de solicitud mínima.
Opacity

La Opacity propiedad es un double valor de cero a uno que determina la opacidad del control durante la
representación. El valor predeterminado de esta propiedad es 1,0. Se fijarán los valores fuera del intervalo de 0 a 1.
La Opacity propiedad solo se aplica si la IsVisible propiedad es true . La opacidad se aplica de forma iterativa.
Por lo tanto, si un control principal tiene una opacidad de 0,5 y su elemento secundario tiene una opacidad de 0,5,
el elemento secundario se representará con un valor de opacidad de 0,25 vigente. Establecer la Opacity propiedad
de un control de entrada en 0 tiene un comportamiento indefinido.
Parent

La propiedad Parent se hereda de la clase Element . Esta propiedad es un Element objeto que es el elemento
primario del control. Parent Normalmente, la propiedad se establece automáticamente en un elemento cuando se
agrega como elemento secundario de otro elemento.
Resources

La Resources propiedad es una ResourceDictionary instancia de que se rellena con pares clave-valor que
normalmente se rellenan en tiempo de ejecución desde XAML. Este diccionario permite a los desarrolladores de
aplicaciones reutilizar los objetos definidos en XAML en tiempo de compilación y en tiempo de ejecución. Las
claves del diccionario se rellenan a partir del x:Key atributo de la etiqueta XAML. El objeto creado a partir de
XAML se inserta en ResourceDictionary para la clave especificada. una vez inicializado.
Para obtener más información, vea diccionarios de recursos.
Rotation

La Rotation propiedad es un double valor entre cero y 360 que define el giro sobre el eje Z en grados. El valor
predeterminado de esta propiedad es 0. La rotación se aplica en relación con los AnchorX AnchorY valores y.
RotationX

La RotationX propiedad es un double valor entre cero y 360 que define el giro sobre el eje X en grados. El valor
predeterminado de esta propiedad es 0. La rotación se aplica en relación con los AnchorX AnchorY valores y.
RotationY

La RotationY propiedad es un double valor entre cero y 360 que define el giro sobre el eje Y en grados. El valor
predeterminado de esta propiedad es 0. La rotación se aplica en relación con los AnchorX AnchorY valores y.
Scale

La Scale propiedad es un double valor que define la escala del control. El valor predeterminado de esta
propiedad es 1,0. La escala se aplica en relación con los AnchorX AnchorY valores y.
ScaleX

La ScaleX propiedad es un double valor que define la escala del control a lo largo del eje X. El valor
predeterminado de esta propiedad es 1,0. La ScaleX propiedad se aplica en relación con el AnchorX valor.
ScaleY

La ScaleY propiedad es un double valor que define la escala del control a lo largo del eje Y. El valor
predeterminado de esta propiedad es 1,0. La ScaleY propiedad se aplica en relación con el AnchorY valor.
Style

La propiedad Style se hereda de la clase NavigableElement . Esta propiedad es una instancia de la Style clase. La
Style clase contiene desencadenadores, establecedores y comportamientos que definen la apariencia y el
comportamiento de los elementos visuales. Para obtener más información, vea :::no-loc(Xamarin.Forms)::: estilos
XAML.
StyleClass

La StyleClass propiedad es una lista de string objetos que representan los nombres de Style las clases. Esta
propiedad se hereda de la clase NavigableElement . La StyleClass propiedad permite aplicar varios atributos de
estilo a una VisualElement instancia de. Para obtener más información, vea :::no-loc(Xamarin.Forms)::: clases de
estilo.
TabIndex

La TabIndex propiedad es un int valor que define el orden de control al avanzar por los controles con la tecla
TAB. La TabIndex propiedad es la implementación de la propiedad definida en la ITabStopElement interfaz, que la
VisualElement clase implementa.

TranslationX

La TranslationX propiedad es un double valor que define la conversión Delta que se va a aplicar en el eje X. La
traducción se aplica después del diseño y se utiliza normalmente para aplicar animaciones. La conversión de un
elemento fuera de los límites de su contenedor primario My impide que se funcionen las entradas.
Para obtener más información, vea animación :::no-loc(Xamarin.Forms)::: en .
TranslationY

La TranslationY propiedad es un double valor que define la conversión Delta que se va a aplicar en el eje Y. La
traducción se aplica después del diseño y se utiliza normalmente para aplicar animaciones. La conversión de un
elemento fuera de los límites de su contenedor primario My impide que se funcionen las entradas.
Para obtener más información, vea animación :::no-loc(Xamarin.Forms)::: en .
Triggers

La Triggers propiedad es de solo lectura List de TriggerBase objetos. Los desencadenadores permiten a los
desarrolladores de aplicaciones expresar acciones en XAML que cambian la apariencia visual de los controles en
respuesta a los cambios de eventos o propiedades. Para obtener más información, vea :::no-loc(Xamarin.Forms):::
desencadenadores.
Visual

La Visual propiedad es una IVisual instancia de que permite crear representadores y aplicarlos de forma
selectiva a VisualElement las instancias de. La Visual propiedad se establece para que coincida con su elemento
primario, por lo que la definición de un representador en un componente también se aplicará a todos los
elementos secundarios de ese componente. Si no se establece ningún representador personalizado en un control o
en sus antecesores, se :::no-loc(Xamarin.Forms)::: usará el representador predeterminado. Para obtener más
información, vea :::no-loc(Xamarin.Forms)::: Visual.
Width

La Width propiedad es un valor de solo lectura double que describe el ancho representado del control. La Width
propiedad se calcula durante el ciclo de diseño y no se puede establecer directamente. El ancho de un control se
puede solicitar mediante la propiedad WidthRequest.
WidthRequest
La WidthRequest propiedad es un double valor que determina el ancho deseado del control. El ancho absoluto del
control puede no coincidir con el valor solicitado. Para obtener más información, consulte propiedades de la
solicitud.
X

La X propiedad es un valor de solo lectura double que describe la posición X actual del control.
Y

La Y propiedad es un valor de solo lectura double que describe la posición Y actual del control.

Métodos
Los métodos siguientes están disponibles en la VisualElement clase. Para obtener una lista completa, consulte
métodos de la API de VisualElement.
FindByName

El FindByName método se hereda de la Element clase y tiene la siguiente firma:

public object FindByName (string name)

Este método busca el argumento proporcionado en todos los elementos secundarios name y devuelve el elemento
que tiene el nombre especificado. Si no se encuentra alguna coincidencia, se devuelve null .
Focus

El Focus método intenta establecer el foco en el elemento. Este método tiene la siguiente firma:

public bool Focus ()

El Focus método devuelve true si se estableció correctamente el foco de teclado y false si la llamada al
método no produjo un cambio de foco. El elemento debe ser capaz de recibir el foco para que este método
funcione. La llamada al Focus método en elementos que están fuera de la definición o que no están en
funcionamiento tiene un comportamiento indefinido.
Unfocus

El Unfocus método intenta quitar el foco en el elemento. Este método tiene la siguiente firma:

public void Unfocus ()

El elemento ya debe tener el foco para que este método funcione.

Eventos
Los eventos siguientes están disponibles en la VisualElement clase. Para obtener una lista completa, vea :::no-
loc(Xamarin.Forms)::: VisualElement Events.
Focused

El Focused evento se desencadena cuando la VisualElement instancia recibe el foco. Este evento no se traspasa a
través de la :::no-loc(Xamarin.Forms)::: pila, sino que se recibe directamente del control nativo. El establecedor de la
propiedad emite este evento IsFocused .
SizeChanged
El SizeChanged evento se desencadena cuando VisualElement cambia la instancia Height o Width las
propiedades. Si los desarrolladores desean responder directamente al cambio de tamaño, en lugar de responder al
evento posterior al cambio, deben implementar el OnSizeAllocated método virtual en su lugar.
Unfocused

El Unfocused evento se desencadena cuando la VisualElement instancia pierde el foco. Este evento no se traspasa
a través de la :::no-loc(Xamarin.Forms)::: pila, sino que se recibe directamente del control nativo. El establecedor de
la propiedad emite este evento IsFocused .

Unidades de medida
Todas las plataformas Android, iOS y UWP tienen unidades de medida diferentes que pueden variar en los
dispositivos. :::no-loc(Xamarin.Forms)::: utiliza una unidad de medida independiente de la plataforma que normaliza
las unidades entre dispositivos y plataformas. Hay 160 unidades por pulgada, o 64 unidades por centímetro, en
:::no-loc(Xamarin.Forms)::: .

Propiedades de solicitud
Las propiedades cuyos nombres contienen "Request" definen un valor deseado, que puede no coincidir con el
valor representado real. Por ejemplo, HeightRequest puede establecerse en 150 pero si el diseño solo permite
espacio para las unidades 100, la representación Height del control solo será 100. El tamaño representado se ve
afectado por el espacio disponible y los componentes contenidos.

Propiedades de solicitud mínimas


Las propiedades de solicitud mínima incluyen MinimumHeightRequest y MinimumWidthRequest , y están diseñadas
para permitir un control más preciso sobre cómo los elementos controlan el desbordamiento entre sí. Sin
embargo, el comportamiento de diseño relacionado con estas propiedades tiene algunas consideraciones
importantes.
Valores de propiedad mínimos no especificados
Si no se establece un valor mínimo, el valor predeterminado de la propiedad minimum es-1. El proceso de diseño
omite este valor y considera que el valor absoluto es el mínimo. La consecuencia práctica de este comportamiento
es que no se reducirá un elemento sin ningún valor mínimo especificado. Se reducirá un elemento con un valor
mínimo especificado.
En el siguiente código XAML se muestran dos BoxView elementos en un horizontal StackLayout :

<StackLayout Orientation="Horizontal">
<BoxView HeightRequest="100" BackgroundColor="Purple" WidthRequest="500"></BoxView>
<BoxView HeightRequest="100" BackgroundColor="Green" WidthRequest="500" MinimumWidthRequest="250">
</BoxView>
</StackLayout>

La primera BoxView instancia solicita un ancho de 500 y no especifica un ancho mínimo. La segunda BoxView
instancia solicita un ancho de 500 y un ancho mínimo de 250. Si el StackLayout elemento primario no es lo
suficientemente ancho como para contener ambos componentes en el ancho solicitado, el BoxView proceso de
diseño tendrá en cuenta la primera instancia para tener un ancho mínimo de 500 porque no se especifica ningún
otro mínimo válido. La segunda BoxView instancia tiene permiso para reducir verticalmente a 250 y se reducirá
para ajustarse hasta que su ancho alcance 250 unidades.
Si el comportamiento deseado es que la primera BoxView instancia se reduzca verticalmente sin ningún ancho
mínimo, MinimumWidthRequest se debe establecer en un valor válido, como 0.
Valores de las propiedades Minimum y Absolute
El comportamiento es indefinido cuando el valor mínimo es mayor que el valor absoluto. Por ejemplo, si
WidthRequest se establece en 100, la MinimumWidthRequest propiedad nunca debe superar 100. Al especificar un
valor de propiedad mínimo, siempre debe especificar un valor absoluto para asegurarse de que el valor absoluto
sea mayor que el valor mínimo.
Propiedades mínimas dentro de una cuadrícula
Grid los diseños tienen su propio sistema para ajustar el tamaño relativo de las filas y las columnas. El uso de
MinimumWidthRequest o MinimumHeightRequest dentro de un Grid diseño no tendrá ningún efecto. Para obtener
más información, vea :::no-loc(Xamarin.Forms)::: Grid.

Vínculos relacionados
API de VisualElement
Xamarin.FormsControles de terceros
18/12/2020 • 2 minutes to read • Edit Online

Además de los controles suministrados con Xamarin.Forms , los controles de terceros están disponibles en las
siguientes empresas:
Telerik
SyncFusion
DevExpress
Infragistics
ComponentOne
Steema
Estos controles proporcionan compatibilidad adicional para Xamarin.Forms los desarrolladores al aumentar los
controles estándar con controles y servicios personalizados.
:::no-loc(Xamarin.Forms)::: BoxView
18/12/2020 • 27 minutes to read • Edit Online

Descargar el ejemplo
BoxView representa un rectángulo simple con el ancho, el alto y el color especificados. Puede usar BoxView para
la decoración, los gráficos rudimentarios y la interacción con el usuario a través de la entrada táctil.
Dado :::no-loc(Xamarin.Forms)::: que no tiene un sistema de gráficos vectoriales integrado, BoxView ayuda a
compensar. Algunos de los programas de ejemplo que se describen en este artículo se usan BoxView para
representar gráficos. BoxView Se puede cambiar el tamaño para que se parezca a una línea de un ancho y un
grosor específicos y, a continuación, girar cualquier ángulo mediante la Rotation propiedad.
Aunque BoxView puede imitar gráficos sencillos, es posible que desee investigar usando :::no-
loc(Xamarin.Forms)::: SkiaSharp en para obtener requisitos de gráficos más sofisticados.

Establecer el color y el tamaño de BoxView


Normalmente, establecerá las siguientes propiedades de BoxView :
Color para establecer su color.
CornerRadius para establecer su radio de redondeo.
WidthRequest para establecer el ancho de BoxView en unidades independientes del dispositivo.
HeightRequest para establecer el alto del BoxView .

La Color propiedad es de tipo Color ; la propiedad se puede establecer en cualquier Color valor, incluidos los
campos de solo lectura estáticos 141 de los colores con nombre que abarcan alfabéticamente de AliceBlue a
YellowGreen .

La CornerRadius propiedad es de tipo CornerRadius ; la propiedad se puede establecer en un único double valor
de radio de esquina uniforme, o una CornerRadius estructura definida por cuatro double valores que se aplican a
la parte superior izquierda, superior derecha, parte inferior izquierda y parte inferior derecha de BoxView .
Las WidthRequest HeightRequest propiedades y solo desempeñan un rol si BoxView no está restringido en el
diseño. Este es el caso cuando el contenedor de diseño necesita conocer el tamaño del elemento secundario, por
ejemplo, cuando BoxView es un elemento secundario de una celda de tamaño automático en el Grid diseño.
BoxView También se puede anular la restricción cuando HorizontalOptions sus VerticalOptions propiedades y se
establecen en valores distintos de LayoutOptions.Fill . Si BoxView no está restringido, pero WidthRequest
HeightRequest no se han establecido las propiedades y, el ancho o el alto se establecen en los valores
predeterminados de 40 unidades o aproximadamente 1/4 pulgadas en los dispositivos móviles.
Las WidthRequest HeightRequest propiedades y se omiten si BoxView está restringido en el diseño, en cuyo caso
el contenedor de diseño impone su propio tamaño en BoxView .
Una vista BoxView se puede restringir en una dimensión y sin restricciones en la otra. Por ejemplo, si BoxView es
un elemento secundario de un elemento vertical StackLayout , la dimensión vertical de la BoxView no está
restringida y su dimensión horizontal está normalmente restringida. Pero hay excepciones para esa dimensión
horizontal: Si BoxView tiene su HorizontalOptions propiedad establecida en un valor distinto de
LayoutOptions.Fill , la dimensión horizontal tampoco tiene restricciones. También es posible que el StackLayout
propio tenga una dimensión horizontal sin restricciones, en cuyo caso BoxView también se anulará la restricción
horizontal.
En el ejemplo BasicBoxView se muestra un cuadrado de una pulgada sin restricciones BoxView en el centro de
su página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BasicBoxView"
x:Class="BasicBoxView.MainPage">

<BoxView Color="CornflowerBlue"
CornerRadius="10"
WidthRequest="160"
HeightRequest="160"
VerticalOptions="Center"
HorizontalOptions="Center" />

</ContentPage>

Este es el resultado:

Si las VerticalOptions HorizontalOptions propiedades y se quitan de la BoxView etiqueta o se establecen en


Fill , el BoxView se vuelve limitado por el tamaño de la página y se expande para rellenar la página.
BoxView También puede ser un elemento secundario de AbsoluteLayout . En ese caso, la ubicación y el tamaño de
BoxView se establecen utilizando la LayoutBounds propiedad enlazable adjunta. AbsoluteLayout Se describe en el
artículo AbsoluteLayout .
Verá ejemplos de todos estos casos en los programas de ejemplo que se indican a continuación.

Representación de decoraciones de texto


Puede usar el BoxView para agregar algunas decoraciones simples en las páginas en forma de líneas horizontales
y verticales. El ejemplo TextDecoration muestra esto. Todos los objetos visuales del programa se definen en el
archivo mainpage. Xaml , que contiene varios Label elementos y BoxView en el StackLayout mostrado aquí:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TextDecoration"
x:Class="TextDecoration.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="BoxView">
<Setter Property="Color" Value="Black" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<ScrollView Margin="15">
<StackLayout>

···

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

Todo el marcado que se muestra a continuación son elementos secundarios de StackLayout . Este marcado se
compone de varios tipos de BoxView elementos decorativos utilizados con el Label elemento:

El encabezado estilizado en la parte superior de la página se logra con un AbsoluteLayout cuyos elementos
secundarios son cuatro BoxView elementos y Label , todos ellos asignados a ubicaciones y tamaños específicos:

<AbsoluteLayout>
<BoxView AbsoluteLayout.LayoutBounds="0, 10, 200, 5" />
<BoxView AbsoluteLayout.LayoutBounds="0, 20, 200, 5" />
<BoxView AbsoluteLayout.LayoutBounds="10, 0, 5, 65" />
<BoxView AbsoluteLayout.LayoutBounds="20, 0, 5, 65" />
<Label Text="Stylish Header"
FontSize="24"
AbsoluteLayout.LayoutBounds="30, 25, AutoSize, AutoSize"/>
</AbsoluteLayout>
En el archivo XAML, va AbsoluteLayout seguido de un Label con texto con formato que describe AbsoluteLayout
.
Puede subrayar una cadena de texto mediante la inclusión de Label y BoxView en StackLayout que tiene su
HorizontalOptions valor establecido en un valor distinto de Fill . A continuación, el ancho de StackLayout se
rige por el ancho de Label , que luego impone ese ancho en BoxView . BoxView Solo se asigna un alto explícito:

<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>

No puede usar esta técnica para subrayar palabras individuales en cadenas de texto más largas o en un párrafo.
También es posible usar un BoxView para que se parezca a un hr elemento HTML (regla horizontal).
Simplemente deje que el ancho de BoxView esté determinado por su contenedor primario, que en este caso es
StackLayout :

<BoxView HeightRequest="3" />

Por último, puede dibujar una línea vertical en un lado de un párrafo de texto incluyendo BoxView y Label en
horizontal StackLayout . En este caso, el alto de BoxView es el mismo que el alto de StackLayout , que se rige por
el alto del Label :

<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>

···

</Label>
</StackLayout>

Enumerar los colores con BoxView


BoxView Es conveniente para mostrar los colores. Este programa usa ListView para enumerar todos los campos
estáticos públicos de solo lectura de la :::no-loc(Xamarin.Forms)::: Color estructura:
El programa ListViewColors incluye una clase denominada NamedColor . El constructor estático utiliza la
reflexión para tener acceso a todos los campos de la Color estructura y crear un NamedColor objeto para cada
uno de ellos. Estos se almacenan en la All propiedad estática:

public class NamedColor


{
// Instance members.
private NamedColor()
{
}

public string Name { private set; get; }

public string FriendlyName { private set; get; }

public Color Color { private set; get; }

public string RgbDisplay { private set; get; }

// Static members.
static NamedColor()
{
List<NamedColor> all = new List<NamedColor>();
StringBuilder stringBuilder = new StringBuilder();

// Loop through the public static fields of the Color structure.


foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields ())
{
if (fieldInfo.IsPublic &&
fieldInfo.IsStatic &&
fieldInfo.FieldType == typeof (Color))
{
// Convert the name to a friendly name.
string name = fieldInfo.Name;
stringBuilder.Clear();
int index = 0;

foreach (char ch in name)


{
if (index != 0 && Char.IsUpper(ch))
{
stringBuilder.Append(' ');
}
stringBuilder.Append(ch);
index++;
}
}

// Instantiate a NamedColor object.


Color color = (Color)fieldInfo.GetValue(null);

NamedColor namedColor = new NamedColor


{
Name = name,
FriendlyName = stringBuilder.ToString(),
Color = color,
RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
(int)(255 * color.R),
(int)(255 * color.G),
(int)(255 * color.B))
};

// Add it to the collection.


all.Add(namedColor);
}
}
all.TrimExcess();
All = all;
}

public static IList<NamedColor> All { private set; get; }


}

Los objetos visuales de programa se describen en el archivo XAML. La ItemsSource propiedad de ListView se
establece en la propiedad estática NamedColor.All , lo que significa que ListView muestra todos los objetos
individuales NamedColor :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ListViewColors"
x:Class="ListViewColors.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="10, 20, 10, 0" />
<On Platform="Android, UWP" Value="10, 0" />
</OnPlatform>
</ContentPage.Padding>

<ListView SeparatorVisibility="None"
ItemsSource="{x:Static local:NamedColor.All}">
<ListView.RowHeight>
<OnPlatform x:TypeArguments="x:Int32">
<On Platform="iOS, Android" Value="80" />
<On Platform="UWP" Value="90" />
</OnPlatform>
</ListView.RowHeight>

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="5">
<Frame OutlineColor="Accent"
Padding="10">
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
WidthRequest="50"
HeightRequest="50" />
<StackLayout>
<Label Text="{Binding FriendlyName}"
FontSize="22"
VerticalOptions="StartAndExpand" />
<Label Text="{Binding RgbDisplay, StringFormat='RGB = {0}'}"
FontSize="16"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

Los NamedColorobjetos se formatean mediante el ViewCell objeto que se establece como la plantilla de datos de
ListView . Esta plantilla incluye una BoxView cuya Color propiedad está enlazada a la Color propiedad del
NamedColor objeto.

Reproducir el juego de vida mediante la subclase de BoxView


El juego de vida es un autómata de telefonía móvil inventado por mathematician John Conway y popular en las
páginas de Ciencias científicas de la década de 1970. El juego de vida del artículo Conway dela Wikipedia
proporciona una buena introducción.
El :::no-loc(Xamarin.Forms)::: programa GameOfLife define una clase denominada LifeCell que se deriva de
BoxView . Esta clase encapsula la lógica de una celda individual en el juego de vida:
class LifeCell : BoxView
{
bool isAlive;

public event EventHandler Tapped;

public LifeCell()
{
BackgroundColor = Color.White;

TapGestureRecognizer tapGesture = new TapGestureRecognizer();


tapGesture.Tapped += (sender, args) =>
{
Tapped?.Invoke(this, EventArgs.Empty);
};
GestureRecognizers.Add(tapGesture);
}

public int Col { set; get; }

public int Row { set; get; }

public bool IsAlive


{
set
{
if (isAlive != value)
{
isAlive = value;
BackgroundColor = isAlive ? Color.Black : Color.White;
}
}
get
{
return isAlive;
}
}
}

LifeCell agrega tres propiedades más a BoxView : las Col Row propiedades y almacenan la posición de la celda
dentro de la cuadrícula y la IsAlive propiedad indica su estado. La IsAlive propiedad también establece la
Color propiedad de BoxView en negro Si la celda está activa y en blanco si la celda no está activa.

LifeCell también instala un TapGestureRecognizer para permitir que el usuario alterne el estado de las celdas al
puntear en ellas. La clase convierte el Tapped evento del reconocedor de gestos en su propio Tapped evento.
El programa GameOfLife también incluye una LifeGrid clase que encapsula gran parte de la lógica del juego y
una MainPage clase que controla los objetos visuales del programa. Incluyen una superposición que describe las
reglas del juego. Este es el programa en acción que muestra un par de cientos LifeCell de objetos en la página:
Crear un reloj digital
El programa DotMatrixClock crea 210 BoxView elementos para simular los puntos de una pantalla de matriz de
puntos de 5 por 7 antigua. Puede leer la hora en modo vertical u horizontal, pero es más grande en horizontal:

El archivo XAML hace poco más que crear una instancia del que se AbsoluteLayout usa para el reloj:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DotMatrixClock"
x:Class="DotMatrixClock.MainPage"
Padding="10"
SizeChanged="OnPageSizeChanged">

<AbsoluteLayout x:Name="absoluteLayout"
VerticalOptions="Center" />
</ContentPage>

Todo lo demás se produce en el archivo de código subyacente. La lógica de visualización de matriz de puntos se ha
simplificado en gran medida por la definición de varias matrices que describen los puntos correspondientes a
cada uno de los 10 dígitos y dos puntos:

public partial class MainPage : ContentPage


{
// Total dots horizontally and vertically.
const int horzDots = 41;
const int vertDots = 7;

// 5 x 7 dot matrix patterns for 0 through 9.


static readonly int[, ,] numberPatterns = new int[10, 7, 5]
{
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 1, 1}, { 1, 0, 1, 0, 1},
{ 1, 1, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 1, 0, 0}, { 0, 1, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0},
{ 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 1, 1, 1, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0},
{ 0, 0, 1, 0, 0}, { 0, 1, 0, 0, 0}, { 1, 1, 1, 1, 1}
},
{
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 0, 1, 0},
{ 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 0, 1, 0}, { 0, 0, 1, 1, 0}, { 0, 1, 0, 1, 0}, { 1, 0, 0, 1, 0},
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 0, 1, 0}
},
{
{ 1, 1, 1, 1, 1}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0}, { 0, 0, 0, 0, 1},
{ 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 1, 1, 0}, { 0, 1, 0, 0, 0}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0},
{ 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0},
{ 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0},
{ 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 1},
{ 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 1, 1, 0, 0}
},
};

// Dot matrix pattern for a colon.


static readonly int[,] colonPattern = new int[7, 2]
{
{ 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }
};

// BoxView colors for on and off.


static readonly Color colorOn = Color.Red;
static readonly Color colorOff = new Color(0.5, 0.5, 0.5, 0.25);

// Box views for 6 digits, 7 rows, 5 columns.


BoxView[, ,] digitBoxViews = new BoxView[6, 7, 5];

···
}

Estos campos finalizan con una matriz tridimensional de BoxView elementos para almacenar los patrones de
puntos para los seis dígitos.
El constructor crea todos los BoxView elementos de los dígitos y dos puntos, y también inicializa la Color
propiedad de los BoxView elementos de los dos puntos:

public partial class MainPage : ContentPage


{

···

public MainPage()
{
InitializeComponent();

// BoxView dot dimensions.


double height = 0.85 / vertDots;
double width = 0.85 / horzDots;

// Create and assemble the BoxViews.


double xIncrement = 1.0 / (horzDots - 1);
double yIncrement = 1.0 / (vertDots - 1);
double x = 0;

for (int digit = 0; digit < 6; digit++)


{
for (int col = 0; col < 5; col++)
{
double y = 0;

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


{
// Create the digit BoxView and add to layout.
BoxView boxView = new BoxView();
digitBoxViews[digit, row, col] = boxView;
absoluteLayout.Children.Add(boxView,
new Rectangle(x, y, width, height),
AbsoluteLayoutFlags.All);
y += yIncrement;
}
x += xIncrement;
}
x += xIncrement;

// Colons between the hours, minutes, and seconds.


if (digit == 1 || digit == 3)
{
int colon = digit / 2;

for (int col = 0; col < 2; col++)


{
double y = 0;

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


{
// Create the BoxView and set the color.
BoxView boxView = new BoxView
{
Color = colonPattern[row, col] == 1 ?
colorOn : colorOff
};
absoluteLayout.Children.Add(boxView,
new Rectangle(x, y, width, height),
AbsoluteLayoutFlags.All);
y += yIncrement;
}
x += xIncrement;
}
x += xIncrement;
}
}

// Set the timer and initialize with a manual call.


Device.StartTimer(TimeSpan.FromSeconds(1), OnTimer);
OnTimer();
}

···

Este programa utiliza la característica de posicionamiento y ajuste de tamaño relativa de AbsoluteLayout . El ancho
y el alto de cada BoxView se establecen en valores fraccionarios, en concreto, el 85% de 1 dividido entre el
número de puntos horizontales y verticales. Las posiciones también se establecen en valores fraccionarios.
Dado que todas las posiciones y tamaños son relativos al tamaño total de AbsoluteLayout , el SizeChanged
controlador de la página solo necesita establecer un HeightRequest de AbsoluteLayout :

public partial class MainPage : ContentPage


{

···

void OnPageSizeChanged(object sender, EventArgs args)


{
// No chance a display will have an aspect ratio > 41:7
absoluteLayout.HeightRequest = vertDots * Width / horzDots;
}

···

El ancho de AbsoluteLayout se establece automáticamente porque se ajusta al ancho completo de la página.


El código final de la MainPage clase procesa la devolución de llamada del temporizador y colorea los puntos de
cada dígito. La definición de las matrices multidimensionales al principio del archivo de código subyacente ayuda
a que esta lógica sea la parte más sencilla del programa:
public partial class MainPage : ContentPage
{

···

bool OnTimer()
{
DateTime dateTime = DateTime.Now;

// Convert 24-hour clock to 12-hour clock.


int hour = (dateTime.Hour + 11) % 12 + 1;

// Set the dot colors for each digit separately.


SetDotMatrix(0, hour / 10);
SetDotMatrix(1, hour % 10);
SetDotMatrix(2, dateTime.Minute / 10);
SetDotMatrix(3, dateTime.Minute % 10);
SetDotMatrix(4, dateTime.Second / 10);
SetDotMatrix(5, dateTime.Second % 10);
return true;
}

void SetDotMatrix(int index, int digit)


{
for (int row = 0; row < 7; row++)
for (int col = 0; col < 5; col++)
{
bool isOn = numberPatterns[digit, row, col] == 1;
Color color = isOn ? colorOn : colorOff;
digitBoxViews[index, row, col].Color = color;
}
}
}

Crear un reloj analógico


Un reloj de matriz de puntos podría parecer una aplicación obvia de BoxView , pero BoxView los elementos
también son capaces de realizar un reloj analógico:

Todos los objetos visuales del programa BoxViewClock son elementos secundarios de AbsoluteLayout . Estos
elementos se ajustan mediante la LayoutBounds propiedad adjunta y se giran con la Rotation propiedad.
BoxView Se crean instancias de los tres elementos de las manecillas del reloj en el archivo XAML, pero no se
colocan ni se ajusta su tamaño:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BoxViewClock"
x:Class="BoxViewClock.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>

<AbsoluteLayout x:Name="absoluteLayout"
SizeChanged="OnAbsoluteLayoutSizeChanged">

<BoxView x:Name="hourHand"
Color="Black" />

<BoxView x:Name="minuteHand"
Color="Black" />

<BoxView x:Name="secondHand"
Color="Black" />
</AbsoluteLayout>
</ContentPage>

El constructor del archivo de código subyacente crea instancias de los BoxView elementos 60 para las marcas de
graduación en torno a la circunferencia del reloj:

public partial class MainPage : ContentPage


{

···

BoxView[] tickMarks = new BoxView[60];

public MainPage()
{
InitializeComponent();

// Create the tick marks (to be sized and positioned later).


for (int i = 0; i < tickMarks.Length; i++)
{
tickMarks[i] = new BoxView { Color = Color.Black };
absoluteLayout.Children.Add(tickMarks[i]);
}

Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), OnTimerTick);


}

···

El tamaño y la posición de todos los BoxView elementos se producen en el SizeChanged controlador de


AbsoluteLayout . Una pequeña estructura interna de la clase denominada HandParams describe el tamaño de cada
una de las tres manecillas en relación con el tamaño total del reloj:
public partial class MainPage : ContentPage
{
// Structure for storing information about the three hands.
struct HandParams
{
public HandParams(double width, double height, double offset) : this()
{
Width = width;
Height = height;
Offset = offset;
}

public double Width { private set; get; } // fraction of radius


public double Height { private set; get; } // ditto
public double Offset { private set; get; } // relative to center pivot
}

static readonly HandParams secondParams = new HandParams(0.02, 1.1, 0.85);


static readonly HandParams minuteParams = new HandParams(0.05, 0.8, 0.9);
static readonly HandParams hourParams = new HandParams(0.125, 0.65, 0.9);

···

El SizeChanged controlador determina el centro y el radio de y, AbsoluteLayout a continuación, ajusta el tamaño y


coloca los BoxView elementos 60 usados como marcas de graduación. El for bucle concluye estableciendo la
Rotation propiedad de cada uno de estos BoxView elementos. Al final del SizeChanged controlador, LayoutHand
se llama al método para ajustar el tamaño y la posición de las tres manos del reloj:
public partial class MainPage : ContentPage
{

···

void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)


{
// Get the center and radius of the AbsoluteLayout.
Point center = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
double radius = 0.45 * Math.Min(absoluteLayout.Width, absoluteLayout.Height);

// Position, size, and rotate the 60 tick marks.


for (int index = 0; index < tickMarks.Length; index++)
{
double size = radius / (index % 5 == 0 ? 15 : 30);
double radians = index * 2 * Math.PI / tickMarks.Length;
double x = center.X + radius * Math.Sin(radians) - size / 2;
double y = center.Y - radius * Math.Cos(radians) - size / 2;
AbsoluteLayout.SetLayoutBounds(tickMarks[index], new Rectangle(x, y, size, size));
tickMarks[index].Rotation = 180 * radians / Math.PI;
}

// Position and size the three hands.


LayoutHand(secondHand, secondParams, center, radius);
LayoutHand(minuteHand, minuteParams, center, radius);
LayoutHand(hourHand, hourParams, center, radius);
}

void LayoutHand(BoxView boxView, HandParams handParams, Point center, double radius)


{
double width = handParams.Width * radius;
double height = handParams.Height * radius;
double offset = handParams.Offset;

AbsoluteLayout.SetLayoutBounds(boxView,
new Rectangle(center.X - 0.5 * width,
center.Y - offset * height,
width, height));

// Set the AnchorY property for rotations.


boxView.AnchorY = handParams.Offset;
}

···

El LayoutHand método dimensiona y coloca cada mano para apuntar directamente hasta la posición 12:00. Al final
del método, la AnchorY propiedad se establece en una posición que corresponde al centro del reloj. Indica el
centro de rotación.
Las manos se giran en la función de devolución de llamada del temporizador:
public partial class MainPage : ContentPage
{

···

bool OnTimerTick()
{
// Set rotation angles for hour and minute hands.
DateTime dateTime = DateTime.Now;
hourHand.Rotation = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute;
minuteHand.Rotation = 6 * dateTime.Minute + 0.1 * dateTime.Second;

// Do an animation for the second hand.


double t = dateTime.Millisecond / 1000.0;

if (t < 0.5)
{
t = 0.5 * Easing.SpringIn.Ease(t / 0.5);
}
else
{
t = 0.5 * (1 + Easing.SpringOut.Ease((t - 0.5) / 0.5));
}

secondHand.Rotation = 6 * (dateTime.Second + t);


return true;
}
}

La segunda mano se trata de un poco diferente: se aplica una función de aceleración de animación para que el
movimiento parezca mecánico, en lugar de suave. En cada paso, la segunda mano vuelve un poco y, a
continuación, supera su destino. Este pequeño fragmento de código agrega mucho al realismo del movimiento.

Vínculos relacionados
BoxView básico (ejemplo)
Decoración de texto (ejemplo)
Colores de ListView (ejemplo)
Juego de vida (ejemplo)
Reloj de matriz de puntos (ejemplo)
Reloj BoxView (ejemplo)
BoxView
:::no-loc(Xamarin.Forms)::: Expansor
18/12/2020 • 13 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms)::: Expander control proporciona un contenedor expansible para hospedar cualquier
contenido. El control tiene un encabezado y contenido, y el contenido se muestra u oculta al puntear en el
Expander encabezado. Cuando solo Expander se muestra el encabezado, Expander se contrae. Cuando el
Expander contenido está visible, Expander se expande.

Las siguientes capturas de pantallas muestran un Expander en los Estados contraído y expandido, con cuadros
rojos que indican el encabezado y el contenido:

IMPORTANT
Expander es experimental actualmente y solo se puede usar si se establece la Expander_Experimental marca. Para más
información, vea Marcas experimentales.
Además, el Expander control está totalmente implementado en el :::no-loc(Xamarin.Forms)::: espacio de nombres.
Por lo tanto, está disponible en todas las plataformas compatibles con :::no-loc(Xamarin.Forms)::: .

El Expander control define las siguientes propiedades:


CollapseAnimationEasing , de tipo Easing , que representa la función de aceleración que se va a aplicar al
Expander contenido cuando se contrae.
CollapseAnimationLength , de tipo uint , que define la duración de la animación cuando Expander se contrae.
El valor predeterminado de esta propiedad es 250 ms.
Command , de tipo ICommand , que se ejecuta cuando Expander se puntea el encabezado.
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .
Content , de tipo View , que define el contenido que se va a mostrar cuando se expande el control Expander .
ContentTemplate , de tipo DataTemplate , que es la plantilla que se usa para aumentar de forma dinámica el
contenido de Expander .
ExpandAnimationEasing , de tipo Easing , que representa la función de aceleración que se va a aplicar al
Expander contenido durante la expansión.
ExpandAnimationLength , de tipo uint , que define la duración de la animación cuando se Expander expande. El
valor predeterminado de esta propiedad es 250 ms.
ForceUpdateSizeCommand , de tipo ICommand , que define el comando que se ejecuta cuando se fuerza la
actualización del tamaño de Expander . Esta propiedad utiliza el OneWayToSource modo de enlace.
Header , de tipo View , que define el contenido del encabezado.
IsExpanded , de tipo bool , que determina si Expander se expande. Esta propiedad utiliza el TwoWay modo de
enlace y tiene un valor predeterminado de false .
State , de tipo ExpanderState , que representa el estado de Expander . Esta propiedad utiliza el
OneWayToSource modo de enlace.

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.

NOTE
La propiedad Content es la propiedad de contenido de la clase Expander y, por tanto, no es necesario establecerla
explícitamente desde XAML.

La enumeración ExpanderState define los miembros siguientes:


Expanding indica que Expander se está expandiendo.
Expanded indica que Expander está expandido.
Collapsing indica que Expander se contrae.
Collapsed indica que Expander está contraído.

El Expander control también define un Tapped evento que se desencadena cuando Expander se puntea el
encabezado. Además, Expander incluye un ForceUpdateSize método al que se puede llamar para cambiar
mediante programación el tamaño de Expander en tiempo de ejecución.

Crear un expansor
En el ejemplo siguiente se muestra cómo crear una instancia Expander de en XAML:

<Expander>
<Expander.Header>
<Label Text="Baboon"
FontAttributes="Bold"
FontSize="Medium" />
</Expander.Header>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Source="http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200p
x-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
Aspect="AspectFill"
HeightRequest="120"
WidthRequest="120" />
<Label Grid.Column="1"
Text="Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of
the subfamily Cercopithecinae."
FontAttributes="Italic" />
</Grid>
</Expander>
En este ejemplo, Expander se contrae de forma predeterminada y muestra Label como su encabezado. Al pulsar
en el encabezado Expander , se expande el contenido para mostrar su contenido, que es un Grid contenedor que
contiene controles secundarios. Cuando Expander se expande el, al puntear en el encabezado, se contrae
Expander .

IMPORTANT
Al establecer la Expander.Content propiedad, ya sea implícita o explícitamente, el Expander contenido se crea cuando se
navega a la página que lo contiene, incluso si Expander está contraído. Sin embargo, la Expander.ContentTemplate
propiedad se puede establecer en el contenido que solo se aumenta cuando se Expander expande por primera vez. Para
obtener más información, consulte crear contenido de expansión a petición.

Como alternativa, Expander se puede crear en el código:

Expander expander = new Expander


{
Header = new Label
{
Text = "Baboon",
FontAttributes = FontAttributes.Bold,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
}
};

Grid grid = new Grid


{
Padding = new Thickness(10),
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = GridLength.Auto }
}
};

grid.Children.Add(new Image
{
Source =
"http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-
Papio_anubis_%28Serengeti%2C_2009%29.jpg",
Aspect = Aspect.AspectFill,
HeightRequest = 120,
WidthRequest = 120
});

grid.Children.Add(new Label
{
Text = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the
subfamily Cercopithecinae.",
FontAttributes = FontAttributes.Italic
}, 1, 0);

expander.Content = grid;

Creación de contenido de expansión a petición


Expander el contenido se puede crear a petición, en respuesta a la Expander expansión. Esto puede realizarse
estableciendo la Expander.ContentTemplate propiedad en un DataTemplate que contiene el contenido:
<Expander>
<Expander.Header>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
</Expander.Header>
<Expander.ContentTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="120"
WidthRequest="120" />
<Label Grid.Column="1"
Text="{Binding Details}"
FontAttributes="Italic" />
</Grid>
</DataTemplate>
</Expander.ContentTemplate>
</Expander>

En este ejemplo, el Expander contenido se infla únicamente cuando se Expander expande por primera vez.
La ventaja de este enfoque es que cuando una página contiene varios Expander objetos, el contenido de un
Expander solo se crea cuando lo expande el usuario por primera vez.

Agregar un indicador de expansión


Image Se puede Agregar un a un Expander encabezado para proporcionar una indicación visual del estado de
expansión. DataTrigger Se puede asociar a Image , que cambia la Source propiedad basándose en el valor de la
Expander.IsExpanded propiedad:
<Expander>
<Expander.Header>
<Grid>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
<Image Source="expand.png"
HorizontalOptions="End"
VerticalOptions="Start">
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding Source={RelativeSource AncestorType={x:Type Expander}},
Path=IsExpanded}"
Value="True">
<Setter Property="Source"
Value="collapse.png" />
</DataTrigger>
</Image.Triggers>
</Image>
</Grid>
</Expander.Header>
<Expander.ContentTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="120"
WidthRequest="120" />
<Label Grid.Column="1"
Text="{Binding Details}"
FontAttributes="Italic" />
</Grid>
</DataTemplate>
</Expander.ContentTemplate>
</Expander>

En este ejemplo, Image muestra el expand icono de forma predeterminada:

La IsExpanded propiedad se convierte true Expander en cuando se puntea el encabezado, lo que hace que se
muestre el collapse icono:
Para obtener más información acerca de los desencadenadores, vea :::no-loc(Xamarin.Forms)::: desencadenadores.

Insertar un expansor en un expansor


El contenido de Expander se puede establecer en otro Expander control para habilitar varios niveles de expansión.
En el código XAML siguiente Expander se muestra un cuyo contenido es otro Expander objeto:

<Expander>
<Expander.Header>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
</Expander.Header>
<Expander Padding="10">
<Expander.Header>
<Label Text="{Binding Location}"
FontSize="Medium" />
</Expander.Header>
<Expander.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="120"
WidthRequest="120" />
<Label Grid.Column="1"
Text="{Binding Details}"
FontAttributes="Italic" />
</Grid>
</DataTemplate>
</Expander.ContentTemplate>
</Expander>
</Expander>

En este ejemplo, al pulsar el Expander encabezado raíz se revela el encabezado del elemento secundario Expander
:
Al pulsar el Expander encabezado secundario, el contenido se infla y muestra:

Definir la animación de expandir y contraer


La animación que se produce cuando se Expander expande o se contrae se puede definir mediante el
establecimiento de las ExpandAnimationEasing CollapseAnimationEasing propiedades y en cualquiera de las
funciones de aceleración incluidas en :::no-loc(Xamarin.Forms)::: o en las funciones de aceleración personalizadas.
De forma predeterminada, las animaciones de expandir y contraer se producen sobre 250 ms. Sin embargo, estas
duraciones se pueden cambiar estableciendo las ExpandAnimationLength CollapseAnimationLength propiedades y
en uint valores.
En el código XAML siguiente se muestra un ejemplo de la definición de la animación que se produce cuando el
Expander usuario expande o contrae el usuario:
<Expander ExpandAnimationEasing="{x:Static Easing.CubicIn}"
ExpandAnimationLength="500"
CollapseAnimationEasing="{x:Static Easing.CubicOut}"
CollapseAnimationLength="500">
<Expander.Header>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
</Expander.Header>
<Expander.ContentTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="120"
WidthRequest="120" />
<Label Grid.Column="1"
Text="{Binding Details}"
FontAttributes="Italic" />
</Grid>
</DataTemplate>
</Expander.ContentTemplate>
</Expander>

En este ejemplo, la CubicIn función de entradas y salidas lentas acelera lentamente la animación de expansión a
través de 500 ms y la CubicOut función de aceleración reduce rápidamente la animación de contraer a través de
500 ms.
Para obtener más información sobre las funciones de aceleración, consulte :::no-loc(Xamarin.Forms)::: funciones de
aceleración.

Cambiar el tamaño de un expansor en tiempo de ejecución


Expander Se puede cambiar el tamaño mediante programación en tiempo de ejecución con el ForceUpdateSize
método.
Dado un Expander denominado expander , cuyo contenido incluye un Label que tiene un TapGestureRecognizer
adjunto, en el ejemplo de código siguiente se muestra la llamada al ForceUpdateSize método:

void OnLabelTapped(object sender, EventArgs e)


{
Label label = sender as Label;
Expander expander = label.Parent.Parent.Parent as Expander;

if (label.FontSize == Device.GetNamedSize(NamedSize.Default, label))


{
label.FontSize = Device.GetNamedSize(NamedSize.Large, label);
}
else
{
label.FontSize = Device.GetNamedSize(NamedSize.Default, label);
}
expander.ForceUpdateSize();
}

En este ejemplo, el FontSize de un Label cambia cuando Label se puntea. Debido al tamaño del cambio de
fuente, es necesario actualizar el tamaño de llamando a Expander su ForceUpdateSize método.
Deshabilitar un expansor
Una aplicación puede entrar en un estado en el que Expander la expansión de no es una operación válida. En tales
casos, Expander se puede deshabilitar estableciendo su IsEnabled propiedad en false. Esto impedirá que los
usuarios expandan o contraigan Expander .

Vínculos relacionados
Demostraciones de Expander (ejemplo)
:::no-loc(Xamarin.Forms)::: Funciones de aceleración
Desencadenadores de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Diseños enlazables
Imágenes en :::no-loc(Xamarin.Forms):::
18/12/2020 • 26 minutes to read • Edit Online

Descargar el ejemplo
Las imágenes se pueden compartir entre plataformas con :::no-loc(Xamarin.Forms)::: , se pueden cargar
específicamente para cada plataforma o se pueden descargar para su visualización.
Las imágenes son una parte fundamental de la navegación, la facilidad de uso y la personalización de la
aplicación. :::no-loc(Xamarin.Forms)::: las aplicaciones deben poder compartir imágenes en todas las plataformas,
pero también pueden mostrar imágenes diferentes en cada plataforma.
También se necesitan imágenes específicas de la plataforma para iconos y pantallas de presentación. deben
configurarse en cada plataforma.

Mostrar imágenes
:::no-loc(Xamarin.Forms)::: usa la Image vista para mostrar las imágenes en una página. Tiene varias
propiedades importantes:
Source : Una ImageSource instancia, ya sea archivo, Uri o recurso, que establece la imagen que se va a
mostrar.
Aspect : Cómo ajustar el tamaño de la imagen dentro de los límites en los que se muestra (si se va a
expandir, recortar o Mostrar en pantalla ancha).
ImageSource las instancias se pueden obtener mediante métodos estáticos para cada tipo de origen de imagen:
FromFile -Requiere un nombre de archivo o una ruta de archivo que se pueda resolver en cada plataforma.
FromUri : Requiere un objeto URI, por ejemplo,. new Uri("http://server.com/image.jpg") .
FromResource -Requiere un identificador de recursos para un archivo de imagen incrustado en el proyecto de
biblioteca o de aplicación de .NET Standard, con una acción de compilación: EmbeddedResource .
FromStream : Requiere una secuencia que proporcione datos de imagen.

La Aspect propiedad determina cómo se escalará la imagen para ajustarse al área de presentación:
Fill : Estira la imagen para rellenar completamente el área de presentación. Esto puede dar lugar a que la
imagen se distorsione.
AspectFill -Recorta la imagen para que rellene el área de visualización conservando el aspecto (es decir, sin
distorsión).
AspectFit : Muestra una panorámica de la imagen (si es necesario) para que toda la imagen quepa en el
área de presentación, con un espacio en blanco agregado a la parte superior o inferior, en función de si la
imagen es ancha o alta.
Las imágenes se pueden cargar desde un archivo local, un recurso incrustado, descargadoo cargado desde una
secuencia. Además, los iconos de fuente se pueden mostrar en la Image vista especificando los datos del icono
de fuente en un FontImageSource objeto. Para obtener más información, vea Mostrar iconos de fuentes en la
guía de fuentes .

Imágenes locales
Los archivos de imagen se pueden agregar a cada proyecto de aplicación y se puede hacer referencia a ellos
desde el :::no-loc(Xamarin.Forms)::: código compartido. Este método de distribución de imágenes es necesario
cuando las imágenes son específicas de la plataforma, por ejemplo, al usar resoluciones diferentes en
plataformas diversas, o bien al emplear diseños que difieren ligeramente.
Para usar una sola imagen en todas las aplicaciones, se debe usar el mismo nombre de archivo en todas las
plataformas y debe ser un nombre de recurso de Android válido (es decir, solo letras minúsculas, números, el
carácter de subrayado y el período permitido).
iOS : la manera preferida de administrar y admitir imágenes ya que Ios 9 es usar conjuntos de imágenes
del catálogo de activos , que deben contener todas las versiones de una imagen necesarias para admitir
varios dispositivos y factores de escala para una aplicación. Para obtener más información, consulte Agregar
imágenes a un conjunto de imágenes del catálogo de recursos.
Android : incluya imágenes en el directorio Resources/drawable con la acción de compilación:
AndroidResource . También se pueden proporcionar versiones alta y baja de PPP de una imagen (en los
subdirectorios de recursos con nombre adecuados, como drawable-ldpi , drawable-hdpi y drawable-
xhdpi ).
Plataforma universal de Windows (UWP) : de forma predeterminada, las imágenes se deben colocar en
el directorio raíz de la aplicación con la acción de compilación: contenido . Como alternativa, las
imágenes se pueden colocar en un directorio diferente que, a continuación, se especifican con una
plataforma específica. Para obtener más información, consulte Directorio de imagen predeterminado en
Windows.

IMPORTANT
Antes de iOS 9, las imágenes se colocaban normalmente en la carpeta de recursos con la acción de compilación:
BundleResource . Sin embargo, Apple ha dejado de usar este método de trabajo con imágenes en una aplicación iOS.
Para obtener más información, vea tamaños de imagen y nombres de archivo.

La compatibilidad con estas reglas para la selección de nombres y la ubicación de los archivos permite que el
código XAML siguiente cargue y muestre la imagen en todas las plataformas:

<Image Source="waterfront.jpg" />

El código de C# equivalente es el siguiente:

var image = new Image { Source = "waterfront.jpg" };

Las siguientes capturas de pantallas muestran el resultado de mostrar una imagen local en cada plataforma:
Para mayor flexibilidad Device.RuntimePlatform , se puede usar la propiedad para seleccionar un archivo de
imagen o una ruta de acceso diferentes para algunas plataformas o para todas, como se muestra en este
ejemplo de código:

image.Source = Device.RuntimePlatform == Device.Android


? ImageSource.FromFile("waterfront.jpg")
: ImageSource.FromFile("Images/waterfront.jpg");

IMPORTANT
Para usar el mismo nombre de archivo de imagen en todas las plataformas, el nombre debe ser válido en todas las
plataformas. Los drawabless de Android tienen restricciones de nomenclatura, solo se permiten letras minúsculas,
números, caracteres de subrayado y período, y para la compatibilidad multiplataforma, debe seguir también en todas las
demás plataformas. El nombre de archivo de ejemplo waterfront.png sigue las reglas, pero los ejemplos de nombres de
archivo no válidos incluyen "Water front.png", "WaterFront.png", "water-front.png" y "wåterfront.png".

Resoluciones nativas (retina y alta PPP)


iOS, Android y UWP incluyen compatibilidad con distintas resoluciones de imagen, donde el sistema operativo
elige la imagen adecuada en tiempo de ejecución en función de las capacidades del dispositivo. :::no-
loc(Xamarin.Forms)::: usa las API de las plataformas nativas para cargar imágenes locales, por lo que admite
automáticamente resoluciones alternativas si los archivos se denominan correctamente y se encuentran en el
proyecto.
La mejor manera de administrar imágenes desde iOS 9 es arrastrar imágenes para cada resolución necesaria
para el conjunto de imágenes del catálogo de activos adecuado. Para obtener más información, consulte
Agregar imágenes a un conjunto de imágenes del catálogo de recursos.
Antes de iOS 9, las versiones de retina de la imagen podrían colocarse en la carpeta de recursos : dos y tres
veces la resolución con los @2x @3x sufijos o en el nombre de archivo antes de la extensión de archivo (por
ejemplo, [email protected] ). Sin embargo, Apple ha dejado de usar este método de trabajo con imágenes en
una aplicación iOS. Para obtener más información, vea tamaños de imagen y nombres de archivo.
Las imágenes de resolución alternativas de Android deben colocarse en directorios con nombre especial en el
proyecto de Android, tal como se muestra en la siguiente captura de pantalla:

Los nombres de los archivos de imagen de UWP se pueden agregar con el sufijo .scale-xxx anterior a la
extensión de archivo, donde xxx es el porcentaje de escalado aplicado al activo, por ejemplo, myimage.scale-
200.png . A continuación, se puede hacer referencia a las imágenes en código o XAML sin el modificador de
escala, por ejemplo, simplemente myimage.png . La plataforma seleccionará la escala de recursos más cercana
adecuada en función de los PPP actuales de la pantalla.
Controles adicionales que muestran imágenes
Algunos controles tienen propiedades que muestran una imagen, como:
Button tiene una ImageSource propiedad que se puede establecer en una imagen de mapa de bits que
se va a mostrar en Button . Para obtener más información, vea usar mapas de bits con botones.
ImageButton tiene una Source propiedad que se puede establecer en la imagen que se va a mostrar en
ImageButton . Para obtener más información, vea establecer el origen de la imagen.
ToolbarItem tiene una IconImageSource propiedad que se puede establecer en una imagen que se carga
desde un archivo, un recurso incrustado, un URI o un flujo.
ImageCell tiene una ImageSource propiedad que se puede establecer en una imagen recuperada de un
archivo, un recurso incrustado, un URI o un flujo.
Page. Cualquier tipo de página que derive de Page tiene IconImageSource BackgroundImageSource
propiedades y, a las que se puede asignar un archivo, un recurso incrustado, un URI o un flujo. En
determinadas circunstancias, como cuando NavigationPage se muestra un ContentPage , el icono se
mostrará si es compatible con la plataforma.

IMPORTANT
En iOS, la Page.IconImageSource propiedad no se puede rellenar a partir de una imagen en un conjunto de
imágenes del catálogo de recursos. En su lugar, cargue las imágenes de icono de la Page.IconImageSource
propiedad desde un archivo, un recurso incrustado, un URI o un flujo.

Imágenes incrustadas
Las imágenes incrustadas también se incluyen con una aplicación (como las imágenes locales), pero en lugar de
tener una copia de la imagen en la estructura de archivos de cada aplicación, el archivo de imagen se incrusta en
el ensamblado como un recurso. Se recomienda este método de distribución de imágenes cuando se usan
imágenes idénticas en cada plataforma y es especialmente adecuado para crear componentes, ya que la imagen
se incluye en el código.
Para insertar una imagen en un proyecto, haga clic con el botón derecho para agregar nuevos elementos y
seleccione la imagen/s que desea agregar. De forma predeterminada, la imagen tendrá la acción de
compilación: ninguno ; debe establecerse en acción de compilación: EmbeddedResource .
Visual Studio
Visual Studio para Mac
La acción de compilación se puede ver y cambiar en la ventana propiedades de un archivo.
En este ejemplo, el identificador de recurso es WorkingWithImages.beach.jpg . El IDE ha generado este valor
predeterminado mediante la concatenación del espacio de nombres predeterminado para este proyecto
con el nombre de archivo, con un punto (.) entre cada valor.
Si coloca imágenes incrustadas en carpetas dentro del proyecto, los nombres de carpeta también se separan por
puntos (.) en el identificador de recurso. Al mover la imagen de beach.jpg a una carpeta llamada mis imágenes
, se produciría un identificador de recurso de WorkingWithImages.MyImages.beach.jpg
El código para cargar una imagen incrustada simplemente pasa el identificador de recurso al
ImageSource.FromResource método, como se muestra a continuación:

Image embeddedImage = new Image


{
Source = ImageSource.FromResource("WorkingWithImages.beach.jpg", typeof(MyClass).GetTypeInfo().Assembly)
};

NOTE
Para admitir la visualización de imágenes incrustadas en modo de versión en el Plataforma universal de Windows, es
necesario usar la sobrecarga de ImageSource.FromResource que especifica el ensamblado de origen en el que se va a
buscar la imagen.

Actualmente no hay ninguna conversión implícita para los identificadores de recursos. En su lugar, debe usar
ImageSource.FromResource o new ResourceImageSource() para cargar imágenes incrustadas.

Las siguientes capturas de pantallas muestran el resultado de mostrar una imagen incrustada en cada
plataforma:
XAML
Dado que no hay ningún convertidor de tipos integrado de string a ResourceImageSource , estos tipos de
imágenes no se pueden cargar de forma nativa por XAML. En su lugar, se puede escribir una extensión de
marcado XAML personalizada simple para cargar imágenes mediante un identificador de recurso
especificado en XAML:

[ContentProperty (nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }

public object ProvideValue (IServiceProvider serviceProvider)


{
if (Source == null)
{
return null;
}

// Do your translation lookup here, using whatever method you require


var imageSource = ImageSource.FromResource(Source,
typeof(ImageResourceExtension).GetTypeInfo().Assembly);

return imageSource;
}
}

NOTE
Para admitir la visualización de imágenes incrustadas en modo de versión en el Plataforma universal de Windows, es
necesario usar la sobrecarga de ImageSource.FromResource que especifica el ensamblado de origen en el que se va a
buscar la imagen.

Para usar esta extensión, agregue un personalizado xmlns al XAML, con los valores de espacio de nombres y
ensamblado correctos para el proyecto. Después, el origen de la imagen se puede establecer mediante esta
sintaxis: {local:ImageResource WorkingWithImages.beach.jpg} . A continuación se muestra un ejemplo de XAML
completo:
<?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:local="clr-namespace:WorkingWithImages;assembly=WorkingWithImages"
x:Class="WorkingWithImages.EmbeddedImagesXaml">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center">
<!-- use a custom Markup Extension -->
<Image Source="{local:ImageResource WorkingWithImages.beach.jpg}" />
</StackLayout>
</ContentPage>

Solucionar problemas de imágenes incrustadas


Depurar código
Dado que a veces es difícil comprender por qué no se carga un recurso de imagen concreto, se puede agregar
temporalmente el siguiente código de depuración a una aplicación para ayudar a confirmar que los recursos
están configurados correctamente. Generará todos los recursos conocidos insertados en el ensamblado
especificado en la consola de para ayudar a depurar los problemas de carga de recursos.

using System.Reflection;
// ...
// NOTE: use for debugging, not in released app code!
var assembly = typeof(MyClass).GetTypeInfo().Assembly;
foreach (var res in assembly.GetManifestResourceNames())
{
System.Diagnostics.Debug.WriteLine("found resource: " + res);
}

Imágenes incrustadas en otros proyectos


De forma predeterminada, el ImageSource.FromResource método solo busca imágenes en el mismo ensamblado
que el código que llama al ImageSource.FromResource método. Con el código de depuración anterior, puede
determinar qué ensamblados contienen un recurso específico cambiando la typeof() instrucción a un Type
conocido para que esté en cada ensamblado.
Sin embargo, el ensamblado de origen en el que se busca una imagen incrustada se puede especificar como
argumento para el ImageSource.FromResource método:

var imageSource = ImageSource.FromResource("filename.png",


typeof(MyClass).GetTypeInfo().Assembly);

Descargar imágenes
Las imágenes se pueden descargar automáticamente para su presentación, tal como se muestra en el código
XAML siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WorkingWithImages.DownloadImagesXaml">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center">
<Label Text="Image UriSource Xaml" />
<Image Source="https://aka.ms/campus.jpg" />
<Label Text="campus.jpg gets downloaded from microsoft.com" />
</StackLayout>
</ContentPage>

El código de C# equivalente es el siguiente:


var webImage = new Image {
Source = ImageSource.FromUri(
new Uri("https://aka.ms/campus.jpg")
) };

El ImageSource.FromUri método requiere un Uri objeto y devuelve un nuevo UriImageSource que lee desde
Uri .
También hay una conversión implícita de las cadenas URI, por lo que el siguiente ejemplo también funcionará:

webImage.Source = "https://aka.ms/campus.jpg";

Las siguientes capturas de pantallas muestran el resultado de mostrar una imagen remota en cada plataforma:

Almacenamiento en caché de imágenes descargado


UriImageSource También admite el almacenamiento en caché de imágenes descargadas, configuradas mediante
las siguientes propiedades:
CachingEnabled -Si el almacenamiento en caché está habilitado ( true de forma predeterminada).
CacheValidity -A TimeSpan que define cuánto tiempo se almacenará la imagen localmente.

El almacenamiento en caché está habilitado de forma predeterminada y almacenará la imagen localmente


durante 24 horas. Para deshabilitar el almacenamiento en caché de una imagen determinada, cree una instancia
del origen de la imagen como se indica a continuación:

image.Source = new UriImageSource { CachingEnabled = false, Uri = new Uri("https://server.com/image") };

Para establecer un período de caché específico (por ejemplo, 5 días), cree una instancia del origen de la imagen
como se indica a continuación:

webImage.Source = new UriImageSource


{
Uri = new Uri("https://aka.ms/campus.jpg"),
CachingEnabled = true,
CacheValidity = new TimeSpan(5,0,0,0)
};

El almacenamiento en caché integrado facilita la compatibilidad con escenarios como el desplazamiento de listas
de imágenes, donde puede establecer (o enlazar) una imagen en cada celda y dejar que la memoria caché
integrada se encargue de volver a cargar la imagen cuando la celda se desplace de nuevo a la vista.
Archivos GIF animados
:::no-loc(Xamarin.Forms)::: incluye compatibilidad para mostrar pequeños archivos GIF animados. Esto se logra
estableciendo la Image.Source propiedad en un archivo GIF animado:

<Image Source="demo.gif" />

IMPORTANT
Aunque la compatibilidad con GIF animado en :::no-loc(Xamarin.Forms)::: incluye la capacidad de descargar archivos, no
admite el almacenamiento en caché ni los GIF animados de streaming.

De forma predeterminada, cuando se carga un GIF animado, no se reproducirá. Esto se debe


IsAnimationPlaying a que la propiedad, que controla si un GIF animado se reproduce o se detiene, tiene un
valor predeterminado de false . Esta propiedad, de tipo bool , está respaldada por un BindableProperty
objeto, lo que significa que puede ser el destino de un enlace de datos y con estilo.
Por lo tanto, cuando se carga un GIF animado, no se reproducirá hasta que la IsAnimationPlaying propiedad se
establezca en true . A continuación, la reproducción se puede detener estableciendo la IsAnimationPlaying
propiedad en false . Tenga en cuenta que esta propiedad no tiene ningún efecto al mostrar un origen de
imagen no GIF.

NOTE
En Android, la compatibilidad con GIF animados requiere que la aplicación use representadores rápidos y no funcionará si
ha optado por el uso de los representadores heredados. En UWP, la compatibilidad con GIF animados requiere una versión
mínima de la actualización de aniversario de Windows 10 (versión 1607).

Iconos y pantallas de presentación


Aunque no está relacionado con la Image vista, los iconos de la aplicación y las pantallas de presentación
también son un uso importante de las imágenes en los :::no-loc(Xamarin.Forms)::: proyectos.
La configuración de iconos y pantallas de presentación para :::no-loc(Xamarin.Forms)::: aplicaciones se realiza en
cada uno de los proyectos de aplicación. Esto significa que se generan imágenes con el tamaño correcto para
iOS, Android y UWP. Estas imágenes deben tener un nombre y estar ubicadas según los requisitos de cada
plataforma.

Iconos
Para obtener más información sobre la creación de estos recursos de aplicación, consulte las directrices sobre
Cómo trabajar con imágenes, Google iconografíay UWP para los activos de iconos e iconos .
Además, los iconos de fuente se pueden mostrar en la Image vista especificando los datos del icono de fuente
en un FontImageSource objeto. Para obtener más información, vea Mostrar iconos de fuentes en la guía de
fuentes .

Pantallas de presentación
Solo las aplicaciones de iOS y UWP requieren una pantalla de presentación (también denominada pantalla de
inicio o imagen predeterminada).
Consulte la documentación de iOS trabajar con imágenes y pantallas de presentación en el centro de desarrollo
de Windows.

Vínculos relacionados
WorkingWithImages (ejemplo)
iOS trabajar con imágenes
Iconografía de Android
Directrices sobre los activos de iconos y ventanas
:::no-loc(Xamarin.Forms)::: Rótulo
18/12/2020 • 25 minutes to read • Edit Online

Descargar el ejemplo
Mostrar texto en :::no-loc(Xamarin.Forms):::
La Label vista se usa para mostrar texto, tanto de una sola línea como de varias líneas. Las etiquetas pueden
tener decoraciones de texto, texto de color y usar fuentes personalizadas (familias, tamaños y opciones).

Decoraciones de texto
Las decoraciones de texto de subrayado y tachado se pueden aplicar a Label las instancias estableciendo la
Label.TextDecorations propiedad en uno o varios miembros de la TextDecorations enumeración:

None
Underline
Strikethrough

En el siguiente ejemplo de XAML se muestra cómo establecer la Label.TextDecorations propiedad:

<Label Text="This is underlined text." TextDecorations="Underline" />


<Label Text="This is text with strikethrough." TextDecorations="Strikethrough" />
<Label Text="This is underlined text with strikethrough." TextDecorations="Underline, Strikethrough" />

El código de C# equivalente es el siguiente:

var underlineLabel = new Label { Text = "This is underlined text.", TextDecorations =


TextDecorations.Underline };
var strikethroughLabel = new Label { Text = "This is text with strikethrough.", TextDecorations =
TextDecorations.Strikethrough };
var bothLabel = new Label { Text = "This is underlined text with strikethrough.", TextDecorations =
TextDecorations.Underline | TextDecorations.Strikethrough };

Las capturas de pantallas siguientes muestran los TextDecorations miembros de enumeración que se aplican a
Label las instancias:

NOTE
También se pueden aplicar decoraciones de texto a Span las instancias de. Para obtener más información sobre la Span
clase, vea texto con formato.

Transformar texto
Un Label puede transformar el uso de mayúsculas y minúsculas de su texto, almacenado en la Text propiedad,
estableciendo la TextTransform propiedad en un valor de la TextTransform enumeración. Esta enumeración tiene
cuatro valores:
None indica que el texto no se transformará.
Default indica que se utilizará el comportamiento predeterminado para la plataforma. Este es el valor
predeterminado de la propiedad TextTransform .
Lowercase indica que el texto se transformará a minúsculas.
Uppercase indica que el texto se transformará en mayúsculas.

En el ejemplo siguiente se muestra cómo transformar texto a mayúsculas:

<Label Text="This text will be displayed in uppercase."


TextTransform="Uppercase" />

El código de C# equivalente es el siguiente:

Label label = new Label


{
Text = "This text will be displayed in uppercase.",
TextTransform = TextTransform.Uppercase
};

espaciado entre caracteres


El espaciado de caracteres se puede aplicar a Label las instancias estableciendo la Label.CharacterSpacing
propiedad en un double valor:

<Label Text="Character spaced text"


CharacterSpacing="10" />

El código de C# equivalente es el siguiente:

Label label = new Label { Text = "Character spaced text", CharacterSpacing = 10 };

El resultado es que los caracteres del texto mostrados por Label están separados por las CharacterSpacing
unidades independientes del dispositivo.

Nuevas líneas
Hay dos técnicas principales para forzar el texto de una en Label una nueva línea, desde XAML:
1. Use el carácter de salto de línea Unicode, que es " & #10;".
2. Especifique el texto mediante la sintaxis del elemento de propiedad .
En el código siguiente se muestra un ejemplo de ambas técnicas:
<!-- Unicode line feed character -->
<Label Text="First line &#10; Second line" />

<!-- Property element syntax -->


<Label>
<Label.Text>
First line
Second line
</Label.Text>
</Label>

En C#, el texto se puede forzar en una nueva línea con el carácter "\n":

Label label = new Label { Text = "First line\nSecond line" };

Colores
Las etiquetas se pueden establecer para usar un color de texto personalizado mediante la TextColor propiedad
enlazable.
Es necesario tener especial cuidado para asegurarse de que se puedan usar los colores en cada plataforma. Dado
que cada plataforma tiene valores predeterminados diferentes para los colores de texto y de fondo, deberá tomar
precauciones para elegir un valor predeterminado que funcione en cada uno de ellos.
En el siguiente ejemplo de XAML se establece el color del texto de un Label :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextSample.LabelPage"
Title="Label Demo">
<StackLayout Padding="5,10">
<Label TextColor="#77d065" FontSize = "20" Text="This is a green label." />
</StackLayout>
</ContentPage>

El código de C# equivalente es el siguiente:

public partial class LabelPage : ContentPage


{
public LabelPage ()
{
InitializeComponent ();

var layout = new StackLayout { Padding = new Thickness(5,10) };


var label = new Label { Text="This is a green label.", TextColor = Color.FromHex("#77d065"), FontSize
= 20 };
layout.Children.Add(label);
this.Content = layout;
}
}

Las siguientes capturas de pantallas muestran el resultado de establecer la TextColor propiedad:


Para obtener más información sobre los colores, vea colores.

Fuentes
Para obtener más información sobre cómo especificar fuentes en un Label , vea fuentes.

Truncamiento y ajuste
Las etiquetas se pueden establecer para controlar el texto que no cabe en una línea de una de varias maneras,
expuesta por la LineBreakMode propiedad. LineBreakMode es una enumeración con los valores siguientes:
HeadTruncation – trunca el encabezado del texto, mostrando el final.
CharacterWrap – ajusta el texto en una nueva línea en el límite de un carácter.
MiddleTruncation – muestra el principio y el final del texto, con el reemplazo central por puntos suspensivos.
NoWrap – no ajusta el texto y muestra solo el texto que cabe en una línea.
TailTruncation – muestra el principio del texto, truncando el final.
WordWrap – ajusta el texto en el límite de palabras.

Mostrar un número específico de líneas


Se puede especificar el número de líneas que se muestran mediante Label el establecimiento de la
Label.MaxLines propiedad en un int valor:

Cuando MaxLines es-1, que es su valor predeterminado, Label respeta el valor de la LineBreakMode
propiedad para mostrar solo una línea, posiblemente truncada, o todas las líneas con todo el texto.
Cuando MaxLines es 0, Label no se muestra.
Cuando MaxLines es 1, el resultado es idéntico al establecimiento de la LineBreakMode propiedad en NoWrap ,
HeadTruncation , MiddleTruncation o TailTruncation . Sin embargo, el Label respetará el valor de la
LineBreakMode propiedad con respecto a la colocación de puntos suspensivos, si procede.
Cuando MaxLines es mayor que 1, mostrará Label hasta el número especificado de líneas, respetando el valor
de la LineBreakMode propiedad con respecto a la posición de los puntos suspensivos, si procede. Sin embargo,
MaxLines si se establece la propiedad en un valor mayor que 1, no se produce ningún efecto si la
LineBreakMode propiedad está establecida en NoWrap .

En el siguiente ejemplo de XAML se muestra cómo establecer la MaxLines propiedad en un Label :

<Label Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. In facilisis nulla eu felis fringilla
vulputate. Nullam porta eleifend lacinia. Donec at iaculis tellus."
LineBreakMode="WordWrap"
MaxLines="2" />

El código de C# equivalente es el siguiente:

var label =
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In facilisis nulla eu felis fringilla
vulputate. Nullam porta eleifend lacinia. Donec at iaculis tellus.", LineBreakMode = LineBreakMode.WordWrap,
MaxLines = 2
};

Las siguientes capturas de pantallas muestran el resultado de establecer la MaxLines propiedad en 2, cuando el
texto es lo suficientemente largo como para ocupar más de 2 líneas:
mostrar HTML
La Label clase tiene una TextType propiedad, que determina si la Label instancia debe mostrar texto sin
formato o texto HTML. Esta propiedad debe establecerse en uno de los miembros de la TextType enumeración:
Text indica que mostrará el Label texto sin formato y es el valor predeterminado de la Label.TextType
propiedad.
Html indica que mostrará el Label texto HTML.

Por lo tanto, Label las instancias de pueden mostrar HTML estableciendo la Label.TextType propiedad en Html
y la Label.Text propiedad en una cadena HTML:

Label label = new Label


{
Text = "This is <strong style=\"color:red\">HTML</strong> text.",
TextType = TextType.Html
};

En el ejemplo anterior, los caracteres de comillas dobles del código HTML deben usarse con el símbolo de escape
\ .

En XAML, las cadenas HTML se pueden volver ilegibles debido a la escape adicional de los < > símbolos y:

<Label Text="This is &lt;strong style=&quot;color:red&quot;&gt;HTML&lt;/strong&gt; text."


TextType="Html" />

Como alternativa, para mayor legibilidad, el código HTML se puede incluir en una CDATA sección:

<Label TextType="Html">
<![CDATA[
This is <strong style="color:red">HTML</strong> text.
]]>
</Label>

En este ejemplo, la Label.Text propiedad se establece en la cadena HTML que se inserta en la CDATA sección.
Esto funciona porque la Text propiedad es ContentProperty para la Label clase.
Las capturas de pantallas siguientes muestran un Label código HTML que muestra:

IMPORTANT
La visualización de HTML en un Label se limita a las etiquetas HTML admitidas por la plataforma subyacente.

Texto con formato


Las etiquetas exponen una FormattedText propiedad que permite la presentación de texto con varias fuentes y
colores en la misma vista.
La FormattedText propiedad es de tipo FormattedString , que consta de una o más Span instancias, establecida a
través de la Spans propiedad. Las siguientes Span propiedades se pueden usar para establecer la apariencia
visual:
BackgroundColor : color del fondo del intervalo.
CharacterSpacing , del tipo double , es el espaciado entre los caracteres del texto de Span .
Font : la fuente del texto del intervalo.
FontAttributes : los atributos de fuente para el texto del intervalo.
FontFamily : la familia de fuentes a la que pertenece la fuente del texto del intervalo.
FontSize : tamaño de la fuente del texto del intervalo.
ForegroundColor : color del texto del intervalo. Esta propiedad está obsoleta y se ha reemplazado por la
TextColor propiedad.
LineHeight : multiplicador que se va a aplicar al alto de línea predeterminado del intervalo. Para obtener más
información, consulte alto de línea.
Style : el estilo que se va a aplicar al intervalo.
Text : el texto del intervalo.
TextColor : color del texto del intervalo.
TextDecorations : las decoraciones que se aplican al texto en el intervalo. Para obtener más información, vea
decoraciones de texto.
Las BackgroundColor Textpropiedades, y Text enlazables tienen un modo de enlace predeterminado de
OneWay . Para obtener más información acerca de este modo de enlace, vea el modo de enlace predeterminado en
la guía del modo de enlace .
Además, la GestureRecognizers propiedad se puede utilizar para definir una colección de reconocedores de gestos
que responderán a los gestos en Span .

NOTE
No es posible mostrar HTML en Span .

En el siguiente ejemplo de XAML se muestra una FormattedText propiedad que consta de tres Span instancias:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextSample.LabelPage"
Title="Label Demo - XAML">
<StackLayout Padding="5,10">
...
<Label LineBreakMode="WordWrap">
<Label.FormattedText>
<FormattedString>
<Span Text="Red Bold, " TextColor="Red" FontAttributes="Bold" />
<Span Text="default, " Style="{DynamicResource BodyStyle}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" />
</Span.GestureRecognizers>
</Span>
<Span Text="italic small." FontAttributes="Italic" FontSize="Small" />
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</ContentPage>
El código de C# equivalente es el siguiente:

public class LabelPageCode : ContentPage


{
public LabelPageCode ()
{
var layout = new StackLayout{ Padding = new Thickness (5, 10) };
...
var formattedString = new FormattedString ();
formattedString.Spans.Add (new Span{ Text = "Red bold, ", ForegroundColor = Color.Red, FontAttributes
= FontAttributes.Bold });

var span = new Span { Text = "default, " };


span.GestureRecognizers.Add(new TapGestureRecognizer { Command = new Command(async () => await
DisplayAlert("Tapped", "This is a tapped Span.", "OK")) });
formattedString.Spans.Add(span);
formattedString.Spans.Add (new Span { Text = "italic small.", FontAttributes = FontAttributes.Italic,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)) });

layout.Children.Add (new Label { FormattedText = formattedString });


this.Content = layout;
}
}

IMPORTANT
La Text propiedad de Span se puede establecer mediante el enlace de datos. Para obtener más información, vea Enlace
de datos.

Tenga en cuenta que Span también puede responder a cualquier gesto que se agregue a la colección del intervalo
GestureRecognizers . Por ejemplo, se ha TapGestureRecognizer agregado a la segunda Span en los ejemplos de
código anteriores. Por lo tanto, cuando Span se puntea TapGestureRecognizer en, responderá ejecutando el
ICommand definido por la Command propiedad. Para obtener más información acerca de los reconocedores de
gestos, consulte :::no-loc(Xamarin.Forms)::: gestos.
Las siguientes capturas de pantallas muestran el resultado de establecer la FormattedString propiedad en tres
Span instancias:

Alto de línea
El alto vertical de un Label y un Span se puede personalizar estableciendo la Label.LineHeight propiedad o
Span.LineHeight en un double valor. En iOS y Android, estos valores son multiplicadores del alto de línea original
y, en el Plataforma universal de Windows (UWP), el Label.LineHeight valor de propiedad es un multiplicador del
tamaño de fuente de la etiqueta.
NOTE
En iOS, las Label.LineHeight Span.LineHeight propiedades y cambian el alto de línea del texto que cabe en una sola
línea y el texto que se ajusta en varias líneas.
En Android, las Label.LineHeight Span.LineHeight propiedades y solo cambian el alto de línea del texto que se
ajusta en varias líneas.
En UWP, la Label.LineHeight propiedad cambia el alto de línea del texto que se ajusta en varias líneas, y la
Span.LineHeight propiedad no tiene ningún efecto.

En el siguiente ejemplo de XAML se muestra cómo establecer la LineHeight propiedad en un Label :

<Label Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. In facilisis nulla eu felis fringilla
vulputate. Nullam porta eleifend lacinia. Donec at iaculis tellus."
LineBreakMode="WordWrap"
LineHeight="1.8" />

El código de C# equivalente es el siguiente:

var label =
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In facilisis nulla eu felis fringilla
vulputate. Nullam porta eleifend lacinia. Donec at iaculis tellus.", LineBreakMode = LineBreakMode.WordWrap,
LineHeight = 1.8
};

Las capturas de pantallas siguientes muestran el resultado de establecer la Label.LineHeight propiedad en 1,8:

En el siguiente ejemplo de XAML se muestra cómo establecer la LineHeight propiedad en un Span :

<Label LineBreakMode="WordWrap">
<Label.FormattedText>
<FormattedString>
<Span Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. In a tincidunt sem. Phasellus
mollis sit amet turpis in rutrum. Sed aliquam ac urna id scelerisque. "
LineHeight="1.8"/>
<Span Text="Nullam feugiat sodales elit, et maximus nibh vulputate id."
LineHeight="1.8" />
</FormattedString>
</Label.FormattedText>
</Label>

El código de C# equivalente es el siguiente:


var formattedString = new FormattedString();
formattedString.Spans.Add(new Span
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In a tincidunt sem. Phasellus mollis sit
amet turpis in rutrum. Sed aliquam ac urna id scelerisque. ",
LineHeight = 1.8
});
formattedString.Spans.Add(new Span
{
Text = "Nullam feugiat sodales elit, et maximus nibh vulputate id.",
LineHeight = 1.8
});
var label = new Label
{
FormattedText = formattedString,
LineBreakMode = LineBreakMode.WordWrap
};

Las capturas de pantallas siguientes muestran el resultado de establecer la Span.LineHeight propiedad en 1,8:

Relleno
El relleno representa el espacio que hay entre un elemento y sus elementos secundarios, y se utiliza para separar
el elemento de su propio contenido. El relleno se puede aplicar a Label las instancias estableciendo la
Label.Padding propiedad en un Thickness valor:

<Label Padding="10">
<Label.FormattedText>
<FormattedString>
<Span Text="Lorem ipsum" />
<Span Text="dolor sit amet." />
</FormattedString>
</Label.FormattedText>
</Label>

El código de C# equivalente es el siguiente:


FormattedString formattedString = new FormattedString();
formattedString.Spans.Add(new Span
{
Text = "Lorem ipsum"
});
formattedString.Spans.Add(new Span
{
Text = "dolor sit amet."
});
Label label = new Label
{
FormattedText = formattedString,
Padding = new Thickness(20)
};

IMPORTANT
En iOS, cuando Label se crea un que establece la Padding propiedad, se aplicará el relleno y el valor de relleno se podrá
actualizar más adelante. Sin embargo, cuando Label se crea una que no establece la Padding propiedad, intentar
establecerla más tarde no tendrá ningún efecto.
En Android y en el Plataforma universal de Windows, el valor de la Padding propiedad se puede especificar cuando Label
se crea o después.

Para obtener más información sobre el relleno, vea márgenes y relleno.

Hipervínculos
El texto que se muestra en Label Span las instancias de y se puede convertir en hipervínculos con el siguiente
enfoque:
1. Establezca las TextColor TextDecoration propiedades y de Label o Span .
2. Agregue un TapGestureRecognizer a la GestureRecognizers colección de Label o Span , cuya Command
propiedad se enlaza a un ICommand , y cuya CommandParameter propiedad contiene la dirección URL que se va a
abrir.
3. Defina el ICommand que va a ejecutar TapGestureRecognizer .
4. Escriba el código que va a ejecutar ICommand .

En el ejemplo de código siguiente, tomado del ejemplo de demostraciones de hipervínculo , se muestra un Label
cuyo contenido se establece a partir de varias Span instancias:

<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Alternatively, click " />
<Span Text="here"
TextColor="Blue"
TextDecorations="Underline">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}"
CommandParameter="https://docs.microsoft.com/xamarin/" />
</Span.GestureRecognizers>
</Span>
<Span Text=" to view Xamarin documentation." />
</FormattedString>
</Label.FormattedText>
</Label>
En este ejemplo, la primera y la tercera Span instancia comprenden el texto, mientras que la segunda Span
representa un hipervínculo tappable. Su color de texto se establece en azul y tiene una decoración de texto de
subrayado. Esto crea la apariencia de un hipervínculo, tal como se muestra en las siguientes capturas de pantallas:

Cuando se puntea el hipervínculo, TapGestureRecognizer responderá ejecutando el ICommand definido por su


Command propiedad. Además, la dirección URL especificada por la CommandParameter propiedad se pasará a
ICommand como un parámetro.

El código subyacente de la página XAML contiene la TapCommand implementación:

public partial class MainPage : ContentPage


{
// Launcher.OpenAsync is provided by :::no-loc(Xamarin.Essentials):::.
public ICommand TapCommand => new Command<string>(async (url) => await Launcher.OpenAsync(url));

public MainPage()
{
InitializeComponent();
BindingContext = this;
}
}

TapCommand Ejecuta el Launcher.OpenAsync método, pasando el valor de la TapGestureRecognizer.CommandParameter


propiedad como un parámetro. Launcher.OpenAsync Proporciona el método :::no-loc(Xamarin.Essentials)::: y abre la
dirección URL en un explorador Web. Por lo tanto, el efecto general es que cuando se puntea el hipervínculo en la
página, aparece un explorador Web y se navega a la dirección URL asociada al hipervínculo.
Crear una clase de hipervínculo reutilizable
El enfoque anterior para crear un hipervínculo requiere la escritura de código repetitivo cada vez que se requiere
un hipervínculo en la aplicación. Sin embargo, Label Span se pueden crear subclases para las clases y
HyperlinkLabel HyperlinkSpan , con el reconocedor de gestos y el código de formato de texto agregado allí.

En el ejemplo de código siguiente, tomado del ejemplo de demostraciones de hipervínculo , se muestra una
HyperlinkSpan clase:
public class HyperlinkSpan : Span
{
public static readonly BindableProperty UrlProperty =
BindableProperty.Create(nameof(Url), typeof(string), typeof(HyperlinkSpan), null);

public string Url


{
get { return (string)GetValue(UrlProperty); }
set { SetValue(UrlProperty, value); }
}

public HyperlinkSpan()
{
TextDecorations = TextDecorations.Underline;
TextColor = Color.Blue;
GestureRecognizers.Add(new TapGestureRecognizer
{
// Launcher.OpenAsync is provided by :::no-loc(Xamarin.Essentials):::.
Command = new Command(async () => await Launcher.OpenAsync(Url))
});
}
}

La HyperlinkSpan clase define una Url propiedad, y BindableProperty se asocia, y el constructor establece la
apariencia del hipervínculo y el objeto TapGestureRecognizer que responderá cuando se puntee en el hipervínculo.
Cuando HyperlinkSpan se puntea en, el TapGestureRecognizer responderá ejecutando el Launcher.OpenAsync
método para abrir la dirección URL, especificada por la Url propiedad, en un explorador Web.
La HyperlinkSpan clase se puede consumir agregando una instancia de la clase a XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:HyperlinkDemo"
x:Class="HyperlinkDemo.MainPage">
<StackLayout>
...
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Alternatively, click " />
<local:HyperlinkSpan Text="here"
Url="https://docs.microsoft.com/appcenter/" />
<Span Text=" to view AppCenter documentation." />
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</ContentPage>

Aplicar estilo a etiquetas


En las secciones anteriores se describe la configuración Label y Span las propiedades en cada instancia. Sin
embargo, los conjuntos de propiedades se pueden agrupar en un estilo que se aplica de forma coherente a una o
varias vistas. Esto puede aumentar la legibilidad del código y facilitar la implementación de los cambios de diseño.
Para obtener más información, vea estilos.

Vínculos relacionados
Texto (ejemplo)
Hipervínculos (ejemplo)
Creación de Mobile Apps con :::no-loc(Xamarin.Forms)::: , capítulo 3
API de las etiquetas
API de span
Mapa de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Inicialización y configuración
El Xamarin.Forms . El paquete NuGet de Maps es necesario para usar la funcionalidad de Maps en una aplicación.
Además, el acceso a la ubicación del usuario requiere que se hayan concedido permisos de ubicación a la
aplicación.

Control de mapa
El Map control es una vista multiplataforma para mostrar y anotar asignaciones. Usa el control de mapa nativo
para cada plataforma, lo que proporciona una experiencia de mapas rápida y familiar a los usuarios.

Posición y distancia
La Position estructura se usa normalmente al colocar una asignación y sus clavijas, y el Distance struct que se
puede usar opcionalmente al colocar un mapa.

Marcas
El Map control permite marcar ubicaciones con Pin objetos. Un Pin es un marcador de mapa que abre una
ventana de información cuando se puntea.

Polígonos, polilíneas y círculos


Polygon``Polyline Circlelos elementos, y permiten resaltar áreas específicas en un mapa. Un Polygon es una
forma totalmente adjunta que puede tener un trazo y un color de relleno. Una Polyline es una línea que no
rodea completamente un área. Un Circle resalta un área circular del mapa.

Codificación geográfica
La Geocoder clase convierte entre las direcciones de cadena y las coordenadas de latitud y longitud que se
almacenan en los Position objetos.

Inicio de la aplicación de mapa nativa


La aplicación de asignación nativa en cada plataforma se puede iniciar desde una Xamarin.Forms aplicación
mediante la Xamarin.Essentials Launcher clase.
:::no-loc(Xamarin.Forms)::: Inicialización y
configuración de asignaciones
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
El Map control utiliza el control de mapa nativo en cada plataforma. Esto proporciona una experiencia de mapas
rápida y familiar a los usuarios, pero significa que se necesitan algunos pasos de configuración para cumplir los
requisitos de la API de cada plataforma.

Inicialización de asignación
Map Proporciona el control :::no-loc(Xamarin.Forms)::: . Asigna el paquete NuGet, que debe agregarse a todos los
proyectos de la solución.
Después de instalar el :::no-loc(Xamarin.Forms)::: . Asigna el paquete NuGet, debe inicializarse en cada proyecto de
plataforma.
En iOS, esto debe ocurrir en AppDelegate.CS invocando el :::no-loc(Xamarin.Forms):::Maps.Init método
después del :::no-loc(Xamarin.Forms):::.Forms.Init método:

:::no-loc(Xamarin.Forms):::Maps.Init();

En Android, esto debe ocurrir en MainActivity.CS invocando el :::no-loc(Xamarin.Forms):::Maps.Init método


después del :::no-loc(Xamarin.Forms):::.Forms.Init método:

:::no-loc(Xamarin.Forms):::Maps.Init(this, savedInstanceState);

En el Plataforma universal de Windows (UWP), esto debe ocurrir en mainpage.Xaml.CS invocando el


:::no-loc(Xamarin.Forms):::Maps.Init método desde el MainPage constructor:

:::no-loc(Xamarin.Forms):::Maps.Init("INSERT_AUTHENTICATION_TOKEN_HERE");

Para obtener información sobre el token de autenticación necesario en UWP, consulte plataforma universal de
Windows.
Una vez que se ha agregado el paquete NuGet y se ha llamado al método de inicialización dentro de cada
aplicación, :::no-loc(Xamarin.Forms):::.Maps se pueden usar API en el proyecto de código compartido.

Configuración de plataforma
Se requiere configuración adicional en Android y en el Plataforma universal de Windows (UWP) antes de que se
muestre la asignación. Además, en iOS, Android y UWP, el acceso a la ubicación del usuario requiere que se hayan
concedido permisos de ubicación a la aplicación.
iOS
La visualización e interacción con un mapa en iOS no requiere ninguna configuración adicional. Sin embargo, para
tener acceso a los servicios de ubicación, debe establecer las siguientes claves en info. plist :
iOS 11 y versiones posteriores
NSLocationWhenInUseUsageDescription : para usar los servicios de ubicación cuando la aplicación está en
uso
NSLocationAlwaysAndWhenInUseUsageDescription : para usar servicios de ubicación en todo momento
iOS 10 y versiones anteriores
NSLocationWhenInUseUsageDescription : para usar los servicios de ubicación cuando la aplicación está en
uso
NSLocationAlwaysUsageDescription : para usar servicios de ubicación en todo momento
Para admitir iOS 11 y versiones anteriores, puede incluir las tres claves: NSLocationWhenInUseUsageDescription ,
NSLocationAlwaysAndWhenInUseUsageDescription y NSLocationAlwaysUsageDescription .

A continuación se muestra la representación XML para estas claves en info. plist . Debe actualizar los string
valores para reflejar el modo en que la aplicación usa la información de Ubicación:

<key>NSLocationAlwaysUsageDescription</key>
<string>Can we use your location at all times?</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Can we use your location when your application is being used?</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Can we use your location at all times?</string>

Las entradas info. plist también se pueden agregar en la vista código fuente mientras se edita el archivo info.
plist :

A continuación, se muestra un mensaje cuando la aplicación intenta obtener acceso a la ubicación del usuario,
solicitando acceso:

Android
El proceso de configuración para mostrar e interactuar con un mapa en Android es el siguiente:
1. Obtenga una clave de API de Google Maps y agréguela al manifiesto.
2. Especifique el número de versión de Google Play Services en el manifiesto.
3. Especifique el requisito de la biblioteca de Apache HTTP Legacy en el manifiesto.
4. opta Especifique el permiso WRITE_EXTERNAL_STORAGE en el manifiesto.
5. opta Especifique los permisos de ubicación en el manifiesto.
6. opta Permisos de ubicación en tiempo de ejecución de solicitud en la MainActivity clase.

Para obtener un ejemplo de un archivo de manifiesto configurado correctamente, vea AndroidManifest.xml de la


aplicación de ejemplo.
Obtención de una clave de API de Google Maps
Para usar la API de Google Maps en Android, debe generar una clave de API. Para ello, siga las instrucciones de
obtención de una clave de API de Google Maps.
Una vez que haya obtenido una clave de API, debe agregarse dentro del <application> elemento del archivo de
propiedades/AndroidManifest.xml :

<application ...>
<meta-data android:name="com.google.android.geo.API_KEY" android:value="PASTE-YOUR-API-KEY-HERE" />
</application>

Esto incrusta la clave de API en el manifiesto. Sin una clave de API válida Map , el control mostrará una cuadrícula
en blanco.

NOTE
com.google.android.geo.API_KEY es el nombre de metadatos recomendado para la clave de API. Por compatibilidad con
versiones anteriores, com.google.android.maps.v2.API_KEY se puede usar el nombre de los metadatos, pero solo permite
la autenticación en la API de mapas de Android v2.

Para que el APK acceda a Google Maps, debe incluir las huellas digitales y los nombres de paquete de SHA-1 para
cada almacén de claves (depuración y lanzamiento) que use para firmar el APK. Por ejemplo, si usa un equipo para
la depuración y otro para generar el APK de versión, debe incluir la huella digital del certificado SHA-1 desde el
almacén de claves de depuración del primer equipo y la huella digital del certificado SHA-1 del almacén de claves
de versión del segundo equipo. Recuerde también editar las credenciales clave si cambia el nombre del paquete
de la aplicación. Consulte obtención de una clave de API de Google Maps.
Especificar el número de versión de los servicios de Google Play
Agregue la siguiente declaración en el <application> elemento de AndroidManifest.xml :

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"


/>

Esto incrusta la versión de Google Play servicios con los que se compiló la aplicación, en el manifiesto.
Especificación del requisito de la biblioteca de Apache HTTP Legacy
Si su :::no-loc(Xamarin.Forms)::: aplicación tiene como destino la API 28 o una versión posterior, debe agregar la
siguiente declaración en el <application> elemento de AndroidManifest.xml :

<uses-library android:name="org.apache.http.legacy" android:required="false" />

Esto indica a la aplicación que use la biblioteca de cliente de Apache HTTP, que se ha quitado del bootclasspath en
Android 9.
Especificar el permiso WRITE_EXTERNAL_STORAGE
Si su aplicación tiene como destino la API 22 o una inferior, puede que sea necesario agregar el
WRITE_EXTERNAL_STORAGE permiso al manifiesto, como un elemento secundario del <manifest> elemento:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Esto no es necesario si la aplicación tiene como destino la API 23 o una versión posterior.
Especificar permisos de ubicación
Si la aplicación necesita tener acceso a la ubicación del usuario, debe solicitar el permiso agregando
ACCESS_COARSE_LOCATION los ACCESS_FINE_LOCATION permisos o al manifiesto (o ambos), como un elemento
secundario del <manifest> elemento:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"


android:versionName="1.0" package="com.companyname.myapp">
...
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

El ACCESS_COARSE_LOCATION permiso permite a la API usar datos de Wi-Fi o móviles, o ambos, para determinar la
ubicación del dispositivo. Los ACCESS_FINE_LOCATION permisos permiten a la API usar el sistema de posicionamiento
global (GPS), Wi-Fi o datos móviles para determinar una ubicación precisa como sea posible.
Como alternativa, estos permisos se pueden habilitar mediante el editor de manifiestos para agregar los permisos
siguientes:
AccessCoarseLocation
AccessFineLocation

Se muestran en la siguiente captura de pantalla:

Permisos de ubicación en tiempo de ejecución de solicitud


Si su aplicación tiene como destino la API 23 o una versión posterior y necesita acceder a la ubicación del usuario,
debe comprobar si tiene el permiso necesario en tiempo de ejecución y solicitarlo si no lo tiene. Esto se puede
lograr de la siguiente manera:
1. En la MainActivity clase, agregue los siguientes campos:

const int RequestLocationId = 0;

readonly string[] LocationPermissions =


{
Manifest.Permission.AccessCoarseLocation,
Manifest.Permission.AccessFineLocation
};

2. En la MainActivity clase, agregue la siguiente OnStart invalidación:


protected override void OnStart()
{
base.OnStart();

if ((int)Build.VERSION.SdkInt >= 23)


{
if (CheckSelfPermission(Manifest.Permission.AccessFineLocation) != Permission.Granted)
{
RequestPermissions(LocationPermissions, RequestLocationId);
}
else
{
// Permissions already granted - display a message.
}
}
}

Siempre que la aplicación esté destinada a la API 23 o una versión posterior, este código realiza una
comprobación del permiso en tiempo de ejecución para el AccessFineLocation permiso. Si no se ha
concedido el permiso, se realiza una solicitud de permiso mediante una llamada al RequestPermissions
método.
3. En la MainActivity clase, agregue la siguiente OnRequestPermissionsResult invalidación:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum]


Permission[] grantResults)
{
if (requestCode == RequestLocationId)
{
if ((grantResults.Length == 1) && (grantResults[0] == (int)Permission.Granted))
// Permissions granted - display a message.
else
// Permissions denied - display a message.
}
else
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

Esta invalidación controla el resultado de la solicitud de permiso.


El efecto general de este código es que cuando la aplicación solicita la ubicación del usuario, se muestra el
siguiente cuadro de diálogo que solicita permiso:
Plataforma universal de Windows
En UWP, la aplicación debe autenticarse para poder mostrar un mapa y consumir servicios de mapa. Para autenticar
la aplicación, debe especificar una clave de autenticación de maps. Para obtener más información, consulte solicitar
una clave de autenticación de Maps. Después, el token de autenticación debe especificarse en la
FormsMaps.Init("AUTHORIZATION_TOKEN") llamada al método para autenticar la aplicación con mapas de Bing.

NOTE
En UWP, para usar servicios de mapa como geocodificación, también debe establecer la MapService.ServiceToken
propiedad en el valor de la clave de autenticación. Esto puede realizarse con la siguiente línea de código:
Windows.Services.Maps.MapService.ServiceToken = "INSERT_AUTH_TOKEN_HERE"; .

Además, si la aplicación necesita tener acceso a la ubicación del usuario, debe habilitar la funcionalidad de
ubicación en el manifiesto del paquete. Esto se puede lograr de la siguiente manera:
1. En el Explorador de soluciones , haz doble clic en package.appxmanifest y selecciona la pestaña
Funcionalidades .
2. En la lista capacidades , active la casilla Ubicación . Esto agrega la location capacidad del dispositivo al
archivo de manifiesto del paquete.

<Capabilities>
<!-- DeviceCapability elements must follow Capability elements (if present) -->
<DeviceCapability Name="location"/>
</Capabilities>

Versiones de lanzamiento
Las compilaciones de versión de UWP usan la compilación nativa de .NET para compilar la aplicación directamente
en código nativo. Sin embargo, una consecuencia de esto es que el representador del Map control en UWP puede
estar vinculado fuera del archivo ejecutable. Esto se puede corregir mediante una sobrecarga específica de UWP
del Forms.Init método en app.Xaml.CS :

var assembliesToInclude = new [] { typeof(:::no-


loc(Xamarin.Forms):::.Maps.UWP.MapRenderer).GetTypeInfo().Assembly };
:::no-loc(Xamarin.Forms):::.Forms.Init(e, assembliesToInclude);

Este código pasa el ensamblado en el que :::no-loc(Xamarin.Forms):::.Maps.UWP.MapRenderer reside la clase, al


Forms.Init método. Esto garantiza que el ensamblado no esté vinculado fuera del ejecutable mediante el proceso
de compilación de .NET Native.

IMPORTANT
Si no lo hace, el Map control no aparecerá al ejecutar una compilación de versión.

Vínculos relacionados
Ejemplo de Maps
:::no-loc(Xamarin.Forms):::. Asigna PIN.
API de Maps
Representador personalizado de asignación
:::no-loc(Xamarin.Forms)::: Control de mapa
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
El Map control es una vista multiplataforma para mostrar y anotar asignaciones. Usa el control de asignación
nativa para cada plataforma, lo que proporciona una experiencia de mapas rápida y familiar a los usuarios:

La Map clase define las siguientes propiedades que controlan la apariencia y el comportamiento de los mapas:
IsShowingUser , de tipo bool , indica si la asignación muestra la ubicación actual del usuario.
ItemsSource , de tipo IEnumerable , que especifica la colección de IEnumerable elementos que se van a mostrar.
ItemTemplate , de tipo DataTemplate , que especifica la DataTemplate que se va a aplicar a cada elemento de la
colección de elementos mostrados.
ItemTemplateSelector , de tipo DataTemplateSelector , que especifica DataTemplateSelector que se usará para
elegir un DataTemplate objeto para un elemento en tiempo de ejecución.
HasScrollEnabled , de tipo bool , determina si se permite desplazar el mapa.
HasZoomEnabled , de tipo bool , determina si el mapa puede hacer zoom.
MapElements , de tipo IList<MapElement> , representa la lista de elementos del mapa, como polígonos y
polilíneas.
MapType , de tipo MapType , indica el estilo de presentación del mapa.
MoveToLastRegionOnLayoutChange , de tipo bool , controla si la región de mapa mostrada se moverá de su región
actual a su región establecida anteriormente cuando se produzca un cambio de diseño.
Pins , de tipo IList<Pin> , representa la lista de marcadores en el mapa.
TrafficEnabled , de tipo bool , indica si los datos de tráfico están superpuestos en el mapa.
VisibleRegion , de tipo MapSpan , devuelve la región de la asignación que se muestra actualmente.

Estas propiedades, con la excepción de las MapElements Pins propiedades, y VisibleRegion , están respaldadas
por BindableProperty objetos, lo que significa que pueden ser destinos de los enlaces de datos.
La Map clase también define un MapClicked evento que se desencadena cuando se puntea en la asignación. El
MapClickedEventArgs objeto que acompaña al evento tiene una propiedad única denominada Position , de tipo
Position . Cuando se desencadena el evento, la Position propiedad se establece en la ubicación de asignación
que se ha punteado. Para obtener más información sobre el Position struct, consulte asignación de posición y
distancia.
Para obtener información sobre ItemsSource las ItemTemplate propiedades, y ItemTemplateSelector , vea Mostrar
una colección de PIN.

Mostrar un mapa
Map Se puede mostrar si se agrega a un diseño o una página:

<ContentPage ...
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<maps:Map x:Name="map" />
</ContentPage>

NOTE
xmlns Se requiere una definición de espacio de nombres adicional para hacer referencia a :::no-loc(Xamarin.Forms)::: . Asigna
controles. En el ejemplo anterior, :::no-loc(Xamarin.Forms):::.Maps se hace referencia al espacio de nombres a través de
la maps palabra clave.

El código de C# equivalente es el siguiente:

using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.Maps;

namespace WorkingWithMaps
{
public class MapTypesPageCode : ContentPage
{
public MapTypesPageCode()
{
Map map = new Map();
Content = map;
}
}
}

En este ejemplo se llama al Map constructor predeterminado, que centra el mapa en Roma:
Como alternativa, MapSpan se puede pasar un argumento a un Map constructor para establecer el punto central y
el nivel de zoom del mapa cuando se carga. Para obtener más información, vea Mostrar una ubicación específica en
un mapa.

Tipos de asignación
La Map.MapType propiedad se puede establecer en un MapType miembro de enumeración para definir el estilo de
presentación del mapa. La enumeración MapType define los miembros siguientes:
Street Especifica que se mostrará un mapa de calles.
Satellite Especifica que se mostrará un mapa que contiene imágenes de satélite.
Hybrid Especifica que se mostrará un mapa que combina una calle y datos satélite.

De forma predeterminada, un Map mostrará un mapa de calle si la MapType propiedad no está definida. Como
alternativa, la MapType propiedad se puede establecer en uno de los MapType miembros de enumeración:

<maps:Map MapType="Satellite" />

El código de C# equivalente es el siguiente:

Map map = new Map


{
MapType = MapType.Satellite
};

Las capturas de pantallas siguientes muestran Map cuando la MapType propiedad está establecida en Street :
Las capturas de pantallas siguientes muestran Map cuando la MapType propiedad está establecida en Satellite :

Las capturas de pantallas siguientes muestran Map cuando la MapType propiedad está establecida en Hybrid :

Mostrar una ubicación específica en un mapa


Mostrar una ubicación específica en un mapa
La región de un mapa que se va a mostrar cuando se carga una asignación se puede establecer pasando un
MapSpan argumento al Map constructor:

<maps:Map>
<x:Arguments>
<maps:MapSpan>
<x:Arguments>
<maps:Position>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</maps:Position>
<x:Double>0.01</x:Double>
<x:Double>0.01</x:Double>
</x:Arguments>
</maps:MapSpan>
</x:Arguments>
</maps:Map>

El código de C# equivalente es el siguiente:

Position position = new Position(36.9628066, -122.0194722);


MapSpan mapSpan = new MapSpan(position, 0.01, 0.01);
Map map = new Map(mapSpan);

En este ejemplo se crea un Map objeto que muestra la región especificada por el MapSpan objeto. El MapSpan
objeto se centra en la latitud y la longitud representada por un Position objeto y abarca los grados 0,01 latitud y
0,01 de longitud. Para obtener más información sobre el Position struct, consulte asignación de posición y
distancia. Para obtener información sobre cómo pasar argumentos en XAML, vea pasar argumentos en XAML.
El resultado es que, cuando se muestra el mapa, se centra en una ubicación específica y abarca un número
específico de grados de latitud y longitud:

Creación de un objeto MapSpan


Existen varios enfoques para crear MapSpan objetos. Un enfoque común es proporcionar los argumentos
necesarios para el MapSpan constructor. Se trata de una latitud y una longitud representada por un Position
objeto, y double valores que representan los grados de latitud y longitud que abarca MapSpan . Para obtener más
información sobre el Position struct, consulte asignación de posición y distancia.
Como alternativa, hay tres métodos en la MapSpan clase que devuelven nuevos MapSpan objetos:
1. ClampLatitudeDevuelve un MapSpan con el mismo LongitudeDegrees que la instancia de la clase del método y
un radio definido por north sus south argumentos y.
2. FromCenterAndRadius Devuelve un MapSpan que se define por sus Position Distance argumentos y.
3. WithZoom Devuelve un MapSpan con el mismo centro que la instancia de la clase del método, pero con un radio
multiplicado por su double argumento.

Para obtener más información sobre el Distance struct, consulte asignación de posición y distancia.
Una vez que se ha MapSpan creado un, se puede tener acceso a las propiedades siguientes para recuperar datos
sobre ella:
Center , que representa el Position en el centro geográfico de MapSpan .
LatitudeDegrees , que representa los grados de latitud que abarca MapSpan .
LongitudeDegrees , que representa los grados de longitud que abarca MapSpan .
Radius , que representa el MapSpan radio.

Movimiento del mapa


Map.MoveToRegion Se puede llamar al método para cambiar la posición y el nivel de zoom de un mapa. Este método
acepta un MapSpan argumento que define la región del mapa que se va a mostrar y su nivel de zoom.
En el código siguiente se muestra un ejemplo de cómo mover la región mostrada en un mapa:

MapSpan mapSpan = MapSpan.FromCenterAndRadius(position, Distance.FromKilometers(0.444));


map.MoveToRegion(mapSpan);

Acercamiento/alejamiento del mapa


El nivel de zoom de un Map se puede cambiar sin alterar su ubicación. Esto puede realizarse mediante la interfaz
de usuario de asignación o mediante programación llamando al MoveToRegion método con un MapSpan
argumento que utiliza la ubicación actual como Position argumento:

double zoomLevel = 0.5;


double latlongDegrees = 360 / (Math.Pow(2, zoomLevel));
if (map.VisibleRegion != null)
{
map.MoveToRegion(new MapSpan(map.VisibleRegion.Center, latlongDegrees, latlongDegrees));
}

En este ejemplo, MoveToRegion se llama al método con un MapSpan argumento que especifica la ubicación actual
del mapa, a través de Map.VisibleRegion la propiedad y el nivel de zoom como grados de latitud y longitud. El
resultado general es que se cambia el nivel de zoom del mapa, pero su ubicación no lo es. Un enfoque alternativo
para implementar zoom en un mapa es usar el MapSpan.WithZoom método para controlar el factor de zoom.

IMPORTANT
El zoom de un mapa, ya sea a través de la interfaz de usuario del mapa o mediante programación, requiere que la
Map.HasZoomEnabled propiedad sea true . Para obtener más información sobre esta propiedad, vea deshabilitar zoom.
Personalizar el comportamiento del mapa
El comportamiento de Map se puede personalizar estableciendo algunas de sus propiedades y controlando el
MapClicked evento.

NOTE
Se puede lograr una personalización del comportamiento de mapa adicional mediante la creación de un representador
personalizado de mapa. Para obtener más información, consulte Personalización de un :::no-loc(Xamarin.Forms)::: mapa.

Mostrar datos del tráfico


La Map clase define una TrafficEnabled propiedad de tipo bool . De forma predeterminada, esta propiedad es
false , que indica que los datos de tráfico no se superpondrán en el mapa. Cuando esta propiedad está
establecida en true , los datos de tráfico se superpondrán en el mapa. En el ejemplo siguiente se muestra cómo
establecer esta propiedad:

<maps:Map TrafficEnabled="true" />

El código de C# equivalente es el siguiente:

Map map = new Map


{
TrafficEnabled = true
};

Deshabilitar desplazamiento
La Map clase define una HasScrollEnabled propiedad de tipo bool . De forma predeterminada, esta propiedad es
true , que indica que la asignación puede desplazarse. Cuando esta propiedad se establece en false , el mapa no
se desplazará. En el ejemplo siguiente se muestra cómo establecer esta propiedad:

<maps:Map HasScrollEnabled="false" />

El código de C# equivalente es el siguiente:

Map map = new Map


{
HasScrollEnabled = false
};

Deshabilitar zoom
La Map clase define una HasZoomEnabled propiedad de tipo bool . De forma predeterminada, esta propiedad es
true , que indica que se puede realizar un zoom en el mapa. Cuando esta propiedad se establece en false , no se
puede hacer zoom en el mapa. En el ejemplo siguiente se muestra cómo establecer esta propiedad:

<maps:Map HasZoomEnabled="false" />

El código de C# equivalente es el siguiente:


Map map = new Map
{
HasZoomEnabled = false
};

Mostrar la ubicación del usuario


La Map clase define una IsShowingUser propiedad de tipo bool . De forma predeterminada, esta propiedad es
false , que indica que la asignación no muestra la ubicación actual del usuario. Cuando esta propiedad se
establece en true , el mapa muestra la ubicación actual del usuario. En el ejemplo siguiente se muestra cómo
establecer esta propiedad:

<maps:Map IsShowingUser="true" />

El código de C# equivalente es el siguiente:

Map map = new Map


{
IsShowingUser = true
};

IMPORTANT
En iOS, Android y el Plataforma universal de Windows, el acceso a la ubicación del usuario requiere la concesión de permisos
de ubicación a la aplicación. Para obtener más información, consulte Configuración de la plataforma.

Mantener la región de mapa en el cambio de diseño


La Map clase define una MoveToLastRegionOnLayoutChange propiedad de tipo bool . De forma predeterminada, esta
propiedad es true , que indica que la región de mapa mostrada pasará de su región actual a su región establecida
anteriormente cuando se produzca un cambio de diseño, como en la rotación de dispositivos. Cuando esta
propiedad está establecida en false , la región de mapa que se muestra permanecerá centrada cuando se
produzca un cambio de diseño. En el ejemplo siguiente se muestra cómo establecer esta propiedad:

<maps:Map MoveToLastRegionOnLayoutChange="false" />

El código de C# equivalente es el siguiente:

Map map = new Map


{
MoveToLastRegionOnLayoutChange = false
};

Clics de mapa
La Map clase define un MapClicked evento que se desencadena cuando se puntea la asignación. El
MapClickedEventArgs objeto que acompaña al evento tiene una propiedad única denominada Position , de tipo
Position . Cuando se desencadena el evento, la Position propiedad se establece en la ubicación de asignación
que se ha punteado. Para obtener más información sobre el Position struct, consulte asignación de posición y
distancia.
En el ejemplo de código siguiente se muestra un controlador de eventos para el MapClicked evento:
void OnMapClicked(object sender, MapClickedEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"MapClick: {e.Position.Latitude}, {e.Position.Longitude}");
}

En este ejemplo, el OnMapClicked controlador de eventos genera la latitud y la longitud que representa la ubicación
de asignación punteada. El controlador de eventos se puede registrar con el MapClicked evento de la siguiente
manera:

<maps:Map MapClicked="OnMapClicked" />

El código de C# equivalente es el siguiente:

Map map = new Map();


map.MapClicked += OnMapClicked;

Vínculos relacionados
Ejemplo de Maps
Posición y distancia del mapa
Personalización de un :::no-loc(Xamarin.Forms)::: mapa
Paso de argumentos en XAML
:::no-loc(Xamarin.Forms)::: Posición y distancia del
mapa
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms):::.Maps espacio de nombres contiene un Position struct que se usa normalmente
al colocar una asignación y sus clavijas, y un Distance struct que se puede usar opcionalmente al colocar un
mapa.

Posición
La Position estructura encapsula una posición almacenada como valores de latitud y longitud. Este struct
define dos propiedades de solo lectura:
Latitude , de tipo double , que representa la latitud de la posición en grados decimales.
Longitude , de tipo double , que representa la longitud de la posición en grados decimales.

Position los objetos se crean con el Position constructor, que requiere que los argumentos de latitud y
longitud se especifiquen como double valores:

Position position = new Position(36.9628066, -122.0194722);

Al crear un Position objeto, el valor de latitud se unirá entre-90,0 y 90,0, y el valor de longitud se fijará entre-
180,0 y 180,0.

NOTE
La GeographyUtils clase tiene un ToRadians método de extensión que convierte un double valor de grados a
radianes y un ToDegrees método de extensión que convierte un double valor de radianes en grados.

Distancia
El Distance struct encapsula una distancia almacenada como un double valor, que representa la distancia en
metros. Este struct define tres propiedades de solo lectura:
Kilometers , de tipo double , que representa la distancia en kilómetros que abarca Distance .
Meters , de tipo double , que representa la distancia en metros que abarca Distance .
Miles , de tipo double , que representa la distancia en millas que abarca Distance .

Distance los objetos se pueden crear con el Distance constructor, que requiere un argumento de metros
especificado como double :

Distance distance = new Distance(1450.5);

Como alternativa, Distance los objetos se pueden crear con FromKilometers los FromMeters métodos de
generador,, FromMiles y BetweenPositions :
Distance distance1 = Distance.FromKilometers(1.45); // argument represents the number of kilometers
Distance distance2 = Distance.FromMeters(1450.5); // argument represents the number of meters
Distance distance3 = Distance.FromMiles(0.969); // argument represents the number of miles
Distance distance4 = Distance.BetweenPositions(position1, position2);

Vínculos relacionados
Ejemplo de Maps
:::no-loc(Xamarin.Forms)::: Anclajes de mapa
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms)::: Map control permite marcar ubicaciones con Pin objetos. Un Pin es un marcador
de mapa que abre una ventana de información al puntear:

Cuando Pin se agrega un objeto a la Map.Pins colección, el PIN se representa en el mapa.


La Pin clase tiene las siguientes propiedades:
Address , de tipo string , que normalmente representa la dirección de la ubicación del PIN. Sin embargo,
puede ser cualquier string contenido, no solo una dirección.
Label , de tipo string , que normalmente representa el título del PIN.
Position , de tipo Position , que representa la latitud y la longitud del PIN.
Type , de tipo PinType , que representa el tipo de PIN.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que Pin puede ser el destino
de los enlaces de datos. Para obtener más información sobre los objetos de enlace de datos Pin , vea Mostrar una
colección de PIN.
Además, la Pin clase define los MarkerClicked InfoWindowClicked eventos y. El MarkerClicked evento se
desencadena cuando se puntea un PIN y el InfoWindowClicked evento se desencadena cuando se puntea en la
ventana de información. El PinClickedEventArgs objeto que acompaña a ambos eventos tiene una HideInfoWindow
propiedad única, de tipo bool .

Mostrar un PIN
Pin Se puede Agregar un a Map en XAML:
<ContentPage ...
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<maps:Map x:Name="map"
IsShowingUser="True"
MoveToLastRegionOnLayoutChange="False">
<x:Arguments>
<maps:MapSpan>
<x:Arguments>
<maps:Position>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</maps:Position>
<x:Double>0.01</x:Double>
<x:Double>0.01</x:Double>
</x:Arguments>
</maps:MapSpan>
</x:Arguments>
<maps:Map.Pins>
<maps:Pin Label="Santa Cruz"
Address="The city with a boardwalk"
Type="Place">
<maps:Pin.Position>
<maps:Position>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</maps:Position>
</maps:Pin.Position>
</maps:Pin>
</maps:Map.Pins>
</maps:Map>
</ContentPage>

Este código XAML crea un Map objeto que muestra la región especificada por el MapSpan objeto. El MapSpan
objeto se centra en la latitud y la longitud representada por un Position objeto, que extiende los grados 0,01 de
latitud y longitud. Un Pin objeto se agrega a la Map.Pins colección y se dibuja en en Map la ubicación
especificada por su Position propiedad. Para obtener más información sobre el Position struct, consulte
asignación de posición y distancia. Para obtener información sobre cómo pasar argumentos en XAML a objetos
que carecen de constructores predeterminados, vea pasar argumentos en XAML.
El código de C# equivalente es el siguiente:

using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Map map = new Map
{
// ...
};
Pin pin = new Pin
{
Label = "Santa Cruz",
Address = "The city with a boardwalk",
Type = PinType.Place,
Position = new Position(36.9628066, -122.0194722)
};
map.Pins.Add(pin);
WARNING
Si no se establece la Pin.Label propiedad, se ArgumentException produce una excepción cuando Pin se agrega a
Map .

Este código de ejemplo da como resultado un solo PIN que se representa en un mapa:

Interacción con un PIN


De forma predeterminada, cuando se puntea en, Pin se muestra la ventana de información:

Al puntear en otra parte del mapa, se cierra la ventana de información.


La Pin clase define un MarkerClicked evento, que se desencadena cuando Pin se puntea en. No es necesario
controlar este evento para mostrar la ventana de información. En su lugar, este evento debe administrarse cuando
hay un requisito para recibir una notificación de que se ha punteado con un PIN específico.
La Pin clase también define un InfoWindowClicked evento que se desencadena cuando se puntea una ventana de
información. Este evento debe administrarse cuando hay un requisito para que se le notifique que se ha punteado
una ventana de información específica.
En el código siguiente se muestra un ejemplo de cómo controlar estos eventos:
using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Pin boardwalkPin = new Pin
{
Position = new Position(36.9641949, -122.0177232),
Label = "Boardwalk",
Address = "Santa Cruz",
Type = PinType.Place
};
boardwalkPin.MarkerClicked += async (s, args) =>
{
args.HideInfoWindow = true;
string pinName = ((Pin)s).Label;
await DisplayAlert("Pin Clicked", $"{pinName} was clicked.", "Ok");
};

Pin wharfPin = new Pin


{
Position = new Position(36.9571571, -122.0173544),
Label = "Wharf",
Address = "Santa Cruz",
Type = PinType.Place
};
wharfPin.InfoWindowClicked += async (s, args) =>
{
string pinName = ((Pin)s).Label;
await DisplayAlert("Info Window Clicked", $"The info window was clicked for {pinName}.", "Ok");
};

El objeto que acompaña a ambos eventos tiene una HideInfoWindow propiedad única, de tipo
PinClickedEventArgs
bool . Cuando esta propiedad se establece en true dentro de un controlador de eventos, se oculta la ventana de
información.

Tipos de PIN
Pin los objetos incluyen una Type propiedad, de tipo PinType , que representa el tipo de PIN. La enumeración
PinType define los miembros siguientes:

Generic , representa un PIN genérico.


Place , representa un PIN para un lugar.
SavedPin , representa un PIN para una ubicación guardada.
SearchResult , representa un PIN para un resultado de búsqueda.

Sin embargo, si Pin.Type se establece la propiedad en cualquier PinType miembro, no se cambia la apariencia
del código PIN representado. En su lugar, debe crear un representador personalizado para personalizar la
apariencia del PIN. Para obtener más información, consulte Personalización de un PIN de mapa.

Presentación de una colección de pin


La Map clase define las siguientes propiedades:
ItemsSource, de tipo IEnumerable , que especifica la colección de IEnumerable elementos que se van a mostrar.
ItemTemplate , de tipo DataTemplate , que especifica la DataTemplate que se va a aplicar a cada elemento de la
colección de elementos mostrados.
ItemTemplateSelector , de tipo DataTemplateSelector , que especifica DataTemplateSelector que se usará para
elegir un DataTemplate objeto para un elemento en tiempo de ejecución.
IMPORTANT
La ItemTemplate propiedad tiene prioridad cuando ItemTemplate ItemTemplateSelector se establecen las
propiedades y.

Un se puede rellenar con PIN mediante el enlace de datos para enlazar su


Map ItemsSource propiedad a una
IEnumerable colección:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps"
x:Class="WorkingWithMaps.PinItemsSourcePage">
<Grid>
...
<maps:Map x:Name="map"
ItemsSource="{Binding Locations}">
<maps:Map.ItemTemplate>
<DataTemplate>
<maps:Pin Position="{Binding Position}"
Address="{Binding Address}"
Label="{Binding Description}" />
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
...
</Grid>
</ContentPage>

Los ItemsSource datos de la propiedad se enlazan a la Locations propiedad del ViewModel conectado, que
devuelve una ObservableCollection de Location objetos, que es un tipo personalizado. Cada Location objeto
define Address Description las propiedades y, de tipo string y una Position propiedad, de tipo Position .
La apariencia de cada elemento de la IEnumerable colección se define estableciendo la ItemTemplate propiedad
en un DataTemplate que contiene un Pin objeto que los datos enlazan a las propiedades adecuadas.
Las capturas de pantallas siguientes muestran Map Cómo mostrar una Pin colección mediante el enlace de
datos:

Elección de la apariencia del elemento en tiempo de ejecución


La apariencia de cada elemento de la IEnumerable colección se puede elegir en tiempo de ejecución, en función
del valor del elemento, estableciendo la ItemTemplateSelector propiedad en DataTemplateSelector :

<ContentPage ...
xmlns:local="clr-namespace:WorkingWithMaps"
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<ContentPage.Resources>
<local:MapItemTemplateSelector x:Key="MapItemTemplateSelector">
<local:MapItemTemplateSelector.DefaultTemplate>
<DataTemplate>
<maps:Pin Position="{Binding Position}"
Address="{Binding Address}"
Label="{Binding Description}" />
</DataTemplate>
</local:MapItemTemplateSelector.DefaultTemplate>
<local:MapItemTemplateSelector.XamarinTemplate>
<DataTemplate>
<!-- Change the property values, or the properties that are bound to. -->
<maps:Pin Position="{Binding Position}"
Address="{Binding Address}"
Label="Xamarin!" />
</DataTemplate>
</local:MapItemTemplateSelector.XamarinTemplate>
</local:MapItemTemplateSelector>
</ContentPage.Resources>

<Grid>
...
<maps:Map x:Name="map"
ItemsSource="{Binding Locations}"
ItemTemplateSelector="{StaticResource MapItemTemplateSelector}" />
...
</Grid>
</ContentPage>

En el ejemplo siguiente se muestra la MapItemTemplateSelector clase:

public class MapItemTemplateSelector : DataTemplateSelector


{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate XamarinTemplate { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
return ((Location)item).Address.Contains("San Francisco") ? XamarinTemplate : DefaultTemplate;
}
}

La MapItemTemplateSelector clase define DefaultTemplate XamarinTemplate DataTemplate las propiedades y que


se establecen en distintas plantillas de datos. El OnSelectTemplate método devuelve XamarinTemplate , que
muestra "Xamarin" como etiqueta cuando Pin se puntea en, cuando el elemento tiene una dirección que contiene
"San Francisco". Cuando el elemento no tiene una dirección que contiene "San Francisco", el OnSelectTemplate
método devuelve DefaultTemplate .

NOTE
Un caso de uso de esta funcionalidad consiste en enlazar las propiedades de Pin los objetos de subclase a propiedades
diferentes, en función del Pin subtipo.
Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-loc(Xamarin.Forms):::
DataTemplateSelector.

Vínculos relacionados
Ejemplo de Maps
Representador personalizado de asignación
Paso de argumentos en XAML
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
:::no-loc(Xamarin.Forms)::: Rectángulos de mapa y
polilíneas
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Polygon``Polyline los elementos, y permiten resaltar áreas específicas en un mapa. Un Polygon es una
Circle
forma totalmente adjunta que puede tener un trazo y un color de relleno. Una Polyline es una línea que no rodea
completamente un área. Un Circle resalta un área circular del mapa:

Las Polygon Polyline clases, y se Circle derivan de la MapElement clase, que expone las siguientes propiedades
enlazables:
StrokeColor es un Color objeto que determina el color de la línea.
StrokeWidth es un float objeto que determina el ancho de línea.
La Polygon clase define una propiedad enlazable adicional:
FillColor es un Color objeto que determina el color de fondo del polígono.
Además, las Polygon clases y Polyline definen una GeoPath propiedad, que es una lista de Position objetos que
especifican los puntos de la forma.
La Circle clase define las siguientes propiedades enlazables:
Center es un Position objeto que define el centro del círculo, en latitud y longitud.
Radius es un Distance objeto que define el radio del círculo en metros, kilómetros o millas.
FillColor es una Color propiedad que determina el color del perímetro del círculo.

NOTE
Si StrokeColor no se especifica la propiedad, el trazo se establecerá de forma predeterminada en negro. Si FillColor no
se especifica la propiedad, el relleno se establecerá de forma predeterminada en transparente. Por lo tanto, si no se especifica
ninguna propiedad, la forma tendrá un contorno negro sin relleno.

Crear un polígono
Polygon Se puede Agregar un objeto a un mapa creando una instancia de él y agregándolo a la colección del mapa
MapElements . Esto se puede lograr en XAML de la siguiente manera:

<ContentPage ...
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<maps:Map>
<maps:Map.MapElements>
<maps:Polygon StrokeColor="#FF9900"
StrokeWidth="8"
FillColor="#88FF9900">
<maps:Polygon.Geopath>
<maps:Position>
<x:Arguments>
<x:Double>47.6368678</x:Double>
<x:Double>-122.137305</x:Double>
</x:Arguments>
</maps:Position>
...
</maps:Polygon.Geopath>
</maps:Polygon>
</maps:Map.MapElements>
</maps:Map>
</ContentPage>

El código de C# equivalente es el siguiente:


using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Map map = new Map
{
// ...
};

// instantiate a polygon
Polygon polygon = new Polygon
{
StrokeWidth = 8,
StrokeColor = Color.FromHex("#1BA1E2"),
FillColor = Color.FromHex("#881BA1E2"),
Geopath =
{
new Position(47.6368678, -122.137305),
new Position(47.6368894, -122.134655),
new Position(47.6359424, -122.134655),
new Position(47.6359496, -122.1325521),
new Position(47.6424124, -122.1325199),
new Position(47.642463, -122.1338932),
new Position(47.6406414, -122.1344833),
new Position(47.6384943, -122.1361248),
new Position(47.6372943, -122.1376912)
}
};

// add the polygon to the map's MapElements collection


map.MapElements.Add(polygon);

StrokeColor StrokeWidth Se especifican las propiedades y para personalizar el contorno del polígono. El
FillColor valor de propiedad coincide con el valor de la StrokeColor propiedad, pero tiene un valor alfa
especificado para que sea transparente, lo que permite que el mapa subyacente sea visible a través de la forma. La
GeoPath propiedad contiene una lista de Position objetos que definen las coordenadas geográficas de los puntos
del polígono. Un Polygon objeto se representa en el mapa una vez que se ha agregado a la MapElements colección
de Map .

NOTE
Un Polygon es una forma totalmente adjunta. Los puntos primero y último se conectarán automáticamente si no coinciden.

Crear una polilínea


PolylineSe puede Agregar un objeto a un mapa creando una instancia de él y agregándolo a la colección del
mapa MapElements . Esto se puede lograr en XAML de la siguiente manera:
<ContentPage ...
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<maps:Map>
<maps:Map.MapElements>
<maps:Polyline StrokeColor="Blue"
StrokeWidth="12">
<maps:Polyline.Geopath>
<maps:Position>
<x:Arguments>
<x:Double>47.6381401</x:Double>
<x:Double>-122.1317367</x:Double>
</x:Arguments>
</maps:Position>
...
</maps:Polyline.Geopath>
</maps:Polyline>
</maps:Map.MapElements>
</maps:Map>
</ContentPage>

using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Map map = new Map
{
// ...
};
// instantiate a polyline
Polyline polyline = new Polyline
{
StrokeColor = Color.Blue,
StrokeWidth = 12,
Geopath =
{
new Position(47.6381401, -122.1317367),
new Position(47.6381473, -122.1350841),
new Position(47.6382847, -122.1353094),
new Position(47.6384582, -122.1354703),
new Position(47.6401136, -122.1360819),
new Position(47.6403883, -122.1364681),
new Position(47.6407426, -122.1377019),
new Position(47.6412558, -122.1404056),
new Position(47.6414148, -122.1418647),
new Position(47.6414654, -122.1432702)
}
};

// add the polyline to the map's MapElements collection


map.MapElements.Add(polyline);

StrokeColor StrokeWidth Se especifican las propiedades y para personalizar la línea. La GeoPath propiedad
contiene una lista de Position objetos que definen las coordenadas geográficas de los puntos de polilínea. Un
Polyline objeto se representa en el mapa una vez que se ha agregado a la MapElements colección de Map .

Crear un círculo
Circle Se puede Agregar un objeto a un mapa creando una instancia de él y agregándolo a la colección del mapa
MapElements . Esto se puede lograr en XAML de la siguiente manera:
<ContentPage ...
xmlns:maps="clr-namespace::::no-loc(Xamarin.Forms):::.Maps;assembly=:::no-
loc(Xamarin.Forms):::.Maps">
<maps:Map>
<maps:Map.MapElements>
<maps:Circle StrokeColor="#88FF0000"
StrokeWidth="8"
FillColor="#88FFC0CB">
<maps:Circle.Center>
<maps:Position>
<x:Arguments>
<x:Double>37.79752</x:Double>
<x:Double>-122.40183</x:Double>
</x:Arguments>
</maps:Position>
</maps:Circle.Center>
<maps:Circle.Radius>
<maps:Distance>
<x:Arguments>
<x:Double>250</x:Double>
</x:Arguments>
</maps:Distance>
</maps:Circle.Radius>
</maps:Circle>
</maps:Map.MapElements>
...
</maps:Map>
</ContentPage>

El código de C# equivalente es el siguiente:

using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Map map = new Map();

// Instantiate a Circle
Circle circle = new Circle
{
Center = new Position(37.79752, -122.40183),
Radius = new Distance(250),
StrokeColor = Color.FromHex("#88FF0000"),
StrokeWidth = 8,
FillColor = Color.FromHex("#88FFC0CB")
};

// Add the Circle to the map's MapElements collection


map.MapElements.Add(circle);

La ubicación de Circle en el mapa viene determinada por el valor de las Center propiedades y Radius . La
Center propiedad define el centro del círculo, en latitud y longitud, mientras que la Radius propiedad define el
radio del círculo en metros. StrokeColor StrokeWidth Se especifican las propiedades y para personalizar el
contorno del círculo. El FillColor valor de propiedad especifica el color del perímetro del círculo. Ambos valores
de color especifican un canal alfa, lo que permite que el mapa subyacente sea visible a través del círculo. El Circle
objeto se representa en el mapa una vez que se ha agregado a la MapElements colección de Map .

NOTE
La GeographyUtils clase tiene un ToCircumferencePositions método de extensión que convierte un Circle objeto
(que define Center Radius los valores de las propiedades y) en una lista de Position objetos que constituyen las
coordenadas de latitud y longitud del perímetro del círculo.
Vínculos relacionados
Ejemplo de Maps
:::no-loc(Xamarin.Forms)::: Geocodificación de mapas
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms):::.Maps espacio de nombres proporciona una Geocoder clase, que convierte entre las
direcciones de cadena y las coordenadas de latitud y longitud que se almacenan en Position objetos. Para obtener
más información sobre el Position struct, vea asignación de posición y distancia.

NOTE
Se disponibles una API de geocodificación alternativa en :::no-loc(Xamarin.Essentials)::: . La :::no-loc(Xamarin.Essentials):::
Geocoding API ofrece datos de dirección estructurados al geocodificar direcciones, en contraposición a las cadenas
devueltas por esta API. Para obtener más información, vea :::no-loc(Xamarin.Essentials)::: : geocodificación.

Codificación de una dirección en geocode


Una dirección postal se puede codificar en coordenadas de latitud y longitud mediante la creación de una
Geocoder instancia de y la llamada al GetPositionsForAddressAsync método en la Geocoder instancia de:

using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Geocoder geoCoder = new Geocoder();

IEnumerable<Position> approximateLocations = await geoCoder.GetPositionsForAddressAsync("Pacific Ave, San


Francisco, California");
Position position = approximateLocations.FirstOrDefault();
string coordinates = $"{position.Latitude}, {position.Longitude}";

El GetPositionsForAddressAsync método toma un string argumento que representa la dirección y devuelve de


forma asincrónica una colección de Position objetos que podrían representar la dirección.

Inverso de una dirección con geocode


Las coordenadas de latitud y longitud pueden estar invertidas geocodificadas en una dirección postal mediante la
creación de una Geocoder instancia de y la llamada al GetAddressesForPositionAsync método en la Geocoder
instancia de:

using :::no-loc(Xamarin.Forms):::.Maps;
// ...
Geocoder geoCoder = new Geocoder();

Position position = new Position(37.8044866, -122.4324132);


IEnumerable<string> possibleAddresses = await geoCoder.GetAddressesForPositionAsync(position);
string address = possibleAddresses.FirstOrDefault();

El GetAddressesForPositionAsync método toma un Position argumento compuesto de coordenadas de latitud y


longitud, y devuelve de forma asincrónica una colección de cadenas que representan las direcciones cercanas a la
posición.
Vínculos relacionados
Ejemplo de Maps
:::no-loc(Xamarin.Forms)::: Posición y distancia del mapa
API del codificador
Inicie la aplicación de asignación nativa desde :::no-
loc(Xamarin.Forms):::
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
La aplicación de asignación nativa en cada plataforma se puede iniciar desde una :::no-loc(Xamarin.Forms):::
aplicación mediante la :::no-loc(Xamarin.Essentials)::: Launcher clase. Esta clase permite a una aplicación abrir otra
aplicación a través de su esquema de URI personalizado. La funcionalidad del iniciador se puede invocar con el
OpenAsync método, pasando string un Uri argumento o que representa el esquema de la dirección URL
personalizada que se va a abrir. Para obtener más información acerca de :::no-loc(Xamarin.Essentials)::: , vea :::no-
loc(Xamarin.Essentials)::: .

NOTE
Una alternativa al uso de la :::no-loc(Xamarin.Essentials)::: Launcher clase es usar su Map clase. Para obtener más
información, vea :::no-loc(Xamarin.Essentials)::: : Map.

La aplicación Maps en cada plataforma usa un esquema URI personalizado único. Para obtener información sobre
el esquema de URI de Maps en iOS, consulte asignación de vínculos en Developer.Apple.com. Para obtener
información sobre el esquema de URI de Maps en Android, consulte la Guía para desarrolladores de Maps y
Google Maps intents for Android en developers.Android.com. Para obtener información sobre el esquema de URI
de Maps en el Plataforma universal de Windows (UWP), consulte Inicio de la aplicación Windows Maps.

Iniciar la aplicación de asignación en una ubicación específica


Se puede abrir una ubicación en la aplicación de mapas nativos agregando los parámetros de consulta adecuados
al esquema de URI personalizado para cada aplicación de mapa:

if (Device.RuntimePlatform == Device.iOS)
{
//
https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
await Launcher.OpenAsync("http://maps.apple.com/?q=394+Pacific+Ave+San+Francisco+CA");
}
else if (Device.RuntimePlatform == Device.Android)
{
// open the maps app directly
await Launcher.OpenAsync("geo:0,0?q=394+Pacific+Ave+San+Francisco+CA");
}
else if (Device.RuntimePlatform == Device.UWP)
{
await Launcher.OpenAsync("bingmaps:?where=394 Pacific Ave San Francisco CA");
}

Este código de ejemplo hace que la aplicación de asignación nativa se inicie en cada plataforma, con el mapa
centrado en un PIN que representa la ubicación especificada:
Inicio de la aplicación de asignación con directions
La aplicación de mapas nativos se puede iniciar mostrando instrucciones mediante la adición de los parámetros de
consulta adecuados al esquema de URI personalizado para cada aplicación de mapa:

if (Device.RuntimePlatform == Device.iOS)
{
//
https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
await Launcher.OpenAsync("http://maps.apple.com/?daddr=San+Francisco,+CA&saddr=cupertino");
}
else if (Device.RuntimePlatform == Device.Android)
{
// opens the 'task chooser' so the user can pick Maps, Chrome or other mapping app
await Launcher.OpenAsync("http://maps.google.com/?daddr=San+Francisco,+CA&saddr=Mountain+View");
}
else if (Device.RuntimePlatform == Device.UWP)
{
await Launcher.OpenAsync("bingmaps:?rtp=adr.394 Pacific Ave San Francisco CA~adr.One Microsoft Way Redmond
WA 98052");
}

Este código de ejemplo hace que la aplicación de asignación nativa se inicie en cada plataforma, con el mapa
centrado en una ruta entre las ubicaciones especificadas:
Vínculos relacionados
Ejemplo de Maps
:::no-loc(Xamarin.Essentials):::
Vínculos de mapa
Guía para desarrolladores de Maps
Google Maps para Android
Iniciar la aplicación Mapas de Windows
:::no-loc(Xamarin.Forms)::: MediaElement
18/12/2020 • 31 minutes to read • Edit Online

Descargar el ejemplo
MediaElement es una vista para reproducir vídeo y audio. Los medios admitidos por la plataforma subyacente se
pueden reproducir desde los orígenes siguientes:
La web, mediante un URI (HTTP o HTTPS).
Un recurso incrustado en la aplicación de la plataforma con el ms-appx:/// esquema del URI.
Archivos que proceden de las carpetas de datos locales y temporales de la aplicación, mediante el
ms-appdata:/// esquema de URI.
Biblioteca del dispositivo.
MediaElement puede utilizar los controles de reproducción de la plataforma, que se conocen como controles de
transporte. Sin embargo, están deshabilitadas de forma predeterminada y se pueden reemplazar por sus propios
controles de transporte. Las capturas de MediaElement pantalla siguientes muestran cómo reproducir un vídeo
con los controles de transporte de plataforma:

MediaElement está disponible en :::no-loc(Xamarin.Forms)::: 4,5. Sin embargo, actualmente es experimental y solo
se puede usar agregando la siguiente línea de código al archivo app.Xaml.CS :

Device.SetFlags(new string[]{ "MediaElement_Experimental" });

NOTE
MediaElement está disponible en iOS, Android, el Plataforma universal de Windows (UWP), macOS, Windows Presentation
Foundation y Tizen.
MediaElement define las siguientes propiedades:
Aspect , de tipo Aspect , determina cómo se escalará el elemento multimedia para ajustarse al área de
presentación. El valor predeterminado de esta propiedad es AspectFit .
AutoPlay , de tipo bool , indica si la reproducción multimedia se iniciará automáticamente cuando Source se
establezca la propiedad. El valor predeterminado de esta propiedad es true .
BufferingProgress , de tipo double , indica el progreso del almacenamiento en búfer actual. El valor
predeterminado de esta propiedad es 0,0.
CanSeek , de tipo bool , indica si se puede cambiar la posición de los elementos multimedia estableciendo el
valor de la Position propiedad. Se trata de una propiedad de solo lectura.
CurrentState , de tipo MediaElementState , indica el estado actual del control. Se trata de una propiedad de solo
lectura, cuyo valor predeterminado es MediaElementState.Closed .
Duration , de tipo TimeSpan? , indica la duración del medio abierto actualmente. Se trata de una propiedad de
solo lectura cuyo valor predeterminado es null .
IsLooping , de tipo bool , describe si el origen multimedia cargado actualmente debe reanudar la
reproducción desde el principio después de llegar al final. El valor predeterminado de esta propiedad es false .
KeepScreenOn , de tipo bool , determina si la pantalla del dispositivo debe permanecer activada durante la
reproducción multimedia. El valor predeterminado de esta propiedad es false .
Position , de tipo TimeSpan , describe el progreso actual a través del tiempo de reproducción del elemento
multimedia. El valor predeterminado de esta propiedad es TimeSpan.Zero .
ShowsPlaybackControls , de tipo bool , determina si se muestran los controles de reproducción de plataformas.
El valor predeterminado de esta propiedad es false .
Source , de tipo MediaSource , indica el origen del medio cargado en el control.
VideoHeight , de tipo int , indica el alto del control. Se trata de una propiedad de solo lectura.
VideoWidth , de tipo int , indica el ancho del control. Se trata de una propiedad de solo lectura.
Volume , de tipo double , determina el volumen del medio, que se representa en una escala lineal entre 0 y 1.
Esta propiedad usa un TwoWay enlace y su valor predeterminado es 1.

Estas propiedades, con la excepción de la CanSeek propiedad, están respaldadas por BindableProperty objetos, lo
que significa que pueden ser destinos de enlaces de datos y con estilo.
La MediaElement clase también define cuatro eventos:
MediaOpened se desencadena cuando se ha validado y abierto el flujo multimedia.
MediaEnded se desencadena cuando MediaElement finaliza la reproducción de su multimedia.
MediaFailed se desencadena cuando hay un error asociado con el origen del medio.
SeekCompleted se desencadena cuando el punto de búsqueda de una operación de búsqueda solicitada está
listo para la reproducción.
Además, MediaElement incluye Play Pause los métodos, y Stop .
Para obtener información sobre los formatos multimedia compatibles en Android, consulte formatos de medios
admitidos en Developer.Android.com. Para obtener información sobre los formatos multimedia compatibles en el
Plataforma universal de Windows (UWP), consulte Códecs compatibles.

Reproducir medios remotos


MediaElement Puede reproducir archivos multimedia remotos mediante los esquemas de URI http y https. Esto se
logra estableciendo la Source propiedad en el URI del archivo multimedia:
<MediaElement Source="https://sec.ch9.ms/ch9/5d93/a1eab4bf-3288-4faf-81c4-294402a85d93/XamarinShow_mid.mp4"
ShowsPlaybackControls="True" />

De forma predeterminada, los medios que define la Source propiedad se reproducen inmediatamente después de
abrir los elementos multimedia. Para suprimir la reproducción de medios automática, establezca la AutoPlay
propiedad en false .
Los controles de reproducción multimedia están deshabilitados de forma predeterminada y se habilitan
estableciendo la ShowsPlaybackControls propiedad en true . MediaElement usará los controles de reproducción
de la plataforma.

Reproducir medios locales


Los medios locales se pueden reproducir desde los orígenes siguientes:
Un recurso incrustado en la aplicación de la plataforma con el ms-appx:/// esquema del URI.
Archivos que proceden de las carpetas de datos locales y temporales de la aplicación, mediante el
ms-appdata:/// esquema de URI.
Biblioteca del dispositivo.
Para obtener más información sobre estos esquemas de URI, vea esquemas de URI.
Reproducir medios incrustados en el paquete de la aplicación
MediaElement Puede reproducir archivos multimedia que están incrustados en el paquete de la aplicación
mediante el ms-appx:/// esquema de URI. Los archivos multimedia se incrustan en el paquete de la aplicación
colocándolos en el proyecto de plataforma.
Almacenar un archivo multimedia en el proyecto de plataforma es diferente para cada plataforma:
En iOS, los archivos multimedia deben almacenarse en la carpeta recursos o en una subcarpeta de la carpeta
recursos . El archivo multimedia debe tener un Build Action de BundleResource .
En Android, los archivos multimedia deben almacenarse en una subcarpeta de recursos denominada sin
formato . La carpeta raw no puede contener subcarpetas. El archivo multimedia debe tener un Build Action
de AndroidResource .
En UWP, los archivos multimedia se pueden almacenar en cualquier carpeta del proyecto. El archivo multimedia
debe tener un BuildAction de Content .

Los archivos multimedia que cumplen estos criterios se pueden reproducir con el ms-appx:/// esquema URI:

<MediaElement Source="ms-appx:///XamarinForms101UsingEmbeddedImages.mp4"
ShowsPlaybackControls="True" />

Al utilizar el enlace de datos, se puede usar un convertidor de valores para aplicar este esquema de URI:
public class VideoSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;

if (string.IsNullOrWhiteSpace(value.ToString()))
return null;

if (Device.RuntimePlatform == Device.UWP)
return new Uri($"ms-appx:///Assets/{value}");
else
return new Uri($"ms-appx:///{value}");
}
// ...
}

VideoSourceConverter Después, se puede usar una instancia de para aplicar el ms-appx:/// esquema de URI a un
archivo multimedia incrustado:

<MediaElement Source="{Binding MediaSource, Converter={StaticResource VideoSourceConverter}}"


ShowsPlaybackControls="True" />

Para obtener más información sobre el esquema de URI de MS-appx, consulte MS-appx y MS-appx-web.
Reproducir medios desde las carpetas locales y temporales de la aplicación
MediaElement Puede reproducir archivos multimedia que se copian en las carpetas de datos locales o temporales
de la aplicación, mediante el ms-appdata:/// esquema de URI.
En el ejemplo siguiente se muestra la Source propiedad establecida en un archivo multimedia almacenado en la
carpeta de datos locales de la aplicación:

<MediaElement Source="ms-appdata:///local/XamarinVideo.mp4"
ShowsPlaybackControls="True" />

En el ejemplo siguiente se muestra la Source propiedad de un archivo multimedia almacenado en la carpeta de


datos temporales de la aplicación:

<MediaElement Source="ms-appdata:///temp/XamarinVideo.mp4"
ShowsPlaybackControls="True" />

IMPORTANT
Además de reproducir archivos multimedia que se almacenan en las carpetas de datos locales o temporales de la aplicación,
UWP también puede reproducir archivos multimedia que se encuentran en la carpeta móvil de la aplicación. Esto se puede
lograr si se prefija el archivo multimedia con ms-appdata:///roaming/ .

Al utilizar el enlace de datos, se puede usar un convertidor de valores para aplicar este esquema de URI:
public class VideoSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;

if (string.IsNullOrWhiteSpace(value.ToString()))
return null;

return new Uri($"ms-appdata:///{value}");


}
// ...
}

VideoSourceConverter Después, se puede usar una instancia de para aplicar el ms-appdata:/// esquema de URI a
un archivo multimedia en la carpeta de datos local o temporal de la aplicación:

<MediaElement Source="{Binding MediaSource, Converter={StaticResource VideoSourceConverter}}"


ShowsPlaybackControls="True" />

Para obtener más información sobre el esquema de URI MS-AppData, consulte MS-AppData.
Copiar un archivo multimedia en la carpeta de datos local o temporal de la aplicación
La reproducción de un archivo multimedia almacenado en la carpeta de datos local o temporal de la aplicación
requiere que la aplicación copie el archivo multimedia. Esto puede realizarse, por ejemplo, mediante la copia de un
archivo multimedia desde el paquete de la aplicación:

// This method copies the video from the app package to the app data
// directory for your app. To copy the video to the temp directory
// for your app, comment out the first line of code, and uncomment
// the second line of code.
public static async Task CopyVideoIfNotExists(string filename)
{
string folder = FileSystem.AppDataDirectory;
//string folder = Path.GetTempPath();
string videoFile = Path.Combine(folder, "XamarinVideo.mp4");

if (!File.Exists(videoFile))
{
using (Stream inputStream = await FileSystem.OpenAppPackageFileAsync(filename))
{
using (FileStream outputStream = File.Create(videoFile))
{
await inputStream.CopyToAsync(outputStream);
}
}
}
}

NOTE
En el ejemplo de código anterior se usa la FileSystem clase incluida en :::no-loc(Xamarin.Essentials)::: . Para obtener más
información, vea :::no-loc(Xamarin.Essentials)::: : aplicaciones auxiliares del sistema de archivos.

Reproducir medios desde la biblioteca de dispositivos


La mayoría de los dispositivos móviles y equipos de escritorio modernos tienen la capacidad de grabar vídeos y
audio con la cámara y el micrófono del dispositivo. El medio que se crea se almacena a continuación como
archivos en el dispositivo. Estos archivos se pueden recuperar de la biblioteca y reproducir por el MediaElement .
Cada una de las plataformas incluye una instalación que permite al usuario seleccionar archivos multimedia de la
biblioteca del dispositivo. En :::no-loc(Xamarin.Forms)::: , los proyectos de la plataforma pueden invocar esta
funcionalidad y la clase puede llamarla DependencyService .
El servicio de dependencia de selección de vídeo usado en la aplicación de ejemplo es muy similar a uno definido
en la selección de una foto de la biblioteca de imágenes, con la excepción de que el selector devuelve un nombre
de archivo en lugar de un Stream objeto. El proyecto de código compartido define una interfaz denominada
IVideoPicker , que define un único método denominado GetVideoFileAsync . Cada plataforma implementa esta
interfaz en una VideoPicker clase.
En el ejemplo de código siguiente se muestra cómo recuperar un archivo multimedia de la biblioteca de
dispositivos:

string filename = await DependencyService.Get<IVideoPicker>().GetVideoFileAsync();


if (!string.IsNullOrWhiteSpace(filename))
{
mediaElement.Source = new FileMediaSource
{
File = filename
};
}

El servicio de dependencia de selección de vídeo se invoca llamando al DependencyService.Get método para


obtener la implementación de una IVideoPicker interfaz en el proyecto de plataforma. GetVideoFileAsync A
continuación, se llama al método en esa instancia y el nombre de archivo devuelto se usa para crear un
FileMediaSource objeto y establecerlo en la Source propiedad de MediaElement .

Cambiar la relación de aspecto de vídeo


La Aspect propiedad determina cómo se escalará el medio de vídeo para ajustarse al área de presentación. De
forma predeterminada, esta propiedad se establece en el AspectFit miembro de la enumeración, pero se puede
establecer en cualquiera de los Aspect miembros de enumeración:
AspectFit indica que el vídeo se mostrará en la pantalla, si es necesario, para que quepa en el área de
visualización, a la vez que se conserva la relación de aspecto.
AspectFill indica que el vídeo se recortará para que rellene el área de visualización, a la vez que conserva la
relación de aspecto.
Fill indica que el vídeo se ajustará para rellenar el área de presentación.

Sondear datos de posición


La notificación de cambio de propiedad para la Position propiedad enlazable solo se desencadena en momentos
clave como la reproducción inicial y final, y se produce una pausa. Por lo tanto, el enlace de datos a la Position
propiedad no producirá datos de posición precisos. En su lugar, debe configurar un temporizador y sondear la
propiedad.
Una buena forma de hacerlo es en la OnAppearing invalidación de la página que requiere los datos de la posición a
medida que se reproduce el medio:
bool polling = true;

protected override void OnAppearing()


{
base.OnAppearing();

Device.StartTimer(TimeSpan.FromMilliseconds(1000), () =>
{
Device.BeginInvokeOnMainThread(() =>
{
positionLabel.Text = mediaElement.Position.ToString("hh\\:mm\\:ss");
});
return polling;
});
}

protected override void OnDisappearing()


{
base.OnDisappearing();
polling = false;
}

En este ejemplo, la OnAppearing invalidación inicia un temporizador que se actualiza positionLabel con el
Position valor cada segundo. La devolución de llamada del temporizador se invoca cada segundo, hasta que la
devolución de llamada vuelve false . Cuando se produce la navegación por la página OnDisappearing , se ejecuta
la invalidación, lo que detiene la devolución de llamada del temporizador.

Descripción de los tipos MediaSource


Un MediaElement puede reproducir elementos multimedia estableciendo su Source propiedad en un archivo
multimedia local o remoto. La Source propiedad es de tipo MediaSource y esta clase define dos métodos
estáticos:
FromFile , devuelve una MediaSource instancia de a partir de un string argumento.
FromUri , devuelve una MediaSource instancia de a partir de un Uri argumento.

Además, la clase también tiene operadores implícitos que devuelven


MediaSource MediaSource instancias de los
string Uri argumentos y.

NOTE
Cuando la Source propiedad se establece en XAML, se invoca un convertidor de tipos para devolver una MediaSource
instancia de un string o Uri .

La MediaSource clase también tiene dos clases derivadas:


UriMediaSource , que se usa para especificar un archivo multimedia remoto desde un URI. Esta clase tiene una
Uri propiedad que se puede establecer en Uri .
FileMediaSource , que se usa para especificar un archivo multimedia local de un string . Esta clase tiene una
File propiedad que se puede establecer en string . Además, esta clase tiene operadores implícitos para
convertir un string en un FileMediaSource objeto y un FileMediaSource objeto en string .

NOTE
Cuando FileMediaSource se crea un objeto en XAML, se invoca un convertidor de tipos para devolver una
FileMediaSource instancia de un string .
Determinar el estado de MediaElement
La MediaElementclase define una propiedad enlazable de solo lectura denominada CurrentState , de tipo
MediaElementState . Esta propiedad indica el estado actual del control, como si el contenido multimedia se
reproduce o está en pausa, o si todavía no está listo para reproducir el medio.
La MediaElementState enumeración define los siguientes miembros:
Closed indica que MediaElement no contiene ningún medio.
Opening indica que MediaElement está validando e intentando cargar el origen especificado.
Buffering indica que MediaElement está cargando los medios para la reproducción. Su Position propiedad
no avanza durante este estado. Si MediaElement se está reproduciendo vídeo, continúa mostrando el último
fotograma mostrado.
Playing indica que MediaElement está reproduciendo el origen del medio.
Paused indica que no MediaElement hace avanzar su Position propiedad. Si MediaElement se está
reproduciendo vídeo, continúa mostrando el fotograma actual.
Stopped indica que el MediaElement elemento contiene medios pero no se está reproduciendo o en pausa. Su
Position propiedad es 0 y no avanza. Si el medio cargado es un vídeo, MediaElement muestra el primer
fotograma.
Por lo general, no es necesario examinar la CurrentState propiedad cuando se usan los MediaElement controles
de transporte. Sin embargo, esta propiedad se convierte en importante al implementar sus propios controles de
transporte.

Implementar controles de transporte personalizados


Los controles de transporte de un reproductor multimedia incluyen los botones que realizan las funciones de
reproducción , pausa y detención . Estos botones suelen identificarse con iconos conocidos en lugar de texto, y
las funciones Reproducir y Pausa suelen combinarse en un mismo botón.
De forma predeterminada, los MediaElement controles de reproducción están deshabilitados. Esto le permite
controlar MediaElement mediante programación o proporcionando sus propios controles de transporte. Para ello,
MediaElement incluye Play Pause Stop los métodos, y.

En el ejemplo de XAML siguiente se muestra una página que contiene un MediaElement control y los controles de
transporte personalizados:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MediaElementDemos.CustomTransportPage"
Title="Custom transport">
<Grid>
...
<MediaElement x:Name="mediaElement"
AutoPlay="False"
... />
<StackLayout BindingContext="{x:Reference mediaElement}"
...>
<Button Text="&#x25B6;&#xFE0F; Play"
HorizontalOptions="CenterAndExpand"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding CurrentState}"
Value="{x:Static MediaElementState.Playing}">
<Setter Property="Text"
Value="&#x23F8; Pause" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding CurrentState}"
Value="{x:Static MediaElementState.Buffering}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
<Button Text="&#x23F9; Stop"
HorizontalOptions="CenterAndExpand"
Clicked="OnStopButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding CurrentState}"
Value="{x:Static MediaElementState.Stopped}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
</StackLayout>
</Grid>
</ContentPage>

En este ejemplo, los controles de transporte personalizados se definen como Button objetos. Sin embargo, solo
hay dos Button objetos, donde el primero Button representa Play y PAUSE , y el segundo Button representa
Stop . DataTrigger los objetos se usan para habilitar y deshabilitar los botones, y para cambiar el primer botón
entre reproducir y pausar . Para obtener más información acerca de los desencadenadores de datos, vea :::no-
loc(Xamarin.Forms)::: desencadenadores.
El archivo de código subyacente tiene los controladores para los Clicked eventos:
void OnPlayPauseButtonClicked(object sender, EventArgs args)
{
if (mediaElement.CurrentState == MediaElementState.Stopped ||
mediaElement.CurrentState == MediaElementState.Paused)
{
mediaElement.Play();
}
else if (mediaElement.CurrentState == MediaElementState.Playing)
{
mediaElement.Pause();
}
}

void OnStopButtonClicked(object sender, EventArgs args)


{
mediaElement.Stop();
}

El botón reproducir se puede presionar una vez que se habilita para iniciar la reproducción:

Al presionar el botón pausar se produce una pausa en la reproducción:

Al presionar el botón detener se detiene la reproducción y se devuelve la posición del archivo multimedia al
principio.
Implementar una barra de posición personalizada
Los controles de transporte que cada plataforma implementa incluyen una barra de posición. Esta barra es similar
a un control deslizante y muestra la ubicación actual del medio dentro de su duración total. Además, puede
manipular la barra de posición para desplace hacia delante o hacia atrás hasta una nueva posición en el vídeo.
La implementación de una barra de posición personalizada requiere conocer la duración del medio y la posición
de reproducción actual. Estos datos están disponibles en las Duration Position propiedades y.

IMPORTANT
Position Se debe sondear para obtener datos de posición precisos. Para obtener más información, consulte sondear para
datos de posición.

Una barra de posición personalizada se puede implementar mediante un Slider , como se muestra en el ejemplo
siguiente:
public class PositionSlider : Slider
{
public static readonly BindableProperty DurationProperty =
BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(1),
propertyChanged: (bindable, oldValue, newValue) =>
{
((PositionSlider)bindable).SetTimeToEnd();
double seconds = ((TimeSpan)newValue).TotalSeconds;
((Slider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
});

public TimeSpan Duration


{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}

public static readonly BindableProperty PositionProperty =


BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(0),
propertyChanged: (bindable, oldValue, newValue) =>
((PositionSlider)bindable).SetTimeToEnd());

public TimeSpan Position


{
get { return (TimeSpan)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}

static readonly BindablePropertyKey TimeToEndPropertyKey =


BindableProperty.CreateReadOnly(nameof(TimeToEnd), typeof(TimeSpan), typeof(PositionSlider), new
TimeSpan());

public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;

public TimeSpan TimeToEnd


{
get { return (TimeSpan)GetValue(TimeToEndProperty); }
private set { SetValue(TimeToEndPropertyKey, value); }
}

public PositionSlider()
{
PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "Value")
{
TimeSpan newPosition = TimeSpan.FromSeconds(Value);
if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
{
Position = newPosition;
}
}
};
}

void SetTimeToEnd()
{
TimeToEnd = Duration - Position;
}
}

La PositionSlider clase define sus propias Duration Position propiedades y enlazables, y una TimeToEnd
propiedad enlazable. Las tres propiedades son del tipo TimeSpan . El controlador de propiedad modificada para la
Duration propiedad establece la Maximum propiedad de Slider en la TotalSeconds propiedad del TimeSpan
valor. La TimeToEnd propiedad se calcula en función de los cambios realizados en las Duration Position
propiedades y, y comienza en la duración del medio y disminuye hasta cero a medida que continúa la
reproducción.
PositionSlider Se actualiza desde el subyacente Slider cuando Slider se mueve para indicar que el medio
debe ser avanzado o inverso a una nueva posición. Esto se detecta en el PropertyChanged controlador en el
PositionSlider constructor. El controlador comprueba si hay algún cambio en la propiedad Value y, si es
diferente de la propiedad Position , la propiedad Position se establece desde la propiedad Value . Para obtener
más información acerca del uso de un Slider :::no-loc(Xamarin.Forms)::: control deslizante , vea

NOTE
En Android, el Slider solo tiene 1000 pasos discretos, independientemente de la Minimum configuración de y Maximum .
Si la longitud del medio es superior a 1000 segundos, dos valores diferentes se Position corresponden con el mismo
Value Slider . Este es el motivo por el que el código anterior comprueba que la nueva posición y la posición existente
son mayores que una centésima de la duración total.

En el ejemplo siguiente se muestra el PositionSlider que se está consumiendo en una página:

<controls:PositionSlider x:Name="positionSlider"
BindingContext="{x:Reference mediaElement}"
Duration="{Binding Duration}"
ValueChanged="OnPositionSliderValueChanged">
<controls:PositionSlider.Triggers>
<DataTrigger TargetType="controls:PositionSlider"
Binding="{Binding CurrentState}"
Value="{x:Static MediaElementState.Buffering}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</controls:PositionSlider.Triggers>
</controls:PositionSlider>

En este ejemplo, la Duration propiedad de PositionSlider está enlazada a datos a la Duration propiedad de
MediaElement . Cuando Value cambia la propiedad de Slider , el ValueChanged evento se activa y
OnPositionSliderValueChanged se ejecuta el controlador. Este controlador establece la MediaElement.Position
propiedad en el valor de la PositionSlider.Position propiedad. Por lo tanto, si se arrastran los Slider resultados
en el cambio de posición de reproducción multimedia:

Además, DataTrigger se utiliza un objeto para deshabilitar PositionSlider cuando el medio se almacena en el
búfer. Para obtener más información acerca de los desencadenadores de datos, vea :::no-loc(Xamarin.Forms):::
desencadenadores.

Implementar un control de volumen personalizado


Los controles de reproducción multimedia implementados por cada plataforma incluyen una barra de volumen.
Esta barra es similar a un control deslizante y muestra el volumen del medio. Además, puede manipular la barra
de volumen para aumentar o disminuir el volumen.
Una barra de volumen personalizada se puede implementar mediante un Slider , como se muestra en el ejemplo
siguiente:

<StackLayout>
<MediaElement AutoPlay="False"
Source="{StaticResource AdvancedAsync}" />
<Slider Maximum="1.0"
Minimum="0.0"
Value="{Binding Volume}"
Rotation="270"
WidthRequest="100" />
</StackLayout>

En este ejemplo, los Slider datos enlazan su Value propiedad a la Volume propiedad de MediaElement . Esto es
posible porque la Volume propiedad usa un TwoWay enlace. Por lo tanto, el cambio de la propiedad provocará
Value el cambio de la Volume propiedad.

NOTE
La Volume propiedad tiene una devolución de llamada de validación que garantiza que su valor es mayor o igual que 0,0 y
menor o igual que 1,0.

Para obtener más información acerca del uso de un Slider :::no-loc(Xamarin.Forms)::: control deslizante , vea

Vínculos relacionados
MediaElementDemos (ejemplo)
Esquemas de URI
Desencadenadores de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Hasta
Android: formatos de medios admitidos
UWP: códecs admitidos
Xamarin.Forms Colocar
18/12/2020 • 12 minutes to read • Edit Online

Un Shape es un tipo de View que permite dibujar una forma en la pantalla. Shape los objetos se pueden usar
dentro de las clases de diseño y la mayoría de los controles, ya que la Shape clase se deriva de la View clase.
Xamarin.Forms Las formas están disponibles en el Xamarin.Forms.Shapes espacio de nombres de iOS, Android,
MacOS, el plataforma universal de Windows (UWP) y el Windows Presentation Foundation (WPF).

IMPORTANT
Xamarin.Forms Las formas actualmente son experimentales y solo se pueden usar si se establece la
Shapes_Experimental marca. Para más información, vea Marcas experimentales.

Shape define las siguientes propiedades:


Aspect , de tipo Stretch , describe cómo la forma rellena su espacio asignado. El valor predeterminado de
esta propiedad es Stretch.None .
Fill , de tipo Brush , indica el pincel utilizado para pintar el interior de la forma.
Stroke , de tipo Brush , indica el pincel que se usa para pintar el contorno de la forma.
StrokeDashArray , de tipo DoubleCollection , que representa una colección de double valores que indican el
modelo de guiones y espacios que se usan para esquematizar una forma.
StrokeDashOffset , de tipo double , especifica la distancia dentro del modelo de guiones donde comienza un
guión. El valor predeterminado de esta propiedad es 0,0.
StrokeLineCap , de tipo PenLineCap , describe la forma al principio y al final de una línea o segmento. El valor
predeterminado de esta propiedad es PenLineCap.Flat .
StrokeLineJoin , de tipo PenLineJoin , especifica el tipo de combinación que se usa en los vértices de una
forma. El valor predeterminado de esta propiedad es PenLineJoin.Miter .
StrokeMiterLimit , de tipo double , especifica el límite en la proporción de la longitud del ángulo en la mitad
StrokeThickness de una forma. El valor predeterminado de esta propiedad es 10,0.
StrokeThickness , de tipo double , indica el ancho del contorno de la forma. El valor predeterminado de esta
propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Xamarin.Forms define un número de objetos que derivan de la Shape clase. Son Ellipse ,, Line Path ,
Polygon , Polyline y Rectangle .

Formas de dibujo
Brush los objetos se utilizan para pintar las formas Stroke y Fill :
<Ellipse Fill="DarkBlue"
Stroke="Red"
StrokeThickness="4"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />

En este ejemplo, se especifican el trazo y el relleno de un Ellipse :

IMPORTANT
Brush los objetos usan un convertidor de tipos que permite Color especificar valores para la Stroke propiedad.

Si no se especifica un Brush objeto para Stroke , o si se establece StrokeThickness en 0, no se dibuja el borde


alrededor de la forma.
Para obtener más información sobre los Brush objetos, vea Xamarin.Forms pinceles. Para obtener más
información sobre Color los valores válidos, vea colores Xamarin.Forms en .

Formas de ajuste
Shape los objetos tienen una Aspect propiedad, de tipo Stretch . Esta propiedad determina cómo Shape se
ajusta el contenido de un objeto para rellenar el Shape espacio de diseño del objeto. El Shape espacio de diseño
de un objeto es la cantidad de espacio que el Shape sistema de diseño asigna a Xamarin.Forms , debido a una
WidthRequest configuración explícita y a la HeightRequest HorizontalOptions configuración de y
VerticalOptions .

La enumeración Stretch define los miembros siguientes:


None , que indica que el contenido conserva su tamaño original. Este es el valor predeterminado de la
propiedad Shape.Aspect .
Fill , que indica que el contenido cambia de tamaño para rellenar las dimensiones de destino. No se
mantiene la relación de aspecto.
Uniform , que indica que se cambia el tamaño del contenido para ajustarse a las dimensiones de destino, a la
vez que se conserva la relación de aspecto.
UniformToFill , indica que se cambia el tamaño del contenido para rellenar las dimensiones de destino, a la
vez que se conserva la relación de aspecto. Si la relación de aspecto del rectángulo de destino no coincide con
el de origen, el contenido de origen se recorta para ajustarse a las dimensiones de destino.
En el siguiente código XAML se muestra cómo establecer la Aspect propiedad:
<Path Aspect="Uniform"
Stroke="Yellow"
StrokeThickness="1"
Fill="Red"
BackgroundColor="LightGray"
HorizontalOptions="Start"
HeightRequest="100"
WidthRequest="100">
<Path.Data>
<!-- Path data goes here -->
</Path.Data>
</Path>

En este ejemplo, un Path objeto dibuja un corazón. Las Path WidthRequest propiedades y del objeto
HeightRequest se establecen en unidades independientes del dispositivo 100 y su Aspect propiedad se
establece en Uniform . Como resultado, se cambia el tamaño del contenido del objeto para ajustarse a las
dimensiones de destino, a la vez que se conserva la relación de aspecto:

Dibujar formas discontinuas


Shape los objetos tienen una StrokeDashArray propiedad, de tipo DoubleCollection . Esta propiedad representa
una colección de double valores que indican el modelo de guiones y espacios que se usan para esquematizar
una forma. Un DoubleCollection es ObservableCollection de double valores. Cada double de la colección
especifica la longitud de un guión o un espacio. El primer elemento de la colección, que se encuentra en el índice
0, especifica la longitud de un guión. El segundo elemento de la colección, que se encuentra en el índice 1,
especifica la longitud de un intervalo. Por lo tanto, los objetos con un valor de índice par especifican guiones,
mientras que los objetos con un valor de índice impar especifican huecos.
Shape los objetos también tienen una StrokeDashOffset propiedad, de tipo double , que especifica la distancia
dentro del patrón de guiones donde comienza un guión. Si no se establece esta propiedad, se producirá Shape
un contorno sólido.
Las formas de guiones se pueden dibujar mediante el establecimiento de las StrokeDashArray StrokeDashOffset
propiedades y. La StrokeDashArray propiedad se debe establecer en uno o más double valores, donde cada par
está delimitado por una sola coma y/o uno o varios espacios. Por ejemplo, "0,5 1,0" y "0.5, 1.0" son válidos.
En el siguiente ejemplo de XAML se muestra cómo dibujar un rectángulo discontinuo:

<Rectangle Fill="DarkBlue"
Stroke="Red"
StrokeThickness="4"
StrokeDashArray="1,1"
StrokeDashOffset="6"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />

En este ejemplo, se dibuja un rectángulo relleno con un trazo discontinuo:


Extremos de la línea de control
Una línea tiene tres partes: el extremo inicial, el cuerpo de la línea y el extremo final. Los extremos inicial y final
describen la forma al principio y al final de una línea, o segmento.
Shape los objetos tienen una StrokeLineCap propiedad, de tipo PenLineCap , que describe la forma en el
principio y el final de una línea, o segmento. La enumeración PenLineCap define los miembros siguientes:
Flat , que representa un extremo que no se extiende más allá del último punto de la línea. Esto es
comparable a ningún extremo de línea y es el valor predeterminado de la StrokeLineCap propiedad.
Square , que representa un rectángulo que tiene un alto igual al grosor de la línea y una longitud igual a la
mitad del grosor de la línea.
Round , que representa un semicírculo que tiene un diámetro igual al grosor de la línea.

IMPORTANT
La StrokeLineCap propiedad no tiene ningún efecto si se establece en una forma que no tiene ningún punto inicial o
final. Por ejemplo, esta propiedad no tiene ningún efecto si se establece en Ellipse , o Rectangle .

En el siguiente código XAML se muestra cómo establecer la StrokeLineCap propiedad:

<Line X1="0"
Y1="20"
X2="300"
Y2="20"
StrokeLineCap="Round"
Stroke="Red"
StrokeThickness="12" />

En este ejemplo, la línea roja se redondea al principio y al final de la línea:

Combinaciones de línea de control


Shape los objetos tienen una StrokeLineJoin propiedad, de tipo PenLineJoin , que especifica el tipo de
combinación que se usa en los vértices de la forma. La enumeración PenLineJoin define los miembros
siguientes:
Miter , que representa los vértices angulares normales. Este es el valor predeterminado de la propiedad
StrokeLineJoin .
Bevel , que representa los vértices biselados.
Round , que representa los vértices redondeados.

NOTE
Cuando la StrokeLineJoin propiedad está establecida en Miter , la StrokeMiterLimit propiedad se puede
establecer en un double para limitar la longitud del inglete de las combinaciones de líneas de la forma.

En el siguiente código XAML se muestra cómo establecer la StrokeLineJoin propiedad:

<Polyline Points="20 20,250 50,20 120"


Stroke="DarkBlue"
StrokeThickness="20"
StrokeLineJoin="Round" />

En este ejemplo, la polilínea azul oscuro tiene combinaciones redondeadas en sus vértices:

Vínculos relacionados
ShapeDemos (ejemplo)
Pinceles de Xamarin.Forms
Colores en Xamarin.Forms
:::no-loc(Xamarin.Forms)::: Formas: elipse
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
La Ellipse clase se deriva de la Shape clase y se puede usar para dibujar elipses y círculos. Para obtener
información sobre las propiedades que la Ellipse clase hereda de la Shape clase, vea :::no-loc(Xamarin.Forms):::
formas.
La Ellipse clase establece la Aspect propiedad, heredada de la Shape clase, en Stretch.Fill . Para obtener más
información sobre la Aspect propiedad, consulte Stretch Shapes.

Creación de una elipse


Para dibujar una elipse, cree un Ellipse objeto y establezca WidthRequest sus HeightRequest propiedades y. Para
pintar el interior de la elipse, establezca su Fill propiedad en Color . Para asignar a la elipse un contorno,
establezca su Stroke propiedad en Color . La StrokeThickness propiedad especifica el grosor del contorno de la
elipse.
Para dibujar un círculo, haga que WidthRequest las HeightRequest propiedades y del Ellipse objeto sean iguales.
En el siguiente ejemplo de XAML se muestra cómo dibujar una elipse rellena:

<Ellipse Fill="Red"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />

En este ejemplo, se dibuja una elipse rellena roja con las dimensiones 150x50 (unidades independientes del
dispositivo):

En el siguiente ejemplo de XAML se muestra cómo dibujar un círculo:

<Ellipse Stroke="Red"
StrokeThickness="4"
WidthRequest="150"
HeightRequest="150"
HorizontalOptions="Start" />

En este ejemplo, se dibuja un círculo rojo con dimensiones 150 x 150 (unidades independientes del dispositivo):
Para obtener información sobre cómo dibujar una elipse discontinua, vea dibujar formas con guiones.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: reglas de relleno
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Varias :::no-loc(Xamarin.Forms)::: clases Shapes tienen FillRule propiedades, de tipo FillRule . Estas incluyen
Polygon , Polyline y GeometryGroup .

La FillRule enumeración EvenOdd define Nonzero los miembros y. Cada miembro representa una regla
diferente para determinar si un punto está en la región de relleno de una forma.

IMPORTANT
Todas las formas se consideran cerradas para los propósitos de las reglas de relleno.

EvenOdd
La EvenOdd regla de relleno dibuja un rayo desde el punto hasta el infinito en cualquier dirección y cuenta el
número de segmentos de la forma que cruza el rayo. Si este número es impar, el punto está dentro de. Si este
número es par, el punto está fuera.
En el siguiente ejemplo de XAML se crea y se representa una forma compuesta, con el FillRule valor
predeterminado EvenOdd :

<Path Stroke="Black"
StrokeThickness="1"
Fill="#CCCCFF"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<!-- FillRule doesn't need to be set, because EvenOdd is the default. -->
<GeometryGroup>
<EllipseGeometry RadiusX="50"
RadiusY="50"
Center="75,75" />
<EllipseGeometry RadiusX="70"
RadiusY="70"
Center="75,75" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="75,75" />
<EllipseGeometry RadiusX="120"
RadiusY="120"
Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>

En este ejemplo, se muestra una forma compuesta formada por una serie de anillos concéntricos:
En la forma compuesta, observe que no se rellenan los anillos del centro y el tercer. Esto se debe a que un rayo
dibujado desde cualquier punto dentro de cualquiera de esos dos anillos pasa a través de un número par de
segmentos:

En la imagen anterior, los círculos rojos representan puntos y las líneas representan rayos arbitrarios. En el punto
superior, los dos rayos arbitrarios atraviesan un número par de segmentos de línea. Por lo tanto, el anillo en el
que se encuentra el punto no se rellena. En el punto inferior, los dos rayos arbitrarios pasan a través de un
número impar de segmentos de línea. Por lo tanto, se rellena el anillo en el que se encuentra el punto.

Distinto
La Nonzero regla de relleno dibuja un rayo desde el punto hasta el infinito en cualquier dirección y, a
continuación, examina los lugares donde un segmento de la forma cruza el rayo. A partir de un recuento de cero,
el recuento se incrementa cada vez que un segmento cruza el rayo de izquierda a derecha y disminuye cada vez
que un segmento cruza el radio de derecha a izquierda. Después de contar las cruces, si el resultado es cero, el
punto está fuera del polígono. De lo contrario, está dentro de.
En el siguiente ejemplo de XAML se crea y se representa una forma compuesta, con el FillRule valor de
establecido en Nonzero :
<Path Stroke="Black"
StrokeThickness="1"
Fill="#CCCCFF"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<GeometryGroup FillRule="Nonzero">
<EllipseGeometry RadiusX="50"
RadiusY="50"
Center="75,75" />
<EllipseGeometry RadiusX="70"
RadiusY="70"
Center="75,75" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="75,75" />
<EllipseGeometry RadiusX="120"
RadiusY="120"
Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>

En este ejemplo, se muestra una forma compuesta formada por una serie de anillos concéntricos:

En la forma compuesta, observe que se rellenan todos los anillos. Esto se debe a que todos los segmentos se
ejecutan en la misma dirección, por lo que un rayo dibujado desde cualquier punto cruzará uno o más segmentos
y la suma de las cruces no será igual a cero:

En la imagen anterior, las flechas rojas representan la dirección en la que se dibujan los segmentos y la flecha
negra representa un rayo arbitrario que se ejecuta desde un punto en el anillo más interno. A partir de un valor
de cero, para cada segmento que cruza el radio, se agrega un valor de uno porque el segmento cruza el radio de
izquierda a derecha.
Se necesita una forma más compleja con segmentos que se ejecuten en diferentes direcciones para demostrar
mejor el comportamiento de la Nonzero regla de relleno. En el ejemplo de XAML siguiente se crea una forma
similar al ejemplo anterior, salvo que se crea con un PathGeometry en lugar de un EllipseGeometry :
<Path Stroke="Black"
StrokeThickness="1"
Fill="#CCCCFF">
<Path.Data>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathGeometry.Figures>
<!-- Inner ring -->
<PathFigure StartPoint="120,120">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="50,50"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,120" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>

<!-- Second ring -->


<PathFigure StartPoint="120,100">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="70,70"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>

<!-- Third ring -->


<PathFigure StartPoint="120,70">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,100"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,70" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>

<!-- Outer ring -->


<PathFigure StartPoint="120,300">
<PathFigure.Segments>
<ArcSegment Size="130,130"
IsLargeArc="True"
SweepDirection="Clockwise"
Point="140,300" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>

En este ejemplo, se dibuja una serie de segmentos de arco que no están cerrados:
En la imagen anterior, no se rellena el tercer arco desde el centro. Esto se debe a que la suma de los valores de un
rayo determinado que cruza los segmentos en su ruta de acceso es cero:

En la imagen anterior, el círculo rojo representa un punto, las líneas negras representan rayos arbitrarios que
salen del punto en la región no rellena y las flechas rojas representan la dirección en la que se dibujan los
segmentos. Como se puede observar, la suma de los valores de los rayos que cruzan los segmentos es cero:
El rayo arbitrario que se desplaza diagonalmente a la derecha cruza dos segmentos que se ejecutan en
direcciones diferentes. Por lo tanto, los segmentos se cancelan entre sí y proporcionan un valor de cero.
El rayo arbitrario que se desplaza diagonalmente a la izquierda cruza un total de seis segmentos. Sin embargo,
las cruces se cancelan entre sí para que cero sea la suma final.
Una suma de cero da como resultado que el anillo no se rellene.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: geometrías
18/12/2020 • 32 minutes to read • Edit Online

Descargar el ejemplo
La Geometry clase y las clases que derivan de ella permiten describir la geometría de una forma 2D. Geometry los
objetos pueden ser simples, como rectángulos y círculos, o compuestos, creados a partir de dos o más objetos
Geometry. Además, se pueden crear geometrías más complejas que incluyen arcos y curvas.
La Geometry clase es la clase primaria para varias clases que definen diferentes categorías de geometrías:
EllipseGeometry , que representa la geometría de una elipse o un círculo.
GeometryGroup , que representa un contenedor que puede combinar varios objetos Geometry en un solo objeto.
LineGeometry , que representa la geometría de una línea.
PathGeometry , que representa la geometría de una forma compleja que puede estar formada por arcos, curvas,
elipses, líneas y rectángulos.
RectangleGeometry , que representa la geometría de un rectángulo o cuadrado.

Las Geometry Shape clases y parecen similares, ya que ambos describen las formas 2D, pero tienen una diferencia
importante. La Geometry clase se deriva de la BindableObject clase, mientras que la Shape clase se deriva de la
View clase. Por lo tanto, Shape los objetos se pueden representar y participar en el sistema de diseño, mientras
que los Geometry objetos no pueden. Aunque los objetos Shape se pueden usar más fácilmente que los Geometry
objetos, los Geometry objetos son más versátiles. Mientras Shape que un objeto se usa para representar gráficos
2D, Geometry se puede usar un objeto para definir la región geométrica de los gráficos 2D y definir una región
para el recorte.
Las siguientes clases tienen propiedades que se pueden establecer en Geometry objetos:
La clase utiliza Geometry para describir su contenido. Puede representar una Geometry estableciendo la
Path
Path.Data propiedad en un Geometry objeto y estableciendo las Path Fill propiedades y del objeto Stroke
.
La VisualElement clase tiene una Clip propiedad, de tipo Geometry , que define el contorno del contenido de
un elemento. Cuando la Clip propiedad se establece en un Geometry objeto, solo se verá el área que está
dentro de la región de Geometry . Para obtener más información, vea Recorte con una geometría.
Las clases que derivan de la Geometry clase se pueden agrupar en tres categorías: geometrías simples, geometrías
de trazado y geometrías compuestas.

Geometrías simples
Las clases de geometría simples son EllipseGeometry , LineGeometry y RectangleGeometry . Se usan para crear
formas geométricas básicas, como círculos, líneas y rectángulos. Estas mismas formas, así como formas más
complejas, se pueden crear mediante PathGeometry o combinando objetos Geometry, pero estas clases
proporcionan un enfoque más sencillo para generar estas formas geométricas básicas.
EllipseGeometry
Una geometría de elipse representa la geometría o una elipse o un círculo, y se define mediante un punto central,
un radio x y un radio y.
La clase EllipseGeometry define las propiedades siguientes:
Center , de tipo Point , que representa el punto central de la geometría.
RadiusX , de tipo double , que representa el valor del radio x de la geometría. El valor predeterminado de esta
propiedad es 0,0.
RadiusY , de tipo double , que representa el valor del radio y de la geometría. El valor predeterminado de esta
propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.
En el ejemplo siguiente se muestra cómo crear y representar un EllipseGeometry en un Path objeto:

<Path Fill="Blue"
Stroke="Red"
StrokeThickness="1">
<Path.Data>
<EllipseGeometry Center="50,50"
RadiusX="50"
RadiusY="50" />
</Path.Data>
</Path>

En este ejemplo, el centro de EllipseGeometry se establece en (50, 50) y los radios x e y se establecen en 50. Esto
crea un círculo rojo con un diámetro de unidades independientes del dispositivo 100, cuyo interior se pinta azul:

LineGeometry
Una geometría de línea representa la geometría de una línea y se define especificando el punto inicial de la línea y
el punto final.
La clase LineGeometry define las propiedades siguientes:
StartPoint , de tipo Point , que representa el punto inicial de la línea.
EndPoint , de tipo Point , que representa el punto final de la línea.

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.
En el ejemplo siguiente se muestra cómo crear y representar un LineGeometry en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<LineGeometry StartPoint="10,20"
EndPoint="100,130" />
</Path.Data>
</Path>

En este ejemplo, LineGeometry se dibuja un de (10, 20) a (100.130):


NOTE
Establecer la Fill propiedad de un Path que representa un LineGeometry no tendrá ningún efecto, ya que una línea no
tiene ningún interior.

RectangleGeometry
Una geometría de rectángulo representa la geometría de un rectángulo o cuadrado y se define con una Rect
estructura que especifica su posición relativa y su alto y ancho.
La RectangleGeometry clase define la Rect propiedad, de tipo Rect , que representa las dimensiones del
rectángulo. Esta propiedad está respaldada por un BindableProperty objeto, lo que significa que puede ser el
destino de los enlaces de datos y con estilo.
En el ejemplo siguiente se muestra cómo crear y representar un RectangleGeometry en un Path objeto:

<Path Fill="Blue"
Stroke="Red"
StrokeThickness="1">
<Path.Data>
<RectangleGeometry Rect="10,10,150,100" />
</Path.Data>
</Path>

La posición y las dimensiones del rectángulo se definen mediante una Rect estructura. En este ejemplo, la
posición es (10, 10), el ancho es 150 y el alto es de 100 unidades independientes del dispositivo:

Geometrías de ruta de acceso


Una geometría de trazado describe una forma compleja que puede estar formada por arcos, curvas, elipses, líneas
y rectángulos.
La clase PathGeometry define las propiedades siguientes:
Figures , de tipo PathFigureCollection , que representa la colección de PathFigure objetos que describen el
contenido de la ruta de acceso.
FillRule , de tipo FillRule , que determina cómo se combinan las áreas de intersección contenidas en la
geometría. El valor predeterminado de esta propiedad es FillRule.EvenOdd .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.
Para obtener más información acerca de la FillRule enumeración, consulte :::no-loc(Xamarin.Forms)::: formas:
rellenar reglas.
NOTE
La Figures propiedad es ContentProperty de la PathGeometry clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

Un PathGeometry objeto se compone de una colección de PathFigure objetos, cada uno de PathFigure los cuales
describe una forma en la geometría. Cada una de ellas PathFigure se compone de uno o varios PathSegment
objetos, cada uno de los cuales describe un segmento de la forma. Hay muchos tipos de segmentos:
ArcSegment , que crea un arco elíptico entre dos puntos.
BezierSegment , que crea una curva Bézier cúbica entre dos puntos.
LineSegment , que crea una línea entre dos puntos.
PolyBezierSegment , que crea una serie de curvas Bézier cúbicas.
PolyLineSegment , que crea una serie de líneas.
PolyQuadraticBezierSegment , que crea una serie de curvas Bézier cuadráticas.
QuadraticBezierSegment , que crea una curva Bézier cuadrática.

Todas las clases anteriores derivan de la PathSegment clase abstracta.


Los segmentos de un PathFigure se combinan en una sola forma geométrica con el punto final de cada segmento
que es el punto inicial del segmento siguiente. La StartPoint propiedad de un PathFigure especifica el punto
desde el que se dibuja el primer segmento. Cada segmento posterior comienza en el punto final del segmento
anterior. Por ejemplo, se puede definir una línea vertical de 10,50 a estableciendo 10,150 la StartPoint
propiedad en 10,50 y creando un LineSegment con un Point valor de propiedad de 10,150 :

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,50">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="10,150" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

Se pueden crear geometrías más complejas mediante el uso de una combinación de PathSegment objetos y
mediante el uso de varios PathFigure objetos dentro de PathGeometry .
Crear un ArcSegment
Un ArcSegment crea un arco elíptico entre dos puntos. Un arco elíptico se define mediante sus puntos inicial y final,
x-e y-radio, factor de rotación del eje x, un valor que indica si el arco debe ser superior a 180 grados y un valor que
describe la dirección en la que se dibuja el arco.
La clase ArcSegment define las propiedades siguientes:
Point , de tipo Point , que representa el extremo del arco elíptico. El valor predeterminado de esta propiedad
es (0,0).
Size , de tipo , que representa el radio x e y del arco. El valor predeterminado de esta propiedad es (0,0).
Size
RotationAngle , de tipo double , que representa la cantidad en grados que la elipse gira alrededor del eje x. El
valor predeterminado de esta propiedad es 0.
SweepDirection , de tipo SweepDirection , que especifica la dirección en la que se dibuja el arco. El valor
predeterminado de esta propiedad es SweepDirection.CounterClockwise .
IsLargeArc , de tipo bool , que indica si el arco debe ser mayor que 180 grados. El valor predeterminado de
esta propiedad es false .
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.

NOTE
La ArcSegment clase no contiene una propiedad para el punto inicial del arco. Solo define el punto final del arco que
representa. El punto inicial del arco es el punto actual del PathFigure al que ArcSegment se agrega.

La enumeración SweepDirection define los miembros siguientes:


CounterClockwise , que especifica que los arcos se dibujan en sentido de las agujas del reloj.
Clockwise , que especifica que los arcos se dibujan en el sentido de las agujas del reloj.

En el ejemplo siguiente se muestra cómo crear y representar un ArcSegment en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,50"
RotationAngle="45"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="200,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, se dibuja un arco elíptico de (10.100) a (200.100).


Creación de un BezierSegment
Un BezierSegment crea una curva Bézier cúbica entre dos puntos. Una curva Bézier cúbica se define mediante
cuatro puntos: un punto inicial, un punto final y dos puntos de control.
La clase BezierSegment define las propiedades siguientes:
Point1 , de tipo Point , que representa el primer punto de control de la curva. El valor predeterminado de esta
propiedad es (0,0).
Point2 , de tipo Point , que representa el segundo punto de control de la curva. El valor predeterminado de
esta propiedad es (0,0).
Point3 , de tipo Point , que representa el punto final de la curva. El valor predeterminado de esta propiedad es
(0,0).
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.

NOTE
La BezierSegment clase no contiene una propiedad para el punto inicial de la curva. El punto inicial de la curva es el punto
actual del PathFigure al que BezierSegment se agrega.

Los dos puntos de control de una curva Bézier cúbica se comportan como imanes, y atraen partes de lo que de
otro modo serían una línea recta hacia ellos mismos y generando una curva. El primer punto de control afecta a la
parte inicial de la curva. El segundo punto de control afecta a la parte final de la curva. La curva no pasa
necesariamente a través de ninguno de los puntos de control. En su lugar, cada punto de control mueve su parte
de la línea hacia sí misma, pero no a sí misma.
En el ejemplo siguiente se muestra cómo crear y representar un BezierSegment en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<BezierSegment Point1="100,0"
Point2="200,200"
Point3="300,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, una curva Bézier cúbica se dibuja de (10, 10) a (300, 10). La curva tiene dos puntos de control en
(100, 0) y (200.200):

Crear un LineSegment
Un LineSegment crea una línea entre dos puntos.
La LineSegment clase define la Point propiedad, de tipo Point , que representa el punto final del segmento de
línea. El valor predeterminado de esta propiedad es (0,0) y está respaldado por un BindableProperty objeto, lo que
significa que puede ser el destino de los enlaces de datos y con estilo.
NOTE
La LineSegment clase no contiene una propiedad para el punto inicial de la línea. Solo define el punto final. El punto inicial
de la línea es el punto actual del PathFigure al que LineSegment se agrega.

En el ejemplo siguiente se muestra cómo crear y representar LineSegment objetos en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True"
StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="100,100" />
<LineSegment Point="100,50" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, un segmento de línea se dibuja de (10.100) a (100.100) y de (100.100) a (100, 50). Además, el
PathFigure está cerrado porque su IsClosed propiedad está establecida en true . Esto da como resultado que se
dibuje un triángulo:

Creación de un PolyBezierSegment
Un PolyBezierSegment crea una o varias curvas Bézier cúbicas.
La PolyBezierSegment clase define la Points propiedad, de tipo PointCollection , que representa los puntos que
definen PolyBezierSegment . Un PointCollection es ObservableCollection de Point objetos. Esta propiedad está
respaldada por un BindableProperty objeto, lo que significa que puede ser el destino de los enlaces de datos y con
estilo.

NOTE
La PolyBezierSegment clase no contiene una propiedad para el punto inicial de la curva. El punto inicial de la curva es el
punto actual del PathFigure al que PolyBezierSegment se agrega.

En el ejemplo siguiente se muestra cómo crear y representar un PolyBezierSegment en un Path objeto:


<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<PolyBezierSegment Points="0,0 100,0 150,100 150,0 200,0 300,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, PolyBezierSegment especifica dos curvas Bézier cúbicas. La primera curva va de (10, 10) a
(150.100) con un punto de control de (0,0) y otro punto de control de (100, 0). La segunda curva va de (150.100) a
(300, 10) con un punto de control de (150, 0) y otro punto de control de (200, 0):

Creación de un PolyLineSegment
Un PolyLineSegment crea uno o más segmentos de línea.
La PolyLineSegment clase define la Points propiedad, de tipo PointCollection , que representa los puntos que
definen PolyLineSegment . Un PointCollection es ObservableCollection de Point objetos. Esta propiedad está
respaldada por un BindableProperty objeto, lo que significa que puede ser el destino de los enlaces de datos y con
estilo.

NOTE
La PolyLineSegment clase no contiene una propiedad para el punto inicial de la línea. El punto inicial de la línea es el punto
actual del PathFigure al que PolyLineSegment se agrega.

En el ejemplo siguiente se muestra cómo crear y representar un PolyLineSegment en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PolyLineSegment Points="50,10 50,50" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
En este ejemplo, PolyLineSegment especifica dos líneas. La primera línea va de (10, 10) a (50, 10) y la segunda línea
va de (50, 10) a (50, 50):

Creación de un PolyQuadraticBezierSegment
Un PolyQuadraticBezierSegment crea una o varias curvas Bézier cuadráticas.
La PolyQuadraticBezierSegment clase define la Points propiedad, de tipo PointCollection , que representa los
puntos que definen PolyQuadraticBezierSegment . Un PointCollection es ObservableCollection de Point objetos.
Esta propiedad está respaldada por un BindableProperty objeto, lo que significa que puede ser el destino de los
enlaces de datos y con estilo.

NOTE
La PolyQuadraticBezierSegment clase no contiene una propiedad para el punto inicial de la curva. El punto inicial de la
curva es el punto actual del PathFigure al que PolyQuadraticBezierSegment se agrega.

En el ejemplo siguiente se muestra cómo crear y representar un PolyQuadraticBezierSegment en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<PolyQuadraticBezierSegment Points="100,100 150,50 0,100 15,200" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, PolyQuadraticBezierSegment especifica dos curvas de Bézier. La primera curva va de (10, 10) a
(150, 50) con un punto de control en (100.100). La segunda curva va de (100.100) a (15.200) con un punto de
control en (0100):

Creación de un QuadraticBezierSegment
Un QuadraticBezierSegment crea una curva Bézier cuadrática entre dos puntos.
La clase QuadraticBezierSegment define las propiedades siguientes:
Point1 , de tipo Point , que representa el punto de control de la curva. El valor predeterminado de esta
propiedad es (0,0).
Point2 , de tipo Point , que representa el punto final de la curva. El valor predeterminado de esta propiedad es
(0,0).
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.

NOTE
La QuadraticBezierSegment clase no contiene una propiedad para el punto inicial de la curva. El punto inicial de la curva es
el punto actual del PathFigure al que QuadraticBezierSegment se agrega.

En el ejemplo siguiente se muestra cómo crear y representar un QuadraticBezierSegment en un Path objeto:

<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="200,200"
Point2="300,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, se dibuja una curva Bézier cuadrática de (10, 10) a (300, 10). La curva tiene un punto de control
en (200.200):

Crear geometrías complejas


Se pueden crear geometrías más complejas mediante una combinación de PathSegment objetos. En el ejemplo
siguiente se crea una forma mediante un BezierSegment , un LineSegment y un ArcSegment :
<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,50">
<PathFigure.Segments>
<BezierSegment Point1="100,0"
Point2="200,200"
Point3="300,100"/>
<LineSegment Point="400,100" />
<ArcSegment Size="50,50"
RotationAngle="45"
IsLargeArc="True"
SweepDirection="Clockwise"
Point="200,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, BezierSegment se define primero un con cuatro puntos. A continuación, en el ejemplo se agrega
un LineSegment , que se dibuja entre el punto final de BezierSegment hasta el punto especificado por LineSegment
. Por último, ArcSegment se dibuja desde el punto final del LineSegment hasta el punto especificado por
ArcSegment .

Se pueden crear geometrías incluso más complejas mediante el uso PathFigure de varios objetos dentro de
PathGeometry . En el ejemplo siguiente se crea un a PathGeometry partir de siete PathFigure objetos, algunos de
los cuales contienen varios PathSegment objetos:
<Path Stroke="Red"
StrokeThickness="12"
StrokeLineJoin="Round">
<Path.Data>
<PathGeometry>
<!-- H -->
<PathFigure StartPoint="0,0">
<LineSegment Point="0,100" />
</PathFigure>
<PathFigure StartPoint="0,50">
<LineSegment Point="50,50" />
</PathFigure>
<PathFigure StartPoint="50,0">
<LineSegment Point="50,100" />
</PathFigure>

<!-- E -->
<PathFigure StartPoint="125, 0">
<BezierSegment Point1="60, -10"
Point2="60, 60"
Point3="125, 50" />
<BezierSegment Point1="60, 40"
Point2="60, 110"
Point3="125, 100" />
</PathFigure>

<!-- L -->
<PathFigure StartPoint="150, 0">
<LineSegment Point="150, 100" />
<LineSegment Point="200, 100" />
</PathFigure>

<!-- L -->
<PathFigure StartPoint="225, 0">
<LineSegment Point="225, 100" />
<LineSegment Point="275, 100" />
</PathFigure>

<!-- O -->
<PathFigure StartPoint="300, 50">
<ArcSegment Size="25, 50"
Point="300, 49.9"
IsLargeArc="True" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, la palabra "Hello" se dibuja utilizando una combinación de LineSegment BezierSegment objetos y,
junto con un solo ArcSegment objeto:

Geometrías compuestas
Los objetos de geometría compuestos se pueden crear mediante GeometryGroup . La GeometryGroup clase crea una
geometría compuesta a partir de uno o más Geometry objetos. Geometry Se puede agregar cualquier número de
objetos a GeometryGroup .
La clase GeometryGroup define las propiedades siguientes:
Children , de tipo , que tiene en especie los objetos que definen GeomtryGroup . Un
GeometryCollection
GeometryCollection es ObservableCollection de Geometry objetos.
FillRule , de tipo FillRule , que especifica cómo se combinan las áreas de intersección de GeometryGroup . El
valor predeterminado de esta propiedad es FillRule.EvenOdd .
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de los
enlaces de datos, y con estilo.

NOTE
La Children propiedad es ContentProperty de la GeometryGroup clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

Para obtener más información acerca de la FillRule enumeración, consulte :::no-loc(Xamarin.Forms)::: formas:
rellenar reglas.
Para dibujar una geometría compuesta, establezca los Geometry objetos necesarios como los elementos
secundarios de un elemento GeometryGroup y muestrelos con un Path objeto. En el código XAML siguiente se
muestra un ejemplo de esto:

<Path Stroke="Green"
StrokeThickness="2"
Fill="Orange">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="150,150" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="250,150" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="150,250" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="250,250" />
</GeometryGroup>
</Path.Data>
</Path>

En este ejemplo, EllipseGeometry se combinan cuatro objetos con las mismas coordenadas x-radio e y, pero con
distintas coordenadas de centro. Esto crea cuatro círculos superpuestos, cuyos interiores se rellenan de color
naranja debido a la EvenOdd regla de relleno predeterminada:

Recortar con una geometría


La VisualElement clase tiene una Clip propiedad, de tipo Geometry , que define el contorno del contenido de un
elemento. Cuando la Clip propiedad se establece en un Geometry objeto, solo se verá el área que está dentro de
la región de Geometry .
En el ejemplo siguiente se muestra cómo usar un Geometry objeto como la región de recorte de un Image :

<Image Source="monkeyface.png">
<Image.Clip>
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="180,180" />
</Image.Clip>
</Image>

En este ejemplo, un EllipseGeometry con RadiusX RadiusY los valores y de 100 y un Center valor de (180.180)
se establece en la Clip propiedad de Image . Solo se mostrará la parte de la imagen que se encuentra dentro del
área de la elipse:

NOTE
Las geometrías simples, las geometrías de trazado y las geometrías compuestas se pueden usar para recortar
VisualElement objetos.

Otras características
La GeometryHelper clase proporciona los siguientes métodos auxiliares:
FlattenGeometry , que alisa un Geometry en un PathGeometry .
FlattenCubicBezier , que alisa una curva Bézier cúbica en una List<Point> colección.
FlattenQuadraticBezier , que alisa una curva Bézier cuadrática en una List<Point> colección.
FlattenArc , que alisa un arco elíptico en una List<Point> colección.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: reglas de relleno
:::no-loc(Xamarin.Forms)::: Formas: línea
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
La Line clase se deriva de la Shape clase y se puede usar para dibujar líneas. Para obtener información sobre las
propiedades que la Line clase hereda de la Shape clase, vea :::no-loc(Xamarin.Forms)::: formas.
Line define las siguientes propiedades:
X1 , de tipo Double, indica la coordenada x del punto inicial de la línea. El valor predeterminado de esta
propiedad es 0,0.
Y1 , de tipo Double, indica la coordenada y del punto inicial de la línea. El valor predeterminado de esta
propiedad es 0,0.
X2 , de tipo Double, indica la coordenada x del punto final de la línea. El valor predeterminado de esta
propiedad es 0,0.
Y2 , de tipo Double, indica la coordenada y del punto final de la línea. El valor predeterminado de esta
propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Para obtener información acerca de cómo controlar el modo en que se dibujan los extremos de línea, vea
extremos de línea de control.

Crear una línea


Para dibujar una línea, cree un Line objeto y establezca sus X1 Y1 propiedades y en su punto inicial, y sus X2
Y propiedades y en su punto final. Además, establezca su Stroke propiedad en Color porque una línea sin
trazo no es visible.

NOTE
Establecer la Fill propiedad de Line no tiene ningún efecto, ya que una línea no tiene ningún interior.

En el siguiente ejemplo de XAML se muestra cómo dibujar una línea:

<Line X1="40"
Y1="0"
X2="0"
Y2="120"
Stroke="Red"
StrokeThickness="1" />

En este ejemplo, se dibuja una línea diagonal roja desde (40, 0) a (0120):
Dado que X1 las Y1 propiedades,, X2 y Y2 tienen valores predeterminados de 0, es posible dibujar algunas
líneas con la sintaxis mínima:

<Line Stroke="Red"
StrokeThickness="1"
X2="200" />

En este ejemplo, se define una línea horizontal que tiene una longitud de 200 unidades independientes del
dispositivo. Dado que las otras propiedades son 0 de forma predeterminada, se dibuja una línea de (0,0) a (200, 0).
En el siguiente ejemplo de XAML se muestra cómo dibujar una línea discontinua:

<Line X1="40"
Y1="0"
X2="0"
Y2="120"
Stroke="DarkBlue"
StrokeThickness="1"
StrokeDashArray="1,1"
StrokeDashOffset="6" />

En este ejemplo, se dibuja una línea diagonal discontinua azul oscura de (40, 0) a (0120):

Para obtener más información sobre cómo dibujar una línea discontinua, vea dibujar formas discontinuas.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: ruta de acceso
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
La Path clase se deriva de la Shape clase y se puede usar para dibujar curvas y formas complejas. Estas curvas y
formas suelen describirse mediante Geometry objetos. Para obtener información sobre las propiedades que la
Path clase hereda de la Shape clase, vea :::no-loc(Xamarin.Forms)::: formas.

Path define las siguientes propiedades:


Data , de tipo , que especifica la forma que se va a dibujar.
Geometry
RenderTransform , de tipo Transform , que representa la transformación que se aplica a la geometría de una
ruta de acceso antes de que se dibuje.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Para obtener más información sobre las transformaciones, vea :::no-loc(Xamarin.Forms)::: transformaciones de
trazado.

Crear una ruta de acceso


Para dibujar un trazado, cree un Path objeto y establezca su Data propiedad. Hay dos técnicas para establecer la
Data propiedad:

Puede establecer un valor de cadena para Data en XAML mediante la sintaxis de marcado de trazados. Con
este enfoque, el Path.Data valor está consumiendo un formato de serialización para los gráficos.
Normalmente, no edite este valor de cadena manualmente después de crearlo. En su lugar, se usan las
herramientas de diseño para manipular los datos y se exportan como un fragmento de cadena que puede
utilizar la Data propiedad.
Puede establecer la Data propiedad en un Geometry objeto. Puede ser un objeto específico Geometry o una
GeometryGroup que actúa como un contenedor que puede combinar varios objetos Geometry en un solo
objeto.
Crear una ruta de acceso con sintaxis de marcado de trazados
En el siguiente ejemplo de XAML se muestra cómo dibujar un triángulo mediante la sintaxis de marcado de
trazados:

<Path Data="M 10,100 L 100,100 100,50Z"


Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Start" />

La Data cadena comienza con el comando move, indicado por M , que establece un punto de inicio absoluto para
la ruta de acceso. L es el comando de línea, que crea una línea recta desde el punto inicial hasta el punto final
especificado. Z es el comando cerrar, que crea una línea que conecta el punto actual con el punto inicial. El
resultado es un triángulo:

Para obtener más información sobre la sintaxis de marcado de trazados, vea :::no-loc(Xamarin.Forms)::: Sintaxis de
marcado de trazados.
Crear una ruta de acceso con objetos Geometry
Las curvas y formas se pueden describir mediante Geometry objetos, que se utilizan para establecer la Path
propiedad del objeto Data . Hay una variedad de Geometry objetos entre los que elegir. Las EllipseGeometry
LineGeometry clases, y RectangleGeometry describen formas relativamente simples. Para crear formas más
complejas o crear curvas, use PathGeometry .
PathGeometry los objetos se componen de uno o varios PathFigure objetos. Cada PathFigure objeto representa
una forma diferente. Cada PathFigure objeto se compone de uno o varios PathSegment objetos, cada uno de los
cuales representa una parte de conexión de la forma. Los tipos de segmento incluyen las LineSegment
BezierSegment clases, y ArcSegment .

En el siguiente ejemplo de XAML se muestra cómo dibujar un triángulo mediante un PathGeometry objeto:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True"
StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="100,100" />
<LineSegment Point="100,50" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

En este ejemplo, el punto inicial del triángulo es (10.100). Un segmento de línea se dibuja de (10.100) a (100.100)
y de (100.100) a (100, 50). A continuación, se conectan los segmentos primero y último, porque la
PathFigure.IsClosed propiedad está establecida en true . El resultado es un triángulo:
Para obtener más información sobre las geometrías, vea :::no-loc(Xamarin.Forms)::: geometrías.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Geometrías
:::no-loc(Xamarin.Forms)::: Sintaxis de marcado de trazados
:::no-loc(Xamarin.Forms)::: Transformaciones de ruta de acceso
:::no-loc(Xamarin.Forms)::: Formas: sintaxis de
marcado de trazados
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: la sintaxis de marcado de trazados le permite especificar de forma compacta las
geometrías de ruta de acceso en XAML. La sintaxis se especifica como un valor de cadena para la Path.Data
propiedad:

<Path Stroke="Black"
StrokeThickness="1"
Data="M13.908992,16.207977 L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983Z" />

La sintaxis de marcado de trazados se compone de un FillRule valor opcional y una o varias descripciones de la
figura. Esta sintaxis se puede expresar como: <Path Data=" [fillRule] figureDescription [figureDescription] *
" ... />

En esta sintaxis:
fillRule es un opcional :::no-loc(Xamarin.Forms):::.Shapes.FillRule que especifica si la geometría debe usar
EvenOdd o Nonzero FillRule . F0 se utiliza para especificar la EvenOdd regla de relleno, mientras F1 que se
usa para especificar la Nonzero regla de relleno. Para obtener más información sobre las reglas de relleno,
consulte :::no-loc(Xamarin.Forms)::: formas: rellenar reglas.
figureDescription representa una figura compuesta por un comando de movimiento, comandos de dibujo y un
comando de cierre opcional. Un comando de movimiento especifica el punto inicial de la figura. Los comandos
Draw describen el contenido de la figura y el comando opcional Close cierra la figura.
En el ejemplo anterior, la sintaxis de marcado de trazados especifica un punto de inicio mediante el comando move
( M ), una serie de líneas rectas con el comando line ( L ) y cierra la ruta de acceso con el comando CLOSE ( Z ).
En la sintaxis de marcado de trazado, no se necesitan espacios antes ni después de los comandos. Además, no es
necesario separar dos números con una coma o un espacio en blanco, pero esto solo se puede lograr cuando la
cadena no es ambigua.

TIP
La sintaxis de marcado de rutas de acceso es compatible con las definiciones de rutas de acceso de imágenes SVG (Scalable
Vector Graphics), por lo que puede ser útil para migrar gráficos del formato SVG.

Mientras que la sintaxis de marcado de la ruta de acceso está diseñada para el consumo en XAML, se puede
convertir en un Geometry objeto en código invocando el ConvertFromInvariantString método en la
PathGeometryConverter clase:
Geometry pathData = (Geometry)new PathGeometryConverter().ConvertFromInvariantString("M13.908992,16.207977
L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983Z");

Comando de movimiento
El comando move especifica el punto inicial de una nueva figura. La sintaxis de este comando es: M startPoint o
m StartPoint.

En esta sintaxis, startPoint es una Point estructura que especifica el punto inicial de una nueva figura. Si enumera
varios puntos después del comando de movimiento, se dibuja una línea en esos puntos.
M 10,10 es un ejemplo de un comando de movimiento válido.

Comandos de dibujo
Un comando de dibujo puede constar de varios comandos de forma. Están disponibles los siguientes comandos
Draw:
Línea ( L o l ).
Línea horizontal ( H o h ).
Línea vertical ( V o v ).
Arco elíptico ( A o a ).
Curva Bézier cúbica ( C o c ).
Curva Bézier cuadrática ( Q o q ).
Curva Bézier cúbica suavizada ( S o s ).
Curva Bézier cuadrática suavizada ( T o t ).
Cada comando Draw se especifica con una letra que no distingue mayúsculas de minúsculas. Cuando accedes a
más de un comando del mismo tipo de forma secuencial, puedes omitir la entrada de comando duplicada. Por
ejemplo, L 100,200 300,400 es equivalente a L 100,200 L 300,400 .
Comando de línea
El comando line crea una línea recta entre el punto actual y el punto final especificado. La sintaxis de este comando
es: L extremo o l punto de conexión.
En esta sintaxis, el punto de conexión es un Point que representa el punto final de la línea.
L 20,30 y L 20 30 son ejemplos de comandos de línea válidos.
Para obtener información sobre cómo crear una línea recta como un PathGeometry objeto, consulte crear un
LineSegment.
Comando de línea horizontal
El comando de línea horizontal crea una línea horizontal entre el punto actual y la coordenada x especificada. La
sintaxis de este comando es: H x o h x.
En esta sintaxis, x es un double que representa la coordenada x del punto final de la línea.
H 90 es un ejemplo de un comando de línea horizontal válido.
Comando de línea vertical
El comando de línea vertical crea una línea vertical entre el punto actual y la coordenada y especificada. La sintaxis
de este comando es: V y o v y.
En esta sintaxis, y es un double que representa la coordenada y del punto final de la línea.
V 90 es un ejemplo de comando de línea vertical válido.
Comando de arco elíptico
El comando de arco elíptico crea un arco elíptico entre el punto actual y el punto final especificado. La sintaxis de
este comando es: A size rotationAngle isLargeArcFlag sweepDirectionFlag Endpoint or a size rotationAngle
isLargeArcFlag sweepDirectionFlag endPoint.
En esta sintaxis:
size es un que representa el radio x e y del arco.
Size
rotationAngle es un double que representa el giro de la elipse, en grados.
isLargeArcFlag debe establecerse en 1 si el ángulo del arco debe ser de 180 grados o superior; de lo contrario,
establézcalo en 0.
sweepDirectionFlag debe establecerse en 1 si el arco se dibuja en una dirección de ángulo positivo; de lo
contrario, establézcalo en 0.
endPoint es un Point en el que se dibuja el arco.

A 150,150 0 1,0 150,-150 es un ejemplo de un comando de arco elíptico válido.


Para obtener información sobre cómo crear un arco elíptico como un PathGeometry objeto, vea crear un objeto
ArcSegment.
Comando de curva Bézier cúbica
El comando de curva Bézier cúbica crea una curva Bézier cúbica entre el punto actual y el punto final especificado
utilizando los dos puntos de control especificados. La sintaxis de este comando es: C controlPoint1 controlPoint2
Endpoint o c controlPoint1 controlPoint2 endPoint.
En esta sintaxis:
controlPoint1 es un Point que representa el primer punto de control de la curva, que determina la tangente
inicial de la curva.
controlPoint2 es un Point que representa el segundo punto de control de la curva, que determina la tangente
final de la curva.
el punto de conexión es un Point que representa el punto en el que se dibuja la curva.

C 100,200 200,400 300,200 es un ejemplo de un comando de curva Bézier cúbica válido.


Para obtener información sobre cómo crear una curva Bézier cúbica como un PathGeometry objeto, consulte crear
un BezierSegment.
Comando de curva Bézier cuadrática
El comando de curva Bézier cuadrática crea una curva Bézier cuadrática entre el punto actual y el punto final
especificado utilizando el punto de control especificado. La sintaxis de este comando es: Q controlPoint Endpoint
o q controlPoint Endpoint.
En esta sintaxis:
controlPoint es un Point que representa el punto de control de la curva, que determina las tangentes inicial y
final de la curva.
el punto de conexión es un Point que representa el punto en el que se dibuja la curva.

Q 100,200 300,200 es un ejemplo de un comando de curva Bézier cuadrática válido.


Para obtener información sobre cómo crear una curva Bézier cuadrática como un PathGeometry objeto, vea crear
un QuadraticBezierSegment.
Comando de curva Bézier cúbica suavizada
El comando de curva Bézier cúbica suavizada crea una curva Bézier cúbica entre el punto actual y el punto final
especificado mediante el punto de control especificado. La sintaxis de este comando es: S controlPoint2 Endpoint
o s controlPoint2 Endpoint.
En esta sintaxis:
controlPoint2 es un Point que representa el segundo punto de control de la curva, que determina la tangente
final de la curva.
el punto de conexión es un Point que representa el punto en el que se dibuja la curva.
Se supone que el primer punto de control es el reflejo del segundo punto de control del comando anterior, con
respecto al punto actual. Si no hay ningún comando anterior, o si el comando anterior no era un comando de
curva Bézier cúbica o un comando de curva Bézier cúbica suavizada, se supone que el primer punto de control
tiene coincidencias con el punto actual.
S 100,200 200,300 es un ejemplo de un comando de curva Bézier cúbica suave válido.
Comando de curva Bézier cuadrática suavizada
El comando Smooth curva Bézier cuadrática crea una curva Bézier cuadrática entre el punto actual y el punto final
especificado mediante un punto de control. La sintaxis de este comando es: T extremo o t punto de conexión.
En esta sintaxis, el punto de conexión es un Point que representa el punto en el que se dibuja la curva.
El punto de control se supone que es el reflejo del punto de control del comando anterior en relación al punto
actual. Si no hay ningún comando anterior o si el comando anterior no era una curva de Bézier cuadrática o un
comando de curva Bézier cuadrática suavizada, se supone que el punto de control tiene coincidencias con el punto
actual.
T 100,30 es un ejemplo de un comando válido de curva Bézier cuadrática suavizada.

Comando de cierre
El comando cerrar finaliza la figura actual y crea una línea que conecta el punto actual con el punto inicial de la
figura. Por lo tanto, este comando crea una combinación de líneas entre el último segmento y el primer segmento
de la figura.
La sintaxis del comando cerrar es: Z o z .

Valores adicionales
En lugar de un valor numérico estándar, también puede usar los siguientes valores especiales que distinguen
mayúsculas de minúsculas:
Infinity representa double.PositiveInfinity .
-Infinity representa double.NegativeInfinity .
NaN representa double.NaN .

Además, también puede usar la notación científica sin distinción entre mayúsculas y minúsculas. Por lo tanto,
+1.e17 es un valor válido.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Formas: geometrías
:::no-loc(Xamarin.Forms)::: Formas: reglas de relleno
:::no-loc(Xamarin.Forms)::: Formas: transformaciones
de trazado
18/12/2020 • 26 minutes to read • Edit Online

Descargar el ejemplo
Transform Define cómo transformar un Path objeto de un espacio de coordenadas en otro. Cuando se aplica una
transformación a un Path objeto, cambia el modo en que el objeto se representa en la interfaz de usuario.
Las transformaciones se pueden clasificar en cuatro clasificaciones generales: giro, escala, sesgo y traslación. :::no-
loc(Xamarin.Forms)::: define una clase para cada una de estas clasificaciones de transformación:
RotateTransform , que gira un Path según un especificado Angle .
ScaleTransform , que escala un Path objeto según los ScaleX valores y especificados ScaleY .
SkewTransform , que sesga un Path objeto especificando AngleX y AngleY cantidades.
TranslateTransform , que mueve un Path objeto especificando X y Y cantidades.

:::no-loc(Xamarin.Forms)::: también proporciona las siguientes clases para crear transformaciones más complejas:
TransformGroup , que representa una transformación compuesta compuesta por varios objetos transform.
CompositeTransform , que aplica varias operaciones de transformación a un Path objeto.
MatrixTransform , que crea transformaciones personalizadas que no proporcionan las otras clases de
transformación.
Todas estas clases se derivan de la Transform clase, que define una Value propiedad de tipo Matrix , que
representa la transformación actual como un Matrix objeto. Esta propiedad está respaldada por un
BindableProperty objeto, lo que significa que puede ser el destino de los enlaces de datos y con estilo. Para
obtener más información sobre el Matrix struct, vea Transform Matrix.
Para aplicar una transformación a un Path , cree una clase de transformación y establézcala como el valor de la
Path.RenderTransform propiedad.

Transformación de giro
Una transformación girar gira un Path objeto en el sentido de las agujas del reloj sobre un punto especificado en
un sistema de coordenadas x-y 2D.
La RotateTransform clase, que deriva de la Transform clase, define las siguientes propiedades:
Angle , de tipo double , representa el ángulo, en grados, de la rotación en el sentido de las agujas del reloj. El
valor predeterminado de esta propiedad es 0,0.
CenterX , de tipo double , representa la coordenada x del punto central de rotación. El valor predeterminado
de esta propiedad es 0,0.
CenterY , de tipo double , representa la coordenada y del punto central de rotación. El valor predeterminado
de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Las CenterX CenterY propiedades y especifican el punto sobre el que Path se gira el objeto. Este punto central
se expresa en el espacio de coordenadas del objeto que se transforma. De forma predeterminada, la rotación se
aplica a (0,0), que es la esquina superior izquierda del Path objeto.
En el ejemplo siguiente se muestra cómo rotar un Path objeto:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<RotateTransform CenterX="0"
CenterY="0"
Angle="45" />
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se gira 45 grados sobre su esquina superior izquierda.

Transformación de escala
Una transformación de escala escala un Path objeto en el sistema de coordenadas x-y 2D.
La ScaleTransform clase, que deriva de la Transform clase, define las siguientes propiedades:
ScaleX , de tipo double , que representa el factor de escala del eje x. El valor predeterminado de esta
propiedad es 1,0.
ScaleY , de tipo double , que representa el factor de escala del eje y. El valor predeterminado de esta
propiedad es 1,0.
CenterX , de tipo double , que representa la coordenada x del punto central de esta transformación. El valor
predeterminado de esta propiedad es 0,0.
CenterY , de tipo double , que representa la coordenada y del punto central de esta transformación. El valor
predeterminado de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
El valor de ScaleX y ScaleY tiene un gran impacto en el escalado resultante:
Los valores entre 0 y 1 disminuyen el ancho y el alto del objeto escalado.
Los valores mayores que 1 aumentan el ancho y el alto del objeto escalado.
Los valores 1 indican que el objeto no se ha escalado.
Los valores negativos voltean el objeto de escala horizontal y verticalmente.
Los valores entre 0 y-1 voltean el objeto de escala y reducen su ancho y alto.
Los valores menores que-1 voltean el objeto y aumentan su ancho y alto.
Los valores de-1 voltean el objeto escalado, pero no cambian su tamaño horizontal o vertical.
Las CenterX CenterY propiedades y especifican el punto sobre el que Path se escala el objeto. Este punto central
se expresa en el espacio de coordenadas del objeto que se transforma. De forma predeterminada, el escalado se
aplica a (0,0), que es la esquina superior izquierda del Path objeto. Esto tiene el efecto de mover el Path objeto y
hacer que parezca mayor, porque al aplicar una transformación cambia el espacio de coordenadas en el que Path
reside el objeto.
En el ejemplo siguiente se muestra cómo escalar un Path objeto:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<ScaleTransform CenterX="0"
CenterY="0"
ScaleX="1.5"
ScaleY="1.5" />
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se escala hasta 1,5 veces el tamaño.

Sesgar transformación
Una transformación de sesgo sesga un Path objeto en el sistema de coordenadas x-y 2D, y es útil para crear la
ilusión de profundidad 3D en un objeto 2D.
La SkewTransform clase, que deriva de la Transform clase, define las siguientes propiedades:
AngleX , de tipo double , que representa el ángulo de sesgado del eje x, que se mide en grados en sentido
contrario a las agujas del reloj desde el eje y. El valor predeterminado de esta propiedad es 0,0.
AngleY , de tipo double , que representa el ángulo de sesgado del eje y, que se mide en grados en sentido
contrario a las agujas del reloj desde el eje x. El valor predeterminado de esta propiedad es 0,0.
CenterX , de tipo double , que representa la coordenada x del centro de la transformación. El valor
predeterminado de esta propiedad es 0,0.
CenterY , de tipo double , que representa la coordenada y del centro de la transformación. El valor
predeterminado de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Para predecir el efecto de una transformación de sesgo, tenga en cuenta que AngleX sesga los valores del eje x en
relación con el sistema de coordenadas original. Por lo tanto, para un AngleX de 30, el eje y gira 30 grados a
través del origen y sesga los valores en x por 30 grados desde ese origen. Del mismo modo, un AngleY de 30
sesga los valores y del Path objeto en 30 grados desde el origen.

NOTE
Para sesgar un Path objeto en su lugar, establezca las CenterX CenterY propiedades y en el punto central del objeto.

En el ejemplo siguiente se muestra cómo sesgar un Path objeto:


<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<SkewTransform CenterX="0"
CenterY="0"
AngleX="45"
AngleY="0" />
</Path.RenderTransform>
</Path>

En este ejemplo, se aplica un sesgo horizontal de 45 grados al Path objeto, desde un punto central de (0,0).

Trasladar transformación
Una transformación traducir mueve un objeto en el sistema de coordenadas x-y 2D.
La TranslateTransform clase, que deriva de la Transform clase, define las siguientes propiedades:
X , de tipo double , que representa la distancia que se va a desplace a lo largo del eje x. El valor
predeterminado de esta propiedad es 0,0.
Y , de tipo double , que representa la distancia que se va a desplace a lo largo del eje y. El valor
predeterminado de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
X Los valores negativos mueven un objeto a la izquierda, mientras que los valores positivos mueven un objeto a
la derecha. Y Los valores negativos mueven un objeto hacia arriba, mientras que los valores positivos mueven un
objeto hacia abajo.
En el ejemplo siguiente se muestra cómo trasladar un Path objeto:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<TranslateTransform X="50"
Y="50" />
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se mueve 50 unidades independientes del dispositivo a la derecha y las unidades
independientes del dispositivo 50.

Varias transformaciones
:::no-loc(Xamarin.Forms)::: tiene dos clases que admiten la aplicación de varias transformaciones a un Path objeto.
Estos son TransformGroup , y CompositeTransform . Una TransformGroup realiza transformaciones en cualquier
orden deseado, mientras que CompositeTransform realiza transformaciones en un orden concreto.
Transformar grupos
Los grupos de transformación representan transformaciones compuestas compuestas de varios Transform
objetos.
La clase, que deriva de la Transform clase, define una Children propiedad, de tipo
TransformGroup
TransformCollection , que representa una colección de Transform objetos. Esta propiedad está respaldada por un
BindableProperty objeto, lo que significa que puede ser el destino de los enlaces de datos y con estilo.

El orden de las transformaciones es importante en una transformación compuesta que utiliza la TransformGroup
clase. Por ejemplo, si primero se gira, después se realiza la escala y después se traduce, se obtiene un resultado
diferente que si primero se traslada, después se gira y luego se escala. Un orden de razón es importante es que las
transformaciones como la rotación y el escalado se llevan a cabo en relación con el origen del sistema de
coordenadas. El escalado de un objeto que está centrado en el origen genera un resultado diferente para escalar
un objeto que se ha quitado del origen. Del mismo modo, la rotación de un objeto que está centrado en el origen
genera un resultado diferente que la rotación de un objeto que se ha quitado del origen.
En el ejemplo siguiente se muestra cómo realizar una transformación compuesta mediante la TransformGroup
clase:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1.5"
ScaleY="1.5" />
<RotateTransform Angle="45" />
</TransformGroup>
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se escala hasta 1,5 veces su tamaño y, a continuación, se gira en 45 grados.

Transformaciones compuestas
Una transformación compuesta aplica varias transformaciones a un objeto.
La CompositeTransform clase, que deriva de la Transform clase, define las siguientes propiedades:
CenterX , de tipo double , que representa la coordenada x del punto central de esta transformación. El valor
predeterminado de esta propiedad es 0,0.
CenterY , de tipo double , que representa la coordenada y del punto central de esta transformación. El valor
predeterminado de esta propiedad es 0,0.
ScaleX , de tipo double , que representa el factor de escala del eje x. El valor predeterminado de esta
propiedad es 1,0.
ScaleY , de tipo double , que representa el factor de escala del eje y. El valor predeterminado de esta
propiedad es 1,0.
SkewX , de tipo double , que representa el ángulo de sesgado del eje x, que se mide en grados en sentido
contrario a las agujas del reloj desde el eje y. El valor predeterminado de esta propiedad es 0,0.
SkewY , de tipo double , que representa el ángulo de sesgado del eje y, que se mide en grados en sentido
contrario a las agujas del reloj desde el eje x. El valor predeterminado de esta propiedad es 0,0.
Rotation , de tipo double , representa el ángulo, en grados, de la rotación en el sentido de las agujas del reloj.
El valor predeterminado de esta propiedad es 0,0.
TranslateX , de tipo double , que representa la distancia que se va a desplace a lo largo del eje x. El valor
predeterminado de esta propiedad es 0,0.
TranslateY , de tipo double , que representa la distancia que se va a desplace a lo largo del eje y. El valor
predeterminado de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Un CompositeTransform aplica las transformaciones en este orden:
1. Escala ( ScaleX y ScaleY ).
2. Sesgar ( SkewX y SkewY ).
3. Rotate ( Rotation ).
4. Translate ( TranslateX , TranslateY ).
Si desea aplicar varias transformaciones a un objeto en un orden diferente, debe crear TransformGroup e insertar
las transformaciones en el orden previsto.

IMPORTANT
CompositeTransform Usa los mismos puntos centrales, CenterX y CenterY , para todas las transformaciones. Si desea
especificar diferentes puntos centrales por transformación, use TransformGroup ,

En el ejemplo siguiente se muestra cómo realizar una transformación compuesta mediante la CompositeTransform
clase:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<CompositeTransform ScaleX="1.5"
ScaleY="1.5"
Rotation="45"
TranslateX="50"
TranslateY="50" />
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se escala hasta 1,5 veces su tamaño, girado en 45 grados y, a continuación, lo
traducen las unidades independientes del dispositivo 50.

Matriz de transformación
Una transformación se puede describir en términos de una matriz de transformación afín de 3x3 que realiza
transformaciones en el espacio 2D. Esta matriz de 3x3 se representa mediante la Matrix estructura, que es una
colección de tres filas y tres columnas de double valores.
La Matrix estructura define las siguientes propiedades:
Determinant , de tipo double , que obtiene el factor determinante de la matriz.
HasInverse , de tipo bool , que indica si la matriz se va a invertir.
Identity , de tipo Matrix , que obtiene una matriz de identidad.
HasIdentity , de tipo bool , que indica si la matriz es una matriz de identidad.
M11 , de tipo double , que representa el valor de la primera fila y la primera columna de la matriz.
M12 , de tipo double , que representa el valor de la primera fila y la segunda columna de la matriz.
M21 , de tipo double , que representa el valor de la segunda fila y la primera columna de la matriz.
M22 , de tipo double , que representa el valor de la segunda fila y la segunda columna de la matriz.
OffsetX , de tipo double , que representa el valor de la tercera fila y la primera columna de la matriz.
OffsetY , de tipo double , que representa el valor de la tercera fila y la segunda columna de la matriz.

Las OffsetX OffsetY propiedades y se denominan así porque especifican la cantidad de trasladar el espacio de
coordenadas a lo largo del eje x, y del eje y, respectivamente.
Además, el Matrix struct expone una serie de métodos que se pueden usar para manipular los valores de la
matriz, incluidos Append , Invert , Multiply Prepend y muchos más.
En la tabla siguiente se muestra la estructura de una :::no-loc(Xamarin.Forms)::: matriz:
M11
M12
0,0
M21
M22
0,0
OffsetX
OffsetY
1.0

NOTE
Una matriz de transformación afín tiene su columna final igual a (0, 0, 1), por lo que solo es necesario especificar los
miembros de las dos primeras columnas.

Al manipular los valores de la matriz, puede girar, escalar, sesgar y trasladar Path objetos. Por ejemplo, si cambia
el OffsetX valor a 100, puede utilizarlo para trasladar un Path objeto 100 unidades independientes del
dispositivo a lo largo del eje x. Si cambia el M22 valor a 3, puede usarlo para expandir un Path objeto hasta tres
veces su alto actual. Si cambia ambos valores, mueva el Path objeto 100 unidades independientes del dispositivo
a lo largo del eje x y estire el alto con un factor de 3. Además, las matrices de transformación afines se pueden
multiplicar para formar cualquier número de transformaciones lineales, como la rotación y el sesgo, seguida de la
traslación.

Transformaciones personalizadas
La MatrixTransform clase, que se deriva de la Transform clase, define una Matrix propiedad, de tipo Matrix , que
representa la matriz que define la transformación. Esta propiedad está respaldada por un BindableProperty
objeto, lo que significa que puede ser el destino de los enlaces de datos y con estilo.
Cualquier transformación que puede describir con un TranslateTransform objeto, ScaleTransform ,
RotateTransform o SkewTransform se puede describir igualmente mediante un MatrixTransform . Sin embargo,
TranslateTransform las ScaleTransform clases,, RotateTransform y SkewTransform son más fáciles de
conceptualizar que establecer los componentes de vector en un Matrix . Por lo tanto, la MatrixTransform clase se
utiliza normalmente para crear transformaciones personalizadas que no se proporcionan mediante las
RotateTransform ScaleTransform clases,, SkewTransform o TranslateTransform .

En el ejemplo siguiente se muestra cómo transformar un Path objeto mediante MatrixTransform :

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<MatrixTransform>
<MatrixTransform.Matrix>
<!-- M11 stretches, M12 skews -->
<Matrix OffsetX="10"
OffsetY="100"
M11="1.5"
M12="1" />
</MatrixTransform.Matrix>
</MatrixTransform>
</Path.RenderTransform>
</Path>

En este ejemplo, el Path objeto se ajusta, se sesga y se desplaza en las dimensiones X e y.


Como alternativa, se puede escribir en una forma simplificada que usa un convertidor de tipos integrado en :::no-
loc(Xamarin.Forms)::: :

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z">
<Path.RenderTransform>
<MatrixTransform Matrix="1.5,1,0,1,10,100" />
</Path.RenderTransform>
</Path>

En este ejemplo, la Matrix propiedad se especifica como una cadena delimitada por comas que consta de seis
miembros M11 :, M12 , M21 , M22 , OffsetX , OffsetY . Aunque los miembros están delimitados por comas en
este ejemplo, también se pueden delimitar con uno o varios espacios.
Además, el ejemplo anterior se puede simplificar aún más si se especifican los mismos seis miembros como el
valor de la RenderTransform propiedad:

<Path Stroke="Black"
StrokeThickness="1"
Aspect="Uniform"
HorizontalOptions="Center"
RenderTransform="1.5 1 0 1 10 100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985 13.908992,30.109983z" />

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: Polígono
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
La Polygon clase se deriva de la Shape clase y se puede usar para dibujar polígonos, que son una serie de líneas
conectadas que forman formas cerradas. Para obtener información sobre las propiedades que la Polygon clase
hereda de la Shape clase, vea :::no-loc(Xamarin.Forms)::: formas.
Polygon define las siguientes propiedades:
Points , de tipo PointCollection , que es una colección de Point estructuras que describen los puntos de
vértice del polígono.
FillRule , de tipo FillRule , que especifica cómo se determina el relleno interior de la forma. El valor
predeterminado de esta propiedad es FillRule.EvenOdd .
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
El PointsCollection tipo es ObservableCollection de Point objetos. La Point estructura define X Y las
propiedades y, de tipo double , que representan un par de coordenadas x e y en el espacio 2D. Por lo tanto, la
Points propiedad debe establecerse en una lista de pares de coordenada x y de coordenada y que describan los
puntos de vértice del polígono, delimitados por una sola coma y/o uno o varios espacios. Por ejemplo, "40, 10 70,
80" y "40 10, 70 80" son válidos.
Para obtener más información acerca de la FillRule enumeración, consulte :::no-loc(Xamarin.Forms)::: formas:
rellenar reglas.

Crear un polígono
Para dibujar un polígono, cree un Polygon objeto y establezca su Points propiedad en los vértices de una forma.
Se dibuja automáticamente una línea que conecta los puntos primero y último. Para pintar el interior del polígono,
establezca su Fill propiedad en Color . Para dar un contorno al polígono, establezca su Stroke propiedad en
Color . La StrokeThickness propiedad especifica el grosor del contorno del polígono.

En el siguiente ejemplo de XAML se muestra cómo dibujar un polígono relleno:

<Polygon Points="40,10 70,80 10,50"


Fill="AliceBlue"
Stroke="Green"
StrokeThickness="5" />

En este ejemplo, se dibuja un polígono relleno que representa un triángulo:


En el siguiente ejemplo de XAML se muestra cómo dibujar un polígono discontinuo:

<Polygon Points="40,10 70,80 10,50"


Fill="AliceBlue"
Stroke="Green"
StrokeThickness="5"
StrokeDashArray="1,1"
StrokeDashOffset="6" />

En este ejemplo, el contorno de polígono es discontinuo:

Para obtener más información sobre cómo dibujar un polígono discontinuo, vea dibujar formas con guiones.
En el ejemplo de XAML siguiente se muestra un polígono que usa la regla de relleno predeterminada:

<Polygon Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192, 150 200 144 48"
Fill="Blue"
Stroke="Red"
StrokeThickness="3" />

En este ejemplo, el comportamiento de relleno de cada polígono se determina mediante la EvenOdd regla de
relleno.

En el ejemplo de XAML siguiente se muestra un polígono que usa la Nonzero regla de relleno:

<Polygon Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192, 150 200 144 48"
Fill="Black"
FillRule="Nonzero"
Stroke="Yellow"
StrokeThickness="3" />
En este ejemplo, el comportamiento de relleno de cada polígono se determina mediante la Nonzero regla de
relleno.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: reglas de relleno
:::no-loc(Xamarin.Forms)::: Formas: Polyline
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
La Polyline clase se deriva de la Shape clase y se puede usar para dibujar una serie de líneas rectas conectadas.
Una polilínea es similar a un polígono, excepto que el último punto de una polilínea no está conectado al primer
punto. Para obtener información sobre las propiedades que la Polyline clase hereda de la Shape clase, vea :::no-
loc(Xamarin.Forms)::: formas.
Polyline define las siguientes propiedades:
Points , de tipo PointCollection , que es una colección de Point estructuras que describen los puntos de
vértice de la polilínea.
FillRule , de tipo FillRule , que especifica cómo se combinan las áreas de intersección de la polilínea. El valor
predeterminado de esta propiedad es FillRule.EvenOdd .
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
El PointsCollection tipo es ObservableCollection de Point objetos. La Point estructura define X Y las
propiedades y, de tipo double , que representan un par de coordenadas x e y en el espacio 2D. Por lo tanto, la
Points propiedad debe establecerse en una lista de pares de coordenada x y de coordenada y que describan los
puntos de vértice de la polilínea, delimitados por una sola coma y/o uno o varios espacios. Por ejemplo, "40, 10
70, 80" y "40 10, 70 80" son válidos.
Para obtener más información acerca de la FillRule enumeración, consulte :::no-loc(Xamarin.Forms)::: formas:
rellenar reglas.

Crear una polilínea


Para dibujar una polilínea, cree un Polyline objeto y establezca su Points propiedad en los vértices de una
forma. Para dar a la polilínea un contorno, establezca su Stroke propiedad en Color . La StrokeThickness
propiedad especifica el grosor del contorno de polilínea.

IMPORTANT
Si establece la Fill propiedad de un Polyline en Color , se pinta el espacio interior de la polilínea, incluso si el punto
inicial y el final no forman una intersección.

En el siguiente ejemplo de XAML se muestra cómo dibujar una polilínea:

<Polyline Points="0,0 10,30, 15,0 18,60 23,30 35,30 40,0 43,60 48,30 100,30"
Stroke="Red"
StrokeThickness="1" />
En este ejemplo, se dibuja una polilínea roja:

En el siguiente ejemplo de XAML se muestra cómo dibujar una polilínea discontinua:

<Polyline Points="0,0 10,30, 15,0 18,60 23,30 35,30 40,0 43,60 48,30 100,30"
Stroke="Red"
StrokeThickness="2"
StrokeDashArray="1,1"
StrokeDashOffset="6" />

En este ejemplo, la polilínea es discontinua:

Para obtener más información sobre cómo dibujar una polilínea discontinua, vea dibujar formas con guiones.
En el siguiente ejemplo de XAML se muestra una polilínea que utiliza la regla de relleno predeterminada:

<Polyline Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192, 150 200 144 48"
Fill="Blue"
Stroke="Red"
StrokeThickness="3" />

En este ejemplo, el comportamiento de relleno de la polilínea se determina mediante la EvenOdd regla de relleno.

En el siguiente ejemplo de XAML se muestra una polilínea que utiliza la Nonzero regla de relleno:

<Polyline Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192, 150 200 144 48"
Fill="Black"
FillRule="Nonzero"
Stroke="Yellow"
StrokeThickness="3" />
En este ejemplo, el comportamiento de relleno de la polilínea se determina mediante la Nonzero regla de relleno.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: Formas: reglas de relleno
:::no-loc(Xamarin.Forms)::: Formas: rectángulo
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
La Rectangle clase se deriva de la Shape clase y se puede usar para dibujar rectángulos y cuadrados. Para
obtener información sobre las propiedades que la Rectangle clase hereda de la Shape clase, vea :::no-
loc(Xamarin.Forms)::: formas.
Rectangle define las siguientes propiedades:
RadiusX , de tipo double , que es el radio del eje x que se usa para redondear las esquinas del rectángulo. El
valor predeterminado de esta propiedad es 0,0.
RadiusY , de tipo double , que es el radio del eje y que se usa para redondear las esquinas del rectángulo. El
valor predeterminado de esta propiedad es 0,0.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
La Rectangle clase establece la Aspect propiedad, heredada de la Shape clase, en Stretch.Fill . Para obtener
más información sobre la Aspect propiedad, consulte Stretch Shapes.

Crear un rectángulo
Para dibujar un rectángulo, cree un Rectangle objeto y establezca sus WidthRequest HeightRequest propiedades
y. Para pintar el interior del rectángulo, establezca su Fill propiedad en Color . Para dar al rectángulo un
contorno, establezca su Stroke propiedad en Color . La StrokeThickness propiedad especifica el grosor del
contorno del rectángulo.
Para dar al rectángulo esquinas redondeadas, establezca sus RadiusX RadiusY propiedades y. Estas propiedades
establecen los radios de los ejes x e y que se usan para redondear las esquinas del rectángulo.
Para dibujar un cuadrado, haga que WidthRequest las HeightRequest propiedades y del Rectangle objeto sean
iguales.
En el siguiente ejemplo de XAML se muestra cómo dibujar un rectángulo relleno:

<Rectangle Fill="Red"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />

En este ejemplo, se dibuja un rectángulo relleno rojo con las dimensiones 150x50 (unidades independientes del
dispositivo):
En el siguiente ejemplo de XAML se muestra cómo dibujar un rectángulo relleno con esquinas redondeadas:

<Rectangle Fill="Blue"
Stroke="Black"
StrokeThickness="3"
RadiusX="50"
RadiusY="10"
WidthRequest="200"
HeightRequest="100"
HorizontalOptions="Start" />

En este ejemplo, se dibuja un rectángulo relleno azul con esquinas redondeadas:

Para obtener información sobre cómo dibujar un rectángulo discontinuo, vea dibujar formas con guiones.

Vínculos relacionados
ShapeDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Colocar
:::no-loc(Xamarin.Forms)::: WebView
18/12/2020 • 26 minutes to read • Edit Online

Descargar el ejemplo
WebView es una vista para mostrar contenido web y HTML en la aplicación:

Contenido
WebView admite los siguientes tipos de contenido:
HTML & – WebView de sitios web CSS es totalmente compatible con sitios web escritos con HTML & CSS,
incluida la compatibilidad con JavaScript.
Documentos – dado que WebView se implementa mediante componentes nativos en cada plataforma,
WebView es capaz de mostrar documentos en los formatos admitidos por la plataforma subyacente.
Las cadenas HTML – WebView pueden mostrar cadenas HTML de la memoria.
WebView – de archivos locales puede presentar cualquiera de los tipos de contenido anteriores incrustados en
la aplicación.

NOTE
WebView en Windows no es compatible con Silverlight, flash ni con ningún control ActiveX, aunque sea compatible con
Internet Explorer en esa plataforma.
Websites
Para mostrar un sitio web desde Internet, establezca la WebView Source propiedad de en una dirección URL de
cadena:

var browser = new WebView


{
Source = "http://xamarin.com"
};

NOTE
Las direcciones URL deben estar formadas por completo con el protocolo especificado (es decir, deben tener "http://" o
"https://" antepuesto).

iOS y ATS
Desde la versión 9, iOS solo permitirá que la aplicación se comunique con los servidores que implementan la
seguridad de prácticas recomendadas de forma predeterminada. Los valores se deben establecer en Info.plist
para permitir la comunicación con servidores no seguros.

NOTE
Si la aplicación requiere una conexión a un sitio web inseguro, siempre debe escribir el dominio como una excepción
mediante NSExceptionDomains en lugar de desactivar completamente el ATS usando NSAllowsArbitraryLoads .
NSAllowsArbitraryLoads solo se debe usar en situaciones extremas de emergencia.

A continuación se muestra cómo habilitar un dominio específico (en este caso, xamarin.com) para omitir los
requisitos de ATS:

<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>xamarin.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
...
</key>

Se recomienda habilitar solo algunos dominios para omitir ATS, lo que le permite usar sitios de confianza mientras
benefician de la seguridad adicional en dominios que no son de confianza. A continuación se muestra el método
menos seguro para deshabilitar ATS para la aplicación:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads </key>
<true/>
</dict>
...
</key>

Consulte seguridad de transporte de aplicaciones para obtener más información sobre esta nueva característica
en iOS 9.
Cadenas HTML
Si desea presentar una cadena de HTML definida dinámicamente en el código, deberá crear una instancia de
HtmlWebViewSource :

var browser = new WebView();


var htmlSource = new HtmlWebViewSource();
htmlSource.Html = @"<html><body>
<h1>:::no-loc(Xamarin.Forms):::</h1>
<p>Welcome to WebView.</p>
</body></html>";
browser.Source = htmlSource;

En el código anterior, @ se usa para marcar el HTML como un literal de cadena textual, lo que significa que la
mayoría de los caracteres de escape se omiten.
NOTE
Puede que sea necesario establecer las WidthRequest propiedades y HeightRequest de WebView para ver el contenido
HTML, dependiendo del diseño del que WebView sea un elemento secundario de. Por ejemplo, esto es necesario en
StackLayout .

Contenido HTML local


WebView puede mostrar contenido de HTML, CSS y JavaScript incrustado dentro de la aplicación. Por ejemplo:

<html>
<head>
<title>Xamarin Forms</title>
</head>
<body>
<h1>:::no-loc(Xamarin.Forms):::</h1>
<p>This is an iOS web page.</p>
<img src="XamarinLogo.png" />
</body>
</html>

CSS

html,body {
margin:0;
padding:10;
}
body,p,h1 {
font-family: Chalkduster;
}

Tenga en cuenta que las fuentes especificadas en la CSS anterior deberán personalizarse para cada plataforma, ya
que no todas las plataformas tienen las mismas fuentes.
Para mostrar el contenido local mediante WebView , deberá abrir el archivo HTML como cualquier otro y, a
continuación, cargar el contenido como una cadena en la Html propiedad de HtmlWebViewSource . Para obtener
más información sobre cómo abrir archivos, vea trabajar con archivos.
Las siguientes capturas de pantallas muestran el resultado de mostrar el contenido local en cada plataforma:
Aunque se ha cargado la primera página, el WebView no tiene ningún conocimiento de dónde procede el código
HTML. Esto es un problema cuando se trabaja con páginas que hacen referencia a recursos locales. Algunos
ejemplos de Cuándo se pueden producir cuando las páginas locales se vinculan entre sí, una página utiliza un
archivo de JavaScript independiente o una página se vincula a una hoja de estilos CSS.
Para solucionar este problemas, debe indicar WebView dónde encontrar los archivos en el sistema de archivos.
Para ello, establezca la BaseUrl propiedad en la HtmlWebViewSource utilizada por WebView .
Dado que el sistema de archivos de cada uno de los sistemas operativos es diferente, debe determinar esa
dirección URL en cada plataforma. :::no-loc(Xamarin.Forms)::: expone el DependencyService para resolver las
dependencias en tiempo de ejecución en cada plataforma.
Para usar DependencyService , defina primero una interfaz que se pueda implementar en cada plataforma:

public interface IBaseUrl { string Get(); }

Tenga en cuenta que hasta que la interfaz se implementa en cada plataforma, la aplicación no se ejecutará. En el
proyecto común, asegúrese de que no se olvide de establecer BaseUrl mediante DependencyService :

var source = new HtmlWebViewSource();


source.BaseUrl = DependencyService.Get<IBaseUrl>().Get();

A continuación, se deben proporcionar las implementaciones de la interfaz para cada plataforma.


iOS
En iOS, el contenido web debe estar ubicado en el directorio raíz del proyecto o en el directorio de recursos con
la acción de compilación BundleResource , como se muestra a continuación:
Visual Studio
Visual Studio para Mac

BaseUrl Debe establecerse en la ruta de acceso del paquete principal:

[assembly: Dependency (typeof (BaseUrl_iOS))]


namespace WorkingWithWebview.iOS
{
public class BaseUrl_iOS : IBaseUrl
{
public string Get()
{
return NSBundle.MainBundle.BundlePath;
}
}
}

Android
En Android, coloque HTML, CSS e imágenes en la carpeta assets con la acción de compilación AndroidAsset como
se muestra a continuación:
Visual Studio
Visual Studio para Mac
En Android, BaseUrl debe establecerse en "file:///android_asset/" :

[assembly: Dependency (typeof(BaseUrl_Android))]


namespace WorkingWithWebview.Android
{
public class BaseUrl_Android : IBaseUrl
{
public string Get()
{
return "file:///android_asset/";
}
}
}

En Android, también se puede tener acceso a los archivos de la carpeta assets a través del contexto actual de
Android, que se expone mediante la MainActivity.Instance propiedad:

var assetManager = MainActivity.Instance.Assets;


using (var streamReader = new StreamReader (assetManager.Open ("local.html")))
{
var html = streamReader.ReadToEnd ();
}

Plataforma universal de Windows


En los proyectos de Plataforma universal de Windows (UWP), coloque HTML, CSS e imágenes en la raíz del
proyecto con la acción de compilación establecida en contenido.
BaseUrl Debe establecerse en "ms-appx-web:///" :

[assembly: Dependency(typeof(BaseUrl))]
namespace WorkingWithWebview.UWP
{
public class BaseUrl : IBaseUrl
{
public string Get()
{
return "ms-appx-web:///";
}
}
}

Navegación
WebView admite la navegación a través de varios métodos y propiedades que pone a su disposición:
GoFor ward () – Si CanGoForward es true, la llamada a GoForward navega hacia delante hasta la siguiente
página visitada.
GoBack () – Si CanGoBack es true, al llamar GoBack a se navegará a la última página visitada.
CanGoBack – true si hay páginas a las que navegar de nuevo, false si el explorador está en la dirección
URL de inicio.
CanGoFor ward – true si el usuario ha navegado hacia atrás y puede avanzar a una página que ya se ha
visitado.
Dentro de las páginas, no WebView admite movimientos multitáctiles. Es importante asegurarse de que el
contenido esté optimizado para dispositivos móviles y aparezca sin necesidad de hacer zoom.
Es habitual que las aplicaciones muestren un vínculo dentro de un WebView , en lugar del explorador del
dispositivo. En esas situaciones, es útil permitir la navegación normal, pero cuando el usuario se vuelve a utilizar
mientras están en el vínculo de inicio, la aplicación debe volver a la vista normal de la aplicación.
Use los métodos y las propiedades de navegación integrados para habilitar este escenario.
Empiece por crear la página para la vista de explorador:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WebViewSample.InAppBrowserXaml"
Title="Browser">
<StackLayout Margin="20">
<StackLayout Orientation="Horizontal">
<Button Text="Back" HorizontalOptions="StartAndExpand" Clicked="OnBackButtonClicked" />
<Button Text="Forward" HorizontalOptions="EndAndExpand" Clicked="OnForwardButtonClicked" />
</StackLayout>
<!-- WebView needs to be given height and width request within layouts to render. -->
<WebView x:Name="webView" WidthRequest="1000" HeightRequest="1000" />
</StackLayout>
</ContentPage>

En el código subyacente:
public partial class InAppBrowserXaml : ContentPage
{
public InAppBrowserXaml(string URL)
{
InitializeComponent();
webView.Source = URL;
}

async void OnBackButtonClicked(object sender, EventArgs e)


{
if (webView.CanGoBack)
{
webView.GoBack();
}
else
{
await Navigation.PopAsync();
}
}

void OnForwardButtonClicked(object sender, EventArgs e)


{
if (webView.CanGoForward)
{
webView.GoForward();
}
}
}

Eso es todo.

Eventos
WebView genera los siguientes eventos para ayudarle a responder a los cambios en el estado:
Navigating : evento que se genera cuando la vista Web comienza a cargar una nueva página.
Navigated : evento que se genera cuando se carga la página y se detiene la navegación.
ReloadRequested : evento que se genera cuando se realiza una solicitud para volver a cargar el contenido
actual.
El WebNavigatingEventArgs objeto que acompaña al Navigating evento tiene cuatro propiedades:
Cancel : indica si se debe cancelar o no la navegación.
NavigationEvent : evento de navegación que se ha producido.
Source : el elemento que llevó a cabo la navegación.
Url : el destino de navegación.

El WebNavigatedEventArgs objeto que acompaña al Navigated evento tiene cuatro propiedades:


NavigationEvent : evento de navegación que se ha producido.
Result : describe el resultado de la navegación mediante un WebNavigationResult miembro de enumeración.
Los valores válidos son Cancel , Failure , Success y Timeout .
Source : el elemento que llevó a cabo la navegación.
Url : el destino de navegación.

Si prevé usar páginas web que tardan mucho tiempo en cargarse, considere la posibilidad de usar Navigating los
Navigated eventos y para implementar un indicador de estado. Por ejemplo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WebViewSample.LoadingLabelXaml"
Title="Loading Demo">
<StackLayout>
<!--Loading label should not render by default.-->
<Label x:Name="labelLoading" Text="Loading..." IsVisible="false" />
<WebView HeightRequest="1000" WidthRequest="1000" Source="http://www.xamarin.com"
Navigated="webviewNavigated" Navigating="webviewNavigating" />
</StackLayout>
</ContentPage>

Los dos controladores de eventos:

void webviewNavigating(object sender, WebNavigatingEventArgs e)


{
labelLoading.IsVisible = true;
}

void webviewNavigated(object sender, WebNavigatedEventArgs e)


{
labelLoading.IsVisible = false;
}

Esto da como resultado el siguiente resultado (cargando):


Finalizó la carga:
Recarga de contenido
WebView tiene un Reload método que se puede usar para volver a cargar el contenido actual:

var webView = new WebView();


...
webView.Reload();

Cuando Reload se invoca el método ReloadRequested , se desencadena el evento, lo que indica que se ha
realizado una solicitud para recargar el contenido actual.

Rendimiento
Los exploradores Web más populares adoptan tecnologías como la representación acelerada de hardware y la
compilación de JavaScript. Antes de :::no-loc(Xamarin.Forms)::: 4,4, la :::no-loc(Xamarin.Forms)::: WebView clase
implementaba en iOS UIWebView . Sin embargo, muchas de estas tecnologías no estaban disponibles en esta
implementación. Por lo tanto, desde :::no-loc(Xamarin.Forms)::: 4,4, :::no-loc(Xamarin.Forms)::: WebView se
implementa en iOS mediante la WkWebView clase, que admite una exploración más rápida.

NOTE
En iOS, WkWebViewRenderer tiene una sobrecarga de constructor que acepta un WkWebViewConfiguration argumento.
Esto permite configurar el representador durante la creación.

Una aplicación puede volver a usar la clase de iOS UIWebView para implementar :::no-loc(Xamarin.Forms):::
WebView , por motivos de compatibilidad. Esto se puede lograr agregando el código siguiente al archivo
AssemblyInfo.CS en el proyecto de la plataforma iOS para la aplicación:

// Opt-in to using UIWebView instead of WkWebView.


[assembly: ExportRenderer(typeof(:::no-loc(Xamarin.Forms):::.WebView), typeof(:::no-
loc(Xamarin.Forms):::.Platform.iOS.WebViewRenderer))]

WebView de forma predeterminada, en Android es tan rápido como el explorador integrado.


La WebView de UWP usa el motor de representación de Microsoft Edge. Los dispositivos de escritorio y tableta
deben ver el mismo rendimiento que usar el explorador Edge.

Permisos
Para WebView que funcione, debe asegurarse de que los permisos se establecen para cada plataforma. Tenga en
cuenta que en algunas plataformas, WebView funcionará en modo de depuración, pero no cuando se compile para
la versión. Esto se debe a que algunos permisos, como los de acceso a Internet en Android, se establecen de forma
predeterminada en Visual Studio para Mac en modo de depuración.
UWP – requiere la capacidad de Internet (servidor de & de cliente) al mostrar el contenido de la red.
Android – solo se requiere INTERNET cuando se muestra el contenido de la red. El contenido local no requiere
ningún permiso especial.
iOS – no requiere ningún permiso especial.

Layout
A diferencia de la mayoría de las demás :::no-loc(Xamarin.Forms)::: vistas, WebView requiere que HeightRequest y
WidthRequest estén especificados cuando se incluyen en StackLayout o RelativeLayout. Si no se especifican esas
propiedades, WebView no se representará.
En los siguientes ejemplos se muestran los diseños que dan como resultado el trabajo, la representación de
WebView s:

StackLayout con WidthRequest & HeightRequest:

<StackLayout>
<Label Text="test" />
<WebView Source="http://www.xamarin.com/"
HeightRequest="1000"
WidthRequest="1000" />
</StackLayout>

RelativeLayout con WidthRequest & HeightRequest:

<RelativeLayout>
<Label Text="test"
RelativeLayout.XConstraint= "{ConstraintExpression
Type=Constant, Constant=10}"
RelativeLayout.YConstraint= "{ConstraintExpression
Type=Constant, Constant=20}" />
<WebView Source="http://www.xamarin.com/"
RelativeLayout.XConstraint="{ConstraintExpression Type=Constant,
Constant=10}"
RelativeLayout.YConstraint="{ConstraintExpression Type=Constant,
Constant=50}"
WidthRequest="1000" HeightRequest="1000" />
</RelativeLayout>

AbsoluteLayout sin WidthRequest & HeightRequest:

<AbsoluteLayout>
<Label Text="test" AbsoluteLayout.LayoutBounds="0,0,100,100" />
<WebView Source="http://www.xamarin.com/"
AbsoluteLayout.LayoutBounds="0,150,500,500" />
</AbsoluteLayout>

Grid sin WidthRequest & HeightRequest. Grid es uno de los pocos diseños que no requieren la especificación de
alto y ancho solicitados.:

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="test" Grid.Row="0" />
<WebView Source="http://www.xamarin.com/" Grid.Row="1" />
</Grid>

Invocar JavaScript
WebView incluye la capacidad de invocar una función de JavaScript desde C# y devolver los resultados al código
C# que realiza la llamada. Esto se logra con el WebView.EvaluateJavaScriptAsync método, que se muestra en el
ejemplo siguiente desde el ejemplo WebView :
var numberEntry = new Entry { Text = "5" };
var resultLabel = new Label();
var webView = new WebView();
...

int number = int.Parse(numberEntry.Text);


string result = await webView.EvaluateJavaScriptAsync($"factorial({number})");
resultLabel.Text = $"Factorial of {number} is {result}.";

El WebView.EvaluateJavaScriptAsync método evalúa el JavaScript que se especifica como argumento y devuelve


cualquier resultado como string . En este ejemplo, factorial se invoca la función de JavaScript, que devuelve el
factorial de number como resultado. Esta función de JavaScript se define en el archivo HTML local que WebView
carga y se muestra en el ejemplo siguiente:

<html>
<body>
<script type="text/javascript">
function factorial(num) {
if (num === 0 || num === 1)
return 1;
for (var i = num - 1; i >= 1; i--) {
num *= i;
}
return num;
}
</script>
</body>
</html>

Cookies
Las cookies se pueden establecer en un WebView , que luego se envían con la solicitud Web a la dirección URL
especificada. Esto se logra agregando Cookie objetos a un objeto CookieContainer , que se establece como el
valor de la WebView.Cookies propiedad enlazable. El código siguiente muestra un ejemplo de esto:

using System.Net;
using :::no-loc(Xamarin.Forms):::;
// ...

CookieContainer cookieContainer = new CookieContainer();


Uri uri = new Uri("https://dotnet.microsoft.com/apps/xamarin", UriKind.RelativeOrAbsolute);

Cookie cookie = new Cookie


{
Name = "XamarinCookie",
Expires = DateTime.Now.AddDays(1),
Value = "My cookie",
Domain = uri.Host,
Path = "/"
};
cookieContainer.Add(uri, cookie);
webView.Cookies = cookieContainer;
webView.Source = new UrlWebViewSource { Url = uri.ToString() };

En este ejemplo, se agrega un único al CookieContainer objeto, que se establece como el valor de la
Cookie
WebView.Cookies propiedad. Cuando WebView envía una solicitud Web a la dirección URL especificada, la cookie se
envía con la solicitud.
UIWebView desuso y rechazo de la tienda de aplicaciones (ITMS-
90809)
A partir del 2020 de abril, Apple rechazará las aplicaciones que siguen usando la API en desuso UIWebView .
Aunque :::no-loc(Xamarin.Forms)::: ha cambiado a WKWebView como valor predeterminado, todavía hay una
referencia al SDK anterior en los :::no-loc(Xamarin.Forms)::: archivos binarios. El comportamiento actual del
enlazador de iOS no lo quita y, como resultado, la UIWebView API en desuso seguirá apareciendo como referencia
desde la aplicación cuando se envíe a la tienda de aplicaciones.
Hay disponible una versión preliminar del enlazador para corregir este problema. Para habilitar la vista previa,
debe proporcionar un argumento adicional --optimize=experimental-xforms-product-type al enlazador.
Los requisitos previos para que funcionen son los siguientes:
:::no-loc(Xamarin.Forms)::: 4,5 o superior . :::no-loc(Xamarin.Forms)::: se requiere 4,6, o posterior, si la
aplicación usa el material visual.
Xamarin. iOS 13.10.0.17 o superior . Compruebe la versión de Xamarin. iOS en Visual Studio. Esta versión
de Xamarin. iOS se incluye con Visual Studio para Mac 8.4.1 y Visual Studio 16.4.3.
Quite las referencias UIWebView a . El código no debe tener ninguna referencia a UIWebView o a clases que
hagan uso de UIWebView .
Para obtener más información sobre cómo detectar y quitar UIWebView referencias, vea UIWebView deprecated.
Configurar el enlazador
Visual Studio
Visual Studio para Mac
Siga estos pasos para que el vinculador Quite UIWebView las referencias:
1. Abrir propiedades – del proyecto de iOS Haga clic con el botón derecho en el proyecto de iOS y elija
propiedades .
2. Vaya a la sección – compilación de iOS. Seleccione la sección compilación de iOS .
3. Actualización de los argumentos – de Mtouch adicionales En los argumentos Mtouch adicionales ,
agregue esta marca --optimize=experimental-xforms-product-type (además de cualquier valor que ya esté en
ella). Nota: esta marca funciona junto con el compor tamiento del vinculador establecido en solo SDK o
vincular todo . Si, por cualquier motivo, ve errores al establecer el comportamiento del enlazador en todos, lo
más probable es que haya un problema en el código de la aplicación o en una biblioteca de terceros que no sea
segura para el vinculador. Para obtener más información sobre el enlazador, consulte vinculación de
aplicaciones de Xamarin. iOS.
4. Actualizar todas las configuraciones – de compilación Use las listas configuración y plataforma en la
parte superior de la ventana para actualizar todas las configuraciones de compilación. La configuración más
importante que hay que actualizar es la configuración de lanzamiento o iPhone , ya que normalmente se usa
para crear compilaciones para el envío de la tienda de aplicaciones.
Puede ver la ventana con la nueva marca en su lugar en esta captura de pantalla:
Ahora, cuando se crea una nueva compilación (versión) y se envía a la tienda de aplicaciones, no debería haber
ninguna advertencia sobre la API en desuso.

Vínculos relacionados
Trabajar con WebView (ejemplo)
WebView (ejemplo)
Desuso de UIWebView
:::no-loc(Xamarin.Forms)::: Botón
18/12/2020 • 34 minutes to read • Edit Online

Descargar el ejemplo
El botón responde a una pulsación o clic que dirige una aplicación para llevar a cabo una tarea determinada.
Button Es el control interactivo más importante en todos :::no-loc(Xamarin.Forms)::: . Button Normalmente
muestra una cadena de texto breve que indica un comando, pero también puede mostrar una imagen de mapa
de bits o una combinación de texto y una imagen. El usuario presiona el Button con un dedo o hace clic con el
mouse para iniciar ese comando.
La mayoría de los temas descritos a continuación corresponden a las páginas del ejemplo ButtonDemos .

Control de clics de botón


Button define un Clicked evento que se desencadena cuando el usuario puntea Button con un dedo o un
puntero del mouse. El evento se desencadena cuando el dedo o el botón del mouse se suelta de la superficie de
Button . El Button debe tener su IsEnabled propiedad establecida en para que true responda a los grifos.

La página de clics del botón básico en el ejemplo ButtonDemos muestra cómo crear una instancia Button
de en XAML y controlar su Clicked evento. El archivo BasicButtonClickPage. Xaml contiene un StackLayout
con un Label y un Button :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.BasicButtonClickPage"
Title="Basic Button Click">
<StackLayout>

<Label x:Name="label"
Text="Click the Button below"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Button Text="Click to Rotate Text!"


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

</StackLayout>
</ContentPage>

Button Tiende a ocupar todo el espacio permitido. Por ejemplo, si no establece la HorizontalOptions propiedad
de Button en un valor distinto de Fill , Button ocupará el ancho completo de su elemento primario.
De forma predeterminada, Button es rectangular, pero puede asignarle esquinas redondeadas mediante la
CornerRadius propiedad, como se describe a continuación en la sección apariencia del botón .

La Text propiedad especifica el texto que aparece en Button . El Clicked evento se establece en un controlador
de eventos denominado OnButtonClicked . Este controlador se encuentra en el archivo de código subyacente,
BasicButtonClickPage.Xaml.CS :
public partial class BasicButtonClickPage : ContentPage
{
public BasicButtonClickPage ()
{
InitializeComponent ();
}

async void OnButtonClicked(object sender, EventArgs args)


{
await label.RelRotateTo(360, 1000);
}
}

Cuando se pulsa Button , se ejecuta el método OnButtonClicked . El sender argumento es el Button objeto
responsable de este evento. Puede utilizar esto para tener acceso al Button objeto o para distinguir entre varios
Button objetos que comparten el mismo Clicked evento.

Este Clicked controlador concreto llama a una función de animación que gira los Label 360 grados en 1000
milisegundos. Este es el programa que se ejecuta en dispositivos iOS y Android, y como una aplicación
Plataforma universal de Windows (UWP) en el escritorio de Windows 10:

Observe que el OnButtonClicked método incluye el async modificador porque await se usa dentro del
controlador de eventos. Un Clicked controlador de eventos solo requiere el async modificador si el cuerpo del
controlador utiliza await .
Cada plataforma representa el Button de forma específica. En la sección apariencia del botón , verá cómo
establecer colores y hacer que el borde sea visible para ver una Button apariencia más personalizada. Button
implementa la IFontElement interfaz, por lo que incluye FontFamily FontSize FontAttributes las propiedades,
y.

Crear un botón en el código


Es habitual crear una instancia de Button en XAML, pero también se puede crear un Button en el código. Esto
puede resultar práctico cuando la aplicación necesita crear varios botones basados en datos enumerables con un
foreach bucle.

La página de clics del botón de código muestra cómo crear una página que sea funcionalmente equivalente a
la página de clics del botón básico , pero completamente en C#:
public class CodeButtonClickPage : ContentPage
{
public CodeButtonClickPage ()
{
Title = "Code Button Click";

Label label = new Label


{
Text = "Click the Button below",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
};

Button button = new Button


{
Text = "Click to Rotate Text!",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
};
button.Clicked += async (sender, args) => await label.RelRotateTo(360, 1000);

Content = new StackLayout


{
Children =
{
label,
button
}
};
}
}

Todo se realiza en el constructor de la clase. Dado que el Clicked controlador es solo una instrucción Long, se
puede adjuntar al evento muy simplemente:

button.Clicked += async (sender, args) => await label.RelRotateTo(360, 1000);

Por supuesto, también puede definir el controlador de eventos como un método independiente (al igual que el
OnButtonClick método en el clic de botón básico ) y asociar ese método al evento:

button.Clicked += OnButtonClicked;

Deshabilitar el botón
A veces, una aplicación se encuentra en un estado determinado en el que un Button clic determinado no es una
operación válida. En esos casos, Button se debe deshabilitar estableciendo su IsEnabled propiedad en false .
El ejemplo clásico es un Entry control para un nombre de archivo que va acompañado de un archivo abierto
Button : el Button solo se debe habilitar si se ha escrito texto en Entry . Puede usar DataTrigger para esta
tarea, tal como se muestra en el artículo desencadenadores de datos .

Usar la interfaz de comandos


Es posible que una aplicación responda a Button las pulsaciones sin controlar el Clicked evento. Button
Implementa un mecanismo de notificación alternativo denominado comando o interfaz de comandos . Consta de
dos propiedades:
Command de tipo ICommand , una interfaz definida en el System.Windows.Input espacio de nombres.
CommandParameter propiedad de tipo Object .

Este enfoque es especialmente adecuado en relación con el enlace de datos, especialmente cuando se
implementa la arquitectura Model-View-ViewModel (MVVM). Estos temas se tratan en los artículos enlace de
datos, desde enlaces de datos a MVVMy MVVM.
En una aplicación MVVM, el ViewModel define las propiedades de tipo ICommand que se conectan a los Button
elementos XAML con enlaces de datos. :::no-loc(Xamarin.Forms)::: también define Command Command<T> las clases
y que implementan la ICommand interfaz y ayudan al ViewModel en la definición de propiedades de tipo
ICommand .

Los comandos se describen con más detalle en el artículo la interfaz de comando , pero la página de
comandos del botón básico en el ejemplo ButtonDemos muestra el enfoque básico.
La CommandDemoViewModel clase es un ViewModel muy sencillo que define una propiedad de tipo double
denominado y Number dos propiedades de tipo ICommand denominadas MultiplyBy2Command y DivideBy2Command
:

class CommandDemoViewModel : INotifyPropertyChanged


{
double number = 1;

public event PropertyChangedEventHandler PropertyChanged;

public CommandDemoViewModel()
{
MultiplyBy2Command = new Command(() => Number *= 2);

DivideBy2Command = new Command(() => Number /= 2);


}

public double Number


{
set
{
if (number != value)
{
number = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
}
}
get
{
return number;
}
}

public ICommand MultiplyBy2Command { private set; get; }

public ICommand DivideBy2Command { private set; get; }


}

Las dos propiedades se inicializan en el constructor de la clase con dos objetos de tipo Command . Los
ICommand
Command constructores incluyen una pequeña función (denominada execute argumento de constructor) que
hace doble o la mitad de la Number propiedad.
El archivo BasicButtonCommand. Xaml establece su BindingContext en una instancia de
CommandDemoViewModel . El Label elemento y dos Button elementos contienen enlaces a las tres propiedades de
CommandDemoViewModel :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ButtonDemos"
x:Class="ButtonDemos.BasicButtonCommandPage"
Title="Basic Button Command">

<ContentPage.BindingContext>
<local:CommandDemoViewModel />
</ContentPage.BindingContext>

<StackLayout>
<Label Text="{Binding Number, StringFormat='Value is now {0}'}"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Button Text="Multiply by 2"


VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Command="{Binding MultiplyBy2Command}" />

<Button Text="Divide by 2"


VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Command="{Binding DivideBy2Command}" />
</StackLayout>
</ContentPage>

A medida que Button se puntean los dos elementos, los comandos se ejecutan y el número cambia de valor:

La ventaja de este enfoque en lo Clicked que se refiere a los controladores es que toda la lógica que implica la
funcionalidad de esta página se encuentra en el ViewModel en lugar del archivo de código subyacente, con lo que
se logra una mejor separación de la interfaz de usuario de la lógica de negocios.
También es posible que los Command objetos controlen la habilitación y deshabilitación de los Button elementos.
Por ejemplo, supongamos que desea limitar el intervalo de valores numéricos entre 210 y 2 – 10. Puede agregar
otra función al constructor (denominado el canExecute argumento) que devuelve true si Button debe estar
habilitado. Esta es la modificación del CommandDemoViewModel constructor:
class CommandDemoViewModel : INotifyPropertyChanged
{
···
public CommandDemoViewModel()
{
MultiplyBy2Command = new Command(
execute: () =>
{
Number *= 2;
((Command)MultiplyBy2Command).ChangeCanExecute();
((Command)DivideBy2Command).ChangeCanExecute();
},
canExecute: () => Number < Math.Pow(2, 10));

DivideBy2Command = new Command(


execute: () =>
{
Number /= 2;
((Command)MultiplyBy2Command).ChangeCanExecute();
((Command)DivideBy2Command).ChangeCanExecute();
},
canExecute: () => Number > Math.Pow(2, -10));
}
···
}

Las llamadas al ChangeCanExecute método de Command son necesarias para que el Command método pueda llamar
al canExecute método y determinar si Button debe estar deshabilitado o no. Con este cambio de código, a
medida que el número alcanza el límite, el Button está deshabilitado:

Es posible que dos o más Button elementos se enlacen a la misma ICommand propiedad. Los Button elementos
se pueden distinguir mediante la CommandParameter propiedad de Button . En este caso, querrá usar la
Command<T> clase genérica. CommandParameter A continuación, el objeto se pasa como argumento a execute los
canExecute métodos y. Esta técnica se muestra en detalle en la sección de comandos básicos del artículo sobre
la interfaz de comandos .
El ejemplo ButtonDemos también usa esta técnica en su MainPage clase. El archivo mainpage. Xaml contiene
una Button para cada página del ejemplo:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ButtonDemos"
x:Class="ButtonDemos.MainPage"
Title="Button Demos">
<ScrollView>
<FlexLayout Direction="Column"
JustifyContent="SpaceEvenly"
AlignItems="Center">

<Button Text="Basic Button Click"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:BasicButtonClickPage}" />

<Button Text="Code Button Click"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:CodeButtonClickPage}" />

<Button Text="Basic Button Command"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:BasicButtonCommandPage}" />

<Button Text="Press and Release Button"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:PressAndReleaseButtonPage}" />

<Button Text="Button Appearance"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:ButtonAppearancePage}" />

<Button Text="Toggle Button Demo"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:ToggleButtonDemoPage}" />

<Button Text="Image Button Demo"


Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:ImageButtonDemoPage}" />

</FlexLayout>
</ScrollView>
</ContentPage>

Cada Buttonuna tiene su Command propiedad enlazada a una propiedad denominada NavigateCommand y
CommandParameter se establece en un Type objeto que corresponde a una de las clases de página del proyecto.

Esa NavigateCommand propiedad es de tipo ICommand y se define en el archivo de código subyacente:

public partial class MainPage : ContentPage


{
public MainPage()
{
InitializeComponent();

NavigateCommand = new Command<Type>(async (Type pageType) =>


{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
});

BindingContext = this;
}

public ICommand NavigateCommand { private set; get; }


}
El constructor inicializa la NavigateCommand propiedad en un Command<Type> objeto porque Type es el tipo del
conjunto de CommandParameter objetos en el archivo XAML. Esto significa que el execute método tiene un
argumento de tipo Type que corresponde a este CommandParameter objeto. La función crea una instancia de la
página y, a continuación, navega hasta ella.
Tenga en cuenta que el constructor termina estableciendo su BindingContext en sí mismo. Esto es necesario para
que las propiedades del archivo XAML se enlacen a la NavigateCommand propiedad.

Presionar y soltar el botón


Además del Clicked evento, Button también define Pressed Released los eventos y. El Pressed evento tiene
lugar cuando se presiona un dedo en un control Button , o cuando se presiona un botón del mouse con el
puntero situado sobre Button . El Released evento tiene lugar cuando se suelta el dedo o el botón del mouse.
Por lo general, un Clicked evento también se desencadena al mismo tiempo que el Released evento, pero si el
puntero del dedo o del mouse se desplaza fuera de la superficie de Button antes de su lanzamiento, el Clicked
evento podría no producirse.
Los Pressed Released eventos y no se usan a menudo, pero se pueden usar para fines especiales, como se
muestra en la página del botón presionar y soltar . El archivo XAML contiene un Label y un Button con los
controladores adjuntos para los Pressed Released eventos y:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.PressAndReleaseButtonPage"
Title="Press and Release Button">
<StackLayout>

<Label x:Name="label"
Text="Press and hold the Button below"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />

<Button Text="Press to Rotate Text!"


VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Pressed="OnButtonPressed"
Released="OnButtonReleased" />

</StackLayout>
</ContentPage>

El archivo de código subyacente anima el Label cuando Pressed se produce un evento, pero suspende el giro
cuando Released se produce un evento:
public partial class PressAndReleaseButtonPage : ContentPage
{
bool animationInProgress = false;
Stopwatch stopwatch = new Stopwatch();

public PressAndReleaseButtonPage ()
{
InitializeComponent ();
}

void OnButtonPressed(object sender, EventArgs args)


{
stopwatch.Start();
animationInProgress = true;

Device.StartTimer(TimeSpan.FromMilliseconds(16), () =>
{
label.Rotation = 360 * (stopwatch.Elapsed.TotalSeconds % 1);

return animationInProgress;
});
}

void OnButtonReleased(object sender, EventArgs args)


{
animationInProgress = false;
stopwatch.Stop();
}
}

El resultado es que el Label solo gira mientras un dedo está en contacto con el Button , y se detiene cuando se
suelta el dedo:

Este tipo de comportamiento tiene aplicaciones para juegos: un dedo que se mantiene en un Button puede
hacer que un objeto en pantalla se mueva en una dirección determinada.

Apariencia del botón


Button Hereda o define varias propiedades que afectan a su apariencia:
TextColor es el color del Button texto.
BackgroundColor es el color de fondo de ese texto
BorderColor es el color de un área que rodea el Button
FontFamily es la familia de fuentes utilizada para el texto
FontSize es el tamaño del texto.
FontAttributes indica si el texto está en cursiva o en negrita
BorderWidth es el ancho del borde.
CornerRadius es el radio de la esquina del Button
CharacterSpacing es el espaciado entre los caracteres del Button texto.
TextTransform determina las mayúsculas y minúsculas del Button texto.

NOTE
La Button clase también tiene Margin Padding propiedades y que controlan el comportamiento de diseño de
Button . Para obtener más información, vea Márgenes y relleno.

Los efectos de seis de estas propiedades (excepto FontFamily y FontAttributes ) se muestran en la página
apariencia del botón . Otra propiedad, Image , se describe en la sección uso de mapas de bits con el
botón .
Todas las vistas y los enlaces de datos de la página apariencia del botón se definen en el archivo XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ButtonDemos"
x:Class="ButtonDemos.ButtonAppearancePage"
Title="Button Appearance">
<StackLayout>
<Button x:Name="button"
Text="Button"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
TextColor="{Binding Source={x:Reference textColorPicker},
Path=SelectedItem.Color}"
BackgroundColor="{Binding Source={x:Reference backgroundColorPicker},
Path=SelectedItem.Color}"
BorderColor="{Binding Source={x:Reference borderColorPicker},
Path=SelectedItem.Color}" />

<StackLayout BindingContext="{x:Reference button}"


Padding="10">

<Slider x:Name="fontSizeSlider"
Maximum="48"
Minimum="1"
Value="{Binding FontSize}" />

<Label Text="{Binding Source={x:Reference fontSizeSlider},


Path=Value,
StringFormat='FontSize = {0:F0}'}"
HorizontalTextAlignment="Center" />

<Slider x:Name="borderWidthSlider"
Minimum="-1"
Maximum="12"
Value="{Binding BorderWidth}" />

<Label Text="{Binding Source={x:Reference borderWidthSlider},


Path=Value,
StringFormat='BorderWidth = {0:F0}'}"
HorizontalTextAlignment="Center" />

<Slider x:Name="cornerRadiusSlider"
Minimum="-1"
Maximum="24"
Maximum="24"
Value="{Binding CornerRadius}" />

<Label Text="{Binding Source={x:Reference cornerRadiusSlider},


Path=Value,
StringFormat='CornerRadius = {0:F0}'}"
HorizontalTextAlignment="Center" />

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Grid.Resources>
<Style TargetType="Label">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</Grid.Resources>

<Label Text="Text Color:"


Grid.Row="0" Grid.Column="0" />

<Picker x:Name="textColorPicker"
ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
ItemDisplayBinding="{Binding FriendlyName}"
SelectedIndex="0"
Grid.Row="0" Grid.Column="1" />

<Label Text="Background Color:"


Grid.Row="1" Grid.Column="0" />

<Picker x:Name="backgroundColorPicker"
ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
ItemDisplayBinding="{Binding FriendlyName}"
SelectedIndex="0"
Grid.Row="1" Grid.Column="1" />

<Label Text="Border Color:"


Grid.Row="2" Grid.Column="0" />

<Picker x:Name="borderColorPicker"
ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
ItemDisplayBinding="{Binding FriendlyName}"
SelectedIndex="0"
Grid.Row="2" Grid.Column="1" />
</Grid>
</StackLayout>
</StackLayout>
</ContentPage>

Button En la parte superior de la página tiene sus tres Color propiedades enlazadas a Picker elementos en la
parte inferior de la página. Los elementos de los Picker elementos son colores de la NamedColor clase incluida
en el proyecto. Tres Slider elementos contienen enlaces bidireccionales a las FontSize propiedades,
BorderWidth y CornerRadius de Button .

Este programa le permite experimentar con combinaciones de todas estas propiedades:


Para ver el Button borde, debe establecer BorderColor en un valor distinto de Default y BorderWidth en un
valor positivo.
En iOS, observará que los anchos de borde grandes infieren al interior de Button e interfieren con la
presentación del texto. Si decide usar un borde con un iOS Button , probablemente querrá empezar y finalizar la
Text propiedad con espacios para conservar su visibilidad.

En UWP, al seleccionar un CornerRadius que supera la mitad del alto del se Button produce una excepción.

Estados visuales del botón


Button tiene un Pressed VisualState que se puede utilizar para iniciar un cambio visual en Button cuando lo
presiona el usuario, siempre que esté habilitado.
En el siguiente ejemplo de XAML se muestra cómo definir un estado visual para el Pressed Estado:

<Button Text="Click me!"


...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Scale"
Value="1" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
</VisualState.Setters>
</VisualState>

</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>

Pressed VisualState Especifica que cuando Button se presiona, su propiedad se Scale cambiará de su valor
predeterminado de 1 a 0,8. Normal VisualState Especifica que cuando el Button está en un estado normal, su
Scale propiedad se establecerá en 1. Por lo tanto, el efecto general es que, cuando Button se presiona, se
vuelve a escalar para que sea ligeramente menor y, cuando Button se libera, se vuelve a escalar a su tamaño
predeterminado.
Para obtener más información sobre los Estados visuales, consulte el :::no-loc(Xamarin.Forms)::: Visual State
Manager.

Crear un botón de alternancia


Es posible crear una subclase para Button que funcione como un modificador OFF: Pulse el botón una vez para
activar o desactivar el botón y púlselo de nuevo para desactivarlo.
La siguiente ToggleButton clase deriva de Button y define un nuevo evento denominado Toggled y una
propiedad booleana denominada IsToggled . Estas son las dos propiedades definidas por :::no-
loc(Xamarin.Forms)::: Switch :

class ToggleButton : Button


{
public event EventHandler<ToggledEventArgs> Toggled;

public static BindableProperty IsToggledProperty =


BindableProperty.Create("IsToggled", typeof(bool), typeof(ToggleButton), false,
propertyChanged: OnIsToggledChanged);

public ToggleButton()
{
Clicked += (sender, args) => IsToggled ^= true;
}

public bool IsToggled


{
set { SetValue(IsToggledProperty, value); }
get { return (bool)GetValue(IsToggledProperty); }
}

protected override void OnParentSet()


{
base.OnParentSet();
VisualStateManager.GoToState(this, "ToggledOff");
}

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


{
ToggleButton toggleButton = (ToggleButton)bindable;
bool isToggled = (bool)newValue;

// Fire event
toggleButton.Toggled?.Invoke(toggleButton, new ToggledEventArgs(isToggled));

// Set the visual state


VisualStateManager.GoToState(toggleButton, isToggled ? "ToggledOn" : "ToggledOff");
}
}

El ToggleButton constructor adjunta un controlador al Clicked evento para que pueda cambiar el valor de la
IsToggled propiedad. El OnIsToggledChanged método desencadena el Toggled evento.
La última línea del OnIsToggledChanged método llama al método estático VisualStateManager.GoToState con las
dos cadenas de texto "ToggledOn" y "ToggledOff". Puede leer sobre este método y cómo la aplicación puede
responder a los Estados visuales en el artículo :::no-loc(Xamarin.Forms)::: Visual State Manager .
Dado que ToggleButton realiza la llamada a VisualStateManager.GoToState , la propia clase no necesita incluir
recursos adicionales para cambiar la apariencia del botón en función de su IsToggled Estado. Es responsabilidad
del código XAML que hospeda ToggleButton .
La página de demostración del botón de alternancia contiene dos instancias de ToggleButton , incluido el
marcado de Visual State Manager que establece Text , BackgroundColor y TextColor del botón en función del
estado visual:

<?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:local="clr-namespace:ButtonDemos"
x:Class="ButtonDemos.ToggleButtonDemoPage"
Title="Toggle Button Demo">

<ContentPage.Resources>
<Style TargetType="local:ToggleButton">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
</ContentPage.Resources>

<StackLayout Padding="10, 0">


<local:ToggleButton Toggled="OnItalicButtonToggled">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ToggleStates">
<VisualState Name="ToggledOff">
<VisualState.Setters>
<Setter Property="Text" Value="Italic Off" />
<Setter Property="BackgroundColor" Value="#C0C0C0" />
<Setter Property="TextColor" Value="Black" />
</VisualState.Setters>
</VisualState>

<VisualState Name="ToggledOn">
<VisualState.Setters>
<Setter Property="Text" Value=" Italic On " />
<Setter Property="BackgroundColor" Value="#404040" />
<Setter Property="TextColor" Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</local:ToggleButton>

<local:ToggleButton Toggled="OnBoldButtonToggled">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ToggleStates">
<VisualState Name="ToggledOff">
<VisualState.Setters>
<Setter Property="Text" Value="Bold Off" />
<Setter Property="BackgroundColor" Value="#C0C0C0" />
<Setter Property="TextColor" Value="Black" />
</VisualState.Setters>
</VisualState>

<VisualState Name="ToggledOn">
<VisualState.Setters>
<Setter Property="Text" Value=" Bold On " />
<Setter Property="BackgroundColor" Value="#404040" />
<Setter Property="TextColor" Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</local:ToggleButton>

<Label x:Name="label"
Text="Just a little passage of some sample text that can be formatted in italic or boldface by
toggling the two buttons."
FontSize="Large"
HorizontalTextAlignment="Center"
VerticalOptions="CenterAndExpand" />

</StackLayout>
</ContentPage>

Los Toggled controladores de eventos se encuentran en el archivo de código subyacente. Son responsables del
establecimiento de la FontAttributes propiedad de Label basándose en el estado de los botones:

public partial class ToggleButtonDemoPage : ContentPage


{
public ToggleButtonDemoPage ()
{
InitializeComponent ();
}

void OnItalicButtonToggled(object sender, ToggledEventArgs args)


{
if (args.Value)
{
label.FontAttributes |= FontAttributes.Italic;
}
else
{
label.FontAttributes &= ~FontAttributes.Italic;
}
}

void OnBoldButtonToggled(object sender, ToggledEventArgs args)


{
if (args.Value)
{
label.FontAttributes |= FontAttributes.Bold;
}
else
{
label.FontAttributes &= ~FontAttributes.Bold;
}
}
}

Este es el programa que se ejecuta en iOS, Android y UWP:

Uso de mapas de bits con botones.


La Button clase define una ImageSource propiedad que permite mostrar una imagen de mapa de bits en Button
, ya sea solo o en combinación con el texto. También puede especificar cómo se organizan el texto y la imagen.
La ImageSource propiedad es de tipo ImageSource , lo que significa que los mapas de bits se pueden cargar
desde un archivo, un recurso incrustado, un URI o un flujo.

NOTE
Aunque un Button puede cargar un GIF animado, solo mostrará el primer fotograma del GIF.

Cada plataforma compatible con :::no-loc(Xamarin.Forms)::: permite almacenar imágenes en varios tamaños para
diferentes resoluciones de píxeles de los distintos dispositivos en los que se puede ejecutar la aplicación. Estos
varios mapas de bits se denominan o almacenan de manera que el sistema operativo puede elegir la mejor
coincidencia para la resolución de pantalla del vídeo del dispositivo.
En el caso de un mapa de bits en un Button , el mejor tamaño suele ser de entre 32 y 64 unidades
independientes del dispositivo, en función de lo grande que desee. Las imágenes que se usan en este ejemplo se
basan en un tamaño de 48 unidades independientes del dispositivo.
En el proyecto de iOS, la carpeta recursos contiene tres tamaños de esta imagen:
Mapa de bits cuadrado de 48 píxeles almacenado como /Resources/MonkeyFace.png
Mapa de bits cuadrado de 96 píxeles almacenado como /Resource/[email protected]
Mapa de bits cuadrado de 144 píxeles almacenado como /Resource/[email protected]
A los tres mapas de bits se les proporcionó una acción de compilación de BundleResource .
En el caso del proyecto de Android, todos los mapas de bits tienen el mismo nombre, pero se almacenan en
diferentes subcarpetas de la carpeta de recursos :
Mapa de bits cuadrado de 72 píxeles almacenado como /Resources/drawable-hdpi/MonkeyFace.png
Mapa de bits cuadrado de 96 píxeles almacenado como /Resources/drawable-xhdpi/MonkeyFace.png
Mapa de bits cuadrado de 144 píxeles almacenado como /Resources/drawable-xxhdpi/MonkeyFace.png
Mapa de bits cuadrado de 192 píxeles almacenado como /Resources/drawable-
xxxhdpi/MonkeyFace.png
A estos se les proporcionó una acción de compilación de AndroidResource .
En el proyecto de UWP, los mapas de bits se pueden almacenar en cualquier parte del proyecto, pero
normalmente se almacenan en una carpeta personalizada o en la carpeta de recursos existentes. El proyecto de
UWP contiene estos mapas de bits:
Mapa de bits cuadrado de 48 píxeles almacenado como /Assets/MonkeyFace.scale-100.png
Mapa de bits cuadrado de 96 píxeles almacenado como /Assets/MonkeyFace.scale-200.png
Mapa de bits cuadrado de 192 píxeles almacenado como /Assets/MonkeyFace.scale-400.png
Se les ha dado una acción de compilación de contenido .
Puede especificar cómo Text ImageSource se organizan las propiedades y en Button mediante la
ContentLayout propiedad de Button . Esta propiedad es de tipo ButtonContentLayout , que es una clase
incrustada en Button . [Constructor] (XREF: :::no-loc(Xamarin.Forms)::: . Button. ButtonContentLayout .% 23ctor (
:::no-loc(Xamarin.Forms)::: . Button. ButtonContentLayout. ImagePosition, System. Double)) tiene dos argumentos:
Miembro de la ImagePosition enumeración: Left , Top , Right o Bottom que indica cómo aparece el mapa
de bits en relación con el texto.
Un double valor para el espaciado entre el mapa de bits y el texto.
Los valores predeterminados son Left y 10 unidades. Dos propiedades de solo lectura de ButtonContentLayout
con nombre Position y Spacing proporcionan los valores de esas propiedades.
En el código, puede crear Button y establecer la propiedad de la siguiente ContentLayout manera:

Button button = new Button


{
Text = "button text",
ImageSource = new FileImageSource
{
File = "image filename"
},
ContentLayout = new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 20)
};

En XAML, solo es necesario especificar el miembro de la enumeración, el espaciado o ambos en cualquier orden
separado por comas:

<Button Text="button text"


ImageSource="image filename"
ContentLayout="Right, 20" />

En la página de demostración del botón imagen OnPlatform se usa para especificar nombres de archivo
diferentes para los archivos de mapa de bits de iOS, Android y UWP. Si desea usar el mismo nombre de archivo
para cada plataforma y evitar el uso de OnPlatform , deberá almacenar los mapas de bits de UWP en el directorio
raíz del proyecto.
La primera Button de la página de demostración del botón de imagen establece la Image propiedad pero
no la Text propiedad:

<Button>
<Button.ImageSource>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="MonkeyFace.png" />
<On Platform="UWP" Value="Assets/MonkeyFace.png" />
</OnPlatform>
</Button.ImageSource>
</Button>

Si los mapas de bits de UWP se almacenan en el directorio raíz del proyecto, este marcado se puede simplificar
considerablemente:

<Button ImageSource="MonkeyFace.png" />

Para evitar una gran cantidad de marcado redundante resulta en el archivo ImageButtonDemo. Xaml , Style
también se define un implícito para establecer la ImageSource propiedad. Esto Style se aplica automáticamente
a otros cinco Button elementos. Este es el archivo XAML completo:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.ImageButtonDemoPage">

<FlexLayout Direction="Column"
JustifyContent="SpaceEvenly"
AlignItems="Center">

<FlexLayout.Resources>
<Style TargetType="Button">
<Setter Property="ImageSource">
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="MonkeyFace.png" />
<On Platform="UWP" Value="Assets/MonkeyFace.png" />
</OnPlatform>
</Setter>
</Style>
</FlexLayout.Resources>

<Button>
<Button.ImageSource>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="MonkeyFace.png" />
<On Platform="UWP" Value="Assets/MonkeyFace.png" />
</OnPlatform>
</Button.ImageSource>
</Button>

<Button Text="Default" />

<Button Text="Left - 10"


ContentLayout="Left, 10" />

<Button Text="Top - 10"


ContentLayout="Top, 10" />

<Button Text="Right - 20"


ContentLayout="Right, 20" />

<Button Text="Bottom - 20"


ContentLayout="Bottom, 20" />
</FlexLayout>
</ContentPage>

Los cuatro Button elementos finales hacen uso de la ContentLayout propiedad para especificar la posición y el
espaciado del texto y el mapa de bits:
Ahora ha visto las distintas formas en que puede controlar Button eventos y cambiar la Button apariencia.

Vínculos relacionados
Ejemplo de ButtonDemos
API de los botones
:::no-loc(Xamarin.Forms)::: ImageButton
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
El ImageButton muestra una imagen y responde a una derivación o a un clic que dirige una aplicación para llevar a
cabo una tarea determinada.
La ImageButton vista combina la Button vista y la Image vista para crear un botón cuyo contenido es una
imagen. El usuario presiona el ImageButton con un dedo o hace clic en él con el mouse para indicar a la aplicación
que lleve a cabo una tarea determinada. Sin embargo, a diferencia Button de la vista, la ImageButton vista no
tiene ningún concepto de aspecto de texto y texto.

NOTE
Mientras Button que la vista define una Image propiedad, que permite mostrar una imagen en Button , esta propiedad
está pensada para usarse al mostrar un icono pequeño junto al Button texto.

Los ejemplos de código de esta guía se toman del ejemplo FormsGallery.

Establecer el origen de la imagen


ImageButton define una Source propiedad que debe establecerse en la imagen que se va a mostrar en el botón,
con el origen de la imagen como un archivo, un URI, un recurso o un flujo. Para obtener más información sobre
cómo cargar imágenes de orígenes diferentes, vea imágenes en :::no-loc(Xamarin.Forms)::: .
En el ejemplo siguiente se muestra cómo crear una instancia ImageButton de en XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FormsGallery.XamlExamples.ImageButtonDemoPage"
Title="ImageButton Demo">
<StackLayout>
<Label Text="ImageButton"
FontSize="50"
FontAttributes="Bold"
HorizontalOptions="Center" />

<ImageButton Source="XamarinLogo.png"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

La Source propiedad especifica la imagen que aparece en el ImageButton . En este ejemplo, se establece en un
archivo local que se cargará desde cada proyecto de plataforma, lo que generará las siguientes capturas de
pantallas:
De forma predeterminada, ImageButton es rectangular, pero puede asignarle esquinas redondeadas mediante la
CornerRadius propiedad. Para obtener más información sobre la ImageButton apariencia, vea la apariencia de
ImageButton.

NOTE
Mientras ImageButton que un puede cargar un GIF animado, solo mostrará el primer fotograma del GIF.

En el ejemplo siguiente se muestra cómo crear una página que es funcionalmente equivalente al ejemplo XAML
anterior, pero completamente en C#:

public class ImageButtonDemoPage : ContentPage


{
public ImageButtonDemoPage()
{
Label header = new Label
{
Text = "ImageButton",
FontSize = 50,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center
};

ImageButton imageButton = new ImageButton


{
Source = "XamarinLogo.png",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

// Build the page.


Title = "ImageButton Demo";
Content = new StackLayout
{
Children = { header, imageButton }
};
}
}

Controlar los clics de ImageButton


ImageButton define un Clicked evento que se desencadena cuando el usuario puntea ImageButton con un dedo
o un puntero del mouse. El evento se desencadena cuando el dedo o el botón del mouse se suelta de la superficie
de ImageButton . El ImageButton debe tener su IsEnabled propiedad establecida en true para responder a los
grifos.
En el ejemplo siguiente se muestra cómo crear una instancia ImageButton de en XAML y controlar su Clicked
evento:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FormsGallery.XamlExamples.ImageButtonDemoPage"
Title="ImageButton Demo">
<StackLayout>
<Label Text="ImageButton"
FontSize="50"
FontAttributes="Bold"
HorizontalOptions="Center" />

<ImageButton Source="XamarinLogo.png"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnImageButtonClicked" />

<Label x:Name="label"
Text="0 ImageButton clicks"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

El Clicked evento se establece en un controlador de eventos denominado OnImageButtonClicked que se


encuentra en el archivo de código subyacente:

public partial class ImageButtonDemoPage : ContentPage


{
int clickTotal;

public ImageButtonDemoPage()
{
InitializeComponent();
}

void OnImageButtonClicked(object sender, EventArgs e)


{
clickTotal += 1;
label.Text = $"{clickTotal} ImageButton click{(clickTotal == 1 ? "" : "s")}";
}
}

Cuando se pulsa ImageButton , se ejecuta el método OnImageButtonClicked . El sender argumento es el


ImageButton responsable de este evento. Puede utilizar esto para tener acceso al ImageButton objeto o para
distinguir entre varios ImageButton objetos que comparten el mismo Clicked evento.
Este Clicked controlador determinado incrementa un contador y muestra el valor del contador en un Label :
En el ejemplo siguiente se muestra cómo crear una página que es funcionalmente equivalente al ejemplo XAML
anterior, pero completamente en C#:
public class ImageButtonDemoPage : ContentPage
{
Label label;
int clickTotal = 0;

public ImageButtonDemoPage()
{
Label header = new Label
{
Text = "ImageButton",
FontSize = 50,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center
};

ImageButton imageButton = new ImageButton


{
Source = "XamarinLogo.png",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};
imageButton.Clicked += OnImageButtonClicked;

label = new Label


{
Text = "0 ImageButton clicks",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

// Build the page.


Title = "ImageButton Demo";
Content = new StackLayout
{
Children =
{
header,
imageButton,
label
}
};
}

void OnImageButtonClicked(object sender, EventArgs e)


{
clickTotal += 1;
label.Text = $"{clickTotal} ImageButton click{(clickTotal == 1 ? "" : "s")}";
}
}

Deshabilitar ImageButton
A veces, una aplicación se encuentra en un estado determinado en el que un ImageButton clic determinado no es
una operación válida. En esos casos, ImageButton se debe deshabilitar estableciendo su IsEnabled propiedad en
false .

Usar la interfaz de comandos


Es posible que una aplicación responda a ImageButton las pulsaciones sin controlar el Clicked evento.
ImageButton Implementa un mecanismo de notificación alternativo denominado comando o interfaz de comandos
. Consta de dos propiedades:
Command de tipo ICommand, una interfaz definida en el System.Windows.Input espacio de nombres.
CommandParameter propiedad de tipo Object .

Este enfoque es adecuado en relación con el enlace de datos, especialmente cuando se implementa la arquitectura
Model-View-ViewModel (MVVM).
Para obtener más información acerca del uso de la interfaz de comandos, consulte uso de la interfaz de comandos
en la guía de botones .

Presionar y soltar el ImageButton


Además del evento Clicked , ImageButton también define los eventos Pressed y Released . El Pressed evento
tiene lugar cuando se presiona un dedo en un control ImageButton , o cuando se presiona un botón del mouse
con el puntero situado sobre ImageButton . El Released evento tiene lugar cuando se suelta el dedo o el botón del
mouse. Normalmente, el Clicked evento también se desencadena al mismo tiempo que el Released evento, pero
si el dedo o el puntero del mouse se deslizan fuera de la superficie de ImageButton antes de su lanzamiento, el
Clicked evento podría no producirse.

Para obtener más información sobre estos eventos, vea presionar y soltar el botón en la guía de botones .

Apariencia de ImageButton
Además de las propiedades que ImageButton heredan de la View clase, ImageButton también define varias
propiedades que afectan a su apariencia:
Aspect es el modo en que la imagen se escalará para ajustarse al área de presentación.
BorderColor es el color de un área que rodea a ImageButton .
BorderWidth es el ancho del borde.
CornerRadius es el radio de la esquina del ImageButton .

La Aspect propiedad se puede establecer en uno de los miembros de la Aspect enumeración:


Fill : estira la imagen para rellenar completamente el ImageButton . Esto puede dar lugar a que la imagen se
distorsione.
AspectFill -recorta la imagen para que rellene ImageButton mientras conserva la relación de aspecto.
AspectFit -panorámica la imagen (si es necesario) para que toda la imagen quepa en el ImageButton , con un
espacio en blanco agregado a la parte superior o inferior, dependiendo de si la imagen es ancha o alta. Este es
el valor predeterminado de la Aspect enumeración.

NOTE
La ImageButton clase también tiene Margin Padding propiedades y que controlan el comportamiento de diseño de
ImageButton . Para obtener más información, vea Márgenes y relleno.

Estados visuales de ImageButton


ImageButton tiene un Pressed VisualState que se puede utilizar para iniciar un cambio visual en ImageButton
cuando lo presiona el usuario, siempre que esté habilitado.
En el siguiente ejemplo de XAML se muestra cómo definir un estado visual para el Pressed Estado:
<ImageButton Source="XamarinLogo.png"
...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Scale"
Value="1" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
</VisualState.Setters>
</VisualState>

</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ImageButton>

Pressed VisualState Especifica que cuando ImageButton se presiona, su propiedad se Scale cambiará de su
valor predeterminado de 1 a 0,8. Normal VisualState Especifica que cuando el ImageButton está en un estado
normal, su Scale propiedad se establecerá en 1. Por lo tanto, el efecto general es que, cuando ImageButton se
presiona, se vuelve a escalar para que sea ligeramente menor y, cuando ImageButton se libera, se vuelve a escalar
a su tamaño predeterminado.
Para obtener más información sobre los Estados visuales, consulte el :::no-loc(Xamarin.Forms)::: Visual State
Manager.

Vínculos relacionados
Ejemplo de FormsGallery
:::no-loc(Xamarin.Forms)::: Índices
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: RadioButton Es un tipo de botón que permite a los usuarios seleccionar una opción de
un conjunto. Cada opción está representada por un botón de radio y solo puede seleccionar un botón de radio de
un grupo. La RadioButton clase hereda de la Button clase.
En las siguientes capturas de pantallas se muestran los RadioButton objetos en su estado desactivado y
seleccionado, en iOS y Android:

IMPORTANT
RadioButton es experimental actualmente y solo se puede usar si se establece la RadioButton_Experimental marca. Para
más información, vea Marcas experimentales.

El RadioButton control define las siguientes propiedades:


IsChecked , de tipo bool , que define si RadioButton está seleccionado. Esta propiedad usa un TwoWay enlace y
tiene un valor predeterminado de false .
GroupName , de tipo string , que define el nombre que especifica qué RadioButton controles se excluyen
mutuamente. Esta propiedad tiene un valor predeterminado de null .
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
El RadioButton control define un CheckedChanged evento que se desencadena cuando IsChecked cambia la
propiedad, ya sea a través de la manipulación de usuario o mediante programación. El CheckedChangedEventArgs
objeto que acompaña al CheckedChanged evento tiene una propiedad única denominada Value , de tipo bool .
Cuando se desencadena el evento, el valor de la Value propiedad se establece en el nuevo valor de la IsChecked
propiedad.
Además, la RadioButton clase hereda las siguientes propiedades utilizadas normalmente de la Button clase:
Command , de tipo , que se ejecuta cuando RadioButton se selecciona el.
ICommand
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .
FontAttributes , de tipo FontAttributes , que determina el estilo del texto.
FontFamily , de tipo string , que define la familia de fuentes.
FontSize , de tipo double , que define el tamaño de fuente.
Text , de tipo string , que define el texto que se va a mostrar.
TextColor , de tipo Color , que define el color del texto mostrado.

Para obtener más información sobre el Button control, vea :::no-loc(Xamarin.Forms)::: Button.
Crear RadioButtons
En el ejemplo siguiente se muestra cómo crear instancias RadioButton de objetos en XAML:

<StackLayout>
<Label Text="What's your favorite animal?" />
<RadioButton Text="Cat" />
<RadioButton Text="Dog" />
<RadioButton Text="Elephant" />
<RadioButton Text="Monkey"
IsChecked="true" />
</StackLayout>

En este ejemplo, RadioButton los objetos se agrupan implícitamente dentro del mismo contenedor primario. Este
código XAML da como resultado la apariencia que se muestra en las siguientes capturas de pantallas:

Como alternativa, RadioButton se pueden crear objetos en el código:

StackLayout stackLayout = new StackLayout


{
Children =
{
new Label { Text = "What's your favorite animal?" },
new RadioButton { Text = "Cat" },
new RadioButton { Text = "Dog" },
new RadioButton { Text = "Elephant" },
new RadioButton { Text = "Monkey", IsChecked = true }
}
};

RadioButtons de grupo
Los botones de radio funcionan en grupos y existen dos enfoques para agrupar los botones de radio:
Colocándolos en el mismo contenedor primario. Esto se conoce como agrupación implícita.
Estableciendo la GroupName propiedad en cada botón de radio en el mismo valor. Esto se conoce como
agrupación explícita.
En el siguiente ejemplo de XAML se muestra cómo agrupar objetos explícitamente estableciendo RadioButton sus
GroupName propiedades:
<Label Text="What's your favorite color?" />
<RadioButton Text="Red"
TextColor="Red"
GroupName="colors" />
<RadioButton Text="Green"
TextColor="Green"
GroupName="colors" />
<RadioButton Text="Blue"
TextColor="Blue"
GroupName="colors" />
<RadioButton Text="Other"
GroupName="colors" />

En este ejemplo, cada RadioButton es mutuamente excluyente porque comparte el mismo GroupName valor. Este
código XAML da como resultado la apariencia que se muestra en las siguientes capturas de pantallas:

Responder a un cambio de estado de RadioButton


Un botón de radio tiene dos estados: seleccionado o desactivado. Cuando se selecciona un botón de radio, su
IsChecked propiedad es true . Cuando se desactiva un botón de radio, su propiedad IsChecked es false . Un
botón de radio se puede desactivar haciendo clic en otro botón de radio del mismo grupo, pero no se puede
borrar haciendo clic de nuevo en él. Sin embargo, para desactivar un botón de radio mediante programación
puede establecer su propiedad IsChecked en false .
Cuando la IsChecked propiedad cambia, ya sea a través de la manipulación de usuario o mediante programación,
se CheckedChanged desencadena el evento. Se puede registrar un controlador de eventos para este evento para
responder al cambio:

<RadioButton Text="Red"
TextColor="Red"
GroupName="colors"
CheckedChanged="OnColorsRadioButtonCheckedChanged" />

El código subyacente contiene el controlador del CheckedChanged evento:

void OnColorsRadioButtonCheckedChanged(object sender, CheckedChangedEventArgs e)


{
// Perform required operation
}

El sender argumento es el RadioButton responsable de este evento. Puede utilizar esto para tener acceso al
RadioButton objeto o para distinguir entre varios RadioButton objetos que comparten el mismo CheckedChanged
controlador de eventos.
Como alternativa, se puede registrar un controlador de eventos para el CheckedChanged evento en el código:

RadioButton radioButton = new RadioButton { ... };


radioButton.CheckedChanged += (sender, e) =>
{
// Perform required operation
};

NOTE
Un enfoque alternativo para responder a un RadioButton cambio de estado es definir ICommand y asignarlo a la
RadioButton.Command propiedad. Para obtener más información, vea Button: Using the Command interface.

Estados visuales de RadioButton


RadioButton tiene un IsChecked VisualState que se puede utilizar para iniciar un cambio visual cuando
RadioButton se selecciona un.
En el siguiente ejemplo de XAML se muestra cómo definir un estado visual para el IsChecked Estado:
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="RadioButton">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="TextColor"
Value="Red" />
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="IsChecked">
<VisualState.Setters>
<Setter Property="TextColor"
Value="Green" />
<Setter Property="Opacity"
Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout>
<Label Text="What's your favorite mode of transport?" />
<RadioButton Text="Car"
CheckedChanged="OnRadioButtonCheckedChanged" />
<RadioButton Text="Bike"
CheckedChanged="OnRadioButtonCheckedChanged" />
<RadioButton Text="Train"
CheckedChanged="OnRadioButtonCheckedChanged" />
<RadioButton Text="Walking"
CheckedChanged="OnRadioButtonCheckedChanged" />
</StackLayout>
</ContentPage>

En este ejemplo, la clase Style implícita se destina a objetos RadioButton . IsChecked VisualState Especifica que
cuando RadioButton se selecciona un, su TextColor propiedad se establecerá en verde con un Opacity valor de
1. Normal VisualState Especifica que cuando RadioButton se encuentra en un estado desactivado, su TextColor
propiedad se establecerá en rojo con un Opacity valor de 0,5. Por lo tanto, el efecto general es que cuando
RadioButton se borra un, es rojo y parcialmente transparente, y es verde sin transparencia cuando se selecciona:

Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.

Deshabilitar un RadioButton
A veces, una aplicación entra en un estado en el que un RadioButton que se está comprobando no es una
operación válida. En tales casos, RadioButton se puede deshabilitar estableciendo su IsEnabled propiedad en
false .

Vínculos relacionados
Demostraciones de RadioButton (ejemplo)
Botón de :::no-loc(Xamarin.Forms):::
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: :::no-loc(RefreshView):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(RefreshView)::: Es un control contenedor que proporciona funcionalidad de extracción para actualizar
el contenido desplazable. Por lo tanto, el elemento secundario de :::no-loc(RefreshView)::: debe ser un control
desplazable, como ScrollView , CollectionView o ListView .
:::no-loc(RefreshView)::: define las siguientes propiedades:
Command , de tipo , que se ejecuta cuando se desencadena una actualización.
ICommand
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .
IsRefreshing , de tipo bool , que indica el estado actual de :::no-loc(RefreshView)::: .
RefreshColor , de tipo Color , el color del círculo de progreso que aparece durante la actualización.

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.

NOTE
En :::no-loc(Universal Windows Platform)::: , la dirección de extracción de un :::no-loc(RefreshView)::: se puede
establecer con una específica de la plataforma. Para obtener más información, vea :::no-loc(RefreshView)::: dirección de
extracción.

Creación de una clase :::no-loc(RefreshView):::


En el ejemplo siguiente se muestra cómo crear una instancia :::no-loc(RefreshView)::: de en XAML:

<:::no-loc(RefreshView)::: IsRefreshing="{Binding IsRefreshing}"


Command="{Binding RefreshCommand}">
<ScrollView>
<FlexLayout Direction="Row"
Wrap="Wrap"
AlignItems="Center"
AlignContent="Center"
BindableLayout.ItemsSource="{Binding Items}"
BindableLayout.ItemTemplate="{StaticResource ColorItemTemplate}" />
</ScrollView>
</:::no-loc(RefreshView):::>

:::no-loc(RefreshView)::: También se puede crear en el código:


:::no-loc(RefreshView)::: refreshView = new :::no-loc(RefreshView):::();
ICommand refreshCommand = new Command(() =>
{
// IsRefreshing is true
// Refresh data here
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;

ScrollView scrollView = new ScrollView();


FlexLayout flexLayout = new FlexLayout { ... };
scrollView.Content = flexLayout;
refreshView.Content = scrollView;

En este ejemplo, :::no-loc(RefreshView)::: proporciona la funcionalidad de extracción para actualizar a un


ScrollView cuyo elemento secundario es FlexLayout . FlexLayout Utiliza un diseño enlazable para generar su
contenido enlazando a una colección de elementos y establece la apariencia de cada elemento con un
DataTemplate . Para obtener más información sobre los diseños enlazables, vea diseños enlazables en :::no-
loc(Xamarin.Forms)::: .
El valor de la propiedad indica el estado actual de
:::no-loc(RefreshView):::.IsRefreshing
:::no-loc(RefreshView)::: . Cuando el usuario desencadena una actualización, esta propiedad pasará
automáticamente a true . Una vez finalizada la actualización, debe restablecer la propiedad a false .
Cuando el usuario inicia una actualización, ICommand se ejecuta la definida por la Command propiedad, que debe
actualizar los elementos que se muestran. Se muestra una visualización de actualización mientras se produce la
actualización, que consta de un círculo de progreso animado:

NOTE
Al establecer manualmente la IsRefreshing propiedad en true , se desencadenará la visualización de la actualización y
se ejecutará el ICommand definido por la Command propiedad.

:::no-loc(RefreshView)::: aparición
Además de las propiedades que :::no-loc(RefreshView)::: heredan de la VisualElement clase,
:::no-loc(RefreshView)::: también define la RefreshColor propiedad. Esta propiedad se puede establecer para
definir el color del círculo de progreso que aparece durante la actualización:

<:::no-loc(RefreshView)::: RefreshColor="Teal"
... />

En la captura de pantalla siguiente se muestra un :::no-loc(RefreshView)::: con el RefreshColor conjunto de


propiedades:
Además, la BackgroundColor propiedad se puede establecer en un Color que representa el color de fondo del
círculo de progreso.

NOTE
En iOS, la BackgroundColor propiedad establece el color de fondo del UIView que contiene el círculo de progreso.

Deshabilitar un :::no-loc(RefreshView):::
Una aplicación puede entrar en un estado en el que la extracción para actualizar no es una operación válida. En
tales casos, :::no-loc(RefreshView)::: se puede deshabilitar estableciendo su IsEnabled propiedad en false .
Esto impedirá que los usuarios puedan desencadenar la incorporación de cambios a la actualización.
Como alternativa, al definir la Command propiedad, CanExecute ICommand se puede especificar el delegado de
para habilitar o deshabilitar el comando.

Vínculos relacionados
:::no-loc(RefreshView)::: AdventureWorks
Diseños enlazables en :::no-loc(Xamarin.Forms):::
:::no-loc(RefreshView)::: Específico de la plataforma de dirección de extracción
:::no-loc(Xamarin.Forms)::: Barra
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: SearchBar Es un control de entrada de usuario que se usa para iniciar una búsqueda. El
SearchBar control admite el texto de marcador de posición, la entrada de consulta, la ejecución de búsqueda y la
cancelación. En la captura de pantalla siguiente se muestra una SearchBar consulta con los resultados mostrados
en un ListView :

La clase SearchBar define las propiedades siguientes:


CancelButtonColor es un Color que define el color del botón Cancelar.
CharacterSpacing , del tipo double , es el espaciado entre los caracteres del texto de SearchBar .
FontAttributes es un FontAttributes valor de enumeración que determina si la SearchBar fuente está en
negrita, en cursiva o en ninguna de ellas.
FontFamily es un string que determina la familia de fuentes utilizada por SearchBar .
FontSize puede ser un NamedSize valor de enumeración o un double valor que represente tamaños de fuente
específicos entre plataformas.
HorizontalTextAlignment es un TextAlignment valor de enumeración que define la alineación horizontal del
texto de la consulta.
VerticalTextAlignment es un TextAlignment valor de enumeración que define la alineación vertical del texto de
la consulta.
Placeholder es un string que define el texto del marcador de posición, como "Buscar...".
PlaceholderColor es un Color que define el color del texto del marcador de posición.
SearchCommand es un ICommand que permite enlazar acciones de usuario, como pulsaciones de dedo o clics, a
comandos definidos en un ViewModel.
SearchCommandParameter es un object que especifica el parámetro que se debe pasar a SearchCommand .
Text es un valor de tipo string que contiene el texto de la consulta en SearchBar .
TextColor es un Color que define el color del texto de la consulta.
TextTransform es un TextTransform valor que determina las mayúsculas y minúsculas del SearchBar texto.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que se SearchBar puede
personalizar y ser el destino de los enlaces de datos. Especificar las propiedades de fuente en SearchBar es
coherente con la personalización de texto en otros :::no-loc(Xamarin.Forms)::: controles de texto. Para obtener más
información, vea fuentes :::no-loc(Xamarin.Forms)::: en .

Creación de un barra
SearchBar Se puede crear una instancia de en XAML. Su Placeholder propiedad opcional se puede establecer para
definir el texto de la sugerencia en el cuadro de entrada de la consulta. El valor predeterminado de Placeholder es
una cadena vacía, por lo que no aparecerá ningún marcador de posición si no se establece. En el ejemplo siguiente
se muestra cómo crear una instancia SearchBar de en XAML con el Placeholder conjunto de propiedades
opcional:

<SearchBar Placeholder="Search items..." />

SearchBar También se puede crear en el código:

SearchBar searchBar = new SearchBar{ Placeholder = "Search items..." };

Propiedades de apariencia de barra


El SearchBar control define muchas propiedades que personalizan la apariencia del control. En el ejemplo
siguiente se muestra cómo crear una instancia SearchBar de en XAML con varias propiedades especificadas:

<SearchBar Placeholder="Search items..."


CancelButtonColor="Orange"
PlaceholderColor="Orange"
TextColor="Orange"
TextTransform="Lowercase"
HorizontalTextAlignment="Center"
FontSize="Medium"
FontAttributes="Italic" />

Estas propiedades también se pueden especificar al crear un SearchBar objeto en el código:

SearchBar searchBar = new SearchBar


{
Placeholder = "Search items...",
PlaceholderColor = Color.Orange,
TextColor = Color.Orange,
TextTransform = TextTransform.Lowercase,
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(SearchBar)),
FontAttributes = FontAttributes.Italic
};

En la captura de pantalla siguiente se muestra el SearchBar control resultante:

NOTE
En iOS, la SearchBarRenderer clase contiene un método reemplazable UpdateCancelButton . Este método controla
cuándo aparece el botón Cancelar y se puede invalidar en un representador personalizado. Para obtener más información
acerca de los representadores personalizados, vea :::no-loc(Xamarin.Forms)::: representadores personalizados.
Realizar una búsqueda con controladores de eventos
Se puede ejecutar una búsqueda mediante el SearchBar control adjuntando un controlador de eventos a uno de
los siguientes eventos:
SearchButtonPressed se llama a cuando el usuario hace clic en el botón buscar o presiona la tecla entrar.
TextChanged se llama siempre que se cambia el texto del cuadro consulta.

En el ejemplo siguiente se muestra un controlador de eventos asociado al TextChanged evento en XAML y


ListView se usa para mostrar los resultados de la búsqueda:

<SearchBar TextChanged="OnTextChanged" />


<ListView x:Name="searchResults" >

También se puede adjuntar un controlador de eventos a un SearchBar creado en el código:

SearchBar searchBar = new SearchBar {/*...*/};


searchBar.TextChanged += OnTextChanged;

El TextChanged controlador de eventos del archivo de código subyacente es el mismo, independientemente de que
SearchBar se cree a través de XAML o código:

void OnTextChanged(object sender, EventArgs e)


{
SearchBar searchBar = (SearchBar)sender;
searchResults.ItemsSource = DataService.GetSearchResults(searchBar.Text);
}

El ejemplo anterior implica la existencia de una DataService clase con un GetSearchResults método capaz de
devolver elementos que coinciden con una consulta. El SearchBar valor de la propiedad del control Text se pasa
al GetSearchResults método y el resultado se utiliza para actualizar la ListView propiedad del control
ItemsSource . El efecto general es que los resultados de la búsqueda se muestran en el ListView control.

La aplicación de ejemplo proporciona una DataService implementación de clase que se puede usar para probar la
funcionalidad de búsqueda.

Realizar una búsqueda con un ViewModel


Se puede ejecutar una búsqueda sin controladores de eventos enlazando las SearchCommand
SearchCommandParameter propiedades y a las ICommand implementaciones. En el proyecto de ejemplo se muestran
estas implementaciones mediante el patrón Model-View-ViewModel (MVVM). Para obtener más información
sobre los enlaces de datos con MVVM, consulte enlaces de datos con MVVM.
El ViewModel en la aplicación de ejemplo contiene el código siguiente:
public class SearchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public ICommand PerformSearch => new Command<string>((string query) =>


{
SearchResults = DataService.GetSearchResults(query);
});

private List<string> searchResults = DataService.Fruits;


public List<string> SearchResults
{
get
{
return searchResults;
}
set
{
searchResults = value;
NotifyPropertyChanged();
}
}
}

NOTE
El ViewModel supone la existencia de una DataService clase capaz de realizar búsquedas. La DataService clase, incluidos
los datos de ejemplo, está disponible en la aplicación de ejemplo.

En el siguiente código XAML se muestra cómo enlazar un SearchBar al modelo de presentación de ejemplo, con
un ListView control que muestra los resultados de la búsqueda:

<ContentPage ...>
<ContentPage.BindingContext>
<viewmodels:SearchViewModel />
</ContentPage.BindingContext>
<StackLayout ...>
<SearchBar x:Name="searchBar"
...
SearchCommand="{Binding PerformSearch}"
SearchCommandParameter="{Binding Text, Source={x:Reference searchBar}}"/>
<ListView x:Name="searchResults"
...
ItemsSource="{Binding SearchResults}" />
</StackLayout>
</ContentPage>

En este ejemplo BindingContext se establece para que sea una instancia de la SearchViewModel clase. Enlaza la
SearchCommand propiedad a PerformSearch ICommand en el ViewModel y enlaza la SearchBar Text propiedad a la
SearchCommandParameter propiedad. La ListView.ItemsSource propiedad está enlazada a la SearchResults
propiedad del ViewModel.
Para obtener más información sobre la ICommand interfaz y los enlaces, consulte :::no-loc(Xamarin.Forms)::: enlace
de datos y la interfaz Icommand.
Vínculos relacionados
Demostraciones de barra
:::no-loc(Xamarin.Forms)::: Controles de texto
Fuentes en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: enlace de datos
:::no-loc(Xamarin.Forms)::: SwipeView
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
SwipeView Es un control contenedor que se ajusta alrededor de un elemento de contenido y proporciona
elementos de menú contextual que se revelan mediante un gesto de deslizar rápidamente:

SwipeView está disponible en :::no-loc(Xamarin.Forms)::: 4,4. Sin embargo, actualmente es experimental y solo se
puede usar agregando la siguiente línea de código a la AppDelegate clase en iOS, a la MainActivity clase en
Android o a la App clase en UWP, antes de llamar a Forms.Init :

Forms.SetFlags("SwipeView_Experimental");

SwipeView define las siguientes propiedades:


LeftItems , de tipo SwipeItems , que representa los elementos de deslizamiento que se pueden invocar
cuando se pasa el dedo por el control hacia la izquierda.
RightItems , de tipo SwipeItems , que representa los elementos de deslizamiento que se pueden invocar
cuando se pasa el dedo por el control hacia la derecha.
TopItems , de tipo SwipeItems , que representa los elementos de deslizamiento que se pueden invocar cuando
el control se desliza hacia arriba hacia abajo.
BottomItems , de tipo SwipeItems , que representa los elementos de deslizamiento que se pueden invocar
cuando se pasa el dedo hacia arriba en el control.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Además, SwipeView hereda la Content propiedad de la ContentView clase. La Content propiedad es la propiedad
de contenido de la SwipeView clase y, por tanto, no es necesario establecer explícitamente.
La SwipeView clase también define cuatro eventos:
SwipeStarted se desencadena cuando se inicia un deslizamiento. El SwipeStartedEventArgs objeto que
acompaña a este evento tiene una SwipeDirection propiedad, de tipo SwipeDirection .
SwipeChanging se desencadena cuando se mueve el dedo. El SwipeChangingEventArgs objeto que acompaña a
este evento tiene una SwipeDirection propiedad, de tipo SwipeDirection y una Offset propiedad de tipo
double .
SwipeEnded se desencadena cuando finaliza un deslizamiento. El SwipeEndedEventArgs objeto que acompaña a
este evento tiene una SwipeDirection propiedad, de tipo SwipeDirection .
CloseRequested se desencadena cuando se cierran los elementos de deslizamiento.

Además, SwipeView incluye Open métodos y Close , que abren y cierran los elementos de deslizamiento
mediante programación, respectivamente.

NOTE
SwipeView tiene una plataforma específica para iOS y Android, que controla la transición que se usa al abrir un
SwipeView . Para obtener más información, consulte el modo de transición de SwipeView con el dedo en iOS y SwipeView
Deslice el modo de transición en Android.

Creación de un SwipeView
Un SwipeView debe definir el contenido que SwipeView rodea y los elementos de deslizamiento que se revelan
por el gesto de deslizar rápidamente. Los elementos de deslizamiento son uno o más SwipeItem objetos que se
colocan en una de las cuatro SwipeView colecciones direccionales: LeftItems , RightItems , TopItems o
BottomItems .

En el ejemplo siguiente se muestra cómo crear una instancia SwipeView de en XAML:

<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
<Grid HeightRequest="60"
WidthRequest="300"
BackgroundColor="LightGray">
<Label Text="Swipe right"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</SwipeView>

El código de C# equivalente es el siguiente:


// SwipeItems
SwipeItem favoriteSwipeItem = new SwipeItem
{
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Color.LightGreen
};
favoriteSwipeItem.Invoked += OnFavoriteSwipeItemInvoked;

SwipeItem deleteSwipeItem = new SwipeItem


{
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Color.LightPink
};
deleteSwipeItem.Invoked += OnDeleteSwipeItemInvoked;

List<SwipeItem> swipeItems = new List<SwipeItem>() { favoriteSwipeItem, deleteSwipeItem };

// SwipeView content
Grid grid = new Grid
{
HeightRequest = 60,
WidthRequest = 300,
BackgroundColor = Color.LightGray
};
grid.Children.Add(new Label
{
Text = "Swipe right",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
});

SwipeView swipeView = new SwipeView


{
LeftItems = new SwipeItems(swipeItems),
Content = grid
};

En este ejemplo, el SwipeView contenido es un Grid que contiene Label :

Los elementos de deslizamiento se utilizan para realizar acciones en el SwipeView contenido y se revelan cuando
el control se desliza rápidamente desde el lado izquierdo:

De forma predeterminada, un elemento de deslizamiento se ejecuta cuando el usuario lo puntea. No obstante, se


puede modificar este comportamiento. Para obtener más información, consulte el modo de deslizar rápidamente.
Una vez que se ha ejecutado un dedo, se ocultan los elementos de deslizamiento y SwipeView se vuelve a mostrar
el contenido. No obstante, se puede modificar este comportamiento. Para obtener más información, consulte el
comportamiento de deslizamiento.
NOTE
El deslizamiento de contenido y el deslizamiento de los elementos se pueden colocar en línea o definirse como recursos.

Deslizar rápidamente los elementos


Las LeftItems RightItems colecciones,, TopItems y BottomItems son de tipo SwipeItems . La clase SwipeItems
define las propiedades siguientes:
Mode , de tipo SwipeMode , que indica el efecto de una interacción de deslizar rápidamente. Para obtener más
información sobre el modo de deslizar rápidamente, vea el modo de deslizar rápidamente.
SwipeBehaviorOnInvoked , de tipo SwipeBehaviorOnInvoked , que indica cómo SwipeView se comporta una vez
que se invoca un elemento de deslizamiento. Para obtener más información sobre el comportamiento de los
deslizamientos, vea deslizar el comportamiento.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Cada elemento de deslizamiento se define como un SwipeItem objeto que se coloca en una de las cuatro
SwipeItems colecciones direccionales. La SwipeItem clase se deriva de la MenuItem clase y agrega los siguientes
miembros:
BackgroundColor Propiedad de tipo Color que define el color de fondo del elemento de deslizamiento. Esta
propiedad está respaldada por una propiedad enlazable.
Un Invoked evento, que se desencadena cuando se ejecuta el elemento de deslizamiento.

IMPORTANT
La MenuItem clase define varias propiedades, entre las que se incluyen Command ,, CommandParameter
IconImageSource y Text . Estas propiedades se pueden establecer en un SwipeItem objeto para definir su apariencia y
para definir un ICommand que se ejecute cuando se invoque el elemento de deslizamiento. Para obtener más información,
vea :::no-loc(Xamarin.Forms)::: MenuItem.

En el ejemplo siguiente se muestran dos SwipeItem objetos en la LeftItems colección de un objeto SwipeView :

<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>

La apariencia de cada SwipeItem se define mediante una combinación de las Text IconImageSource propiedades,
y BackgroundColor :
Cuando SwipeItem se puntea en, su Invoked evento se activa y se controla mediante su controlador de eventos
registrado. Como alternativa, la Command propiedad se puede establecer en una ICommand implementación que se
ejecutará cuando SwipeItem se invoque.

NOTE
Cuando se define la apariencia de un SwipeItem solo mediante las Text IconImageSource propiedades o, el contenido
siempre se centra.

Además de definir los elementos de deslizar rápidamente como SwipeItem objetos, también es posible definir
vistas de elemento de deslizamiento personalizado. Para obtener más información, vea elementos de
deslizamiento personalizado.

Deslizar rápidamente
SwipeView admite cuatro direcciones de deslizamiento diferentes, con la dirección de deslizamiento que se define
en la colección direcciona SwipeItems a la que SwipeItem se agregan los objetos. Cada dirección de
deslizamiento puede mantener sus propios elementos de deslizamiento. Por ejemplo, en el ejemplo siguiente se
muestra un SwipeView cuyos elementos de deslizamiento dependen de la dirección de deslizamiento:

<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding DeleteCommand}" />
</SwipeItems>
</SwipeView.LeftItems>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Command="{Binding FavoriteCommand}" />
<SwipeItem Text="Share"
IconImageSource="share.png"
BackgroundColor="LightYellow"
Command="{Binding ShareCommand}" />
</SwipeItems>
</SwipeView.RightItems>
<!-- Content -->
</SwipeView>

En este ejemplo, el SwipeView contenido se puede deslizar hacia la derecha o hacia la izquierda. Al deslizar el
dedo hacia la derecha, se mostrará el elemento de deslizar rápidamente el dedo, mientras que al pasar a la
izquierda se mostrarán los elementos de deslizamiento favorito y de uso compar tido .
WARNING
Solo se puede establecer una instancia de una SwipeItems colección direccional a la vez en SwipeView . Por lo tanto, no
puede tener dos LeftItems definiciones en SwipeView .

Los SwipeStarted eventos, y SwipeEnded notifican la dirección de deslizamiento a través de la


SwipeChanging
SwipeDirection propiedad en los argumentos del evento. Esta propiedad es de tipo SwipeDirection , que es una
enumeración que consta de cuatro miembros:
Right indica que se ha producido un dedo a la derecha.
Left indica que se ha producido un deslizamiento a la izquierda.
Up indica que se ha producido un deslizamiento hacia arriba.
Down indica que se ha producido un deslizamiento hacia abajo.

Modo de deslizar rápidamente


La SwipeItems clase tiene una Mode propiedad, que indica el efecto de una interacción de deslizar rápidamente.
Esta propiedad debe establecerse en uno de los SwipeMode miembros de enumeración:
indica que un deslizador revela los elementos de deslizamiento. Este es el valor predeterminado de la
Reveal
propiedad SwipeItems.Mode .
Execute indica que un dedo ejecuta los elementos de deslizamiento.

En el modo de visualización, el usuario desliza el dedo SwipeView para abrir un menú que consta de uno o varios
elementos de deslizamiento y debe pulsar explícitamente en un elemento de deslizamiento para ejecutarlo. Una
vez que se haya ejecutado el dedo, se cerrarán los elementos de deslizamiento y SwipeView se volverá a mostrar
el contenido. En el modo de ejecución, el usuario desliza el dedo SwipeView para abrir un menú que consta de
uno o varios elementos de deslizamiento, que se ejecutan automáticamente. Después de la ejecución, se cierran
los elementos de deslizamiento y SwipeView se vuelve a mostrar el contenido.
En el ejemplo siguiente se muestra una SwipeView configurada para usar el modo de ejecución:

<SwipeView>
<SwipeView.LeftItems>
<SwipeItems Mode="Execute">
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding DeleteCommand}" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>

En este ejemplo, el SwipeView contenido se puede deslizar hacia la derecha para mostrar el elemento de deslizar
rápidamente, que se ejecuta inmediatamente. Después de la ejecución, SwipeView se vuelve a mostrar el
contenido.

Comportamiento de deslizar rápidamente


La SwipeItems clase tiene una SwipeBehaviorOnInvoked propiedad, que indica cómo SwipeView se comporta una
vez que se invoca un elemento de deslizamiento. Esta propiedad debe establecerse en uno de los
SwipeBehaviorOnInvoked miembros de enumeración:
Auto indica que en modo de revelar SwipeView se cierra después de invocar un elemento de deslizamiento y
en modo de ejecución, el SwipeView permanece abierto después de invocar un elemento de deslizamiento.
Este es el valor predeterminado de la propiedad SwipeItems.SwipeBehaviorOnInvoked .
Close indica que SwipeView se cierra después de invocar un elemento de deslizamiento.
RemainOpen indica que SwipeView permanece abierto después de invocar un elemento de deslizamiento.

En el ejemplo siguiente se muestra una SwipeView configurada para que permanezca abierta después de invocar
un elemento de deslizamiento:

<SwipeView>
<SwipeView.LeftItems>
<SwipeItems SwipeBehaviorOnInvoked="RemainOpen">
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>

Elementos deslizantes personalizados


Los elementos de deslizamiento personalizado se pueden definir con el SwipeItemView tipo. La SwipeItemView
clase se deriva de la ContentView clase y agrega las siguientes propiedades:
Command , de tipo , que se ejecuta cuando se puntea un elemento de deslizamiento.
ICommand
CommandParameter , de tipo object , que es el parámetro que se pasa a Command .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
La SwipeItemView clase también define un Invoked evento que se desencadena cuando se puntea el elemento,
después de que Command se ejecute.
En el ejemplo siguiente se muestra un SwipeItemView objeto de la LeftItems colección de SwipeView :
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItemView Command="{Binding CheckAnswerCommand}"
CommandParameter="{Binding Source={x:Reference resultEntry}, Path=Text}">
<StackLayout Margin="10"
WidthRequest="300">
<Entry x:Name="resultEntry"
Placeholder="Enter answer"
HorizontalOptions="CenterAndExpand" />
<Label Text="Check"
FontAttributes="Bold"
HorizontalOptions="Center" />
</StackLayout>
</SwipeItemView>
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>

En este ejemplo, el objeto SwipeItemView incluye un objeto StackLayout que contiene un objeto Entry y un
objeto Label . Una vez que el usuario escribe la entrada en el Entry , se puede puntear en el resto de,
SwipeViewItem que ejecuta la ICommand definida por la SwipeItemView.Command propiedad.

Abrir y cerrar un SwipeView mediante programación


SwipeView incluye Open Close métodos y, que abren y cierran los elementos de deslizamiento mediante
programación, respectivamente.
El Open método requiere un OpenSwipeItem argumento, para especificar la dirección desde la que se SwipeView
abrirá. La OpenSwipeItem enumeración tiene cuatro miembros:
LeftItems , que indica que se SwipeView abrirá a la izquierda para que se muestren los elementos de
deslizamiento en la LeftItems colección.
TopItems , que indica que se SwipeView abrirá desde la parte superior para mostrar los elementos de
deslizamiento en la TopItems colección.
RightItems , que indica que se SwipeView abrirá desde la derecha para mostrar los elementos que se
desconectan en la RightItems colección.
BottomItems , que indica que se SwipeView abrirá desde la parte inferior, para mostrar los elementos de
deslizamiento en BottomItems la colección.

Dado un SwipeView denominado swipeView , en el ejemplo siguiente se muestra cómo abrir un SwipeView para
mostrar los elementos de deslizamiento en la LeftItems colección:

swipeView.Open(OpenSwipeItem.LeftItems);

swipeView A continuación, se puede cerrar con el Close método:

swipeView.Close();

NOTE
Cuando Close se invoca el método, CloseRequested se desencadena el evento.
Deshabilitar un SwipeView
Una aplicación puede entrar en un estado en el que deslizar un elemento de contenido no es una operación
válida. En tales casos, SwipeView se puede deshabilitar estableciendo su IsEnabled propiedad en false . Esto
impedirá que los usuarios puedan pasar el dedo por el contenido para mostrar los elementos de deslizamiento.
Además, al definir la Command propiedad de un SwipeItem objeto o SwipeItemView , CanExecute se puede
especificar el delegado de ICommand para habilitar o deshabilitar el elemento de deslizar rápidamente.

Vínculos relacionados
SwipeView (ejemplo)
:::no-loc(Xamarin.Forms)::: Objetos
:::no-loc(Xamarin.Forms)::: Casilla
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: CheckBox Es un tipo de botón que puede estar activado o vacío. Cuando se activa una
casilla, se considera que está activada. Cuando una casilla está vacía, se considera que está desactivada.
CheckBox define una bool propiedad denominada IsChecked , que indica si CheckBox está activada. Esta
propiedad también está respaldada por un BindableProperty objeto, lo que significa que se puede aplicar un estilo
a y ser el destino de los enlaces de datos.

NOTE
La IsChecked propiedad enlazable tiene un modo de enlace predeterminado de BindingMode.TwoWay .

CheckBox define un CheckedChanged evento que se desencadena cuando IsChecked cambia la propiedad, ya sea a
través de la manipulación de usuarios o cuando una aplicación establece la IsChecked propiedad. El
CheckedChangedEventArgs objeto que acompaña al CheckedChanged evento tiene una propiedad única denominada
Value , de tipo bool . Cuando se desencadena el evento, el valor de la Value propiedad se establece en el nuevo
valor de la IsChecked propiedad.

Crear una casilla


En el ejemplo siguiente se muestra cómo crear una instancia CheckBox de en XAML:

<CheckBox />

Este código XAML da como resultado la apariencia que se muestra en las siguientes capturas de pantallas:

De forma predeterminada, CheckBox está vacío. CheckBox Se puede comprobar mediante la manipulación de
usuarios o estableciendo la IsChecked propiedad en true :

<CheckBox IsChecked="true" />

Este código XAML da como resultado la apariencia que se muestra en las siguientes capturas de pantallas:

Como alternativa, CheckBox se puede crear en el código:

CheckBox checkBox = new CheckBox { IsChecked = true };


Responder a un estado de cambio de casilla
Cuando la IsChecked propiedad cambia, ya sea a través de la manipulación del usuario o cuando una aplicación
establece la IsChecked propiedad, se CheckedChanged desencadena el evento. Se puede registrar un controlador
de eventos para este evento para responder al cambio:

<CheckBox CheckedChanged="OnCheckBoxCheckedChanged" />

El archivo de código subyacente contiene el controlador del CheckedChanged evento:

void OnCheckBoxCheckedChanged(object sender, CheckedChangedEventArgs e)


{
// Perform required operation after examining e.Value
}

El argumento es el CheckBox responsable de este evento. Puede utilizar esto para tener acceso al
sender
CheckBox objeto o para distinguir entre varios CheckBox objetos que comparten el mismo CheckedChanged
controlador de eventos.
Como alternativa, se puede registrar un controlador de eventos para el CheckedChanged evento en el código:

CheckBox checkBox = new CheckBox { ... };


checkBox.CheckedChanged += (sender, e) =>
{
// Perform required operation after examining e.Value
};

Enlace de datos de una casilla


El CheckedChanged controlador de eventos se puede eliminar mediante el enlace de datos y los desencadenadores
para responder a un control CheckBox que se está comprobando o está vacío:

<CheckBox x:Name="checkBox" />


<Label Text="Lorem ipsum dolor sit amet, elit rutrum, enim hendrerit augue vitae praesent sed non, lorem
aenean quis praesent pede.">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference checkBox}, Path=IsChecked}"
Value="true">
<Setter Property="FontAttributes"
Value="Italic, Bold" />
<Setter Property="FontSize"
Value="Large" />
</DataTrigger>
</Label.Triggers>
</Label>

En este ejemplo, Label utiliza una expresión de enlace en un desencadenador de datos para supervisar la
IsChecked propiedad de CheckBox . Cuando esta propiedad se convierte en true , las FontAttributes FontSize
propiedades y del Label cambio. Cuando la IsChecked propiedad vuelve a false , las FontAttributes FontSize
propiedades y de Label se restablecen a su estado inicial.
En las siguientes capturas de pantalla, la captura de pantalla de iOS muestra el Label formato cuando CheckBox
está vacío, mientras que en la captura de pantalla de Android se muestra el Label formato cuando CheckBox se
comprueba el:
Para obtener más información acerca de los desencadenadores, vea :::no-loc(Xamarin.Forms)::: desencadenadores.

Deshabilitar una casilla


A veces, una aplicación entra en un estado en el que un CheckBox que se está comprobando no es una operación
válida. En tales casos, CheckBox se puede deshabilitar estableciendo su IsEnabled propiedad en false .

apariencia de CheckBox
Además de las propiedades que CheckBox heredan de la View clase, CheckBox también define una Color
propiedad que establece su color en Color :

<CheckBox Color="Red" />

En las siguientes capturas de pantallas se muestran una serie de objetos comprobados CheckBox , donde cada
objeto tiene su Color propiedad establecida en un valor diferente Color :

Estados visuales de la casilla


CheckBox tiene un IsChecked VisualState que se puede utilizar para iniciar un cambio visual en CheckBox
cuando se activa.
En el siguiente ejemplo de XAML se muestra cómo definir un estado visual para el IsChecked Estado:

<CheckBox ...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Color"
Value="Red" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="IsChecked">
<VisualState.Setters>
<Setter Property="Color"
Value="Green" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</CheckBox>
En este ejemplo, IsChecked VisualState especifica que cuando CheckBox se comprueba, su propiedad se Color
establecerá en verde. Normal VisualState Especifica que cuando el CheckBox está en un estado normal, su
Color propiedad se establecerá en rojo. Por lo tanto, el efecto general es que el CheckBox es rojo cuando está
vacío y verde cuando está activado.
Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.

Vínculos relacionados
Demostraciones de CheckBox (ejemplo)
Desencadenadores de :::no-loc(Xamarin.Forms):::
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: DatePicker
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Una :::no-loc(Xamarin.Forms)::: vista que permite al usuario seleccionar una fecha.
:::no-loc(Xamarin.Forms)::: DatePicker Invoca el control selector de fecha y de la plataforma y permite al usuario
seleccionar una fecha. DatePicker define ocho propiedades:
MinimumDate de tipo , que tiene como valor predeterminado el primer día del año 1900.
DateTime
MaximumDate de tipo
DateTime , cuyo valor predeterminado es el último día del año 2100.
Date de tipo DateTime , la fecha seleccionada, que tiene como valor predeterminado el valor DateTime.Today .
Format de tipo string , una cadena de formato .net estándar o personalizada , cuyo valor predeterminado es
"D", el patrón de fecha larga.
TextColor de tipo Color , el color que se usa para mostrar la fecha seleccionada, cuyo valor predeterminado
es Color.Default .
FontAttributes de tipo FontAttributes , cuyo valor predeterminado es FontAtributes.None .
FontFamily de tipo string , cuyo valor predeterminado es null .
FontSize de tipo double , que tiene como valor predeterminado-1,0.
CharacterSpacing , del tipo double , es el espaciado entre los caracteres del texto de DatePicker .

DatePicker Desencadena un DateSelected evento cuando el usuario selecciona una fecha.

WARNING
Al establecer MinimumDate y MaximumDate , asegúrese de que MinimumDate siempre es menor o igual que
MaximumDate . De lo contrario, generará DatePicker una excepción.

Internamente, DatePicker garantiza que Date está entre MinimumDate y MaximumDate , ambos incluidos. Si
MinimumDate MaximumDate se establece o para que Date no esté entre ellos, DatePicker ajustará el valor de Date
.
Las ocho propiedades están respaldadas por BindableProperty objetos, lo que significa que se pueden aplicar
estilos y las propiedades pueden ser destinos de los enlaces de datos. La Date propiedad tiene un modo de
enlace predeterminado de BindingMode.TwoWay , lo que significa que puede ser un destino de un enlace de datos
en una aplicación que usa la arquitectura Model-View-ViewModel (MVVM) .

Inicializar las propiedades DateTime


En el código, puede inicializar MinimumDate las MaximumDate propiedades, y Date para los valores de tipo
DateTime :

DatePicker datePicker = new DatePicker


{
MinimumDate = new DateTime(2018, 1, 1),
MaximumDate = new DateTime(2018, 12, 31),
Date = new DateTime(2018, 6, 21)
};
Cuando DateTime se especifica un valor en XAML, el analizador de XAML usa el DateTime.Parse método con un
CultureInfo.InvariantCulture argumento para convertir la cadena en un DateTime valor. Las fechas deben
especificarse en un formato preciso: los meses de dos dígitos, los días de dos dígitos y los años de cuatro dígitos
separados por barras diagonales:

<DatePicker MinimumDate="01/01/2018"
MaximumDate="12/31/2018"
Date="06/21/2018" />

Si la BindingContext propiedad de DatePicker se establece en una instancia de un ViewModel que contiene


propiedades de tipo DateTime denominados MinDate , MaxDate y SelectedDate (por ejemplo,), puede crear una
instancia de de la DatePicker manera siguiente:

<DatePicker MinimumDate="{Binding MinDate}"


MaximumDate="{Binding MaxDate}"
Date="{Binding SelectedDate}" />

En este ejemplo, las tres propiedades se inicializan en las propiedades correspondientes del ViewModel. Dado
Date que la propiedad tiene un modo de enlace de TwoWay , cualquier fecha nueva que seleccione el usuario se
reflejará automáticamente en el ViewModel.
Si no DatePickercontiene un enlace en su Date propiedad, una aplicación debe adjuntar un controlador al
DateSelected evento que se va a notificar cuando el usuario seleccione una nueva fecha.
Para obtener información sobre cómo establecer las propiedades de la fuente, vea fuentes.

DatePicker y diseño
Es posible utilizar una opción de diseño horizontal sin restricciones como Center , Start o End con DatePicker
:

<DatePicker ···
HorizontalOptions="Center"
··· />

Sin embargo, esto no es recomendable. Dependiendo del valor de la Format propiedad, las fechas seleccionadas
pueden requerir anchos de pantalla diferentes. Por ejemplo, la cadena de formato "D" hace que DateTime se
muestren las fechas en un formato largo y "miércoles, 12 de septiembre de 2018" requiere un ancho de pantalla
mayor que "viernes, 4 de mayo de 2018". Dependiendo de la plataforma, esta diferencia podría hacer que la
DateTime vista cambie el ancho en el diseño o que la pantalla se trunque.

TIP
Es preferible usar la configuración predeterminada HorizontalOptions de Fill con DatePicker y no utilizar un ancho
Auto cuando se coloca DatePicker en una Grid celda.

DatePicker en una aplicación


El ejemplo DaysBetweenDates incluye dos DatePicker vistas en su página. Se pueden usar para seleccionar dos
fechas y el programa calcula el número de días entre esas fechas. El programa no cambia la configuración de las
MinimumDate MaximumDate propiedades y, por lo que las dos fechas deben estar entre 1900 y 2100.

Este es el archivo XAML:


<?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:local="clr-namespace:DaysBetweenDates"
x:Class="DaysBetweenDates.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>

<StackLayout Margin="10">
<Label Text="Days Between Dates"
Style="{DynamicResource TitleStyle}"
Margin="0, 20"
HorizontalTextAlignment="Center" />

<Label Text="Start Date:" />

<DatePicker x:Name="startDatePicker"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />

<Label Text="End Date:" />

<DatePicker x:Name="endDatePicker"
MinimumDate="{Binding Source={x:Reference startDatePicker},
Path=Date}"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />

<StackLayout Orientation="Horizontal"
Margin="0, 0, 0, 30">
<Label Text="Include both days in total: "
VerticalOptions="Center" />
<Switch x:Name="includeSwitch"
Toggled="OnSwitchToggled" />
</StackLayout>

<Label x:Name="resultLabel"
FontAttributes="Bold"
HorizontalTextAlignment="Center" />

</StackLayout>
</ContentPage>

A cada una DatePicker se le asigna una Format propiedad de "D" para un formato de fecha larga. Observe
también que el endDatePicker objeto tiene un enlace que tiene como destino su MinimumDate propiedad. El origen
de enlace es la Date propiedad seleccionada del startDatePicker objeto. Esto garantiza que la fecha de
finalización sea siempre posterior o igual a la fecha de inicio. Además de los dos DatePicker objetos, Switch se
etiqueta "incluir ambos días en total".
Las dos DatePicker vistas tienen controladores adjuntados al DateSelected evento y Switch tienen un
controlador asociado a su Toggled evento. Estos controladores de eventos se encuentran en el archivo de código
subyacente y desencadenan un nuevo cálculo de los días entre las dos fechas:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}

void OnDateSelected(object sender, DateChangedEventArgs args)


{
Recalculate();
}

void OnSwitchToggled(object sender, ToggledEventArgs args)


{
Recalculate();
}

void Recalculate()
{
TimeSpan timeSpan = endDatePicker.Date - startDatePicker.Date +
(includeSwitch.IsToggled ? TimeSpan.FromDays(1) : TimeSpan.Zero);

resultLabel.Text = String.Format("{0} day{1} between dates",


timeSpan.Days, timeSpan.Days == 1 ? "" : "s");
}
}

Cuando se ejecuta por primera vez el ejemplo, ambas DatePicker vistas se inicializan en la fecha de hoy. En la
captura de pantalla siguiente se muestra el programa que se ejecuta en iOS y Android:

Al pulsar cualquiera de las DatePicker pantallas se invoca el selector de fecha de la plataforma. Las plataformas
implementan este selector de fecha de maneras muy diferentes, pero cada enfoque lo conocen los usuarios de esa
plataforma:
TIP
En Android, el DatePicker cuadro de diálogo se puede personalizar invalidando el CreateDatePickerDialog método en
un representador personalizado. Esto permite, por ejemplo, agregar botones adicionales al cuadro de diálogo.

Después de seleccionar dos fechas, la aplicación muestra el número de días entre esas fechas:

Vínculos relacionados
Ejemplo de DaysBetweenDates
API de DatePicker
:::no-loc(Xamarin.Forms)::: Hasta
18/12/2020 • 22 minutes to read • Edit Online

Descargar el ejemplo
Use un control deslizante para seleccionar entre un intervalo de valores continuos.
:::no-loc(Xamarin.Forms)::: Slider Es una barra horizontal que el usuario puede manipular para seleccionar un
double valor de un intervalo continuo.

Slider Define tres propiedades de tipo double :


Minimum es el mínimo del intervalo, con un valor predeterminado de 0.
Maximum es el máximo del intervalo, con un valor predeterminado de 1.
Value es el valor del control deslizante, que puede oscilar entre Minimum y Maximum y tiene un valor
predeterminado de 0.
Las tres propiedades están respaldadas por BindableProperty objetos. La Value propiedad tiene un modo de
enlace predeterminado de BindingMode.TwoWay , lo que significa que es adecuado como origen de enlace en una
aplicación que usa la arquitectura Model-View-ViewModel (MVVM) .

WARNING
Internamente, Slider garantiza que Minimum es menor que Maximum . Si Minimum o Maximum se establecen en
cualquier momento para que Minimum no sea menor que Maximum , se produce una excepción. Vea la sección
precauciones a continuación para obtener más información sobre cómo establecer las Minimum Maximum propiedades
y.

Slider Convierte la propiedad de Value modo que esté entre Minimum y Maximum , ambos incluidos. Si la
Minimum propiedad se establece en un valor mayor que la Value propiedad, Slider establece la Value
propiedad en Minimum . Del mismo modo, si Maximum se establece en un valor menor que Value , Slider
establece la Value propiedad en Maximum .
Slider define un ValueChanged evento que se desencadena cuando Value cambia, ya sea a través de la
manipulación del usuario Slider o cuando el programa establece la Value propiedad directamente.
ValueChanged También se desencadena un evento cuando la Value propiedad se convierte como se describe en
el párrafo anterior.
El objeto que acompaña al ValueChanged evento tiene dos propiedades, ambas de tipo
ValueChangedEventArgs
double : OldValue y NewValue . En el momento en que se desencadena el evento, el valor de NewValue es igual
que la Value propiedad del Slider objeto.
Slider también define DragStarted DragCompleted los eventos y, que se desencadenan al principio y al final de
la acción de arrastrar. A diferencia del ValueChanged evento, los DragStarted DragCompleted eventos y solo se
activan a través de la manipulación de usuarios de Slider . Cuando DragStarted se activa el evento,
DragStartedCommand se ejecuta el tipo de ICommand . De forma similar, cuando DragCompleted se desencadena el
evento, DragCompletedCommand se ejecuta el tipo de ICommand .
WARNING
No utilice las opciones de diseño horizontal sin restricciones de Center , Start o End con Slider . Tanto en Android
como en UWP, Slider se contrae en una barra de longitud cero y, en iOS, la barra es muy breve. Mantenga el
HorizontalOptions valor predeterminado de Fill y no use el ancho Auto al colocar Slider un Grid diseño.

Slider También define varias propiedades que afectan a su apariencia:


MinimumTrackColor es el color de la barra que se encuentra en el lado izquierdo del control.
MaximumTrackColor es el color de la barra que se encuentra en el lado derecho del control.
ThumbColor es el color del control de posición.
ThumbImageSource es la imagen que se va a usar para el control de posición, de tipo ImageSource .

NOTE
Las ThumbColor ThumbImageSource propiedades y son mutuamente excluyentes. Si se establecen ambas propiedades, la
ThumbImageSource propiedad tendrá prioridad.

Código y marcado del control deslizante básico


El ejemplo SliderDemos comienza con tres páginas que son funcionalmente idénticas, pero se implementan de
maneras diferentes. La primera página solo usa código C#, la segunda usa XAML con un controlador de eventos
en el código y la tercera puede evitar el controlador de eventos mediante el enlace de datos en el archivo XAML.
Crear un control deslizante en el código
La página de códigos del control deslizante básico en el ejemplo SliderDemos muestra Mostrar para crear
un Slider y dos Label objetos en el código:
public class BasicSliderCodePage : ContentPage
{
public BasicSliderCodePage()
{
Label rotationLabel = new Label
{
Text = "ROTATING TEXT",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

Label displayLabel = new Label


{
Text = "(uninitialized)",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

Slider slider = new Slider


{
Maximum = 360
};
slider.ValueChanged += (sender, args) =>
{
rotationLabel.Rotation = slider.Value;
displayLabel.Text = String.Format("The Slider value is {0}", args.NewValue);
};

Title = "Basic Slider Code";


Padding = new Thickness(10, 0);
Content = new StackLayout
{
Children =
{
rotationLabel,
slider,
displayLabel
}
};
}
}

Slider Se inicializa para tener una Maximum propiedad de 360. El ValueChanged controlador de Slider utiliza la
Value propiedad del slider objeto para establecer la Rotation propiedad de la primera Label y usa el
String.Format método con la NewValue propiedad de los argumentos del evento para establecer la Text
propiedad de la segunda Label . Estos dos enfoques para obtener el valor actual de Slider son
intercambiables.
Este es el programa que se ejecuta en dispositivos iOS y Android:
En el segundo Label se muestra el texto "(no inicializado)" hasta que Slider se manipula, lo que hace que se
desencadene el primer ValueChanged evento. Tenga en cuenta que el número de posiciones decimales que se
muestran es diferente para cada plataforma. Estas diferencias están relacionadas con las implementaciones de la
plataforma de Slider y se describen más adelante en este artículo en la sección diferencias de implementación
de plataforma.
Crear un control deslizante en XAML
La página XAML del control deslizante básico es funcionalmente igual que el código deslizante básico ,
pero se implementa principalmente en XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SliderDemos.BasicSliderXamlPage"
Title="Basic Slider XAML"
Padding="10, 0">
<StackLayout>
<Label x:Name="rotatingLabel"
Text="ROTATING TEXT"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider Maximum="360"
ValueChanged="OnSliderValueChanged" />

<Label x:Name="displayLabel"
Text="(uninitialized)"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

El archivo de código subyacente contiene el controlador del ValueChanged evento:


public partial class BasicSliderXamlPage : ContentPage
{
public BasicSliderXamlPage()
{
InitializeComponent();
}

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)


{
double value = args.NewValue;
rotatingLabel.Rotation = value;
displayLabel.Text = String.Format("The Slider value is {0}", value);
}
}

También es posible que el controlador de eventos obtenga el Slider que está desencadenando el evento a
través del sender argumento. La Value propiedad contiene el valor actual:

double value = ((Slider)sender).Value;

Si al Slider objeto se le asignó un nombre en el archivo XAML con un x:Name atributo (por ejemplo, "slider"), el
controlador de eventos podría hacer referencia a ese objeto directamente:

double value = slider.Value;

Enlace de datos del control deslizante


En la página de enlaces del control deslizante básico se muestra cómo escribir un programa casi
equivalente que elimina el controlador de eventos mediante el Value enlace de datos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SliderDemos.BasicSliderBindingsPage"
Title="Basic Slider Bindings"
Padding="10, 0">
<StackLayout>
<Label Text="ROTATING TEXT"
Rotation="{Binding Source={x:Reference slider},
Path=Value}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />

<Slider x:Name="slider"
Maximum="360" />

<Label x:Name="displayLabel"
Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='The Slider value is {0:F0}'}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

La Rotation propiedad de la primera Label se enlaza a la Value propiedad de Slider , como es la Text
propiedad del segundo Label con una StringFormat especificación. La página de enlaces del control
deslizante básico funciona de forma ligeramente diferente a la de las dos páginas anteriores: cuando la página
aparece por primera vez, la segunda Label muestra la cadena de texto con el valor. Esta es una de las ventajas
de usar el enlace de datos. Para mostrar texto sin enlace de datos, tendría que inicializar específicamente la Text
propiedad de Label o simular una activación del evento llamando ValueChanged al controlador de eventos
desde el constructor de clase.

PRECAUCIONES
El valor de la Minimum propiedad siempre debe ser menor que el valor de la Maximum propiedad. El fragmento de
código siguiente hace que Slider genere una excepción:

// Throws an exception!
Slider slider = new Slider
{
Minimum = 10,
Maximum = 20
};

El compilador de C# genera código que establece estas dos propiedades en secuencia y, cuando la Minimum
propiedad se establece en 10, es mayor que el Maximum valor predeterminado de 1. Puede evitar la excepción en
este caso estableciendo Maximum primero la propiedad:

Slider slider = new Slider


{
Maximum = 20,
Minimum = 10
};

Establecer Maximum en 20 no es un problema porque es mayor que el valor predeterminado Minimum de 0.


Cuando Minimum se establece, el valor es menor que el Maximum valor de 20.
El mismo problema existe en XAML. Establezca las propiedades en un orden que garantice que Maximum siempre
sea mayor que Minimum :

<Slider Maximum="20"
Minimum="10" ... />

Puede establecer los Minimum valores y Maximum en números negativos, pero solo en un orden en el que
Minimum siempre sea menor que Maximum :

<Slider Minimum="-20"
Maximum="-10" ... />

La Value propiedad siempre es mayor o igual que el Minimum valor y menor o igual que Maximum . Si Value se
establece en un valor fuera de ese intervalo, el valor se convertirá para que se encuentre dentro del intervalo,
pero no se genera ninguna excepción. Por ejemplo, este código no producirá una excepción:

Slider slider = new Slider


{
Value = 10
};

En su lugar, la Value propiedad se convierte en el Maximum valor de 1.


A continuación se muestra un fragmento de código mostrado anteriormente:
Slider slider = new Slider
{
Maximum = 20,
Minimum = 10
};

Cuando Minimum está establecido en 10, Value también se establece en 10.


Si se ha ValueChanged adjuntado un controlador de eventos en el momento en que la Value propiedad se
convierte en algo distinto de su valor predeterminado de 0, ValueChanged se desencadena un evento. A
continuación se muestra un fragmento de código XAML:

<Slider ValueChanged="OnSliderValueChanged"
Maximum="20"
Minimum="10" />

Cuando Minimum se establece en 10, Value también se establece en 10 y ValueChanged se desencadena el


evento. Esto puede ocurrir antes de que se haya construido el resto de la página y el controlador podría intentar
hacer referencia a otros elementos de la página que aún no se han creado. Es posible que desee agregar código
al ValueChanged controlador que compruebe null los valores de otros elementos de la página. O bien, puede
establecer el ValueChanged controlador de eventos después de Slider inicializar los valores.

Diferencias de implementación de plataforma


Las capturas de pantalla mostradas anteriormente muestran el valor de Slider con un número diferente de
separadores decimales. Esto se refiere al modo Slider en que se implementa en las plataformas Android y UWP.
La implementación de Android
La implementación de Android de Slider se basa en Android SeekBar y siempre establece la Max propiedad
en 1000. Esto significa que Slider en Android solo tiene 1.001 valores discretos. Si establece para que Slider
tenga un Minimum de 0 y un Maximum de 5000, a medida que Slider se manipule, la Value propiedad tiene los
valores 0, 5, 10, 15, etc.
La implementación de UWP
La implementación de UWP de Slider se basa en el Slider control de UWP. La StepFrequency propiedad de
UWP Slider se establece en la diferencia de las Maximum propiedades y Minimum divididas entre 10, pero no
mayor que 1.
Por ejemplo, para el intervalo predeterminado de 0 a 1, la StepFrequency propiedad se establece en 0,1. A
medida que Slider se manipula, la Value propiedad está restringida a 0, 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, 0,9 y
1,0. (Esto es evidente en la última página del ejemplo SliderDemos ). Cuando la diferencia entre las Maximum
Minimum propiedades y es 10 o superior, StepFrequency se establece en 1 y la Value propiedad tiene valores
enteros.
La solución StepSlider
Un más versátil StepSlider se describe en el capítulo 27. Los representadores personalizados del libro que
crean :::no-loc(Xamarin.Forms)::: Mobile Apps con. StepSlider Es similar a Slider , pero agrega una Steps
propiedad para especificar el número de valores entre Minimum y Maximum .

Controles deslizantes para la selección de color


Las dos páginas finales del ejemplo SliderDemos usan tres Slider instancias para la selección de color. La
primera página controla todas las interacciones del archivo de código subyacente, mientras que la segunda
página muestra cómo usar el enlace de datos con un ViewModel.
Control de controles deslizantes en el archivo de código subyacente
La página de controles deslizantes de color RGB crea una instancia de BoxView para mostrar un color, tres
Slider instancias para seleccionar los componentes rojo, verde y azul del color, y tres Label elementos para
mostrar esos valores de color:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SliderDemos.RgbColorSlidersPage"
Title="RGB Color Sliders">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
<Setter Property="Maximum" Value="255" />
</Style>

<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Margin="10">
<BoxView x:Name="boxView"
Color="Black"
VerticalOptions="FillAndExpand" />

<Slider x:Name="redSlider"
ValueChanged="OnSliderValueChanged" />

<Label x:Name="redLabel" />

<Slider x:Name="greenSlider"
ValueChanged="OnSliderValueChanged" />

<Label x:Name="greenLabel" />

<Slider x:Name="blueSlider"
ValueChanged="OnSliderValueChanged" />

<Label x:Name="blueLabel" />


</StackLayout>
</ContentPage>

Una Style asigna a los tres Slider elementos un intervalo de 0 a 255. Los Slider elementos comparten el
mismo ValueChanged controlador, que se implementa en el archivo de código subyacente:
public partial class RgbColorSlidersPage : ContentPage
{
public RgbColorSlidersPage()
{
InitializeComponent();
}

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)


{
if (sender == redSlider)
{
redLabel.Text = String.Format("Red = {0:X2}", (int)args.NewValue);
}
else if (sender == greenSlider)
{
greenLabel.Text = String.Format("Green = {0:X2}", (int)args.NewValue);
}
else if (sender == blueSlider)
{
blueLabel.Text = String.Format("Blue = {0:X2}", (int)args.NewValue);
}

boxView.Color = Color.FromRgb((int)redSlider.Value,
(int)greenSlider.Value,
(int)blueSlider.Value);
}
}

En la primera sección se establece la Text propiedad de una de las Label instancias de en una cadena de texto
corta que indica el valor de Slider en hexadecimal. A continuación, Slider se tiene acceso a las tres instancias
para crear un Color valor a partir de los componentes RGB:

Enlazar el control deslizante a un ViewModel


La página de controles deslizantes de color HSL muestra cómo usar un ViewModel para realizar los cálculos
utilizados para crear un Color valor a partir de los valores de matiz, saturación y luminosidad. Al igual que
todos los ViewModels, la HSLColorViewModel clase implementa la INotifyPropertyChanged interfaz y activa un
PropertyChanged evento cada vez que cambia una de las propiedades:

public class HslColorViewModel : INotifyPropertyChanged


{
Color color;

public event PropertyChangedEventHandler PropertyChanged;


public event PropertyChangedEventHandler PropertyChanged;

public double Hue


{
set
{
if (color.Hue != value)
{
Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
}
}
get
{
return color.Hue;
}
}

public double Saturation


{
set
{
if (color.Saturation != value)
{
Color = Color.FromHsla(color.Hue, value, color.Luminosity);
}
}
get
{
return color.Saturation;
}
}

public double Luminosity


{
set
{
if (color.Luminosity != value)
{
Color = Color.FromHsla(color.Hue, color.Saturation, value);
}
}
get
{
return color.Luminosity;
}
}

public Color Color


{
set
{
if (color != value)
{
color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));
}
}
get
{
return color;
}
}
}

ViewModels y la INotifyPropertyChanged interfaz se describen en el artículo enlace de datos.


El archivo HslColorSlidersPage. Xaml crea una instancia de HslColorViewModel y lo establece en la propiedad
de la página BindingContext . Esto permite que todos los elementos del archivo XAML se enlacen a las
propiedades de ViewModel:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SliderDemos"
x:Class="SliderDemos.HslColorSlidersPage"
Title="HSL Color Sliders">

<ContentPage.BindingContext>
<local:HslColorViewModel Color="Chocolate" />
</ContentPage.BindingContext>

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout Margin="10">
<BoxView Color="{Binding Color}"
VerticalOptions="FillAndExpand" />

<Slider Value="{Binding Hue}" />


<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />

<Slider Value="{Binding Saturation}" />


<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />

<Slider Value="{Binding Luminosity}" />


<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</ContentPage>

A medida que Slider se manipulan los elementos, los BoxView Label elementos y se actualizan desde el
ViewModel:

El StringFormat componente de la Binding extensión de marcado se establece para que el formato "F2"
muestre dos posiciones decimales. (El formato de cadena en los enlaces de datos se describe en el artículo
formato de cadena). Sin embargo, la versión de UWP del programa se limita a los valores 0, 0,1, 0,2,... 0,9 y 1,0.
Este es un resultado directo de la implementación de UWP Slider tal y como se ha descrito anteriormente en la
sección diferencias de implementación de plataforma.

Vínculos relacionados
Ejemplo de demostraciones de control deslizante
Slider API
:::no-loc(Xamarin.Forms)::: Hacer
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
Use un stepper para seleccionar un valor numérico de un intervalo de valores.
:::no-loc(Xamarin.Forms)::: Stepper Consta de dos botones etiquetados con signos menos y más. El usuario puede
manipular estos botones para seleccionar incrementalmente un double valor de un intervalo de valores.
Stepper Define cuatro propiedades de tipo double :
Increment es la cantidad por la que se va a cambiar el valor seleccionado, con un valor predeterminado de 1.
Minimum es el mínimo del intervalo, con un valor predeterminado de 0.
Maximum es el máximo del intervalo, con un valor predeterminado de 100.
Value es el valor del stepper, que puede oscilar entre Minimum y Maximum y tiene un valor predeterminado de
0.
Todas estas propiedades están respaldadas por BindableProperty objetos. La Value propiedad tiene un modo de
enlace predeterminado de BindingMode.TwoWay , lo que significa que es adecuado como origen de enlace en una
aplicación que usa la arquitectura Model-View-ViewModel (MVVM) .

WARNING
Internamente, Stepper garantiza que Minimum es menor que Maximum . Si Minimum o Maximum se establecen en
cualquier momento para que Minimum no sea menor que Maximum , se produce una excepción. Para obtener más
información sobre cómo establecer las Minimum Maximum propiedades y, consulte la sección precauciones .

Stepper Convierte la propiedad de Value modo que esté entre Minimum y Maximum , ambos incluidos. Si la
Minimum propiedad se establece en un valor mayor que la Value propiedad, Stepper establece la Value
propiedad en Minimum . Del mismo modo, si Maximum se establece en un valor menor que Value , Stepper
establece la Value propiedad en Maximum .
Stepper define un ValueChanged evento que se desencadena cuando Value cambia, ya sea a través de la
manipulación del usuario Stepper o cuando la aplicación establece la Value propiedad directamente.
ValueChanged También se desencadena un evento cuando la Value propiedad se convierte como se describe en el
párrafo anterior.
El objeto que acompaña al ValueChanged evento tiene dos propiedades, ambas de tipo
ValueChangedEventArgs
double : OldValue y NewValue . En el momento en que se desencadena el evento, el valor de NewValue es igual
que la Value propiedad del Stepper objeto.

Código y marcado de stepper básico


El ejemplo StepperDemos contiene tres páginas que son funcionalmente idénticas, pero se implementan de
maneras diferentes. En la primera página solo se usa código C#, el segundo usa XAML con un controlador de
eventos en el código y el tercero puede evitar el controlador de eventos mediante el enlace de datos en el archivo
XAML.
Creación de un stepper en el código
La página de códigos de stepper Básica en el ejemplo StepperDemos muestra cómo crear un Stepper y dos
Label objetos en el código:

public class BasicStepperCodePage : ContentPage


{
public BasicStepperCodePage()
{
Label rotationLabel = new Label
{
Text = "ROTATING TEXT",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

Label displayLabel = new Label


{
Text = "(uninitialized)",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};

Stepper stepper = new Stepper


{
Maximum = 360,
Increment = 30,
HorizontalOptions = LayoutOptions.Center
};
stepper.ValueChanged += (sender, e) =>
{
rotationLabel.Rotation = stepper.Value;
displayLabel.Text = string.Format("The Stepper value is {0}", e.NewValue);
};

Title = "Basic Stepper Code";


Content = new StackLayout
{
Margin = new Thickness(20),
Children = { rotationLabel, stepper, displayLabel }
};
}
}

Stepper Se inicializa para tener una Maximum propiedad de 360 y una Increment propiedad de 30. La
manipulación de Stepper cambia el valor seleccionado incrementalmente entre Minimum a Maximum basándose
en el valor de la Increment propiedad. El ValueChanged controlador de Stepper utiliza la Value propiedad del
stepper objeto para establecer la Rotation propiedad de la primera Label y usa el string.Format método con
la NewValue propiedad de los argumentos del evento para establecer la Text propiedad de la segunda Label .
Estos dos enfoques para obtener el valor actual de Stepper son intercambiables.
Las capturas de pantallas siguientes muestran la página de códigos de stepper Básica :
En el segundo Label se muestra el texto "(no inicializado)" hasta que Stepper se manipula, lo que hace que se
desencadene el primer ValueChanged evento.
Crear un stepper en XAML
La página XAML de stepper Básica es funcionalmente igual que el código de stepper básico , pero se
implementa principalmente en XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StepperDemo.BasicStepperXAMLPage"
Title="Basic Stepper XAML">
<StackLayout Margin="20">
<Label x:Name="_rotatingLabel"
Text="ROTATING TEXT"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Stepper Maximum="360"
Increment="30"
HorizontalOptions="Center"
ValueChanged="OnStepperValueChanged" />
<Label x:Name="_displayLabel"
Text="(uninitialized)"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

El archivo de código subyacente contiene el controlador del ValueChanged evento:


public partial class BasicStepperXAMLPage : ContentPage
{
public BasicStepperXAMLPage()
{
InitializeComponent();
}

void OnStepperValueChanged(object sender, ValueChangedEventArgs e)


{
double value = e.NewValue;
_rotatingLabel.Rotation = value;
_displayLabel.Text = string.Format("The Stepper value is {0}", value);
}
}

También es posible que el controlador de eventos obtenga el Stepper que está desencadenando el evento a
través del sender argumento. La Value propiedad contiene el valor actual:

double value = ((Stepper)sender).Value;

Si al Stepper objeto se le asignó un nombre en el archivo XAML con un x:Name atributo (por ejemplo, "stepper"),
el controlador de eventos podría hacer referencia a ese objeto directamente:

double value = stepper.Value;

Enlazar datos al stepper


La página de enlaces de stepper Básica muestra cómo escribir una aplicación casi equivalente que elimina el
Value controlador de eventos mediante el enlace de datos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StepperDemo.BasicStepperBindingsPage"
Title="Basic Stepper Bindings">
<StackLayout Margin="20">
<Label Text="ROTATING TEXT"
Rotation="{Binding Source={x:Reference _stepper}, Path=Value}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Stepper x:Name="_stepper"
Maximum="360"
Increment="30"
HorizontalOptions="Center" />
<Label Text="{Binding Source={x:Reference _stepper}, Path=Value, StringFormat='The Stepper value is
{0:F0}'}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>

La Rotation propiedad de la primera Label se enlaza a la Value propiedad de Stepper , como es la Text
propiedad del segundo Label con una StringFormat especificación. La página de enlaces de stepper Básica
funciona de forma ligeramente diferente a la de las dos páginas anteriores: cuando la página aparece por primera
vez, la segunda Label muestra la cadena de texto con el valor. Esta es una de las ventajas de usar el enlace de
datos. Para mostrar texto sin enlace de datos, tendría que inicializar específicamente la Text propiedad de Label
o simular una activación del evento llamando ValueChanged al controlador de eventos desde el constructor de
clase.
PRECAUCIONES
El valor de la Minimum propiedad siempre debe ser menor que el valor de la Maximum propiedad. El fragmento de
código siguiente hace que Stepper genere una excepción:

// Throws an exception!
Stepper stepper = new Stepper
{
Minimum = 180,
Maximum = 360
};

El compilador de C# genera código que establece estas dos propiedades en secuencia y, cuando la Minimum
propiedad se establece en 180, es mayor que el Maximum valor predeterminado de 100. Puede evitar la excepción
en este caso estableciendo Maximum primero la propiedad:

Stepper stepper = new Stepper


{
Maximum = 360,
Minimum = 180
};

Establecer Maximum en 360 no es un problema porque es mayor que el valor predeterminado Minimum de 0.
Cuando Minimum se establece, el valor es menor que el Maximum valor de 360.
El mismo problema existe en XAML. Establezca las propiedades en un orden que garantice que Maximum siempre
sea mayor que Minimum :

<Stepper Maximum="360"
Minimum="180" ... />

Puede establecer los Minimum valores y Maximum en números negativos, pero solo en un orden en el que Minimum
siempre sea menor que Maximum :

<Stepper Minimum="-360"
Maximum="-180" ... />

La Value propiedad siempre es mayor o igual que el Minimum valor y menor o igual que Maximum . Si Value se
establece en un valor fuera de ese intervalo, el valor se convertirá para que se encuentre dentro del intervalo, pero
no se genera ninguna excepción. Por ejemplo, este código no producirá una excepción:

Stepper stepper = new Stepper


{
Value = 180
};

En su lugar, la Value propiedad se convierte en el Maximum valor de 100.


A continuación se muestra un fragmento de código mostrado anteriormente:
Stepper stepper = new Stepper
{
Maximum = 360,
Minimum = 180
};

Cuando Minimum se establece en 180, Value también se establece en 180.


Si se ha ValueChanged adjuntado un controlador de eventos en el momento en que la Value propiedad se
convierte en algo distinto de su valor predeterminado de 0, ValueChanged se desencadena un evento. A
continuación se muestra un fragmento de código XAML:

<Stepper ValueChanged="OnStepperValueChanged"
Maximum="360"
Minimum="180" />

Cuando Minimum se establece en 180, Value también se establece en 180 y ValueChanged se desencadena el
evento. Esto puede ocurrir antes de que se haya construido el resto de la página y el controlador podría intentar
hacer referencia a otros elementos de la página que aún no se han creado. Es posible que desee agregar código al
ValueChanged controlador que compruebe null los valores de otros elementos de la página. O bien, puede
establecer el ValueChanged controlador de eventos después de Stepper inicializar los valores.

Vínculos relacionados
Ejemplo de demostraciones de stepper
API de stepper
:::no-loc(Xamarin.Forms)::: Conmutador
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms)::: Switch control es un botón de alternancia horizontal que el usuario puede manipular
para alternar entre los Estados on y OFF, que están representados por un boolean valor. La Switch clase hereda
de View .
Las siguientes capturas de pantallas muestran un Switch control en su estado de alternancia activado y
desactivado en iOS y Android:

El Switch control define las siguientes propiedades:


IsToggled es un boolean valor que indica si Switch está activado .
OnColor es un Color que afecta al modo Switch en que se representa en el estado de alternancia o activada .
ThumbColor es el Color de la Thumb del modificador.

Estas propiedades están respaldadas por un BindableProperty objeto, lo que significa que se puede aplicar un
Switch estilo a y ser el destino de los enlaces de datos.

El Switch control define un Toggled evento que se desencadena cuando IsToggled cambia la propiedad, ya sea
a través de la manipulación de usuarios o cuando una aplicación establece la IsToggled propiedad. El
ToggledEventArgs objeto que acompaña al Toggled evento tiene una propiedad única denominada Value , de
tipo bool . Cuando se desencadena el evento, el valor de la Value propiedad refleja el nuevo valor de la
IsToggled propiedad.

Crear un conmutador
Switch Se puede crear una instancia de en XAML. Su IsToggled propiedad se puede establecer para alternar
Switch . De forma predeterminada, la IsToggled propiedad es false . En el ejemplo siguiente se muestra cómo
crear una instancia Switch de en XAML con el IsToggled conjunto de propiedades opcional:

<Switch IsToggled="true"/>

Switch También se puede crear en el código:

Switch switchControl = new Switch { IsToggled = true };

Cambiar apariencia
Además de las propiedades que Switch heredan de la View clase, Switch también define OnColor ThumbColor
las propiedades y. La OnColor propiedad se puede establecer para definir el Switch color cuando se cambia a su
estado, on y la ThumbColor propiedad se puede establecer para definir el Color de la Thumb del conmutador. En
el ejemplo siguiente se muestra cómo crear una instancia Switch de en XAML con estas propiedades establecidas:

<Switch OnColor="Orange"
ThumbColor="Green" />

También se pueden establecer las propiedades al crear un Switch en el código:

Switch switch = new Switch { OnColor = Color.Orange, ThumbColor = Color.Green };

En la captura de pantalla siguiente se muestra el Switch en sus Estados de alternancia on y OFF , con las
OnColor ThumbColor propiedades y establecidas:

Responder a un cambio de estado del conmutador


Cuando la IsToggled propiedad cambia, ya sea a través de la manipulación del usuario o cuando una aplicación
establece la IsToggled propiedad, se Toggled desencadena el evento. Se puede registrar un controlador de
eventos para este evento para responder al cambio:

<Switch Toggled="OnToggled" />

El archivo de código subyacente contiene el controlador del Toggled evento:

void OnToggled(object sender, ToggledEventArgs e)


{
// Perform an action after examining e.Value
}

El sender argumento del controlador de eventos es el Switch responsable de desencadenar este evento. Puede
utilizar la sender propiedad para tener acceso al Switch objeto o para distinguir entre varios Switch objetos que
comparten el mismo Toggled controlador de eventos.
Toggled También se puede asignar el controlador de eventos en el código:

Switch switchControl = new Switch {...};


switchControl.Toggled += (sender, e) =>
{
// Perform an action after examining e.Value
}

Enlazar datos a un conmutador


El Toggled controlador de eventos se puede eliminar mediante el enlace de datos y los desencadenadores para
responder a un Switch cambio de estado de alternancia.
<Switch x:Name="styleSwitch" />
<Label Text="Lorem ipsum dolor sit amet, elit rutrum, enim hendrerit augue vitae praesent sed non, lorem
aenean quis praesent pede.">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference styleSwitch}, Path=IsToggled}"
Value="true">
<Setter Property="FontAttributes"
Value="Italic, Bold" />
<Setter Property="FontSize"
Value="Large" />
</DataTrigger>
</Label.Triggers>
</Label>

En este ejemplo, Label utiliza una expresión de enlace en DataTrigger para supervisar la IsToggled propiedad
del Switch denominado styleSwitch . Cuando esta propiedad se convierte en true , FontAttributes FontSize
se cambian las propiedades y de Label . Cuando la IsToggled propiedad vuelve a false , las FontAttributes
FontSize propiedades y de Label se restablecen a su estado inicial.

Para obtener información acerca de los desencadenadores, vea :::no-loc(Xamarin.Forms)::: desencadenadores.

Cambiar Estados visuales


Switch tiene On Off Estados visuales y que se pueden usar para iniciar un cambio visual cuando IsToggled
cambia la propiedad.
En el siguiente ejemplo de XAML se muestra cómo definir Estados visuales para los On Off Estados y:

<Switch IsToggled="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="ThumbColor"
Value="MediumSpringGreen" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor"
Value="Red" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Switch>

En este ejemplo, el On VisualState valor de especifica que cuando la IsToggled propiedad es true , la
ThumbColor propiedad se establecerá en verde primavera medio. Off VisualState Especifica que cuando la
IsToggled propiedad es false , la ThumbColor propiedad se establecerá en rojo. Por lo tanto, el efecto general es
que cuando el Switch está en una posición de desactivación, su Thumb es rojo, y su Thumb es verde primavera
medio cuando el Switch está en una posición ON:
Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.

Deshabilitar un modificador
Una aplicación puede entrar en un estado en el que la Switch alternancia no es una operación válida. En tales
casos, Switch se puede deshabilitar estableciendo su IsEnabled propiedad en false . Esto impedirá que los
usuarios puedan manipular el Switch .

Vínculos relacionados
Cambiar demostraciones
Desencadenadores de :::no-loc(Xamarin.Forms):::
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: TimePicker
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Una :::no-loc(Xamarin.Forms)::: vista que permite al usuario seleccionar una hora.
:::no-loc(Xamarin.Forms)::: TimePicker Invoca el control de selector de tiempo de la plataforma y permite al
usuario seleccionar una hora. TimePicker define las siguientes propiedades:
Time de tipo TimeSpan , la hora seleccionada, cuyo valor predeterminado es TimeSpan 0. El TimeSpan tipo
indica una duración de tiempo desde la medianoche.
Format de tipo string , una cadena de formato .net estándar o personalizada , cuyo valor predeterminado es
"t", el patrón de hora corta.
TextColor de tipo Color , el color que se usa para mostrar la hora seleccionada, cuyo valor predeterminado
es Color.Default .
FontAttributes de tipo FontAttributes , cuyo valor predeterminado es FontAtributes.None .
FontFamily de tipo string , cuyo valor predeterminado es null .
FontSize de tipo double , que tiene como valor predeterminado-1,0.
CharacterSpacing , del tipo double , es el espaciado entre los caracteres del texto de TimePicker .

Todas estas propiedades están respaldadas por BindableProperty objetos, lo que significa que se pueden aplicar
estilos y las propiedades pueden ser destinos de los enlaces de datos. La Time propiedad tiene un modo de
enlace predeterminado de BindingMode.TwoWay , lo que significa que puede ser un destino de un enlace de datos
en una aplicación que usa la arquitectura Model-View-ViewModel (MVVM) .
TimePicker No incluye un evento para indicar un nuevo valor seleccionado Time . Si necesita recibir una
notificación de esto, puede Agregar un controlador para el PropertyChanged evento.

Inicializando la propiedad Time


En el código, puede inicializar la Time propiedad en un valor de tipo TimeSpan :

TimePicker timePicker = new TimePicker


{
Time = new TimeSpan(4, 15, 26) // Time set to "04:15:26"
};

Cuando la Time propiedad se especifica en XAML, el valor se convierte en TimeSpan y se valida para asegurarse
de que el número de milisegundos sea mayor o igual que 0 y que el número de horas sea menor que 24. Los
componentes de hora deben separarse con dos puntos:

<TimePicker Time="4:15:26" />

Si la BindingContext propiedad de TimePicker se establece en una instancia de un ViewModel que contiene una
propiedad de tipo TimeSpan denominada SelectedTime (por ejemplo,), puede crear una instancia de de la
TimePicker manera siguiente:
<TimePicker Time="{Binding SelectedTime}" />

En este ejemplo, la Time propiedad se inicializa en la SelectedTime propiedad en el ViewModel. Dado Time que
la propiedad tiene un modo de enlace de TwoWay , cualquier nueva hora que seleccione el usuario se propagará
automáticamente al ViewModel.
Si no TimePicker contiene un enlace en su Time propiedad, una aplicación debe adjuntar un controlador al
PropertyChanged evento que se va a notificar cuando el usuario seleccione una nueva hora.
Para obtener información sobre cómo establecer las propiedades de la fuente, vea fuentes.

TimePicker y diseño
Es posible utilizar una opción de diseño horizontal sin restricciones como Center , Start o End con TimePicker
:

<TimePicker ···
HorizontalOptions="Center"
··· />

Sin embargo, esto no es recomendable. Dependiendo del valor de la Format propiedad, las horas seleccionadas
pueden requerir anchos de pantalla diferentes. Por ejemplo, la cadena de formato "T" hace que la TimePicker vista
muestre horas en un formato largo y "4:15:26 AM" requiere un ancho de pantalla mayor que el formato de hora
corta ("T") de "4:15 AM". Dependiendo de la plataforma, esta diferencia podría hacer que la TimePicker vista
cambie el ancho en el diseño o que la pantalla se trunque.

TIP
Es preferible usar la configuración predeterminada HorizontalOptions de Fill con TimePicker y no utilizar un ancho
Auto cuando se coloca TimePicker en una Grid celda.

TimePicker en una aplicación


En el ejemplo SetTimer se incluyen las TimePicker Entry vistas, y Switch en su página. Se TimePicker puede
usar para seleccionar una hora y, cuando esa hora se produce, se muestra un cuadro de diálogo de alerta que
recuerda al usuario del texto de Entry , siempre y cuando Switch se alterne. Este es el archivo XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SetTimer"
x:Class="SetTimer.MainPage">
<StackLayout>
...
<Entry x:Name="_entry"
Placeholder="Enter event to be reminded of" />
<Label Text="Select the time below to be reminded at." />
<TimePicker x:Name="_timePicker"
Time="11:00:00"
Format="T"
PropertyChanged="OnTimePickerPropertyChanged" />
<StackLayout Orientation="Horizontal">
<Label Text="Enable timer:" />
<Switch x:Name="_switch"
HorizontalOptions="EndAndExpand"
Toggled="OnSwitchToggled" />
</StackLayout>
</StackLayout>
</ContentPage>

Entry Permite escribir el texto del recordatorio que se mostrará cuando se produzca la hora seleccionada.
TimePicker Se le asigna una Format propiedad de "T" para el formato de hora larga. Tiene un controlador de
eventos adjuntado al PropertyChanged evento y Switch tiene un controlador asociado a su Toggled evento. Estos
controladores de eventos se encuentran en el archivo de código subyacente y llaman al SetTriggerTime método:
public partial class MainPage : ContentPage
{
DateTime _triggerTime;

public MainPage()
{
InitializeComponent();

Device.StartTimer(TimeSpan.FromSeconds(1), OnTimerTick);
}

bool OnTimerTick()
{
if (_switch.IsToggled && DateTime.Now >= _triggerTime)
{
_switch.IsToggled = false;
DisplayAlert("Timer Alert", "The '" + _entry.Text + "' timer has elapsed", "OK");
}
return true;
}

void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args)


{
if (args.PropertyName == "Time")
{
SetTriggerTime();
}
}

void OnSwitchToggled(object sender, ToggledEventArgs args)


{
SetTriggerTime();
}

void SetTriggerTime()
{
if (_switch.IsToggled)
{
_triggerTime = DateTime.Today + _timePicker.Time;
if (_triggerTime < DateTime.Now)
{
_triggerTime += TimeSpan.FromDays(1);
}
}
}
}

El método calcula un tiempo de temporizador basado en el DateTime.Today valor de propiedad y


SetTriggerTime
el TimeSpan valor devuelto de TimePicker . Esto es necesario porque la DateTime.Today propiedad devuelve un
DateTime que indica la fecha actual, pero con una hora de medianoche. Si el tiempo del temporizador ya se ha
pasado hoy, se supone que es mañana.
El temporizador se marca cada segundo, ejecutando el OnTimerTick método que comprueba si Switch está
activado y si la hora actual es mayor o igual que el tiempo del temporizador. Cuando se produce la hora del
temporizador, el DisplayAlert método presenta un cuadro de diálogo de alerta al usuario como recordatorio.
Cuando el ejemplo se ejecuta por primera vez, la TimePicker vista se inicializa en 11. Al pulsar el se TimePicker
invoca el selector de tiempo de plataforma. Las plataformas implementan el selector de tiempo de maneras muy
diferentes, pero cada enfoque es familiar para los usuarios de esa plataforma:
TIP
En Android, el TimePicker cuadro de diálogo se puede personalizar invalidando el CreateTimePickerDialog método en
un representador personalizado. Esto permite, por ejemplo, agregar botones adicionales al cuadro de diálogo.

Después de seleccionar una hora, la hora seleccionada se muestra en la TimePicker :

Siempre que el Switch se alterne a la posición activada, la aplicación muestra un cuadro de diálogo de alerta que
recuerda al usuario del texto de Entry cuando se produce la hora seleccionada:
En cuanto se muestra el cuadro de diálogo de alerta, el Switch se cambia a la posición de apagado.

Vínculos relacionados
Ejemplo de SetTimer
API de TimePicker
:::no-loc(Xamarin.Forms)::: Editor
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
El Editor control se usa para aceptar la entrada de varias líneas.

Establecer y leer texto


Editor , Al igual que otras vistas de presentación de texto, expone la Text propiedad. Esta propiedad se puede
utilizar para establecer y leer el texto presentado por Editor . En el ejemplo siguiente se muestra cómo establecer
la Text propiedad en XAML:

<Editor Text="I am an Editor" />

En C#:

var MyEditor = new Editor { Text = "I am an Editor" };

Para leer texto, acceda a la Text propiedad en C#:

var text = MyEditor.Text;

Establecer texto de marcador de posición


Editor Se puede establecer para mostrar el texto del marcador de posición cuando no se almacena la entrada del
usuario. Esto se logra estableciendo la Placeholder propiedad en string y, a menudo, se usa para indicar el tipo
de contenido adecuado para Editor . Además, el color del texto del marcador de posición se puede controlar
estableciendo la PlaceholderColor propiedad en Color :

<Editor Placeholder="Enter text here" PlaceholderColor="Olive" />

var editor = new Editor { Placeholder = "Enter text here", PlaceholderColor = Color.Olive };

Impedir la entrada de texto


Se puede impedir que los usuarios modifiquen el texto en un estableciendo Editor la IsReadOnly propiedad, que
tiene un valor predeterminado de false , en true :

<Editor Text="This is a read-only Editor"


IsReadOnly="true" />

var editor = new Editor { Text = "This is a read-only Editor", IsReadOnly = true });
NOTE
La IsReadonly propiedad no modifica la apariencia visual de un Editor , a diferencia de la IsEnabled propiedad que
también cambia la apariencia visual del Editor a gris.

Transformar texto
Editor Puede transformar el uso de mayúsculas y minúsculas de su texto, almacenado en la Text propiedad,
estableciendo la TextTransform propiedad en un valor de la TextTransform enumeración. Esta enumeración tiene
cuatro valores:
None indica que el texto no se transformará.
Default indica que se utilizará el comportamiento predeterminado para la plataforma. Este es el valor
predeterminado de la propiedad TextTransform .
Lowercase indica que el texto se transformará a minúsculas.
Uppercase indica que el texto se transformará en mayúsculas.

En el ejemplo siguiente se muestra cómo transformar texto a mayúsculas:

<Editor Text="This text will be displayed in uppercase."


TextTransform="Uppercase" />

El código de C# equivalente es el siguiente:

Editor editor = new Editor


{
Text = "This text will be displayed in uppercase.",
TextTransform = TextTransform.Uppercase
};

Limitar la longitud de entrada


La MaxLength propiedad se puede utilizar para limitar la longitud de entrada permitida para Editor . Esta
propiedad debe establecerse en un entero positivo:

<Editor ... MaxLength="10" />

var editor = new Editor { ... MaxLength = 10 };

Un MaxLength valor de propiedad de 0 indica que no se permitirá ninguna entrada, y un valor de int.MaxValue ,
que es el valor predeterminado para Editor , indica que no hay ningún límite efectivo en el número de caracteres
que se pueden escribir.

espaciado entre caracteres


El espaciado de caracteres se puede aplicar a Editor si se establece la Editor.CharacterSpacing propiedad en un
double valor:
<Editor ...
CharacterSpacing="10" />

El código de C# equivalente es el siguiente:

Editor editor = new editor { CharacterSpacing = 10 };

El resultado es que los caracteres del texto mostrados por Editor están separados por las CharacterSpacing
unidades independientes del dispositivo.

NOTE
El CharacterSpacing valor de la propiedad se aplica al texto mostrado por Text las Placeholder propiedades y.

Cambiar automáticamente el tamaño de un editor


Se Editor puede establecer el tamaño automático de su contenido estableciendo la Editor.AutoSize propiedad
en TextChanges , que es un valor de la EditorAutoSizeOption enumeración. Esta enumeración tiene dos valores:
Disabled indica que el cambio de tamaño automático está deshabilitado y es el valor predeterminado.
TextChanges indica que el cambio de tamaño automático está habilitado.

Esto puede realizarse en el código como se indica a continuación:

<Editor Text="Enter text here" AutoSize="TextChanges" />

var editor = new Editor { Text = "Enter text here", AutoSize = EditorAutoSizeOption.TextChanges };

Cuando el cambio de tamaño automático está habilitado, el alto de Editor aumentará cuando el usuario lo
rellene con texto y el alto disminuirá a medida que el usuario elimine el texto.

NOTE
No cambiará Editor el tamaño de forma automática si se ha HeightRequest establecido la propiedad.

Personalizar el teclado
El teclado que se presenta cuando los usuarios interactúan con Editor se puede establecer mediante
programación a través de la Keyboard propiedad, en una de las siguientes propiedades de la Keyboard clase:
Chat : se usa para el texto y los lugares donde los emoji son útiles.
Default : el teclado predeterminado.
Email : se usa al especificar direcciones de correo electrónico.
Numeric : se usa al escribir números.
Plain : se usa al escribir texto, sin ningún KeyboardFlags especificado.
Telephone : se usa al escribir números de teléfono.
Text : se usa al escribir texto.
Url : se usa para especificar las rutas de acceso de archivo y direcciones web.
Esto se puede lograr en XAML de la siguiente manera:

<Editor Keyboard="Chat" />

El código de C# equivalente es el siguiente:

var editor = new Editor { Keyboard = Keyboard.Chat };

Puede encontrar ejemplos de cada teclado en nuestro repositorio de recetas .


La clase Keyboard tiene también un patrón de diseño Factory Method Create que puede usarse para personalizar
un teclado mediante la especificación del comportamiento de las mayúsculas y minúsculas, el corrector
ortográfico y las sugerencias. Los valores de enumeración KeyboardFlags se especifican como argumentos para el
método, con la devolución de un Keyboard personalizado. La enumeración KeyboardFlags contiene los valores
siguientes:
None : no se agregan características al teclado.
CapitalizeSentence : indica que la primera letra de la primera palabra de cada frase se escribirá
automáticamente en mayúsculas.
Spellcheck : indica que se pasará el corrector ortográfico al texto especificado.
Suggestions : indica que se ofrecerán finalizaciones de palabra para el texto especificado.
CapitalizeWord : indica que las primeras letras de todas las palabras se escribirán automáticamente en
mayúsculas.
CapitalizeCharacter : indica que todos los caracteres se escribirán automáticamente en mayúsculas.
CapitalizeNone : indica que no se producirá ningún uso automático de mayúsculas.
All : indica que se pasará el corrector automático, se ofrecerán finalizaciones de palabras y las frases
empezarán en mayúsculas en el texto especificado.
El ejemplo de código XAML siguiente muestra cómo personalizar el Keyboard predeterminado para ofrecer
finalizaciones de palabras y poner en mayúsculas todos los caracteres especificados:

<Editor>
<Editor.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</Editor.Keyboard>
</Editor>

El código de C# equivalente es el siguiente:

var editor = new Editor();


editor.Keyboard = Keyboard.Create(KeyboardFlags.Suggestions | KeyboardFlags.CapitalizeCharacter);

Habilitar y deshabilitar la revisión ortográfica


La IsSpellCheckEnabled propiedad controla si está habilitada la revisión ortográfica. De forma predeterminada, la
propiedad se establece en true . Cuando el usuario escribe texto, se indican errores ortográficos.
Sin embargo, en algunos escenarios de entrada de texto, como la especificación de un nombre de usuario, la
revisión ortográfica proporciona una experiencia negativa, por lo que debe deshabilitarse estableciendo la
IsSpellCheckEnabled propiedad en false :

<Editor ... IsSpellCheckEnabled="false" />

var editor = new Editor { ... IsSpellCheckEnabled = false };

NOTE
Cuando la IsSpellCheckEnabled propiedad está establecida en false y no se usa un teclado personalizado, se
deshabilitará el corrector ortográfico nativo. Sin embargo, si se ha Keyboard establecido un que deshabilita la revisión
ortográfica, como Keyboard.Chat , IsSpellCheckEnabled se omite la propiedad. Por consiguiente, la propiedad no se
puede usar para habilitar la revisión ortográfica de un Keyboard que lo deshabilita explícitamente.

Habilitar y deshabilitar la predicción de texto


La IsTextPredictionEnabled propiedad controla si está habilitada la predicción de texto y la corrección de texto
automática. De forma predeterminada, la propiedad se establece en true . A medida que el usuario escribe texto,
se presentan las predicciones de palabras.
Sin embargo, en algunos escenarios de entrada de texto, como la especificación de un nombre de usuario, la
predicción de texto y la corrección automática de texto proporcionan una experiencia negativa y se deben
deshabilitar estableciendo la IsTextPredictionEnabled propiedad en false :

<Editor ... IsTextPredictionEnabled="false" />

var editor = new Editor { ... IsTextPredictionEnabled = false };

NOTE
Cuando la IsTextPredictionEnabled propiedad se establece en false y no se usa un teclado personalizado, se
deshabilita la predicción de texto y la corrección de texto automática. Sin embargo, si se ha Keyboard establecido un que
deshabilita la predicción de texto, IsTextPredictionEnabled se omite la propiedad. Por consiguiente, la propiedad no se
puede usar para habilitar la predicción de texto para un Keyboard que lo deshabilita explícitamente.

Colores
Editor se puede establecer para utilizar un color de fondo personalizado a través de la BackgroundColor
propiedad. Es necesario tener especial cuidado para asegurarse de que se puedan usar los colores en cada
plataforma. Dado que cada plataforma tiene valores predeterminados diferentes para el color del texto, puede que
tenga que establecer un color de fondo personalizado para cada plataforma. Vea trabajar con ajustes de
plataforma para obtener más información sobre la optimización de la interfaz de usuario para cada plataforma.
En C#:
public partial class EditorPage : ContentPage
{
public EditorPage ()
{
InitializeComponent ();
var layout = new StackLayout { Padding = new Thickness(5,10) };
this.Content = layout;
//dark blue on UWP & Android, light blue on iOS
var editor = new Editor { BackgroundColor = Device.RuntimePlatform == Device.iOS ?
Color.FromHex("#A4EAFF") : Color.FromHex("#2c3e50") };
layout.Children.Add(editor);
}
}

En XAML:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextSample.EditorPage"
Title="Editor Demo">
<ContentPage.Content>
<StackLayout Padding="5,10">
<Editor>
<Editor.BackgroundColor>
<OnPlatform x:TypeArguments="x:Color">
<On Platform="iOS" Value="#a4eaff" />
<On Platform="Android, UWP" Value="#2c3e50" />
</OnPlatform>
</Editor.BackgroundColor>
</Editor>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Asegúrese de que los colores de fondo y de texto que elija se pueden usar en cada plataforma y no oculte ningún
texto de marcador de posición.

Eventos e interactividad
Editor expone dos eventos:
TextChanged – se genera cuando cambia el texto en el editor. Proporciona el texto antes y después del cambio.
Completado – se genera cuando el usuario ha finalizado la entrada presionando la tecla RETURN del teclado.

NOTE
La VisualElement clase, de la que Entry hereda, también tiene Focused Unfocused eventos y.

Completado
El Completed evento se utiliza para reaccionar a la finalización de una interacción con Editor . Completed se
genera cuando el usuario finaliza la entrada con un campo escribiendo la tecla RETURN en el teclado (o
presionando la tecla TAB en UWP). El controlador del evento es un controlador de eventos genérico que toma el
remitente y EventArgs :

void EditorCompleted (object sender, EventArgs e)


{
var text = ((Editor)sender).Text; // sender is cast to an Editor to enable reading the `Text` property of
the view.
}

Se puede suscribir el evento completado en código y XAML:


En C#:

public partial class EditorPage : ContentPage


{
public EditorPage ()
{
InitializeComponent ();
var layout = new StackLayout { Padding = new Thickness(5,10) };
this.Content = layout;
var editor = new Editor ();
editor.Completed += EditorCompleted;
layout.Children.Add(editor);
}
}

En XAML:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextSample.EditorPage"
Title="Editor Demo">
<ContentPage.Content>
<StackLayout Padding="5,10">
<Editor Completed="EditorCompleted" />
</StackLayout>
</ContentPage.Content>
</Contentpage>

TextChanged
El TextChanged evento se utiliza para reaccionar a un cambio en el contenido de un campo.
TextChanged se genera cuando Text cambia el de Editor . El controlador del evento toma una instancia de
TextChangedEventArgs . TextChangedEventArgs proporciona acceso a los valores antiguos y nuevos de Editor
Text a través de OldTextValue las NewTextValue propiedades y:

void EditorTextChanged (object sender, TextChangedEventArgs e)


{
var oldText = e.OldTextValue;
var newText = e.NewTextValue;
}

Se puede suscribir el evento completado en código y XAML:


Mediante código:

public partial class EditorPage : ContentPage


{
public EditorPage ()
{
InitializeComponent ();
var layout = new StackLayout { Padding = new Thickness(5,10) };
this.Content = layout;
var editor = new Editor ();
editor.TextChanged += EditorTextChanged;
layout.Children.Add(editor);
}
}
En XAML:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TextSample.EditorPage"
Title="Editor Demo">
<ContentPage.Content>
<StackLayout Padding="5,10">
<Editor TextChanged="EditorTextChanged" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Vínculos relacionados
Texto (ejemplo)
API de los editores
:::no-loc(Xamarin.Forms)::: Movimientos
18/12/2020 • 20 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: Entry Se utiliza para la entrada de texto de una sola línea. Entry , Al igual que la
Editor vista, admite varios tipos de teclado. Además, Entry se puede usar como un campo de contraseña.

Establecer y leer texto


Entry , Al igual que otras vistas de presentación de texto, expone la Text propiedad. Esta propiedad se puede
utilizar para establecer y leer el texto presentado por Entry . En el ejemplo siguiente se muestra cómo establecer
la Text propiedad en XAML:

<Entry Text="I am an Entry" />

En C#:

var MyEntry = new Entry { Text = "I am an Entry" };

Para leer texto, acceda a la Text propiedad en C#:

var text = MyEntry.Text;

Establecer texto de marcador de posición


Entry Se puede establecer para mostrar el texto del marcador de posición cuando no se almacena la entrada del
usuario. Esto se logra estableciendo la Placeholder propiedad en string y, a menudo, se usa para indicar el tipo
de contenido adecuado para Entry . Además, el color del texto del marcador de posición se puede controlar
estableciendo la PlaceholderColor propiedad en Color :

<Entry Placeholder="Username" PlaceholderColor="Olive" />

var entry = new Entry { Placeholder = "Username", PlaceholderColor = Color.Olive };

NOTE
El ancho de un Entry se puede definir estableciendo su WidthRequest propiedad. No dependa del ancho de Entry que
se esté definiendo según el valor de su Text propiedad.

Impedir la entrada de texto


Se puede impedir que los usuarios modifiquen el texto en un estableciendo Entry la IsReadOnly propiedad, que
tiene un valor predeterminado de false , en true :
<Entry Text="This is a read-only Entry"
IsReadOnly="true" />

var entry = new Entry { Text = "This is a read-only Entry", IsReadOnly = true });

NOTE
La IsReadonly propiedad no modifica la apariencia visual de un Entry , a diferencia de la IsEnabled propiedad que
también cambia la apariencia visual del Entry a gris.

Transformar texto
Entry Puede transformar el uso de mayúsculas y minúsculas de su texto, almacenado en la Text propiedad,
estableciendo la TextTransform propiedad en un valor de la TextTransform enumeración. Esta enumeración tiene
cuatro valores:
None indica que el texto no se transformará.
Default indica que se utilizará el comportamiento predeterminado para la plataforma. Este es el valor
predeterminado de la propiedad TextTransform .
Lowercase indica que el texto se transformará a minúsculas.
Uppercase indica que el texto se transformará en mayúsculas.

En el ejemplo siguiente se muestra cómo transformar texto a mayúsculas:

<Entry Text="This text will be displayed in uppercase."


TextTransform="Uppercase" />

El código de C# equivalente es el siguiente:

Entry entry = new Entry


{
Text = "This text will be displayed in uppercase.",
TextTransform = TextTransform.Uppercase
};

Limitar la longitud de entrada


La MaxLength propiedad se puede utilizar para limitar la longitud de entrada permitida para Entry . Esta
propiedad debe establecerse en un entero positivo:

<Entry ... MaxLength="10" />

var entry = new Entry { ... MaxLength = 10 };

Un MaxLength valor de propiedad de 0 indica que no se permitirá ninguna entrada, y un valor de int.MaxValue ,
que es el valor predeterminado para Entry , indica que no hay ningún límite efectivo en el número de caracteres
que se pueden escribir.
espaciado entre caracteres
El espaciado de caracteres se puede aplicar a Entry si se establece la Entry.CharacterSpacing propiedad en un
double valor:

<Entry ...
CharacterSpacing="10" />

El código de C# equivalente es el siguiente:

Entry entry = new Entry { CharacterSpacing = 10 };

El resultado es que los caracteres del texto mostrados por Entry están separados por las CharacterSpacing
unidades independientes del dispositivo.

NOTE
El CharacterSpacing valor de la propiedad se aplica al texto mostrado por Text las Placeholder propiedades y.

Campos de contraseña
Entry proporciona la IsPassword propiedad. Cuando IsPassword es true , el contenido del campo se
presentará como círculos negros:
En XAML:

<Entry IsPassword="true" />

En C#:

var MyEntry = new Entry { IsPassword = true };


Los marcadores de posición se pueden usar con instancias de Entry que están configuradas como campos de
contraseña:
En XAML:

<Entry IsPassword="true" Placeholder="Password" />

En C#:

var MyEntry = new Entry { IsPassword = true, Placeholder = "Password" };


Establecer la posición del cursor y la longitud de la selección de texto
La CursorPosition propiedad se puede utilizar para devolver o establecer la posición en la que se insertará el
siguiente carácter en la cadena almacenada en la Text propiedad:

<Entry Text="Cursor position set" CursorPosition="5" />

var entry = new Entry { Text = "Cursor position set", CursorPosition = 5 };

El valor predeterminado de la CursorPosition propiedad es 0, que indica que el texto se insertará al principio del
Entry .

Además, la SelectionLength propiedad se puede utilizar para devolver o establecer la longitud de la selección de
texto dentro de Entry :

<Entry Text="Cursor position and selection length set" CursorPosition="2" SelectionLength="10" />

var entry = new Entry { Text = "Cursor position and selection length set", CursorPosition = 2, SelectionLength
= 10 };

El valor predeterminado de la SelectionLength propiedad es 0, que indica que no se ha seleccionado ningún texto.

Mostrar un botón borrar


La ClearButtonVisibility propiedad se puede utilizar para controlar si Entry muestra un botón borrar, lo que
permite al usuario borrar el texto. Esta propiedad se debe establecer en un ClearButtonVisibility miembro de
enumeración:
Never indica que nunca se mostrará un botón CLEAR. Este es el valor predeterminado de la propiedad
Entry.ClearButtonVisibility .
WhileEditing indica que se mostrará un botón borrar en Entry , mientras que tiene el foco y el texto.

En el ejemplo siguiente se muestra cómo establecer la propiedad en XAML:

<Entry Text=":::no-loc(Xamarin.Forms):::"
ClearButtonVisibility="WhileEditing" />

El código de C# equivalente es el siguiente:

var entry = new Entry { Text = ":::no-loc(Xamarin.Forms):::", ClearButtonVisibility =


ClearButtonVisibility.WhileEditing };

Las capturas de pantallas siguientes muestran un Entry con el botón Borrar habilitado:

Personalizar el teclado
El teclado que se presenta cuando los usuarios interactúan con Entry se puede establecer mediante
programación a través de la Keyboard propiedad, en una de las siguientes propiedades de la Keyboard clase:
Chat : se usa para el texto y los lugares donde los emoji son útiles.
Default : el teclado predeterminado.
Email : se usa al especificar direcciones de correo electrónico.
Numeric : se usa al escribir números.
Plain : se usa al escribir texto, sin ningún KeyboardFlags especificado.
Telephone : se usa al escribir números de teléfono.
Text : se usa al escribir texto.
Url : se usa para especificar las rutas de acceso de archivo y direcciones web.

Esto se puede lograr en XAML de la siguiente manera:

<Entry Keyboard="Chat" />

El código de C# equivalente es el siguiente:

var entry = new Entry { Keyboard = Keyboard.Chat };

Puede encontrar ejemplos de cada teclado en nuestro repositorio de recetas .


La clase Keyboard tiene también un patrón de diseño Factory Method Create que puede usarse para personalizar
un teclado mediante la especificación del comportamiento de las mayúsculas y minúsculas, el corrector
ortográfico y las sugerencias. Los valores de enumeración KeyboardFlags se especifican como argumentos para el
método, con la devolución de un Keyboard personalizado. La enumeración KeyboardFlags contiene los valores
siguientes:
None : no se agregan características al teclado.
CapitalizeSentence : indica que la primera letra de la primera palabra de cada frase se escribirá
automáticamente en mayúsculas.
Spellcheck : indica que se pasará el corrector ortográfico al texto especificado.
Suggestions : indica que se ofrecerán finalizaciones de palabra para el texto especificado.
CapitalizeWord : indica que las primeras letras de todas las palabras se escribirán automáticamente en
mayúsculas.
CapitalizeCharacter : indica que todos los caracteres se escribirán automáticamente en mayúsculas.
CapitalizeNone : indica que no se producirá ningún uso automático de mayúsculas.
All : indica que se pasará el corrector automático, se ofrecerán finalizaciones de palabras y las frases
empezarán en mayúsculas en el texto especificado.
El ejemplo de código XAML siguiente muestra cómo personalizar el Keyboard predeterminado para ofrecer
finalizaciones de palabras y poner en mayúsculas todos los caracteres especificados:

<Entry Placeholder="Enter text here">


<Entry.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</Entry.Keyboard>
</Entry>

El código de C# equivalente es el siguiente:

var entry = new Entry { Placeholder = "Enter text here" };


entry.Keyboard = Keyboard.Create(KeyboardFlags.Suggestions | KeyboardFlags.CapitalizeCharacter);

Personalizar la tecla entrar


La apariencia de la tecla retorno en el teclado para software, que se muestra cuando Entry tiene el foco, puede
personalizarse estableciendo la ReturnType propiedad en un valor de la ReturnType enumeración:
Default : indica que no se requiere ninguna clave de devolución específica y que se usará el valor
predeterminado de la plataforma.
Done : indica una tecla de retorno "Done".
Go : indica una tecla de retorno "Go".
Next : indica una clave de retorno "Next".
Search : indica una clave de "búsqueda".
Send : indica una clave de "envío".

En el siguiente ejemplo de XAML se muestra cómo establecer la clave de devolución:

<Entry ReturnType="Send" />

El código de C# equivalente es el siguiente:

var entry = new Entry { ReturnType = ReturnType.Send };


NOTE
La apariencia exacta de la tecla RETURN depende de la plataforma. En iOS, la tecla RETURN es un botón basado en texto. Sin
embargo, en las plataformas Android y universal de Windows, la tecla RETURN es un botón basado en iconos.

Cuando se presiona la tecla entrar, el Completed evento se activa y ICommand se ejecuta cualquier valor
especificado por la ReturnCommand propiedad. Además, cualquier object especificado por la
ReturnCommandParameter propiedad se pasará a ICommand como un parámetro. Para obtener más información
sobre los comandos, consulte The Command Interface (La interfaz de comandos).

Habilitar y deshabilitar la revisión ortográfica


La IsSpellCheckEnabled propiedad controla si está habilitada la revisión ortográfica. De forma predeterminada, la
propiedad se establece en true . Cuando el usuario escribe texto, se indican errores ortográficos.
Sin embargo, en algunos escenarios de entrada de texto, como la especificación de un nombre de usuario, la
revisión ortográfica proporciona una experiencia negativa y debe deshabilitarse estableciendo la
IsSpellCheckEnabled propiedad en false :

<Entry ... IsSpellCheckEnabled="false" />

var entry = new Entry { ... IsSpellCheckEnabled = false };

NOTE
Cuando la IsSpellCheckEnabled propiedad está establecida en false y no se usa un teclado personalizado, se
deshabilitará el corrector ortográfico nativo. Sin embargo, si se ha Keyboard establecido un que deshabilita la revisión
ortográfica, como Keyboard.Chat , IsSpellCheckEnabled se omite la propiedad. Por consiguiente, la propiedad no se
puede usar para habilitar la revisión ortográfica de un Keyboard que lo deshabilita explícitamente.

Habilitar y deshabilitar la predicción de texto


La IsTextPredictionEnabled propiedad controla si está habilitada la predicción de texto y la corrección de texto
automática. De forma predeterminada, la propiedad se establece en true . A medida que el usuario escribe texto,
se presentan las predicciones de palabras.
Sin embargo, en algunos escenarios de entrada de texto, como la especificación de un nombre de usuario, la
predicción de texto y la corrección automática de texto proporcionan una experiencia negativa y se deben
deshabilitar estableciendo la IsTextPredictionEnabled propiedad en false :

<Entry ... IsTextPredictionEnabled="false" />

var entry = new Entry { ... IsTextPredictionEnabled = false };


NOTE
Cuando la IsTextPredictionEnabled propiedad se establece en false y no se usa un teclado personalizado, se
deshabilita la predicción de texto y la corrección de texto automática. Sin embargo, si se ha Keyboard establecido un que
deshabilita la predicción de texto, IsTextPredictionEnabled se omite la propiedad. Por consiguiente, la propiedad no se
puede usar para habilitar la predicción de texto para un Keyboard que lo deshabilita explícitamente.

Colores
La entrada se puede establecer para utilizar un fondo personalizado y colores de texto a través de las siguientes
propiedades enlazables:
TextColor – establece el color del texto.
BackgroundColor – establece el color que se muestra detrás del texto.
Es necesario tener especial cuidado para asegurarse de que se puedan usar los colores en cada plataforma. Dado
que cada plataforma tiene valores predeterminados diferentes para los colores de texto y de fondo, a menudo
necesitará establecer ambos si establece uno.
Use el código siguiente para establecer el color del texto de una entrada:
En XAML:

<Entry TextColor="Green" />

En C#:

var entry = new Entry();


entry.TextColor = Color.Green;
Tenga en cuenta que el marcador de posición no se ve afectado por el especificado TextColor .
Para establecer el color de fondo en XAML:

<Entry BackgroundColor="#2c3e50" />

En C#:

var entry = new Entry();


entry.BackgroundColor = Color.FromHex("#2c3e50");
Asegúrese de que los colores de fondo y de texto que elija se pueden usar en cada plataforma y no oculte ningún
texto de marcador de posición.

Eventos e interactividad
La entrada expone dos eventos:
TextChanged se – genera cuando el texto cambia en la entrada. Proporciona el texto antes y después del cambio.
Completed –se genera cuando el usuario ha finalizado la entrada presionando la tecla RETURN del teclado.

NOTE
La VisualElement clase, de la que Entry hereda, también tiene Focused Unfocused eventos y.

Completado
El Completed evento se utiliza para reaccionar a la finalización de una interacción con una entrada. Completed se
genera cuando el usuario finaliza la entrada con un campo presionando la tecla entrar en el teclado (o
presionando la tecla TAB en UWP). El controlador del evento es un controlador de eventos genérico que toma el
remitente y EventArgs :

void Entry_Completed (object sender, EventArgs e)


{
var text = ((Entry)sender).Text; //cast sender to access the properties of the Entry
}

Se puede suscribir el evento completado a en XAML:


<Entry Completed="Entry_Completed" />

y C#:

var entry = new Entry ();


entry.Completed += Entry_Completed;

Una vez que Completed se desencadena el evento, ICommand se ejecuta cualquier especificado por la
ReturnCommand propiedad, con el object especificado por la ReturnCommandParameter propiedad que se pasa a
ICommand .
TextChanged
El TextChanged evento se utiliza para reaccionar a un cambio en el contenido de un campo.
TextChanged se genera cuando Text cambia el de Entry . El controlador del evento toma una instancia de
TextChangedEventArgs . TextChangedEventArgs proporciona acceso a los valores antiguos y nuevos de Entry Text
a través de OldTextValue las NewTextValue propiedades y:

void Entry_TextChanged (object sender, TextChangedEventArgs e)


{
var oldText = e.OldTextValue;
var newText = e.NewTextValue;
}

El TextChanged evento se puede suscribir a en XAML:

<Entry TextChanged="Entry_TextChanged" />

y C#:

var entry = new Entry ();


entry.TextChanged += Entry_TextChanged;

Vínculos relacionados
Texto (ejemplo)
API de las entradas
:::no-loc(Xamarin.Forms)::: ActivityIndicator
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms)::: ActivityIndicator control muestra una animación para mostrar que la aplicación
está ocupada en una actividad prolongada. A diferencia ProgressBar de, el ActivityIndicator no proporciona
ninguna indicación de progreso. ActivityIndicator Hereda de View .
Las capturas de pantallas siguientes muestran un ActivityIndicator control en iOS y Android:

El ActivityIndicator control define las siguientes propiedades:


Color es un Color valor que define el color de presentación de ActivityIndicator .
IsRunning es un bool valor que indica si ActivityIndicator debe ser visible y animar, u ocultarse. Cuando el
valor es false , ActivityIndicator no está visible.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que se puede aplicar un
ActivityIndicator estilo a y ser el destino de los enlaces de datos.

Creación de un ActivityIndicator
ActivityIndicator Se puede crear una instancia de la clase en XAML. Su IsRunning propiedad determina si el
control está visible y animando. La IsRunning propiedad tiene como valor predeterminado false . En el ejemplo
siguiente se muestra cómo crear una instancia ActivityIndicator de en XAML con el IsRunning conjunto de
propiedades opcional:

<ActivityIndicator IsRunning="true" />

ActivityIndicator También se puede crear en el código:

ActivityIndicator activityIndicator = new ActivityIndicator { IsRunning = true };

Propiedades de apariencia de ActivityIndicator


La Color propiedad define el ActivityIndicator color. En el ejemplo siguiente se muestra cómo crear una
instancia ActivityIndicator de en XAML con el Color conjunto de propiedades:

<ActivityIndicator Color="Orange" />

La Color propiedad también se puede establecer al crear un ActivityIndicator en el código:


ActivityIndicator activityIndicator = new ActivityIndicator { Color = Color.Orange };

Las siguientes capturas de pantallas muestran el ActivityIndicator con la Color propiedad establecida en
Color.Orange en iOS y Android:

Vínculos relacionados
Demostraciones de ActivityIndicator
ProgressBar
:::no-loc(Xamarin.Forms)::: ProgressBar
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
El :::no-loc(Xamarin.Forms)::: ProgressBar control representa visualmente el progreso como una barra horizontal
que se rellena con un porcentaje representado por un float valor. La ProgressBar clase hereda de View .
En las capturas de pantalla siguientes se muestra un elemento ProgressBar en iOS y Android:

El ProgressBar control define dos propiedades:


Progress es un float valor que representa el progreso actual como un valor de 0 a 1. Progress los valores
menores que 0 se fijarán en 0; los valores mayores que 1 se fijarán en 1.
ProgressColor es un valor de tipo Color que afecta al color de la barra interior que representa el progreso
actual.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que se puede aplicar un
ProgressBar estilo a y ser el destino de los enlaces de datos.

El ProgressBar control también define un ProgressTo método que anima la barra desde su valor actual hasta un
valor especificado. Para obtener más información, vea animar un componente ProgressBar.

NOTE
No ProgressBar acepta la manipulación de usuarios, por lo que se omite cuando se usa la tecla TAB para seleccionar
controles.

Crear un ProgressBar
ProgressBar Se puede crear una instancia de en XAML. Su Progress propiedad determina el porcentaje de relleno
de la barra de color interna. El valor predeterminado de la Progress propiedad es 0. En el ejemplo siguiente se
muestra cómo crear una instancia ProgressBar de en XAML con el Progress conjunto de propiedades opcional:

<ProgressBar Progress="0.5" />

ProgressBar También se puede crear en el código:

ProgressBar progressBar = new ProgressBar { Progress = 0.5f };


WARNING
No utilice opciones de diseño horizontal sin restricciones como Center , Start o End con ProgressBar . En UWP,
ProgressBar se contrae en una barra de ancho cero. Mantenga el HorizontalOptions valor predeterminado de Fill y
no use un ancho de Auto al colocar un ProgressBar en un Grid diseño.

Propiedades de apariencia de ProgressBar


La ProgressColor propiedad define el color de la barra interior cuando la Progress propiedad es mayor que cero.
En el ejemplo siguiente se muestra cómo crear una instancia ProgressBar de en XAML con el ProgressColor
conjunto de propiedades:

<ProgressBar ProgressColor="Orange" />

La ProgressColor propiedad también se puede establecer al crear un ProgressBar en el código:

ProgressBar progressBar = new ProgressBar { ProgressColor = Color.Orange };

Las siguientes capturas de pantallas muestran el ProgressBar con la ProgressColor propiedad establecida en
Color.Orange en iOS y Android:

Animar un ProgressBar
El ProgressTo método anima ProgressBar desde su Progress valor actual hasta un valor proporcionado a lo
largo del tiempo. El método acepta un float valor de progreso, una uint duración en milisegundos, un Easing
valor de enumeración y devuelve un Task<bool> . En el código siguiente se muestra cómo animar un
ProgressBar :

// animate to 75% progress over 500 milliseconds with linear easing


await progressBar.ProgressTo(0.75, 500, Easing.Linear);

Para obtener más información acerca de la Easing enumeración, consulte funciones de aceleración en :::no-
loc(Xamarin.Forms)::: .

Vínculos relacionados
Demostraciones de ProgressBar
Xamarin.FormsCarouselView
18/12/2020 • 2 minutes to read • Edit Online

Introducción
CarouselView Es una vista para presentar los datos en un diseño desplazable, donde los usuarios pueden
deslizarse para desplazarse por una colección de elementos.

Data
Un CarouselView se rellena con datos estableciendo su ItemsSource propiedad en cualquier colección que
implementa IEnumerable . La apariencia de cada elemento se puede definir estableciendo la ItemTemplate
propiedad en DataTemplate .

Diseño
De forma predeterminada, un mostrará CarouselView sus elementos en una lista horizontal. Sin embargo,
también tiene acceso a los mismos diseños como CollectionView, incluida una orientación vertical.

Interacción
Se puede tener acceso al elemento mostrado actualmente en un CarouselView a través de las CurrentItem
Position propiedades y.

Vistas vacías
En CarouselView , se puede especificar una vista vacía que proporciona comentarios al usuario cuando no hay
datos disponibles para mostrar. La vista vacía puede ser una cadena, una vista o varias vistas.

Desplazarse
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Además, CarouselView define dos ScrollTo
métodos, que desplazan los elementos mediante programación a la vista. Una de las sobrecargas desplaza el
elemento en el índice especificado en la vista, mientras que el otro desplaza el elemento especificado a la vista.
Xamarin.FormsIntroducción a CarouselView
18/12/2020 • 2 minutes to read • Edit Online

CarouselView es una vista para presentar los datos en un diseño desplazable, donde los usuarios pueden deslizarse
para desplazarse por una colección de elementos. De forma predeterminada, mostrará CarouselView sus
elementos en una orientación horizontal. Se mostrará un solo elemento en la pantalla, con gestos de deslizar
rápidamente, lo que da como resultado la navegación hacia delante y hacia atrás a través de la colección de
elementos. Además, se pueden mostrar indicadores que representan cada elemento de CarouselView :

CarouselView está disponible en Xamarin.Forms 4,3. Sin embargo, actualmente es experimental y solo se puede
usar agregando la siguiente línea de código a la AppDelegate clase en iOS, o bien a la MainActivity clase en
Android, antes de llamar a Forms.Init :

Forms.SetFlags("CarouselView_Experimental");

IMPORTANT
CarouselView está disponible en iOS y Android, pero es posible que algunas funciones solo estén disponibles parcialmente
en el Plataforma universal de Windows.

CarouselView comparte gran parte de su implementación con CollectionView . Sin embargo, los dos controles
tienen diferentes casos de uso. CollectionView normalmente se usa para presentar listas de datos de cualquier
longitud, mientras que CarouselView normalmente se usa para resaltar información en una lista de longitud
limitada.
:::no-loc(Xamarin.Forms)::: Datos de CarouselView
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
CarouselView incluye las siguientes propiedades que definen los datos que se van a mostrar y su apariencia:
ItemsSource , de tipo IEnumerable , especifica la colección de elementos que se van a mostrar y tiene un valor
predeterminado de null .
ItemTemplate , de tipo DataTemplate , especifica la plantilla que se va a aplicar a cada elemento de la colección
de elementos que se va a mostrar.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.

NOTE
CarouselView define una ItemsUpdatingScrollMode propiedad que representa el comportamiento de desplazamiento de
CarouselView cuando se agregan nuevos elementos a él. Para obtener más información sobre esta propiedad, vea control
de posición de desplazamiento cuando se agregan nuevos elementos.

CarouselView admite la virtualización de datos incrementales a medida que el usuario se desplaza. Para obtener
más información, vea cargar datos de forma incremental.

Rellenar un CarouselView con datos


Un CarouselView se rellena con datos estableciendo su ItemsSource propiedad en cualquier colección que
implementa IEnumerable . De forma predeterminada, CarouselView muestra los elementos horizontalmente.

IMPORTANT
Si CarouselView es necesario actualizar a medida que se agregan, quitan o cambian elementos en la colección subyacente,
la colección subyacente debe ser una IEnumerable colección que envíe notificaciones de cambios de propiedades, como
ObservableCollection .

CarouselView se puede rellenar con datos mediante el enlace de datos para enlazar su ItemsSource propiedad a
una IEnumerable colección. En XAML, esto se consigue con la Binding extensión de marcado:

<CarouselView ItemsSource="{Binding Monkeys}" />

El código de C# equivalente es el siguiente:


CarouselView carouselView = new CarouselView();
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

En este ejemplo, los ItemsSource datos de la propiedad se enlazan a la Monkeys propiedad del ViewModel
conectado.

NOTE
Los enlaces compilados se pueden habilitar para mejorar el rendimiento del enlace de datos en :::no-loc(Xamarin.Forms)::: las
aplicaciones. Para obtener más información, vea Enlaces compilados.

Para obtener información sobre cómo cambiar la CarouselView orientación, vea :::no-loc(Xamarin.Forms)::: diseño
de CarouselView. Para obtener información sobre cómo definir la apariencia de cada elemento en CarouselView ,
vea definir la apariencia del elemento. Para obtener más información sobre el enlace de datos, consulte Enlace de
datos de :::no-loc(Xamarin.Forms):::.

Definir la apariencia del elemento


La apariencia de cada elemento en CarouselView puede definirse estableciendo la CarouselView.ItemTemplate
propiedad en DataTemplate :

<CarouselView ItemsSource="{Binding Monkeys}">


<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
<Label Text="{Binding Location}"
HorizontalOptions="Center" />
<Label Text="{Binding Details}"
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>

El código de C# equivalente es el siguiente:


CarouselView carouselView = new CarouselView();
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

carouselView.ItemTemplate = new DataTemplate(() =>


{
Label nameLabel = new Label { ... };
nameLabel.SetBinding(Label.TextProperty, "Name");

Image image = new Image { ... };


image.SetBinding(Image.SourceProperty, "ImageUrl");

Label locationLabel = new Label { ... };


locationLabel.SetBinding(Label.TextProperty, "Location");

Label detailsLabel = new Label { ... };


detailsLabel.SetBinding(Label.TextProperty, "Details");

StackLayout stackLayout = new StackLayout


{
Children = { nameLabel, image, locationLabel, detailsLabel }
};

Frame frame = new Frame { ... };


StackLayout rootStackLayout = new StackLayout
{
Children = { frame }
};

return rootStackLayout;
});

Los elementos especificados en DataTemplate definen la apariencia de cada elemento en CarouselView . En el


ejemplo, el diseño dentro de DataTemplate se administra mediante un StackLayout , y los datos se muestran con
un Image objeto y tres Label objetos, que se enlazan a las propiedades de la Monkey clase:

public class Monkey


{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}

Las siguientes capturas de pantallas muestran el resultado de la plantilla de cada elemento:

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Elección de la apariencia del elemento en tiempo de ejecución


La apariencia de cada elemento en CarouselView se puede elegir en tiempo de ejecución, según el valor del
elemento, estableciendo la CarouselView.ItemTemplate propiedad en un DataTemplateSelector objeto:

<ContentPage ...
xmlns:controls="clr-namespace:CarouselViewDemos.Controls"
x:Class="CarouselViewDemos.Views.HorizontalLayoutDataTemplateSelectorPage">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
...
</DataTemplate>

<DataTemplate x:Key="OtherMonkeyTemplate">
...
</DataTemplate>

<controls:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource OtherMonkeyTemplate}" />
</ContentPage.Resources>

<CarouselView ItemsSource="{Binding Monkeys}"


ItemTemplate="{StaticResource MonkeySelector}" />
</ContentPage>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
ItemTemplate = new MonkeyDataTemplateSelector { ... }
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

La ItemTemplate propiedad se establece en un MonkeyDataTemplateSelector objeto. En el ejemplo siguiente se


muestra la MonkeyDataTemplateSelector clase:

public class MonkeyDataTemplateSelector : DataTemplateSelector


{
public DataTemplate AmericanMonkey { get; set; }
public DataTemplate OtherMonkey { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
return ((Monkey)item).Location.Contains("America") ? AmericanMonkey : OtherMonkey;
}
}

La MonkeyDataTemplateSelector clase define AmericanMonkey OtherMonkey DataTemplate las propiedades y que se


establecen en distintas plantillas de datos. La OnSelectTemplate invalidación devuelve la AmericanMonkey plantilla
cuando el nombre de Monkey contiene "America". Cuando el nombre de Monkey no contiene "America", la
OnSelectTemplate invalidación devuelve la OtherMonkey plantilla, que muestra los datos atenuados:
Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-loc(Xamarin.Forms):::
DataTemplateSelector.

IMPORTANT
Al utilizar CarouselView , no establezca nunca el elemento raíz de los DataTemplate objetos en un ViewCell . Esto hará
que se produzca una excepción porque CarouselView no tiene concepto de celdas.

presentación de indicadores
Los indicadores, que representan el número de elementos y la posición actual en un CarouselView , se pueden
mostrar junto a CarouselView . Esto puede hacerse con el IndicatorView control:

<StackLayout>
<CarouselView ItemsSource="{Binding Monkeys}"
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
<!-- DataTemplate that defines item appearance -->
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
</StackLayout>

En este ejemplo, IndicatorView se representa debajo de CarouselView , con un indicador para cada elemento en
CarouselView . IndicatorView Se rellena con datos estableciendo la CarouselView.IndicatorView propiedad en el
IndicatorView objeto. Cada indicador es un círculo gris claro, mientras que el indicador que representa el
elemento actual del CarouselView es gris oscuro:
IMPORTANT
Al establecer la propiedad CarouselView.IndicatorView , se obtiene el enlace de la IndicatorView.Position propiedad a
la CarouselView.Position propiedad y la IndicatorView.ItemsSource propiedad se enlaza a la
CarouselView.ItemsSource propiedad.

Para obtener más información acerca de los indicadores, vea :::no-loc(Xamarin.Forms)::: IndicatorView.

Menús contextuales
CarouselView admite menús contextuales para elementos de datos a través de SwipeView , que revela el menú
contextual con un gesto de deslizar rápidamente. SwipeView Es un control contenedor que se ajusta alrededor de
un elemento de contenido y proporciona elementos de menú contextual para ese elemento de contenido. Por lo
tanto, los menús contextuales se implementan para un CarouselView mediante la creación de un SwipeView que
define el contenido que SwipeView contiene y los elementos de menú contextual revelados por el gesto de deslizar
rápidamente. Esto se logra agregando SwipeView a DataTemplate que define la apariencia de cada elemento de
datos en CarouselView :
<CarouselView x:Name="carouselView"
ItemsSource="{Binding Monkeys}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<SwipeView>
<SwipeView.TopItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Command="{Binding Source={x:Reference carouselView},
Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.TopItems>
<SwipeView.BottomItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding Source={x:Reference carouselView},
Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.BottomItems>
<StackLayout>
<!-- Define item appearance -->
</StackLayout>
</SwipeView>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>

El código de C# equivalente es el siguiente:


CarouselView carouselView = new CarouselView();
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

carouselView.ItemTemplate = new DataTemplate(() =>


{
StackLayout stackLayout = new StackLayout();
Frame frame = new Frame { ... };

SwipeView swipeView = new SwipeView();


SwipeItem favoriteSwipeItem = new SwipeItem
{
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Color.LightGreen
};
favoriteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.FavoriteCommand",
source: carouselView));
favoriteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

SwipeItem deleteSwipeItem = new SwipeItem


{
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Color.LightPink
};
deleteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.DeleteCommand", source:
carouselView));
deleteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

swipeView.TopItems = new SwipeItems { favoriteSwipeItem };


swipeView.BottomItems = new SwipeItems { deleteSwipeItem };

StackLayout swipeViewStackLayout = new StackLayout { ... };


swipeView.Content = swipeViewStackLayout;
frame.Content = swipeView;
stackLayout.Children.Add(frame);

return stackLayout;
});

En este ejemplo, el SwipeView contenido es un StackLayout que define la apariencia de cada elemento rodeado
por un Frame en CarouselView . Los elementos de deslizamiento se utilizan para realizar acciones en el SwipeView
contenido y se revelan cuando el control se desliza rápidamente desde la parte superior y desde la parte inferior:
SwipeView admite cuatro direcciones de deslizamiento diferentes, con la dirección de deslizamiento que se define
en la colección direcciona SwipeItems a la que SwipeItems se agregan los objetos. De forma predeterminada, se
ejecuta un dedo al puntear en el usuario. Además, una vez que se ha ejecutado un dedo, se ocultan los elementos
de deslizamiento y SwipeView se vuelve a mostrar el contenido. Sin embargo, estos comportamientos se pueden
cambiar.
Para obtener más información sobre el SwipeView control, vea :::no-loc(Xamarin.Forms)::: SwipeView.

Extraer para actualizar


CarouselView admite la funcionalidad de extracción para actualizar a través de RefreshView , lo que permite que
los datos que se muestran se actualicen al desplazarse por los elementos. RefreshView Es un control contenedor
que proporciona la funcionalidad de extracción para actualizar a su elemento secundario, siempre que el elemento
secundario admita contenido desplazable. Por lo tanto, la extracción de la actualización se implementa para una
CarouselView al establecerla como el elemento secundario de un RefreshView :

<RefreshView IsRefreshing="{Binding IsRefreshing}"


Command="{Binding RefreshCommand}">
<CarouselView ItemsSource="{Binding Animals}">
...
</CarouselView>
</RefreshView>

El código de C# equivalente es el siguiente:

RefreshView refreshView = new RefreshView();


ICommand refreshCommand = new Command(() =>
{
// IsRefreshing is true
// Refresh data here
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
refreshView.Content = carouselView;
// ...
Cuando el usuario inicia una actualización, ICommand se ejecuta la definida por la Command propiedad, que debe
actualizar los elementos que se muestran. Se muestra una visualización de actualización mientras se produce la
actualización, que consta de un círculo de progreso animado:

El valor de la RefreshView.IsRefreshing propiedad indica el estado actual de RefreshView . Cuando el usuario


desencadena una actualización, esta propiedad pasará automáticamente a true . Una vez finalizada la
actualización, debe restablecer la propiedad a false .
Para obtener más información sobre RefreshView , vea :::no-loc(Xamarin.Forms)::: RefreshView.

Cargar datos incrementalmente


CarouselView admite la virtualización de datos incrementales a medida que el usuario se desplaza. Esto permite
escenarios como la carga asincrónica de una página de datos de un servicio Web, a medida que el usuario se
desplaza. Además, el punto en el que se cargan más datos se puede configurar para que los usuarios no vean el
espacio en blanco o se detengan del desplazamiento.
CarouselView define las siguientes propiedades para controlar la carga incremental de datos:
RemainingItemsThreshold , de tipo , el umbral de elementos que todavía no están visibles en la lista en la
int
que se RemainingItemsThresholdReached desencadenará el evento.
RemainingItemsThresholdReachedCommand , de tipo ICommand , que se ejecuta cuando RemainingItemsThreshold se
alcanza el.
RemainingItemsThresholdReachedCommandParameter , de tipo object , que es el parámetro que se pasa a
RemainingItemsThresholdReachedCommand .

CarouselView también define un RemainingItemsThresholdReached evento que se desencadena cuando


CarouselView se desplaza lo suficiente como para que RemainingItemsThreshold no se muestren los elementos.
Este evento se puede controlar para cargar más elementos. Además, cuando RemainingItemsThresholdReached se
desencadena el evento, RemainingItemsThresholdReachedCommand se ejecuta, lo que permite que la carga de datos
incrementales se realice en un ViewModel.
El valor predeterminado de la propiedad es-1, lo que indica que el
RemainingItemsThreshold
RemainingItemsThresholdReached evento nunca se desencadenará. Cuando el valor de la propiedad es 0, el
RemainingItemsThresholdReached evento se desencadena cuando se muestra el último elemento de ItemsSource .
En el caso de valores mayores que 0, el RemainingItemsThresholdReached evento se desencadena cuando
ItemsSource contiene ese número de elementos a los que todavía no se ha desplazado.
NOTE
CarouselView valida la RemainingItemsThreshold propiedad de modo que su valor sea siempre mayor o igual que-1.

En el siguiente ejemplo de XAML se muestra un CarouselView que carga datos incrementalmente:

<CarouselView ItemsSource="{Binding Animals}"


RemainingItemsThreshold="2"
RemainingItemsThresholdReached="OnCarouselViewRemainingItemsThresholdReached"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreDataCommand}">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
RemainingItemsThreshold = 2
};
carouselView.RemainingItemsThresholdReached += OnCollectionViewRemainingItemsThresholdReached;
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");

En este ejemplo de código, el RemainingItemsThresholdReached evento se desencadena cuando hay dos elementos
que todavía no se han desplazado a y, en respuesta, ejecuta el OnCollectionViewRemainingItemsThresholdReached
controlador de eventos:

void OnCollectionViewRemainingItemsThresholdReached(object sender, EventArgs e)


{
// Retrieve more data here and add it to the CollectionView's ItemsSource collection.
}

NOTE
Los datos también se pueden cargar incrementalmente enlazando el RemainingItemsThresholdReachedCommand a una
ICommand implementación de ViewModel.

Vínculos relacionados
CarouselView (ejemplo)
:::no-loc(Xamarin.Forms)::: IndicatorView
:::no-loc(Xamarin.Forms)::: RefreshView
Enlace de datos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Plantillas de datos
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
:::no-loc(Xamarin.Forms)::: Diseño de CarouselView
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
CarouselView define las siguientes propiedades que controlan el diseño:
ItemsLayout , de tipo LinearItemsLayout , especifica el diseño que se va a utilizar.
PeekAreaInsets , de tipo Thickness , especifica la cantidad de tiempo que pueden ver los elementos adyacentes
parcialmente.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
De forma predeterminada, un mostrará CarouselView sus elementos en una orientación horizontal. Se mostrará
un solo elemento en la pantalla, con gestos de deslizar rápidamente, lo que da como resultado la navegación hacia
delante y hacia atrás a través de la colección de elementos. Sin embargo, también es posible una orientación
vertical. Esto se debe ItemsLayout a que la propiedad es de tipo LinearItemsLayout , que hereda de la
ItemsLayout clase. La clase ItemsLayout define las propiedades siguientes:

Orientation , de tipo ItemsLayoutOrientation , especifica la dirección en la que CarouselView se expande a


medida que se agregan elementos.
SnapPointsAlignment , de tipo SnapPointsAlignment , especifica cómo se alinean los puntos de ajuste con los
elementos.
SnapPointsType , de tipo SnapPointsType , especifica el comportamiento de los puntos de ajuste al desplazarse.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos. Para obtener más información acerca de los puntos de acoplamiento, vea puntos
de acoplamiento en la guía de :::no-loc(Xamarin.Forms)::: desplazamiento de CollectionView .
La ItemsLayoutOrientation enumeración define los siguientes miembros:
Vertical indica que CarouselView se expandirá verticalmente a medida que se agreguen elementos.
Horizontal indica que CarouselView se expandirá horizontalmente a medida que se agreguen elementos.

La LinearItemsLayout clase hereda de la ItemsLayout clase y define una ItemSpacing propiedad, de tipo double ,
que representa el espacio vacío alrededor de cada elemento. El valor predeterminado de esta propiedad es 0, y su
valor siempre debe ser mayor o igual que 0. La LinearItemsLayout clase también define Vertical los miembros y
estáticos Horizontal . Estos miembros se pueden usar para crear listas verticales u horizontales, respectivamente.
Como alternativa, LinearItemsLayout se puede crear un objeto, especificando un ItemsLayoutOrientation
miembro de enumeración como argumento.

NOTE
CarouselView usa los motores de diseño nativo para realizar el diseño.
Diseño horizontal
De forma predeterminada, mostrará CarouselView sus elementos horizontalmente. Por lo tanto, no es necesario
establecer la ItemsLayout propiedad para utilizar este diseño:

<CarouselView ItemsSource="{Binding Monkeys}">


<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
<Label Text="{Binding Location}"
HorizontalOptions="Center" />
<Label Text="{Binding Details}"
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>

Como alternativa, este diseño también puede realizarse estableciendo la ItemsLayout propiedad en un
LinearItemsLayout objeto, especificando el miembro de la Horizontal ItemsLayoutOrientation enumeración
como el valor de la Orientation propiedad:

<CarouselView ItemsSource="{Binding Monkeys}">


<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" />
</CarouselView.ItemsLayout>
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
...
ItemsLayout = LinearItemsLayout.Horizontal
};

Esto da como resultado un diseño que crece horizontalmente a medida que se agregan nuevos elementos:
Diseño vertical
CarouselView puede mostrar sus elementos verticalmente estableciendo la ItemsLayout propiedad en un
LinearItemsLayout objeto, especificando el miembro de la Vertical ItemsLayoutOrientation enumeración como
el valor de la Orientation propiedad:

<CarouselView ItemsSource="{Binding Monkeys}">


<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CarouselView.ItemsLayout>
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
<Label Text="{Binding Location}"
HorizontalOptions="Center" />
<Label Text="{Binding Details}"
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>

El código de C# equivalente es el siguiente:


CarouselView carouselView = new CarouselView
{
...
ItemsLayout = LinearItemsLayout.Vertical
};

Esto da como resultado un diseño que crece verticalmente a medida que se agregan nuevos elementos:

Elementos adyacentes parcialmente visibles


De forma predeterminada, CarouselView muestra todos los elementos a la vez. Sin embargo, este
comportamiento se puede cambiar estableciendo la PeekAreaInsets propiedad en un Thickness valor que
especifica la cantidad de tiempo que pueden ver los elementos adyacentes parcialmente. Esto puede ser útil para
indicar a los usuarios que hay elementos adicionales que ver. En el código XAML siguiente se muestra un ejemplo
de cómo establecer esta propiedad:

<CarouselView ItemsSource="{Binding Monkeys}"


PeekAreaInsets="100">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
...
PeekAreaInsets = new Thickness(100)
};

El resultado es que los elementos adyacentes se exponen parcialmente en la pantalla:


Espaciado de elementos
De forma predeterminada, no hay ningún espacio entre cada elemento de CarouselView . Este comportamiento se
puede cambiar estableciendo la ItemSpacing propiedad en el diseño de los elementos que utiliza CarouselView .
Cuando un CarouselView establece su ItemsLayout propiedad en un LinearItemsLayout objeto, la
LinearItemsLayout.ItemSpacing propiedad se puede establecer en un double valor que representa el espacio entre
los elementos:

<CarouselView ItemsSource="{Binding Monkeys}">


<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20" />
</CarouselView.ItemsLayout>
...
</CarouselView>

NOTE
La LinearItemsLayout.ItemSpacing propiedad tiene un conjunto de devoluciones de llamada de validación, que garantiza
que el valor de la propiedad sea siempre mayor o igual que 0.

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
...
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
ItemSpacing = 20
}
};

Este código genera un diseño vertical, que tiene un espaciado de 20 elementos.

Cambio de tamaño dinámico de los elementos


Los elementos de un CarouselView se pueden cambiar de tamaño dinámicamente en tiempo de ejecución
cambiando las propiedades relacionadas con el diseño de los elementos dentro de DataTemplate . Por ejemplo, en
el ejemplo de código siguiente se cambian las HeightRequest WidthRequest propiedades y de un Image objeto, y
la HeightRequest propiedad de su elemento primario Frame :

void OnImageTapped(object sender, EventArgs e)


{
Image image = sender as Image;
image.HeightRequest = image.WidthRequest = image.HeightRequest.Equals(150) ? 200 : 150;
Frame frame = ((Frame)image.Parent.Parent);
frame.HeightRequest = frame.HeightRequest.Equals(300) ? 350 : 300;
}

El OnImageTapped controlador de eventos se ejecuta en respuesta a un Image objeto que se está punteando y
cambia las dimensiones de la imagen (y su elemento primario Frame ), para que se vea más fácilmente:
Diseño de derecha a izquierda
CarouselView puede diseñar su contenido en una dirección de flujo de derecha a izquierda estableciendo su
FlowDirection propiedad en RightToLeft . Sin embargo, la FlowDirection propiedad debe establecerse
idealmente en un diseño de página o raíz, lo que hace que todos los elementos de la página o el diseño raíz
respondan a la dirección del flujo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CarouselViewDemos.Views.HorizontalTemplateLayoutRTLPage"
Title="Horizontal layout (RTL FlowDirection)"
FlowDirection="RightToLeft">
<CarouselView ItemsSource="{Binding Monkeys}">
...
</CarouselView>
</ContentPage>

El valor predeterminado FlowDirection de un elemento con un elemento primario es MatchParent . Por


consiguiente, CarouselView hereda el FlowDirection valor de propiedad de ContentPage .
Para obtener más información acerca de la dirección de flujo, consulte localización de derecha a izquierda.

Vínculos relacionados
CarouselView (ejemplo)
Localización de derecha a izquierda
:::no-loc(Xamarin.Forms)::: Desplazar CarouselView
:::no-loc(Xamarin.Forms)::: Interacción con
CarouselView
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
CarouselView define las siguientes propiedades que controlan la interacción del usuario:
CurrentItem , de tipo object , que se muestra en el elemento actual. Esta propiedad tiene un modo de enlace
predeterminado de TwoWay , y tiene un null valor cuando no hay datos para mostrar.
CurrentItemChangedCommand , de tipo ICommand , que se ejecuta cuando cambia el elemento actual.
CurrentItemChangedCommandParameter , de tipo object , que es el parámetro que se pasa a
CurrentItemChangedCommand .
IsBounceEnabled , de tipo bool , que especifica si CarouselView se rebotará en un límite de contenido. El valor
predeterminado es true .
IsSwipeEnabled , de tipo bool , que determina si un gesto de deslizar rápidamente cambiará el elemento
mostrado. El valor predeterminado es true .
Position , de tipo int , el índice del elemento actual de la colección subyacente. Esta propiedad tiene un modo
de enlace predeterminado de TwoWay , y tiene un valor 0 cuando no hay datos para mostrar.
PositionChangedCommand , de tipo ICommand , que se ejecuta cuando cambia la posición.
PositionChangedCommandParameter , de tipo object , que es el parámetro que se pasa a PositionChangedCommand .
VisibleViews , de tipo ObservableCollection<View> , que es una propiedad de solo lectura que contiene los
objetos de los elementos que están visibles actualmente.
Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos.
CarouselView define un CurrentItemChanged evento que se desencadena cuando CurrentItem cambia la
propiedad, ya sea debido al desplazamiento por el usuario, o cuando una aplicación establece la propiedad. El
CurrentItemChangedEventArgs objeto que acompaña al CurrentItemChanged evento tiene dos propiedades, ambas de
tipo object :
PreviousItem : el elemento anterior, después del cambio de la propiedad.
CurrentItem : el elemento actual, después de cambiar la propiedad.
CarouselView también define un PositionChanged evento que se desencadena cuando Position cambia la
propiedad, ya sea debido al desplazamiento por el usuario, o cuando una aplicación establece la propiedad. El
PositionChangedEventArgs objeto que acompaña al PositionChanged evento tiene dos propiedades, ambas de tipo
int :

PreviousPosition : la posición anterior, después del cambio de la propiedad.


CurrentPosition : la posición actual, después del cambio de la propiedad.

Responder al cambio del elemento actual


Cuando el elemento mostrado actualmente cambie, la CurrentItem propiedad se establecerá en el valor del
elemento. Cuando esta propiedad cambia, CurrentItemChangedCommand se ejecuta con el valor de
CurrentItemChangedCommandParameter que se pasa a ICommand . Position A continuación, la propiedad se actualiza y
se CurrentItemChanged desencadena el evento.

IMPORTANT
La Position propiedad cambia cuando CurrentItem cambia la propiedad. Esto hará PositionChangedCommand que se
ejecute y se PositionChanged desencadene el evento.

Evento
En el ejemplo de XAML siguiente CarouselView se muestra un objeto que utiliza un controlador de eventos para
responder al cambio del elemento actual:

<CarouselView ItemsSource="{Binding Monkeys}"


CurrentItemChanged="OnCurrentItemChanged">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.CurrentItemChanged += OnCurrentItemChanged;

En este ejemplo, el OnCurrentItemChanged controlador de eventos se ejecuta cuando se CurrentItemChanged activa


el evento:

void OnCurrentItemChanged(object sender, CurrentItemChangedEventArgs e)


{
Monkey previousItem = e.PreviousItem as Monkey;
Monkey currentItem = e.CurrentItem as Monkey;
}

En este ejemplo, el OnCurrentItemChanged controlador de eventos expone los elementos anteriores y actuales:

Get-Help
En el siguiente ejemplo de XAML CarouselView se muestra un que usa un comando para responder al elemento
actual Changing:
<CarouselView ItemsSource="{Binding Monkeys}"
CurrentItemChangedCommand="{Binding ItemChangedCommand}"
CurrentItemChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=CurrentItem}">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.CurrentItemChangedCommandProperty, "ItemChangedCommand");
carouselView.SetBinding(CarouselView.CurrentItemChangedCommandParameterProperty, new Binding("CurrentItem",
source: RelativeBindingSource.Self));

En este ejemplo, la CurrentItemChangedCommand propiedad se enlaza a la ItemChangedCommand propiedad, pasando el


CurrentItem valor de la propiedad como argumento. ItemChangedCommand Después, puede responder al cambio del
elemento actual, según sea necesario:

public ICommand ItemChangedCommand => new Command<Monkey>((item) =>


{
PreviousMonkey = CurrentMonkey;
CurrentMonkey = item;
});

En este ejemplo, ItemChangedCommand actualiza los objetos que almacenan los elementos anteriores y actuales.

Responder al cambio de posición


Cuando el elemento mostrado actualmente cambia, la Position propiedad se establecerá en el índice del
elemento actual de la colección subyacente. Cuando esta propiedad cambia, PositionChangedCommand se ejecuta con
el valor de PositionChangedCommandParameter que se pasa a ICommand . PositionChanged A continuación, se activa el
evento. Si la Position propiedad se ha cambiado mediante programación, se CarouselView desplazará al
elemento que se corresponde con el Position valor.

NOTE
Si el valor de la propiedad se establece Position en 0, se mostrará el primer elemento de la colección subyacente.

Evento
En el ejemplo de XAML siguiente CarouselView se muestra un que usa un controlador de eventos para responder
al Position cambio de propiedad:

<CarouselView ItemsSource="{Binding Monkeys}"


PositionChanged="OnPositionChanged">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.PositionChanged += OnPositionChanged;
En este ejemplo, el OnPositionChanged controlador de eventos se ejecuta cuando se PositionChanged activa el
evento:

void OnPositionChanged(object sender, PositionChangedEventArgs e)


{
int previousItemPosition = e.PreviousPosition;
int currentItemPosition = e.CurrentPosition;
}

En este ejemplo, el OnCurrentItemChanged controlador de eventos expone las posiciones anterior y actual:

Get-Help
En el siguiente ejemplo de XAML CarouselView se muestra un que usa un comando para responder al Position
cambio de propiedad:

<CarouselView ItemsSource="{Binding Monkeys}"


PositionChangedCommand="{Binding PositionChangedCommand}"
PositionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=Position}">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.PositionChangedCommandProperty, "PositionChangedCommand");
carouselView.SetBinding(CarouselView.PositionChangedCommandParameterProperty, new Binding("Position", source:
RelativeBindingSource.Self));

En este ejemplo, la PositionChangedCommand propiedad se enlaza a la PositionChangedCommand propiedad, pasando el


Position valor de la propiedad como argumento. PositionChangedCommand A continuación, puede responder a la
posición que cambia, según sea necesario:

public ICommand PositionChangedCommand => new Command<int>((position) =>


{
PreviousPosition = CurrentPosition;
CurrentPosition = position;
});
En este ejemplo, se PositionChangedCommand actualizan los objetos que almacenan las posiciones anterior y actual.

Predefinir el elemento actual


El elemento actual de un CarouselView se puede establecer mediante programación estableciendo la CurrentItem
propiedad en el elemento. En el ejemplo de XAML siguiente se muestra un CarouselView objeto que elige
previamente el elemento actual:

<CarouselView ItemsSource="{Binding Monkeys}"


CurrentItem="{Binding CurrentItem}">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.CurrentItemProperty, "CurrentItem");

NOTE
La CurrentItem propiedad tiene un modo de enlace predeterminado de TwoWay .

Los CarouselView.CurrentItem datos de la propiedad se enlazan a la CurrentItem propiedad del modelo de vista
conectado, que es de tipo Monkey . De forma predeterminada, TwoWay se utiliza un enlace para que si el usuario
cambia el elemento actual, el valor de la CurrentItem propiedad se establecerá en el Monkey objeto actual. La
CurrentItem propiedad se define en la MonkeysViewModel clase:

public class MonkeysViewModel : INotifyPropertyChanged


{
// ...
public ObservableCollection<Monkey> Monkeys { get; private set; }

public Monkey CurrentItem { get; set; }

public MonkeysViewModel()
{
// ...
CurrentItem = Monkeys.Skip(3).FirstOrDefault();
OnPropertyChanged("CurrentItem");
}
}

En este ejemplo, la CurrentItem propiedad se establece en el cuarto elemento de la Monkeys colección:


Valor preestablecido de la posición
El elemento mostrado CarouselView se puede establecer mediante programación estableciendo la Position
propiedad en el índice del elemento de la colección subyacente. En el ejemplo de XAML siguiente se muestra un
CarouselView objeto que establece el elemento mostrado:

<CarouselView ItemsSource="{Binding Monkeys}"


Position="{Binding Position}">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.PositionProperty, "Position");

NOTE
La Position propiedad tiene un modo de enlace predeterminado de TwoWay .

Los CarouselView.Position datos de la propiedad se enlazan a la Position propiedad del modelo de vista
conectado, que es de tipo int . De forma predeterminada, TwoWay se usa un enlace para que si el usuario se
desplaza por el CarouselView , el valor de la Position propiedad se establecerá en el índice del elemento
mostrado. La Position propiedad se define en la MonkeysViewModel clase:
public class MonkeysViewModel : INotifyPropertyChanged
{
// ...
public int Position { get; set; }

public MonkeysViewModel()
{
// ...
Position = 3;
OnPropertyChanged("Position");
}
}

En este ejemplo, la Position propiedad se establece en el cuarto elemento de la Monkeys colección:

Definir Estados visuales


CarouselView define cuatro Estados visuales:
CurrentItem representa el estado visual del elemento que se muestra actualmente.
PreviousItem representa el estado visual del elemento mostrado previamente.
NextItem representa el estado visual del elemento siguiente.
DefaultItem representa el estado visual para el resto de los elementos.

Estos Estados visuales se pueden usar para iniciar cambios visuales en los elementos mostrados por CarouselView
.
En el siguiente ejemplo de XAML se muestra cómo definir los CurrentItem Estados de, PreviousItem , NextItem y
DefaultItem :
<CarouselView ItemsSource="{Binding Monkeys}"
PeekAreaInsets="100">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="CurrentItem">
<VisualState.Setters>
<Setter Property="Scale"
Value="1.1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PreviousItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NextItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="DefaultItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.25" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<!-- Item template content -->


<Frame HasShadow="true">
...
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>

En este ejemplo, el CurrentItem estado visual especifica que el elemento actual mostrado por CarouselView tendrá
su Scale propiedad cambiada de su valor predeterminado de 1 a 1,1. Los PreviousItem NextItem Estados visual
y especifican que los elementos que rodean al elemento actual se mostrarán con un Opacity valor de 0,5. El
DefaultItem estado visual especifica que el resto de los elementos mostrados por se CarouselView mostrarán con
un Opacity valor de 0,25.

NOTE
Como alternativa, los Estados visuales se pueden definir en un Style que tiene un TargetType valor de propiedad que es
el tipo del elemento raíz de DataTemplate , que se establece como el valor de la ItemTemplate propiedad.

Las capturas de pantallas siguientes muestran los CurrentItem PreviousItem NextItem Estados visuales, y:
Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.

Borrar el elemento actual


La CurrentItem propiedad se puede borrar estableciéndolo, o el objeto al que se enlaza, en null .

Deshabilitar rebote
De forma predeterminada, CarouselView rebota los elementos en los límites de contenido. Esto se puede
deshabilitar estableciendo la IsBounceEnabled propiedad en false .

Deshabilitar la interacción de deslizar rápidamente


De forma predeterminada, CarouselView permite a los usuarios desplazarse por los elementos con un gesto de
deslizar rápidamente. Esta interacción de deslizar rápidamente puede deshabilitarse estableciendo la
IsSwipeEnabled propiedad en false .

Vínculos relacionados
CarouselView (ejemplo)
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: CarouselView EmptyView
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
CarouselView define las siguientes propiedades que se pueden usar para proporcionar comentarios del usuario
cuando no hay datos para mostrar:
EmptyView , de tipo object , la cadena, el enlace o la vista que se mostrarán cuando la ItemsSource propiedad
sea null , o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. El valor
predeterminado es null .
EmptyViewTemplate , de tipo DataTemplate , la plantilla que se va a utilizar para dar formato al especificado
EmptyView . El valor predeterminado es null .

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
Los principales escenarios de uso para establecer la propiedad muestran los EmptyView comentarios de los
usuarios cuando una operación de filtrado en CarouselView no produce datos y muestra los comentarios de los
usuarios mientras se recuperan los datos de un servicio Web.

NOTE
La EmptyView propiedad se puede establecer en una vista que incluya contenido interactivo si es necesario.

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Mostrar una cadena cuando los datos no estén disponibles


La EmptyView propiedad se puede establecer en una cadena, que se mostrará cuando la ItemsSource propiedad
sea null o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. En el código
XAML siguiente se muestra un ejemplo de este escenario:

<CarouselView ItemsSource="{Binding EmptyMonkeys}"


EmptyView="No items to display." />

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
EmptyView = "No items to display."
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "EmptyMonkeys");
El resultado es que, dado que la colección enlazada a datos es null , se muestra la cadena establecida como el
valor de la EmptyView propiedad.

Mostrar vistas cuando los datos no están disponibles


La EmptyViewpropiedad se puede establecer en una vista, que se mostrará cuando la ItemsSource propiedad sea
null o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. Puede ser una vista
única o una vista que contiene varias vistas secundarias. En el ejemplo de XAML siguiente EmptyView se muestra la
propiedad establecida en una vista que contiene varias vistas secundarias:

<StackLayout Margin="20">
<SearchBar SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource Self}, Path=Text}"
Placeholder="Filter" />
<CarouselView ItemsSource="{Binding Monkeys}">
<CarouselView.EmptyView>
<ContentView>
<StackLayout HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<Label Text="No results matched your filter."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
<Label Text="Try a broader filter?"
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>

En este ejemplo, lo que parece que se ha agregado un redundante es ContentView el elemento raíz de EmptyView .
Esto se debe a que internamente, EmptyView se agrega a un contenedor nativo que no proporciona ningún
contexto para el :::no-loc(Xamarin.Forms)::: diseño. Por lo tanto, para colocar las vistas que componen su EmptyView
, debe agregar un diseño raíz, cuyo elemento secundario es un diseño que puede colocarse en el diseño raíz.
El código de C# equivalente es el siguiente:
SearchBar searchBar = new SearchBar { ... };
CarouselView carouselView = new CarouselView
{
EmptyView = new ContentView
{
Content = new StackLayout
{
Children =
{
new Label { Text = "No results matched your filter.", ... },
new Label { Text = "Try a broader filter?", ... }
}
}
}
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

Cuando SearchBar ejecuta FilterCommand , la colección que muestra CarouselView se filtra para el término de
búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos, StackLayout
se muestra el conjunto como EmptyView valor de propiedad.

Mostrar un tipo personalizado con plantilla cuando los datos no estén


disponibles
La EmptyView propiedad se puede establecer en un tipo personalizado, cuya plantilla se muestra cuando la
ItemsSource propiedad es null , o cuando la colección especificada por la ItemsSource propiedad es null o está
vacía. La EmptyViewTemplate propiedad se puede establecer en un DataTemplate que define la apariencia de
EmptyView . En el código XAML siguiente se muestra un ejemplo de este escenario:

<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource Self}, Path=Text}"
Placeholder="Filter" />
<CarouselView ItemsSource="{Binding Monkeys}">
<CarouselView.EmptyView>
<controls:FilterData Filter="{Binding Source={x:Reference searchBar}, Path=Text}" />
</CarouselView.EmptyView>
<CarouselView.EmptyViewTemplate>
<DataTemplate>
<Label Text="{Binding Filter, StringFormat='Your filter term of {0} did not match any
records.'}"
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</DataTemplate>
</CarouselView.EmptyViewTemplate>
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>

El código de C# equivalente es el siguiente:


SearchBar searchBar = new SearchBar { ... };
CarouselView carouselView = new CarouselView
{
EmptyView = new FilterData { Filter = searchBar.Text },
EmptyViewTemplate = new DataTemplate(() =>
{
return new Label { ... };
})
};

El FilterData tipo define una Filter propiedad y un correspondiente BindableProperty :

public class FilterData : BindableObject


{
public static readonly BindableProperty FilterProperty = BindableProperty.Create(nameof(Filter),
typeof(string), typeof(FilterData), null);

public string Filter


{
get { return (string)GetValue(FilterProperty); }
set { SetValue(FilterProperty, value); }
}
}

La EmptyView propiedad se establece en un FilterData objeto y los datos de la Filter propiedad se enlazan a la
SearchBar.Text propiedad. Cuando SearchBar ejecuta FilterCommand , la colección que muestra CarouselView se
filtra para el término de búsqueda almacenado en la Filter propiedad. Si la operación de filtrado no produce
datos, Label se muestra el definido en DataTemplate , que se establece como el valor de la EmptyViewTemplate
propiedad.

NOTE
Cuando se muestra un tipo personalizado con plantilla cuando los datos no están disponibles, la EmptyViewTemplate
propiedad se puede establecer en una vista que contiene varias vistas secundarias.

Elegir un EmptyView en tiempo de ejecución


Las vistas que se mostrarán como EmptyView cuando los datos no estén disponibles, se pueden definir como
ContentView objetos en un ResourceDictionary . EmptyView A continuación, la propiedad se puede establecer en un
específico ContentView , en función de alguna lógica de negocios, en tiempo de ejecución. En el ejemplo de XAML
siguiente se muestra un ejemplo de este escenario:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:CarouselViewDemos.ViewModels"
x:Class="CarouselViewDemos.Views.EmptyViewSwapPage"
Title="EmptyView (swap)">
<ContentPage.BindingContext>
<viewmodels:MonkeysViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ContentView x:Key="BasicEmptyView">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
<Label Text="No results matched your filter."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
<Label Text="Try a broader filter?"
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<SearchBar SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource Self}, Path=Text}"
Placeholder="Filter" />
<StackLayout Orientation="Horizontal">
<Label Text="Toggle EmptyViews" />
<Switch Toggled="OnEmptyViewSwitchToggled" />
</StackLayout>
<CarouselView x:Name="carouselView"
ItemsSource="{Binding Monkeys}">
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentPage>

Este código XAML define dos ContentView objetos en el nivel de página ResourceDictionary , con el Switch objeto
que controla qué ContentView objeto se establecerá como el valor de la EmptyView propiedad. Cuando Switch se
alterna, el controlador de OnEmptyViewSwitchToggled eventos ejecuta el ToggleEmptyView método:

void ToggleEmptyView(bool isToggled)


{
carouselView.EmptyView = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
}

El ToggleEmptyView método establece la EmptyView propiedad del carouselView objeto en uno de los dos
ContentView objetos almacenados en ResourceDictionary , basándose en el valor de la Switch.IsToggled
propiedad. Cuando SearchBar ejecuta FilterCommand , la colección que muestra CarouselView se filtra para el
término de búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos,
ContentView se muestra el objeto establecido como la EmptyView propiedad.

Para obtener más información acerca de los diccionarios de recursos, consulte :::no-loc(Xamarin.Forms):::
diccionarios de recursos.

Elegir un EmptyViewTemplate en tiempo de ejecución


La apariencia de se puede elegir en tiempo de ejecución, en función de su valor, estableciendo la
EmptyView
CarouselView.EmptyViewTemplate propiedad en un DataTemplateSelector objeto:

<ContentPage ...
xmlns:controls="clr-namespace:CarouselViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AdvancedTemplate">
...
</DataTemplate>

<DataTemplate x:Key="BasicTemplate">
...
</DataTemplate>

<controls:SearchTermDataTemplateSelector x:Key="SearchSelector"
DefaultTemplate="{StaticResource AdvancedTemplate}"
OtherTemplate="{StaticResource BasicTemplate}" />
</ContentPage.Resources>

<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource Self}, Path=Text}"
Placeholder="Filter" />
<CarouselView ItemsSource="{Binding Monkeys}"
EmptyView="{Binding Source={x:Reference searchBar}, Path=Text}"
EmptyViewTemplate="{StaticResource SearchSelector}">
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentPage>

El código de C# equivalente es el siguiente:

SearchBar searchBar = new SearchBar { ... };


CarouselView carouselView = new CarouselView()
{
EmptyView = searchBar.Text,
EmptyViewTemplate = new SearchTermDataTemplateSelector { ... }
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

La EmptyView propiedad se establece en la SearchBar.Text propiedad y la EmptyViewTemplate propiedad se


establece en un SearchTermDataTemplateSelector objeto.
Cuando SearchBar ejecuta FilterCommand , la colección que muestra CarouselView se filtra para el término de
búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos, el
DataTemplate elegido por el SearchTermDataTemplateSelector objeto se establece como la EmptyViewTemplate
propiedad y se muestra.
En el ejemplo siguiente se muestra la SearchTermDataTemplateSelector clase:

public class SearchTermDataTemplateSelector : DataTemplateSelector


{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate OtherTemplate { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
string query = (string)item;
return query.ToLower().Equals("xamarin") ? OtherTemplate : DefaultTemplate;
}
}

La SearchTermTemplateSelector clase define DefaultTemplate OtherTemplate DataTemplate las propiedades y que


se establecen en distintas plantillas de datos. La OnSelectTemplate invalidación devuelve DefaultTemplate , que
muestra un mensaje al usuario, cuando la consulta de búsqueda no es igual a "Xamarin". Cuando la consulta de
búsqueda es igual a "Xamarin", la OnSelectTemplate invalidación devuelve OtherTemplate , que muestra un
mensaje básico al usuario.
Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-loc(Xamarin.Forms):::
DataTemplateSelector.

Vínculos relacionados
CarouselView (ejemplo)
:::no-loc(Xamarin.Forms)::: Plantillas de datos
Diccionarios de recursos de :::no-loc(Xamarin.Forms):::
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
:::no-loc(Xamarin.Forms)::: Desplazar CarouselView
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
CarouselView define las siguientes propiedades relacionadas con el desplazamiento:
HorizontalScrollBarVisibility , de tipo ScrollBarVisibility , que especifica cuándo está visible la barra de
desplazamiento horizontal.
IsDragging , de tipo bool , que indica si CarouselView se está desplazando el. Se trata de una propiedad de
solo lectura, cuyo valor predeterminado es false .
IsScrollAnimated , de tipo bool , que especifica si se producirá una animación al desplazarse CarouselView . El
valor predeterminado es true .
ItemsUpdatingScrollMode , de tipo ItemsUpdatingScrollMode , que representa el comportamiento de
desplazamiento de CarouselView cuando se agregan nuevos elementos a él.
VerticalScrollBarVisibility , de tipo ScrollBarVisibility , que especifica cuándo está visible la barra de
desplazamiento vertical.
Todas estas propiedades están respaldadas por BindableProperty objetos, lo que significa que pueden ser
destinos de enlaces de datos.
CarouselView también define dos ScrollTo métodos, que desplazan los elementos a la vista. Una de las
sobrecargas desplaza el elemento en el índice especificado en la vista, mientras que el otro desplaza el elemento
especificado a la vista. Ambas sobrecargas tienen argumentos adicionales que se pueden especificar para indicar
la posición exacta del elemento una vez que se ha completado el desplazamiento y si se debe animar el
desplazamiento.
CarouselView define un evento que se desencadena cuando se invoca a uno de los ScrollTo
ScrollToRequested
métodos. El ScrollToRequestedEventArgs objeto que acompaña al ScrollToRequested evento tiene muchas
propiedades, entre las que se incluyen IsAnimated , Index , Item y ScrollToPosition . Estas propiedades se
establecen a partir de los argumentos especificados en las ScrollTo llamadas de método.
Además, CarouselView define un Scrolled evento que se desencadena para indicar que se ha producido el
desplazamiento. El ItemsViewScrolledEventArgs objeto que acompaña al Scrolled evento tiene muchas
propiedades. Para obtener más información, vea detectar desplazamiento.
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Esta característica se conoce como ajuste, ya
que los elementos se ajustan a la posición cuando se detiene el desplazamiento. Para obtener más información,
vea puntos de acoplamiento.
CarouselView también puede cargar datos de forma incremental a medida que el usuario se desplaza. Para
obtener más información, vea cargar datos de forma incremental.

Detectar desplazamiento
Se IsDragging puede examinar la propiedad para determinar si CarouselView se está desplazando actualmente
por los elementos.
Además, CarouselView define un Scrolled evento que se desencadena para indicar que se ha producido el
desplazamiento. Este evento se debe usar cuando se requieren datos sobre el desplazamiento.
En el ejemplo de XAML siguiente se muestra un CarouselView que establece un controlador de eventos para el
Scrolled evento:

<CarouselView Scrolled="OnCollectionViewScrolled">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView();


carouselView.Scrolled += OnCarouselViewScrolled;

En este ejemplo de código, el OnCarouselViewScrolled controlador de eventos se ejecuta cuando se Scrolled


desencadena el evento:

void OnCarouselViewScrolled(object sender, ItemsViewScrolledEventArgs e)


{
Debug.WriteLine("HorizontalDelta: " + e.HorizontalDelta);
Debug.WriteLine("VerticalDelta: " + e.VerticalDelta);
Debug.WriteLine("HorizontalOffset: " + e.HorizontalOffset);
Debug.WriteLine("VerticalOffset: " + e.VerticalOffset);
Debug.WriteLine("FirstVisibleItemIndex: " + e.FirstVisibleItemIndex);
Debug.WriteLine("CenterItemIndex: " + e.CenterItemIndex);
Debug.WriteLine("LastVisibleItemIndex: " + e.LastVisibleItemIndex);
}

En este ejemplo, el OnCarouselViewScrolled controlador de eventos genera los valores del


ItemsViewScrolledEventArgs objeto que acompaña al evento.

IMPORTANT
El Scrolled evento se desencadena para los desplazamientos iniciados por el usuario y para los desplazamientos mediante
programación.

Desplazar un elemento en un índice en la vista


La primera ScrollTo sobrecarga del método desplaza el elemento en el índice especificado en la vista. Dado un
CarouselView objeto denominado carouselView , en el ejemplo siguiente se muestra cómo desplazar el elemento
en el índice 6 a la vista:

carouselView.ScrollTo(6);

NOTE
El ScrollToRequested evento se desencadena cuando ScrollTo se invoca el método.
Desplazar un elemento a la vista
La segunda ScrollTo sobrecarga del método desplaza el elemento especificado a la vista. Dado un CarouselView
objeto denominado carouselView , en el ejemplo siguiente se muestra cómo desplazar el elemento Proboscis
Monkey a la vista:

MonkeysViewModel viewModel = BindingContext as MonkeysViewModel;


Monkey monkey = viewModel.Monkeys.FirstOrDefault(m => m.Name == "Proboscis Monkey");
carouselView.ScrollTo(monkey);

NOTE
El ScrollToRequested evento se desencadena cuando ScrollTo se invoca el método.

Deshabilitar animación de desplazamiento


Se muestra una animación de desplazamiento al moverse entre los elementos de un CarouselView . Esta
animación se produce para los desplazamientos iniciados por el usuario y para los desplazamientos mediante
programación. Al establecer la IsScrollAnimated propiedad en false , se deshabilitará la animación para ambas
categorías de desplazamiento.
Como alternativa, el animate argumento del ScrollTo método se puede establecer en false para deshabilitar la
animación de desplazamiento en desplazamientos mediante programación:

carouselView.ScrollTo(monkey, animate: false);

Posición de desplazamiento de control


Al desplazar un elemento a la vista, se puede especificar la posición exacta del elemento una vez completado el
desplazamiento con el position argumento de los ScrollTo métodos. Este argumento acepta un
ScrollToPosition miembro de enumeración.

MakeVisible
El ScrollToPosition.MakeVisible miembro indica que el elemento debe desplazarse hasta que esté visible en la
vista:

carouselView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible);

Este código de ejemplo produce el desplazamiento mínimo necesario para desplazar el elemento a la vista.

NOTE
El ScrollToPosition.MakeVisible miembro se utiliza de forma predeterminada si position no se especifica el
argumento al llamar al ScrollTo método.

Start
El ScrollToPosition.Start miembro indica que el elemento debe desplazarse hasta el inicio de la vista:

carouselView.ScrollTo(monkey, position: ScrollToPosition.Start);


Este código de ejemplo hace que el elemento se desplace hasta el inicio de la vista.
Center
El ScrollToPosition.Center miembro indica que el elemento debe desplazarse hasta el centro de la vista:

carouselViewView.ScrollTo(monkey, position: ScrollToPosition.Center);

Este código de ejemplo hace que el elemento se desplace hasta el centro de la vista.
End
El ScrollToPosition.End miembro indica que el elemento debe desplazarse hasta el final de la vista:

carouselViewView.ScrollTo(monkey, position: ScrollToPosition.End);

Este código de ejemplo hace que el elemento se desplace hasta el final de la vista.

Controlar la posición de desplazamiento cuando se agregan nuevos


elementos
CarouselView define una ItemsUpdatingScrollMode propiedad, que está respaldada por una propiedad enlazable.
Esta propiedad obtiene o establece un ItemsUpdatingScrollMode valor de enumeración que representa el
comportamiento de desplazamiento de CarouselView cuando se agregan nuevos elementos a él. La enumeración
ItemsUpdatingScrollMode define los miembros siguientes:

KeepItemsInView mantiene el primer elemento de la lista que se muestra cuando se agregan nuevos
elementos.
KeepScrollOffset garantiza que la posición de desplazamiento actual se mantiene cuando se agregan nuevos
elementos.
KeepLastItemInViewajusta el desplazamiento de desplazamiento para mantener el último elemento de la lista
que se muestra cuando se agregan nuevos elementos.
El valor predeterminado de la ItemsUpdatingScrollMode propiedad es KeepItemsInView . Por lo tanto, cuando se
agreguen nuevos elementos al CarouselView primer elemento de la lista, se mostrarán. Para asegurarse de que el
último elemento de la lista se muestra cuando se agregan nuevos elementos, establezca la
ItemsUpdatingScrollMode propiedad en KeepLastItemInView :

<CarouselView ItemsUpdatingScrollMode="KeepLastItemInView">
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView
};

Visibilidad de la barra de desplazamiento


CarouselView define HorizontalScrollBarVisibility VerticalScrollBarVisibility las propiedades y, que están
respaldadas por propiedades enlazables. Estas propiedades obtienen o establecen un ScrollBarVisibility valor
de enumeración que representa Cuándo está visible la barra de desplazamiento horizontal o vertical. La
enumeración ScrollBarVisibility define los miembros siguientes:
Default indica el comportamiento predeterminado de la barra de desplazamiento para la plataforma y es el
valor predeterminado para las HorizontalScrollBarVisibility VerticalScrollBarVisibility propiedades y.
Always indica que las barras de desplazamiento serán visibles, incluso cuando el contenido quepa en la vista.
Never indica que las barras de desplazamiento no estarán visibles, incluso si el contenido no cabe en la vista.

Puntos de acoplamiento
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Esta característica se conoce como ajuste, ya
que los elementos se ajustan a la posición cuando se detiene el desplazamiento y se controlan mediante las
siguientes propiedades de la ItemsLayout clase:
SnapPointsType , de tipo SnapPointsType , especifica el comportamiento de los puntos de ajuste al desplazarse.
SnapPointsAlignment , de tipo SnapPointsAlignment , especifica cómo se alinean los puntos de ajuste con los
elementos.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.

NOTE
Cuando se produce el ajuste, se producirá en la dirección que produce la menor cantidad de movimiento.

Tipo de puntos de ajuste


La SnapPointsType enumeración define los siguientes miembros:
None indica que el desplazamiento no se ajusta a los elementos.
Mandatory indica que el contenido se ajusta siempre al punto de acoplamiento más cercano a donde se
detendría el desplazamiento de forma natural, a lo largo de la dirección de inercia.
MandatorySingle indica el mismo comportamiento que Mandatory , pero solo desplaza un elemento cada vez.

De forma predeterminada CarouselView , en, la SnapPointsType propiedad se establece en


SnapPointsType.MandatorySingle , lo que garantiza que el desplazamiento solo se desplaza un elemento cada vez.
Las siguientes capturas de pantallas muestran un CarouselView con el ajuste desactivado:

Alineación de puntos de acoplamiento


La SnapPointsAlignment enumeración Start define Center End los miembros, y.
IMPORTANT
El valor de la SnapPointsAlignment propiedad solo se respeta cuando la SnapPointsType propiedad está establecida en
Mandatory , o MandatorySingle .

Start
El SnapPointsAlignment.Start miembro indica que los puntos de ajuste están alineados con el borde inicial de los
elementos. En el siguiente ejemplo de XAML se muestra cómo establecer este miembro de enumeración:

<CarouselView ItemsSource="{Binding Monkeys}"


PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Start" />
</CarouselView.ItemsLayout>
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Start
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento en un desplazamiento horizontal CarouselView
, el elemento de la izquierda se alineará a la izquierda de la vista:

Center
El SnapPointsAlignment.Center miembro indica que los puntos de ajuste están alineados con el centro de los
elementos.
De forma predeterminada CarouselView , en, la SnapPointsAlignment propiedad se establece en Center . Sin
embargo, por integridad, en el siguiente ejemplo de XAML se muestra cómo establecer este miembro de
enumeración:
<CarouselView ItemsSource="{Binding Monkeys}"
PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CarouselView.ItemsLayout>
...
</CarouselView>

El código de C# equivalente es el siguiente:

CarouselView carouselView = new CarouselView


{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Center
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento en un desplazamiento horizontal CarouselView
, el elemento central se alinea con el centro de la vista:

End
El SnapPointsAlignment.End miembro indica que los puntos de ajuste están alineados con el borde final de los
elementos. En el siguiente ejemplo de XAML se muestra cómo establecer este miembro de enumeración:

<CarouselView ItemsSource="{Binding Monkeys}"


PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="End" />
</CarouselView.ItemsLayout>
...
</CarouselView>

El código de C# equivalente es el siguiente:


CarouselView carouselView = new CarouselView
{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.End
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento en un desplazamiento horizontal CarouselView
, el elemento de la derecha se alineará a la derecha de la vista.

Vínculos relacionados
CarouselView (ejemplo)
CollectionView de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Introducción
La CollectionView es una vista flexible y eficaz para presentar listas de datos con diferentes especificaciones de
diseño.

Data
Un CollectionView se rellena con datos estableciendo su ItemsSource propiedad en cualquier colección que
implementa IEnumerable . La apariencia de cada elemento de la lista se puede definir estableciendo la
ItemTemplate propiedad en DataTemplate .

Diseño
De forma predeterminada, un mostrará CollectionView sus elementos en una lista vertical. Sin embargo, se
pueden especificar listas y cuadrículas verticales y horizontales.

Selección
De forma predeterminada, la CollectionView selección está deshabilitada. Sin embargo, se puede habilitar la
selección única y múltiple.

Vistas vacías
En CollectionView , se puede especificar una vista vacía que proporciona comentarios al usuario cuando no hay
datos disponibles para mostrar. La vista vacía puede ser una cadena, una vista o varias vistas.

Desplazarse
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Además, CollectionView define dos
ScrollTo métodos, que desplazan los elementos mediante programación a la vista. Una de las sobrecargas
desplaza el elemento en el índice especificado en la vista, mientras que el otro desplaza el elemento especificado
a la vista.

Agrupación
CollectionView puede mostrar datos agrupados correctamente estableciendo su IsGrouped propiedad en true .
:::no-loc(Xamarin.Forms)::: Introducción a
CollectionView
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
CollectionView es una vista para presentar listas de datos con diferentes especificaciones de diseño. Pretende
proporcionar una alternativa más flexible y de rendimiento a ListView . Por ejemplo, las siguientes capturas de
pantallas muestran un CollectionView que usa una cuadrícula vertical de dos columnas y que permite la selección
múltiple:

CollectionView debe usarse para presentar listas de datos que requieren desplazamiento o selección. Se puede
usar un diseño enlazable cuando los datos que se van a mostrar no requieren desplazamiento o selección. Para
obtener más información, vea diseños enlazables en :::no-loc(Xamarin.Forms)::: .
CollectionView está disponible en :::no-loc(Xamarin.Forms)::: 4,3.

IMPORTANT
CollectionView está disponible en iOS y Android, pero solo está disponible parcialmente en el plataforma universal de
Windows.

Diferencias de CollectionView y ListView


Aunque las CollectionView ListView API y son similares, existen algunas diferencias notables:
CollectionView tiene un modelo de diseño flexible, que permite presentar los datos vertical u horizontalmente,
en una lista o una cuadrícula.
CollectionView admite la selección única y múltiple.
CollectionView no tiene concepto de celdas. En su lugar, se usa una plantilla de datos para definir la apariencia
de cada elemento de datos de la lista.
CollectionView emplea automáticamente la virtualización proporcionada por los controles nativos
subyacentes.
CollectionView reduce la superficie de la API de ListView . Muchas propiedades y eventos de ListView no
están presentes en CollectionView .
CollectionView no incluye separadores integrados.
CollectionView producirá una excepción si ItemsSource se actualiza fuera del subproceso de la interfaz de
usuario.
Pasar de ListView a CollectionView
ListView las implementaciones en :::no-loc(Xamarin.Forms)::: aplicaciones existentes se pueden migrar a
CollectionView implementaciones con la ayuda de la tabla siguiente:

C O N C EP TO A P I DE L A S VISTA S DE L ISTA C O L L EC T IO N VIEW

data ItemsSource Un CollectionView se rellena con


datos estableciendo su ItemsSource
propiedad. Para obtener más
información, vea rellenar una
CollectionView con datos.

Apariencia del elemento ItemTemplate La apariencia de cada elemento de un


CollectionView puede definirse
estableciendo la ItemTemplate
propiedad en DataTemplate . Para
obtener más información, vea definir la
aparienciade los elementos.

Celdas TextCell , ImageCell , ViewCell CollectionView no tiene concepto de


celdas y, por lo tanto, no hay ningún
concepto de indicadores de divulgación.
En su lugar, se usa una plantilla de
datos para definir la apariencia de cada
elemento de datos de la lista.

Separadores de filas SeparatorColor , CollectionView no incluye


SeparatorVisibility separadores integrados. Se pueden
proporcionar, si se desea, en la plantilla
de elemento.

Selección SelectionMode , SelectedItem CollectionView admite la selección


única y múltiple. Para obtener más
información, consulte :::no-
loc(Xamarin.Forms)::: selección de
CollectionView.

Alto de fila HasUnevenRows , RowHeight En CollectionView , el alto de la fila


de cada elemento viene determinado
por la ItemSizingStrategy propiedad.
Para obtener más información, vea
tamaño de los elementos.

Almacenamiento en memoria caché CachingStrategy CollectionView utiliza


automáticamente la virtualización
proporcionada por los controles nativos
subyacentes.

Encabezados y pies de página Header , HeaderElement , CollectionView puede presentar un


HeaderTemplate , Footer , encabezado y un pie de página que se
FooterElement , FooterTemplate desplacen con los elementos de la lista,
a través de las Header Footer
propiedades,, HeaderTemplate y
FooterTemplate . Para obtener más
información, vea encabezados y pies de
página.
C O N C EP TO A P I DE L A S VISTA S DE L ISTA C O L L EC T IO N VIEW

Agrupar GroupDisplayBinding , CollectionView muestra datos


GroupHeaderTemplate , agrupados correctamente estableciendo
GroupShortNameBinding , su IsGrouped propiedad en true .
IsGroupingEnabled Los encabezados de grupo y los pies de
grupo se pueden personalizar
estableciendo GroupHeaderTemplate
las GroupFooterTemplate propiedades
y en DataTemplate objetos. Para
obtener más información, consulte :::no-
loc(Xamarin.Forms)::: agrupación de
CollectionView.

Extraer para actualizar IsPullToRefreshEnabled , La funcionalidad de extracción para


IsRefreshing , RefreshAllowed , actualizar se admite al establecer
RefreshCommand , CollectionView como el elemento
RefreshControlColor , secundario de RefreshView . Para
BeginRefresh() , EndRefresh() obtener más información, consulte
incorporación de cambios a la
actualización.

Elementos del menú contextual ContextActions Los elementos de menú contextual se


admiten al establecer SwipeView como
la vista raíz en el DataTemplate que
define la apariencia de cada elemento
de datos en CollectionView . Para
más información, consulte Menús
contextuales.

Desplazarse ScrollTo() CollectionView define ScrollTo


métodos que desplazan los elementos
en la vista. Para obtener más
información, vea desplazamiento.

Vínculos relacionados
CollectionView (ejemplo)
Diseños enlazables en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Datos de CollectionView
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
CollectionView incluye las siguientes propiedades que definen los datos que se van a mostrar y su apariencia:
ItemsSource , de tipo IEnumerable , especifica la colección de elementos que se van a mostrar y tiene un valor
predeterminado de null .
ItemTemplate , de tipo DataTemplate , especifica la plantilla que se va a aplicar a cada elemento de la colección
de elementos que se va a mostrar.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.

NOTE
CollectionView define una ItemsUpdatingScrollMode propiedad que representa el comportamiento de desplazamiento
de CollectionView cuando se agregan nuevos elementos a él. Para obtener más información sobre esta propiedad, vea
control de posición de desplazamiento cuando se agregan nuevos elementos.

CollectionView admite la virtualización de datos incrementales a medida que el usuario se desplaza. Para obtener
más información, vea cargar datos de forma incremental.

Rellenar un CollectionView con datos


Un CollectionView se rellena con datos estableciendo su ItemsSource propiedad en cualquier colección que
implementa IEnumerable . De forma predeterminada, CollectionView muestra los elementos en una lista vertical.

IMPORTANT
Si CollectionView es necesario actualizar a medida que se agregan, quitan o cambian elementos en la colección
subyacente, la colección subyacente debe ser una IEnumerable colección que envíe notificaciones de cambios de
propiedades, como ObservableCollection .

CollectionView se puede rellenar con datos mediante el enlace de datos para enlazar su ItemsSource propiedad a
una IEnumerable colección. En XAML, esto se consigue con la Binding extensión de marcado:

<CollectionView ItemsSource="{Binding Monkeys}" />

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView();


collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

En este ejemplo, los ItemsSource datos de la propiedad se enlazan a la Monkeys propiedad del ViewModel
conectado.
NOTE
Los enlaces compilados se pueden habilitar para mejorar el rendimiento del enlace de datos en :::no-loc(Xamarin.Forms)::: las
aplicaciones. Para obtener más información, vea Enlaces compilados.

Para obtener información sobre cómo cambiar el CollectionView diseño, vea el :::no-loc(Xamarin.Forms)::: diseño
de CollectionView. Para obtener información sobre cómo definir la apariencia de cada elemento en CollectionView
, vea definir la apariencia del elemento. Para obtener más información sobre el enlace de datos, consulte Enlace de
datos de :::no-loc(Xamarin.Forms):::.

WARNING
CollectionView producirá una excepción si ItemsSource se actualiza fuera del subproceso de la interfaz de usuario.

Definir la apariencia del elemento


La apariencia de cada elemento en CollectionView puede definirse estableciendo la CollectionView.ItemTemplate
propiedad en DataTemplate :

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
...
</CollectionView>

El código de C# equivalente es el siguiente:


CollectionView collectionView = new CollectionView();
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

collectionView.ItemTemplate = new DataTemplate(() =>


{
Grid grid = new Grid { Padding = 10 };
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });

Image image = new Image { Aspect = Aspect.AspectFill, HeightRequest = 60, WidthRequest = 60 };


image.SetBinding(Image.SourceProperty, "ImageUrl");

Label nameLabel = new Label { FontAttributes = FontAttributes.Bold };


nameLabel.SetBinding(Label.TextProperty, "Name");

Label locationLabel = new Label { FontAttributes = FontAttributes.Italic, VerticalOptions =


LayoutOptions.End };
locationLabel.SetBinding(Label.TextProperty, "Location");

Grid.SetRowSpan(image, 2);

grid.Children.Add(image);
grid.Children.Add(nameLabel, 1, 0);
grid.Children.Add(locationLabel, 1, 1);

return grid;
});

Los elementos especificados en DataTemplate definen la apariencia de cada elemento de la lista. En el ejemplo, el
diseño dentro de DataTemplate se administra mediante un Grid . Grid Contiene un Image objeto y dos Label
objetos, que se enlazan a las propiedades de la Monkey clase:

public class Monkey


{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}

Las siguientes capturas de pantallas muestran el resultado de la plantilla de cada elemento de la lista:
Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Elección de la apariencia del elemento en tiempo de ejecución


La apariencia de cada elemento en CollectionView se puede elegir en tiempo de ejecución, según el valor del
elemento, estableciendo la CollectionView.ItemTemplate propiedad en un DataTemplateSelector objeto:

<ContentPage ...
xmlns:controls="clr-namespace:CollectionViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
...
</DataTemplate>

<DataTemplate x:Key="OtherMonkeyTemplate">
...
</DataTemplate>

<controls:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource OtherMonkeyTemplate}" />
</ContentPage.Resources>

<CollectionView ItemsSource="{Binding Monkeys}"


ItemTemplate="{StaticResource MonkeySelector}" />
</ContentPage>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
ItemTemplate = new MonkeyDataTemplateSelector { ... }
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

La ItemTemplate propiedad se establece en un MonkeyDataTemplateSelector objeto. En el ejemplo siguiente se


muestra la MonkeyDataTemplateSelector clase:

public class MonkeyDataTemplateSelector : DataTemplateSelector


{
public DataTemplate AmericanMonkey { get; set; }
public DataTemplate OtherMonkey { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
return ((Monkey)item).Location.Contains("America") ? AmericanMonkey : OtherMonkey;
}
}

La MonkeyDataTemplateSelector clase define AmericanMonkey OtherMonkey DataTemplate las propiedades y que se


establecen en distintas plantillas de datos. La OnSelectTemplate invalidación devuelve la AmericanMonkey plantilla,
que muestra el nombre y la ubicación de Monkey en verde azulado, cuando el nombre de Monkey contiene
"America". Cuando el nombre de Monkey no contiene "America", la OnSelectTemplate invalidación devuelve la
OtherMonkey plantilla, que muestra el nombre y la ubicación de Monkey en Silver:
Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-loc(Xamarin.Forms):::
DataTemplateSelector.

IMPORTANT
Al utilizar CollectionView , no establezca nunca el elemento raíz de los DataTemplate objetos en un ViewCell . Esto
hará que se produzca una excepción porque CollectionView no tiene concepto de celdas.

Menús contextuales
CollectionView admite menús contextuales para elementos de datos a través de SwipeView , que revela el menú
contextual con un gesto de deslizar rápidamente. SwipeView Es un control contenedor que se ajusta alrededor de
un elemento de contenido y proporciona elementos de menú contextual para ese elemento de contenido. Por lo
tanto, los menús contextuales se implementan para un CollectionView mediante la creación de un SwipeView que
define el contenido que SwipeView contiene y los elementos de menú contextual revelados por el gesto de deslizar
rápidamente. Esto se logra estableciendo SwipeView como la vista raíz en el DataTemplate que define la apariencia
de cada elemento de datos en CollectionView :
<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Command="{Binding Source={x:Reference collectionView},
Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding Source={x:Reference collectionView},
Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.LeftItems>
<Grid BackgroundColor="White"
Padding="10">
<!-- Define item appearance -->
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

El código de C# equivalente es el siguiente:


CollectionView collectionView = new CollectionView();
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

collectionView.ItemTemplate = new DataTemplate(() =>


{
// Define item appearance
Grid grid = new Grid { Padding = 10, BackgroundColor = Color.White };
// ...

SwipeView swipeView = new SwipeView();


SwipeItem favoriteSwipeItem = new SwipeItem
{
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Color.LightGreen
};
favoriteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.FavoriteCommand",
source: collectionView));
favoriteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

SwipeItem deleteSwipeItem = new SwipeItem


{
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Color.LightPink
};
deleteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.DeleteCommand", source:
collectionView));
deleteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

swipeView.LeftItems = new SwipeItems { favoriteSwipeItem, deleteSwipeItem };


swipeView.Content = grid;
return swipeView;
});

En este ejemplo, el SwipeView contenido es un Grid que define la apariencia de cada elemento en CollectionView
. Los elementos de deslizamiento se utilizan para realizar acciones en el SwipeView contenido y se revelan cuando
el control se desliza rápidamente desde el lado izquierdo:

SwipeView admite cuatro direcciones de deslizamiento diferentes, con la dirección de deslizamiento que se define
en la colección direcciona SwipeItems a la que SwipeItems se agregan los objetos. De forma predeterminada, se
ejecuta un dedo al puntear en el usuario. Además, una vez que se ha ejecutado un dedo, se ocultan los elementos
de deslizamiento y SwipeView se vuelve a mostrar el contenido. Sin embargo, estos comportamientos se pueden
cambiar.
Para obtener más información sobre el SwipeView control, vea :::no-loc(Xamarin.Forms)::: SwipeView.

Extraer para actualizar


CollectionView admite la funcionalidad de extracción para actualizar a través de RefreshView , que permite que
los datos que se muestran se actualicen en la lista de elementos. RefreshView Es un control contenedor que
proporciona la funcionalidad de extracción para actualizar a su elemento secundario, siempre que el elemento
secundario admita contenido desplazable. Por lo tanto, la extracción de la actualización se implementa para una
CollectionView al establecerla como el elemento secundario de un RefreshView :

<RefreshView IsRefreshing="{Binding IsRefreshing}"


Command="{Binding RefreshCommand}">
<CollectionView ItemsSource="{Binding Animals}">
...
</CollectionView>
</RefreshView>

El código de C# equivalente es el siguiente:

RefreshView refreshView = new RefreshView();


ICommand refreshCommand = new Command(() =>
{
// IsRefreshing is true
// Refresh data here
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;

CollectionView collectionView = new CollectionView();


collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
refreshView.Content = collectionView;
// ...

Cuando el usuario inicia una actualización, ICommand se ejecuta la definida por la Command propiedad, que debe
actualizar los elementos que se muestran. Se muestra una visualización de actualización mientras se produce la
actualización, que consta de un círculo de progreso animado:

El valor de la RefreshView.IsRefreshing propiedad indica el estado actual de RefreshView . Cuando el usuario


desencadena una actualización, esta propiedad pasará automáticamente a true . Una vez finalizada la
actualización, debe restablecer la propiedad a false .
Para obtener más información sobre RefreshView , vea :::no-loc(Xamarin.Forms)::: RefreshView.

Cargar datos incrementalmente


CollectionView admite la virtualización de datos incrementales a medida que el usuario se desplaza. Esto permite
escenarios como la carga asincrónica de una página de datos de un servicio Web, a medida que el usuario se
desplaza. Además, el punto en el que se cargan más datos se puede configurar para que los usuarios no vean el
espacio en blanco o se detengan del desplazamiento.
CollectionView define las siguientes propiedades para controlar la carga incremental de datos:
RemainingItemsThreshold , de tipo , el umbral de elementos que todavía no están visibles en la lista en la
int
que se RemainingItemsThresholdReached desencadenará el evento.
RemainingItemsThresholdReachedCommand , de tipo ICommand , que se ejecuta cuando RemainingItemsThreshold se
alcanza el.
RemainingItemsThresholdReachedCommandParameter , de tipo object , que es el parámetro que se pasa a
RemainingItemsThresholdReachedCommand .

CollectionView también define un RemainingItemsThresholdReached evento que se desencadena cuando


CollectionView se desplaza lo suficiente como para que RemainingItemsThreshold no se muestren los elementos.
Este evento se puede controlar para cargar más elementos. Además, cuando RemainingItemsThresholdReached se
desencadena el evento, RemainingItemsThresholdReachedCommand se ejecuta, lo que permite que la carga de datos
incrementales se realice en un ViewModel.
El valor predeterminado de la propiedad es-1, lo que indica que el
RemainingItemsThreshold
RemainingItemsThresholdReached evento nunca se desencadenará. Cuando el valor de la propiedad es 0, el
RemainingItemsThresholdReached evento se desencadena cuando se muestra el último elemento de ItemsSource .
En el caso de valores mayores que 0, el RemainingItemsThresholdReached evento se desencadena cuando
ItemsSource contiene ese número de elementos a los que todavía no se ha desplazado.

NOTE
CollectionView valida la RemainingItemsThreshold propiedad de modo que su valor sea siempre mayor o igual que-1.

En el siguiente ejemplo de XAML se muestra un CollectionView que carga datos incrementalmente:

<CollectionView ItemsSource="{Binding Animals}"


RemainingItemsThreshold="5"
RemainingItemsThresholdReached="OnCollectionViewRemainingItemsThresholdReached">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
RemainingItemsThreshold = 5
};
collectionView.RemainingItemsThresholdReached += OnCollectionViewRemainingItemsThresholdReached;
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");

En este ejemplo de código, el RemainingItemsThresholdReached evento se desencadena cuando hay 5 elementos que
todavía no se han desplazado a y, en respuesta, ejecuta el OnCollectionViewRemainingItemsThresholdReached
controlador de eventos:

void OnCollectionViewRemainingItemsThresholdReached(object sender, EventArgs e)


{
// Retrieve more data here and add it to the CollectionView's ItemsSource collection.
}

NOTE
Los datos también se pueden cargar incrementalmente enlazando el RemainingItemsThresholdReachedCommand a una
ICommand implementación de ViewModel.

Vínculos relacionados
CollectionView (ejemplo)
:::no-loc(Xamarin.Forms)::: RefreshView
:::no-loc(Xamarin.Forms)::: SwipeView
Enlace de datos de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Plantillas de datos
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
:::no-loc(Xamarin.Forms)::: Diseño de CollectionView
18/12/2020 • 21 minutes to read • Edit Online

Descargar el ejemplo
CollectionView define las siguientes propiedades que controlan el diseño:
ItemsLayout , de tipo IItemsLayout , especifica el diseño que se va a utilizar.
ItemSizingStrategy , de tipo ItemSizingStrategy , especifica la estrategia de medida de elementos que se va a
utilizar.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
De forma predeterminada, un mostrará CollectionView sus elementos en una lista vertical. Sin embargo, se
puede usar cualquiera de los siguientes diseños:
Lista vertical: una lista de una sola columna que crece verticalmente a medida que se agregan nuevos
elementos.
Lista horizontal: una lista de una sola fila que crece horizontalmente a medida que se agregan nuevos
elementos.
Cuadrícula vertical: una cuadrícula de varias columnas que crece verticalmente a medida que se agregan
nuevos elementos.
Cuadrícula horizontal: una cuadrícula de varias filas que crece horizontalmente a medida que se agregan
nuevos elementos.
Estos diseños se pueden especificar estableciendo la ItemsLayout propiedad en la clase que deriva de la
ItemsLayout clase. Esta clase define las propiedades siguientes:

Orientation , de tipo ItemsLayoutOrientation , especifica la dirección en la que CollectionView se expande a


medida que se agregan elementos.
SnapPointsAlignment , de tipo SnapPointsAlignment , especifica cómo se alinean los puntos de ajuste con los
elementos.
SnapPointsType , de tipo SnapPointsType , especifica el comportamiento de los puntos de ajuste al desplazarse.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos. Para obtener más información acerca de los puntos de acoplamiento, vea puntos
de acoplamiento en la guía de :::no-loc(Xamarin.Forms)::: desplazamiento de CollectionView .
La ItemsLayoutOrientation enumeración define los siguientes miembros:
Vertical indica que CollectionView se expandirá verticalmente a medida que se agreguen elementos.
Horizontal indica que CollectionView se expandirá horizontalmente a medida que se agreguen elementos.

La LinearItemsLayout clase hereda de la ItemsLayout clase y define una ItemSpacing propiedad, de tipo double ,
que representa el espacio vacío alrededor de cada elemento. El valor predeterminado de esta propiedad es 0, y su
valor siempre debe ser mayor o igual que 0. La LinearItemsLayout clase también define Vertical los miembros y
estáticos Horizontal . Estos miembros se pueden usar para crear listas verticales u horizontales, respectivamente.
Como alternativa, LinearItemsLayout se puede crear un objeto, especificando un ItemsLayoutOrientation
miembro de enumeración como argumento.
La GridItemsLayout clase hereda de la ItemsLayout clase y define las siguientes propiedades:
VerticalItemSpacing , de tipo double , que representa el espacio vacío vertical alrededor de cada elemento. El
valor predeterminado de esta propiedad es 0, y su valor siempre debe ser mayor o igual que 0.
HorizontalItemSpacing , de tipo double , que representa el espacio vacío horizontal alrededor de cada
elemento. El valor predeterminado de esta propiedad es 0, y su valor siempre debe ser mayor o igual que 0.
Span , de tipo int , que representa el número de columnas o filas que se van a mostrar en la cuadrícula. El
valor predeterminado de esta propiedad es 1 y su valor siempre debe ser mayor o igual que 1.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.

NOTE
CollectionView usa los motores de diseño nativo para realizar el diseño.

Lista vertical
De forma predeterminada, mostrará CollectionView sus elementos en un diseño de lista vertical. Por lo tanto, no
es necesario establecer la ItemsLayout propiedad para utilizar este diseño:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

Sin embargo, por integridad, en XAML CollectionView se puede establecer para mostrar sus elementos en una
lista vertical estableciendo su ItemsLayout propiedad en VerticalList :

<CollectionView ItemsSource="{Binding Monkeys}"


ItemsLayout="VerticalList">
...
</CollectionView>
Como alternativa, también se puede lograr estableciendo la ItemsLayout propiedad en un LinearItemsLayout
objeto, especificando el miembro de la Vertical ItemsLayoutOrientation enumeración como el valor de la
Orientation propiedad:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemsLayout = LinearItemsLayout.Vertical
};

Esto da como resultado una lista de una sola columna, que crece verticalmente a medida que se agregan nuevos
elementos:

Lista horizontal
En XAML, un CollectionView puede mostrar sus elementos en una lista horizontal estableciendo su ItemsLayout
propiedad en HorizontalList :
<CollectionView ItemsSource="{Binding Monkeys}"
ItemsLayout="HorizontalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="140" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

Como alternativa, este diseño también puede realizarse estableciendo la ItemsLayout propiedad en un
LinearItemsLayout objeto, especificando el miembro de la Horizontal ItemsLayoutOrientation enumeración
como el valor de la Orientation propiedad:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemsLayout = LinearItemsLayout.Horizontal
};

Esto da como resultado una lista de una sola fila, que crece horizontalmente a medida que se agregan nuevos
elementos:
Cuadrícula vertical
En XAML, un CollectionViewpuede mostrar sus elementos en una cuadrícula vertical estableciendo su
ItemsLayout propiedad en VerticalGrid :

<CollectionView ItemsSource="{Binding Monkeys}"


ItemsLayout="VerticalGrid, 2">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

También puede realizar este diseño si establece la ItemsLayout propiedad en un GridItemsLayout objeto cuya
Orientation propiedad se establece en Vertical :

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:


CollectionView collectionView = new CollectionView
{
...
ItemsLayout = new GridItemsLayout(2, ItemsLayoutOrientation.Vertical)
};

De forma predeterminada, un GridItemsLayout elemento vertical mostrará los elementos en una sola columna.
Sin embargo, en este ejemplo GridItemsLayout.Span se establece la propiedad en 2. Esto da como resultado una
cuadrícula de dos columnas, que crece verticalmente a medida que se agregan nuevos elementos:

Cuadrícula horizontal
En XAML, un CollectionViewpuede mostrar sus elementos en una cuadrícula horizontal estableciendo su
ItemsLayout propiedad en HorizontalGrid :
<CollectionView ItemsSource="{Binding Monkeys}"
ItemsLayout="HorizontalGrid, 4">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="140" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

También puede realizar este diseño si establece la ItemsLayout propiedad en un GridItemsLayout objeto cuya
Orientation propiedad se establece en Horizontal :

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal"
Span="4" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemsLayout = new GridItemsLayout(4, ItemsLayoutOrientation.Horizontal)
};

De forma predeterminada, un horizontal mostrará GridItemsLayout los elementos en una sola fila. Sin embargo,
en este ejemplo GridItemsLayout.Span se establece la propiedad en 4. Esto da como resultado una cuadrícula de
cuatro filas, que crece horizontalmente a medida que se agregan nuevos elementos:
Encabezados y pies de página
CollectionView puede presentar un encabezado y un pie de página que se desplacen con los elementos de la lista.
El encabezado y el pie de página pueden ser cadenas, vistas u DataTemplate objetos.
CollectionView define las siguientes propiedades para especificar el encabezado y el pie de página:
Header , de tipo , especifica la cadena, el enlace o la vista que se mostrarán al principio de la lista.
object
HeaderTemplate , de tipo DataTemplate , especifica el DataTemplate que se va a utilizar para dar formato a
Header .
Footer , de tipo object , especifica la cadena, el enlace o la vista que se mostrarán al final de la lista.
FooterTemplate , de tipo DataTemplate , especifica el DataTemplate que se va a utilizar para dar formato a
Footer .

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
Cuando se agrega un encabezado a un diseño que crece horizontalmente, de izquierda a derecha, el encabezado
se muestra a la izquierda de la lista. Del mismo modo, cuando un pie de página se agrega a un diseño que crece
horizontalmente, de izquierda a derecha, el pie de página se muestra a la derecha de la lista.
Mostrar cadenas en el encabezado y el pie de página
Las Header Footer propiedades y se pueden establecer en string valores, tal y como se muestra en el ejemplo
siguiente:

<CollectionView ItemsSource="{Binding Monkeys}"


Header="Monkeys"
Footer="2019">
...
</CollectionView>

El código de C# equivalente es el siguiente:


CollectionView collectionView = new CollectionView
{
Header = "Monkeys",
Footer = "2019"
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

Este código genera las siguientes capturas de pantalla, con el encabezado que se muestra en la captura de pantalla
de iOS y el pie de página que se muestra en la captura de pantalla de Android:

Mostrar vistas en el encabezado y el pie de página


Las Header Footer propiedades y se pueden establecer cada una de ellas en una vista. Puede ser una vista única
o una vista que contiene varias vistas secundarias. En el ejemplo siguiente se muestran las Header Footer
propiedades y establecidas en un StackLayout objeto que contiene un Label objeto:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.Header>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Header>
<CollectionView.Footer>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Xamarin Monkey"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Footer>
...
</CollectionView>

El código de C# equivalente es el siguiente:


CollectionView collectionView = new CollectionView
{
Header = new StackLayout
{
Children =
{
new Label { Text = "Monkeys", ... }
}
},
Footer = new StackLayout
{
Children =
{
new Label { Text = "Friends of Xamarin Monkey", ... }
}
}
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

Este código genera las siguientes capturas de pantalla, con el encabezado que se muestra en la captura de pantalla
de iOS y el pie de página que se muestra en la captura de pantalla de Android:

Mostrar un encabezado y un pie de página con plantilla


Las HeaderTemplate FooterTemplate propiedades y se pueden establecer en DataTemplate objetos que se utilizan
para dar formato al encabezado y al pie de página. En este escenario, las Header Footer propiedades y deben
enlazarse al origen actual de las plantillas que se van a aplicar, tal como se muestra en el ejemplo siguiente:
<CollectionView ItemsSource="{Binding Monkeys}"
Header="{Binding .}"
Footer="{Binding .}">
<CollectionView.HeaderTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</CollectionView.HeaderTemplate>
<CollectionView.FooterTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Xamarin Monkey"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</CollectionView.FooterTemplate>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
HeaderTemplate = new DataTemplate(() =>
{
return new StackLayout { };
}),
FooterTemplate = new DataTemplate(() =>
{
return new StackLayout { };
})
};
collectionView.SetBinding(ItemsView.HeaderProperty, ".");
collectionView.SetBinding(ItemsView.FooterProperty, ".");
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

Este código genera las siguientes capturas de pantalla, con el encabezado que se muestra en la captura de pantalla
de iOS y el pie de página que se muestra en la captura de pantalla de Android:
Espaciado de elementos
De forma predeterminada, no hay ningún espacio entre cada elemento de CollectionView . Este comportamiento
se puede cambiar estableciendo las propiedades en el diseño de los elementos que utiliza CollectionView .
Cuando un CollectionView establece su ItemsLayout propiedad en un LinearItemsLayout objeto, la
LinearItemsLayout.ItemSpacing propiedad se puede establecer en un double valor que representa el espacio entre
los elementos:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20" />
</CollectionView.ItemsLayout>
...
</CollectionView>

NOTE
La LinearItemsLayout.ItemSpacing propiedad tiene un conjunto de devoluciones de llamada de validación, que garantiza
que el valor de la propiedad sea siempre mayor o igual que 0.

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
ItemSpacing = 20
}
};

Este código genera una lista de una sola columna vertical, que tiene un espaciado de 20 elementos:
Cuando CollectionView establece su propiedad en un GridItemsLayout objeto, las
ItemsLayout
GridItemsLayout.VerticalItemSpacing propiedades y GridItemsLayout.HorizontalItemSpacing se pueden establecer
en double valores que representan el espacio vacío vertical y horizontalmente entre elementos:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="20"
HorizontalItemSpacing="30" />
</CollectionView.ItemsLayout>
...
</CollectionView>

NOTE
Las GridItemsLayout.VerticalItemSpacing GridItemsLayout.HorizontalItemSpacing propiedades y tienen conjuntos
de devoluciones de llamada de validación, que garantizan que los valores de las propiedades sean siempre mayores o iguales
que 0.

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemsLayout = new GridItemsLayout(2, ItemsLayoutOrientation.Vertical)
{
VerticalItemSpacing = 20,
HorizontalItemSpacing = 30
}
};

Este código da como resultado una cuadrícula de dos columnas vertical, que tiene un espaciado vertical de 20
entre elementos y un espaciado horizontal de 30 elementos:

Tamaño del elemento


De forma predeterminada, cada elemento de un objeto CollectionView se mide y ajusta de forma individual,
siempre que los elementos de la interfaz de usuario de DataTemplate no especifiquen tamaños fijos. Este
comportamiento, que se puede cambiar, se especifica mediante el CollectionView.ItemSizingStrategy valor de
propiedad. Este valor de propiedad se puede establecer en uno de los ItemSizingStrategy miembros de
enumeración:
MeasureAllItems: cada elemento se mide individualmente. Este es el valor predeterminado.
MeasureFirstItem : solo se mide el primer elemento, donde todos los elementos subsiguientes reciben el
mismo tamaño que el primer elemento.

IMPORTANT
La MeasureFirstItem estrategia de ajuste de tamaño dará lugar a un aumento del rendimiento cuando se usa en
situaciones en las que el tamaño del elemento está diseñado para ser uniforme en todos los elementos.

En el ejemplo de código siguiente se muestra cómo establecer la ItemSizingStrategy propiedad:

<CollectionView ...
ItemSizingStrategy="MeasureFirstItem">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem
};

Cambio de tamaño dinámico de los elementos


Los elementos de un CollectionView se pueden cambiar de tamaño dinámicamente en tiempo de ejecución
cambiando las propiedades relacionadas con el diseño de los elementos dentro de DataTemplate . Por ejemplo, en
el ejemplo de código siguiente se cambian las HeightRequest WidthRequest propiedades y de un Image objeto:

void OnImageTapped(object sender, EventArgs e)


{
Image image = sender as Image;
image.HeightRequest = image.WidthRequest = image.HeightRequest.Equals(60) ? 100 : 60;
}

El OnImageTapped controlador de eventos se ejecuta en respuesta a un Image objeto que se puntea y cambia las
dimensiones de la imagen para que se vea más fácilmente:
Diseño de derecha a izquierda
CollectionView puede diseñar su contenido en una dirección de flujo de derecha a izquierda estableciendo su
FlowDirection propiedad en RightToLeft . Sin embargo, la FlowDirection propiedad debe establecerse
idealmente en un diseño de página o raíz, lo que hace que todos los elementos de la página o el diseño raíz
respondan a la dirección del flujo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CollectionViewDemos.Views.VerticalListFlowDirectionPage"
Title="Vertical list (RTL FlowDirection)"
FlowDirection="RightToLeft">
<StackLayout Margin="20">
<CollectionView ItemsSource="{Binding Monkeys}">
...
</CollectionView>
</StackLayout>
</ContentPage>

El valor predeterminado FlowDirection de un elemento con un elemento primario es MatchParent . Por


consiguiente, CollectionView hereda el FlowDirection valor de propiedad de StackLayout , que a su vez hereda
el FlowDirection valor de propiedad de ContentPage . Esto da como resultado el diseño de derecha a izquierda
que se muestra en las siguientes capturas de pantallas:

Para obtener más información acerca de la dirección de flujo, consulte localización de derecha a izquierda.

Vínculos relacionados
CollectionView (ejemplo)
Localización de derecha a izquierda
:::no-loc(Xamarin.Forms)::: Desplazamientos de CollectionView
:::no-loc(Xamarin.Forms)::: Selección de
CollectionView
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
CollectionView define las siguientes propiedades que controlan la selección de elementos:
SelectionMode , de tipo SelectionMode , el modo de selección.
SelectedItem , de tipo object , el elemento seleccionado en la lista. Esta propiedad tiene un modo de enlace
predeterminado de TwoWay , y tiene un null valor cuando no se selecciona ningún elemento.
SelectedItems , de tipo IList<object> , los elementos seleccionados en la lista. Esta propiedad tiene un modo
de enlace predeterminado de OneWay , y tiene un null valor cuando no se selecciona ningún elemento.
SelectionChangedCommand , de tipo ICommand , que se ejecuta cuando cambia el elemento seleccionado.
SelectionChangedCommandParameter , de tipo object , que es el parámetro que se pasa a
SelectionChangedCommand .

Todas estas propiedades están respaldados por objetos BindableProperty , lo que significa que las propiedades
pueden ser destinos de los enlaces de datos.
De forma predeterminada, la CollectionView selección está deshabilitada. Sin embargo, este comportamiento se
puede cambiar estableciendo el SelectionMode valor de la propiedad en uno de los SelectionMode miembros de
enumeración:
None : indica que no se pueden seleccionar elementos. Este es el valor predeterminado.
Single : indica que se puede seleccionar un solo elemento, con el elemento seleccionado resaltado.
Multiple : indica que se pueden seleccionar varios elementos, con los elementos seleccionados resaltados.

CollectionView define un SelectionChanged evento que se desencadena cuando SelectedItem cambia la


propiedad, ya sea porque el usuario selecciona un elemento de la lista o cuando una aplicación establece la
propiedad. Además, este evento también se desencadena cuando cambia la SelectedItems propiedad. El
SelectionChangedEventArgs objeto que acompaña al SelectionChanged evento tiene dos propiedades, ambas de
tipo IReadOnlyList<object> :
PreviousSelection : la lista de elementos que se seleccionaron antes de que cambiara la selección.
CurrentSelection : la lista de elementos que se seleccionan después de cambiar la selección.
Además, CollectionView tiene un UpdateSelectedItems método que actualiza la SelectedItems propiedad con una
lista de elementos seleccionados, y solo activa una notificación de cambio única.

Selección única
Cuando la SelectionMode propiedad está establecida en Single , se puede seleccionar un solo elemento de
CollectionView . Cuando se selecciona un elemento, la SelectedItem propiedad se establecerá en el valor del
elemento seleccionado. Cuando esta propiedad cambia, SelectionChangedCommand se ejecuta (con el valor de
SelectionChangedCommandParameter que se pasa a ICommand ) y se SelectionChanged desencadena el evento.

En el siguiente ejemplo de XAML CollectionView se muestra un que puede responder a una selección de un solo
elemento:
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
SelectionMode = SelectionMode.Single
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SelectionChanged += OnCollectionViewSelectionChanged;

En este ejemplo de código, el OnCollectionViewSelectionChanged controlador de eventos se ejecuta cuando se


SelectionChanged desencadena el evento, con el controlador de eventos que recupera el elemento seleccionado
anteriormente y el elemento seleccionado actual:

void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)


{
string previous = (e.PreviousSelection.FirstOrDefault() as Monkey)?.Name;
string current = (e.CurrentSelection.FirstOrDefault() as Monkey)?.Name;
...
}

IMPORTANT
Los SelectionChanged cambios que se producen como resultado de la modificación de la propiedad pueden desencadenar
el evento SelectionMode .

En las siguientes capturas de pantallas se muestra la selección de un solo elemento en un CollectionView :

Selección múltiple
Cuando la SelectionMode propiedad está establecida en Multiple , CollectionView se pueden seleccionar varios
elementos en. Cuando se seleccionan los elementos, la SelectedItems propiedad se establecerá en los elementos
seleccionados. Cuando esta propiedad cambia, SelectionChangedCommand se ejecuta (con el valor de
SelectionChangedCommandParameter que se pasa a ICommand ) y se SelectionChanged desencadena el evento.

En el siguiente ejemplo de XAML CollectionView se muestra un que puede responder a la selección de varios
elementos:
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Multiple"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SelectionChanged += OnCollectionViewSelectionChanged;

En este ejemplo de código, el OnCollectionViewSelectionChanged controlador de eventos se ejecuta cuando se


SelectionChanged desencadena el evento, con el controlador de eventos que recupera los elementos
seleccionados anteriormente y los elementos seleccionados actualmente:

void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)


{
var previous = e.PreviousSelection;
var current = e.CurrentSelection;
...
}

IMPORTANT
Los SelectionChanged cambios que se producen como resultado de la modificación de la propiedad pueden desencadenar
el evento SelectionMode .

En las siguientes capturas de pantallas se muestra la selección de varios elementos en un CollectionView :

Selección previa única


Cuando la SelectionMode propiedad está establecida en Single , se puede preseleccionar un único elemento de la
CollectionView propiedad mediante el establecimiento de la SelectedItem propiedad en el elemento. En el
siguiente ejemplo de XAML se muestra un CollectionView que preselecciona un solo elemento:

<CollectionView ItemsSource="{Binding Monkeys}"


SelectionMode="Single"
SelectedItem="{Binding SelectedMonkey}">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
SelectionMode = SelectionMode.Single
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SetBinding(SelectableItemsView.SelectedItemProperty, "SelectedMonkey");

NOTE
La SelectedItem propiedad tiene un modo de enlace predeterminado de TwoWay .

Los SelectedItem datos de la propiedad se enlazan a la SelectedMonkey propiedad del modelo de vista conectado,
que es de tipo Monkey . De forma predeterminada, TwoWay se utiliza un enlace para que si el usuario cambia el
elemento seleccionado, el valor de la SelectedMonkey propiedad se establecerá en el Monkey objeto seleccionado.
La SelectedMonkey propiedad se define en la MonkeysViewModel clase y se establece en el cuarto elemento de la
Monkeys colección:

public class MonkeysViewModel : INotifyPropertyChanged


{
...
public ObservableCollection<Monkey> Monkeys { get; private set; }

Monkey selectedMonkey;
public Monkey SelectedMonkey
{
get
{
return selectedMonkey;
}
set
{
if (selectedMonkey != value)
{
selectedMonkey = value;
}
}
}

public MonkeysViewModel()
{
...
selectedMonkey = Monkeys.Skip(3).FirstOrDefault();
}
...
}

Por lo tanto, cuando CollectionView aparece, el cuarto elemento de la lista se selecciona previamente:
Selección previa múltiple
Cuando la SelectionMode propiedad está establecida en Multiple , CollectionView se pueden preseleccionar
varios elementos en. En el ejemplo de XAML siguiente CollectionView se muestra un que habilitará la selección
previa de varios elementos:

<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedMonkeys}">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SetBinding(SelectableItemsView.SelectedItemsProperty, "SelectedMonkeys");

NOTE
La SelectedItems propiedad tiene un modo de enlace predeterminado de OneWay .

Los SelectedItems datos de la propiedad se enlazan a la SelectedMonkeys propiedad del modelo de vista
conectado, que es de tipo ObservableCollection<object> . La SelectedMonkeys propiedad se define en la
MonkeysViewModel clase y se establece en el segundo, cuarto y quinto elemento de la Monkeys colección:
namespace CollectionViewDemos.ViewModels
{
public class MonkeysViewModel : INotifyPropertyChanged
{
...
ObservableCollection<object> selectedMonkeys;
public ObservableCollection<object> SelectedMonkeys
{
get
{
return selectedMonkeys;
}
set
{
if (selectedMonkeys != value)
{
selectedMonkeys = value;
}
}
}

public MonkeysViewModel()
{
...
SelectedMonkeys = new ObservableCollection<object>()
{
Monkeys[1], Monkeys[3], Monkeys[4]
};
}
...
}
}

Por lo tanto, cuando CollectionView aparece, los elementos segundo, cuarto y quinto de la lista se seleccionan
previamente:

Borrar selecciones
Las SelectedItem SelectedItems propiedades y se pueden borrar estableciéndolo, o los objetos a los que se
enlazan, null en.

Cambiar el color de los elementos seleccionados


CollectionView tiene un Selected que se puede utilizar para iniciar un cambio visual en el elemento
VisualState
seleccionado en CollectionView . Un caso de uso común para esto VisualState es cambiar el color de fondo del
elemento seleccionado, que se muestra en el siguiente ejemplo de XAML:
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="LightSkyBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout Margin="20">
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
...
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>

IMPORTANT
El Style que contiene Selected VisualState debe tener un TargetType valor de propiedad que sea el tipo del
elemento raíz de DataTemplate , que se establece como el valor de la ItemTemplate propiedad.

En este ejemplo, el Style.TargetType valor de la propiedad se establece en Grid porque el elemento raíz de
ItemTemplate es un Grid . Selected VisualState Especifica que cuando se selecciona un elemento de la
CollectionView , el BackgroundColor del elemento se establecerá en LightSkyBlue :

Para obtener más información sobre los estados visuales, vea Administrador de estado visual de :::no-
loc(Xamarin.Forms):::.

Deshabilitar selección
CollectionView la selección está deshabilitada de forma predeterminada. Sin embargo, si CollectionView tiene
habilitada la selección, se puede deshabilitar estableciendo la SelectionMode propiedad en None :
<CollectionView ...
SelectionMode="None" />

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
...
SelectionMode = SelectionMode.None
};

Cuando la SelectionMode propiedad está establecida en None , los elementos de CollectionView no se pueden
seleccionar, la SelectedItem propiedad permanecerá null y el SelectionChanged evento no se desencadenará.

NOTE
Cuando se ha seleccionado un elemento y SelectionMode se ha cambiado la propiedad de Single a None , la
SelectedItem propiedad se establecerá en null y el SelectionChanged evento se desencadenará con una
CurrentSelection propiedad vacía.

Vínculos relacionados
CollectionView (ejemplo)
Administrador de estado visual de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: CollectionView EmptyView
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
CollectionView define las siguientes propiedades que se pueden usar para proporcionar comentarios del usuario
cuando no hay datos para mostrar:
EmptyView , de tipo object , la cadena, el enlace o la vista que se mostrarán cuando la ItemsSource propiedad
sea null , o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. El valor
predeterminado es null .
EmptyViewTemplate , de tipo DataTemplate , la plantilla que se va a utilizar para dar formato al especificado
EmptyView . El valor predeterminado es null .

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
Los principales escenarios de uso para establecer la propiedad muestran los EmptyView comentarios de los
usuarios cuando una operación de filtrado en CollectionView no produce datos y muestra los comentarios de los
usuarios mientras se recuperan los datos de un servicio Web.

NOTE
La EmptyView propiedad se puede establecer en una vista que incluya contenido interactivo si es necesario.

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Mostrar una cadena cuando los datos no estén disponibles


La EmptyView propiedad se puede establecer en una cadena, que se mostrará cuando la ItemsSource propiedad
sea null o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. En el código
XAML siguiente se muestra un ejemplo de este escenario:

<CollectionView ItemsSource="{Binding EmptyMonkeys}"


EmptyView="No items to display" />

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
EmptyView = "No items to display"
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "EmptyMonkeys");

El resultado es que, dado que la colección enlazada a datos es null , se muestra la cadena establecida como el
valor de la EmptyView propiedad:
Mostrar vistas cuando los datos no están disponibles
La EmptyViewpropiedad se puede establecer en una vista, que se mostrará cuando la ItemsSource propiedad sea
null o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. Puede ser una vista
única o una vista que contiene varias vistas secundarias. En el ejemplo de XAML siguiente EmptyView se muestra la
propiedad establecida en una vista que contiene varias vistas secundarias:

<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference searchBar}, Path=Text}"
Placeholder="Filter" />
<CollectionView ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.EmptyView>
<ContentView>
<StackLayout HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<Label Text="No results matched your filter."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
<Label Text="Try a broader filter?"
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
</StackLayout>

En este ejemplo, lo que parece que se ha agregado un redundante es ContentView el elemento raíz de EmptyView .
Esto se debe a que internamente, EmptyView se agrega a un contenedor nativo que no proporciona ningún
contexto para el :::no-loc(Xamarin.Forms)::: diseño. Por lo tanto, para colocar las vistas que componen su EmptyView
, debe agregar un diseño raíz, cuyo elemento secundario es un diseño que puede colocarse en el diseño raíz.
El código de C# equivalente es el siguiente:
SearchBar searchBar = new SearchBar { ... };
CollectionView collectionView = new CollectionView
{
EmptyView = new ContentView
{
Content = new StackLayout
{
Children =
{
new Label { Text = "No results matched your filter.", ... },
new Label { Text = "Try a broader filter?", ... }
}
}
}
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

Cuando SearchBar ejecuta FilterCommand , la colección que muestra CollectionView se filtra para el término de
búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos, StackLayout
se muestra el conjunto como el valor de la EmptyView propiedad:

Mostrar un tipo personalizado con plantilla cuando los datos no estén


disponibles
La EmptyView propiedad se puede establecer en un tipo personalizado, cuya plantilla se muestra cuando la
ItemsSource propiedad es null , o cuando la colección especificada por la ItemsSource propiedad es null o está
vacía. La EmptyViewTemplate propiedad se puede establecer en un DataTemplate que define la apariencia de
EmptyView . En el código XAML siguiente se muestra un ejemplo de este escenario:
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference searchBar}, Path=Text}"
Placeholder="Filter" />
<CollectionView ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.EmptyView>
<views:FilterData Filter="{Binding Source={x:Reference searchBar}, Path=Text}" />
</CollectionView.EmptyView>
<CollectionView.EmptyViewTemplate>
<DataTemplate>
<Label Text="{Binding Filter, StringFormat='Your filter term of {0} did not match any
records.'}"
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</DataTemplate>
</CollectionView.EmptyViewTemplate>
</CollectionView>
</StackLayout>

El código de C# equivalente es el siguiente:

SearchBar searchBar = new SearchBar { ... };


CollectionView collectionView = new CollectionView
{
EmptyView = new FilterData { Filter = searchBar.Text },
EmptyViewTemplate = new DataTemplate(() =>
{
return new Label { ... };
})
};

El FilterData tipo define una Filter propiedad y un correspondiente BindableProperty :

public class FilterData : BindableObject


{
public static readonly BindableProperty FilterProperty = BindableProperty.Create(nameof(Filter),
typeof(string), typeof(FilterData), null);

public string Filter


{
get { return (string)GetValue(FilterProperty); }
set { SetValue(FilterProperty, value); }
}
}

La EmptyView propiedad se establece en un FilterData objeto y los datos de la Filter propiedad se enlazan a la
SearchBar.Text propiedad. Cuando SearchBar ejecuta FilterCommand , la colección que muestra CollectionView
se filtra para el término de búsqueda almacenado en la Filter propiedad. Si la operación de filtrado no produce
datos, Label se muestra el definido en DataTemplate , que se establece como el valor de la EmptyViewTemplate
propiedad:
NOTE
Cuando se muestra un tipo personalizado con plantilla cuando los datos no están disponibles, la EmptyViewTemplate
propiedad se puede establecer en una vista que contiene varias vistas secundarias.

Elegir un EmptyView en tiempo de ejecución


Las vistas que se mostrarán como EmptyView cuando los datos no estén disponibles, se pueden definir como
ContentView objetos en un ResourceDictionary . EmptyView A continuación, la propiedad se puede establecer en un
específico ContentView , en función de alguna lógica de negocios, en tiempo de ejecución. En el código XAML
siguiente se muestra un ejemplo de este escenario:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CollectionViewDemos.Views.EmptyViewSwapPage"
Title="EmptyView (swap)">
<ContentPage.Resources>
<ContentView x:Key="BasicEmptyView">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
<Label Text="No results matched your filter."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
<Label Text="Try a broader filter?"
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>

<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference searchBar}, Path=Text}"
Placeholder="Filter" />
<StackLayout Orientation="Horizontal">
<Label Text="Toggle EmptyViews" />
<Switch Toggled="OnEmptyViewSwitchToggled" />
</StackLayout>
<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>

Este código XAML define dos ContentView objetos en el nivel de página ResourceDictionary , con el Switch objeto
que controla qué ContentView objeto se establecerá como el valor de la EmptyView propiedad. Cuando Switch se
alterna, el controlador de OnEmptyViewSwitchToggled eventos ejecuta el ToggleEmptyView método:

void ToggleEmptyView(bool isToggled)


{
collectionView.EmptyView = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
}

El ToggleEmptyView método establece la EmptyView propiedad del collectionView objeto en uno de los dos
ContentView objetos almacenados en ResourceDictionary , basándose en el valor de la Switch.IsToggled
propiedad. Cuando SearchBar ejecuta FilterCommand , la colección que muestra CollectionView se filtra para el
término de búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos,
ContentView se muestra el objeto establecido como la EmptyView propiedad:

Para obtener más información acerca de los diccionarios de recursos, consulte :::no-loc(Xamarin.Forms):::
diccionarios de recursos.

Elegir un EmptyViewTemplate en tiempo de ejecución


La apariencia de se puede elegir en tiempo de ejecución, en función de su valor, estableciendo la
EmptyView
CollectionView.EmptyViewTemplate propiedad en un DataTemplateSelector objeto:

<ContentPage ...
xmlns:controls="clr-namespace:CollectionViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AdvancedTemplate">
...
</DataTemplate>

<DataTemplate x:Key="BasicTemplate">
...
</DataTemplate>

<controls:SearchTermDataTemplateSelector x:Key="SearchSelector"
DefaultTemplate="{StaticResource AdvancedTemplate}"
OtherTemplate="{StaticResource BasicTemplate}" />
</ContentPage.Resources>

<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference searchBar}, Path=Text}"
Placeholder="Filter" />
<CollectionView ItemsSource="{Binding Monkeys}"
EmptyView="{Binding Source={x:Reference searchBar}, Path=Text}"
EmptyViewTemplate="{StaticResource SearchSelector}" />
</StackLayout>
</ContentPage>

El código de C# equivalente es el siguiente:

SearchBar searchBar = new SearchBar { ... };


CollectionView collectionView = new CollectionView
{
EmptyView = searchBar.Text,
EmptyViewTemplate = new SearchTermDataTemplateSelector { ... }
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

La EmptyView propiedad se establece en la SearchBar.Text propiedad y la EmptyViewTemplate propiedad se


establece en un SearchTermDataTemplateSelector objeto.
Cuando SearchBar ejecuta FilterCommand , la colección que muestra CollectionView se filtra para el término de
búsqueda almacenado en la SearchBar.Text propiedad. Si la operación de filtrado no produce datos, el
DataTemplate elegido por el SearchTermDataTemplateSelector objeto se establece como la EmptyViewTemplate
propiedad y se muestra.
En el ejemplo siguiente se muestra la SearchTermDataTemplateSelector clase:

public class SearchTermDataTemplateSelector : DataTemplateSelector


{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate OtherTemplate { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
string query = (string)item;
return query.ToLower().Equals("xamarin") ? OtherTemplate : DefaultTemplate;
}
}

La SearchTermTemplateSelector clase define DefaultTemplate OtherTemplate DataTemplate las propiedades y que


se establecen en distintas plantillas de datos. La OnSelectTemplate invalidación devuelve DefaultTemplate , que
muestra un mensaje al usuario, cuando la consulta de búsqueda no es igual a "Xamarin". Cuando la consulta de
búsqueda es igual a "Xamarin", la OnSelectTemplate invalidación devuelve OtherTemplate , que muestra un
mensaje básico al usuario:

Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-loc(Xamarin.Forms):::
DataTemplateSelector.

Vínculos relacionados
CollectionView (ejemplo)
:::no-loc(Xamarin.Forms)::: Plantillas de datos
Diccionarios de recursos de :::no-loc(Xamarin.Forms):::
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
:::no-loc(Xamarin.Forms)::: Desplazamientos de
CollectionView
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
CollectionView define dos ScrollTo métodos que desplazan los elementos en la vista. Una de las sobrecargas
desplaza el elemento en el índice especificado en la vista, mientras que el otro desplaza el elemento especificado a
la vista. Ambas sobrecargas tienen argumentos adicionales que se pueden especificar para indicar el grupo al que
pertenece el elemento, la posición exacta del elemento una vez que se ha completado el desplazamiento y si se
debe animar el desplazamiento.
CollectionView define un ScrollToRequested evento que se desencadena cuando se invoca a uno de los
ScrollTo métodos. El ScrollToRequestedEventArgs objeto que acompaña al ScrollToRequested evento tiene
muchas propiedades, entre las que se incluyen IsAnimated , Index , Item y ScrollToPosition . Estas
propiedades se establecen a partir de los argumentos especificados en las ScrollTo llamadas de método.
Además, CollectionView define un Scrolled evento que se desencadena para indicar que se ha producido el
desplazamiento. El ItemsViewScrolledEventArgs objeto que acompaña al Scrolled evento tiene muchas
propiedades. Para obtener más información, vea detectar desplazamiento.
CollectionView también define una ItemsUpdatingScrollMode propiedad que representa el comportamiento de
desplazamiento de CollectionView cuando se les agregan nuevos elementos. Para obtener más información
sobre esta propiedad, vea control de posición de desplazamiento cuando se agregan nuevos elementos.
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Esta característica se conoce como ajuste, ya
que los elementos se ajustan a la posición cuando se detiene el desplazamiento. Para obtener más información,
vea puntos de acoplamiento.
CollectionView también puede cargar datos de forma incremental a medida que el usuario se desplaza. Para
obtener más información, vea cargar datos de forma incremental.

Detectar desplazamiento
CollectionView define un Scrolled evento que se desencadena para indicar que se ha producido el
desplazamiento. En el ejemplo de XAML siguiente se muestra un CollectionView que establece un controlador de
eventos para el Scrolled evento:

<CollectionView Scrolled="OnCollectionViewScrolled">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView();


collectionView.Scrolled += OnCollectionViewScrolled;

En este ejemplo de código, el OnCollectionViewScrolled controlador de eventos se ejecuta cuando se Scrolled


desencadena el evento:
void OnCollectionViewScrolled(object sender, ItemsViewScrolledEventArgs e)
{
Debug.WriteLine("HorizontalDelta: " + e.HorizontalDelta);
Debug.WriteLine("VerticalDelta: " + e.VerticalDelta);
Debug.WriteLine("HorizontalOffset: " + e.HorizontalOffset);
Debug.WriteLine("VerticalOffset: " + e.VerticalOffset);
Debug.WriteLine("FirstVisibleItemIndex: " + e.FirstVisibleItemIndex);
Debug.WriteLine("CenterItemIndex: " + e.CenterItemIndex);
Debug.WriteLine("LastVisibleItemIndex: " + e.LastVisibleItemIndex);
}

En este ejemplo, el controlador de eventos genera los valores del


OnCollectionViewScrolled
ItemsViewScrolledEventArgs objeto que acompaña al evento.

IMPORTANT
El Scrolled evento se desencadena para los desplazamientos iniciados por el usuario y para los desplazamientos
mediante programación.

Desplazar un elemento en un índice en la vista


La primera ScrollTo sobrecarga del método desplaza el elemento en el índice especificado en la vista. Dado un
CollectionView objeto denominado collectionView , en el ejemplo siguiente se muestra cómo desplazar el
elemento en el índice 12 a la vista:

collectionView.ScrollTo(12);

Como alternativa, un elemento de los datos agrupados se puede desplazar en la vista especificando los índices de
elemento y de grupo. En el ejemplo siguiente se muestra cómo desplazar el tercer elemento del segundo grupo a
la vista:

// Items and groups are indexed from zero.


collectionView.ScrollTo(2, 1);

NOTE
El ScrollToRequested evento se desencadena cuando ScrollTo se invoca el método.

Desplazar un elemento a la vista


La segunda ScrollTo sobrecarga del método desplaza el elemento especificado a la vista. Dado un
CollectionView objeto denominado collectionView , en el ejemplo siguiente se muestra cómo desplazar el
elemento Proboscis Monkey a la vista:

MonkeysViewModel viewModel = BindingContext as MonkeysViewModel;


Monkey monkey = viewModel.Monkeys.FirstOrDefault(m => m.Name == "Proboscis Monkey");
collectionView.ScrollTo(monkey);

Como alternativa, un elemento de los datos agrupados se puede desplazar en la vista especificando el elemento y
el grupo. En el ejemplo siguiente se muestra cómo desplazar el elemento Monkey de Proboscis en el grupo
Monkeys a la vista:
GroupedAnimalsViewModel viewModel = BindingContext as GroupedAnimalsViewModel;
AnimalGroup group = viewModel.Animals.FirstOrDefault(a => a.Name == "Monkeys");
Animal monkey = group.FirstOrDefault(m => m.Name == "Proboscis Monkey");
collectionView.ScrollTo(monkey, group);

NOTE
El ScrollToRequested evento se desencadena cuando ScrollTo se invoca el método.

Deshabilitar animación de desplazamiento


Cuando se desplaza un elemento a la vista, se muestra una animación de desplazamiento. Sin embargo, esta
animación se puede deshabilitar estableciendo el animate argumento del ScrollTo método en false :

collectionView.ScrollTo(monkey, animate: false);

Posición de desplazamiento de control


Al desplazar un elemento a la vista, se puede especificar la posición exacta del elemento una vez completado el
desplazamiento con el position argumento de los ScrollTo métodos. Este argumento acepta un
ScrollToPosition miembro de enumeración.

MakeVisible
El ScrollToPosition.MakeVisible miembro indica que el elemento debe desplazarse hasta que esté visible en la
vista:

collectionView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible);

Este código de ejemplo produce el desplazamiento mínimo necesario para desplazar el elemento a la vista:

NOTE
El ScrollToPosition.MakeVisible miembro se utiliza de forma predeterminada si position no se especifica el
argumento al llamar al ScrollTo método.
Start
El ScrollToPosition.Start miembro indica que el elemento debe desplazarse hasta el inicio de la vista:

collectionView.ScrollTo(monkey, position: ScrollToPosition.Start);

Este código de ejemplo hace que el elemento se desplace hasta el inicio de la vista:

Center
El ScrollToPosition.Center miembro indica que el elemento debe desplazarse hasta el centro de la vista:

collectionView.ScrollTo(monkey, position: ScrollToPosition.Center);

Este código de ejemplo hace que el elemento se desplace hasta el centro de la vista:

End
El ScrollToPosition.End miembro indica que el elemento debe desplazarse hasta el final de la vista:

collectionView.ScrollTo(monkey, position: ScrollToPosition.End);


Este código de ejemplo hace que el elemento se desplace hasta el final de la vista:

Controlar la posición de desplazamiento cuando se agregan nuevos


elementos
CollectionView define una ItemsUpdatingScrollMode propiedad, que está respaldada por una propiedad
enlazable. Esta propiedad obtiene o establece un ItemsUpdatingScrollMode valor de enumeración que representa
el comportamiento de desplazamiento de CollectionView cuando se agregan nuevos elementos a él. La
enumeración ItemsUpdatingScrollMode define los miembros siguientes:
KeepItemsInView mantiene el primer elemento de la lista que se muestra cuando se agregan nuevos
elementos.
KeepScrollOffset garantiza que la posición de desplazamiento actual se mantiene cuando se agregan nuevos
elementos.
KeepLastItemInViewajusta el desplazamiento de desplazamiento para mantener el último elemento de la lista
que se muestra cuando se agregan nuevos elementos.
El valor predeterminado de la ItemsUpdatingScrollMode propiedad es KeepItemsInView . Por lo tanto, cuando se
agreguen nuevos elementos al CollectionView primer elemento de la lista, se mostrarán. Para asegurarse de que
el último elemento de la lista se muestra cuando se agregan nuevos elementos, establezca la
ItemsUpdatingScrollMode propiedad en KeepLastItemInView :

<CollectionView ItemsUpdatingScrollMode="KeepLastItemInView">
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView
};

Visibilidad de la barra de desplazamiento


CollectionView define HorizontalScrollBarVisibility VerticalScrollBarVisibility las propiedades y, que están
respaldadas por propiedades enlazables. Estas propiedades obtienen o establecen un ScrollBarVisibility valor
de enumeración que representa Cuándo está visible la barra de desplazamiento horizontal o vertical. La
enumeración ScrollBarVisibility define los miembros siguientes:
Default indica el comportamiento predeterminado de la barra de desplazamiento para la plataforma y es el
valor predeterminado para las HorizontalScrollBarVisibility VerticalScrollBarVisibility propiedades y.
Always indica que las barras de desplazamiento serán visibles, incluso cuando el contenido quepa en la vista.
Never indica que las barras de desplazamiento no estarán visibles, incluso si el contenido no cabe en la vista.

Puntos de acoplamiento
Cuando un usuario desliza el dedo para iniciar un desplazamiento, se puede controlar la posición final del
desplazamiento para que los elementos se muestren por completo. Esta característica se conoce como ajuste, ya
que los elementos se ajustan a la posición cuando se detiene el desplazamiento y se controlan mediante las
siguientes propiedades de la ItemsLayout clase:
SnapPointsType , de tipo SnapPointsType , especifica el comportamiento de los puntos de ajuste al desplazarse.
SnapPointsAlignment , de tipo SnapPointsAlignment , especifica cómo se alinean los puntos de ajuste con los
elementos.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.

NOTE
Cuando se produce el ajuste, se producirá en la dirección que produce la menor cantidad de movimiento.

Tipo de puntos de ajuste


La SnapPointsType enumeración define los siguientes miembros:
None indica que el desplazamiento no se ajusta a los elementos.
Mandatory indica que el contenido se ajusta siempre al punto de acoplamiento más cercano a donde se
detendría el desplazamiento de forma natural, a lo largo de la dirección de inercia.
MandatorySingle indica el mismo comportamiento que Mandatory , pero solo desplaza un elemento cada vez.

De forma predeterminada, la SnapPointsType propiedad se establece en SnapPointsType.None , lo que garantiza


que el desplazamiento no ajusta los elementos, como se muestra en las siguientes capturas de pantallas:
Alineación de puntos de acoplamiento
La SnapPointsAlignment enumeración Start define Center End los miembros, y.

IMPORTANT
El valor de la SnapPointsAlignment propiedad solo se respeta cuando la SnapPointsType propiedad está establecida en
Mandatory , o MandatorySingle .

Start
El SnapPointsAlignment.Start miembro indica que los puntos de ajuste están alineados con el borde inicial de los
elementos.
De forma predeterminada, la SnapPointsAlignment propiedad se establece en SnapPointsAlignment.Start . Sin
embargo, por integridad, en el siguiente ejemplo de XAML se muestra cómo establecer este miembro de
enumeración:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Start" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Start
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento, el elemento superior se alinea con la parte
superior de la vista:
Center
El SnapPointsAlignment.Center miembro indica que los puntos de ajuste están alineados con el centro de los
elementos. En el siguiente ejemplo de XAML se muestra cómo establecer este miembro de enumeración:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
...
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Center
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento, el elemento superior se alineará en el centro
de la parte superior de la vista:

End
El SnapPointsAlignment.End miembro indica que los puntos de ajuste están alineados con el borde final de los
elementos. En el siguiente ejemplo de XAML se muestra cómo establecer este miembro de enumeración:

<CollectionView ItemsSource="{Binding Monkeys}">


<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="End" />
</CollectionView.ItemsLayout>
...
</CollectionView>
El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.End
},
// ...
};

Cuando un usuario desliza el dedo para iniciar un desplazamiento, el elemento inferior se alinea con la parte
inferior de la vista:

Vínculos relacionados
CollectionView (ejemplo)
:::no-loc(Xamarin.Forms)::: Agrupación de
CollectionView
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
A menudo, los conjuntos de datos de gran tamaño pueden resultar difíciles de manejar cuando se presentan en
una lista de desplazamiento continuo. En este escenario, la organización de los datos en grupos puede mejorar la
experiencia del usuario al facilitar la navegación por los datos.
CollectionView admite la visualización de datos agrupados y define las siguientes propiedades que controlan
cómo se presentará:
IsGrouped , de tipo bool , indica si los datos subyacentes se deben mostrar en grupos. El valor predeterminado
de esta propiedad es false .
GroupHeaderTemplate , de tipo DataTemplate , la plantilla que se va a usar para el encabezado de cada grupo.
GroupFooterTemplate , de tipo DataTemplate , la plantilla que se va a usar para el pie de página de cada grupo.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos.
Las capturas de pantallas siguientes muestran una CollectionView visualización de datos agrupados:

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Agrupar datos
Los datos se deben agrupar antes de que se puedan mostrar. Esto puede realizarse mediante la creación de una
lista de grupos, donde cada grupo es una lista de elementos. La lista de grupos debe ser una IEnumerable<T>
colección, donde T define dos fragmentos de datos:
Un nombre de grupo.
IEnumerable Colección que define los elementos que pertenecen al grupo.

El proceso de agrupación de datos, por lo tanto, es:


Cree un tipo que modela un solo elemento.
Cree un tipo que modela un solo grupo de elementos.
Cree una IEnumerable<T> colección, donde T es el tipo que modela un solo grupo de elementos. Por lo tanto,
esta colección es una colección de grupos, que almacena los datos agrupados.
Agregar datos a la IEnumerable<T> colección.
Ejemplo
Al agrupar los datos, el primer paso es crear un tipo que modela un solo elemento. En el ejemplo siguiente se
muestra la Animal clase de la aplicación de ejemplo:

public class Animal


{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}

La Animal clase modela un elemento único. Se puede crear un tipo que modela un grupo de elementos. En el
ejemplo siguiente se muestra la AnimalGroup clase de la aplicación de ejemplo:

public class AnimalGroup : List<Animal>


{
public string Name { get; private set; }

public AnimalGroup(string name, List<Animal> animals) : base(animals)


{
Name = name;
}
}

La AnimalGroup clase hereda de la List<T> clase y agrega una Name propiedad que representa el nombre del
grupo.
IEnumerable<T> Después, se puede crear una colección de grupos:

public List<AnimalGroup> Animals { get; private set; } = new List<AnimalGroup>();

Este código define una colección denominada Animals , donde cada elemento de la colección es un AnimalGroup
objeto. Cada AnimalGroup objeto incluye un nombre y una List<Animal> colección que define los Animal objetos
del grupo.
Después, los datos agrupados se pueden agregar a la Animals colección:
Animals.Add(new AnimalGroup("Bears", new List<Animal>
{
new Animal
{
Name = "American Black Bear",
Location = "North America",
Details = "Details about the bear go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg"
},
new Animal
{
Name = "Asian Black Bear",
Location = "Asia",
Details = "Details about the bear go here.",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG/180px-
Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG"
},
// ...
}));

Animals.Add(new AnimalGroup("Monkeys", new List<Animal>


{
new Animal
{
Name = "Baboon",
Location = "Africa & Asia",
Details = "Details about the monkey go here.",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-
Papio_anubis_%28Serengeti%2C_2009%29.jpg"
},
new Animal
{
Name = "Capuchin Monkey",
Location = "Central & South America",
Details = "Details about the monkey go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-
Capuchin_Costa_Rica.jpg"
},
new Animal
{
Name = "Blue Monkey",
Location = "Central and East Africa",
Details = "Details about the monkey go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-
BlueMonkey.jpg"
},
// ...
}));

Este código crea dos grupos en la Animals colección. El primero AnimalGroup se denomina Bears y contiene una
List<Animal> colección de detalles del osito. La segunda AnimalGroup se denomina Monkeys y contiene una
List<Animal> colección de detalles de Monkey.

Mostrar datos agrupados


CollectionView mostrará los datos agrupados, siempre que los datos se hayan agrupado correctamente,
estableciendo la IsGrouped propiedad en true :
<CollectionView ItemsSource="{Binding Animals}"
IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
...
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

El código de C# equivalente es el siguiente:

CollectionView collectionView = new CollectionView


{
IsGrouped = true
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
// ...

La apariencia de cada elemento de CollectionView se define estableciendo la CollectionView.ItemTemplate


propiedad en DataTemplate . Para obtener más información, vea definir la aparienciade los elementos.

NOTE
De forma predeterminada, mostrará CollectionView el nombre del grupo en el encabezado y pie de grupo. Este
comportamiento se puede cambiar personalizando el encabezado de grupo y el pie de grupo.

Personalizar el encabezado de grupo


La apariencia de cada encabezado de grupo se puede personalizar estableciendo la
CollectionView.GroupHeaderTemplate propiedad en un DataTemplate :

<CollectionView ItemsSource="{Binding Animals}"


IsGrouped="true">
...
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding Name}"
BackgroundColor="LightGray"
FontSize="Large"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
En este ejemplo, cada encabezado de grupo se establece en un Label que muestra el nombre del grupo y que
tiene establecidas otras propiedades de apariencia. Las capturas de pantallas siguientes muestran el encabezado
de grupo personalizado:

Personalizar el pie de grupo


La apariencia de cada pie de grupo se puede personalizar estableciendo la CollectionView.GroupFooterTemplate
propiedad en DataTemplate :

<CollectionView ItemsSource="{Binding Animals}"


IsGrouped="true">
...
<CollectionView.GroupFooterTemplate>
<DataTemplate>
<Label Text="{Binding Count, StringFormat='Total animals: {0:D}'}"
Margin="0,0,0,10" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
</CollectionView>

En este ejemplo, cada pie de grupo se establece en un valor de Label que muestra el número de elementos del
grupo. Las capturas de pantallas siguientes muestran el pie de grupo personalizado:

Grupos vacíos
Cuando un CollectionView muestra los datos agrupados, mostrará los grupos que estén vacíos. Estos grupos se
mostrarán con un encabezado y un pie de grupo, lo que indica que el grupo está vacío. Las capturas de pantallas
siguientes muestran un grupo vacío:

NOTE
En iOS 10 y versiones anteriores, los encabezados y pies de grupo de los grupos vacíos se pueden mostrar en la parte
superior de CollectionView .

Grupo sin plantillas


CollectionView puede mostrar datos agrupados correctamente sin establecer la CollectionView.ItemTemplate
propiedad en DataTemplate :

<CollectionView ItemsSource="{Binding Animals}"


IsGrouped="true" />

En este escenario, se pueden mostrar datos significativos invalidando el ToString método en el tipo que modela
un elemento único y el tipo que modela un solo grupo de elementos.

Vínculos relacionados
CollectionView (ejemplo)
:::no-loc(Xamarin.Forms)::: Plantillas de datos
:::no-loc(Xamarin.Forms)::: IndicatorView
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
IndicatorView Es un control que muestra los indicadores que representan el número de elementos y la posición
actual en un CarouselView :

IndicatorView define las siguientes propiedades:


Count , de tipo int , el número de indicadores.
HideSingle , de tipo bool , indica si el indicador debe ocultarse cuando solo existe uno. El valor
predeterminado es true .
IndicatorColor , de tipo Color , el color de los indicadores.
IndicatorSize , de tipo double , el tamaño de los indicadores. El valor predeterminado es 6,0.
IndicatorLayout , de tipo Layout<View> , define la clase de diseño utilizada para representar IndicatorView .
Esta propiedad está establecida por :::no-loc(Xamarin.Forms)::: y, por lo general, no es necesario que la
configuren los desarrolladores.
IndicatorTemplate , de tipo DataTemplate , la plantilla que define la apariencia de cada indicador.
IndicatorsShape , de tipo IndicatorShape , la forma de cada indicador.
ItemsSource , de tipo IEnumerable , la colección para la que se mostrarán los indicadores. Esta propiedad se
establecerá automáticamente cuando CarouselView.IndicatorView se establezca la propiedad.
MaximumVisible , de tipo int , el número máximo de indicadores visibles. El valor predeterminado es
int.MaxValue .
Position , de tipo int , el índice del indicador actualmente seleccionado. Esta propiedad usa un TwoWay
enlace. Esta propiedad se establecerá automáticamente cuando CarouselView.IndicatorView se establezca la
propiedad.
SelectedIndicatorColor , de tipo Color , el color del indicador que representa el elemento actual del objeto
CarouselView .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
Creación de un IndicatorView
En el ejemplo siguiente se muestra cómo crear una instancia IndicatorView de en XAML:

<StackLayout>
<CarouselView ItemsSource="{Binding Monkeys}"
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
<!-- DataTemplate that defines item appearance -->
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
</StackLayout>

En este ejemplo, IndicatorView se representa debajo de CarouselView , con un indicador para cada elemento en
CarouselView . IndicatorView Se rellena con datos estableciendo la CarouselView.IndicatorView propiedad en el
IndicatorView objeto. Cada indicador es un círculo gris claro, mientras que el indicador que representa el
elemento actual del CarouselView es gris oscuro.

IMPORTANT
Al establecer la propiedad CarouselView.IndicatorView , se obtiene el enlace de la IndicatorView.Position propiedad
a la CarouselView.Position propiedad y la IndicatorView.ItemsSource propiedad se enlaza a la
CarouselView.ItemsSource propiedad.

Cambiar forma de indicador


La IndicatorView clase tiene una IndicatorsShape propiedad, que determina la forma de los indicadores. Esta
propiedad se puede establecer en uno de los IndicatorShape miembros de enumeración:
Circle Especifica que las formas de indicador serán circulares. Este es el valor predeterminado de la
propiedad IndicatorView.IndicatorsShape .
Square indica que las formas de indicador serán cuadradas.

En el ejemplo siguiente se muestra una IndicatorView configurada para usar indicadores cuadrados:

<IndicatorView x:Name="indicatorView"
IndicatorsShape="Square"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray" />

Cambiar tamaño del indicador


La IndicatorView clase tiene una IndicatorSize propiedad, de tipo double , que determina el tamaño de los
indicadores en unidades independientes del dispositivo. El valor predeterminado de esta propiedad es 6,0.
En el ejemplo siguiente se muestra una IndicatorView configurada para mostrar indicadores mayores:

<IndicatorView x:Name="indicatorView"
IndicatorSize="18" />
Limitar el número de indicadores mostrados
La IndicatorView clase tiene una MaximumVisible propiedad, de tipo int , que determina el número máximo de
indicadores visibles.
En el ejemplo siguiente se muestra una IndicatorView configurada para mostrar un máximo de seis indicadores:

<IndicatorView x:Name="indicatorView"
MaximumVisible="6" />

Definir la apariencia del indicador


La apariencia de cada indicador puede definirse estableciendo la IndicatorView.IndicatorTemplate propiedad en
DataTemplate :

<StackLayout>
<CarouselView ItemsSource="{Binding Monkeys}"
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
<!-- DataTemplate that defines item appearance -->
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="Black"
HorizontalOptions="Center">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<Image Source="{FontImage &#xf30c;, FontFamily={OnPlatform iOS=Ionicons,
Android=ionicons.ttf#}, Size=12}" />
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</StackLayout>

Los elementos especificados en DataTemplate definen la apariencia de cada indicador. En este ejemplo, cada
indicador es un Image que muestra un icono de fuente mediante la FontImage extensión de marcado.
Las capturas de pantallas siguientes muestran los indicadores representados mediante un icono de fuente:
Para obtener más información sobre la FontImage extensión de marcado, consulte extensión de marcado
FontImage.

Vínculos relacionados
IndicatorView (ejemplo)
Extensión de marcado FontImage
:::no-loc(Xamarin.Forms)::: ListView
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
ListView es una vista para presentar listas de datos, especialmente listas largas que requieren desplazamiento.

IMPORTANT
CollectionView es una vista para presentar listas de datos con diferentes especificaciones de diseño. Pretende
proporcionar una alternativa más flexible y de rendimiento a ListView . Para obtener más información, vea
CollectionView de :::no-loc(Xamarin.Forms):::.

Casos de uso
ListView Se puede usar un control en cualquier situación en la que se muestren listas desplazables de datos. La
ListView clase admite acciones de contexto y enlace de datos.

El ListView control no debe confundirse con el TableView control. El TableView control es una opción mejor
cuando tiene una lista no enlazada de opciones o datos, ya que permite especificar opciones predefinidas en
XAML. Por ejemplo, la aplicación de configuración de iOS, que tiene un conjunto de opciones principalmente
predefinida, es más adecuada para usar un TableView que ListView .
La ListView clase no admite la definición de elementos de lista en XAML, debe utilizar la ItemsSource
propiedad o el enlace de datos con ItemTemplate para definir los elementos de la lista.
ListView Es más adecuado para las colecciones que se componen de un solo tipo de datos. Este requisito se
debe a que solo se puede usar un tipo de celda para cada fila de la lista. El TableView control puede admitir
varios tipos de celda, por lo que es una opción mejor cuando necesita mostrar varios tipos de datos.
Para obtener más información sobre cómo enlazar datos a una ListView instancia de, vea orígenes de datos de
ListView.

Componentes
El ListView control tiene una serie de componentes disponibles para ejecutar la funcionalidad nativa de cada
plataforma. Estos componentes se definen en las secciones siguientes.
Encabezados y pies de página
Los componentes de encabezado y pie de página se muestran al principio y al final de una lista, separados de
los datos de la lista. Los encabezados y pies de página se pueden enlazar a un origen de datos independiente
del origen de datos de ListView.
Grupos
Los datos de un ListView se pueden agrupar para facilitar la navegación. Los grupos normalmente están
enlazados a datos. En la captura de pantalla siguiente se muestra un ListView con datos agrupados:
Celdas
Los elementos de datos de un ListView se denominan celdas. Cada celda corresponde a una fila de datos. Hay
celdas integradas entre las que elegir, o bien puede definir su propia celda personalizada. Tanto las celdas
integradas como las personalizadas pueden usarse o definirse en XAML o en código.
Las celdas integradas, como TextCell y ImageCell , corresponden a los controles nativos y tienen un
rendimiento muy eficaz.
Un TextCell muestra una cadena de texto, opcionalmente con texto detallado. El texto detallado se
representa como una segunda línea en una fuente menor con un color de énfasis.
Un ImageCell muestra una imagen con texto. Aparece como un TextCell con una imagen a la
izquierda.
Las celdas personalizadas se usan para presentar datos complejos. Por ejemplo, una celda personalizada
podría usarse para presentar una lista de canciones que incluye el álbum y el intérprete.
En la captura de pantalla siguiente se muestra un ListView elemento con elementos ImageCell:

Para obtener más información sobre cómo personalizar las celdas de un ListView , vea personalizar el aspecto
de las celdas de ListView.

Funcionalidad
La ListView clase admite varios estilos de interacción.
La extracción a actualización permite al usuario extraer el ListView contenido para actualizar el contenido.
Las acciones de contexto permiten al desarrollador especificar acciones personalizadas en elementos
individuales de la lista. Por ejemplo, puede implementar el deslizamiento a la acción en iOS o en las acciones
de punteo largo en Android.
La selección permite al desarrollador adjuntar la funcionalidad a los eventos de selección y anulación de
selección en los elementos de la lista.
En la captura de pantalla siguiente se muestra un ListView con acciones de contexto:
Para obtener más información sobre las características de interactividad de ListView , vea acciones &
interactividad con ListView.

Vínculos relacionados
Trabajar con ListView (ejemplo)
Enlace bidireccional (ejemplo)
Celdas integradas (ejemplo)
Celdas personalizadas (ejemplo)
Agrupación (ejemplo)
Vista de representador personalizado (ejemplo)
Interactividad de ListView (ejemplo)
Orígenes de datos de ListView
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: ListView Se usa para mostrar listas de datos. En este artículo se explica cómo rellenar
ListView con datos y cómo enlazar datos al elemento seleccionado.

ItemsSource
ListView Se rellena con datos mediante la ItemsSource propiedad, que puede aceptar cualquier colección que
implemente IEnumerable . La manera más sencilla de rellenar un ListView implica el uso de una matriz de
cadenas:

<ListView>
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>mono</x:String>
<x:String>monodroid</x:String>
<x:String>monotouch</x:String>
<x:String>monorail</x:String>
<x:String>monodevelop</x:String>
<x:String>monotone</x:String>
<x:String>monopoly</x:String>
<x:String>monomodal</x:String>
<x:String>mononucleosis</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>

El código de C# equivalente es el siguiente:

var listView = new ListView();


listView.ItemsSource = new string[]
{
"mono",
"monodroid",
"monotouch",
"monorail",
"monodevelop",
"monotone",
"monopoly",
"monomodal",
"mononucleosis"
};
Este enfoque rellenará ListView con una lista de cadenas. De forma predeterminada, ListView llamará a
ToString y mostrará el resultado en una TextCell para cada fila. Para personalizar el modo en que se muestran
los datos, vea apariencia de la celda.
Dado ItemsSource que se ha enviado a una matriz, el contenido no se actualizará a medida que la lista o la matriz
subyacentes cambien. Si desea que ListView se actualice automáticamente a medida que se agregan, quitan y
cambian elementos en la lista subyacente, deberá utilizar ObservableCollection . ObservableCollection se define
en System.Collections.ObjectModel y es igual List que, salvo que puede notificar los ListView cambios:

ObservableCollection<Employee> employees = new ObservableCollection<Employee>();


listView.ItemsSource = employees;

//Mr. Mono will be added to the ListView because it uses an ObservableCollection


employees.Add(new Employee(){ DisplayName="Mr. Mono"});

Enlace de datos
El enlace de datos es el "pegamento" que enlaza las propiedades de un objeto de interfaz de usuario a las
propiedades de algún objeto CLR, como una clase en el ViewModel. El enlace de datos es útil porque simplifica el
desarrollo de las interfaces de usuario al reemplazar una gran cantidad de código reutilizable de aburrido.
El enlace de datos funciona manteniendo los objetos sincronizados a medida que cambian sus valores enlazados.
En lugar de tener que escribir controladores de eventos para cada vez que cambie el valor de un control, establezca
el enlace y habilite el enlace en el ViewModel.
Para obtener más información sobre el enlace de datos, vea conceptos básicos del enlace de datos , que es la
cuarta parte de la :::no-loc(Xamarin.Forms)::: serie de artículos conceptos básicos de XAML.
Enlazar celdas
Las propiedades de las celdas (y los elementos secundarios de las celdas) se pueden enlazar a las propiedades de
los objetos de ItemsSource . Por ejemplo, ListView se podría usar una para presentar una lista de empleados.
La clase Employee:

public class Employee


{
public string DisplayName {get; set;}
}

ObservableCollection<Employee> Se crea un, se establece como ListView ItemsSource y la lista se rellena con
datos:

ObservableCollection<Employee> employees = new ObservableCollection<Employee>();


public ObservableCollection<Employee> Employees { get { return employees; }}

public EmployeeListPage()
{
EmployeeView.ItemsSource = employees;

// ObservableCollection allows items to be added after ItemsSource


// is set and the UI will react to changes
employees.Add(new Employee{ DisplayName="Rob Finnerty"});
employees.Add(new Employee{ DisplayName="Bill Wrestler"});
employees.Add(new Employee{ DisplayName="Dr. Geri-Beth Hooper"});
employees.Add(new Employee{ DisplayName="Dr. Keith Joyce-Purdy"});
employees.Add(new Employee{ DisplayName="Sheri Spruce"});
employees.Add(new Employee{ DisplayName="Burt Indybrick"});
}

WARNING
Mientras ListView que se va a actualizar en respuesta a los cambios en su subyacente ObservableCollection ,
ListView no se actualizará si ObservableCollection se asigna una instancia diferente a la ObservableCollection
referencia original (por ejemplo, employees = otherObservableCollection; ).

El siguiente fragmento de código muestra un ListView enlazado a una lista de empleados:

<?xml version="1.0" encoding="utf-8" ?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:constants="clr-namespace:XamarinFormsSample;assembly=XamarinFormsXamlSample"
x:Class="XamarinFormsXamlSample.Views.EmployeeListPage"
Title="Employee List">
<ListView x:Name="EmployeeView"
ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding DisplayName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>

En este ejemplo de XAML se define un ContentPage que contiene un ListView . El origen de datos del control
ListView se establece mediante el atributo ItemsSource . El diseño de cada fila de ItemsSource se define dentro
del elemento ListView.ItemTemplate . Esto da como resultado las siguientes capturas de pantallas:
WARNING
ObservableCollection no es un subproceso seguro. ObservableCollection La modificación de hace que las
actualizaciones de la interfaz de usuario se realicen en el mismo subproceso que realizó las modificaciones. Si el subproceso
no es el subproceso de la interfaz de usuario principal, producirá una excepción.

Enlace SelectedItem
A menudo, querrá enlazar al elemento seleccionado de un ListView , en lugar de usar un controlador de eventos
para responder a los cambios. Para hacer esto en XAML, enlace la SelectedItem propiedad:

<ListView x:Name="listView"
SelectedItem="{Binding Source={x:Reference SomeLabel},
Path=Text}">

</ListView>

Suponiendo listView que ItemsSource es una lista de cadenas, SomeLabel tendrá su Text propiedad enlazada a
SelectedItem .

Vínculos relacionados
Enlace bidireccional (ejemplo)
Personalizar el aspecto de las celdas de ListView
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: ListView clase se usa para presentar listas desplazables, que se pueden
personalizar mediante el uso de ViewCell elementos. Un ViewCell elemento puede mostrar texto e imágenes,
indicar un estado verdadero/falso y recibir datos proporcionados por el usuario.

Celdas integradas
:::no-loc(Xamarin.Forms)::: incluye celdas integradas que funcionan para muchas aplicaciones:
los controles se usan para mostrar texto con una segunda línea opcional para el texto detallado.
TextCell
ImageCell los controles son similares a TextCell s, pero incluyen una imagen a la izquierda del texto.
SwitchCell los controles se usan para presentar y capturar Estados de activado/desactivado o
verdadero/falso.
EntryCell los controles se usan para presentar datos de texto que el usuario puede editar.

Los SwitchCell EntryCell controles y se utilizan con más frecuencia en el contexto de un TableView .
TextCell
TextCell es una celda para mostrar texto, opcionalmente con una segunda línea como texto detallado. En la
captura de pantalla siguiente se muestran TextCell los elementos de iOS y Android:

TextCells se representan como controles nativos en tiempo de ejecución, por lo que el rendimiento es muy bueno
en comparación con un personalizado ViewCell . Los TextCells son personalizables, lo que le permite establecer
las siguientes propiedades:
Text –texto que se muestra en la primera línea, en una fuente grande.
Detail –texto que se muestra debajo de la primera línea, en una fuente menor.
TextColor –color del texto.
DetailColor –color del texto de detalle

La siguiente captura de pantalla muestra TextCell los elementos con propiedades de color personalizadas:
ImageCell
ImageCell , como TextCell , se puede usar para mostrar texto y texto de detalle secundario, y ofrece un gran
rendimiento mediante el uso de los controles nativos de cada plataforma. ImageCell se diferencia de TextCell
en que muestra una imagen a la izquierda del texto.
En la captura de pantalla siguiente se muestran ImageCell los elementos de iOS y Android:

ImageCell resulta útil cuando es necesario mostrar una lista de datos con un aspecto visual, como una lista de
contactos o películas. ImageCell los s son personalizables, lo que permite establecer:
Text –texto que se muestra en la primera línea, en una fuente grande.
Detail –texto que se muestra debajo de la primera línea, en una fuente menor.
TextColor –color del texto.
DetailColor –color del texto de detalle
ImageSource –la imagen que se va a mostrar junto al texto.

La siguiente captura de pantalla muestra ImageCell los elementos con propiedades de color personalizadas:

Celdas personalizadas
Las celdas personalizadas permiten crear diseños de celda que no son compatibles con las celdas integradas. Por
ejemplo, puede que desee presentar una celda con dos etiquetas que tengan el mismo peso. Un TextCell no
sería suficiente porque TextCell tiene una etiqueta menor. La mayoría de las personalizaciones de celda
agregan datos de solo lectura adicionales (como etiquetas adicionales, imágenes u otra información de
presentación).
Todas las celdas personalizadas se deben derivar de ViewCell , la misma clase base que usan todos los tipos de
celdas integrados.
:::no-loc(Xamarin.Forms)::: ofrece un comportamiento de almacenamiento en caché en el ListView control que
puede mejorar el rendimiento del desplazamiento para algunos tipos de celdas personalizadas.
En la captura de pantalla siguiente se muestra un ejemplo de una celda personalizada:

XAML
La celda personalizada que se muestra en la captura de pantalla anterior se puede crear con el código XAML
siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="demoListView.ImageCellPage">
<ContentPage.Content>
<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee"
Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<Image Source="{Binding image}" />
<Label Text="{Binding title}"
TextColor="#f35e20" />
<Label Text="{Binding subtitle}"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>

El código XAML funciona de la siguiente manera:


La celda personalizada está anidada dentro de un DataTemplate , que está dentro de ListView.ItemTemplate .
Este es el mismo proceso que el uso de cualquier celda integrada.
ViewCell es el tipo de la celda personalizada. El elemento secundario del DataTemplate elemento debe ser de
la clase o derivarse de ella ViewCell .
Dentro de ViewCell , el diseño se puede administrar con cualquier :::no-loc(Xamarin.Forms)::: diseño. En este
ejemplo, el diseño se administra mediante un StackLayout , que permite personalizar el color de fondo.

NOTE
Cualquier propiedad de StackLayout que se pueda enlazar se puede enlazar dentro de una celda personalizada. Sin
embargo, esta funcionalidad no se muestra en el ejemplo de XAML.

Código
También se puede crear una celda personalizada en el código. En primer lugar, se debe crear una clase
personalizada derivada de ViewCell :

public class CustomCell : ViewCell


{
public CustomCell()
{
//instantiate each of our views
var image = new Image ();
StackLayout cellWrapper = new StackLayout ();
StackLayout horizontalLayout = new StackLayout ();
Label left = new Label ();
Label right = new Label ();

//set bindings
left.SetBinding (Label.TextProperty, "title");
right.SetBinding (Label.TextProperty, "subtitle");
image.SetBinding (Image.SourceProperty, "image");

//Set properties for desired design


cellWrapper.BackgroundColor = Color.FromHex ("#eee");
horizontalLayout.Orientation = StackOrientation.Horizontal;
right.HorizontalOptions = LayoutOptions.EndAndExpand;
left.TextColor = Color.FromHex ("#f35e20");
right.TextColor = Color.FromHex ("503026");

//add views to the view hierarchy


horizontalLayout.Children.Add (image);
horizontalLayout.Children.Add (left);
horizontalLayout.Children.Add (right);
cellWrapper.Children.Add (horizontalLayout);
View = cellWrapper;
}
}

En el constructor de la página, la ItemTemplate propiedad del objeto ListView está establecida en DataTemplate
con el CustomCell tipo especificado:

public partial class ImageCellPage : ContentPage


{
public ImageCellPage ()
{
InitializeComponent ();
listView.ItemTemplate = new DataTemplate (typeof(CustomCell));
}
}

Cambios de contexto de enlace


Al enlazar a las instancias de un tipo de celda personalizado BindableProperty , los controles de interfaz de
usuario que muestran los BindableProperty valores deben usar la OnBindingContextChanged invalidación para
establecer los datos que se van a mostrar en cada celda, en lugar del constructor de celda, como se muestra en el
ejemplo de código siguiente:

public class CustomCell : ViewCell


{
Label nameLabel, ageLabel, locationLabel;

public static readonly BindableProperty NameProperty =


BindableProperty.Create ("Name", typeof(string), typeof(CustomCell), "Name");
public static readonly BindableProperty AgeProperty =
BindableProperty.Create ("Age", typeof(int), typeof(CustomCell), 0);
public static readonly BindableProperty LocationProperty =
BindableProperty.Create ("Location", typeof(string), typeof(CustomCell), "Location");

public string Name


{
get { return(string)GetValue (NameProperty); }
set { SetValue (NameProperty, value); }
}

public int Age


{
get { return(int)GetValue (AgeProperty); }
set { SetValue (AgeProperty, value); }
}

public string Location


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

protected override void OnBindingContextChanged ()


{
base.OnBindingContextChanged ();

if (BindingContext != null)
{
nameLabel.Text = Name;
ageLabel.Text = Age.ToString ();
locationLabel.Text = Location;
}
}
}

Se OnBindingContextChanged llamará a la invalidación cuando se BindingContextChanged desencadene el evento,


en respuesta al valor de la BindingContext propiedad que cambia. Por consiguiente, cuando BindingContext
cambia, los controles de la interfaz de usuario que muestran los BindableProperty valores deben establecer sus
datos. Tenga en cuenta que BindingContext debe comprobarse un null valor, ya que se puede establecer
mediante :::no-loc(Xamarin.Forms)::: para la recolección de elementos no utilizados, lo que, a su vez, dará lugar a
la OnBindingContextChanged invalidación.
Como alternativa, los controles de interfaz de usuario se pueden enlazar a las BindableProperty instancias para
mostrar sus valores, lo que elimina la necesidad de invalidar el OnBindingContextChanged método.
NOTE
Al reemplazar OnBindingContextChanged , asegúrese de que se llama al método de la clase base
OnBindingContextChanged para que los delegados registrados reciban el BindingContextChanged evento.

En XAML, el enlace del tipo de celda personalizado a los datos se puede lograr tal y como se muestra en el
ejemplo de código siguiente:

<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<local:CustomCell Name="{Binding Name}" Age="{Binding Age}" Location="{Binding Location}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Esto enlaza las Name propiedades, Age y Location enlazables en la instancia de CustomCell , a las Name
propiedades, Age y Location de cada objeto de la colección subyacente.
En el ejemplo de código siguiente se muestra el enlace equivalente en C#:

var customCell = new DataTemplate (typeof(CustomCell));


customCell.SetBinding (CustomCell.NameProperty, "Name");
customCell.SetBinding (CustomCell.AgeProperty, "Age");
customCell.SetBinding (CustomCell.LocationProperty, "Location");

var listView = new ListView


{
ItemsSource = people,
ItemTemplate = customCell
};

En iOS y Android, si el ListView elemento está reciclando elementos y la celda personalizada usa un
representador personalizado, el representador personalizado debe implementar correctamente la notificación de
cambio de propiedad. Cuando se reutilizan las celdas, sus valores de propiedad cambiarán cuando el contexto de
enlace se actualice al de una celda disponible, con PropertyChanged los eventos que se generan. Para obtener
más información, consulte Personalización de un ViewCell. Para obtener más información sobre el reciclaje de
celdas, vea estrategia de almacenamiento en caché.

Vínculos relacionados
Celdas integradas (ejemplo)
Celdas personalizadas (ejemplo)
Contexto de enlace cambiado (ejemplo)
Apariencia de ListView
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: ListView Permite personalizar la presentación de la lista, además de las ViewCell
instancias de para cada fila de la lista.

Agrupar
Los grandes conjuntos de datos pueden resultar difíciles de manejar cuando se presentan en una lista de
desplazamiento continuo. La habilitación de la agrupación puede mejorar la experiencia del usuario en estos casos
al organizar mejor el contenido y activar controles específicos de la plataforma que facilitan la navegación por los
datos.
Cuando la agrupación está activada para un ListView , se agrega una fila de encabezado para cada grupo.
Para habilitar la agrupación:
Cree una lista de listas (una lista de grupos, cada grupo es una lista de elementos).
Establezca el valor ListView de ItemsSource en esa lista.
Establezca IsGroupingEnabled en true.
Establezca GroupDisplayBinding para enlazar a la propiedad de los grupos que se utiliza como título del grupo.
Opta Establezca GroupShortNameBinding para enlazar a la propiedad de los grupos que se usa como nombre
corto para el grupo. El nombre corto se usa para las Jump Lists (columna derecha en iOS).
Empiece por crear una clase para los grupos:

public class PageTypeGroup : List<PageModel>


{
public string Title { get; set; }
public string ShortName { get; set; } //will be used for jump lists
public string Subtitle { get; set; }
private PageTypeGroup(string title, string shortName)
{
Title = title;
ShortName = shortName;
}

public static IList<PageTypeGroup> All { private set; get; }


}

En el código anterior, All es la lista que se proporcionará a nuestro ListView como el origen de enlace. Title y
ShortName son las propiedades que se usarán para los encabezados de grupo.

En esta fase, All es una lista vacía. Agregue un constructor estático para que la lista se rellene al iniciar el
programa:
static PageTypeGroup()
{
List<PageTypeGroup> Groups = new List<PageTypeGroup> {
new PageTypeGroup ("Alpha", "A"){
new PageModel("Amelia", "Cedar", new switchCellPage(),""),
new PageModel("Alfie", "Spruce", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Ava", "Pine", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Archie", "Maple", new switchCellPage(), "grapefruit.jpg")
},
new PageTypeGroup ("Bravo", "B"){
new PageModel("Brooke", "Lumia", new switchCellPage(),""),
new PageModel("Bobby", "Xperia", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Bella", "Desire", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Ben", "Chocolate", new switchCellPage(), "grapefruit.jpg")
}
};
All = Groups; //set the publicly accessible list
}

En el código anterior, también se puede llamar a Add en elementos de Groups , que son instancias de tipo
PageTypeGroup . Este método es posible porque PageTypeGroup hereda de List<PageModel> .

Este es el código XAML para mostrar la lista agrupada:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DemoListView.GroupingViewPage"
<ContentPage.Content>
<ListView x:Name="GroupedView"
GroupDisplayBinding="{Binding Title}"
GroupShortNameBinding="{Binding ShortName}"
IsGroupingEnabled="true">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}"
Detail="{Binding Subtitle}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>

Este código XAML realiza las siguientes acciones:


GroupShortNameBinding Se establece en la ShortName propiedad definida en la clase Group.
GroupDisplayBinding Se establece en la Title propiedad definida en la clase Group.
Establézcalo IsGroupingEnabled en true
Ha cambiado el ListView de ItemsSource a la lista agrupada

En la siguiente captura de pantalla se muestra la interfaz de usuario resultante:


Personalización de la agrupación
Si se ha habilitado la agrupación en la lista, el encabezado de grupo también se puede personalizar.
De forma similar a como ListView tiene un ItemTemplate para definir cómo se muestran las filas, ListView tiene
un GroupHeaderTemplate .
A continuación se muestra un ejemplo de personalización del encabezado de grupo en XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DemoListView.GroupingViewPage">
<ContentPage.Content>
<ListView x:Name="GroupedView"
GroupDisplayBinding="{Binding Title}"
GroupShortNameBinding="{Binding ShortName}"
IsGroupingEnabled="true">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}"
Detail="{Binding Subtitle}"
TextColor="#f35e20"
DetailColor="#503026" />
</DataTemplate>
</ListView.ItemTemplate>
<!-- Group Header Customization-->
<ListView.GroupHeaderTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}"
Detail="{Binding ShortName}"
TextColor="#f35e20"
DetailColor="#503026" />
</DataTemplate>
</ListView.GroupHeaderTemplate>
<!-- End Group Header Customization -->
</ListView>
</ContentPage.Content>
</ContentPage>

Encabezados y pies de página


Es posible que un control ListView presente un encabezado y un pie de página que se desplacen con los elementos
de la lista. El encabezado y el pie de página pueden ser cadenas de texto o un diseño más complicado. Este
comportamiento es independiente de los grupos de sección.
Puede establecer Header y/o Footer en un string valor, o puede establecerlos en un diseño más complejo.
También hay HeaderTemplate propiedades y FooterTemplate que permiten crear diseños más complejos para el
encabezado y el pie de página que admiten el enlace de datos.
Para crear un encabezado/pie de página básico, solo tiene que establecer las propiedades de encabezado o de pie
de página en el texto que desea mostrar. Mediante código:

ListView HeaderList = new ListView()


{
Header = "Header",
Footer = "Footer"
};

En XAML:

<ListView x:Name="HeaderList"
Header="Header"
Footer="Footer">
...
</ListView>
Para crear un encabezado y un pie de página personalizados, defina las vistas de encabezado y pie de página:

<ListView.Header>
<StackLayout Orientation="Horizontal">
<Label Text="Header"
TextColor="Olive"
BackgroundColor="Red" />
</StackLayout>
</ListView.Header>
<ListView.Footer>
<StackLayout Orientation="Horizontal">
<Label Text="Footer"
TextColor="Gray"
BackgroundColor="Blue" />
</StackLayout>
</ListView.Footer>
Visibilidad de la barra de desplazamiento
La ListView clase tiene HorizontalScrollBarVisibility VerticalScrollBarVisibility las propiedades y, que
obtienen o establecen un ScrollBarVisibility valor que representa Cuándo está visible la barra de
desplazamiento horizontal o vertical. Ambas propiedades se pueden establecer en los siguientes valores:
Default indica el comportamiento predeterminado de la barra de desplazamiento para la plataforma y es el
valor predeterminado para las HorizontalScrollBarVisibility VerticalScrollBarVisibility propiedades y.
Always indica que las barras de desplazamiento serán visibles, incluso cuando el contenido quepa en la vista.
Never indica que las barras de desplazamiento no estarán visibles, incluso si el contenido no cabe en la vista.

Separadores de filas
Las líneas separadoras se muestran entre ListView los elementos de forma predeterminada en iOS y Android. Si
prefiere ocultar las líneas de separación en iOS y Android, establezca la SeparatorVisibility propiedad en
ListView. Las opciones de SeparatorVisibility son:
Predeterminada : muestra una línea de separación en iOS y Android.
Ninguno : oculta el separador en todas las plataformas.
Visibilidad predeterminada:
C#:

SeparatorDemoListView.SeparatorVisibility = SeparatorVisibility.Default;

XAML:
<ListView x:Name="SeparatorDemoListView" SeparatorVisibility="Default" />

None (Ninguna):
C#:

SeparatorDemoListView.SeparatorVisibility = SeparatorVisibility.None;

XAML:

<ListView x:Name="SeparatorDemoListView" SeparatorVisibility="None" />


También puede establecer el color de la línea de separación a través de la SeparatorColor propiedad:
C#:

SeparatorDemoListView.SeparatorColor = Color.Green;

XAML:

<ListView x:Name="SeparatorDemoListView" SeparatorColor="Green" />


NOTE
Si se establece cualquiera de estas propiedades en Android después de cargar, se produce ListView una gran penalización
del rendimiento.

Alto de fila
Todas las filas de un control ListView tienen el mismo alto de forma predeterminada. ListView tiene dos
propiedades que se pueden usar para cambiar este comportamiento:
HasUnevenRows – true / false valor, las filas tienen altos variables si están establecidas en true . Tiene como
valor predeterminado false .
RowHeight –establece el alto de cada fila cuando HasUnevenRows es false .

Puede establecer el alto de todas las filas estableciendo la RowHeight propiedad en ListView .
Alto de fila fijo personalizado
C#:

RowHeightDemoListView.RowHeight = 100;

XAML:

<ListView x:Name="RowHeightDemoListView" RowHeight="100" />


Filas desiguales
Si desea que las filas individuales tengan distintos altos, puede establecer la HasUnevenRows propiedad en true . El
alto de las filas no tiene que establecerse manualmente una vez que se ha HasUnevenRows establecido en true ,
porque el alto se calculará automáticamente mediante :::no-loc(Xamarin.Forms)::: .
C#:

RowHeightDemoListView.HasUnevenRows = true;

XAML:

<ListView x:Name="RowHeightDemoListView" HasUnevenRows="true" />


Cambiar el tamaño de las filas en tiempo de ejecución
Se ListView puede cambiar el tamaño de las filas individuales mediante programación en tiempo de ejecución,
siempre que la HasUnevenRows propiedad esté establecida en true . El Cell.ForceUpdateSize método actualiza el
tamaño de una celda, incluso cuando no está visible actualmente, como se muestra en el ejemplo de código
siguiente:

void OnImageTapped (object sender, EventArgs args)


{
var image = sender as Image;
var viewCell = image.Parent.Parent as ViewCell;

if (image.HeightRequest < 250) {


image.HeightRequest = image.Height + 100;
viewCell.ForceUpdateSize ();
}
}

El OnImageTapped controlador de eventos se ejecuta en respuesta a un Image objeto en una celda que se está
punteando y aumenta el tamaño del que Image se muestra en la celda para que se pueda ver fácilmente.
WARNING
El uso excesivo del cambio de tamaño de las filas en tiempo de ejecución puede degradar el rendimiento.

Vínculos relacionados
Agrupación (ejemplo)
Vista de representador personalizado (ejemplo)
Cambio de tamaño dinámico de filas (ejemplo)
Notas de la versión de 1,4
Notas de la versión de 1,3
Interactividad de ListView
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: ListView clase admite la interacción del usuario con los datos que presenta.

Selección y pulsaciones
El modo de selección se controla estableciendo la
ListView ListView.SelectionMode propiedad en un valor de la
ListViewSelectionMode enumeración:

Single indica que se puede seleccionar un solo elemento, con el elemento seleccionado resaltado. Este es el
valor predeterminado.
None indica que no se pueden seleccionar elementos.

Cuando un usuario pulsa un elemento, se activan dos eventos:


ItemSelected se desencadena cuando se selecciona un nuevo elemento.
ItemTapped se desencadena cuando se puntea un elemento.
Si puntea dos veces en el mismo elemento, se activarán dos ItemTapped eventos, pero solo se desencadenará un
ItemSelected evento único.

NOTE
La ItemTappedEventArgs clase, que contiene los argumentos de evento para el ItemTapped evento, Group tiene Item
las propiedades y, y una ItemIndex propiedad cuyo valor representa el índice en el ListView del elemento punteado. Del
mismo modo, la SelectedItemChangedEventArgs clase, que contiene los argumentos de evento para el ItemSelected
evento, tiene una SelectedItem propiedad y una SelectedItemIndex propiedad cuyo valor representa el índice de la
propiedad ListView del elemento seleccionado.

Cuando la propiedad está establecida en Single , se pueden seleccionar los elementos de


SelectionMode
ListView , se ItemSelected ItemTapped activarán los eventos y, y la SelectedItem propiedad se establecerá en el
valor del elemento seleccionado.
Cuando la SelectionMode propiedad está establecida en None , los elementos de ListView no se pueden
seleccionar, el ItemSelected evento no se desencadenará y la SelectedItem propiedad permanecerá null . Sin
embargo, los ItemTapped eventos se seguirán activando y el elemento punteado se resaltará brevemente durante
la derivación.
Cuando se ha seleccionado un elemento y SelectionMode se ha cambiado la propiedad de Single a None , la
SelectedItem propiedad se establecerá en null y el evento se ItemSelected desencadenará con un null
elemento.
Las capturas de pantallas siguientes muestran un ListView con el modo de selección predeterminado:
Deshabilitar selección
Para deshabilitar ListView la selección, establezca la SelectionMode propiedad en None :

<ListView ... SelectionMode="None" />

var listView = new ListView { ... SelectionMode = ListViewSelectionMode.None };

Acciones de contexto
A menudo, los usuarios quieren tomar medidas en un elemento de un ListView . Por ejemplo, considere una lista
de correos electrónicos en la aplicación de correo. En iOS, puede deslizar rápidamente para eliminar un mensaje:

Las acciones de contexto se pueden implementar en C# y XAML. A continuación encontrará guías específicas para
ambos, pero primero echemos un vistazo a algunos detalles de implementación clave para ambos.
Las acciones de contexto se crean mediante MenuItem elementos. Los eventos TAP para los MenuItems objetos los
genera el MenuItem propio, no el ListView . Esto es diferente de cómo se controlan los eventos TAP para las
celdas, donde ListView provoca el evento en lugar de la celda. Dado ListView que el objeto está provocando el
evento, su controlador de eventos proporciona información de clave, como qué elemento se ha seleccionado o
punteado.
De forma predeterminada, MenuItem no tiene ninguna manera de saber a qué celda pertenece. La
CommandParameter propiedad está disponible en MenuItem para almacenar objetos, como el objeto situado detrás
de MenuItem ViewCell . La CommandParameter propiedad se puede establecer en XAML y C#.
XAML
MenuItem los elementos se pueden crear en una colección XAML. En el código XAML siguiente se muestra una
celda personalizada con dos acciones de contexto implementadas:

<ListView x:Name="ContextDemoList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Clicked="OnMore"
CommandParameter="{Binding .}"
Text="More" />
<MenuItem Clicked="OnDelete"
CommandParameter="{Binding .}"
Text="Delete" IsDestructive="True" />
</ViewCell.ContextActions>
<StackLayout Padding="15,0">
<Label Text="{Binding title}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

En el archivo de código subyacente, asegúrese de que Clicked se implementan los métodos:

public void OnMore (object sender, EventArgs e)


{
var mi = ((MenuItem)sender);
DisplayAlert("More Context Action", mi.CommandParameter + " more context action", "OK");
}

public void OnDelete (object sender, EventArgs e)


{
var mi = ((MenuItem)sender);
DisplayAlert("Delete Context Action", mi.CommandParameter + " delete context action", "OK");
}

NOTE
NavigationPageRenderer Para Android tiene un método reemplazable UpdateMenuItemIcon que se puede usar para
cargar iconos de un personalizado Drawable . Esta invalidación permite usar imágenes SVG como iconos en MenuItem
instancias en Android.

Código
Las acciones de contexto se pueden implementar en cualquier Cell subclase (siempre y cuando no se usen como
encabezado de grupo) creando MenuItem instancias y agregándolas a la ContextActions colección para la celda.
Tiene las siguientes propiedades que se pueden configurar para la acción de contexto:
Texto – de cadena que aparece en el elemento de menú.
Clic en – evento cuando se hace clic en el elemento.
IsDestructive – (opcional) cuando es true, el elemento se representa de forma diferente en iOS.
Se pueden agregar varias acciones de contexto a una celda, pero solo una debe tener IsDestructive establecido
en true . En el código siguiente se muestra cómo se agregarán acciones de contexto a un ViewCell :

var moreAction = new MenuItem { Text = "More" };


moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) =>
{
var mi = ((MenuItem)sender);
Debug.WriteLine("More Context Action clicked: " + mi.CommandParameter);
};

var deleteAction = new MenuItem { Text = "Delete", IsDestructive = true }; // red background
deleteAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
deleteAction.Clicked += async (sender, e) =>
{
var mi = ((MenuItem)sender);
Debug.WriteLine("Delete Context Action clicked: " + mi.CommandParameter);
};
// add to the ViewCell's ContextActions property
ContextActions.Add (moreAction);
ContextActions.Add (deleteAction);

Extraer para actualizar


Los usuarios deben esperar que la extracción de una lista de datos se actualizará en la lista. El ListView control es
compatible con este. Para habilitar la funcionalidad de extracción a actualización, establezca
IsPullToRefreshEnabled en true :

<ListView ...
IsPullToRefreshEnabled="true" />

El código de C# equivalente es el siguiente:

listView.IsPullToRefreshEnabled = true;

Aparece un control de número durante la actualización, que es negro de forma predeterminada. Sin embargo, el
color del control de número se puede cambiar en iOS y Android estableciendo la RefreshControlColor propiedad
en Color :

<ListView ...
IsPullToRefreshEnabled="true"
RefreshControlColor="Red" />

El código de C# equivalente es el siguiente:

listView.RefreshControlColor = Color.Red;

Las siguientes capturas de pantallas muestran la incorporación de cambios a la actualización cuando el usuario
extrae:
Las siguientes capturas de pantallas muestran la extracción a la actualización después de que el usuario haya
lanzado la extracción, con el control de número que se muestra mientras ListView se está actualizando:

ListView activa el Refreshing evento para iniciar la actualización y la IsRefreshing propiedad se establecerá en
true . El código necesario para actualizar el contenido del ListView debería ejecutarse a continuación por el
controlador de eventos para el Refreshing evento, o por el método ejecutado por RefreshCommand . Una vez que
ListView se actualiza, la IsRefreshing propiedad debe establecerse en false , o EndRefresh se debe llamar al
método para indicar que la actualización se ha completado.

NOTE
Al definir un RefreshCommand , CanExecute se puede especificar el método del comando para habilitar o deshabilitar el
comando.

Detectar desplazamiento
ListView define un Scrolled evento que se desencadena para indicar que se ha producido el desplazamiento. En
el ejemplo de XAML siguiente se muestra un ListView que establece un controlador de eventos para el Scrolled
evento:
<ListView Scrolled="OnListViewScrolled">
...
</ListView>

El código de C# equivalente es el siguiente:

ListView listView = new ListView();


listView.Scrolled += OnListViewScrolled;

En este ejemplo de código, el OnListViewScrolled controlador de eventos se ejecuta cuando se Scrolled


desencadena el evento:

void OnListViewScrolled(object sender, ScrolledEventArgs e)


{
Debug.WriteLine("ScrollX: " + e.ScrollX);
Debug.WriteLine("ScrollY: " + e.ScrollY);
}

El OnListViewScrolled controlador de eventos genera los valores del ScrolledEventArgs objeto que acompaña al
evento.

Vínculos relacionados
Interactividad de ListView (ejemplo)
Rendimiento de ListView
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
Al escribir aplicaciones móviles, el rendimiento es importante. Los usuarios tienen que esperar un desplazamiento
suave y tiempos de carga rápidos. Si no se cumplen las expectativas de los usuarios, el costo de las clasificaciones
en el almacén de aplicaciones, o en el caso de una aplicación de línea de negocio, cuestan el tiempo y el dinero de
la organización.
:::no-loc(Xamarin.Forms)::: ListView Es una vista eficaz para mostrar los datos, pero tiene algunas limitaciones. El
rendimiento del desplazamiento puede verse afectado cuando se usan celdas personalizadas, sobre todo cuando
contienen jerarquías de vista profundamente anidadas o se usan determinados diseños que requieren una medida
compleja. Afortunadamente, hay técnicas que puede usar para evitar un bajo rendimiento.

Estrategia de almacenamiento en caché


Los controles ListView suelen usarse para mostrar muchos más datos de los que caben en la pantalla. Por ejemplo,
una aplicación de música podría tener una biblioteca de canciones con miles de entradas. La creación de un
elemento para cada entrada desperdiciaría la memoria valiosa y funcionaría mal. Crear y destruir filas de manera
constante requeriría que la aplicación creara instancias y limpie los objetos constantemente, lo que también
tendría un rendimiento bajo.
Para conservar memoria, los ListView equivalentes nativos de cada plataforma tienen características integradas
para reutilizar las filas. Solo las celdas visibles en la pantalla se cargan en la memoria y el contenido se carga en
las celdas existentes. Este patrón evita que la aplicación Cree instancias de miles de objetos, lo que ahorra tiempo y
memoria.
:::no-loc(Xamarin.Forms)::: permite ListView la reutilización de celdas a través de la ListViewCachingStrategy
enumeración, que tiene los valores siguientes:

public enum ListViewCachingStrategy


{
RetainElement, // the default value
RecycleElement,
RecycleElementAndDataTemplate
}

NOTE
El Plataforma universal de Windows (UWP) omite la RetainElement estrategia de almacenamiento en caché, ya que
siempre usa el almacenamiento en caché para mejorar el rendimiento. Por lo tanto, de forma predeterminada se comporta
como si RecycleElement se aplicara la estrategia de almacenamiento en caché.

RetainElement
La RetainElement estrategia de almacenamiento en caché especifica que ListView generará una celda para cada
elemento de la lista y es el ListView comportamiento predeterminado. Debe usarse en las siguientes
circunstancias:
Cada celda tiene un gran número de enlaces (20-30 +).
La plantilla de celda cambia con frecuencia.
La prueba revela que la RecycleElement estrategia de almacenamiento en caché tiene como resultado una
velocidad de ejecución reducida.
Es importante reconocer las consecuencias de la estrategia de RetainElement almacenamiento en caché al trabajar
con celdas personalizadas. Cualquier código de inicialización de celda deberá ejecutarse para cada creación de
celda, que puede ser varias veces por segundo. En este caso, las técnicas de diseño que estaban bien en una
página, como el uso de varias instancias anidadas StackLayout , se convierten en cuellos de botella de
rendimiento cuando se configuran y destruyen en tiempo real a medida que el usuario se desplaza.
RecycleElement
La RecycleElement estrategia de almacenamiento en caché especifica que el ListView intentará minimizar la
superficie de memoria y la velocidad de ejecución al reciclar las celdas de la lista. Este modo no siempre ofrece una
mejora del rendimiento y las pruebas deben realizarse para determinar las mejoras. Sin embargo, es la opción
preferida y debe usarse en las siguientes circunstancias:
Cada celda tiene un número pequeño o moderado de enlaces.
Cada una de BindingContext las celdas define todos los datos de la celda.
Cada celda es muy similar, y la plantilla de celda no cambia.
Durante la virtualización, la celda tendrá su contexto de enlace actualizado y, por tanto, si una aplicación utiliza este
modo, debe asegurarse de que las actualizaciones de contexto de enlace se controlen adecuadamente. Todos los
datos sobre la celda deben provienen del contexto de enlace o se pueden producir errores de coherencia. Este
problema se puede evitar utilizando el enlace de datos para mostrar los datos de la celda. Como alternativa, los
datos de celda deben establecerse en la OnBindingContextChanged invalidación, en lugar de en el constructor de la
celda personalizada, como se muestra en el ejemplo de código siguiente:

public class CustomCell : ViewCell


{
Image image = null;

public CustomCell ()
{
image = new Image();
View = image;
}

protected override void OnBindingContextChanged ()


{
base.OnBindingContextChanged ();

var item = BindingContext as ImageItem;


if (item != null) {
image.Source = item.ImageUrl;
}
}
}

Para obtener más información, vea enlazar cambios de contexto.


En iOS y Android, si las celdas usan representadores personalizados, deben asegurarse de que la notificación de
cambio de propiedad está implementada correctamente. Cuando se reutilizan las celdas, sus valores de propiedad
cambiarán cuando el contexto de enlace se actualice al de una celda disponible, con PropertyChanged los eventos
que se generan. Para obtener más información, consulte Personalización de un ViewCell.
RecycleElement con un DataTemplateSelector
Cuando ListView usa un DataTemplateSelector para seleccionar un DataTemplate , la estrategia de
almacenamiento en caché no RecycleElement almacena en caché DataTemplate . En su lugar, DataTemplate se
selecciona un para cada elemento de datos de la lista.

NOTE
La RecycleElement estrategia de almacenamiento en caché tiene un requisito previo, presentado en :::no-
loc(Xamarin.Forms)::: 2,4, que cuando DataTemplateSelector se le pide que seleccione un DataTemplate que cada uno
DataTemplate debe devolver el mismo ViewCell tipo. Por ejemplo, dado un ListView con un DataTemplateSelector
que puede devolver MyDataTemplateA (donde MyDataTemplateA devuelve un ViewCell de tipo MyViewCellA ) o
MyDataTemplateB (donde MyDataTemplateB devuelve un ViewCell de tipo MyViewCellB ), cuando se devuelve,
MyDataTemplateA debe devolver o se MyViewCellA producirá una excepción.

RecycleElementAndDataTemplate
La RecycleElementAndDataTemplate estrategia de almacenamiento en caché se basa en la estrategia de
almacenamiento en caché RecycleElement asegurándose también de que cuando ListView utiliza un
DataTemplateSelector para seleccionar DataTemplate , DataTemplate el tipo de elemento de la lista almacena en la
memoria caché. Por lo tanto, DataTemplate se seleccionan una vez por cada tipo de elemento, en lugar de una vez
por cada instancia de elemento.

NOTE
La RecycleElementAndDataTemplate estrategia de almacenamiento en caché tiene un requisito previo de que los
DataTemplate s devueltos por DataTemplateSelector deben usar el DataTemplate constructor que toma un Type .

Establecimiento de la estrategia de almacenamiento en caché


El ListViewCachingStrategy valor de enumeración se especifica con una ListView sobrecarga de constructor, tal y
como se muestra en el ejemplo de código siguiente:

var listView = new ListView(ListViewCachingStrategy.RecycleElement);

En XAML, establezca el CachingStrategy atributo como se muestra en el código XAML siguiente:

<ListView CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Este método tiene el mismo efecto que establecer el argumento de la estrategia de almacenamiento en caché en el
constructor en C#.
Establecer la estrategia de almacenamiento en caché en un control ListView de subclases
Al establecer el CachingStrategy atributo desde XAML en una subclase ListView , no se producirá el
comportamiento deseado, porque no hay ninguna CachingStrategy propiedad en ListView . Además, si XAMLC
está habilitado, se generará el siguiente mensaje de error: no se ha encontrado ninguna propiedad,
propiedad enlazable o evento para ' CachingStrategy '
La solución a este problema es especificar un constructor en la subclase ListView que acepta un
ListViewCachingStrategy parámetro y lo pasa a la clase base:
public class CustomListView : ListView
{
public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
{
}
...
}

Después, el ListViewCachingStrategy valor de enumeración se puede especificar desde XAML mediante la


x:Arguments Sintaxis:

<local:CustomListView>
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</local:CustomListView>

Sugerencias de rendimiento de ListView


Existen muchas técnicas para mejorar el rendimiento de un ListView . Las siguientes sugerencias pueden mejorar
el rendimiento de ListView
Enlace la ItemsSource propiedad a una IList<T> colección en lugar de una IEnumerable<T> colección, ya que
las IEnumerable<T> colecciones no admiten el acceso aleatorio.
Use las celdas integradas (como TextCell / SwitchCell ) en lugar de ViewCell siempre que pueda.
Usar menos elementos. Por ejemplo, considere la posibilidad de usar una sola FormattedString etiqueta en
lugar de varias etiquetas.
Reemplazar ListView por TableView cuando se muestran datos no homogéneos; es decir, datos de tipos
diferentes.
Limite el uso del Cell.ForceUpdateSize método. Si se sobreutiliza, se degradará el rendimiento.
En Android, evite establecer ListView la visibilidad o el color del separador de filas de una vez que se haya
creado una instancia, ya que esto produce una gran penalización del rendimiento.
Evite cambiar el diseño de las celdas en función del BindingContext . Cambiar el diseño conlleva grandes
costos de medición e inicialización.
Evite las jerarquías de diseño profundamente anidadas. Use AbsoluteLayout o Grid para ayudar a reducir el
anidamiento.
Evite LayoutOptions un específico distinto de Fill ( Fill es el más barato de calcular).
Evite colocar un ListView dentro ScrollView de por los siguientes motivos:
ListView Implementa su propio desplazamiento.
ListView No recibirá ningún gesto, ya que lo controlará el elemento primario ScrollView .
ListView Puede presentar un encabezado y un pie de página personalizados que se desplacen con los
elementos de la lista, lo que podría ofrecer la funcionalidad para la que ScrollView se utilizó. Para
obtener más información, vea encabezados y pies de página.
Considere la posibilidad de un representador personalizado si necesita un diseño específico y complejo
presentado en las celdas.
AbsoluteLayout tiene la posibilidad de realizar diseños sin una única llamada de medida, lo que lo convierte en un
rendimiento elevado. Si AbsoluteLayout no se puede usar, tenga en cuenta RelativeLayout . Si se usa
RelativeLayout , pasar restricciones directamente será mucho más rápido que el uso de Expression API. Este
método es más rápido porque la API de expresión usa JIT y, en iOS, el árbol debe interpretarse, lo que es más lento.
Expression API es adecuado para los diseños de página donde solo es necesario en el diseño y la rotación iniciales,
pero en ListView , donde se ejecuta constantemente durante el desplazamiento, afecta al rendimiento.
La creación de un representador personalizado para un ListView o sus celdas es un enfoque para reducir el efecto
de los cálculos de diseño en el rendimiento del desplazamiento. Para obtener más información, vea personalizar
un control ListView y personalizar un ViewCell.

Vínculos relacionados
Vista de representador personalizado (ejemplo)
Representador personalizado ViewCell (ejemplo)
ListViewCachingStrategy
Xamarin.FormsSelector
18/12/2020 • 3 minutes to read • Edit Online

La vista selector es un control para seleccionar un elemento de texto de una lista de datos.
Xamarin.Forms Picker Muestra una lista corta de elementos, desde la que el usuario puede seleccionar un
elemento. Picker define las siguientes propiedades:
CharacterSpacing , de tipo double , es el espaciado entre los caracteres del elemento mostrado por Picker .
FontAttributes de tipo FontAttributes , cuyo valor predeterminado es FontAtributes.None .
FontFamily de tipo string , cuyo valor predeterminado es null .
FontSize de tipo double , que tiene como valor predeterminado-1,0.
HorizontalTextAlignment , de tipo TextAlignment , es la alineación horizontal del texto que muestra Picker .
ItemsSource de tipo IList , la lista de origen de los elementos que se van a mostrar, cuyo valor
predeterminado es null .
SelectedIndex de tipo int , el índice del elemento seleccionado, cuyo valor predeterminado es-1.
SelectedItem de tipo object , el elemento seleccionado, cuyo valor predeterminado es null .
TextColor de tipo Color , el color que se usa para mostrar el texto, cuyo valor predeterminado es
Color.Default .
Title de tipo string , cuyo valor predeterminado es null .
TitleColor de tipo Color , el color usado para mostrar el Title texto.
VerticalTextAlignment , de tipo TextAlignment , es la alineación vertical del texto que muestra Picker .

Todas las propiedades están respaldadas por BindableProperty objetos, lo que significa que se pueden aplicar
estilos y las propiedades pueden ser destinos de los enlaces de datos. Las SelectedIndex SelectedItem
propiedades y tienen un modo de enlace predeterminado de BindingMode.TwoWay , lo que significa que pueden ser
destinos de enlaces de datos en una aplicación que usa la arquitectura Model-View-ViewModel (MVVM) . Para
obtener información sobre cómo establecer las propiedades de la fuente, vea fuentes.
Picker No muestra ningún dato cuando se muestra por primera vez. En su lugar, el valor de su Title propiedad
se muestra como un marcador de posición en las plataformas iOS y Android:

Cuando Picker obtiene el foco, se muestran sus datos y el usuario puede seleccionar un elemento:
Picker Desencadena un SelectedIndexChanged evento cuando el usuario selecciona un elemento. A continuación,
se muestra el elemento seleccionado Picker :

Hay dos técnicas para rellenar un Picker con datos:


Establecer la ItemsSource propiedad en los datos que se van a mostrar. Esta es la técnica recomendada. Para
obtener más información, consulte configuración de la propiedad ItemsSource de un selector.
Agregar los datos que se van a mostrar a la Items colección. Esta técnica era el proceso original para rellenar
Picker con datos. Para obtener más información, vea Agregar datos a la colección de elementos de un
selector.

Vínculos relacionados
Selector
Configuración de la propiedad ItemsSource de un
selector
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
La vista selector es un control para seleccionar un elemento de texto de una lista de datos. En este artículo se
explica cómo rellenar un selector con datos estableciendo la propiedad ItemsSource y cómo responder a la
selección de elementos por parte del usuario.
:::no-loc(Xamarin.Forms)::: 2.3.4 ha mejorado la Picker vista agregando la capacidad de rellenarla con datos
estableciendo su ItemsSource propiedad y recuperando el elemento seleccionado de la SelectedItem propiedad.
Además, se puede cambiar el color del texto del elemento seleccionado estableciendo la TextColor propiedad en
Color .

Rellenar un selector con datos


Un Picker se puede rellenar con datos estableciendo su ItemsSource propiedad en una IList colección. Cada
elemento de la colección debe ser de tipo, o se puede derivar de él object . Los elementos se pueden agregar en
XAML inicializando la ItemsSource propiedad desde una matriz de elementos:

<Picker x:Name="picker"
Title="Select a monkey"
TitleColor="Red">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>

NOTE
Tenga en cuenta que el elemento x:Array requiere un atributo Type que indica el tipo de los elementos de la matriz.

El código de C# equivalente se muestra a continuación:


var monkeyList = new List<string>();
monkeyList.Add("Baboon");
monkeyList.Add("Capuchin Monkey");
monkeyList.Add("Blue Monkey");
monkeyList.Add("Squirrel Monkey");
monkeyList.Add("Golden Lion Tamarin");
monkeyList.Add("Howler Monkey");
monkeyList.Add("Japanese Macaque");

var picker = new Picker { Title = "Select a monkey", TitleColor = Color.Red };


picker.ItemsSource = monkeyList;

Responder a la selección de elementos


Picker Permite seleccionar un elemento cada vez. Cuando un usuario selecciona un elemento,
SelectedIndexChanged se activa el evento, la SelectedIndex propiedad se actualiza a un entero que representa el
índice del elemento seleccionado en la lista y la SelectedItem propiedad se actualiza a que object representa el
elemento seleccionado. La SelectedIndex propiedad es un número basado en cero que indica el elemento
seleccionado por el usuario. Si no hay ningún elemento seleccionado, lo que sucede cuando Picker se crea y se
inicializa por primera vez, SelectedIndex será-1.

NOTE
El comportamiento de la selección de elementos en un Picker puede personalizarse en Ios con una plataforma específica.
Para obtener más información, vea controlar la selección de elementos de selector.

En el ejemplo de código siguiente se muestra cómo recuperar el SelectedItem valor de propiedad de Picker en
XAML:

<Label Text="{Binding Source={x:Reference picker}, Path=SelectedItem}" />

El código de C# equivalente se muestra a continuación:

var monkeyNameLabel = new Label();


monkeyNameLabel.SetBinding(Label.TextProperty, new Binding("SelectedItem", source: picker));

Además, se puede ejecutar un controlador de eventos cuando el SelectedIndexChanged evento se activa:

void OnPickerSelectedIndexChanged(object sender, EventArgs e)


{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;

if (selectedIndex != -1)
{
monkeyNameLabel.Text = (string)picker.ItemsSource[selectedIndex];
}
}

Este método obtiene el SelectedIndex valor de propiedad y utiliza el valor para recuperar el elemento
seleccionado de la ItemsSource colección. Es funcionalmente equivalente a recuperar el elemento seleccionado de
la SelectedItem propiedad. Tenga en cuenta que cada elemento de la ItemsSource colección es de tipo object y,
por lo tanto, se debe convertir en string para mostrarlo.
NOTE
Picker Se puede inicializar para mostrar un elemento específico mediante el establecimiento de SelectedIndex las
SelectedItem propiedades o. Sin embargo, estas propiedades deben establecerse después de inicializar la ItemsSource
colección.

Rellenar un selector con datos mediante el enlace de datos


Picker También se puede rellenar con datos mediante el enlace de datos para enlazar su ItemsSource propiedad a
una IList colección. En XAML, esto se consigue con la Binding extensión de marcado:

<Picker Title="Select a monkey"


TitleColor="Red"
ItemsSource="{Binding Monkeys}"
ItemDisplayBinding="{Binding Name}" />

El código de C# equivalente se muestra a continuación:

var picker = new Picker { Title = "Select a monkey", TitleColor = Color.Red };


picker.SetBinding(Picker.ItemsSourceProperty, "Monkeys");
picker.ItemDisplayBinding = new Binding("Name");

Los ItemsSource datos de la propiedad se enlazan a la Monkeys propiedad del modelo de vista conectado, que
devuelve una IList<Monkey> colección. En el ejemplo de código siguiente se muestra la Monkey clase, que
contiene cuatro propiedades:

public class Monkey


{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}

Al enlazar a una lista de objetos, Picker se debe indicar qué propiedad se va a mostrar de cada objeto. Esto se
consigue estableciendo la ItemDisplayBinding propiedad en la propiedad necesaria de cada objeto. En los
ejemplos de código anteriores, Picker se establece para mostrar cada Monkey.Name valor de propiedad.
Responder a la selección de elementos
El enlace de datos se puede usar para establecer un objeto en el SelectedItem valor de propiedad cuando cambia:

<Picker Title="Select a monkey"


TitleColor="Red"
ItemsSource="{Binding Monkeys}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedMonkey}" />
<Label Text="{Binding SelectedMonkey.Name}" ... />
<Label Text="{Binding SelectedMonkey.Location}" ... />
<Image Source="{Binding SelectedMonkey.ImageUrl}" ... />
<Label Text="{Binding SelectedMonkey.Details}" ... />

El código de C# equivalente se muestra a continuación:


var picker = new Picker { Title = "Select a monkey", TitleColor = Color.Red };
picker.SetBinding(Picker.ItemsSourceProperty, "Monkeys");
picker.SetBinding(Picker.SelectedItemProperty, "SelectedMonkey");
picker.ItemDisplayBinding = new Binding("Name");

var nameLabel = new Label { ... };


nameLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Name");

var locationLabel = new Label { ... };


locationLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Location");

var image = new Image { ... };


image.SetBinding(Image.SourceProperty, "SelectedMonkey.ImageUrl");

var detailsLabel = new Label();


detailsLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Details");

Los SelectedItem datos de la propiedad se enlazan a la SelectedMonkey propiedad del modelo de vista conectado,
que es de tipo Monkey . Por lo tanto, cuando el usuario selecciona un elemento en Picker , la SelectedMonkey
propiedad se establecerá en el Monkey objeto seleccionado. Los SelectedMonkey datos del objeto se muestran en
la interfaz de usuario por las Label Image vistas y:

NOTE
Tenga en cuenta que las SelectedItem SelectedIndex propiedades y admiten enlaces bidireccionales de forma
predeterminada.

Vínculos relacionados
Demo de selector (ejemplo)
Aplicación Monkey (ejemplo)
Selector enlazable (ejemplo)
API del selector
Adición de datos a la colección de elementos de un
selector
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
La vista selector es un control para seleccionar un elemento de texto de una lista de datos. En este artículo se
explica cómo rellenar un selector con datos agregándolo a la colección de elementos y cómo responder a la
selección de elementos por parte del usuario.

Rellenar un selector con datos


Antes de :::no-loc(Xamarin.Forms)::: 2.3.4, el proceso de rellenar un Picker con datos era agregar los datos que se
van a mostrar a la colección de solo lectura Items , que es de tipo IList<string> . Cada elemento de la colección
debe ser de tipo string . Los elementos se pueden agregar en XAML inicializando la Items propiedad con una
lista de x:String elementos:

<Picker Title="Select a monkey"


TitleColor="Red">
<Picker.Items>
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</Picker.Items>
</Picker>

El código de C# equivalente se muestra a continuación:

var picker = new Picker { Title = "Select a monkey", TitleColor = Color.Red };


picker.Items.Add("Baboon");
picker.Items.Add("Capuchin Monkey");
picker.Items.Add("Blue Monkey");
picker.Items.Add("Squirrel Monkey");
picker.Items.Add("Golden Lion Tamarin");
picker.Items.Add("Howler Monkey");
picker.Items.Add("Japanese Macaque");

Además de agregar datos mediante el Items.Add método, los datos también se pueden insertar en la colección
mediante el Items.Insert método.

Responder a la selección de elementos


Picker Permite seleccionar un elemento cada vez. Cuando un usuario selecciona un elemento, el
SelectedIndexChanged evento se activa y la SelectedIndex propiedad se actualiza a un entero que representa el
índice del elemento seleccionado en la lista. La SelectedIndex propiedad es un número basado en cero que indica
el elemento que el usuario seleccionó. Si no hay ningún elemento seleccionado, lo que sucede cuando Picker se
crea y se inicializa por primera vez, SelectedIndex será-1.
NOTE
El comportamiento de la selección de elementos en un Picker puede personalizarse en Ios con una plataforma específica.
Para obtener más información, vea controlar la selección de elementos de selector.

En el ejemplo de código siguiente OnPickerSelectedIndexChanged se muestra el método de control de eventos, que


se ejecuta cuando se SelectedIndexChanged activa el evento:

void OnPickerSelectedIndexChanged(object sender, EventArgs e)


{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;

if (selectedIndex != -1)
{
monkeyNameLabel.Text = picker.Items[selectedIndex];
}
}

Este método obtiene el SelectedIndex valor de propiedad y utiliza el valor para recuperar el elemento
seleccionado de la Items colección. Dado que cada elemento de la Items colección es string , se puede mostrar
mediante una Label sin necesidad de una conversión.

NOTE
Picker Se puede inicializar para mostrar un elemento específico estableciendo la SelectedIndex propiedad. Sin embargo,
la SelectedIndex propiedad debe establecerse después de inicializar la Items colección.

Vínculos relacionados
Demo de selector (ejemplo)
Selector
:::no-loc(Xamarin.Forms)::: TableView
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
TableView es una vista para mostrar listas desplazables de datos u opciones en las que hay filas que no
comparten la misma plantilla. A diferencia de ListView, no TableView tiene el concepto de ItemsSource , por lo
que los elementos se deben agregar manualmente como elementos secundarios.

Casos de uso
TableView resulta útil cuando:
presentar una lista de opciones de configuración
recopilación de datos en un formulario o
Mostrar los datos que se presentan de manera diferente de fila a fila (por ejemplo, números, porcentajes e
imágenes).
TableView controla el desplazamiento y la colocación de las filas en secciones atractivas, una necesidad común
de los escenarios anteriores. El TableView control usa la vista equivalente subyacente de cada plataforma
cuando está disponible, lo que crea una apariencia nativa para cada plataforma.

Estructura
Los elementos de TableView se organizan en secciones. En la raíz de TableView TableRoot , es, que es el
elemento primario de una o más instancias de TableSection . Cada TableSection una consta de un encabezado
y una o varias ViewCell instancias:
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="Ring">
<SwitchCell Text="New Voice Mail" />
<SwitchCell Text="New Mail" On="true" />
</TableSection>
</TableRoot>
</TableView>

El código de C# equivalente es el siguiente:

Content = new TableView


{
Root = new TableRoot
{
new TableSection("Ring")
{
// TableSection constructor takes title as an optional parameter
new SwitchCell { Text = "New Voice Mail" },
new SwitchCell { Text = "New Mail", On = true }
}
},
Intent = TableIntent.Settings
};

Aspecto
TableView expone la Intent propiedad, que se puede establecer en cualquiera de los TableIntent miembros
de enumeración:
Data : sirve para mostrar entradas de datos. Tenga en cuenta que ListView puede ser una opción mejor para
desplazar listas de datos.
Form : se utiliza cuando el TableView actúa como un formulario.
Menu : se usa para presentar un menú de selecciones.
Settings : se usa para mostrar una lista de opciones de configuración.

El TableIntent valor que elija puede afectar al modo TableView en que aparece en cada plataforma. Incluso si
no hay diferencias claras, se recomienda seleccionar el TableIntent que más se aproxime a la forma de usar la
tabla.
Además, el color del texto que se muestra para cada TableSection puede cambiarse estableciendo la TextColor
propiedad en Color .

Celdas integradas
:::no-loc(Xamarin.Forms)::: incluye las celdas integradas para recopilar y Mostrar información. Aunque ListView
y TableView pueden utilizar las mismas celdas, SwitchCell y EntryCell son las más relevantes para un
TableView escenario.

Vea la apariencia de una celda de ListView para obtener una descripción detallada de TextCell y ImageCell.
SwitchCell
SwitchCell es el control que se va a utilizar para presentar y capturar un estado ON/OFF o true / false .
Define las siguientes propiedades:
Text : texto que se va a mostrar junto al modificador.
On : indica si el modificador se muestra como activado o desactivado.
OnColor : el Color del modificador cuando se encuentra en la posición de encendido.

Todas estas propiedades son enlazables.


SwitchCell también expone el OnChanged evento, lo que le permite responder a los cambios en el estado de la
celda.

EntryCell
EntryCell resulta útil cuando es necesario Mostrar datos de texto que el usuario puede editar. Define las
siguientes propiedades:
Keyboard : Teclado que se va a mostrar durante la edición. Hay opciones para elementos como valores
numéricos, correo electrónico, números de teléfono, etc. consulte los documentos de la API.
Label : El texto de la etiqueta que se va a mostrar a la izquierda del campo de entrada de texto.
LabelColor : El color del texto de la etiqueta.
Placeholder : Texto que se va a mostrar en el campo de entrada cuando es null o está vacío. Este texto
desaparece cuando comienza la entrada de texto.
Text : El texto del campo de entrada.
HorizontalTextAlignment : Alineación horizontal del texto. Los valores son Center, Left o Right aligned. Vea los
documentos de la API.
VerticalTextAlignment : Alineación vertical del texto. Los valores son Start , Center o End .

EntryCell también expone el Completed evento, que se desencadena cuando el usuario presiona el botón '
DONE ' en el teclado mientras edita el texto.
Celdas personalizadas
Cuando las celdas integradas no son suficientes, se pueden usar celdas personalizadas para presentar y capturar
datos de la forma que tenga sentido para la aplicación. Por ejemplo, puede que desee presentar un control
deslizante para permitir que un usuario elija la opacidad de una imagen.
Todas las celdas personalizadas se deben derivar de ViewCell , la misma clase base que usan todos los tipos de
celdas integrados.
Este es un ejemplo de una celda personalizada:
En el ejemplo siguiente se muestra el código XAML que se usa para crear TableView en las capturas de
pantallas anteriores:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DemoTableView.TablePage"
Title="TableView">
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="Getting Started">
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="bulb.png" />
<Label Text="left"
TextColor="#f35e20" />
<Label Text="right"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
</StackLayout>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
</ContentPage>

El código de C# equivalente es el siguiente:


var table = new TableView();
table.Intent = TableIntent.Settings;
var layout = new StackLayout() { Orientation = StackOrientation.Horizontal };
layout.Children.Add (new Image() { Source = "bulb.png"});
layout.Children.Add (new Label()
{
Text = "left",
TextColor = Color.FromHex("#f35e20"),
VerticalOptions = LayoutOptions.Center
});
layout.Children.Add (new Label ()
{
Text = "right",
TextColor = Color.FromHex ("#503026"),
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.EndAndExpand
});
table.Root = new TableRoot ()
{
new TableSection("Getting Started")
{
new ViewCell() {View = layout}
}
};
Content = table;

El elemento raíz en TableView es TableRoot y hay TableSection inmediatamente debajo de TableRoot .


ViewCell Se define directamente en TableSection , y StackLayout se usa para administrar el diseño de la celda
personalizada, aunque aquí se podría usar cualquier diseño.

NOTE
A diferencia de ListView , no TableView requiere que las celdas personalizadas (o ninguna) estén definidas en
ItemTemplate .

Alto de fila
La TableView clase tiene dos propiedades que se pueden usar para cambiar el alto de las filas de las celdas:
RowHeight : establece el alto de cada fila en un int .
HasUnevenRows : las filas tienen altos variables si están establecidas en true . Tenga en cuenta que al
establecer esta propiedad en, el alto de las true filas se calculará y aplicará automáticamente :::no-
loc(Xamarin.Forms)::: .
Cuando cambia el alto del contenido de una celda de un TableView , el alto de las filas se actualiza
implícitamente en Android y en el plataforma universal de Windows (UWP). Sin embargo, en iOS se debe forzar
la actualización estableciendo la HasUnevenRows propiedad en true y llamando al Cell.ForceUpdateSize
método.
En el ejemplo de XAML siguiente se muestra un TableView que contiene un ViewCell :
<ContentPage ...>
<TableView ...
HasUnevenRows="true">
<TableRoot>
...
<TableSection ...>
...
<ViewCell x:Name="_viewCell"
Tapped="OnViewCellTapped">
<Grid Margin="15,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="Tap this cell." />
<Label x:Name="_target"
Grid.Row="1"
Text="The cell has changed size."
IsVisible="false" />
</Grid>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
</ContentPage>

Cuando ViewCell se puntea en, OnViewCellTapped se ejecuta el controlador de eventos:

void OnViewCellTapped(object sender, EventArgs e)


{
_target.IsVisible = !_target.IsVisible;
_viewCell.ForceUpdateSize();
}

El OnViewCellTapped controlador de eventos muestra u oculta el segundo Label en y ViewCell actualiza


explícitamente el tamaño de la celda llamando al Cell.ForceUpdateSize método.
Las capturas de pantallas siguientes muestran la celda antes de que se Puntee sobre:

Las siguientes capturas de pantallas muestran la celda después de la derivación:

IMPORTANT
Si esta característica está sobreutilizada, existe una gran probabilidad de degradación del rendimiento.

Vínculos relacionados
TableView (ejemplo)
:::no-loc(Xamarin.Forms)::: Objetos
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: MenuItem clase define los elementos de menú para los menús como ListView
menús contextuales de elementos y menús flotante de aplicación de Shell.
Las siguientes capturas MenuItem de pantallas muestran objetos en un ListView menú contextual de iOS y
Android:

La clase MenuItem define las propiedades siguientes:


Command es un ICommand que permite enlazar acciones de usuario, como pulsaciones de dedo o clics, a
comandos definidos en un ViewModel.
CommandParameter es un object que especifica el parámetro que se debe pasar a Command .
IconImageSource es un ImageSource valor que define el icono de presentación.
IsDestructive es un bool valor que indica si MenuItem quita su elemento de interfaz de usuario asociado de
la lista.
IsEnabled es un bool valor que indica si este objeto responde a la entrada del usuario.
Text es un string valor que especifica el texto para mostrar.

Estas propiedades están respaldadas por BindableProperty objetos, por lo que la MenuItem instancia puede ser el
destino de los enlaces de datos.

Crear un MenuItem
MenuItem los objetos se pueden usar dentro de un menú contextual en ListView los elementos de un objeto. El
patrón más común es crear MenuItem objetos dentro de una ViewCell instancia de, que se utiliza como el
DataTemplate objeto para los ListView s ItemTemplate . Cuando el ListView objeto se rellena, creará cada
elemento con DataTemplate , exponiendo las MenuItem opciones cuando el menú contextual se active para un
elemento.
En el ejemplo siguiente se muestra la MenuItem creación de instancias en el contexto de un ListView objeto:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Context Menu Option" />
</ViewCell.ContextActions>
<Label Text="{Binding .}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

MenuItem También se puede crear en el código:

// A function returns a ViewCell instance that


// is used as the template for each list item
DataTemplate dataTemplate = new DataTemplate(() =>
{
// A Label displays the list item text
Label label = new Label();
label.SetBinding(Label.TextProperty, ".");

// A ViewCell serves as the DataTemplate


ViewCell viewCell = new ViewCell
{
View = label
};

// Add a MenuItem instance to the ContextActions


MenuItem menuItem = new MenuItem
{
Text = "Context Menu Option"
};
viewCell.ContextActions.Add(menuItem);

// The function returns the custom ViewCell


// to the DataTemplate constructor
return viewCell;
});

// Finally, the dataTemplate is provided to


// the ListView object
ListView listView = new ListView
{
...
ItemTemplate = dataTemplate
};

Definir el comportamiento de MenuItem con eventos


La clase MenuItem expone un evento Clicked . Se puede adjuntar un controlador de eventos a este evento para
reaccionar a los grifos o clics en la MenuItem instancia en XAML:

<MenuItem ...
Clicked="OnItemClicked" />

También se puede adjuntar un controlador de eventos en el código:


MenuItem item = new MenuItem { ... }
item.Clicked += OnItemClicked;

En los ejemplos anteriores se hacía referencia a un OnItemClicked controlador de eventos. En el código siguiente
se muestra una implementación de ejemplo:

void OnItemClicked(object sender, EventArgs e)


{
// The sender is the menuItem
MenuItem menuItem = sender as MenuItem;

// Access the list item through the BindingContext


var contextItem = menuItem.BindingContext;

// Do something with the contextItem here


}

Definir el comportamiento de MenuItem con MVVM


La MenuItem clase admite el patrón Model-View-ViewModel (MVVM) a través de BindableProperty los objetos y
la ICommand interfaz. En el siguiente código XAML MenuItem se muestran las instancias enlazadas a comandos
definidos en un ViewModel:

<ContentPage.BindingContext>
<viewmodels:ListPageViewModel />
</ContentPage.BindingContext>

<StackLayout>
<Label Text="{Binding Message}" ... />
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Edit"
IconImageSource="icon.png"
Command="{Binding Source={x:Reference contentPage},
Path=BindingContext.EditCommand}"
CommandParameter="{Binding .}"/>
<MenuItem Text="Delete"
Command="{Binding Source={x:Reference contentPage},
Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<Label Text="{Binding .}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>

En el ejemplo anterior, MenuItem se definen dos objetos con sus Command CommandParameter propiedades y
enlazadas a comandos en el ViewModel. El ViewModel contiene los comandos a los que se hace referencia en el
código XAML:
public class ListPageViewModel : INotifyPropertyChanged
{
...

public ICommand EditCommand => new Command<string>((string item) =>


{
Message = $"Edit command was called on: {item}";
});

public ICommand DeleteCommand => new Command<string>((string item) =>


{
Message = $"Delete command was called on: {item}";
});
}

La aplicación de ejemplo incluye una DataService clase que se usa para obtener una lista de elementos para
rellenar los ListView objetos. Se crea una instancia de ViewModel, con elementos de la DataService clase, y se
establece como BindingContext en el código subyacente:

public MenuItemXamlMvvmPage()
{
InitializeComponent();
BindingContext = new ListPageViewModel(DataService.GetListItems());
}

Iconos de MenuItem
WARNING
MenuItem los objetos solo muestran iconos en Android. En otras plataformas, solo se mostrará el texto especificado por la
Text propiedad.

Los iconos se especifican mediante la IconImageSource propiedad. Si se especifica un icono, no se mostrará el


texto especificado por la Text propiedad. En la captura de pantalla siguiente se muestra un MenuItem con un
icono en Android:

Para obtener más información sobre el uso de imágenes en :::no-loc(Xamarin.Forms)::: , vea imágenes en :::no-
loc(Xamarin.Forms)::: .

Habilitar o deshabilitar un MenuItem en tiempo de ejecución


Para habilitar la deshabilitación de un MenuItem en tiempo de ejecución, enlace su Command propiedad a una
ICommand implementación de y asegúrese de que un canExecute delegado habilita y deshabilita ICommand según
corresponda.
IMPORTANT
No enlace la IsEnabled propiedad a otra propiedad cuando utilice la Command propiedad para habilitar o deshabilitar
MenuItem .

En el ejemplo siguiente se muestra una MenuItem cuya Command propiedad se enlaza a una ICommand denominada
MyCommand :

<MenuItem Text="My menu item"


Command="{Binding MyCommand}" />

La ICommand implementación requiere un canExecute delegado que devuelve el valor de una bool propiedad
para habilitar y deshabilitar MenuItem :

public class MyViewModel : INotifyPropertyChanged


{
bool isMenuItemEnabled = false;
public bool IsMenuItemEnabled
{
get { return isMenuItemEnabled; }
set
{
isMenuItemEnabled = value;
MyCommand.ChangeCanExecute();
}
}

public Command MyCommand { get; private set; }

public MyViewModel()
{
MyCommand = new Command(() =>
{
// Execute logic here
},
() => IsMenuItemEnabled);
}
}

En este ejemplo, el MenuItem está deshabilitado hasta que IsMenuItemEnabled se establece la propiedad. Cuando
esto ocurre, Command.ChangeCanExecute se llama al método, que hace que se canExecute vuelva a evaluar el
delegado de MyCommand .

Comportamiento del menú contextual entre plataformas


Se tiene acceso a los menús contextuales y se muestran de forma diferente en cada plataforma.
En Android, el menú contextual se activa en un elemento de lista. El menú contextual reemplaza el área de título y
la barra de navegación, y MenuItem las opciones se muestran como botones horizontales.

En iOS, el menú contextual se activa presionando el dedo en un elemento de lista. El menú contextual se muestra
en el elemento de lista y MenuItems se muestra como botones horizontales.

En UWP, el menú contextual se activa haciendo clic con el botón derecho en un elemento de la lista. El menú
contextual se muestra cerca del cursor como una lista vertical.

Vínculos relacionados
Demostraciones de MenuItem
Imágenes en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: ToolbarItem
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: ToolbarItem clase es un tipo especial de botón que se puede Agregar a la Page
colección de un objeto ToolbarItems . Cada ToolbarItem objeto aparecerá como un botón en la barra de
navegación de la aplicación. Una ToolbarItem instancia puede tener un icono y aparecer como un elemento de
menú primario o secundario. La ToolbarItem clase hereda de MenuItem .
Las siguientes capturas ToolbarItem de pantallas muestran objetos en la barra de navegación de iOS y Android:

La clase ToolbarItem define las propiedades siguientes:


Order es un ToolbarItemOrder valor de enumeración que determina si la ToolbarItem instancia de se muestra
en el menú principal o en el secundario.
Priority es un integer valor que determina el orden de presentación de los elementos de Page la colección
de un objeto ToolbarItems .
La ToolbarItem clase hereda las siguientes propiedades utilizadas normalmente de la MenuItem clase:
Command es un ICommand que permite enlazar acciones de usuario, como pulsaciones de dedo o clics, a
comandos definidos en un ViewModel.
CommandParameter es un object que especifica el parámetro que se debe pasar a Command .
IconImageSource es un ImageSource valor que determina el icono de presentación de un ToolbarItem objeto.
Text es un string que determina el texto que se va a mostrar en un ToolbarItem objeto.

Estas propiedades están respaldadas por BindableProperty objetos, por lo que una ToolbarItem instancia de
puede ser el destino de los enlaces de datos.

NOTE
Una alternativa a la creación de una barra de herramientas a partir de ToolbarItem objetos es establecer la
NavigationPage.TitleView propiedad adjunta en una clase de diseño que contiene varias vistas. Para obtener más
información, vea Mostrar vistas en la barra de navegación.

Crear un ToolbarItem
ToolbarItem Se puede crear una instancia de un objeto en XAML. Las Text IconImageSource propiedades y se
pueden establecer para determinar cómo se muestra el botón en la barra de navegación. En el ejemplo siguiente se
muestra cómo crear una instancia de ToolbarItem con algunas propiedades comunes establecidas y agregarla a
ContentPage una ToolbarItems colección de:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Example Item"
IconImageSource="example_icon.png"
Order="Primary"
Priority="0" />
</ContentPage.ToolbarItems>

En este ejemplo se generará un ToolbarItem objeto con texto, un icono y aparecerá en primer lugar en el área de la
barra de navegación principal. ToolbarItem También se puede crear un en el código y agregarlo a la ToolbarItems
colección:

ToolbarItem item = new ToolbarItem


{
Text = "Example Item",
IconImageSource = ImageSource.FromFile("example_icon.png"),
Order = ToolbarItemOrder.Primary,
Priority = 0
};

// "this" refers to a Page object


this.ToolbarItems.Add(item);

El archivo representado por string , proporcionado como la IconImageSource propiedad, debe existir en cada
proyecto de plataforma.

NOTE
Los recursos de imagen se tratan de forma diferente en cada plataforma. ImageSource Puede proviene de orígenes que
incluyen un archivo local o un recurso incrustado, un URI o un flujo. Para obtener más información sobre cómo establecer la
IconImageSource propiedad y las imágenes en :::no-loc(Xamarin.Forms)::: , vea imágenes en :::no-loc(Xamarin.Forms)::: .

Definir el comportamiento del botón


La ToolbarItem clase hereda el Clicked evento de la MenuItem clase. Se puede adjuntar un controlador de eventos
al Clicked evento para reaccionar a los grifos o clics en ToolbarItem instancias en XAML:

<ToolbarItem ...
Clicked="OnItemClicked" />

También se puede adjuntar un controlador de eventos en el código:

ToolbarItem item = new ToolbarItem { ... }


item.Clicked += OnItemClicked;

En los ejemplos anteriores se hacía referencia a un OnItemClicked controlador de eventos. En el código siguiente se
muestra una implementación de ejemplo:

void OnItemClicked(object sender, EventArgs e)


{
ToolbarItem item = (ToolbarItem)sender;
messageLabel.Text = $"You clicked the \"{item.Text}\" toolbar item.";
}

ToolbarItem los objetos también pueden utilizar Command las CommandParameter propiedades y para reaccionar a
los datos proporcionados por el usuario sin controladores de eventos. Para obtener más información sobre la
ICommand interfaz y el enlace de datos de MVVM, vea el :::no-loc(Xamarin.Forms)::: comportamiento del MVVM de
MenuItem.

Habilitar o deshabilitar un ToolbarItem en tiempo de ejecución


Para habilitar la deshabilitación de un ToolbarItem en tiempo de ejecución, enlace su Command propiedad a una
ICommand implementación de y asegúrese de que un canExecute delegado habilita y deshabilita ICommand según
corresponda.
Para obtener más información, vea habilitar o deshabilitar un MenuItem en tiempo de ejecución.

Menús primarios y secundarios


La ToolbarItemOrder enumeración Default tiene Primary Secondary los valores, y.
Cuando la Order propiedad está establecida en Primary , el ToolbarItem objeto aparecerá en la barra de
navegación principal en todas las plataformas. ToolbarItem los objetos se clasifican por orden de prioridad en el
título de la página, que se truncará para dejar espacio para los elementos. Las siguientes capturas ToolbarItem de
pantallas muestran objetos en el menú principal de iOS y Android:

Cuando la Order propiedad se establece en Secondary , el comportamiento varía entre plataformas. En UWP y
Android, el Secondary menú elementos aparece como tres puntos que se pueden puntear o hacer clic en él para
mostrar los elementos en una lista vertical. En iOS, el Secondary menú elementos aparece debajo de la barra de
navegación como una lista horizontal. Las capturas de pantallas siguientes muestran un menú secundario en iOS y
Android:

WARNING
El comportamiento del icono en ToolbarItem los objetos que tienen su Order propiedad establecida en Secondary es
incoherente entre las plataformas. Evite establecer la IconImageSource propiedad en los elementos que aparecen en el
menú secundario.

Vínculos relacionados
Demostraciones de ToolbarItem
Imágenes en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Objetos
Animación enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Xamarin. Forms incluye su propia infraestructura de animación que es sencilla para crear animaciones sencillas, a
la vez que también es lo suficientemente versátil como para crear animaciones complejas.
Las Xamarin.Forms clases de animación tienen como destino distintas propiedades de los elementos visuales, con
una animación típica que cambia progresivamente una propiedad de un valor a otro durante un período de
tiempo. Tenga en cuenta que no hay ninguna interfaz XAML para las Xamarin.Forms clases de animación. Sin
embargo, las animaciones se pueden encapsular en comportamientos y después se hace referencia a ellas desde
XAML.

Animaciones simples
La ViewExtensions clase proporciona métodos de extensión que se pueden usar para construir animaciones
simples que giran, escalan, traducen y atenúan VisualElement instancias. En este artículo se muestra cómo crear y
cancelar animaciones mediante la ViewExtensions clase.

Funciones de aceleración
Xamarin.Formsincluye una Easing clase que permite especificar una función de transferencia que controla el
modo en que las animaciones se aceleran o reducen a medida que se ejecutan. En este artículo se muestra cómo
utilizar las funciones de aceleración predefinidas y cómo crear funciones de aceleración personalizadas.

Animaciones personalizadas
La Animation clase es el bloque de creación de todas las Xamarin.Forms animaciones, con los métodos de
extensión de la ViewExtensions clase que crea uno o más Animation objetos. En este artículo se muestra cómo
usar la Animation clase para crear y cancelar animaciones, sincronizar varias animaciones y crear animaciones
personalizadas que animan las propiedades que no animan los métodos de animación existentes.
Animaciones simples en :::no-loc(Xamarin.Forms):::
18/12/2020 • 20 minutes to read • Edit Online

Descargar el ejemplo
La clase ViewExtensions proporciona métodos de extensión que se pueden usar para construir animaciones
simples. En este artículo se muestra cómo crear y cancelar animaciones mediante la clase ViewExtensions.
La ViewExtensions clase proporciona los siguientes métodos de extensión que se pueden usar para crear
animaciones simples:
[ TranslateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. translateto ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración): anima
las TranslationX propiedades y TranslationY de un VisualElement .
ScaleTo anima la Scale propiedad de un VisualElement .
ScaleXTo anima la ScaleX propiedad de un VisualElement .
ScaleYTo anima la ScaleY propiedad de un VisualElement .
[ RelScaleTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RelScaleTo ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) aplica un aumento
incremental animado o una disminución a la Scale propiedad de VisualElement .
[ RotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) anima la Rotation
propiedad de un VisualElement .
[ RelRotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RelRotateTo ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) aplica un aumento
incremental animado o una disminución a la Rotation propiedad de VisualElement .
[ RotateXTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RotateXTo ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) anima la RotationX
propiedad de un VisualElement .
[ RotateYTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RotateYTo ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) anima la RotationY
propiedad de un VisualElement .
[ FadeTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Desvaneceto ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . La aceleración)) anima la Opacity
propiedad de un VisualElement .
De forma predeterminada, cada animación tardará 250 milisegundos. Sin embargo, se puede especificar una
duración para cada animación al crear la animación.
La ViewExtensions clase también incluye [ CancelAnimations ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions.
CancelAnimations ( :::no-loc(Xamarin.Forms)::: . VisualElement)) que se puede usar para cancelar cualquier
animación.
NOTE
La ViewExtensions clase proporciona un [ LayoutTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Layoutto ( :::no-
loc(Xamarin.Forms)::: . VisualElement, :::no-loc(Xamarin.Forms)::: . Rectangle, System. UInt32, :::no-loc(Xamarin.Forms)::: .
Aceleración)). Sin embargo, este método está pensado para que lo usen los diseños para animar las transiciones entre los
Estados de diseño que contienen cambios de tamaño y posición. Por lo tanto, solo lo deben usar las Layout subclases.

Los métodos de extensión de animación de la ViewExtensions clase son todos asincrónicos y devuelven un
Task<bool> objeto. El valor devuelto es false si la animación se completa y true si se cancela la animación. Por
lo tanto, los métodos de animación deben usarse normalmente con el await operador, lo que permite determinar
fácilmente Cuándo se ha completado una animación. Además, se pueden crear animaciones secuenciales con
métodos de animación posteriores que se ejecuten después de que se haya completado el método anterior. Para
obtener más información, vea animaciones compuestas.
Si hay un requisito para permitir que una animación se complete en segundo plano, await se puede omitir el
operador. En este escenario, los métodos de extensión de animación devolverán rápidamente después de iniciar la
animación, con la animación en segundo plano. Esta operación se puede aprovechar al crear animaciones
compuestas. Para obtener más información, vea animaciones compuestas.
Para obtener más información sobre el await operador, vea información general sobre la compatibilidad con
Async.

Animaciones únicas
Cada método de extensión de ViewExtensions implementa una operación de animación única que cambia
progresivamente una propiedad de un valor a otro durante un período de tiempo. En esta sección se explora cada
operación de animación.
Rotación
En el ejemplo de código siguiente se muestra el uso de [ RotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-
loc(Xamarin.Forms)::: . Aceleración)) para animar la Rotation propiedad de un Image :

await image.RotateTo (360, 2000);


image.Rotation = 0;

Este código anima la Image instancia rotando hasta 360 grados en 2 segundos (2000 milisegundos). [ RotateTo ]
(XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: . VisualElement, System.
Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)) el método obtiene el Rotation valor de
propiedad actual para el inicio de la animación y, a continuación, lo gira desde ese valor hasta su primer
argumento (360). Una vez completada la animación, la propiedad de la imagen Rotation se restablece en 0. Esto
garantiza que la Rotation propiedad no permanezca en 360 una vez finalizada la animación, lo que impediría
rotaciones adicionales.
Las capturas de pantallas siguientes muestran la rotación en curso en cada plataforma:
NOTE
Además de [ RotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)), también hay [ RotateXTo ] (XREF:
:::no-loc(Xamarin.Forms)::: . ViewExtensions. RotateXTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System.
UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración) y [ RotateYTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions.
RotateYTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: .
Aceleración)) que animan las RotationX RotationY propiedades y, respectivamente.

Rotación relativa
En el ejemplo de código siguiente se muestra el uso de [ RelRotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. RelRotateTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-
loc(Xamarin.Forms)::: . Aceleración)) para aumentar o disminuir incrementalmente la Rotation propiedad de un
Image :

await image.RelRotateTo (360, 2000);

Este código anima la Image instancia rotando 360 grados desde su posición inicial en más de 2 segundos (2000
milisegundos). [ RelRotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RelRotateTo ( :::no-
loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)):
obtiene el valor de Rotation propiedad actual para el inicio de la animación y, a continuación, gira desde ese valor
hasta el valor más el primer argumento (360). Esto garantiza que cada animación siempre tendrá una rotación de
360 grados desde la posición inicial. Por lo tanto, si se invoca una nueva animación mientras una animación ya
está en curso, se iniciará desde la posición actual y puede acabar en una posición que no sea un incremento de 360
grados.
Las capturas de pantallas siguientes muestran la rotación relativa en curso en cada plataforma:

Ampliación
En el ejemplo de código siguiente se muestra cómo utilizar el ScaleTo método para animar la Scale propiedad
de un Image :

await image.ScaleTo (2, 2000);

Este código anima la Image instancia escalando hasta dos veces su tamaño en más de 2 segundos (2000
milisegundos). El ScaleTo método obtiene el valor de Scale propiedad actual (valor predeterminado de 1) para el
inicio de la animación y, a continuación, se escala desde ese valor hasta su primer argumento (2). Esto tiene el
efecto de expandir el tamaño de la imagen al doble de su tamaño.
Las capturas de pantallas siguientes muestran el escalado en curso en cada plataforma:
NOTE
Además del ScaleTo método, también hay ScaleXTo ScaleYTo métodos y que animan las ScaleX ScaleY
propiedades y, respectivamente.

Escalado relativo
En el ejemplo de código siguiente se muestra el uso de [ RelScaleTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. RelScaleTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-
loc(Xamarin.Forms)::: . Aceleración)) para animar la Scale propiedad de un Image :

await image.RelScaleTo (2, 2000);

Este código anima la Image instancia escalando hasta dos veces su tamaño en más de 2 segundos (2000
milisegundos). [ RelScaleTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. RelScaleTo ( :::no-
loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)):
obtiene el valor de Scale propiedad actual para el inicio de la animación y, a continuación, se escala desde ese
valor hasta el valor más el primer argumento (2). Esto garantiza que cada animación siempre será un escala de 2
desde la posición inicial.
Escala y giro con delimitadores
Las AnchorX AnchorY propiedades y establecen el centro del escalado o la rotación de las Rotation Scale
propiedades y. Por lo tanto, sus valores también afectan a [ RotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-
loc(Xamarin.Forms)::: . Aceleración)) y ScaleTo métodos.
Dado que se ha Image colocado en el centro de un diseño, en el ejemplo de código siguiente se muestra cómo se
gira la imagen alrededor del centro del diseño estableciendo su AnchorY propiedad:

double radius = Math.Min(absoluteLayout.Width, absoluteLayout.Height) / 2;


image.AnchorY = radius / image.Height;
await image.RotateTo(360, 2000);

Para girar la Image instancia alrededor del centro del diseño, las AnchorX propiedades y AnchorY deben
establecerse en valores relativos al ancho y alto del Image . En este ejemplo, el centro de Image se define para que
esté en el centro del diseño, por lo que el AnchorX valor predeterminado de 0,5 no requiere un cambio. Sin
embargo, la AnchorY propiedad se redefine para que sea un valor desde la parte superior del Image hasta el
punto central del diseño. Esto garantiza que Image realiza un giro completo de 360 grados alrededor del punto
central del diseño, tal como se muestra en las siguientes capturas de pantallas:

Traducción
En el ejemplo de código siguiente se muestra el uso de [ TranslateTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. translateto ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. Double, System.
UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)) para animar las TranslationX TranslationY propiedades y de un
Image :

await image.TranslateTo (-100, -100, 1000);

Este código anima la Image instancia mediante su traducción horizontal y vertical en un segundo (1000
milisegundos). [ TranslateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. translateto ( :::no-
loc(Xamarin.Forms)::: . VisualElement, System. Double, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: .
Aceleración)) convierte simultáneamente la imagen 100 píxeles a la izquierda y 100 píxeles hacia arriba. Esto se
debe a que el primer y el segundo argumento son números negativos. Proporcionar números positivos traduciría
la imagen a la derecha y hacia abajo.
Las capturas de pantallas siguientes muestran la traducción en curso en cada plataforma:

NOTE
Si un elemento se coloca inicialmente fuera de la pantalla y, a continuación, se convierte en la pantalla, después de la
conversión, el diseño de entrada del elemento permanece fuera de la pantalla y el usuario no puede interactuar con él. Por lo
tanto, se recomienda disponer de una vista en su posición final y, a continuación, realizar las traducciones necesarias.

Corrección selectiva
En el ejemplo de código siguiente se muestra el uso de [ FadeTo ] (XREF: :::no-loc(Xamarin.Forms)::: .
ViewExtensions. Desvaneceto ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-
loc(Xamarin.Forms)::: . Aceleración)) para animar la Opacity propiedad de un Image :

image.Opacity = 0;
await image.FadeTo (1, 4000);

Este código anima la Image instancia mediante una atenuación en más de 4 segundos (4000 milisegundos). [
FadeTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Desvaneceto ( :::no-loc(Xamarin.Forms)::: .
VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)) el método obtiene el
Opacity valor de propiedad actual para el inicio de la animación y, a continuación, se atenúa desde ese valor hasta
su primer argumento (1).
Las capturas de pantallas siguientes muestran el fundido en curso en cada plataforma:
Animaciones compuestas
Una animación compuesta es una combinación secuencial de animaciones y se puede crear con el await
operador, como se muestra en el ejemplo de código siguiente:

await image.TranslateTo (-100, 0, 1000); // Move image left


await image.TranslateTo (-100, -100, 1000); // Move image up
await image.TranslateTo (100, 100, 2000); // Move image diagonally down and right
await image.TranslateTo (0, 100, 1000); // Move image left
await image.TranslateTo (0, 0, 1000); // Move image up

En este ejemplo, Image se traduce en 6 segundos (6000 milisegundos). La conversión de Image utiliza cinco
animaciones, con el await operador que indica que cada animación se ejecuta secuencialmente. Por lo tanto, los
métodos de animación subsiguientes se ejecutan después de que el método anterior se haya completado.

Animaciones compuestas
Una animación compuesta es una combinación de animaciones en las que dos o más animaciones se ejecutan
simultáneamente. Las animaciones compuestas se pueden crear mezclando animaciones esperadas y no
esperadas, tal y como se muestra en el ejemplo de código siguiente:

image.RotateTo (360, 4000);


await image.ScaleTo (2, 2000);
await image.ScaleTo (1, 2000);

En este ejemplo, Image se escala y se gira simultáneamente en 4 segundos (4000 milisegundos). El escalado de
Image usa dos animaciones secuenciales que se producen al mismo tiempo que la rotación. [ RotateTo ] (XREF:
:::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double,
System. UInt32, :::no-loc(Xamarin.Forms)::: . El método de aceleración)) se ejecuta sin un await operador y se
devuelve inmediatamente, con la primera ScaleTo animación a partir de. El await operador de la primera
ScaleTo llamada al método retrasa la segunda llamada al método ScaleTo hasta que ScaleTo se ha completado
la primera llamada al método. En este punto RotateTo , la animación se completará a mitad y se Image girará a
180 grados. Durante los últimos 2 segundos (2000 milisegundos), la segunda ScaleTo animación y la RotateTo
animación se completan.
Ejecutar varios métodos asincrónicos simultáneamente
Los static Task.WhenAny Task.WhenAll métodos y se usan para ejecutar varios métodos asincrónicos
simultáneamente y, por tanto, se pueden usar para crear animaciones compuestas. Ambos métodos devuelven un
Task objeto y aceptan una colección de métodos que devuelven un Task objeto. El Task.WhenAny método se
completa cuando se completa la ejecución de cualquier método de su colección, como se muestra en el ejemplo de
código siguiente:

await Task.WhenAny<bool>
(
image.RotateTo (360, 4000),
image.ScaleTo (2, 2000)
);
await image.ScaleTo (1, 2000);

En este ejemplo, la Task.WhenAny llamada al método contiene dos tareas. La primera tarea gira la imagen en más
de 4 segundos (4000 milisegundos) y la segunda tarea escala la imagen en más de 2 segundos (2000
milisegundos). Cuando se completa la segunda tarea, se Task.WhenAny completa la llamada al método. Sin
embargo, aunque [ RotateTo ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-
loc(Xamarin.Forms)::: . VisualElement, System. Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración))
que todavía se está ejecutando, el segundo ScaleTo método puede comenzar.
El Task.WhenAll método se completa cuando se completan todos los métodos de su colección, como se muestra
en el ejemplo de código siguiente:

// 10 minute animation
uint duration = 10 * 60 * 1000;

await Task.WhenAll (
image.RotateTo (307 * 360, duration),
image.RotateXTo (251 * 360, duration),
image.RotateYTo (199 * 360, duration)
);

En este ejemplo, la Task.WhenAll llamada al método contiene tres tareas, cada una de las cuales se ejecuta en 10
minutos. Cada Task una de ellas realiza un número diferente de giros de 360 grados: 307 giros para [ RotateTo ]
(XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. Rotato ( :::no-loc(Xamarin.Forms)::: . VisualElement, System.
Double, System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración), 251 giros para [ RotateXTo ] (XREF: :::no-
loc(Xamarin.Forms)::: . ViewExtensions. RotateXTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double,
System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración) y 199 giros para [ RotateYTo ] (XREF: :::no-
loc(Xamarin.Forms)::: . ViewExtensions. RotateYTo ( :::no-loc(Xamarin.Forms)::: . VisualElement, System. Double,
System. UInt32, :::no-loc(Xamarin.Forms)::: . Aceleración)). Estos valores son números primos, lo que garantiza que
los giros no estén sincronizados y, por tanto, no producirán patrones repetitivos.
En las siguientes capturas de pantallas se muestran varias rotaciones en curso en cada plataforma:
Cancelar animaciones
Una aplicación puede cancelar una o varias animaciones con una llamada a static [
ViewExtensions.CancelAnimations ] (XREF: :::no-loc(Xamarin.Forms)::: . ViewExtensions. CancelAnimations ( :::no-
loc(Xamarin.Forms)::: . VisualElement)), como se muestra en el ejemplo de código siguiente:

ViewExtensions.CancelAnimations (image);

Esto cancelará inmediatamente todas las animaciones que se están ejecutando actualmente en la Image instancia.

Resumen
En este artículo se ha mostrado la creación y cancelación de animaciones con la ViewExtensions clase. Esta clase
proporciona métodos de extensión que se pueden usar para construir animaciones simples que giran, escalan,
traducen y atenúan VisualElement instancias.

Vínculos relacionados
Información general sobre la compatibilidad con Async
Animación básica (ejemplo)
ViewExtensions
Funciones de aceleración en :::no-
loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: incluye una clase de entradas y salidas lentas que permite especificar una función de
transferencia que controla cómo se aceleran las animaciones o se ralentizan mientras se ejecutan. En este artículo
se muestra cómo utilizar las funciones de aceleración predefinidas y cómo crear funciones de aceleración
personalizadas.
La Easing clase define un número de funciones de aceleración que pueden usar las animaciones:
La BounceIn función de aceleración rebota la animación al principio.
La BounceOut función de aceleración rebota la animación al final.
La CubicIn función de entradas y salidas lentas acelera lentamente la animación.
La CubicInOut función de aceleración acelera la animación al principio y ralentiza la animación al final.
La CubicOut función de aceleración ralentiza rápidamente la animación.
La Linear función de aceleración usa una velocidad constante y es la función de aceleración predeterminada.
La SinIn función de aceleración agiliza la animación.
La SinInOut función de entradas y salidas lentas acelera la animación al principio y suaviza suavemente la
animación al final.
La SinOut función de entradas y salidas lentas desacelera suavemente la animación.
La SpringIn función de aceleración hace que la animación se acelere muy rápidamente hacia el final.
La SpringOut función de aceleración hace que la animación se ralentice rápidamente hacia el final.
Los In Out sufijos y indican si el efecto que proporciona la función de aceleración se percibe al principio de la
animación, al final o a ambos.
Además, se pueden crear funciones de aceleración personalizadas. Para obtener más información, vea funciones
de aceleración personalizadas.

Consumir una función de aceleración


Los métodos de extensión de animación de la ViewExtensions clase permiten especificar una función de
aceleración como el último parámetro del método, como se muestra en el ejemplo de código siguiente:

await image.TranslateTo(0, 200, 2000, Easing.BounceIn);


await image.ScaleTo(2, 2000, Easing.CubicIn);
await image.RotateTo(360, 2000, Easing.SinInOut);
await image.ScaleTo(1, 2000, Easing.CubicOut);
await image.TranslateTo(0, -200, 2000, Easing.BounceOut);

Al especificar una función de aceleración para una animación, la velocidad de la animación no es lineal y produce
el efecto proporcionado por la función de aceleración. Al omitir una función de aceleración al crear una
animación, la animación utiliza la Linear función de aceleración predeterminada, que genera una velocidad
lineal.
Para obtener más información sobre el uso de los métodos de extensión de animación en la ViewExtensions
clase, vea animaciones simples. La clase también puede usar las funciones de aceleración Animation . Para
obtener más información, vea animaciones personalizadas.

Funciones de aceleración personalizadas


Hay tres enfoques principales para crear una función de aceleración personalizada:
1. Cree un método que tome un double argumento y devuelva un double resultado.
2. Creará un control Func<double, double> .
3. Especifique la función de entradas y salidas lentas como argumento para el Easing constructor.
En los tres casos, la función de aceleración personalizada debe devolver 0 para un argumento de 0 y 1 para un
argumento de 1. Sin embargo, se puede devolver cualquier valor entre los valores de argumento de 0 y 1. Cada
enfoque se tratará ahora a su vez.
Método de aceleración personalizado
Una función de aceleración personalizada se puede definir como un método que toma un double argumento y
devuelve un double resultado, como se muestra en el ejemplo de código siguiente:

double CustomEase (double t)


{
return t == 0 || t == 1 ? t : (int)(5 * t) / 5.0;
}

await image.TranslateTo(0, 200, 2000, (Easing)CustomEase);

El CustomEase método trunca el valor de entrada a los valores 0, 0,2, 0,4, 0,6, 0,8 y 1. Por lo tanto, la Image
instancia se traduce en saltos discretos, en lugar de sin problemas.
FUNC. de aceleración personalizada
También se puede definir una función de aceleración personalizada como Func<double, double> , como se
muestra en el ejemplo de código siguiente:

Func<double, double> CustomEaseFunc = t => 9 * t * t * t - 13.5 * t * t + 5.5 * t;


await image.TranslateTo(0, 200, 2000, CustomEaseFunc);

CustomEaseFunc Representa una función de entradas y salidas lentas que se inicia con rapidez, reduce el tiempo y
revierte el curso y, a continuación, invierte el curso de nuevo para acelerar rápidamente hacia el final. Por lo tanto,
mientras el movimiento global de la Image instancia es hacia abajo, también invierte temporalmente el curso a
mitad de la animación.
Constructor de entradas y salidas personalizadas
También se puede definir una función de aceleración personalizada como argumento para el Easing constructor,
tal y como se muestra en el ejemplo de código siguiente:

await image.TranslateTo (0, 200, 2000, new Easing (t => 1 - Math.Cos (10 * Math.PI * t) * Math.Exp (-5 *
t)));

La función de aceleración personalizada se especifica como un argumento de función lambda para el Easing
constructor y usa el Math.Cos método para crear un efecto de eliminación lenta que el método detiene Math.Exp
. Por lo tanto, la Image instancia se traduce para que parezca colocarse en su lugar final.

Resumen
En este artículo se muestra cómo utilizar las funciones de aceleración predefinidas y cómo crear funciones de
aceleración personalizadas. :::no-loc(Xamarin.Forms)::: incluye una Easing clase que permite especificar una
función de transferencia que controla el modo en que las animaciones se aceleran o reducen a medida que se
ejecutan.

Vínculos relacionados
Información general sobre la compatibilidad con Async
Funciones de aceleración (ejemplo)
Pronuncia
ViewExtensions
Animaciones personalizadas en :::no-
loc(Xamarin.Forms):::
18/12/2020 • 20 minutes to read • Edit Online

Descargar el ejemplo
La clase Animation es el bloque de creación de todas las :::no-loc(Xamarin.Forms)::: animaciones, con los métodos
de extensión de la clase ViewExtensions que crea uno o varios objetos Animation. En este artículo se muestra
cómo usar la clase Animation para crear y cancelar animaciones, sincronizar varias animaciones y crear
animaciones personalizadas que animan propiedades que no están animadas por los métodos de animación
existentes.
Se debe especificar una serie de parámetros al crear un Animation objeto, incluidos los valores inicial y final de la
propiedad que se está animando, y una devolución de llamada que cambia el valor de la propiedad. Un Animation
objeto también puede mantener una colección de animaciones secundarias que se pueden ejecutar y sincronizar.
Para obtener más información, vea animaciones secundarias.
La ejecución de una animación creada con la Animation clase, que puede incluir o no las animaciones secundarias,
se consigue mediante una llamada a [ Commit ] (XREF: :::no-loc(Xamarin.Forms)::: . Animation. Commit () :::no-
loc(Xamarin.Forms)::: . IAnimatable, System. String, System. UInt32, System. UInt32, :::no-loc(Xamarin.Forms)::: .
Método de aceleración, System. Action {System. Double, System. Boolean}, System. Func {System. Boolean})). Este
método especifica la duración de la animación y, entre otros elementos, una devolución de llamada que controla si
se debe repetir la animación.
Además, la Animation clase tiene una IsEnabled propiedad que se puede examinar para determinar si el sistema
operativo ha deshabilitado las animaciones, como cuando se activa el modo de ahorro de energía.

Creación de una animación


Al crear un Animation objeto, normalmente se requiere un mínimo de tres parámetros, como se muestra en el
ejemplo de código siguiente:

var animation = new Animation (v => image.Scale = v, 1, 2);

Este código define una animación de la Scale propiedad de una Image instancia de un valor de 1 a un valor de 2.
El valor animado, derivado de :::no-loc(Xamarin.Forms)::: , se pasa a la devolución de llamada especificada como
primer argumento, donde se usa para cambiar el valor de la Scale propiedad.
La animación se inicia con una llamada a [ Commit ] (XREF: :::no-loc(Xamarin.Forms)::: . Animation. Commit () :::no-
loc(Xamarin.Forms)::: . IAnimatable, System. String, System. UInt32, System. UInt32, :::no-loc(Xamarin.Forms)::: .
Método de aceleración, System. Action {System. Double, System. Boolean}, System. Func {System. Boolean})), como
se muestra en el ejemplo de código siguiente:

animation.Commit (this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) => image.Scale = 1, () => true);

Tenga en cuenta que [ Commit ] (XREF: :::no-loc(Xamarin.Forms)::: . Animation. Commit () :::no-loc(Xamarin.Forms):::


. IAnimatable, System. String, System. UInt32, System. UInt32, :::no-loc(Xamarin.Forms)::: . El método de aceleración,
System. Action {System. Double, System. Boolean}, System. Func {System. Boolean})) no devuelve un Task objeto.
En su lugar, las notificaciones se proporcionan a través de métodos de devolución de llamada.
Los siguientes argumentos se especifican en el Commit método:
El primer argumento ( propietario ) identifica el propietario de la animación. Puede ser el elemento visual en el
que se aplica la animación u otro elemento visual, como la página.
El segundo argumento ( Name ) identifica la animación con un nombre. El nombre se combina con el
propietario para identificar de forma única la animación. Esta identificación única se puede usar para
determinar si la animación se está ejecutando ([ AnimationIsRunning ] (XREF: :::no-loc(Xamarin.Forms)::: .
AnimationExtensions. AnimationIsRunning ( :::no-loc(Xamarin.Forms)::: . IAnimatable, System. String))) o para
cancelarla ([ AbortAnimation ] (XREF: :::no-loc(Xamarin.Forms)::: . AnimationExtensions. AbortAnimation ( :::no-
loc(Xamarin.Forms)::: . IAnimatable, System. String))).
El tercer argumento ( Rate ) indica el número de milisegundos entre cada llamada al método de devolución de
llamada definido en el Animation constructor.
El cuarto argumento ( length ) indica la duración de la animación, en milisegundos.
El quinto argumento ( aceleración ) define la función de aceleración que se va a usar en la animación. Como
alternativa, se puede especificar la función de aceleración como argumento para el Animation constructor. Para
obtener más información sobre las funciones de aceleración, consulte funciones de aceleración.
El sexto argumento ( finalizado ) es una devolución de llamada que se ejecutará cuando se complete la
animación. Esta devolución de llamada toma dos argumentos, con el primer argumento que indica un valor
final, y el segundo argumento es un bool que se establece en true si se canceló la animación. Como
alternativa, la devolución de llamada finalizada se puede especificar como argumento para el Animation
constructor. Sin embargo, con una sola animación, si se especifican devoluciones de llamada terminadas tanto
en el Animation constructor como en el Commit método, solo se ejecutará la devolución de llamada
especificada en el Commit método.
El séptimo argumento ( REPEAT ) es una devolución de llamada que permite repetir la animación. Se llama al
final de la animación y true la devolución indica que se debe repetir la animación.

El efecto general es crear una animación que aumente la Scale propiedad de un Image de 1 a 2, más de 2
segundos (2000 milisegundos), mediante la Linear función de aceleración. Cada vez que se completa la
animación, su Scale propiedad se restablece en 1 y la animación se repite.

NOTE
Las animaciones simultáneas, que se ejecutan de forma independiente entre sí, se pueden construir creando un Animation
objeto para cada animación y, a continuación, llamando al Commit método en cada animación.

Animaciones secundarias
La Animation clase también admite animaciones secundarias, lo que implica la creación Animation de un objeto al
que Animation se agregan otros objetos. Esto permite ejecutar y sincronizar una serie de animaciones. En el
ejemplo de código siguiente se muestra cómo crear y ejecutar animaciones secundarias:

var parentAnimation = new Animation ();


var scaleUpAnimation = new Animation (v => image.Scale = v, 1, 2, Easing.SpringIn);
var rotateAnimation = new Animation (v => image.Rotation = v, 0, 360);
var scaleDownAnimation = new Animation (v => image.Scale = v, 2, 1, Easing.SpringOut);

parentAnimation.Add (0, 0.5, scaleUpAnimation);


parentAnimation.Add (0, 1, rotateAnimation);
parentAnimation.Add (0.5, 1, scaleDownAnimation);

parentAnimation.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true,
false));

Como alternativa, el ejemplo de código se puede escribir de forma más concisa, como se muestra en el ejemplo de
código siguiente:

new Animation {
{ 0, 0.5, new Animation (v => image.Scale = v, 1, 2) },
{ 0, 1, new Animation (v => image.Rotation = v, 0, 360) },
{ 0.5, 1, new Animation (v => image.Scale = v, 2, 1) }
}.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));

En ambos ejemplos de código, Animation se crea un objeto primario, al que Animation se agregan los objetos
adicionales. Los dos primeros argumentos para [ Add ] (XREF: :::no-loc(Xamarin.Forms)::: . Animation. Add (System.
Double, System. Double, :::no-loc(Xamarin.Forms)::: . Animation)) especifique Cuándo debe comenzar y finalizar la
animación secundaria. Los valores de argumento deben estar entre 0 y 1, y representan el período relativo dentro
de la animación primaria que la animación secundaria especificada estará activa. Por lo tanto, en este ejemplo,
estará scaleUpAnimation activo para la primera mitad de la animación, estará scaleDownAnimation activo para la
segunda mitad de la animación y el rotateAnimation estará activo durante toda la duración.
El efecto general es que la animación se produce durante 4 segundos (4000 milisegundos). scaleUpAnimation
Anima la Scale propiedad de 1 a 2, en más de 2 segundos. scaleDownAnimation A continuación, anima la Scale
propiedad de 2 a 1, en más de 2 segundos. Mientras se producen ambas animaciones de escala, rotateAnimation
anima la Rotation propiedad de 0 a 360, en 4 segundos. Tenga en cuenta que las animaciones de escalado
también usan funciones de aceleración. La SpringIn función de entradas y salidas lentas hace que Image se
reduzca inicialmente antes de aumentar de tamaño, y la SpringOut función de aceleración hace que se Image
convierta en menor que el tamaño real hacia el final de la animación completa.
Hay una serie de diferencias entre un Animation objeto que utiliza animaciones secundarias y otra que no:
Al usar animaciones secundarias, la devolución de llamada finalizada en una animación secundaria indica
cuándo se ha completado el elemento secundario y la devolución de llamada finalizada pasada al Commit
método indica cuándo se ha completado toda la animación.
Al usar animaciones secundarias, la true devolución de la devolución de llamada de repetición en el Commit
método no hará que se repita la animación, pero la animación continuará ejecutándose sin nuevos valores.
Al incluir una función de aceleración en el Commit método, y la función de aceleración devuelve un valor mayor
que 1, se terminará la animación. Si la función de aceleración devuelve un valor menor que 0, el valor se fija en
0. Para usar una función de entradas y salidas lentas que devuelva un valor menor que 0 o mayor que 1, debe
especificarse en una de las animaciones secundarias, en lugar de en el Commit método.

La Animation clase también incluye [ WithConcurrent ] (XREF: :::no-loc(Xamarin.Forms)::: . Animation.


WithConcurrent ( :::no-loc(Xamarin.Forms)::: . Métodos Animation, System. Double, System. Double)) que se
pueden usar para agregar animaciones secundarias a un Animation objeto primario. Sin embargo, sus valores de
argumento Begin y Finish no están restringidos a 0 a 1, pero solo estarán activos las partes de la animación
secundaria que correspondan a un intervalo de 0 a 1. Por ejemplo, si una WithConcurrent llamada al método
define una animación secundaria que tiene como destino una Scale propiedad de 1 a 6, pero con los valores de
Inicio y fin de-2 y 3, el valor inicial de-2 corresponde a un Scale valor de 1 y el valor de final de 3 corresponde a
un Scale valor de 6. Dado que los valores que están fuera del intervalo de 0 y 1 no juegan ninguna parte en una
animación, la Scale propiedad solo se animará de 3 a 6.

Cancelar una animación


Una aplicación puede cancelar una animación con una llamada a [ AbortAnimation ] (XREF: :::no-
loc(Xamarin.Forms)::: . AnimationExtensions. AbortAnimation ( :::no-loc(Xamarin.Forms)::: . IAnimatable, System.
String)), como se muestra en el ejemplo de código siguiente:
this.AbortAnimation ("SimpleAnimation");

Tenga en cuenta que las animaciones se identifican de forma única mediante una combinación del propietario de
la animación y el nombre de la animación. Por lo tanto, se debe especificar el propietario y el nombre
especificados cuando se ejecuta la animación para cancelar la animación. Por lo tanto, el ejemplo de código
cancelará inmediatamente la animación denominada SimpleAnimation que es propiedad de la página.

Crear una animación personalizada


Los ejemplos que se muestran aquí hasta ahora han demostrado animaciones que se podrían lograr igualmente
con los métodos de la ViewExtensions clase. Sin embargo, la ventaja de la Animation clase es que tiene acceso al
método de devolución de llamada, que se ejecuta cuando cambia el valor animado. Esto permite que la devolución
de llamada implemente cualquier animación deseada. Por ejemplo, en el ejemplo de código siguiente se anima la
BackgroundColor propiedad de una página estableciéndolo en Color valores creados por el Color.FromHsla
método, con valores de matiz comprendidos entre 0 y 1:

new Animation (callback: v => BackgroundColor = Color.FromHsla (v, 1, 0.5),


start: 0,
end: 1).Commit (this, "Animation", 16, 4000, Easing.Linear, (v, c) => BackgroundColor = Color.Default);

La animación resultante proporciona la apariencia de avanzar el fondo de la página a través de los colores del arco
iris.
Para obtener más ejemplos de creación de animaciones complejas, incluida una animación de curva de Bézier, vea
el capítulo 22 de creación de Mobile Apps :::no-loc(Xamarin.Forms)::: con .

Crear un método de extensión de animación personalizado


Los métodos de extensión de la ViewExtensions clase animan una propiedad desde su valor actual hasta un valor
especificado. Esto hace que sea difícil crear, por ejemplo, un ColorTo método de animación que se puede usar
para animar un color de un valor a otro, porque:
La única Color propiedad definida por la VisualElement clase es BackgroundColor , que no siempre es la
Color propiedad deseada que se va a animar.
A menudo, el valor actual de una Color propiedad es Color.Default , que no es un color real, y que no se
puede usar en los cálculos de interpolación.
La solución a este problema es no tener el ColorTo destino del método en una Color propiedad determinada. En
su lugar, se puede escribir con un método de devolución de llamada que devuelve el valor interpolado Color al
llamador. Además, el método tomará los argumentos de inicio y fin Color .
El ColorTométodo se puede implementar como un método de extensión que utiliza el Animate método en la
AnimationExtensions clase para proporcionar su funcionalidad. Esto se debe a que el Animate método se puede
usar para tener como destino propiedades que no son de tipo double , tal y como se muestra en el ejemplo de
código siguiente:
public static class ViewExtensions
{
public static Task<bool> ColorTo(this VisualElement self, Color fromColor, Color toColor, Action<Color>
callback, uint length = 250, Easing easing = null)
{
Func<double, Color> transform = (t) =>
Color.FromRgba(fromColor.R + t * (toColor.R - fromColor.R),
fromColor.G + t * (toColor.G - fromColor.G),
fromColor.B + t * (toColor.B - fromColor.B),
fromColor.A + t * (toColor.A - fromColor.A));
return ColorAnimation(self, "ColorTo", transform, callback, length, easing);
}

public static void CancelAnimation(this VisualElement self)


{
self.AbortAnimation("ColorTo");
}

static Task<bool> ColorAnimation(VisualElement element, string name, Func<double, Color> transform,


Action<Color> callback, uint length, Easing easing)
{
easing = easing ?? Easing.Linear;
var taskCompletionSource = new TaskCompletionSource<bool>();

element.Animate<Color>(name, transform, callback, 16, length, easing, (v, c) =>


taskCompletionSource.SetResult(c));
return taskCompletionSource.Task;
}
}

El Animate método requiere un argumento de transformación , que es un método de devolución de llamada. La


entrada a esta devolución de llamada siempre es un double intervalo de 0 a 1. Por lo tanto, el ColorTo método
define su propia transformación Func que acepta un que double oscila entre 0 y 1, y que devuelve un Color
valor correspondiente a ese valor. El Color valor se calcula interpolando los R valores, G , B y A de los dos
argumentos proporcionados Color . Color Después, el valor se pasa al método de devolución de llamada para la
aplicación a una propiedad determinada.
Este enfoque permite al ColorTo método animar cualquier Color propiedad, tal y como se muestra en el ejemplo
de código siguiente:

await Task.WhenAll(
label.ColorTo(Color.Red, Color.Blue, c => label.TextColor = c, 5000),
label.ColorTo(Color.Blue, Color.Red, c => label.BackgroundColor = c, 5000));
await this.ColorTo(Color.FromRgb(0, 0, 0), Color.FromRgb(255, 255, 255), c => BackgroundColor = c, 5000);
await boxView.ColorTo(Color.Blue, Color.Red, c => boxView.Color = c, 4000);

En este ejemplo de código, el ColorTo método anima las TextColor BackgroundColor propiedades y de un Label
, la BackgroundColor propiedad de una página y la Color propiedad de un BoxView .

Vínculos relacionados
Animaciones personalizadas (ejemplo)
Animation API
API de AnimationExtensions
Xamarin.FormsBrush
18/12/2020 • 2 minutes to read • Edit Online

Un pincel permite pintar un área, como el fondo de un control, mediante distintos enfoques. La compatibilidad
con Xamarin.Forms el pincel en está disponible en el Xamarin.Forms espacio de nombres de iOS, Android, MacOS,
el plataforma universal de Windows (UWP) y el Windows Presentation Foundation (WPF).

IMPORTANT
La compatibilidad con Xamarin.Forms el pincel en es actualmente experimental y solo se puede usar si se establece la
Brush_Experimental marca. Para obtener más información, vea indicadores experimentales.

La Brush clase es una clase abstracta que pinta un área con su salida. Las clases que derivan de Brush describen
diferentes formas de dibujar un área. En la lista siguiente se describen los diferentes tipos de pincel disponibles en
Xamarin.Forms :
SolidColorBrush , que pinta un área con un color sólido. Para obtener más información, vea Xamarin.Forms
pinceles: colores sólidos.
LinearGradientBrush , que pinta un área con un degradado lineal. Para obtener más información, vea
Xamarin.Forms pinceles: degradados lineales.
RadialGradientBrush , que pinta un área con un degradado radial. Para obtener más información, vea
Xamarin.Forms pinceles: degradados radiales.
Las instancias de estos tipos de pincel pueden asignarse a las Stroke Fill propiedades y de un Shape , y a la
Background propiedad de VisualElement .

NOTE
La VisualElement.Background propiedad permite usar pinceles como fondo en cualquier control.

La Brush clase también tiene un IsNullOrEmpty método que devuelve un bool que representa si el pincel está
definido o no.
:::no-loc(Xamarin.Forms)::: Pinceles: colores sólidos
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
La SolidColorBrush clase se deriva de la Brush clase y se utiliza para pintar un área con un color sólido. Hay
varios métodos para especificar el color de un SolidColorBrush . Por ejemplo, puede especificar su color con un
Color valor o mediante uno de los objetos predefinidos SolidColorBrush proporcionados por la Brush clase.

La SolidColorBrush clase define la Color propiedad, de tipo Color , que representa el color del pincel. Esta
propiedad está respaldada por un BindableProperty objeto, lo que significa que puede ser el destino de los enlaces
de datos y con estilo.
La SolidColorBrush clase también tiene un IsEmpty método que devuelve un bool que indica si se ha asignado
un color al pincel.

Crear un SolidColorBrush
Existen tres técnicas principales para crear un SolidColorBrush . Puede crear un a SolidColorBrush partir de un
Color , usar un pincel predefinido o crear un SolidColorBrush con una notación hexadecimal.
Usar un color predefinido
:::no-loc(Xamarin.Forms)::: incluye un convertidor de tipos que crea un a SolidColorBrush partir de un Color valor.
En XAML, esto permite SolidColorBrush crear una a partir de un valor predefinido Color :

<Frame Background="DarkBlue"
BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />

En este ejemplo, el fondo de Frame se pinta con un azul oscuro SolidColorBrush :

Como alternativa, Color se puede especificar el valor mediante la sintaxis de etiqueta de propiedad:
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<SolidColorBrush Color="DarkBlue" />
</Frame.Background>
</Frame>

En este ejemplo, el fondo de Frame se pinta con un SolidColorBrush cuyo color se especifica estableciendo la
SolidColorBrush.Color propiedad.

Usar un pincel predefinido


La Brush clase define un conjunto de objetos de uso común SolidColorBrush . En el ejemplo siguiente se usa uno
de estos objetos predefinidos SolidColorBrush :

<Frame Background="{x:Static Brush.Indigo}"


BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />

El código de C# equivalente es el siguiente:

Frame frame = new Frame


{
Background = Brush.Indigo,
BorderColor = Color.LightGray,
// ...
};

En este ejemplo, el fondo de Frame se pinta con un Indigo SolidColorBrush :

Para obtener una lista de los objetos predefinidos SolidColorBrush proporcionados por la Brush clase, vea
pinceles de color sólido.
Usar notación hexadecimal
SolidColorBrush los objetos también se pueden crear mediante la notación hexadecimal. Con este enfoque, se
especifica un color en términos de la cantidad de rojo, verde y azul que se va a combinar en un solo color. El
formato principal para especificar un color mediante la notación hexadecimal es #rrggbb , donde:
rr es un número hexadecimal de dos dígitos que especifica la cantidad relativa de rojo.
gg es un número hexadecimal de dos dígitos que especifica la cantidad relativa de verde.
bb es un número hexadecimal de dos dígitos que especifica la cantidad relativa de azul.
Además, se puede especificar un color como #aarrggbb donde aa especifica el valor alfa, o transparencia, del
color. Este enfoque le permite crear colores que sean parcialmente transparentes.
En el ejemplo siguiente se establece el valor de color de un SolidColorBrush mediante una notación hexadecimal:

<Frame Background="#FF9988"
BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />

En este ejemplo, el fondo de Frame se pinta con un color de salmón SolidColorBrush :

Para otras formas de describir el color, vea colores en :::no-loc(Xamarin.Forms)::: .

Pinceles de color sólido


Para mayor comodidad, la Brush clase proporciona un conjunto de objetos de uso común SolidColorBrush ,
como AliceBlue y YellowGreen . En la imagen siguiente se muestra el color de cada pincel predefinido, su nombre
y su valor hexadecimal:

Vínculos relacionados
BrushesDemos (ejemplo)
Colores en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Pinceles: degradados
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
La GradientBrush clase se deriva de la Brush clase y es una clase abstracta que describe un degradado, formado
por delimitadores de degradado. Un pincel de degradado pinta un área con varios colores que se mezclan entre sí
a lo largo de un eje. Las clases que derivan de GradientBrush describen diferentes formas de interpretar los
delimitadores de degradado y :::no-loc(Xamarin.Forms)::: proporcionan los siguientes pinceles de degradado:
LinearGradientBrush , que pinta un área con un degradado lineal. Para obtener más información, vea :::no-
loc(Xamarin.Forms)::: pinceles: degradados lineales.
RadialGradientBrush , que pinta un área con un degradado radial. Para obtener más información, vea :::no-
loc(Xamarin.Forms)::: pinceles: degradados radiales.
La GradientBrush clase define la GradientStops propiedad, de tipo GradientStopsCollection , que representa los
delimitadores de degradado del pincel, cada uno de los cuales especifica un color y un desplazamiento a lo largo
del eje de degradado del pincel. Un GradientStopsCollection es ObservableCollection de GradientStop objetos.
La GradientStops propiedad está respaldada por un BindableProperty objeto, lo que significa que puede ser el
destino de los enlaces de datos y con estilo.

NOTE
La GradientStops propiedad es ContentProperty de la GradientBrush clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

Delimitadores de degradado
Los delimitadores de degradado son los bloques de creación de un pincel de degradado y especifican los colores
del degradado y su ubicación a lo largo del eje de degradado. Los delimitadores de degradado se especifican
mediante GradientStop objetos.
La clase GradientStop define las propiedades siguientes:
Color , de tipo Color , que representa el color del delimitador de degradado. El valor predeterminado de esta
propiedad es Color.Default .
Offset , de tipo float , que representa la ubicación del delimitador de degradado dentro del vector de
degradado. El valor predeterminado de esta propiedad es 0 y los valores válidos están en el intervalo 0.0-1.0.
Cuanto más se acerque este valor a 0, más próximo será el color al inicio del degradado. Del mismo modo,
cuanto más se acerque este valor a 1, más cerca será el color hasta el final del degradado.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
IMPORTANT
El sistema de coordenadas utilizado por los degradados es relativo a un cuadro de límite para el área de salida. 0 indica un 0
por ciento del rectángulo de selección, mientras que 1 indica un 100 por cien del rectángulo de selección. Por lo tanto, (0.5,
0.5) describe un punto en el centro del cuadro de límite y (1,1) describe un punto en la parte inferior derecha del cuadro de
límite.

En el ejemplo de XAML siguiente se crea una diagonal LinearGradientBrush con cuatro colores:

<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<GradientStop Color="Yellow"
Offset="0.0" />
<GradientStop Color="Red"
Offset="0.25" />
<GradientStop Color="Blue"
Offset="0.75" />
<GradientStop Color="LimeGreen"
Offset="1.0" />
</LinearGradientBrush>

El color de cada punto entre delimitadores de degradado se interpola como una combinación del color
especificado por los dos delimitadores de degradado de límite. En el diagrama siguiente se muestran los
delimitadores de degradado del ejemplo anterior:

En este diagrama, los círculos marcan la posición de los delimitadores de degradado y la línea discontinua muestra
el eje de degradado. El primer delimitador de degradado especifica el color amarillo en un desplazamiento de 0,0.
El segundo delimitador de degradado especifica el color rojo en un desplazamiento de 0,25. Los puntos entre
estos dos delimitadores de degradado cambian gradualmente de amarillo a rojo a medida que se desplaza de
izquierda a derecha a lo largo del eje de degradado. El tercer delimitador de degradado especifica el color azul en
un desplazamiento de 0,75. Los puntos entre el segundo y el tercer delimitador de degradado cambian
gradualmente de rojo a azul. El cuarto delimitador de degradado especifica el color verde lima en el
desplazamiento de 1,0. Los puntos entre el tercer y el cuarto delimitador de degradado cambian gradualmente de
azul a verde lima.

Vínculos relacionados
BrushesDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Pinceles: degradados lineales
:::no-loc(Xamarin.Forms)::: Pinceles: degradados radiales
:::no-loc(Xamarin.Forms)::: Pinceles: degradados
lineales
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
La LinearGradientBrush clase se deriva de la GradientBrush clase y pinta un área con un degradado lineal, que
combina dos o más colores a lo largo de una línea conocida como el eje de degradado. GradientStop los objetos
se utilizan para especificar los colores del degradado y sus posiciones. Para obtener más información sobre los
GradientStop objetos, consulte :::no-loc(Xamarin.Forms)::: brushes: gradientes.

La clase LinearGradientBrush define las propiedades siguientes:


StartPoint , de tipo Point , que representa las coordenadas bidimensionales iniciales del degradado lineal. El
valor predeterminado de esta propiedad es (0,0).
EndPoint , de tipo Point , que representa las coordenadas bidimensionales finales del degradado lineal. El
valor predeterminado de esta propiedad es (1,1).
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
La LinearGradientBrush clase también como un IsEmpty método que devuelve un bool que representa si se ha
asignado algún objeto al pincel GradientStop .

NOTE
Los degradados lineales también se pueden crear con la linear-gradient() función CSS.

Crear un LinearGradientBrush
Los delimitadores de degradado de un pincel de degradado lineal se colocan en el eje de degradado. La
orientación y el tamaño del eje de degradado se pueden cambiar utilizando las StartPoint propiedades y del
pincel EndPoint . Al manipular estas propiedades, puede crear degradados horizontales, verticales y diagonales,
invertir la dirección del degradado, condensar la distribución del degradado, etc.
Las StartPoint EndPoint propiedades y son relativas al área que se está pintando. (0,0) representa la esquina
superior izquierda del área que se está dibujando y (1,1) representa la esquina inferior derecha del área que se
está pintando. En el diagrama siguiente se muestra el eje de degradado de un pincel de degradado lineal diagonal:
En este diagrama, la línea discontinua muestra el eje de degradado, que resalta la ruta de interpolación del
degradado desde el punto inicial hasta el punto final.
Crear un degradado lineal horizontal
Para crear un degradado lineal horizontal, cree un LinearGradientBrush objeto y establezca su StartPoint en
(0,0) y su EndPoint en (1,0). A continuación, agregue dos o más GradientStop objetos a la
LinearGradientBrush.GradientStops colección, que especifican los colores del degradado y sus posiciones.

En el siguiente ejemplo de XAML se muestra un LinearGradientBrush valor horizontal que se establece como
Background de un Frame :

<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<!-- StartPoint defaults to (0,0) -->
<LinearGradientBrush EndPoint="1,0">
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>

En este ejemplo, el fondo de Frame se pinta con un LinearGradientBrush que se interpola de amarillo a verde
horizontal:

Crear un degradado lineal vertical


Para crear un degradado lineal vertical, cree un LinearGradientBrush objeto y establezca su StartPoint en (0,0) y
su EndPoint en (0, 1). A continuación, agregue dos o más GradientStop objetos a la
LinearGradientBrush.GradientStops colección, que especifican los colores del degradado y sus posiciones.

En el siguiente ejemplo de XAML se muestra un LinearGradientBrush valor vertical que se establece como
Background de un Frame :
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<!-- StartPoint defaults to (0,0) -->
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>

En este ejemplo, el fondo de Frame se pinta con un LinearGradientBrush que se interpola de amarillo a verde
verticalmente:

Crear un degradado lineal diagonal


Para crear un degradado lineal diagonal, cree un LinearGradientBrush objeto y establezca su StartPoint en (0,0)
y su EndPoint en (1,1). A continuación, agregue dos o más GradientStop objetos a la
LinearGradientBrush.GradientStops colección, que especifican los colores del degradado y sus posiciones.

En el siguiente ejemplo de XAML se muestra una diagonal LinearGradientBrush que se establece como la
Background de un Frame :

<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<!-- StartPoint defaults to (0,0)
Endpoint defaults to (1,1) -->
<LinearGradientBrush>
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>

En este ejemplo, el fondo de Frame se pinta con un LinearGradientBrush que se interpola de amarillo a verde en
diagonal:
Vínculos relacionados
BrushesDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Pinceles: degradados
:::no-loc(Xamarin.Forms)::: Pinceles: degradados
radiales
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
La RadialGradientBrush clase se deriva de la GradientBrush clase y pinta un área con un degradado radial, que
combina dos o más colores en un círculo. GradientStop los objetos se utilizan para especificar los colores del
degradado y sus posiciones. Para obtener más información sobre los GradientStop objetos, consulte :::no-
loc(Xamarin.Forms)::: brushes: gradientes.
La clase RadialGradientBrush define las propiedades siguientes:
Center , de tipo Point , que representa el punto central del círculo del degradado radial. El valor
predeterminado de esta propiedad es (0.5, 0.5).
Radius , de tipo double , que representa el radio del círculo del degradado radial. El valor predeterminado de
esta propiedad es 0,5.
Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos de
los enlaces de datos, y con estilo.
La RadialGradientBrush clase también tiene un IsEmpty método que devuelve un bool que indica si se ha
asignado algún objeto al pincel GradientStop .

NOTE
También se pueden crear degradados radiales con la radial-gradient() función CSS.

Crear un RadialGradientBrush
Los delimitadores de degradado del pincel de degradado radial se colocan a lo largo de un eje de degradado
definido por un círculo. El eje de degradado irradia desde el centro del círculo hasta su circunferencia. La posición
y el tamaño del círculo se pueden cambiar utilizando las Center propiedades y del pincel Radius . El círculo
define el punto final del degradado. Por lo tanto, un delimitador de degradado en 1,0 define el color de la
circunferencia del círculo. Un delimitador de degradado en 0,0 define el color en el centro del círculo.
Para crear un degradado radial, cree un RadialGradientBrush objeto y establezca Center sus Radius propiedades
y. A continuación, agregue dos o más GradientStop objetos a la RadialGradientBrush.GradientStops colección, que
especifican los colores del degradado y sus posiciones.
En el siguiente ejemplo de XAML se muestra un RadialGradientBrush que se establece como la Background de un
Frame :
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<!-- Center defaults to (0.5,0.5)
Radius defaults to (0.5) -->
<RadialGradientBrush>
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>
</Frame.Background>
</Frame>

En este ejemplo, el fondo de Frame se pinta con un RadialGradientBrush que se interpola de rojo a azul oscuro. El
centro del degradado radial se coloca en el centro del Frame :

En el siguiente ejemplo de XAML se mueve el centro del degradado radial a la esquina superior izquierda del
Frame :

<!-- Radius defaults to (0.5) -->


<RadialGradientBrush Center="0.0,0.0">
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>

En este ejemplo, el fondo de Frame se pinta con un RadialGradientBrush que se interpola de rojo a azul oscuro. El
centro del degradado radial se coloca en la parte superior izquierda del Frame :

En el siguiente ejemplo de XAML se mueve el centro del degradado radial a la esquina inferior derecha de Frame :
<!-- Radius defaults to (0.5) -->
<RadialGradientBrush Center="1.0,1.0">
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>

En este ejemplo, el fondo de Frame se pinta con un RadialGradientBrush que se interpola de rojo a azul oscuro. El
centro del degradado radial se coloca en la parte inferior derecha del Frame :

Vínculos relacionados
BrushesDemos (ejemplo)
:::no-loc(Xamarin.Forms)::: Pinceles: degradados
Colores en :::no-loc(Xamarin.Forms):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: proporciona una clase de color flexible multiplataforma.
En este artículo se presentan las distintas formas Color en que se puede usar la clase en :::no-
loc(Xamarin.Forms)::: .
La Color clase proporciona una serie de métodos para compilar una Color instancia de:
Colores con nombre : una colección de colores con nombre comunes, incluidos Red , Green y Blue .
FromHex -valor de cadena similar a la sintaxis usada en HTML, por ejemplo "00FF00". Opcionalmente, se
puede especificar alfa como el primer par de caracteres ("CC00FF00").
FromHsla -Valores de matiz, saturación y luminosidad double , con valor alfa opcional (0,0-1,0).
FromHsv : Matiz, saturación, valor int o double valores.
FromHsva : Matiz, saturación, valor int o double valores.
FromRgb -Valores rojo, verde y azul int (0-255).
FromRgba -Valores rojo, verde, azul y alfa int (0-255).
FromUint : establezca un double valor único que represente ARGB .

A continuación se indican algunos colores de ejemplo asignados a la BackgroundColor de algunas etiquetas con
diferentes variaciones de la sintaxis permitida:

var red = new Label { Text = "Red", BackgroundColor = Color.Red };


var orange = new Label { Text = "Orange",BackgroundColor = Color.FromHex("FF6A00") };
var yellow = new Label { Text = "Yellow",BackgroundColor = Color.FromHsla(0.167, 1.0, 0.5, 1.0) };
var green = new Label { Text = "Green", BackgroundColor = Color.FromRgb (38, 127, 0) };
var blue = new Label { Text = "Blue", BackgroundColor = Color.FromRgba(0, 38, 255, 255) };
var indigo = new Label { Text = "Indigo",BackgroundColor = Color.FromRgb (0, 72, 255) };
var violet = new Label { Text = "Violet",BackgroundColor = Color.FromHsla(0.82, 1, 0.25, 1) };

var transparent = new Label { Text = "Transparent",BackgroundColor = Color.Transparent };


var @default = new Label { Text = "Default", BackgroundColor = Color.Default };
var accent = new Label { Text = "Accent", BackgroundColor = Color.Accent };

Estos colores se muestran en cada plataforma siguiente. Observe el color final: Accent es un color azul ish para
iOS y Android; este valor lo define :::no-loc(Xamarin.Forms)::: .
Color. default
Utilice Default para establecer (o volver a establecer) un valor de color de nuevo en el valor predeterminado de
la plataforma (sabiendo que esto representa un color subyacente diferente en cada plataforma para cada
propiedad).
Los desarrolladores pueden usar este valor para establecer una Color propiedad, pero no deben consultar a
esta instancia los valores RGB de sus componentes (todos están establecidos en-1).

Color. Transparent
Establezca el color en borrar.

Color. acento
En iOS y Android, esta instancia se establece en un color de contraste que está visible en el fondo
predeterminado pero no es el mismo que el color de texto predeterminado.

Métodos adicionales
Color las instancias de incluyen los siguientes métodos adicionales:
AddLuminosity : devuelve un Color modificador la luminosidad por el Delta proporcionado.
MultiplyAlpha : devuelve una Color modificando el alfa, multiplicándolo por el valor alfa proporcionado.
ToHex : devuelve una string representación hexadecimal de un Color .
WithHue : devuelve Color , reemplazando el matiz por el valor proporcionado.
WithLuminosity : devuelve Color , reemplazando la luminosidad por el valor proporcionado.
WithSaturation : devuelve Color , reemplazando la saturación por el valor proporcionado.

Conversiones implícitas
Se puede realizar la conversión implícita entre los :::no-loc(Xamarin.Forms):::.Color System.Drawing.Color
tipos y:
:::no-loc(Xamarin.Forms):::.Color xfColor = :::no-loc(Xamarin.Forms):::.Color.FromRgb(0, 72, 255);
System.Drawing.Color sdColor = System.Drawing.Color.FromArgb(38, 127, 0);

// Implicity convert from a :::no-loc(Xamarin.Forms):::.Color to a System.Drawing.Color


System.Drawing.Color sdColor2 = xfColor;

// Implicitly convert from a System.Drawing.Color to a :::no-loc(Xamarin.Forms):::.Color


:::no-loc(Xamarin.Forms):::.Color xfColor2 = sdColor;

Device. RuntimePlatform
Este fragmento de código usa la Device.RuntimePlatform propiedad para establecer de forma selectiva el color de
un ActivityIndicator :

ActivityIndicator activityIndicator = new ActivityIndicator


{
Color = Device.RuntimePlatform == Device.iOS ? Color.Black : Color.Default,
IsRunning = true
};

Usar desde XAML


También se puede hacer referencia a los colores en XAML mediante los nombres de colores definidos o las
representaciones hexadecimales que se muestran aquí:

<Label Text="Sea color" BackgroundColor="Aqua" />


<Label Text="RGB" BackgroundColor="#00FF00" />
<Label Text="Alpha plus RGB" BackgroundColor="#CC00FF00" />
<Label Text="Tiny RGB" BackgroundColor="#0F0" />
<Label Text="Tiny Alpha plus RGB" BackgroundColor="#C0F0" />

NOTE
Cuando se usa la compilación XAML, los nombres de color no distinguen mayúsculas de minúsculas y, por tanto, pueden
escribirse en minúsculas. Para obtener más información acerca de la compilación XAML, consulte XAML Compilation
(Compilación XAML).

Vínculos relacionados
ColorsSample
Selector enlazable (ejemplo)
Xamarin.FormsPáginas de página
18/12/2020 • 4 minutes to read • Edit Online

IMPORTANT
Las páginas de página requieren una Xamarin.Forms referencia de tema que se va a representar. Esto implica la instalación de
Xamarin.Forms . Paquete NuGet Theme. base en el proyecto, seguido de Xamarin.Forms . Theme. Light o Xamarin.Forms .
Paquetes NuGet Theme. Dark .

Xamarin.FormsLas páginas de usuarios se anunciaron a evolucionar 2016 y están disponibles como vista previa
para que los clientes intenten enviar comentarios.
Las páginas de datos proporcionan una API para enlazar de forma rápida y sencilla un origen de datos a vistas
precompiladas. Los elementos de lista y las páginas de detalles representarán los datos automáticamente y se
pueden personalizar con temas.
Para ver cómo funciona la demo de la ponencia de evolución, consulte la Guía de introducción.

Introducción
Los orígenes de datos y las páginas de datos asociadas permiten a los desarrolladores consumir de forma rápida y
sencilla un origen de datos compatible y representarlo mediante la técnica de la interfaz de usuario integrada que
se puede personalizar con los temas.
Las páginas de página se agregan a una Xamarin.Forms aplicación incluyendo el ** Xamarin.Forms . **Paquete
NuGet de páginas.
Orígenes de datos
La vista previa tiene algunos orígenes de datos predefinidos disponibles para su uso:
JsonDataSource
AzureDataSource (NuGet independiente)
AzureEasyTableDataSource (NuGet independiente)
Vea la Guía de introducción para obtener un ejemplo de uso de JsonDataSource .
Páginas & controles
Se incluyen las siguientes páginas y controles para permitir un enlace sencillo a los orígenes de datos
proporcionados:
ListDataPage : vea el ejemplo de introducción.
Director yPage : una lista con la agrupación habilitada.
PersonDetailPage : una sola vista de elemento de datos personalizada para un tipo de objeto específico (una
entrada de contacto).
DataView : una vista para exponer los datos del origen de forma genérica.
CardView : vista con estilo que contiene una imagen, texto de título y texto de descripción.
HeroImage : vista de representación de imágenes.
ListItem : vista pregenerada con un diseño similar a los elementos de lista nativos de iOS y Android.
Vea la referencia de controles de páginas de página para obtener ejemplos.
Una mirada al interior
Un Xamarin.Forms origen de datos se adhiere a la IDataSource interfaz.
La Xamarin.Forms infraestructura interactúa con un origen de datos a través de las siguientes propiedades:
Data : una lista de solo lectura de los elementos de datos que se pueden mostrar.
IsLoading : un valor booleano que indica si los datos se cargan y están disponibles para su representación.
[key] : un indexador para recuperar elementos.

Hay dos métodos MaskKey UnmaskKey que se pueden usar para ocultar (o mostrar) las propiedades de los
elementos de datos (es decir, impedir que se representen). La clave corresponde a una propiedad con nombre en el
objeto de elemento de datos.
Introducción con páginas de páginas
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo

IMPORTANT
Las páginas de página requieren una Xamarin.Forms referencia de tema que se va a representar. Esto implica la instalación
de Xamarin.Forms . Paquete NuGet Theme. base en el proyecto, seguido de Xamarin.Forms . Theme. Light o Xamarin.Forms
. Paquetes NuGet Theme. Dark .

Para empezar a crear una página simple controlada por datos mediante la vista previa de las páginas de datos,
siga los pasos que se indican a continuación. En esta demostración se usa un estilo codificado ("Events") en las
compilaciones de vista previa que solo funciona con el formato JSON específico en el código.

1. agregar paquetes NuGet


Agregue estos paquetes de NuGet a la Xamarin.Forms biblioteca de .net Standard y los proyectos de aplicación:
Xamarin.Forms. Páginas
Xamarin.Forms. Theme. base
Un NuGet de implementación de tema (p. ej., Xamarin.Forms. Theme. Light)
2. Agregar referencia de tema
En el archivo app. Xaml , agregue un personalizado xmlns:mytheme para el tema y asegúrese de que el tema se
combina en el Diccionario de recursos de la aplicación:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mytheme="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Light"
x:Class="DataPagesDemo.App">
<Application.Resources>
<ResourceDictionary MergedWith="mytheme:LightThemeResources" />
</Application.Resources>
</Application>

IMPORTANT
También debe seguir los pasos para cargar ensamblados de tema (a continuación) agregando código reutilizable a iOS
AppDelegate y Android MainActivity . Esto se mejorará en una versión preliminar futura.

3. agregar una página XAML


Agregue una nueva página XAML a la Xamarin.Forms aplicación y cambie la clase base de ContentPage a
Xamarin.Forms.Pages.ListDataPage . Esto se debe hacer en C# y en el código XAML:

Archivo de C#

public partial class SessionDataPage : Xamarin.Forms.Pages.ListDataPage // was ContentPage


{
public SessionDataPage ()
{
InitializeComponent ();
}
}

Archivo XAML
Además de cambiar el elemento raíz al <p:ListDataPage> espacio de nombres personalizado para xmlns:p
también se debe agregar:

<?xml version="1.0" encoding="UTF-8"?>


<p:ListDataPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:p="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"
x:Class="DataPagesDemo.SessionDataPage">

<ContentPage.Content></ContentPage.Content>

</p:ListDataPage>

Subclase de aplicación
Cambie el App constructor de clase para que MainPage se establezca en un valor NavigationPage que contenga
el nuevo SessionDataPage . Se debe usar una página de navegación.

MainPage = new NavigationPage (new SessionDataPage ());


3. agregar el origen de los
Elimine el Content elemento y reemplácelo por un p:ListDataPage.DataSource para rellenar la página con datos.
En el ejemplo siguiente se carga un archivo de datos JSON remoto desde una dirección URL.

NOTE
La vista previa requiere un StyleClass atributo para proporcionar sugerencias de representación para el origen de datos.
El StyleClass="Events" hace referencia a un diseño que está predefinido en la vista previa y contiene estilos codificados
para coincidir con el origen de datos JSON que se está usando.

<?xml version="1.0" encoding="UTF-8"?>


<p:ListDataPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:p="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"
x:Class="DataPagesDemo.SessionDataPage"
Title="Sessions" StyleClass="Events">

<p:ListDataPage.DataSource>
<p:JsonDataSource Source="http://demo3143189.mockable.io/sessions" />
</p:ListDataPage.DataSource>

</p:ListDataPage>

Datos de JSON
A continuación se muestra un ejemplo de los datos JSON del origen de la demostración:

[{
"end": "2016-04-27T18:00:00Z",
"start": "2016-04-27T17:15:00Z",
"abstract": "The new Apple TV has been released, and YOU can be one of the first developers to write apps
for it. To make things even better, you can build these apps in C#! This session will introduce the basics of
how to create a tvOS app with Xamarin, including: differences between tvOS and iOS APIs, TV user interface
best practices, responding to user input, as well as the capabilities and limitations of building apps for a
television. Grab some popcorn—this is going to be good!",
"title": "As Seen On TV … Bringing C# to the Living Room",
"presenter": "Matthew Soucoup",
"biography": "Matthew is a Xamarin MVP and Certified Xamarin Developer from Madison, WI. He founded his
company Code Mill Technologies and started the Madison Mobile .Net Developers Group. Matt regularly speaks
on .Net and Xamarin development at user groups, code camps and conferences throughout the Midwest. Matt
gardens hot peppers, rides bikes, and loves Wisconsin micro-brews and cheese.",
"image": "http://i.imgur.com/ASj60DP.jpg",
"avatar": "http://i.imgur.com/ASj60DP.jpg",
"room": "Crick"
}]

4. ejecutar
Los pasos anteriores deben dar lugar a una página de datos de trabajo:
Esto funciona porque el estilo predefinido "eventos" existe en el paquete NuGet de tema claro y tiene estilos
definidos que coinciden con el origen de datos (por ejemplo, "title", "Image", "Presenter").
Los "eventos" StyleClass se compilan para mostrar el ListDataPage control con un CardView control
personalizado que se define en Xamarin.Forms . Páginas. El CardView control tiene tres propiedades: ImageSource
, Text y Detail . El tema está codificado de forma rígida para enlazar los tres campos de DataSource (del
archivo JSON) a estas propiedades para su presentación.

5. personalizar
El estilo heredado puede invalidarse mediante la especificación de una plantilla y el uso de enlaces de origen de
datos. El código XAML siguiente declara una plantilla personalizada para cada fila utilizando la nueva
ListItemControl {p:DataSourceBinding} sintaxis y que se incluye en ** Xamarin.Forms . Páginas** NuGet:

<p:ListDataPage.DefaultItemTemplate>
<DataTemplate>
<ViewCell>
<p:ListItemControl
Title="{p:DataSourceBinding title}"
Detail="{p:DataSourceBinding room}"
ImageSource="{p:DataSourceBinding image}"
DataSource="{Binding Value}"
HeightRequest="90"
>
</p:ListItemControl>
</ViewCell>
</DataTemplate>
</p:ListDataPage.DefaultItemTemplate>

Al proporcionar un DataTemplate código, se invalida el StyleClass y, en su lugar, se usa el diseño


predeterminado para ListItemControl .
Los desarrolladores que prefieren C# a XAML pueden crear enlaces de origen de datos también (Recuerde incluir
una using Xamarin.Forms.Pages; instrucción):

SetBinding (TitleProperty, new DataSourceBinding ("title"));

Es un poco más trabajo crear temas desde cero, pero las versiones preliminares futuras harán que esto sea más
fácil.

Solución de problemas
No se pudo cargar el archivo o ensamblado ' Xamarin.Forms . Theme.
Light ' o una de sus dependencias
En la versión preliminar, es posible que los temas no se puedan cargar en tiempo de ejecución. Agregue el código
que se muestra a continuación en los proyectos pertinentes para corregir este error.
iOS
En AppDelegate.CS , agregue las siguientes líneas después de LoadApplication

var x = typeof(Xamarin.Forms.Themes.DarkThemeResources);
x = typeof(Xamarin.Forms.Themes.LightThemeResources);
x = typeof(Xamarin.Forms.Themes.iOS.UnderlineEffect);

Android
En MainActivity.CS , agregue las siguientes líneas después de LoadApplication
var x = typeof(Xamarin.Forms.Themes.DarkThemeResources);
x = typeof(Xamarin.Forms.Themes.LightThemeResources);
x = typeof(Xamarin.Forms.Themes.Android.UnderlineEffect);

Vínculos relacionados
Ejemplo de DataPagesDemo
Referencia de controles de páginas de página
18/12/2020 • 7 minutes to read • Edit Online

IMPORTANT
Las páginas de página requieren una Xamarin.Forms referencia de tema que se va a representar. Esto implica la instalación de
Xamarin.Forms . Paquete NuGet Theme. base en el proyecto, seguido de Xamarin.Forms . Theme. Light o Xamarin.Forms .
Paquetes NuGet Theme. Dark .

Xamarin.FormsNuGet de páginas de datos incluye una serie de controles que pueden aprovechar el enlace de
origen de datos.
Para usar estos controles en XAML, asegúrese de que se ha incluido el espacio de nombres, por ejemplo, vea la
xmlns:pages declaración siguiente:

<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"
x:Class="DataPagesDemo.Detail">

En los ejemplos siguientes DynamicResource se incluyen referencias que deben existir en el Diccionario de recursos
del proyecto para que funcione. También hay un ejemplo de cómo crear un control personalizado.

Controles integrados
HeroImage
ListItem
HeroImage
El HeroImage control tiene cuatro propiedades:
Texto
Detalle
ImageSource
Aspecto

<pages:HeroImage
ImageSource="{ DynamicResource HeroImageImage }"
Text="Keith Ballinger"
Detail="Xamarin"
/>

Android
iOS

ListItem
El ListItem diseño del control es similar a las filas nativas de la tabla o la lista de iOS y Android, pero también se
puede usar como vista normal. En el código de ejemplo siguiente, se muestra hospedado en StackLayout , pero
también se puede usar en controles de lista de scolling enlazados a datos.
Hay cinco propiedades:
Title
Detalle
ImageSource
PlaceholdImageSource
Aspecto

<StackLayout Spacing="0">
<pages:ListItemControl
Detail="Xamarin"
ImageSource="{ DynamicResource UserImage }"
Title="Miguel de Icaza"
PlaceholdImageSource="{ DynamicResource IconImage }"
/>

Estas capturas de pantallas muestran ListItem en las plataformas iOS y Android mediante los temas claro y
oscuro:
Android

iOS

Ejemplo de control personalizado


El objetivo de este CardView control personalizado es similar al CardView nativo de Android.
Contendrá tres propiedades:
Texto
Detalle
ImageSource
El objetivo es un control personalizado que se parecerá al código siguiente (tenga en cuenta que xmlns:local se
requiere un personalizado que haga referencia al ensamblado actual):

<local:CardView
ImageSource="{ DynamicResource CardViewImage }"
Text="CardView Text"
Detail="CardView Detail"
/>

Debe ser similar a las capturas de pantallas siguientes con los colores correspondientes a los temas de luz y
oscuridad integrados:
Android

iOS

Compilar el CardView personalizado


1. DataView (subclase)
2. Definir fuente, diseño y márgenes
3. Crear estilos para los elementos secundarios del control
4. Crear la plantilla de diseño de control
5. Agregar los recursos específicos del tema
6. Establecer ControlTemplate para la clase CardView
7. Agregar el control a una página
1. subclase DataView
La subclase de C# de DataView define las propiedades enlazables del control.
public class CardView : DataView
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create ("Text", typeof (string), typeof (CardView), null, BindingMode.TwoWay);

public string Text


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

public static readonly BindableProperty DetailProperty =


BindableProperty.Create ("Detail", typeof (string), typeof (CardView), null, BindingMode.TwoWay);

public string Detail


{
get { return (string)GetValue (DetailProperty); }
set { SetValue (DetailProperty, value); }
}

public static readonly BindableProperty ImageSourceProperty =


BindableProperty.Create ("ImageSource", typeof (ImageSource), typeof (CardView), null,
BindingMode.TwoWay);

public ImageSource ImageSource


{
get { return (ImageSource)GetValue (ImageSourceProperty); }
set { SetValue (ImageSourceProperty, value); }
}

public CardView()
{
}
}

2. definir la fuente, el diseño y los márgenes


El diseñador de controles descifraría estos valores como parte del diseño de la interfaz de usuario para el control
personalizado. Cuando se requieren especificaciones específicas de la plataforma, OnPlatform se usa el elemento.
Tenga en cuenta que algunos valores hacen referencia a StaticResource s: se definirán en el paso 5.
<!-- CARDVIEW FONT SIZES -->
<OnPlatform x:TypeArguments="x:Double" x:Key="CardViewTextFontSize">
<On Platform="iOS, Android" Value="15" />
</OnPlatform>

<OnPlatform x:TypeArguments="x:Double" x:Key="CardViewDetailFontSize">


<On Platform="iOS, Android" Value="13" />
</OnPlatform>

<OnPlatform x:TypeArguments="Color" x:Key="CardViewTextTextColor">


<On Platform="iOS" Value="{StaticResource iOSCardViewTextTextColor}" />
<On Platform="Android" Value="{StaticResource AndroidCardViewTextTextColor}" />
</OnPlatform>

<OnPlatform x:TypeArguments="Thickness" x:Key="CardViewTextlMargin">


<On Platform="iOS" Value="12,10,12,4" />
<On Platform="Android" Value="20,0,20,5" />
</OnPlatform>

<OnPlatform x:TypeArguments="Color" x:Key="CardViewDetailTextColor">


<On Platform="iOS" Value="{StaticResource iOSCardViewDetailTextColor}" />
<On Platform="Android" Value="{StaticResource AndroidCardViewDetailTextColor}" />
</OnPlatform>

<OnPlatform x:TypeArguments="Thickness" x:Key="CardViewDetailMargin">


<On Platform="iOS" Value="12,0,10,12" />
<On Platform="Android" Value="20,0,20,20" />
</OnPlatform>

<OnPlatform x:TypeArguments="Color" x:Key="CardViewBackgroundColor">


<On Platform="iOS" Value="{StaticResource iOSCardViewBackgroundColor}" />
<On Platform="Android" Value="{StaticResource AndroidCardViewBackgroundColor}" />
</OnPlatform>

<OnPlatform x:TypeArguments="x:Double" x:Key="CardViewShadowSize">


<On Platform="iOS" Value="2" />
<On Platform="Android" Value="5" />
</OnPlatform>

<OnPlatform x:TypeArguments="x:Double" x:Key="CardViewCornerRadius">


<On Platform="iOS" Value="0" />
<On Platform="Android" Value="4" />
</OnPlatform>

<OnPlatform x:TypeArguments="Color" x:Key="CardViewShadowColor">


<On Platform="iOS, Android" Value="#CDCDD1" />
</OnPlatform>

3. crear estilos para los elementos secundarios del control


Haga referencia a todos los elementos definidos sobre para crear los elementos secundarios que se usarán en el
control personalizado:
<!-- EXPLICIT STYLES (will be Classes) -->
<Style TargetType="Label" x:Key="CardViewTextStyle">
<Setter Property="FontSize" Value="{ StaticResource CardViewTextFontSize }" />
<Setter Property="TextColor" Value="{ StaticResource CardViewTextTextColor }" />
<Setter Property="HorizontalOptions" Value="Start" />
<Setter Property="Margin" Value="{ StaticResource CardViewTextlMargin }" />
<Setter Property="HorizontalTextAlignment" Value="Start" />
</Style>

<Style TargetType="Label" x:Key="CardViewDetailStyle">


<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="TextColor" Value="{ StaticResource CardViewDetailTextColor }" />
<Setter Property="FontSize" Value="{ StaticResource CardViewDetailFontSize }" />
<Setter Property="HorizontalOptions" Value="Start" />
<Setter Property="Margin" Value="{ StaticResource CardViewDetailMargin }" />
</Style>

<Style TargetType="Image" x:Key="CardViewImageImageStyle">


<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="WidthRequest" Value="220"/>
<Setter Property="HeightRequest" Value="165"/>
</Style>

4. crear la plantilla de diseño de control


El diseño visual del control personalizado se declara explícitamente en la plantilla de control mediante los recursos
definidos anteriormente:

<!--- CARDVIEW -->


<ControlTemplate x:Key="CardViewControlControlTemplate">
<StackLayout
Spacing="0"
BackgroundColor="{ TemplateBinding BackgroundColor }"
>

<!-- CARDVIEW IMAGE -->


<Image
Source="{ TemplateBinding ImageSource }"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand"
Aspect="AspectFill"
Style="{ StaticResource CardViewImageImageStyle }"
/>

<!-- CARDVIEW TEXT -->


<Label
Text="{ TemplateBinding Text }"
LineBreakMode="WordWrap"
VerticalOptions="End"
Style="{ StaticResource CardViewTextStyle }"
/>

<!-- CARDVIEW DETAIL -->


<Label
Text="{ TemplateBinding Detail }"
LineBreakMode="WordWrap"
VerticalOptions="End"
Style="{ StaticResource CardViewDetailStyle }" />

</StackLayout>

</ControlTemplate>

5. agregar los recursos específicos del tema


Dado que se trata de un control personalizado, agregue los recursos que coinciden con el tema que usa el
Diccionario de recursos:
Co l o r es de t em a c l ar o

<Color x:Key="iOSCardViewBackgroundColor">#FFFFFF</Color>
<Color x:Key="AndroidCardViewBackgroundColor">#FFFFFF</Color>

<Color x:Key="AndroidCardViewTextTextColor">#030303</Color>
<Color x:Key="iOSCardViewTextTextColor">#030303</Color>

<Color x:Key="AndroidCardViewDetailTextColor">#8F8E94</Color>
<Color x:Key="iOSCardViewDetailTextColor">#8F8E94</Color>

C o l o r e s d e l t e m a o sc u r o

<!-- CARD VIEW COLORS -->


<Color x:Key="iOSCardViewBackgroundColor">#404040</Color>
<Color x:Key="AndroidCardViewBackgroundColor">#404040</Color>

<Color x:Key="AndroidCardViewTextTextColor">#FFFFFF</Color>
<Color x:Key="iOSCardViewTextTextColor">#FFFFFF</Color>

<Color x:Key="AndroidCardViewDetailTextColor">#B5B4B9</Color>
<Color x:Key="iOSCardViewDetailTextColor">#B5B4B9</Color>

6. establecer ControlTemplate para la clase CardView


Por último, asegúrese de que la clase de C# creada en el paso 1 usa la plantilla de control definida en el paso 4
mediante un Style Setter elemento.

<Style TargetType="local:CardView">
<Setter Property="ControlTemplate" Value="{ StaticResource CardViewControlControlTemplate }" />
... some custom styling omitted
<Setter Property="BackgroundColor" Value="{ StaticResource CardViewBackgroundColor }" />
</Style>

7. agregar el control a una página


CardViewAhora se puede Agregar el control a una página. En el ejemplo siguiente se muestra que se hospeda en
un StackLayout :

<StackLayout Spacing="0">
<local:CardView
Margin="12,6"
ImageSource="{ DynamicResource CardViewImage }"
Text="CardView Text"
Detail="CardView Detail"
/>
</StackLayout>
Mostrar elementos emergentes
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Mostrar una alerta, pedir al usuario que elija una opción o mostrar un mensaje es una tarea común de la interfaz de
usuario. Xamarin.Forms tiene tres métodos en la Page clase para interactuar con el usuario a través de un
elemento emergente: DisplayAlert , DisplayActionSheet y DisplayPromptAsync . Se representan con controles
nativos adecuados en cada plataforma.

Visualización de una alerta


Todas las Xamarin.Forms plataformas compatibles con son un elemento emergente modal para avisar al usuario o
realizar preguntas sencillas. Para mostrar estas alertas en Xamarin.Forms , utilice el DisplayAlert método en any
Page . En la siguiente línea de código, se muestra un mensaje sencillo al usuario:

await DisplayAlert ("Alert", "You have been alerted", "OK");

En este ejemplo, no se recopila información del usuario. La alerta se muestra modalmente y, después de cerrarla, el
usuario sigue interactuando con la aplicación.
El DisplayAlert método también se puede utilizar para capturar la respuesta de un usuario presentando dos
botones y devolviendo un boolean . Para obtener una respuesta de una alerta, agregue texto para los dos botones
y use await para esperar el método. Cuando el usuario seleccione una de las opciones, se devolverá la respuesta al
código. Observe las palabras clave async y await en el código de ejemplo siguiente:

async void OnAlertYesNoClicked (object sender, EventArgs e)


{
bool answer = await DisplayAlert ("Question?", "Would you like to play a game", "Yes", "No");
Debug.WriteLine ("Answer: " + answer);
}
WARNING
De forma predeterminada, en UWP, cuando se muestra una alerta, cualquier clave de acceso que se haya definido en la
página detrás de la alerta todavía se puede activar. Para obtener más información, consulte VisualElement Access Keys in
Windows.

Guiar a los usuarios a través de tareas


El elemento UIActionSheet es un elemento de interfaz de usuario común en iOS. El Xamarin.Forms
DisplayActionSheet método permite incluir este control en aplicaciones multiplataforma y representar alternativas
nativas en Android y UWP.
Para mostrar una hoja de acción, use await DisplayActionSheet en cualquier elemento Page y pase el mensaje y
las etiquetas de botón como cadenas. Este método devuelve la etiqueta de cadena del botón en el que hizo clic el
usuario. A continuación, se muestra un ejemplo sencillo:

async void OnActionSheetSimpleClicked (object sender, EventArgs e)


{
string action = await DisplayActionSheet ("ActionSheet: Send to?", "Cancel", null, "Email", "Twitter",
"Facebook");
Debug.WriteLine ("Action: " + action);
}

El botón destroy se representa de forma distinta y puede dejarse como null , o bien se puede especificar como el
tercer parámetro de cadena. En el ejemplo siguiente, se usa el botón destroy :
async void OnActionSheetCancelDeleteClicked (object sender, EventArgs e)
{
string action = await DisplayActionSheet ("ActionSheet: SavePhoto?", "Cancel", "Delete", "Photo Roll",
"Email");
Debug.WriteLine ("Action: " + action);
}

mostrar un mensaje
Para mostrar un símbolo del sistema, llame a DisplayPromptAsync en cualquier Page , pasando un título y un
mensaje como string argumentos:

string result = await DisplayPromptAsync("Question 1", "What's your name?");

El mensaje se muestra de forma modal:

Si se puntea el botón Aceptar, la respuesta especificada se devuelve como un string . Si se puntea el botón
Cancelar, null se devuelve.
La lista de argumentos completa para el DisplayPromptAsync método es:
title , de tipo string , es el título que se va a mostrar en el símbolo del sistema.
message , de tipo string , es el mensaje que se va a mostrar en el símbolo del sistema.
accept , de tipo string , es el texto del botón Aceptar. Este argumento es opcional, cuyo valor predeterminado
es OK.
cancel , de tipo string , es el texto del botón Cancelar. Es un argumento opcional, cuyo valor predeterminado
es cancelar.
placeholder , de tipo string , es el texto del marcador de posición que se va a mostrar en el símbolo del
sistema. Este argumento es opcional, cuyo valor predeterminado es null .
maxLength , de tipo int , es la longitud máxima de la respuesta del usuario. Este argumento es opcional, cuyo
valor predeterminado es-1.
keyboard , de tipo Keyboard , es el tipo de teclado que se va a utilizar para la respuesta del usuario. Este
argumento es opcional, cuyo valor predeterminado es Keyboard.Default .
initialValue , de tipo string , es una respuesta predefinida que se mostrará y que se puede editar. Este
argumento es opcional, cuyo valor predeterminado es un vacío string .
En el ejemplo siguiente se muestra cómo establecer algunos de los argumentos opcionales:

string result = await DisplayPromptAsync("Question 2", "What's 5 + 5?", initialValue: "10", maxLength: 2,
keyboard: Keyboard.Numeric);

Este código muestra una respuesta predefinida de 10, limita el número de caracteres que se pueden introducir en 2
y muestra el teclado numérico para la entrada del usuario:

WARNING
De forma predeterminada, en UWP, cuando se muestra un mensaje, las teclas de acceso que se definen en la página detrás
del mensaje se pueden activar. Para obtener más información, consulte VisualElement Access Keys in Windows.

Vínculos relacionados
PopupsSample
Fuentes en Xamarin.Forms
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
De forma predeterminada, Xamarin.Forms usa una fuente del sistema definida por cada plataforma. Sin
embargo, los controles que muestran texto definen propiedades que puede usar para cambiar esta fuente:
FontAttributes , de tipo FontAttributes , que es una enumeración con tres miembros: None , Bold y
Italic . El valor predeterminado de esta propiedad es None .
FontSize , de tipo double .
FontFamily , de tipo string .

Estas propiedades están respaldadas por objetos BindableProperty , lo que significa que pueden ser destinos
de los enlaces de datos, y con estilo.

Establecer atributos de fuente


Los controles que muestran texto pueden establecer la FontAttributes propiedad para especificar los atributos
de fuente:

<Label Text="Italics"
FontAttributes="Italic" />
<Label Text="Bold and italics"
FontAttributes="Bold, Italic" />

El código de C# equivalente es el siguiente:

Label label1 = new Label


{
Text = "Italics",
FontAttributes = FontAttributes.Italic
};

Label label2 = new Label


{
Text = "Bold and italics",
FontAttributes = FontAttributes.Bold | FontAttributes.Italic
};

Establecer el tamaño de fuente


Los controles que muestran texto pueden establecer la FontSize propiedad para especificar el tamaño de
fuente. La FontSize propiedad se puede establecer en un double valor directamente o en un NamedSize valor
de enumeración:

<Label Text="Font size 24"


FontSize="24" />
<Label Text="Large font size"
FontSize="Large" />
El código de C# equivalente es el siguiente:

Label label1 = new Label


{
Text = "Font size 24",
FontSize = 24
};

Label label2 = new Label


{
Text = "Large font size",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
};

Como alternativa, el Device.GetNamedSize método tiene una invalidación que especifica el segundo argumento
como Element :

Label myLabel = new Label


{
Text = "Large font size",
};
myLabel.FontSize = Device.GetNamedSize(NamedSize.Large, myLabel);

NOTE
El FontSize valor, cuando se especifica como double , se mide en unidades independientes del dispositivo. Para
obtener más información, consulte unidades de medida.

Para obtener más información sobre los tamaños de fuente con nombre, vea comprender los tamaños de
fuente con nombre.

Establecimiento de la familia de fuentes


Los controles que muestran texto pueden establecer la FontFamily propiedad en un nombre de familia de
fuentes, como "Times Roman". Sin embargo, esto solo funcionará si se admite esa familia de fuentes en la
plataforma en cuestión.
Hay una serie de técnicas que se pueden usar para intentar derivar las fuentes que están disponibles en una
plataforma. Sin embargo, la presencia de un archivo de fuente TTF (True Type Format) no implica
necesariamente una familia de fuentes, y a menudo se incluyen TTFs que no están diseñados para su uso en
aplicaciones. Además, las fuentes instaladas en una plataforma pueden cambiar con la versión de la plataforma.
Por lo tanto, el enfoque más confiable para especificar una familia de fuentes es usar una fuente personalizada.
Las fuentes personalizadas se pueden agregar al Xamarin.Forms proyecto compartido y usarse en proyectos de
plataforma sin ningún trabajo adicional. El proceso para llevarlo a cabo es el siguiente:
1. Agregue la fuente al Xamarin.Forms proyecto compartido como un recurso incrustado (acción de
compilación: EmbeddedResource ).
2. Registre el archivo de fuente con el ensamblado, en un archivo como AssemblyInfo.CS , mediante el
ExportFont atributo. También se puede especificar un alias opcional.

En el ejemplo siguiente se muestra la fuente Lobster-Regular que se registra con el ensamblado, junto con un
alias:
using Xamarin.Forms;

[assembly: ExportFont("Lobster-Regular.ttf", Alias = "Lobster")]

NOTE
La fuente puede residir en cualquier carpeta del proyecto compartido, sin tener que especificar el nombre de la carpeta al
registrar la fuente con el ensamblado.
En Windows, el nombre del archivo de fuente y el nombre de fuente pueden ser diferentes. Para detectar el nombre de
fuente en Windows, haga clic con el botón secundario en el archivo. ttf y seleccione vista previa . El nombre de la fuente
se puede determinar a continuación en la ventana de vista previa.

La fuente se puede consumir después en cada plataforma haciendo referencia a su nombre, sin la extensión de
archivo:

<!-- Use font name -->


<Label Text="Hello Xamarin.Forms"
FontFamily="Lobster-Regular" />

Como alternativa, se puede consumir en cada plataforma haciendo referencia a su alias:

<!-- Use font alias -->


<Label Text="Hello Xamarin.Forms"
FontFamily="Lobster" />

El código de C# equivalente es el siguiente:

// Use font name


Label label1 = new Label
{
Text = "Hello Xamarin.Forms!",
FontFamily = "Lobster-Regular"
};

// Use font alias


Label label2 = new Label
{
Text = "Hello Xamarin.Forms!",
FontFamily = "Lobster"
};

Las siguientes capturas de pantallas muestran la fuente personalizada:

IMPORTANT
En el caso de las compilaciones de versión en Windows, asegúrese de que el ensamblado que contiene la fuente
personalizada se pasa como argumento en la Forms.Init llamada al método. Para más información, consulte Solución
de problemas.
Establecer propiedades de fuente por plataforma
Las OnPlatform On clases y se pueden usar en XAML para establecer las propiedades de fuente por
plataforma. En el ejemplo siguiente se establecen diferentes familias y tamaños de fuente en cada plataforma:

<Label Text="Different font properties on different platforms"


FontSize="{OnPlatform iOS=20, Android=Medium, UWP=24}">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="MarkerFelt-Thin" />
<On Platform="Android" Value="Lobster-Regular" />
<On Platform="UWP" Value="ArimaMadurai-Black" />
</OnPlatform>
</Label.FontFamily>
</Label>

La Device.RuntimePlatform propiedad se puede utilizar en el código para establecer las propiedades de fuente
por plataforma.

Label label = new Label


{
Text = "Different font properties on different platforms"
};

label.FontSize = Device.RuntimePlatform == Device.iOS ? 20 :


Device.RuntimePlatform == Device.Android ? Device.GetNamedSize(NamedSize.Medium, label) : 24;
label.FontFamily = Device.RuntimePlatform == Device.iOS ? "MarkerFelt-Thin" :
Device.RuntimePlatform == Device.Android ? "Lobster-Regular" : "ArimaMadurai-Black";

Para obtener más información sobre cómo proporcionar valores específicos de la plataforma, vea proporcionar
valores específicos de la plataforma. Para obtener información sobre la OnPlatform extensión de marcado,
consulte extensión de marcadoen la plataforma.

Comprender los tamaños de fuente con nombre


Xamarin.Forms define los campos de la NamedSize enumeración que representan tamaños de fuente
concretos. En la tabla siguiente se muestran los NamedSize miembros y sus tamaños predeterminados en iOS,
Android y el plataforma universal de Windows (UWP):

M IEM B RO IO S A N DRO ID UW P

Default 16 14 14

Micro 11 10 15,667

Small 13 14 18,667

Medium 16 17 22,667

Large 20 22 32

Body 17 16 14

Header 17 96 46
M IEM B RO IO S A N DRO ID UW P

Title 28 24 24

Subtitle 22 16 20

Caption 12 12 12

Los valores de tamaño se miden en unidades independientes del dispositivo. Para obtener más información,
consulte unidades de medida.

NOTE
En iOS y Android, los tamaños de fuente con nombre se escalan automáticamente en función de las opciones de
accesibilidad del sistema operativo. Este comportamiento se puede deshabilitar en iOS con una plataforma específica.
Para obtener más información, vea escalado de accesibilidad para tamaños de fuente con nombre en iOS.

Presentación iconos de fuente


Las aplicaciones pueden mostrar iconos Xamarin.Forms de fuente especificando los datos del icono de fuente
en un FontImageSource objeto. Esta clase, que se deriva de la ImageSource clase, tiene las propiedades
siguientes:
Glyph : el valor del carácter Unicode del icono de fuente, especificado como string .
Size : un double valor que indica el tamaño, en unidades independientes del dispositivo, del icono de
fuente representada. El valor predeterminado es 30. Además, esta propiedad se puede establecer en un
tamaño de fuente con nombre.
FontFamily : string que representa la familia de fuentes a la que pertenece el icono de fuente.
Color : un Color valor opcional que se usará al mostrar el icono de fuente.

Estos datos se usan para crear un PNG, que puede mostrarse en cualquier vista que muestre un ImageSource .
Este enfoque permite que varias vistas muestren iconos de fuente, como emojis, en lugar de limitar la
presentación del icono de fuente a una única vista de presentación de texto, como Label .

IMPORTANT
Los iconos de fuente solo se pueden especificar actualmente mediante su representación de caracteres Unicode.

En el siguiente ejemplo de XAML se muestra un icono de fuente único en una Image vista:

<Image BackgroundColor="#D1D1D1">
<Image.Source>
<FontImageSource Glyph="&#xf30c;"
FontFamily="{OnPlatform iOS=Ionicons, Android=ionicons.ttf#}"
Size="44" />
</Image.Source>
</Image>

Este código muestra un icono XBox, de la familia de fuentes Ionicons, en una Image vista. Tenga en cuenta que,
mientras que el carácter Unicode para este icono es \uf30c , tiene que incluir un carácter de escape en XAML,
por lo que se convierte en &#xf30c; . El código de C# equivalente es el siguiente:
Image image = new Image { BackgroundColor = Color.FromHex("#D1D1D1") };
image.Source = new FontImageSource
{
Glyph = "\uf30c",
FontFamily = Device.RuntimePlatform == Device.iOS ? "Ionicons" : "ionicons.ttf#",
Size = 44
};

En las siguientes capturas de pantalla, desde el ejemplo de diseños enlazables , se muestran varios iconos de
fuente mostrados por un diseño enlazable:

Vínculos relacionados
FontsSample
Texto (ejemplo)
Diseños enlazables (ejemplo)
Proporcionar valores específicos de la plataforma
Extensión de marcado en la plataforma
Diseños enlazables
Gráficos de SkiaSharp :::no-loc(Xamarin.Forms):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
Uso de SkiaSharp para gráficos 2D en las :::no-loc(Xamarin.Forms)::: aplicaciones
SkiaSharp es un sistema de gráficos 2D para .NET y C# basado en el motor de gráficos Skia de código abierto que
se usa en gran medida en productos de Google. Puede usar SkiaSharp en las :::no-loc(Xamarin.Forms)::: aplicaciones
para dibujar gráficos vectoriales 2D, mapas de bits y texto.
En esta guía se da por supuesto que está familiarizado con la :::no-loc(Xamarin.Forms)::: programación.

Seminario Web: SkiaSharp para :::no-loc(Xamarin.Forms):::

SkiaSharp Windows°PE
SkiaSharp para :::no-loc(Xamarin.Forms)::: se empaqueta como un paquete NuGet. Después de crear una :::no-
loc(Xamarin.Forms)::: solución en Visual Studio o Visual Studio para Mac, puede usar el administrador de paquetes
NuGet para buscar el paquete SkiaSharp. views. Forms y agregarlo a la solución. Si comprueba la sección
referencias de cada proyecto después de agregar SkiaSharp, puede ver que se han agregado varias bibliotecas
SkiaSharp a cada uno de los proyectos de la solución.
Si su :::no-loc(Xamarin.Forms)::: aplicación tiene como destino iOS, edite su archivo info. plist para cambiar el
destino de implementación mínimo a iOS 8,0.
En cualquier página de C# que use SkiaSharp, querrá incluir una using Directiva para el SkiaSharp espacio de
nombres, que engloba todas las clases, estructuras y enumeraciones SkiaSharp que usará en la programación de
gráficos. También querrá una using Directiva para el SkiaSharp.Views.Forms espacio de nombres para las clases
específicas de :::no-loc(Xamarin.Forms)::: . Se trata de un espacio de nombres mucho más pequeño, con la clase más
importante SKCanvasView . Esta clase se deriva de la :::no-loc(Xamarin.Forms)::: View clase y hospeda la salida de
los gráficos de SkiaSharp.

IMPORTANT
El SkiaSharp.Views.Forms espacio de nombres también contiene una SKGLView clase que se deriva de View pero utiliza
OpenGL para representar gráficos. Por motivos de simplicidad, esta guía se restringe a SKCanvasView , pero usar SKGLView
en su lugar es bastante similar.

Conceptos básicos de dibujo de SkiaSharp


Algunas de las cifras de gráficos más sencillas que se pueden dibujar con SkiaSharp son círculos, óvalos y
rectángulos. Al mostrar estas figuras, obtendrá información sobre las coordenadas, los tamaños y los colores de
SkiaSharp. La presentación de texto y mapas de bits es más compleja, pero estos artículos también presentan esas
técnicas.

Trazados y líneas de SkiaSharp


Una ruta de acceso de gráficos es una serie de líneas rectas y curvas conectadas. Las rutas de acceso se pueden
trazar, rellenar o ambos. En este artículo se incluyen muchos aspectos del dibujo de líneas, incluidos los extremos
del trazo y las combinaciones, y las líneas discontinuas y con puntos, pero se detienen pocas geometrías de curva.

Transformaciones de SkiaSharp
Las transformaciones permiten que los objetos de gráficos se traduzcan, escalen, giren o sesgadon uniformemente.
En este artículo también se muestra cómo puede usar una matriz de transformación estándar de 3 por 3 para crear
transformaciones no afines y aplicar transformaciones a trazados.

Trazados y curvas de SkiaSharp


La exploración de las rutas de acceso continúa con la adición de curvas a los objetos de trazado y la explotación de
otras características de ruta de acceso eficaces. Verá cómo puede especificar una ruta de acceso completa en una
cadena de texto concisa, cómo usar los efectos de la ruta de acceso y cómo profundizar en la ruta de acceso interna.

Mapas de bits de SkiaSharp


Los mapas de bits son matrices rectangulares de bits que corresponden a los píxeles de un dispositivo de pantalla.
En esta serie de artículos se muestra cómo cargar, guardar, mostrar, crear, dibujar, animar y obtener acceso a los bits
de los mapas de bits de SkiaSharp.

Efectos SkiaSharp
Los efectos son propiedades que modifican la presentación normal de gráficos, incluidos los degradados lineales y
circulares, el mosaico de mapas de bits, los modos de mezcla, el desenfoque y otros.

Vínculos relacionados
API de SkiaSharp
SkiaSharpFormsDemos (ejemplo)
SkiaSharp con :::no-loc(Xamarin.Forms)::: seminario web (vídeo)
Pantalla de presentación de Xamarin. Forms
23/11/2019 • 3 minutes to read • Edit Online

Las aplicaciones suelen tener un retraso de inicio mientras la aplicación completa el proceso de inicialización. Los
desarrolladores pueden querer ofrecer una experiencia de marca, normalmente denominada pantalla de
presentación, mientras se inicia la aplicación. En este artículo se explica cómo crear pantallas de presentación para
aplicaciones de Xamarin. Forms.
Xamarin. Forms se inicializa en cada plataforma una vez completada la secuencia de inicio nativa. Se ha inicializado
Xamarin. Forms:
En el método OnCreate de la clase MainActivity en Android.
En el método FinishedLaunching de la clase AppDelegate en iOS.
En el método OnLaunched de la clase App en UWP.

La pantalla de presentación debe mostrarse lo antes posible cuando se inicia la aplicación, pero Xamarin. Forms no
se inicializa hasta el final de la secuencia de inicio, lo que significa que la pantalla de presentación debe
implementarse fuera de Xamarin. Forms en cada plataforma. En las secciones siguientes se explica cómo crear una
pantalla de presentación en cada plataforma.

Pantalla de presentación de Android de Xamarin. Forms


La creación de una pantalla de presentación en Android requiere la creación de un Activity de presentación como
MainLauncher con un tema especial. En cuanto se inicia el Activity de presentación, se inicia el Activity principal
con el tema normal de la aplicación.
Para obtener más información sobre las pantallas de presentación en Xamarin. Android, consulte la pantalla de
presentación de Xamarin. Android.

Pantalla de presentación de Xamarin. Forms iOS


Una pantalla de presentación en iOS se conoce como pantalla de inicio. Para crear una pantalla de inicio en iOS, es
necesario crear un guion gráfico que defina la interfaz de usuario de la pantalla de inicio y, a continuación,
establecer el guión gráfico como la pantalla de inicio en info. plist .
Para obtener más información acerca de las pantallas de inicio de Xamarin. iOS, consulte la pantalla de inicio de
Xamarin. iOS.

Pantalla de presentación de Xamarin. Forms UWP


En UWP, el paquete. appxmanifest contiene una pestaña activos visuales con un submenú de pantalla de
presentación . Los gráficos de la pantalla de presentación se pueden especificar en este menú:
Vínculos relacionados
Pantalla de presentación de Xamarin. Android
Pantalla de inicio de Xamarin. iOS
Aplicación de estilos Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Aplicación de estilo a aplicaciones Xamarin.Forms con estilos XAML


El estilo de una Xamarin.Forms aplicación se realiza tradicionalmente mediante el uso de la Style clase para
agrupar una colección de valores de propiedad en un objeto que se puede aplicar a varias instancias de
elementos visuales. Esto ayuda a reducir el marcado repetitivo y permite cambiar la apariencia de las
aplicaciones más fácilmente.

Aplicación de estilos a Xamarin.Forms aplicaciones con hojas de


estilo CSS
Xamarin.Formsadmite el estilo de los elementos visuales mediante Hojas de estilo CSS (CSS). Una hoja de
estilos se compone de una lista de reglas, donde cada regla consta de uno o varios selectores y un bloque de
declaración.
Aplicación de estilo a aplicaciones Xamarin.Forms
con estilos XAML
18/12/2020 • 2 minutes to read • Edit Online

Introducción
Xamarin.Formslas aplicaciones suelen contener varios controles que tienen un aspecto idéntico. Establecer la
apariencia de cada control individual puede ser repetitivo y propenso a errores. En su lugar, se pueden crear
estilos que personalicen la apariencia del control agrupando y estableciendo las propiedades disponibles en el
tipo de control.

Estilos explícitos
Un estilo explícito es aquel que se aplica de forma selectiva a los controles estableciendo sus Style
propiedades.

Estilos implícitos
Un estilo implícito es aquél que usan todos los controles del mismo TargetType , sin necesidad de que cada
control haga referencia al estilo.

Estilos globales
Los estilos se pueden poner a disposición globalmente agregándolos a la de la aplicación ResourceDictionary .
Esto ayuda a evitar la duplicación de estilos en páginas o controles.

Herencia de estilo
Los estilos pueden heredar de otros estilos para reducir la duplicación y habilitar la reutilización.

Estilos dinámicos
Los estilos no responden a los cambios de propiedad y permanecen sin cambios durante la ejecución de una
aplicación. Sin embargo, las aplicaciones pueden responder dinámicamente a los cambios de estilo en tiempo
de ejecución mediante recursos dinámicos.

Estilos de dispositivo
Xamarin.Formsincluye seis estilos dinámicos , conocidos como estilos de dispositivo , en la Devices.Styles
clase. Los seis estilos solo se pueden aplicar a Label instancias de.

Clases de estilo
Xamarin.Formslas clases de estilo permiten aplicar varios estilos a un control, sin tener que recurrir a la
herencia de estilo.
Introducción a los Xamarin.Forms estilos
18/12/2020 • 7 minutes to read • Edit Online

Los estilos permiten personalizar el aspecto de los elementos visuales. Los estilos se definen para un tipo específico
y contienen valores para las propiedades disponibles en ese tipo.
Xamarin.Formslas aplicaciones suelen contener varios controles que tienen un aspecto idéntico. Por ejemplo, una
aplicación puede tener varias Label instancias que tienen las mismas opciones de fuente y opciones de diseño, tal
y como se muestra en el siguiente ejemplo de código XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Styles.NoStylesPage"
Title="No Styles"
IconImageSource="xaml.png">
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Label Text="These labels"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
FontSize="Large" />
<Label Text="are not"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
FontSize="Large" />
<Label Text="using styles"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
FontSize="Large" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

En el ejemplo de código siguiente se muestra la página equivalente creada en C#:


public class NoStylesPageCS : ContentPage
{
public NoStylesPageCS ()
{
Title = "No Styles";
IconImageSource = "csharp.png";
Padding = new Thickness (0, 20, 0, 0);

Content = new StackLayout {


Children = {
new Label {
Text = "These labels",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label))
},
new Label {
Text = "are not",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label))
},
new Label {
Text = "using styles",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label))
}
}
};
}
}

Cada Label instancia tiene valores de propiedad idénticos para controlar la apariencia del texto que muestra
Label . El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:

Establecer la apariencia de cada control individual puede ser repetitivo y propenso a errores. En su lugar, se puede
crear un estilo que defina la apariencia y se aplique a los controles necesarios.

Crear un estilo
La clase Style agrupa una colección de valores de propiedad en un objeto que después se puede aplicar a varias
instancias de elementos visuales. Esto ayuda a reducir el marcado repetitivo y permite cambiar la apariencia de las
aplicaciones más fácilmente.
Aunque los estilos se diseñaron principalmente para las aplicaciones basadas en XAML, también se pueden crear
en C#:
Style las instancias creadas en XAML se definen normalmente en un ResourceDictionary que se asigna a la
Resources colección de un control, una página o la Resources colección de la aplicación.
Style las instancias creadas en C# normalmente se definen en la clase de la página o en una clase a la que se
puede tener acceso de forma global.
La elección de dónde se puede definir una instancia de Style afecta a dónde se puede usar:
Style las instancias definidas en el nivel de control solo se pueden aplicar al control y a sus elementos
secundarios.
Style las instancias definidas en el nivel de página solo se pueden aplicar a la página y a sus elementos
secundarios.
Las instancias de Style definidas en el nivel de aplicación se pueden aplicar en toda la aplicación.

Cada instancia de Style contiene una colección de uno o varios objetos Setter , donde cada objeto Setter tiene
un elemento Property y un elemento Value . Property es el nombre de la propiedad enlazable del elemento al
que se aplica el estilo y Value es el valor que se aplica a la propiedad.
Cada Style instancia puede ser explícitao implícita:
Una instancia explícita Style se define especificando un TargetType valor de y x:Key , y estableciendo la
propiedad del elemento de destino Style en la x:Key referencia. Para obtener más información sobre los
estilos explícitos , vea estilos explícitos.
Una instancia implícita Style se define especificando solo un TargetType . La Style instancia se aplicará
automáticamente a todos los elementos de ese tipo. Tenga en cuenta que las subclases de no TargetType tienen
el Style aplicado automáticamente. Para obtener más información sobre los estilos implícitos , vea estilos
implícitos.
Al crear un elemento Style , siempre se requiere la propiedad TargetType . En el ejemplo de código siguiente se
muestra un estilo explícito (observe el x:Key ) creado en XAML:

<Style x:Key="labelStyle" TargetType="Label">


<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="FontSize" Value="Large" />
</Style>

Para aplicar Style , el objeto de destino debe ser un VisualElement que coincida con el TargetType valor de
propiedad de Style , tal y como se muestra en el siguiente ejemplo de código XAML:

<Label Text="Demonstrating an explicit style" Style="{StaticResource labelStyle}" />

Los estilos inferiores de la jerarquía de vistas tienen prioridad sobre las definidas más arriba. Por ejemplo, el
establecimiento de un Style que establezca en Label.TextColor Red en el nivel de aplicación será reemplazado
por un estilo de nivel de página que establezca Label.TextColor en Green . De igual forma, un estilo de nivel de
página se reemplazará por un estilo de nivel de control. Además, si Label.TextColor se establece directamente en
una propiedad de control, tiene prioridad sobre cualquier estilo.
En los artículos de esta sección se muestra y se explica cómo crear y aplicar estilos explícitos e implícitos , cómo
crear estilos globales, la herencia de estilo, cómo responder a los cambios de estilo en tiempo de ejecución y cómo
usar los estilos integrados incluidos en Xamarin.Forms .

NOTE
¿Qué es StyleId?
Antes de Xamarin.Forms 2,2, la StyleId propiedad se usaba para identificar elementos individuales en una aplicación para
su identificación en las pruebas de IU y en motores de tema como Pixate. Sin embargo, Xamarin.Forms 2,2 presentó la
AutomationId propiedad, que ha reemplazado la StyleId propiedad.

Vínculos relacionados
Extensiones de marcado XAML
Estilo
Setter
Estilos explícitos en :::no-loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
Un estilo explícito es aquel que se aplica de forma selectiva a los controles estableciendo sus propiedades de estilo.

Crear un estilo explícito en XAML


Para declarar un Style en el nivel de página, ResourceDictionary se debe agregar a la página y, a continuación,
Style se pueden incluir una o más declaraciones en ResourceDictionary . Un Style se hace explícito
proporcionando a su declaración un x:Key atributo, que le da una clave descriptiva en ResourceDictionary . Los
estilos explícitos se deben aplicar a los elementos visuales concretos estableciendo sus Style propiedades.
En el ejemplo de código siguiente se muestran los estilos explícitos declarados en XAML en una página
ResourceDictionary y se aplican a las instancias de la página Label :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.ExplicitStylesPage" Title="Explicit"
IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="labelRedStyle" TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Red" />
</Style>
<Style x:Key="labelGreenStyle" TargetType="Label">
...
<Setter Property="TextColor" Value="Green" />
</Style>
<Style x:Key="labelBlueStyle" TargetType="Label">
...
<Setter Property="TextColor" Value="Blue" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Label Text="These labels"
Style="{StaticResource labelRedStyle}" />
<Label Text="are demonstrating"
Style="{StaticResource labelGreenStyle}" />
<Label Text="explicit styles,"
Style="{StaticResource labelBlueStyle}" />
<Label Text="and an explicit style override"
Style="{StaticResource labelBlueStyle}"
TextColor="Teal" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

ResourceDictionary Define tres estilos explícitos que se aplican a las instancias de la página Label . Cada Style se
usa para mostrar texto en un color diferente, a la vez que se establece el tamaño de fuente y las opciones de diseño
horizontal y vertical. Cada Style se aplica a un distinto estableciendo Label sus Style propiedades mediante la
StaticResource extensión de marcado. El resultado es el aspecto que se muestra en las capturas de pantalla
siguientes:

Además, al final Label se le ha Style aplicado, pero también invalida la TextColor propiedad a un Color valor
diferente.
Crear un estilo explícito en el nivel de control
Además de crear estilos explícitos en el nivel de página, también se pueden crear en el nivel de control, como se
muestra en el ejemplo de código siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.ExplicitStylesPage" Title="Explicit"
IconImageSource="xaml.png">
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<StackLayout.Resources>
<ResourceDictionary>
<Style x:Key="labelRedStyle" TargetType="Label">
...
</Style>
...
</ResourceDictionary>
</StackLayout.Resources>
<Label Text="These labels" Style="{StaticResource labelRedStyle}" />
...
</StackLayout>
</ContentPage.Content>
</ContentPage>

En este ejemplo, las instancias explícitas Style se asignan a la Resources colección del StackLayout control.
Después, los estilos se pueden aplicar al control y a sus elementos secundarios.
Para obtener información sobre cómo crear estilos en la de una aplicación ResourceDictionary , vea estilos
globales.

Crear un estilo explícito en C#


Style las instancias se pueden agregar a la colección de una página Resources en C# mediante la creación de una
nueva ResourceDictionary y, a continuación, agregando las Style instancias a ResourceDictionary , tal y como se
muestra en el ejemplo de código siguiente:
public class ExplicitStylesPageCS : ContentPage
{
public ExplicitStylesPageCS ()
{
var labelRedStyle = new Style (typeof(Label)) {
Setters = {
...
new Setter { Property = Label.TextColorProperty, Value = Color.Red }
}
};
var labelGreenStyle = new Style (typeof(Label)) {
Setters = {
...
new Setter { Property = Label.TextColorProperty, Value = Color.Green }
}
};
var labelBlueStyle = new Style (typeof(Label)) {
Setters = {
...
new Setter { Property = Label.TextColorProperty, Value = Color.Blue }
}
};

Resources = new ResourceDictionary ();


Resources.Add ("labelRedStyle", labelRedStyle);
Resources.Add ("labelGreenStyle", labelGreenStyle);
Resources.Add ("labelBlueStyle", labelBlueStyle);
...

Content = new StackLayout {


Children = {
new Label { Text = "These labels",
Style = (Style)Resources ["labelRedStyle"] },
new Label { Text = "are demonstrating",
Style = (Style)Resources ["labelGreenStyle"] },
new Label { Text = "explicit styles,",
Style = (Style)Resources ["labelBlueStyle"] },
new Label { Text = "and an explicit style override",
Style = (Style)Resources ["labelBlueStyle"], TextColor = Color.Teal }
}
};
}
}

El constructor define tres estilos explícitos que se aplican a las instancias de la página Label . Cada explícito Style
se agrega a ResourceDictionary mediante el Add método, especificando una key cadena para hacer referencia a
la Style instancia. Cada Style se aplica a un distinto Label estableciendo sus Style propiedades.
Sin embargo, no hay ninguna ventaja en utilizar ResourceDictionary aquí. En su lugar, las Style instancias se
pueden asignar directamente a las Style propiedades de los elementos visuales necesarios y ResourceDictionary
se puede quitar, tal y como se muestra en el ejemplo de código siguiente:
public class ExplicitStylesPageCS : ContentPage
{
public ExplicitStylesPageCS ()
{
var labelRedStyle = new Style (typeof(Label)) {
...
};
var labelGreenStyle = new Style (typeof(Label)) {
...
};
var labelBlueStyle = new Style (typeof(Label)) {
...
};
...
Content = new StackLayout {
Children = {
new Label { Text = "These labels", Style = labelRedStyle },
new Label { Text = "are demonstrating", Style = labelGreenStyle },
new Label { Text = "explicit styles,", Style = labelBlueStyle },
new Label { Text = "and an explicit style override", Style = labelBlueStyle,
TextColor = Color.Teal }
}
};
}
}

El constructor define tres estilos explícitos que se aplican a las instancias de la página Label . Cada Style se usa
para mostrar texto en un color diferente, a la vez que se establece el tamaño de fuente y las opciones de diseño
horizontal y vertical. Cada Style se aplica a un distinto Label estableciendo sus Style propiedades. Además, al
final Label se le ha Style aplicado, pero también invalida la TextColor propiedad a un Color valor diferente.

Vínculos relacionados
Extensiones de marcado XAML
Estilos básicos (ejemplo)
Trabajar con estilos (ejemplo)
ResourceDictionary
Estilo
Setter
Estilos implícitos en :::no-loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Un estilo implícito es aquél que usan todos los controles del mismo TargetType, sin necesidad de que cada control
haga referencia al estilo.

Crear un estilo implícito en XAML


Para declarar un Style en el nivel de página, ResourceDictionary se debe agregar a la página y, a continuación,
Style se pueden incluir una o más declaraciones en ResourceDictionary . Style Se hace implícito si no se
especifica un x:Key atributo. A continuación, se aplicará el estilo a los elementos visuales que coinciden
TargetType exactamente, pero no a los elementos que se derivan del TargetType valor.

En el ejemplo de código siguiente se muestra un estilo implícito declarado en XAML en una página
ResourceDictionary y se aplica a las instancias de la página Entry :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Styles;assembly=Styles"
x:Class="Styles.ImplicitStylesPage" Title="Implicit" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Setter Property="HorizontalOptions" Value="Fill" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="BackgroundColor" Value="Yellow" />
<Setter Property="FontAttributes" Value="Italic" />
<Setter Property="TextColor" Value="Blue" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Entry Text="These entries" />
<Entry Text="are demonstrating" />
<Entry Text="implicit styles," />
<Entry Text="and an implicit style override" BackgroundColor="Lime" TextColor="Red" />
<local:CustomEntry Text="Subclassed Entry is not receiving the style" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

ResourceDictionary Define un estilo implícito único que se aplica a las instancias de la página Entry . Style Se usa
para mostrar el texto azul sobre un fondo amarillo, mientras que también se establecen otras opciones de
apariencia. Style Se agrega al de la página ResourceDictionary sin especificar un x:Key atributo. Por lo tanto,
Style se aplica a todas las Entry instancias de forma implícita, ya que coinciden exactamente con la TargetType
propiedad de Style . Sin embargo, Style no se aplica a la CustomEntry instancia de, que es una subclase Entry .
El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:
Además, el cuarto Entry invalida las BackgroundColor TextColor propiedades y del estilo implícito en Color
valores diferentes.
Crear un estilo implícito en el nivel de control
Además de crear estilos implícitos en el nivel de página, también se pueden crear en el nivel de control, como se
muestra en el ejemplo de código siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Styles;assembly=Styles"
x:Class="Styles.ImplicitStylesPage" Title="Implicit" IconImageSource="xaml.png">
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<StackLayout.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Setter Property="HorizontalOptions" Value="Fill" />
...
</Style>
</ResourceDictionary>
</StackLayout.Resources>
<Entry Text="These entries" />
...
</StackLayout>
</ContentPage.Content>
</ContentPage>

En este ejemplo, el implícito Style se asigna a la Resources colección del StackLayout control. A continuación, el
estilo implícito se puede aplicar al control y a sus elementos secundarios.
Para obtener información sobre cómo crear estilos en la de una aplicación ResourceDictionary , vea estilos
globales.

Crear un estilo implícito en C#


Style las instancias se pueden agregar a la colección de una página Resources en C# mediante la creación de una
nueva ResourceDictionary y, a continuación, agregando las Style instancias a ResourceDictionary , tal y como se
muestra en el ejemplo de código siguiente:
public class ImplicitStylesPageCS : ContentPage
{
public ImplicitStylesPageCS ()
{
var entryStyle = new Style (typeof(Entry)) {
Setters = {
...
new Setter { Property = Entry.TextColorProperty, Value = Color.Blue }
}
};

...
Resources = new ResourceDictionary ();
Resources.Add (entryStyle);

Content = new StackLayout {


Children = {
new Entry { Text = "These entries" },
new Entry { Text = "are demonstrating" },
new Entry { Text = "implicit styles," },
new Entry { Text = "and an implicit style override", BackgroundColor = Color.Lime, TextColor =
Color.Red },
new CustomEntry { Text = "Subclassed Entry is not receiving the style" }
}
};
}
}

El constructor define un estilo implícito único que se aplica a las instancias de la página Entry . Style Se usa para
mostrar el texto azul sobre un fondo amarillo, mientras que también se establecen otras opciones de apariencia.
Style Se agrega al de la página ResourceDictionary sin especificar una key cadena. Por lo tanto, Style se aplica
a todas las Entry instancias de forma implícita, ya que coinciden exactamente con la TargetType propiedad de
Style . Sin embargo, Style no se aplica a la CustomEntry instancia de, que es una subclase Entry .

Aplicar un estilo a los tipos derivados


La Style.ApplyToDerivedTypes propiedad permite aplicar un estilo a los controles que se derivan del tipo base al
que hace referencia la TargetType propiedad. Por lo tanto, establecer esta propiedad en true permite que un
único estilo tenga como destino varios tipos, siempre que los tipos deriven del tipo base especificado en la
TargetType propiedad.

En el ejemplo siguiente se muestra un estilo implícito que establece el color de fondo de Button las instancias en
rojo:

<Style TargetType="Button"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="Red" />
</Style>

Al colocar este estilo en el nivel de página ResourceDictionary , se aplicará a todas Button las instancias de la
página y también a los controles que deriven de Button . Sin embargo, si la ApplyToDerivedTypes propiedad
permanece sin establecer, el estilo solo se aplicaría a Button las instancias de.
El código de C# equivalente es el siguiente:
var buttonStyle = new Style(typeof(Button))
{
ApplyToDerivedTypes = true,
Setters =
{
new Setter
{
Property = VisualElement.BackgroundColorProperty,
Value = Color.Red
}
}
};

Resources = new ResourceDictionary { buttonStyle };

Vínculos relacionados
Extensiones de marcado XAML
Estilos básicos (ejemplo)
Trabajar con estilos (ejemplo)
ResourceDictionary
Estilo
Setter
Estilos globales en :::no-loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
Los estilos se pueden poner a disposición globalmente agregándolos al Diccionario de recursos de la aplicación.
Esto ayuda a evitar la duplicación de estilos en páginas o controles.

Crear un estilo global en XAML


De forma predeterminada, todas :::no-loc(Xamarin.Forms)::: las aplicaciones creadas a partir de una plantilla usan la
clase App para implementar la Application subclase. Para declarar un Style en el nivel de aplicación, en el uso
de XAML de la aplicación ResourceDictionary , la clase de aplicación predeterminada debe reemplazarse por una
clase de aplicación XAML y el código subyacente asociado. Para obtener más información, vea trabajar con la
clase App.
En el ejemplo de código siguiente se muestra un Style declarado en el nivel de aplicación:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="BorderColor" Value="Lime" />
<Setter Property="BorderRadius" Value="5" />
<Setter Property="BorderWidth" Value="5" />
<Setter Property="WidthRequest" Value="200" />
<Setter Property="TextColor" Value="Teal" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

Esto ResourceDictionary define un solo estilo explícito , buttonStyle , que se usará para establecer la apariencia de
Button las instancias. Sin embargo, los estilos globales pueden ser explícitos o implícitos.
En el ejemplo de código siguiente se muestra una página XAML buttonStyle que aplica a las instancias de la
página Button :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.ApplicationStylesPage"
Title="Application" IconImageSource="xaml.png">
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Button Text="These buttons" Style="{StaticResource buttonStyle}" />
<Button Text="are demonstrating" Style="{StaticResource buttonStyle}" />
<Button Text="application style overrides" Style="{StaticResource buttonStyle}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:


Para obtener información sobre cómo crear estilos en la de una página ResourceDictionary , vea estilos explícitos y
estilos implícitos.
Reemplazar estilos
Los estilos inferiores de la jerarquía de vistas tienen prioridad sobre las definidas más arriba. Por ejemplo, el
establecimiento de un Style que establezca en Button.TextColor Red en el nivel de aplicación será reemplazado
por un estilo de nivel de página que establezca Button.TextColor en Green . De igual forma, un estilo de nivel de
página se reemplazará por un estilo de nivel de control. Además, si Button.TextColor se establece directamente en
una propiedad de control, tendrá prioridad sobre los estilos. Esta prioridad se muestra en el ejemplo de código
siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.ApplicationStylesPage"
Title="Application" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="buttonStyle" TargetType="Button">
...
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<StackLayout.Resources>
<ResourceDictionary>
<Style x:Key="buttonStyle" TargetType="Button">
...
<Setter Property="TextColor" Value="Blue" />
</Style>
</ResourceDictionary>
</StackLayout.Resources>
<Button Text="These buttons" Style="{StaticResource buttonStyle}" />
<Button Text="are demonstrating" Style="{StaticResource buttonStyle}" />
<Button Text="application style overrides" Style="{StaticResource buttonStyle}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

El original buttonStyle , definido en el nivel de la aplicación, se reemplaza por la buttonStyle instancia definida en
el nivel de página. Además, el estilo de nivel de página se reemplaza por el nivel de control buttonStyle . Por lo
tanto, las Button instancias se muestran con texto azul, tal como se muestra en las siguientes capturas de pantalla:

Crear un estilo global en C#


Style las instancias se pueden agregar a la colección de la aplicación Resources en C# mediante la creación de
una nueva ResourceDictionary y, a continuación, agregando las Style instancias a ResourceDictionary , tal y
como se muestra en el ejemplo de código siguiente:

public class App : Application


{
public App ()
{
var buttonStyle = new Style (typeof(Button)) {
Setters = {
...
new Setter { Property = Button.TextColorProperty, Value = Color.Teal }
}
};

Resources = new ResourceDictionary ();


Resources.Add ("buttonStyle", buttonStyle);
...
}
...
}

El constructor define un solo estilo explícito para aplicar a Button las instancias en toda la aplicación. Explícito
Style las instancias se agregan a ResourceDictionary mediante el Add método, especificando una key cadena
para hacer referencia a la Style instancia. Style A continuación, la instancia se puede aplicar a cualquier control
del tipo correcto en la aplicación. Sin embargo, los estilos globales pueden ser explícitos o implícitos.
En el ejemplo de código siguiente se muestra una página de C# buttonStyle que aplica a las instancias de la
página Button :
public class ApplicationStylesPageCS : ContentPage
{
public ApplicationStylesPageCS ()
{
...
Content = new StackLayout {
Children = {
new Button { Text = "These buttons", Style = (Style)Application.Current.Resources
["buttonStyle"] },
new Button { Text = "are demonstrating", Style = (Style)Application.Current.Resources
["buttonStyle"] },
new Button { Text = "application styles", Style = (Style)Application.Current.Resources
["buttonStyle"]
}
}
};
}
}

buttonStyle Se aplica a las Button instancias mediante el establecimiento Style de sus propiedades y controla la
apariencia de las Button instancias.

Vínculos relacionados
Extensiones de marcado XAML
Estilos básicos (ejemplo)
Trabajar con estilos (ejemplo)
ResourceDictionary
Estilo
Setter
Herencia de estilo en :::no-loc(Xamarin.Forms):::
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
Los estilos pueden heredar de otros estilos para reducir la duplicación y habilitar la reutilización.

Herencia de estilo en XAML


La herencia de estilo se realiza estableciendo la Style.BasedOn propiedad en un existente Style . En XAML, esto se
logra estableciendo la BasedOn propiedad en una StaticResource extensión de marcado que hace referencia a un
creado previamente Style . En C#, esto se consigue estableciendo la BasedOn propiedad en una Style instancia
de.
Los estilos que heredan de un estilo base pueden incluir Setter instancias de nuevas propiedades o usarse para
invalidar estilos del estilo base. Además, los estilos que heredan de un estilo base deben tener como destino el
mismo tipo o un tipo que se derive del tipo de destino del estilo base. Por ejemplo, si un estilo base tiene como
View destino instancias, los estilos basados en el estilo base pueden tener como destino View instancias o tipos
que derivan de la View clase, como Label instancias de y Button .
En el código siguiente se muestra la herencia de estilo explícita en una página XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.StyleInheritancePage"
Title="Inheritance" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="baseStyle" TargetType="View">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
</Style>
<Style x:Key="labelStyle" TargetType="Label"
BasedOn="{StaticResource baseStyle}">
...
<Setter Property="TextColor" Value="Teal" />
</Style>
<Style x:Key="buttonStyle" TargetType="Button"
BasedOn="{StaticResource baseStyle}">
<Setter Property="BorderColor" Value="Lime" />
...
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Label Text="These labels"
Style="{StaticResource labelStyle}" />
...
<Button Text="So is the button"
Style="{StaticResource buttonStyle}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Las baseStyle instancias de destino View y establecen las HorizontalOptions VerticalOptions propiedades y.
baseStyle No se establece directamente en ningún control. En su lugar, labelStyle y buttonStyle heredan de él y
establecen valores de propiedades enlazables adicionales. labelStyle Y buttonStyle se aplican a las Label
instancias y la Button instancia, estableciendo sus Style propiedades. El resultado es el aspecto que se muestra
en las capturas de pantalla siguientes:

NOTE
Un estilo implícito se puede derivar de un estilo explícito, pero no se puede derivar un estilo explícito de un estilo implícito.

Respetar la cadena de herencia


Un estilo solo puede heredar de estilos en el mismo nivel, o superior, en la jerarquía de vistas. Esto significa que:
Un recurso de nivel de aplicación solo puede heredar de otros recursos de nivel de aplicación.
Un recurso de nivel de página puede heredar de recursos de nivel de aplicación y otros recursos de nivel de
página.
Un recurso de nivel de control puede heredar de recursos de nivel de aplicación, recursos de nivel de página y
otros recursos de nivel de control.
Esta cadena de herencia se muestra en el ejemplo de código siguiente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.StyleInheritancePage"
Title="Inheritance" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="baseStyle" TargetType="View">
...
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<StackLayout.Resources>
<ResourceDictionary>
<Style x:Key="labelStyle" TargetType="Label" BasedOn="{StaticResource baseStyle}">
...
</Style>
<Style x:Key="buttonStyle" TargetType="Button" BasedOn="{StaticResource baseStyle}">
...
</Style>
</ResourceDictionary>
</StackLayout.Resources>
...
</StackLayout>
</ContentPage.Content>
</ContentPage>

En este ejemplo, labelStyle y buttonStyle son recursos de nivel de control, mientras que baseStyle es un
recurso de nivel de página. Sin embargo, while labelStyle y buttonStyle heredan de baseStyle , no es posible
baseStyle heredar de labelStyle o buttonStyle , debido a sus respectivas ubicaciones en la jerarquía de vistas.

Herencia de estilo en C#
En el ejemplo de código siguiente se muestra la página de C# equivalente, donde Style las instancias se asignan
directamente a las Style propiedades de los controles necesarios:
public class StyleInheritancePageCS : ContentPage
{
public StyleInheritancePageCS ()
{
var baseStyle = new Style (typeof(View)) {
Setters = {
new Setter {
Property = View.HorizontalOptionsProperty, Value = LayoutOptions.Center },
...
}
};

var labelStyle = new Style (typeof(Label)) {


BasedOn = baseStyle,
Setters = {
...
new Setter { Property = Label.TextColorProperty, Value = Color.Teal }
}
};

var buttonStyle = new Style (typeof(Button)) {


BasedOn = baseStyle,
Setters = {
new Setter { Property = Button.BorderColorProperty, Value = Color.Lime },
...
}
};
...

Content = new StackLayout {


Children = {
new Label { Text = "These labels", Style = labelStyle },
...
new Button { Text = "So is the button", Style = buttonStyle }
}
};
}
}

Las baseStyleinstancias de destino View y establecen las HorizontalOptions VerticalOptions propiedades y.


baseStyle No se establece directamente en ningún control. En su lugar, labelStyle y buttonStyle heredan de él y
establecen valores de propiedades enlazables adicionales. labelStyle Y buttonStyle se aplican a las Label
instancias y la Button instancia, estableciendo sus Style propiedades.

Vínculos relacionados
Extensiones de marcado XAML
Estilos básicos (ejemplo)
Trabajar con estilos (ejemplo)
ResourceDictionary
Estilo
Setter
Estilos dinámicos en :::no-loc(Xamarin.Forms):::
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
Los estilos no responden a los cambios de propiedad y permanecen sin cambios durante la ejecución de una
aplicación. Por ejemplo, después de asignar un estilo a un elemento visual, si una de las instancias del establecedor
se modifica, se quita o se agrega una nueva instancia de establecedor, los cambios no se aplicarán al elemento
visual. Sin embargo, las aplicaciones pueden responder dinámicamente a los cambios de estilo en tiempo de
ejecución mediante recursos dinámicos.
La DynamicResource extensión de marcado es similar a la StaticResource extensión de marcado en que ambos
usan una clave de diccionario para capturar un valor de ResourceDictionary . Sin embargo, mientras
StaticResource realiza una búsqueda de un solo diccionario, DynamicResource mantiene un vínculo a la clave del
diccionario. Por lo tanto, si se reemplaza la entrada del diccionario asociada a la clave, el cambio se aplica al
elemento visual. Esto permite realizar cambios de estilo en tiempo de ejecución en una aplicación.
En el ejemplo de código siguiente se muestran los estilos dinámicos en una página XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.DynamicStylesPage" Title="Dynamic"
IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="baseStyle" TargetType="View">
...
</Style>
<Style x:Key="blueSearchBarStyle"
TargetType="SearchBar"
BasedOn="{StaticResource baseStyle}">
...
</Style>
<Style x:Key="greenSearchBarStyle"
TargetType="SearchBar">
...
</Style>
...
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<SearchBar Placeholder="These SearchBar controls"
Style="{DynamicResource searchBarStyle}" />
...
</StackLayout>
</ContentPage.Content>
</ContentPage>

Las SearchBar instancias de usan la DynamicResource extensión de marcado para hacer referencia a un Style
denominado searchBarStyle , que no está definido en el XAML. Sin embargo, dado que las Style propiedades de
las SearchBar instancias se establecen mediante DynamicResource , la clave del diccionario que falta no produce
una excepción.
En su lugar, en el archivo de código subyacente, el constructor crea una ResourceDictionary entrada con la clave
searchBarStyle , como se muestra en el ejemplo de código siguiente:
public partial class DynamicStylesPage : ContentPage
{
bool originalStyle = true;

public DynamicStylesPage ()
{
InitializeComponent ();
Resources ["searchBarStyle"] = Resources ["blueSearchBarStyle"];
}

void OnButtonClicked (object sender, EventArgs e)


{
if (originalStyle) {
Resources ["searchBarStyle"] = Resources ["greenSearchBarStyle"];
originalStyle = false;
} else {
Resources ["searchBarStyle"] = Resources ["blueSearchBarStyle"];
originalStyle = true;
}
}
}

Cuando OnButtonClicked se ejecuta el controlador de eventos, cambiará searchBarStyle entre


blueSearchBarStyle y greenSearchBarStyle . El resultado es el aspecto que se muestra en las capturas de pantalla
siguientes:

Ejemplo de estilo dinámico de


En el ejemplo de código siguiente se muestra la página equivalente en C#:

public class DynamicStylesPageCS : ContentPage


{
bool originalStyle = true;

public DynamicStylesPageCS ()
{
...
var baseStyle = new Style (typeof(View)) {
...
};
var blueSearchBarStyle = new Style (typeof(SearchBar)) {
...
};
var greenSearchBarStyle = new Style (typeof(SearchBar)) {
...
};
...
var searchBar1 = new SearchBar { Placeholder = "These SearchBar controls" };
searchBar1.SetDynamicResource (VisualElement.StyleProperty, "searchBarStyle");
...
Resources = new ResourceDictionary ();
Resources.Add ("blueSearchBarStyle", blueSearchBarStyle);
Resources.Add ("greenSearchBarStyle", greenSearchBarStyle);
Resources ["searchBarStyle"] = Resources ["blueSearchBarStyle"];

Content = new StackLayout {


Children = { searchBar1, searchBar2, searchBar3, searchBar4, button }
};
}
...
}

En C#, las instancias de usan el SetDynamicResource método para hacer referencia a searchBarStyle . El
SearchBar
OnButtonClicked código del controlador de eventos es idéntico al ejemplo XAML y, cuando se ejecuta, cambiará
searchBarStyle entre blueSearchBarStyle y greenSearchBarStyle .

Herencia de estilo dinámico


No se puede derivar un estilo a partir de un estilo dinámico mediante la Style.BasedOn propiedad. En su lugar, la
Style clase incluye la BaseResourceKey propiedad, que se puede establecer en una clave de diccionario cuyo valor
puede cambiar dinámicamente.
En el ejemplo de código siguiente se muestra la herencia de estilo dinámico en una página XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.DynamicStylesInheritancePage"
Title="Dynamic Inheritance" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="baseStyle" TargetType="View">
...
</Style>
<Style x:Key="blueSearchBarStyle" TargetType="SearchBar" BasedOn="{StaticResource baseStyle}">
...
</Style>
<Style x:Key="greenSearchBarStyle" TargetType="SearchBar">
...
</Style>
<Style x:Key="tealSearchBarStyle" TargetType="SearchBar" BaseResourceKey="searchBarStyle">
...
</Style>
...
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<SearchBar Text="These SearchBar controls" Style="{StaticResource tealSearchBarStyle}" />
...
</StackLayout>
</ContentPage.Content>
</ContentPage>

Las SearchBar instancias de usan la StaticResource extensión de marcado para hacer referencia a un Style
denominado tealSearchBarStyle . Esto Style establece algunas propiedades adicionales y usa la
BaseResourceKey propiedad como referencia searchBarStyle . La DynamicResource extensión de marcado no es
necesaria porque tealSearchBarStyle no cambiará, salvo Style que se derive de. Por lo tanto,
tealSearchBarStyle mantiene un vínculo a searchBarStyle y se modifica cuando cambia el estilo base.

En el archivo de código subyacente, el constructor crea una ResourceDictionary entrada con la clave
searchBarStyle , tal y como se muestra en el ejemplo anterior que demostró los estilos dinámicos. Cuando
OnButtonClicked se ejecuta el controlador de eventos, cambiará searchBarStyle entre blueSearchBarStyle y
greenSearchBarStyle . El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:

Ejemplo de herencia de de
En el ejemplo de código siguiente se muestra la página equivalente en C#:

public class DynamicStylesInheritancePageCS : ContentPage


{
bool originalStyle = true;

public DynamicStylesInheritancePageCS ()
{
...
var baseStyle = new Style (typeof(View)) {
...
};
var blueSearchBarStyle = new Style (typeof(SearchBar)) {
...
};
var greenSearchBarStyle = new Style (typeof(SearchBar)) {
...
};
var tealSearchBarStyle = new Style (typeof(SearchBar)) {
BaseResourceKey = "searchBarStyle",
...
};
...
Resources = new ResourceDictionary ();
Resources.Add ("blueSearchBarStyle", blueSearchBarStyle);
Resources.Add ("greenSearchBarStyle", greenSearchBarStyle);
Resources ["searchBarStyle"] = Resources ["blueSearchBarStyle"];

Content = new StackLayout {


Children = {
new SearchBar { Text = "These SearchBar controls", Style = tealSearchBarStyle },
...
}
};
}
...
}

tealSearchBarStyle Se asigna directamente a la Style propiedad de las SearchBar instancias de. Esto Style
establece algunas propiedades adicionales y usa la BaseResourceKey propiedad como referencia searchBarStyle .
El SetDynamicResource método no es necesario aquí porque tealSearchBarStyle no cambiará, salvo Style que se
derive de. Por lo tanto, tealSearchBarStyle mantiene un vínculo a searchBarStyle y se modifica cuando cambia el
estilo base.
Vínculos relacionados
Extensiones de marcado XAML
Estilos dinámicos (ejemplo)
Trabajar con estilos (ejemplo)
ResourceDictionary
Estilo
Setter

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Estilos de dispositivo en :::no-loc(Xamarin.Forms):::
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: incluye seis estilos dinámicos, conocidos como estilos de dispositivo, en la clase Device.
Styles.
Los estilos de dispositivo son los siguientes:
BodyStyle
CaptionStyle
ListItemDetailTextStyle
ListItemTextStyle
SubtitleStyle
TitleStyle

Los seis estilos solo se pueden aplicar a Label las instancias de. Por ejemplo, un Label que muestra el cuerpo de
un párrafo podría establecer su Style propiedad en BodyStyle .
En el ejemplo de código siguiente se muestra cómo usar los estilos de dispositivo en una página XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.DeviceStylesPage" Title="Device"
IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="myBodyStyle" TargetType="Label"
BaseResourceKey="BodyStyle">
<Setter Property="TextColor" Value="Accent" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="0,20,0,0">
<Label Text="Title style"
Style="{DynamicResource TitleStyle}" />
<Label Text="Subtitle text style"
Style="{DynamicResource SubtitleStyle}" />
<Label Text="Body style"
Style="{DynamicResource BodyStyle}" />
<Label Text="Caption style"
Style="{DynamicResource CaptionStyle}" />
<Label Text="List item detail text style"
Style="{DynamicResource ListItemDetailTextStyle}" />
<Label Text="List item text style"
Style="{DynamicResource ListItemTextStyle}" />
<Label Text="No style" />
<Label Text="My body style"
Style="{StaticResource myBodyStyle}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Los estilos del dispositivo se enlazan al uso de la DynamicResource extensión de marcado. La naturaleza dinámica
de los estilos puede verse en iOS cambiando la configuración de accesibilidad para el tamaño del texto. La
apariencia de los estilos de dispositivo es diferente en cada plataforma, como se muestra en las siguientes capturas
de pantallas:

Los estilos de dispositivo también se pueden derivar mediante el establecimiento de la BaseResourceKey propiedad
en el nombre de clave para el estilo de dispositivo. En el ejemplo de código anterior, myBodyStyle hereda de
BodyStyle y establece un color de texto acentuado. Para obtener más información sobre la herencia de estilo
dinámico, consulte herencia de estilo dinámico.
En el ejemplo de código siguiente se muestra la página equivalente en C#:

public class DeviceStylesPageCS : ContentPage


{
public DeviceStylesPageCS ()
{
var myBodyStyle = new Style (typeof(Label)) {
BaseResourceKey = Device.Styles.BodyStyleKey,
Setters = {
new Setter {
Property = Label.TextColorProperty,
Value = Color.Accent
}
}
};

Title = "Device";
IconImageSource = "csharp.png";
Padding = new Thickness (0, 20, 0, 0);

Content = new StackLayout {


Children = {
new Label { Text = "Title style", Style = Device.Styles.TitleStyle },
new Label { Text = "Subtitle style", Style = Device.Styles.SubtitleStyle },
new Label { Text = "Body style", Style = Device.Styles.BodyStyle },
new Label { Text = "Caption style", Style = Device.Styles.CaptionStyle },
new Label { Text = "List item detail text style",
Style = Device.Styles.ListItemDetailTextStyle },
new Label { Text = "List item text style", Style = Device.Styles.ListItemTextStyle },
new Label { Text = "No style" },
new Label { Text = "My body style", Style = myBodyStyle }
}
};
}
}

La Style propiedad de cada Label instancia se establece en la propiedad adecuada de la Devices.Styles clase.

Accesibilidad
Los estilos de dispositivo respetan las preferencias de accesibilidad, por lo que los tamaños de fuente cambian a
medida que se modifican las preferencias de accesibilidad en cada plataforma. Por lo tanto, para admitir texto
accesible, asegúrese de que los estilos de dispositivo se usan como base para cualquier estilo de texto dentro de la
aplicación.
Las capturas de pantallas siguientes muestran los estilos de dispositivo en cada plataforma, con el tamaño de
fuente accesible más pequeño:

Las capturas de pantallas siguientes muestran los estilos de dispositivo en cada plataforma, con el tamaño de
fuente accesible más grande:

Vínculos relacionados
Estilos de texto
Extensiones de marcado XAML
Estilos dinámicos (ejemplo)
Trabajar con estilos (ejemplo)
Device. Styles
ResourceDictionary
Estilo
Setter
:::no-loc(Xamarin.Forms)::: Clases de estilo
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: las clases de estilo permiten aplicar varios estilos a un control, sin tener que recurrir a la
herencia de estilo.

Crear clases de estilo


Se puede crear una clase de estilo estableciendo la Class propiedad de en Style un string que representa el
nombre de clase. La ventaja que ofrece esto, a través de la definición de un estilo explícito mediante el x:Key
atributo, es que se pueden aplicar varias clases de estilo a VisualElement .

IMPORTANT
Varios estilos pueden compartir el mismo nombre de clase, siempre que tengan como destino tipos diferentes. Esto permite
que varias clases de estilo, que tienen un nombre idéntico, tengan como destino tipos diferentes.

En el ejemplo siguiente se muestran tres BoxView clases de estilo y una VisualElement clase de estilo:
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="BoxView"
Class="Separator">
<Setter Property="BackgroundColor"
Value="#CCCCCC" />
<Setter Property="HeightRequest"
Value="1" />
</Style>

<Style TargetType="BoxView"
Class="Rounded">
<Setter Property="BackgroundColor"
Value="#1FAECE" />
<Setter Property="HorizontalOptions"
Value="Start" />
<Setter Property="CornerRadius"
Value="10" />
</Style>

<Style TargetType="BoxView"
Class="Circle">
<Setter Property="BackgroundColor"
Value="#1FAECE" />
<Setter Property="WidthRequest"
Value="100" />
<Setter Property="HeightRequest"
Value="100" />
<Setter Property="HorizontalOptions"
Value="Start" />
<Setter Property="CornerRadius"
Value="50" />
</Style>

<Style TargetType="VisualElement"
Class="Rotated"
ApplyToDerivedTypes="true">
<Setter Property="Rotation"
Value="45" />
</Style>
</ContentPage.Resources>
</ContentPage>

Las Separator Rounded clases de estilo, y Circle establecen BoxView las propiedades en valores específicos.
La clase de estilo tiene un TargetType de VisualElement , lo que significa que solo se puede aplicar a
Rotated
VisualElement las instancias de. Sin embargo, su ApplyToDerivedTypes propiedad se establece en true , lo que
garantiza que se puede aplicar a los controles que derivan de VisualElement , como BoxView . Para obtener más
información sobre cómo aplicar un estilo a un tipo derivado, vea aplicar un estilo a los tipos derivados.
El código de C# equivalente es el siguiente:

var separatorBoxViewStyle = new Style(typeof(BoxView))


{
Class = "Separator",
Setters =
{
new Setter
{
Property = VisualElement.BackgroundColorProperty,
Value = Color.FromHex("#CCCCCC")
},
new Setter
{
Property = VisualElement.HeightRequestProperty,
Property = VisualElement.HeightRequestProperty,
Value = 1
}
}
};

var roundedBoxViewStyle = new Style(typeof(BoxView))


{
Class = "Rounded",
Setters =
{
new Setter
{
Property = VisualElement.BackgroundColorProperty,
Value = Color.FromHex("#1FAECE")
},
new Setter
{
Property = View.HorizontalOptionsProperty,
Value = LayoutOptions.Start
},
new Setter
{
Property = BoxView.CornerRadiusProperty,
Value = 10
}
}
};

var circleBoxViewStyle = new Style(typeof(BoxView))


{
Class = "Circle",
Setters =
{
new Setter
{
Property = VisualElement.BackgroundColorProperty,
Value = Color.FromHex("#1FAECE")
},
new Setter
{
Property = VisualElement.WidthRequestProperty,
Value = 100
},
new Setter
{
Property = VisualElement.HeightRequestProperty,
Value = 100
},
new Setter
{
Property = View.HorizontalOptionsProperty,
Value = LayoutOptions.Start
},
new Setter
{
Property = BoxView.CornerRadiusProperty,
Value = 50
}
}
};

var rotatedVisualElementStyle = new Style(typeof(VisualElement))


{
Class = "Rotated",
ApplyToDerivedTypes = true,
Setters =
{
new Setter
{
{
Property = VisualElement.RotationProperty,
Value = 45
}
}
};

Resources = new ResourceDictionary


{
separatorBoxViewStyle,
roundedBoxViewStyle,
circleBoxViewStyle,
rotatedVisualElementStyle
};

Usar clases de estilo


Las clases de estilo se pueden utilizar estableciendo la StyleClass propiedad del control, que es de tipo
IList<string> , en una lista de nombres de clase de estilo. Se aplicarán las clases de estilo, siempre que el tipo
del control coincida con el TargetType de las clases de estilo.
En el ejemplo siguiente se muestran tres BoxView instancias, cada una de ellas establecida en clases de estilo
diferentes:

<ContentPage ...>
<ContentPage.Resources>
...
</ContentPage.Resources>
<StackLayout Margin="20">
<BoxView StyleClass="Separator" />
<BoxView WidthRequest="100"
HeightRequest="100"
HorizontalOptions="Center"
StyleClass="Rounded, Rotated" />
<BoxView HorizontalOptions="Center"
StyleClass="Circle" />
</StackLayout>
</ContentPage>

En este ejemplo, el primero BoxView tiene el estilo de un separador de línea, mientras que el tercero BoxView es
circular. La segunda BoxView tiene dos clases de estilo aplicadas, que proporcionan esquinas redondeadas y giran
45 grados:
IMPORTANT
Se pueden aplicar varias clases de estilo a un control porque la StyleClass propiedad es de tipo IList<string> .
Cuando esto ocurre, las clases de estilo se aplican en orden de lista ascendente. Por lo tanto, cuando varias clases de estilo
establecen propiedades idénticas, la propiedad de la clase de estilo que se encuentra en la posición de la lista más alta
tendrá prioridad.

El código de C# equivalente es el siguiente:

...
Content = new StackLayout
{
Children =
{
new BoxView { StyleClass = new [] { "Separator" } },
new BoxView { WidthRequest = 100, HeightRequest = 100, HorizontalOptions = LayoutOptions.Center,
StyleClass = new [] { "Rounded", "Rotated" } },
new BoxView { HorizontalOptions = LayoutOptions.Center, StyleClass = new [] { "Circle" } }
}
};

Vínculos relacionados
Estilos básicos (ejemplo)
Aplicación de estilos a :::no-loc(Xamarin.Forms):::
aplicaciones mediante hojas de estilo CSS (CSS)
18/12/2020 • 27 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: admite el estilo de los elementos visuales mediante Hojas de estilo CSS (CSS).
:::no-loc(Xamarin.Forms)::: las aplicaciones pueden tener estilo mediante CSS. Una hoja de estilos se compone de
una lista de reglas, donde cada regla consta de uno o varios selectores y un bloque de declaración. Un bloque de
declaración se compone de una lista de declaraciones entre llaves, donde cada declaración consta de una
propiedad, un signo de dos puntos y un valor. Cuando hay varias declaraciones en un bloque, se inserta un punto
y coma como separador. En el ejemplo de código siguiente se muestra alguna :::no-loc(Xamarin.Forms)::: CSS
compatible:
navigationpage {
-xf-bar-background-color: lightgray;
}

^contentpage {
background-color: lightgray;
}

#listView {
background-color: lightgray;
}

stacklayout {
margin: 20;
}

.mainPageTitle {
font-style: bold;
font-size: medium;
}

.mainPageSubtitle {
margin-top: 15;
}

.detailPageTitle {
font-style: bold;
font-size: medium;
text-align: center;
}

.detailPageSubtitle {
text-align: center;
font-style: italic;
}

listview image {
height: 60;
width: 60;
}

stacklayout>image {
height: 200;
width: 200;
}

En :::no-loc(Xamarin.Forms)::: , las hojas de estilos CSS se analizan y evalúan en tiempo de ejecución, en lugar de
en tiempo de compilación, y las hojas de estilos se vuelven a analizar en el uso.

NOTE
Actualmente, todo el estilo que es posible con el estilo XAML no se puede realizar con CSS. Sin embargo, los estilos XAML
se pueden usar para complementar CSS para propiedades que actualmente no admite :::no-loc(Xamarin.Forms)::: . Para
obtener más información sobre los estilos XAML, vea Aplicación de estilo a aplicaciones :::no-loc(Xamarin.Forms)::: con
estilos XAML.

En el ejemplo MonkeyAppCSS se muestra cómo usar CSS para aplicar estilo a una aplicación sencilla y se muestra
en las siguientes capturas de pantallas:
Consumir una hoja de estilos
El proceso para agregar una hoja de estilos a una solución es el siguiente:
1. Agregue un archivo CSS vacío al proyecto de biblioteca de .NET Standard.
2. Establezca la acción de compilación del archivo CSS en EmbeddedResource .
Cargar una hoja de estilos
Existen varios enfoques que se pueden usar para cargar una hoja de estilos.

NOTE
Actualmente no es posible cambiar una hoja de estilos en tiempo de ejecución y aplicar la nueva hoja de estilos.

XAML
Una hoja de estilos se puede cargar y analizar con la StyleSheet clase antes de que se agregue a un
ResourceDictionary :
<Application ...>
<Application.Resources>
<StyleSheet Source="/Assets/styles.css" />
</Application.Resources>
</Application>

La StyleSheet.Source propiedad especifica la hoja de estilos como un URI relativo a la ubicación del archivo
XAML envolvente, o relativa a la raíz del proyecto si el identificador URI comienza con / .

WARNING
El archivo CSS no se cargará si su acción de compilación no está establecida en EmbeddedResource .

Como alternativa, se puede cargar y analizar una hoja de estilos con la StyleSheet clase, antes de agregarla a
ResourceDictionary , si se inserta en una CDATA sección:

<ContentPage ...>
<ContentPage.Resources>
<StyleSheet>
<![CDATA[
^contentpage {
background-color: lightgray;
}
]]>
</StyleSheet>
</ContentPage.Resources>
...
</ContentPage>

Para obtener más información acerca de los diccionarios de recursos, consulte diccionarios de recursos.
C#
En C#, una hoja de estilos se puede cargar desde un StringReader y agregar a un ResourceDictionary :

public partial class MyPage : ContentPage


{
public MyPage()
{
InitializeComponent();

using (var reader = new StringReader("^contentpage { background-color: lightgray; }"))


{
this.Resources.Add(StyleSheet.FromReader(reader));
}
}
}

El argumento para el StyleSheet.FromReader método es el TextReader que ha leído la hoja de estilos.

Seleccionar elementos y aplicar propiedades


CSS usa selectores para determinar los elementos de destino. Los estilos con selectores coincidentes se aplican
consecutivamente, en orden de definición. Los estilos definidos en un elemento específico siempre se aplican en
último lugar. Para obtener más información sobre los selectores admitidos, vea Referencia del selector.
CSS utiliza las propiedades para aplicar estilo a un elemento seleccionado. Cada propiedad tiene un conjunto de
valores posibles y algunas propiedades pueden afectar a cualquier tipo de elemento, mientras que otros se
aplican a grupos de elementos. Para obtener más información sobre las propiedades admitidas, vea referencia de
propiedades.
Las hojas de estilo secundarias siempre invalidan las hojas de estilo primarias si establecen las mismas
propiedades. Por lo tanto, se siguen las siguientes reglas de precedencia al aplicar estilos que establecen las
mismas propiedades:
Un estilo definido en los recursos de la aplicación se sobrescribirá con un estilo definido en los recursos de la
página, si establecen las mismas propiedades.
Un estilo definido en los recursos de página se sobrescribirá con un estilo definido en los recursos del control,
si establecen las mismas propiedades.
Un estilo definido en los recursos de la aplicación se sobrescribirá con un estilo definido en los recursos del
control, si establecen las mismas propiedades.

IMPORTANT
No se admiten las variables CSS.

Seleccionar elementos por tipo


Los elementos del árbol visual se pueden seleccionar por tipo con el selector sin distinción de mayúsculas y
minúsculas element :

stacklayout {
margin: 20;
}

Este selector identifica los StackLayout elementos de las páginas que utilizan la hoja de estilos y establece sus
márgenes en un grosor uniforme de 20.

NOTE
El element selector no identifica las subclases del tipo especificado.

Seleccionar elementos por clase base


Los elementos del árbol visual se pueden seleccionar por clase base con el selector sin distinción de mayúsculas y
minúsculas ^base :

^contentpage {
background-color: lightgray;
}

Este selector identifica los ContentPage elementos que utilizan la hoja de estilos y establece su color de fondo en
lightgray .

NOTE
El ^base selector es específico de :::no-loc(Xamarin.Forms)::: y no forma parte de la especificación de CSS.

Seleccionar un elemento por su nombre


Los elementos individuales del árbol visual se pueden seleccionar con el selector distinguir mayúsculas de
minúsculas #id :
#listView {
background-color: lightgray;
}

Este selector identifica el elemento cuya StyleId propiedad está establecida en listView . Sin embargo, si
StyleId no se establece la propiedad, el selector volverá a usar el x:Name del elemento. Por consiguiente, en el
siguiente ejemplo de XAML, el #listView selector identificará el ListView cuyo x:Name atributo está establecido
en listView y establecerá el color de fondo en lightgray .

<ContentPage ...>
<ContentPage.Resources>
<StyleSheet Source="/Assets/styles.css" />
</ContentPage.Resources>
<StackLayout>
<ListView x:Name="listView" ...>
...
</ListView>
</StackLayout>
</ContentPage>

Seleccionar elementos con un atributo de clase específico


Los elementos con un atributo de clase específico se pueden seleccionar con el selector que distingue mayúsculas
de minúsculas .class :

.detailPageTitle {
font-style: bold;
font-size: medium;
text-align: center;
}

.detailPageSubtitle {
text-align: center;
font-style: italic;
}

Una clase CSS se puede asignar a un elemento XAML estableciendo la StyleClass propiedad del elemento en el
nombre de clase CSS. Por lo tanto, en el siguiente ejemplo de XAML, los estilos definidos por la .detailPageTitle
clase se asignan al primero Label , mientras que los estilos definidos por la .detailPageSubtitle clase se
asignan a la segunda Label .

<ContentPage ...>
<ContentPage.Resources>
<StyleSheet Source="/Assets/styles.css" />
</ContentPage.Resources>
<ScrollView>
<StackLayout>
<Label ... StyleClass="detailPageTitle" />
<Label ... StyleClass="detailPageSubtitle"/>
...
</StackLayout>
</ScrollView>
</ContentPage>

Seleccionar elementos secundarios


Los elementos secundarios del árbol visual se pueden seleccionar con el selector sin distinción de mayúsculas y
minúsculas element element :
listview image {
height: 60;
width: 60;
}

Este selector identifica los Image elementos que son elementos secundarios de ListView los elementos y
establece su alto y ancho en 60. Por lo tanto, en el siguiente ejemplo de XAML, el listview image selector
identificará el Image que es un elemento secundario de y ListView establecerá el alto y el ancho en 60.

<ContentPage ...>
<ContentPage.Resources>
<StyleSheet Source="/Assets/styles.css" />
</ContentPage.Resources>
<StackLayout>
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
...
<Image ... />
...
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

NOTE
El element element selector no requiere que el elemento secundario sea un elemento secundario directo del elemento
primario: el elemento secundario puede tener un elemento primario diferente. La selección se produce siempre que un
antecesor es el primer elemento especificado.

Seleccionar elementos secundarios directos


Los elementos secundarios directos del árbol visual se pueden seleccionar con el selector sin distinción de
mayúsculas y minúsculas element>element :

stacklayout>image {
height: 200;
width: 200;
}

Este selector identifica los Image elementos que son elementos secundarios directos de StackLayout los
elementos y establece su alto y ancho en 200. Por lo tanto, en el siguiente ejemplo de XAML, el stacklayout>image
selector identificará el Image que es un elemento secundario directo de StackLayout y establecerá el alto y el
ancho en 200.
<ContentPage ...>
<ContentPage.Resources>
<StyleSheet Source="/Assets/styles.css" />
</ContentPage.Resources>
<ScrollView>
<StackLayout>
...
<Image ... />
...
</StackLayout>
</ScrollView>
</ContentPage>

NOTE
El element>element selector requiere que el elemento secundario sea un elemento secundario directo del elemento
primario.

Referencia del selector


Admite los siguientes selectores de CSS :::no-loc(Xamarin.Forms)::: :

SEL EC TO R E JEM P LO DESC RIP C IÓ N

.class .header Selecciona todos los elementos con la


StyleClass propiedad que contiene '
header '. Tenga en cuenta que este
selector distingue mayúsculas de
minúsculas.

#id #email Selecciona todos los elementos con el


StyleId valor establecido en email
. Si StyleId no se establece, fallback
en x:Name . Cuando se usa XAML,
x:Name se prefiere StyleId . Tenga
en cuenta que este selector distingue
mayúsculas de minúsculas.

* * Selecciona todos los elementos.

element label Selecciona todos los elementos de tipo


Label , pero no las subclases. Tenga
en cuenta que este selector no
distingue mayúsculas de minúsculas.

^base ^contentpage Selecciona todos los elementos con


ContentPage como clase base,
incluido a ContentPage sí mismo.
Tenga en cuenta que este selector no
distingue entre mayúsculas y
minúsculas y no forma parte de la
especificación de CSS.
SEL EC TO R E JEM P LO DESC RIP C IÓ N

element,element label,button Selecciona todos los Button


elementos y todos los Label
elementos. Tenga en cuenta que este
selector no distingue mayúsculas de
minúsculas.

element element stacklayout label Selecciona todos los Label elementos


dentro de StackLayout . Tenga en
cuenta que este selector no distingue
mayúsculas de minúsculas.

element>element stacklayout>label Selecciona todos los Label elementos


con StackLayout como elemento
primario directo. Tenga en cuenta que
este selector no distingue mayúsculas
de minúsculas.

element+element label+entry Selecciona todos los Entry elementos


directamente después de Label .
Tenga en cuenta que este selector no
distingue mayúsculas de minúsculas.

element~element label~entry Selecciona todos los Entry elementos


precedidos por un Label . Tenga en
cuenta que este selector no distingue
mayúsculas de minúsculas.

Los estilos con selectores coincidentes se aplican consecutivamente, en orden de definición. Los estilos definidos
en un elemento específico siempre se aplican en último lugar.

TIP
Los selectores se pueden combinar sin limitación, como StackLayout>ContentView>label.email .

Actualmente no se admiten los siguientes selectores:


[attribute]
@media y @supports
: y ::

NOTE
La especificidad y las invalidaciones de especificidad no se admiten.

Referencia de propiedades
Las siguientes propiedades CSS son compatibles con :::no-loc(Xamarin.Forms)::: (en la columna valores , los tipos
están en cursiva , mientras que los literales de cadena son gray ):

P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO


P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

align-content FlexLayout stretch | center | align-content: space-


start | end | between;

spacebetween |
spacearound |
spaceevenly |
flex-start | flex-end |
space-between |
space-around | initial

align-items FlexLayout stretch | center | align-items: flex-


start | end | start;

flex-start | flex-end |
initial

align-self VisualElement auto | stretch | align-self: flex-end;


center | start | end |
flex-start | flex-end |
initial

background-color VisualElement color | de initial background-color:


springgreen;

background-image Page string | initial background-image:


bg.png;

border-color Button , Frame , color | de initial border-color: #9acd32;


ImageButton

border-radius BoxView , Button , doble | initial border-radius: 10;


Frame , ImageButton

border-width Button , ImageButton doble | initial border-width: .5;

color ActivityIndicator , color | de initial color: rgba(255, 0, 0,


BoxView , Button , 0.3);

CheckBox , DatePicker ,
Editor , Entry , Label ,
Picker , ProgressBar ,
SearchBar , Switch ,
TimePicker

column-gap Grid doble | initial column-gap: 9;

direction VisualElement ltr | rtl | inherit | direction: rtl;


initial

flex-direction FlexLayout column | columnreverse | flex-direction: column-


row | rowreverse | reverse;

row-reverse |
column-reverse |
initial
P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

flex-basis VisualElement valor float | auto | flex-basis: 25%;


initial . Además, se
puede especificar un
porcentaje en el intervalo
comprendido entre 0% y
100% con el % signo.

flex-grow VisualElement valor float | initial flex-grow: 1.5;

flex-shrink VisualElement valor float | initial flex-shrink: 1;

flex-wrap VisualElement nowrap | wrap | flex-wrap: wrap-


reverse | wrap-reverse | reverse;

initial

font-family Button , DatePicker , string | initial font-family: Consolas;


Editor , Entry , Label ,
Picker , SearchBar ,
TimePicker , Span

font-size Button , DatePicker , doble | namedsize | font-size: 12;


Editor , Entry , Label , initial
Picker , SearchBar ,
TimePicker , Span

font-style Button , DatePicker , bold | italic | font-style: bold;


Editor , Entry , Label , initial
Picker , SearchBar ,
TimePicker , Span

height VisualElement doble | initial min-height: 250;

justify-content FlexLayout start | center | end | justify-content: flex-


spacebetween | end;

spacearound |
spaceevenly |
flex-start | flex-end |
space-between |
space-around | initial

letter-spacing Button , DatePicker , doble | initial letter-spacing: 2.5;


Editor , Entry , Label ,
Picker , SearchBar ,
SearchHandler , Span ,
TimePicker

line-height Label , Span doble | initial line-height: 1.8;

margin View grosor | initial margin: 6 12;

margin-left View grosor | initial margin-left: 3;


P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

margin-top View grosor | initial margin-top: 2;

margin-right View grosor | initial margin-right: 1;

margin-bottom View grosor | initial margin-bottom: 6;

max-lines Label int | initial max-lines: 2;

min-height VisualElement doble | initial min-height: 50;

min-width VisualElement doble | initial min-width: 112;

opacity VisualElement doble | initial opacity: .3;

order VisualElement int | initial order: -1;

padding Button , ImageButton , grosor | initial padding: 6 12 12;


Layout , Page

padding-left Button , ImageButton , doble | initial padding-left: 3;


Layout , Page

padding-top Button , ImageButton , doble | initial padding-top: 4;


Layout , Page

padding-right Button , ImageButton , doble | initial padding-right: 2;


Layout , Page

padding-bottom Button , ImageButton , doble | initial padding-bottom: 6;


Layout , Page

position FlexLayout relative | absolute | position: absolute;


initial

row-gap Grid doble | initial row-gap: 12;

text-align Entry , EntryCell , left | top | right | text-align: right;


Label , SearchBar bottom | start |
center | middle | end |
initial . left y right
deben evitarse en entornos
de derecha a izquierda.

text-decoration Label , Span none | underline | text-decoration:


strikethrough | underline, line-
through;
line-through | initial

text-transform Button , Editor , Entry , none | default | text-transform:


Label , SearchBar , uppercase | lowercase | uppercase;

SearchHandler initial
P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

transform VisualElement none , rotate , rotateX , transform: rotate(180),


rotateY , scale , scaleX(2.5);

scaleX , scaleY ,
translate , translateX ,
translateY , initial

transform-origin VisualElement Double , Double | initial transform-origin: 7.5,


12.5;

vertical-align Label left | top | right | vertical-align: bottom;


bottom | start |
center | middle | end |
initial

visibility VisualElement true | visible | false visibility: hidden;


| hidden | collapse |
initial

width VisualElement doble | initial min-width: 320;

NOTE
initial es un valor válido para todas las propiedades. Borra el valor (restablece al valor predeterminado) que se
estableció desde otro estilo.

Actualmente no se admiten las siguientes propiedades:


all: initial.
Propiedades de diseño (cuadro o cuadrícula).
Propiedades abreviadas, como font y border .

Además, no hay ningún inherit valor y, por tanto, no se admite la herencia. Por lo tanto, no es posible, por
ejemplo, establecer la font-size propiedad en un diseño y esperar que todas las Label instancias del diseño
hereden el valor. La única excepción es la direction propiedad, que tiene un valor predeterminado de inherit .
Span Los elementos de destino tienen un problema conocido que evita que los intervalos sean el destino de los
estilos CSS por elemento y nombre (mediante el # símbolo). El Span elemento se deriva de GestureElement ,
que no tiene la StyleClass propiedad, por lo que los intervalos no admiten el destino de clase CSS. Para obtener
más información, vea no se puede aplicar el estilo CSS al control span.
:::no -loc(Xamarin.Forms)::: propiedades específicas
:::no-loc(Xamarin.Forms):::También se admiten las siguientes propiedades CSS específicas (en la columna valores ,
los tipos son cursiva , mientras que los literales de cadena son gray ):

P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

-xf-bar-background- NavigationPage , color | de initial -xf-bar-background-


color TabbedPage color: teal;

-xf-bar-text-color NavigationPage , color | de initial -xf-bar-text-color:


TabbedPage gray
P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

-xf-horizontal-scroll- ScrollView default | always | -xf-horizontal-scroll-


bar-visibility never | initial bar-visibility: never;

-xf-max-length Entry , Editor , int | initial -xf-max-length: 20;


SearchBar

-xf-max-track-color Slider color | de initial -xf-max-track-color:


red;

-xf-min-track-color Slider color | de initial -xf-min-track-color:


yellow;

-xf-orientation ScrollView , horizontal | vertical | -xf-orientation:


StackLayout both | initial . both horizontal;

solo se admite en
ScrollView .

-xf-placeholder Entry , Editor , texto entre comillas quoted -xf-placeholder: Enter


SearchBar text | initial name;

-xf-placeholder-color Entry , Editor , color | de initial -xf-placeholder-color:


green;
SearchBar

-xf-spacing StackLayout doble | initial -xf-spacing: 8;

-xf-thumb-color Slider , Switch color | de initial -xf-thumb-color:


limegreen;

-xf-vertical-scroll- ScrollView default | always | -xf-vertical-scroll-


bar-visibility never | initial bar-visibility: always;

-xf-vertical-text- Label start | center | end | -xf-vertical-text-


alignment initial alignment: end;

-xf-visual VisualElement string | initial -xf-visual: material;

:::no -loc(Xamarin.Forms)::: Propiedades específicas del shell


:::no-loc(Xamarin.Forms):::También se admiten las siguientes propiedades CSS específicas del shell (en la columna
valores , los tipos son cursiva , mientras que los literales de cadena son gray ):

P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

-xf-flyout-background Shell color | de initial -xf-flyout-background:


red;

-xf-shell-background Element color | de initial -xf-shell-background:


green;

-xf-shell-disabled Element color | de initial -xf-shell-disabled:


blue;
P RO P IEDA D SE A P L IC A A VA LO RES E JEM P LO

-xf-shell-foreground Element color | de initial -xf-shell-foreground:


yellow;

-xf-shell-tabbar- Element color | de initial -xf-shell-tabbar-


background background: white;

-xf-shell-tabbar- Element color | de initial -xf-shell-tabbar-


disabled disabled: black;

-xf-shell-tabbar- Element color | de initial -xf-shell-tabbar-


foreground foreground: gray;

-xf-shell-tabbar-title Element color | de initial -xf-shell-tabbar-title:


lightgray;

-xf-shell-tabbar- Element color | de initial -xf-shell-tabbar-


unselected unselected: cyan;

-xf-shell-title Element color | de initial -xf-shell-title: teal;

-xf-shell-unselected Element color | de initial -xf-shell-unselected:


limegreen;

Color
color Se admiten los siguientes valores:
X11 colores, que coinciden con los colores CSS, los colores predefinidos de UWP y los :::no-
loc(Xamarin.Forms)::: colores. Tenga en cuenta que estos valores de color no distinguen mayúsculas de
minúsculas.
colores hex: #rgb , #argb , #rrggbb , #aarrggbb
colores RGB: rgb(255,0,0) , rgb(100%,0%,0%) . Los valores están en el intervalo 0-255 o 0%-100%.
colores RGBA: rgba(255, 0, 0, 0.8) , rgba(100%, 0%, 0%, 0.8) . El valor de opacidad está en el intervalo 0.0-
1.0.
HSL Colors: hsl(120, 100%, 50%) . El valor h está en el intervalo 0-360, mientras que s y l están en el intervalo
0%-100%.
colores de HSLA: hsla(120, 100%, 50%, .8) . El valor de opacidad está en el intervalo 0.0-1.0.
Thickness
Se admiten uno, dos, tres o cuatro thickness valores, cada uno separado por un espacio en blanco:
Un valor único indica el grosor uniforme.
Dos valores indican el grosor vertical y horizontal.
Tres valores indican superior, después horizontal (izquierda y derecha) y, a continuación, el grosor inferior.
Cuatro valores indican la parte superior, después, la parte inferior y, a continuación, el grosor izquierdo.

NOTE
thickness Los valores CSS se diferencian de Thickness los valores XAML. Por ejemplo, en XAML, un valor de dos valores
Thickness indica el grosor horizontal y, a continuación, el grosor vertical, mientras que un valor de cuatro valores
Thickness indica izquierda, superior, derecha y bajo grosor inferior. Además, Thickness los valores XAML están
delimitados por comas.
NamedSize
Se admiten los siguientes valores que no distinguen mayúsculas de minúsculas namedsize :
default
micro
small
medium
large

El significado exacto de cada valor depende de la namedsize plataforma y depende de la vista.

Functions
Se pueden especificar degradados lineales y radiales mediante las linear-gradient() funciones de y
radial-gradient() CSS, respectivamente. El resultado de estas funciones debe asignarse a la background
propiedad de un control.

CSS en :::no-loc(Xamarin.Forms)::: con Xamarin. University


:::no-loc(Xamarin.Forms)::: vídeo de CSS 3,0

Vínculos relacionados
MonkeyAppCSS (ejemplo)
Diccionarios de recursos
Aplicación de estilo a aplicaciones :::no-loc(Xamarin.Forms)::: con estilos XAML
Crear una Xamarin.Forms aplicación
18/12/2020 • 2 minutes to read • Edit Online

Tema de una aplicación


Los temas se pueden implementar en Xamarin.Forms aplicaciones mediante la creación de un ResourceDictionary
para cada tema y, a continuación, cargar los recursos con la DynamicResource extensión de marcado.

Responder a cambios de tema del sistema


Los dispositivos suelen incluir temas claros y oscuros, que hacen referencia a un amplio conjunto de preferencias
de apariencia que se pueden establecer en el nivel del sistema operativo. Las aplicaciones deben respetar estos
temas del sistema y responder inmediatamente cuando cambia el tema del sistema.
Aplicar un tema a una :::no-loc(Xamarin.Forms):::
aplicación
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: las aplicaciones pueden responder dinámicamente a los cambios de estilo en tiempo de
ejecución mediante la DynamicResource extensión de marcado. Esta extensión de marcado es similar a la
StaticResource extensión de marcado, en que ambos usan una clave de diccionario para capturar un valor de un
ResourceDictionary . Sin embargo, mientras que la StaticResource extensión de marcado realiza una búsqueda de
un solo diccionario, la DynamicResource extensión de marcado mantiene un vínculo a la clave del diccionario. Por
consiguiente, si se reemplaza el valor asociado a la clave, el cambio se aplica a VisualElement . Esto permite que
los encargados de tiempo de ejecución se implementen en :::no-loc(Xamarin.Forms)::: aplicaciones.
El proceso de implementación de los procesos en tiempo de ejecución en una :::no-loc(Xamarin.Forms)::: aplicación
es el siguiente:
1. Defina los recursos para cada tema en un ResourceDictionary .
2. Consume recursos de tema en la aplicación mediante la DynamicResource extensión de marcado.
3. Establezca un tema predeterminado en el archivo app. Xaml de la aplicación.
4. Agregue código para cargar un tema en tiempo de ejecución.

IMPORTANT
Use la StaticResource extensión de marcado si no necesita cambiar el tema de la aplicación en tiempo de ejecución.

Las siguientes capturas de pantallas muestran páginas con temas, con la aplicación de iOS que usa un tema claro y
la aplicación Android con un tema oscuro:
NOTE
El cambio de un tema en tiempo de ejecución requiere el uso de estilos XAML y no es posible actualmente con CSS.

Definir temas
Un tema se define como una colección de objetos de recursos almacenados en un ResourceDictionary .
En el ejemplo siguiente se muestra el LightTheme de la aplicación de ejemplo:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.LightTheme">
<Color x:Key="PageBackgroundColor">White</Color>
<Color x:Key="NavigationBarColor">WhiteSmoke</Color>
<Color x:Key="PrimaryColor">WhiteSmoke</Color>
<Color x:Key="SecondaryColor">Black</Color>
<Color x:Key="PrimaryTextColor">Black</Color>
<Color x:Key="SecondaryTextColor">White</Color>
<Color x:Key="TertiaryTextColor">Gray</Color>
<Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

En el ejemplo siguiente se muestra el DarkTheme de la aplicación de ejemplo:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.DarkTheme">
<Color x:Key="PageBackgroundColor">Black</Color>
<Color x:Key="NavigationBarColor">Teal</Color>
<Color x:Key="PrimaryColor">Teal</Color>
<Color x:Key="SecondaryColor">White</Color>
<Color x:Key="PrimaryTextColor">White</Color>
<Color x:Key="SecondaryTextColor">White</Color>
<Color x:Key="TertiaryTextColor">WhiteSmoke</Color>
<Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

Cada ResourceDictionary contiene Color recursos que definen sus respectivos temas, cada uno con
ResourceDictionary valores de clave idénticos. Para obtener más información acerca de los diccionarios de
recursos, consulte diccionarios de recursos.

IMPORTANT
Se requiere un archivo de código subyacente para cada ResourceDictionary , que llama al InitializeComponent
método. Esto es necesario para que un objeto CLR que representa el tema elegido se pueda crear en tiempo de ejecución.

Establecer un tema predeterminado


Una aplicación requiere un tema predeterminado, de modo que los controles tengan valores para los recursos que
consumen. Se puede establecer un tema predeterminado combinando el tema ResourceDictionary en el nivel de
aplicación ResourceDictionary que se define en app. Xaml :

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.App">
<Application.Resources>
<ResourceDictionary Source="Themes/LightTheme.xaml" />
</Application.Resources>
</Application>

Para obtener más información sobre la combinación de diccionarios de recursos, vea diccionarios de recursos
combinados.

Consumir recursos de tema


Cuando una aplicación desea consumir un recurso almacenado en un objeto ResourceDictionary que representa
un tema, debe hacerlo con la DynamicResource extensión de marcado. Esto garantiza que, si se selecciona un tema
diferente en tiempo de ejecución, se aplicarán los valores del nuevo tema.
En el ejemplo siguiente se muestran tres estilos de la aplicación de ejemplo que se pueden aplicar a los Label
objetos:
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.App">
<Application.Resources>

<Style x:Key="LargeLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
Value="{DynamicResource SecondaryTextColor}" />
<Setter Property="FontSize"
Value="30" />
</Style>

<Style x:Key="MediumLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
Value="{DynamicResource PrimaryTextColor}" />
<Setter Property="FontSize"
Value="25" />
</Style>

<Style x:Key="SmallLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
Value="{DynamicResource TertiaryTextColor}" />
<Setter Property="FontSize"
Value="15" />
</Style>

</Application.Resources>
</Application>

Estos estilos se definen en el Diccionario de recursos de nivel de aplicación, de modo que se puedan usar en varias
páginas. Cada estilo consume recursos de tema con la DynamicResource extensión de marcado.
Estos estilos los utilizan las páginas:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ThemingDemo"
x:Class="ThemingDemo.UserSummaryPage"
Title="User Summary"
BackgroundColor="{DynamicResource PageBackgroundColor}">
...
<ScrollView>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200" />
<RowDefinition Height="120" />
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Grid BackgroundColor="{DynamicResource PrimaryColor}">
<Label Text="Face-Palm Monkey"
VerticalOptions="Center"
Margin="15"
Style="{StaticResource MediumLabelStyle}" />
...
</Grid>
<StackLayout Grid.Row="1"
Margin="10">
<Label Text="This monkey reacts appropriately to ridiculous assertions and actions."
Style="{StaticResource SmallLabelStyle}" />
<Label Text=" &#x2022; Cynical but not unfriendly."
Style="{StaticResource SmallLabelStyle}" />
<Label Text=" &#x2022; Seven varieties of grimaces."
Style="{StaticResource SmallLabelStyle}" />
<Label Text=" &#x2022; Doesn't laugh at your jokes."
Style="{StaticResource SmallLabelStyle}" />
</StackLayout>
...
</Grid>
</ScrollView>
</ContentPage>

Cuando un recurso de tema se utiliza directamente, se debe usar con la DynamicResource extensión de marcado.
Sin embargo, cuando se consume un estilo que utiliza la DynamicResource extensión de marcado, se debe usar con
la StaticResource extensión de marcado.
Para obtener más información sobre la aplicación de estilos, consulte aplicar :::no-loc(Xamarin.Forms)::: estilos a
aplicaciones con estilos XAML. Para obtener más información sobre la DynamicResource extensión de marcado, vea
estilos :::no-loc(Xamarin.Forms)::: dinámicos en .

Cargar un tema en tiempo de ejecución


Cuando se selecciona un tema en tiempo de ejecución, la aplicación debe:
1. Quitar el tema actual de la aplicación. Esto se logra borrando la MergedDictionaries propiedad del nivel de la
aplicación ResourceDictionary .
2. Cargar el tema seleccionado. Esto se logra agregando una instancia del tema seleccionado a la
MergedDictionaries propiedad del nivel de la aplicación ResourceDictionary .

Cualquier VisualElement objeto que establezca propiedades con la DynamicResource extensión de marcado
aplicará entonces los nuevos valores de tema. Esto se debe DynamicResource a que la extensión de marcado
mantiene un vínculo a las claves del diccionario. Por lo tanto, cuando se reemplazan los valores asociados a las
claves, los cambios se aplican a los VisualElement objetos.
En la aplicación de ejemplo, se selecciona un tema a través de una página modal que contiene un Picker . En el
código siguiente se muestra el OnPickerSelectionChanged método, que se ejecuta cuando cambia el tema
seleccionado:

void OnPickerSelectionChanged(object sender, EventArgs e)


{
Picker picker = sender as Picker;
Theme theme = (Theme)picker.SelectedItem;

ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;


if (mergedDictionaries != null)
{
mergedDictionaries.Clear();

switch (theme)
{
case Theme.Dark:
mergedDictionaries.Add(new DarkTheme());
break;
case Theme.Light:
default:
mergedDictionaries.Add(new LightTheme());
break;
}
}
}

Vínculos relacionados
Cómo hacerlo (ejemplo)
Responder a cambios de tema del sistema
Diccionarios de recursos
Estilos dinámicos en :::no-loc(Xamarin.Forms):::
Aplicación de estilo a aplicaciones :::no-loc(Xamarin.Forms)::: con estilos XAML
Responder a los cambios de tema del sistema en
:::no-loc(Xamarin.Forms)::: las aplicaciones
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
Los dispositivos suelen incluir temas claros y oscuros, que hacen referencia a un amplio conjunto de preferencias
de apariencia que se pueden establecer en el nivel del sistema operativo. Las aplicaciones deben respetar estos
temas del sistema y responder inmediatamente cuando cambia el tema del sistema.
El tema del sistema puede cambiar por diversos motivos, en función de la configuración del dispositivo. Esto
incluye el tema del sistema que el usuario cambia explícitamente, cambia debido a la hora del día y cambia debido
a factores ambientales como la luz baja.
:::no-loc(Xamarin.Forms)::: las aplicaciones pueden responder a los cambios del tema del sistema mediante el
consumo de recursos con la AppThemeBinding extensión de marcado y los SetAppThemeColor métodos de
SetOnAppTheme<T> extensión y.

Se deben cumplir los siguientes requisitos para :::no-loc(Xamarin.Forms)::: que respondan a un cambio de tema
del sistema:
:::no-loc(Xamarin.Forms)::: 4.6.0.967 o superior.
iOS 13 o posterior.
Android 10 (API 29) o superior.
Compilación 14393 o posterior de UWP.
En las siguientes capturas de pantallas se muestran páginas con tema, para temas del sistema claros y oscuros en
iOS y Android:
Definir y consumir recursos de tema
Los recursos de los temas claros y oscuros se pueden usar con la AppThemeBinding extensión de marcado y los
SetAppThemeColor métodos de SetOnAppTheme<T> extensión y. Con estos enfoques, los recursos se aplican
automáticamente en función del valor del tema del sistema actual. Además, los objetos que consumen estos
recursos se actualizan automáticamente si cambia el tema del sistema mientras se ejecuta una aplicación.
AppThemeBinding (extensión de marcado )
La AppThemeBinding extensión de marcado le permite consumir un recurso, como una imagen o un color,
basándose en el tema del sistema actual:

<ContentPage ...>
<StackLayout Margin="20">
<Label Text="This text is green in light mode, and red in dark mode."
TextColor="{AppThemeBinding Light=Green, Dark=Red}" />
<Image Source="{AppThemeBinding Light=lightlogo.png, Dark=darklogo.png}" />
</StackLayout>
</ContentPage>

En este ejemplo, el color del texto de la primera Label se establece en verde cuando el dispositivo usa su tema
claro y se establece en rojo cuando el dispositivo está usando su tema oscuro. Del mismo modo, Image muestra
un archivo de imagen diferente según el tema actual del sistema.
Además, los recursos definidos en una ResourceDictionary se pueden usar con la StaticResource extensión de
marcado:
<ContentPage ...>
<ContentPage.Resources>

<!-- Light colors -->


<Color x:Key="LightPrimaryColor">WhiteSmoke</Color>
<Color x:Key="LightSecondaryColor">Black</Color>

<!-- Dark colors -->


<Color x:Key="DarkPrimaryColor">Teal</Color>
<Color x:Key="DarkSecondaryColor">White</Color>

<Style x:Key="ButtonStyle"
TargetType="Button">
<Setter Property="BackgroundColor"
Value="{AppThemeBinding Light={StaticResource LightPrimaryColor}, Dark={StaticResource
DarkPrimaryColor}}" />
<Setter Property="TextColor"
Value="{AppThemeBinding Light={StaticResource LightSecondaryColor}, Dark={StaticResource
DarkSecondaryColor}}" />
</Style>

</ContentPage.Resources>

<Grid BackgroundColor="{AppThemeBinding Light={StaticResource LightPrimaryColor}, Dark={StaticResource


DarkPrimaryColor}}">
<Button Text="MORE INFO"
Style="{StaticResource ButtonStyle}" />
</Grid>
</ContentPage>

En este ejemplo, el color de fondo del Grid y el Button estilo cambian en función de si el dispositivo usa su tema
claro o el tema oscuro.
Para obtener más información sobre la AppThemeBinding extensión de marcado, consulte extensión de marcado
AppThemeBinding.
Métodos de extensión
:::no-loc(Xamarin.Forms)::: incluye SetAppThemeColor SetOnAppTheme<T> métodos de extensión y que permiten
VisualElement a los objetos responder a los cambios de tema del sistema.

El SetAppThemeColor método permite Color especificar objetos que se establecerán en una propiedad de destino
basada en el tema del sistema actual:

Label label = new Label();


label.SetAppThemeColor(Label.TextColorProperty, Color.Green, Color.Red);

En este ejemplo, el color del texto de Label está establecido en verde cuando el dispositivo usa su tema claro y se
establece en rojo cuando el dispositivo está usando su tema oscuro.
El SetOnAppTheme<T> método permite especificar objetos de tipo T que se establecerán en una propiedad de
destino basada en el tema del sistema actual:

Image image = new Image();


image.SetOnAppTheme<FileImageSource>(Image.SourceProperty, "lightlogo.png", "darklogo.png");

En este ejemplo, el Image lightlogo.png se muestra cuando el dispositivo usa su tema claro y darklogo.png
cuando el dispositivo está usando su tema oscuro.
Detectar el tema del sistema actual
El tema del sistema actual se puede detectar obteniendo el valor de la Application.RequestedTheme propiedad:

OSAppTheme currentTheme = Application.Current.RequestedTheme;

La RequestedTheme propiedad devuelve un OSAppTheme miembro de enumeración. La enumeración OSAppTheme


define los miembros siguientes:
Unspecified , que indica que el dispositivo está utilizando un tema no especificado.
Light , que indica que el dispositivo está usando su tema claro.
Dark , que indica que el dispositivo está usando su tema oscuro.

Establecer el tema del usuario actual


El tema que utiliza la aplicación se puede establecer con la Application.UserAppTheme propiedad, que es de tipo
OSAppTheme , independientemente del tema del sistema que esté operativo actualmente:

Application.Current.UserAppTheme = OSAppTheme.Dark;

En este ejemplo, la aplicación está configurada para usar el tema definido para el modo oscuro del sistema,
independientemente de qué tema del sistema esté operativo actualmente.

NOTE
Establezca la UserAppTheme propiedad en OSAppTheme.Unspecified como predeterminada en el tema del sistema
operativo.

Reaccionar a cambios de tema


El tema del sistema en un dispositivo puede cambiar por diversos motivos, en función de cómo esté configurado
el dispositivo. :::no-loc(Xamarin.Forms)::: se puede notificar a las aplicaciones cuando el tema del sistema cambia
mediante el control del Application.RequestedThemeChanged evento:

Application.Current.RequestedThemeChanged += (s, a) =>


{
// Respond to the theme change
};

El AppThemeChangedEventArgs objeto, que acompaña al RequestedThemeChanged evento, tiene una propiedad única
denominada RequestedTheme , de tipo OSAppTheme . Esta propiedad se puede examinar para detectar el tema del
sistema solicitado.

IMPORTANT
Para responder a los cambios de tema en Android, debe incluir la ConfigChanges.UiMode marca en el Activity atributo
de la MainActivity clase.

Vínculos relacionados
SystemThemes (ejemplo)
AppThemeBinding (extensión de marcado)
Diccionarios de recursos
Aplicación de estilo a aplicaciones :::no-loc(Xamarin.Forms)::: con estilos XAML
Xamarin.FormsVisual
18/12/2020 • 2 minutes to read • Edit Online

Objeto visual de material de Xamarin.Forms


Xamarin.FormsEl material visual se puede usar para crear Xamarin.Forms aplicaciones que parezcan idénticas o en
gran medida en iOS y Android.

Creación de un Xamarin.Forms representador visual


Xamarin.FormsVisual permite que los representadores se apliquen de forma selectiva a los VisualElement objetos,
sin tener que realizar vistas de subclases Xamarin.Forms .
:::no-loc(Xamarin.Forms)::: Material visual
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
El diseño de material es un sistema de diseño de bien fundamentadas creado por Google que prescribe el
tamaño, el color, el espaciado y otros aspectos de cómo deben buscarse y comportarse las vistas y los diseños.
:::no-loc(Xamarin.Forms)::: El material visual se puede usar para aplicar reglas de diseño de materiales a :::no-
loc(Xamarin.Forms)::: aplicaciones y crear aplicaciones que tengan una apariencia muy idéntica en iOS y Android.
Cuando el material visual está habilitado, las vistas admitidas adoptan el mismo diseño multiplataforma, lo que
crea una apariencia unificada.

El proceso para habilitar el :::no-loc(Xamarin.Forms)::: contenido visual en la aplicación es el siguiente:


1. Agregue el :::no-loc(Xamarin.Forms)::: . Paquete NuGet de visual. material en los proyectos de la plataforma
iOS y Android. Este paquete de NuGet ofrece representadores de diseño de material optimizados en iOS y
Android. En iOS, el paquete proporciona la dependencia transitiva a Xamarin. iOS. MaterialComponents, que
es un enlace de C# a los componentes de material de Google para iOS. En Android, el paquete proporciona
destinos de compilación para asegurarse de que el TargetFramework está correctamente configurado.
2. Inicialice el material visual en cada proyecto de plataforma. Para obtener más información, vea Initialize
material visual.
3. Cree controles visuales de materiales estableciendo la Visual propiedad en Material en cualquier página
que deba adoptar las reglas de diseño del material. Para obtener más información, consulte consumo de
representadores de materiales.
4. opta Personalizar los controles de materiales. Para obtener más información, vea Personalizar controles de
materiales.

IMPORTANT
En Android, el material visual requiere una versión mínima de 5,0 (API 21) o superior, y un TargetFramework de la versión
9,0 (API 28). Además, el proyecto de plataforma requiere las bibliotecas de compatibilidad con Android 28.0.0 o superior, y
su tema debe heredar de un tema de componentes de materiales o seguir heredando de un tema AppCompat. Para
obtener más información, consulte Introducción a los componentes de materiales para Android.

El material visual actualmente admite los siguientes controles:


ActivityIndicator
Button
CheckBox
DatePicker
Editor
Entry
Frame
Picker
ProgressBar
Slider
Stepper
TimePicker

Los representadores de materiales tienen en cuenta los controles de materiales, que aplican las reglas de diseño
de materiales. Funcionalmente, los representadores de material no son diferentes a los representadores
predeterminados. Para obtener más información, vea personalizar el material visual.

Inicializar el material visual


Después de instalar el :::no-loc(Xamarin.Forms)::: . Paquete NuGet de visual. material , los representadores de
materiales deben inicializarse en cada proyecto de plataforma.
En iOS, esto debe ocurrir en AppDelegate.CS invocando el :::no-loc(Xamarin.Forms):::.FormsMaterial.Init
método después del :::no-loc(Xamarin.Forms):::.Forms.Init método:

global:::::no-loc(Xamarin.Forms):::.Forms.Init();
global:::::no-loc(Xamarin.Forms):::.FormsMaterial.Init();

En Android, esto debe ocurrir en MainActivity.CS invocando el :::no-loc(Xamarin.Forms):::.FormsMaterial.Init


método después del :::no-loc(Xamarin.Forms):::.Forms.Init método:

global:::::no-loc(Xamarin.Forms):::.Forms.Init(this, savedInstanceState);
global:::::no-loc(Xamarin.Forms):::.FormsMaterial.Init(this, savedInstanceState);

Aplicar el material visual


Las aplicaciones pueden habilitar el material visual estableciendo la VisualElement.Visual propiedad en una
página, diseño o vista, para Material :

<ContentPage Visual="Material"
...>
...
</ContentPage>

El código de C# equivalente es el siguiente:

ContentPage contentPage = new ContentPage();


contentPage.Visual = VisualMarker.Material;

Al establecer la VisualElement.Visual propiedad en Material , se indica a la aplicación que use los


representadores de material visual en lugar de los representadores predeterminados. La Visual propiedad se
puede establecer en cualquier tipo que implemente IVisual , con la VisualMarker clase que proporciona las
IVisual propiedades siguientes:

Default : indica que la vista debe representarse mediante el representador predeterminado.


MatchParent : indica que la vista debe utilizar el mismo representador que su elemento primario directo.
Material : indica que la vista debe representarse mediante un representador de materiales.

IMPORTANT
La Visual propiedad se define en la VisualElement clase, con vistas que heredan el Visual valor de propiedad de sus
elementos primarios. Por consiguiente, si Visual se establece la propiedad en, ContentPage se garantiza que cualquier
vista admitida en la página utilizará ese visual. Además, la Visual propiedad se puede invalidar en una vista.

Las siguientes capturas de pantallas muestran una interfaz de usuario que se representa mediante los
representadores predeterminados:

Las siguientes capturas de pantallas muestran la misma interfaz de usuario que se representa mediante los
representadores de materiales:

Las principales diferencias visibles entre los representadores predeterminados y los representadores de
materiales, que se muestran aquí, son que los representadores de materiales ponen en mayúsculas Button el
texto y redondean las esquinas de los Frame bordes. Sin embargo, los representadores de materiales usan
controles nativos y, por lo tanto, puede haber diferencias en la interfaz de usuario entre plataformas para áreas
como fuentes, sombras, colores y elevación.

NOTE
Los componentes del diseño material se adhieren a las directrices de Google. Como resultado, los representadores de
diseño de material se sesgan hacia ese tamaño y comportamiento. Cuando necesite un mayor control de los estilos o el
comportamiento, puede crear su propio efecto, comportamientoo representador personalizado para obtener los detalles
que necesita.

Personalización del material visual


El paquete de NuGet visual de material es una colección de representadores que obtienen los :::no-
loc(Xamarin.Forms)::: controles. La personalización de los controles visuales de materiales es idéntica a la
personalización de los controles predeterminados.
Los efectos son la técnica recomendada cuando el objetivo es personalizar un control existente. Si existe un
representador visual de material, es menos trabajo personalizar el control con un efecto que en la subclase del
representador. Para obtener más información sobre los efectos, vea :::no-loc(Xamarin.Forms)::: efectos.
Los representadores personalizados son la técnica recomendada cuando no existe un representador de
materiales. Las siguientes clases de representador se incluyen con el material visual:
MaterialButtonRenderer
MaterialCheckBoxRenderer
MaterialEntryRenderer
MaterialFrameRenderer
MaterialProgressBarRenderer
MaterialDatePickerRenderer
MaterialTimePickerRenderer
MaterialPickerRenderer
MaterialActivityIndicatorRenderer
MaterialEditorRenderer
MaterialSliderRenderer
MaterialStepperRenderer

La subclase de un representador de materiales es casi idéntica a los representadores que no son de materiales.
Sin embargo, al exportar un representador que subclase un representador de materiales, debe proporcionar un
tercer argumento al ExportRenderer atributo que especifica el VisualMarker.MaterialVisual tipo:

using :::no-loc(Xamarin.Forms):::.Material.Android;

[assembly: ExportRenderer(typeof(ProgressBar), typeof(CustomMaterialProgressBarRenderer), new[] {


typeof(VisualMarker.MaterialVisual) })]
namespace MyApp.Android
{
public class CustomMaterialProgressBarRenderer : MaterialProgressBarRenderer
{
//...
}
}
En este ejemplo, ExportRendererAttribute especifica que la CustomMaterialProgressBarRenderer clase se utilizará
para representar la ProgressBar vista, con el IVisual tipo registrado como tercer argumento.

NOTE
Se usará un representador que especifique un IVisual tipo, como parte de su ExportRendererAttribute , para
representar en las vistas, en lugar del representador predeterminado. En el momento de la selección del representador, la
Visual propiedad de la vista se inspecciona y se incluye en el proceso de selección del representador.

Para obtener más información acerca de los representadores personalizados, vea representadores
personalizados.

Vínculos relacionados
Material visual (ejemplo)
Creación de un :::no-loc(Xamarin.Forms)::: representador visual
:::no-loc(Xamarin.Forms)::: Efectos
Representadores personalizados
Creación de un :::no-loc(Xamarin.Forms):::
representador visual
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: Visual permite crear representadores y aplicarlos de forma selectiva a los
VisualElement objetos, sin tener que realizar vistas de subclases :::no-loc(Xamarin.Forms)::: . Se usará un
representador que especifique un IVisual tipo, como parte de su ExportRendererAttribute , para representar en
las vistas, en lugar del representador predeterminado. En el momento de la selección del representador, la Visual
propiedad de la vista se inspecciona y se incluye en el proceso de selección del representador.

IMPORTANT
Actualmente Visual no se puede cambiar la propiedad una vez representada la vista, pero esto cambiará en una versión
futura.

El proceso para crear y utilizar un :::no-loc(Xamarin.Forms)::: representador visual es el siguiente:


1. Cree representadores de plataforma para la vista necesaria. Para obtener más información, vea crear
representadores.
2. Cree un tipo que se derive de IVisual . Para obtener más información, vea crear un tipo IVisual.
3. Registre el IVisual tipo como parte del ExportRendererAttribute que decora los representadores. Para
obtener más información, consulte registrar el tipo IVisual.
4. Para usar el representador visual, establezca la Visual propiedad de la vista en el IVisual nombre. Para
obtener más información, vea usar el representador visual.
5. opta Registre un nombre para el IVisual tipo. Para obtener más información, vea registrar un nombre para el
tipo IVisual.

Creación de representadores de plataforma


Para obtener información sobre cómo crear una clase de representador, vea representadores personalizados. Sin
embargo, tenga en cuenta que un :::no-loc(Xamarin.Forms)::: representador visual se aplica a una vista sin tener
que subclaser la vista.
Las clases de representador que se describen aquí implementan un personalizado Button que muestra el texto
con una sombra.
iOS
En el ejemplo de código siguiente se muestra el representador de botón para iOS:
public class CustomButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);

if (e.OldElement != null)
{
// Cleanup
}

if (e.NewElement != null)
{
Control.TitleShadowOffset = new CoreGraphics.CGSize(1, 1);
Control.SetTitleShadowColor(Color.Black.ToUIColor(), UIKit.UIControlState.Normal);
}
}
}

Android
En el ejemplo de código siguiente se muestra el representador de botón para Android:

public class CustomButtonRenderer : :::no-loc(Xamarin.Forms):::.Platform.Android.AppCompat.ButtonRenderer


{
public CustomButtonRenderer(Context context) : base(context)
{
}

protected override void OnElementChanged(ElementChangedEventArgs<Button> e)


{
base.OnElementChanged(e);

if (e.OldElement != null)
{
// Cleanup
}

if (e.NewElement != null)
{
Control.SetShadowLayer(5, 3, 3, Color.Black.ToAndroid());
}
}
}

Creación de un tipo de IVisual


En la biblioteca multiplataforma, cree un tipo que derive de IVisual :

public class CustomVisual : IVisual


{
}

Después, el CustomVisual tipo se puede registrar en las clases de representador, lo Button que permite a los
objetos participar en el uso de los representadores.

Registrar el tipo de IVisual


En los proyectos de la plataforma, agregue ExportRendererAttribute en el nivel de ensamblado:
[assembly: ExportRenderer(typeof(:::no-loc(Xamarin.Forms):::.Button), typeof(CustomButtonRenderer), new[] {
typeof(CustomVisual) })]
namespace VisualDemos.iOS
{
public class CustomButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
// ...
}
}
}

En este ejemplo del proyecto de plataforma iOS, el ExportRendererAttribute especifica que la


CustomButtonRenderer clase se usará para representar objetos de consumo Button , con el IVisual tipo
registrado como tercer argumento. Se usará un representador que especifique un IVisual tipo, como parte de su
ExportRendererAttribute , para representar en las vistas, en lugar del representador predeterminado.

Usar el representador visual


Un Button objeto puede optar por usar las clases de representador estableciendo su Visual propiedad en
Custom :

<Button Visual="Custom"
Text="CUSTOM BUTTON"
BackgroundColor="{StaticResource PrimaryColor}"
TextColor="{StaticResource SecondaryTextColor}"
HorizontalOptions="FillAndExpand" />

NOTE
En XAML, un convertidor de tipos quita la necesidad de incluir el sufijo "visual" en el Visual valor de propiedad. Sin
embargo, también se puede especificar el nombre de tipo completo.

El código de C# equivalente es el siguiente:

Button button = new Button { Text = "CUSTOM BUTTON", ... };


button.Visual = new CustomVisual();

En el momento de la selección del representador, la Visual propiedad de Button se inspecciona y se incluye en el


proceso de selección del representador. Si no se encuentra un representador, se :::no-loc(Xamarin.Forms)::: usará el
representador predeterminado.
Las capturas de pantallas siguientes muestran el representado Button , que muestra su texto con una sombra:
Registro de un nombre para el tipo IVisual
VisualAttribute Se puede utilizar para registrar opcionalmente un nombre diferente para el IVisual tipo. Este
enfoque se puede usar para resolver conflictos de nomenclatura entre diferentes bibliotecas visuales o en
situaciones en las que solo desea hacer referencia a un visual con un nombre diferente al nombre de su tipo.
VisualAttribute Debe definirse en el nivel de ensamblado en la biblioteca multiplataforma o en el proyecto de
plataforma:

[assembly: Visual("MyVisual", typeof(CustomVisual))]

IVisual A continuación, el tipo se puede consumir a través de su nombre registrado:

<Button Visual="MyVisual"
... />

NOTE
Al consumir un visual a través de su nombre registrado, se debe incluir cualquier sufijo "visual".

Vínculos relacionados
Material visual (ejemplo)
Objeto visual de material de :::no-loc(Xamarin.Forms):::
Representadores personalizados
:::no-loc(Xamarin.Forms)::: Administrador de
estado visual
18/12/2020 • 33 minutes to read • Edit Online

Descargar el ejemplo
Use el administrador de estado visual para realizar cambios en los elementos XAML en función de los
Estados visuales establecidos desde el código.
Visual State Manager (VSM) proporciona una manera estructurada de realizar cambios visuales en la
interfaz de usuario desde el código. En la mayoría de los casos, la interfaz de usuario de la aplicación se
define en XAML y este código XAML incluye el marcado que describe cómo afecta visual State Manager a
los objetos visuales de la interfaz de usuario.
El VSM presenta el concepto de Estados visuales. Una :::no-loc(Xamarin.Forms)::: vista como Button puede
tener varias apariencias visuales diferentes en función de su estado subyacente — , independientemente de
si está deshabilitada, presionada o tiene el foco de entrada. Estos son los Estados del botón.
Los Estados visuales se recopilan en grupos de Estados visuales. Todos los Estados visuales de un grupo de
Estados visuales se excluyen mutuamente. Los Estados visuales y los grupos de Estados visuales se
identifican mediante cadenas de texto simples.
:::no-loc(Xamarin.Forms):::Visual State Manager define un grupo de Estados visuales denominado
"CommonStates" con los siguientes Estados visuales:
"Normal"
Disponible
Dedica
Seleccionadas
Este grupo de Estados visuales es compatible con todas las clases que derivan de VisualElement , que es la
clase base para View y Page .
También puede definir sus propios grupos de Estados visuales y Estados visuales, como se muestra en este
artículo.

NOTE
:::no-loc(Xamarin.Forms)::: los desarrolladores familiarizados con los desencadenadores son conscientes de que los
desencadenadores también pueden realizar cambios en los objetos visuales de la interfaz de usuario en función de
los cambios en las propiedades de una vista o en la activación de eventos. Sin embargo, el uso de desencadenadores
para tratar varias combinaciones de estos cambios puede resultar bastante confuso. Históricamente, el administrador
de estado visual se presentó en entornos basados en XAML de Windows para aliviar la confusión resultante de las
combinaciones de Estados visuales. Con VSM, los Estados visuales dentro de un grupo de Estados visuales siempre
son mutuamente excluyentes. En cualquier momento, solo un estado de cada grupo es el estado actual.

Estados comunes
El administrador de estado visual le permite incluir marcado en el archivo XAML que puede cambiar la
apariencia visual de una vista si la vista es normal, está deshabilitada o tiene el foco de entrada. Estos se
conocen como Estados comunes.
Por ejemplo, supongamos que tiene una Entry vista en la página y desea que el aspecto visual del Entry
cambie de las siguientes maneras:
El debe tener un fondo rosa cuando el Entry está deshabilitado.
Entry
El Entry debe tener normalmente un fondo verde.
Entry Debe expandirse al doble de su alto normal cuando tenga el foco de entrada.

Puede adjuntar el marcado de VSM a una vista individual o puede definirlo en un estilo si se aplica a varias
vistas. En las dos secciones siguientes se describen estos enfoques.
Marcado de VSM en una vista
Para adjuntar el marcado de VSM a una Entry vista, separe primero el Entry en las etiquetas inicial y final:

<Entry FontSize="18">

</Entry>

Se le asigna un tamaño de fuente explícito, ya que uno de los Estados utilizará la FontSize propiedad para
doblar el tamaño del texto en Entry .
A continuación, inserte VisualStateManager.VisualStateGroups etiquetas entre esas etiquetas:

<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>

</VisualStateManager.VisualStateGroups>
</Entry>

VisualStateGroups es una propiedad enlazable adjunta definida por la VisualStateManager clase. (Para
obtener más información sobre las propiedades enlazables asociadas, vea el artículo propiedades adjuntas).
Así es como VisualStateGroups se adjunta la propiedad al Entry objeto.
La VisualStateGroups propiedad es de tipo VisualStateGroupList , que es una colección de
VisualStateGroup objetos. Dentro de las VisualStateManager.VisualStateGroups etiquetas, inserte un par de
VisualStateGroup etiquetas para cada grupo de Estados visuales que quiera incluir:

<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">

</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>

Observe que la VisualStateGroup etiqueta tiene un x:Name atributo que indica el nombre del grupo. La
VisualStateGroup clase define una Name propiedad que puede usar en su lugar:

<VisualStateGroup Name="CommonStates">

Puede usar o, x:Name Name pero no ambos en el mismo elemento.


La VisualStateGroupclase define una propiedad denominada States , que es una colección de
VisualState objetos. States es la propiedad de contenido de VisualStateGroups , por lo que puede incluir
las VisualState etiquetas directamente entre las VisualStateGroup etiquetas. (Las propiedades de
contenido se describen en el artículo sintaxis básica de XAML).
El siguiente paso consiste en incluir un par de etiquetas para cada estado visual de ese grupo. También se
pueden identificar mediante x:Name o Name :

<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">

</VisualState>

<VisualState x:Name="Focused">

</VisualState>

<VisualState x:Name="Disabled">

</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>

VisualState define una propiedad denominada Setters , que es una colección de Setter objetos. Estos
son los mismos Setter objetos que se usan en un Style objeto.
Setters no es la propiedad de contenido de VisualState , por lo que es necesario incluir etiquetas de
elemento de propiedad para la Setters propiedad:

<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>

</VisualState.Setters>
</VisualState>

<VisualState x:Name="Focused">
<VisualState.Setters>

</VisualState.Setters>
</VisualState>

<VisualState x:Name="Disabled">
<VisualState.Setters>

</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>

Ahora puede insertar uno o más Setter objetos entre cada par de Setters etiquetas. Estos son los
Setter objetos que definen los Estados visuales descritos anteriormente:
<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Lime" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Property="FontSize" Value="36" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Pink" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>

Cada Setter etiqueta indica el valor de una propiedad determinada cuando ese estado es actual. Cualquier
propiedad a la que hace referencia un Setter objeto debe estar respaldada por una propiedad enlazable.
El marcado similar a este es la base de la página de VSM en la vista en el programa de ejemplo
VsmDemos . La página incluye tres Entry vistas, pero solo la segunda tiene el marcado de VSM asociado:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:VsmDemos"
x:Class="VsmDemos.MainPage"
Title="VSM Demos">

<StackLayout>
<StackLayout.Resources>
<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
</Style>

<Style TargetType="Label">
<Setter Property="Margin" Value="20, 30, 20, 0" />
<Setter Property="FontSize" Value="Large" />
</Style>
</StackLayout.Resources>

<Label Text="Normal Entry:" />


<Entry />
<Label Text="Entry with VSM: " />
<Entry>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Lime" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Property="FontSize" Value="36" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Pink" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Entry.Triggers>
<DataTrigger TargetType="Entry"
Binding="{Binding Source={x:Reference entry3},
Path=Text.Length}"
Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label Text="Entry to enable 2nd Entry:" />
<Entry x:Name="entry3"
Text=""
Placeholder="Type something to enable 2nd Entry" />
</StackLayout>
</ContentPage>

Observe que el segundo Entry también tiene DataTrigger como parte de su Trigger colección. Esto hace
que Entry se deshabilite el objeto hasta que se escriba algo en el tercero Entry . Esta es la página en el
inicio que se ejecuta en iOS, Android y el Plataforma universal de Windows (UWP):
El estado visual actual es "deshabilitado", por lo que el segundo plano Entry es rosa en las pantallas iOS y
Android. La implementación de UWP de no Entry permite establecer el color de fondo cuando Entry está
deshabilitado.
Al escribir un texto en el tercero Entry , el segundo Entry cambia al estado "normal" y el fondo es ahora
Lima:

Cuando toca el segundo Entry , obtiene el foco de entrada. Cambia al estado "centrado" y se expande a
dos veces su alto:
Tenga en cuenta que el no Entry conserva el fondo de Lima cuando obtiene el foco de entrada. Como el
administrador de estado visual cambia entre los Estados visuales, las propiedades establecidas por el
estado anterior no están establecidas. Tenga en cuenta que los Estados visuales son mutuamente
excluyentes. El estado "normal" no significa únicamente que Entry esté habilitado. Significa que Entry
está habilitado y no tiene el foco de entrada.
Si desea que el Entry tenga un fondo Lima en el estado "centrado", agregue otro Setter a ese estado
visual:

<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Property="FontSize" Value="36" />
<Setter Property="BackgroundColor" Value="Lime" />
</VisualState.Setters>
</VisualState>

Para que estos Setter objetos funcionen correctamente, VisualStateGroup debe contener VisualState
objetos para todos los Estados de ese grupo. Si hay un estado visual que no tiene ningún Setter objeto,
inclúyalo de todos modos como una etiqueta vacía:

<VisualState x:Name="Normal" />

Marcado de Visual State Manager en un estilo


A menudo es necesario compartir el mismo marcado de Visual State Manager entre dos o más vistas. En
este caso, querrá colocar el marcado en una Style definición.
Este es el implícito existente Style para los Entry elementos de la página de VSM en la vista :

<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
</Style>

Agregue Setter etiquetas para la VisualStateManager.VisualStateGroups propiedad enlazable adjunta:


<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
<Setter Property="VisualStateManager.VisualStateGroups">

</Setter>
</Style>

La propiedad de contenido de Setter es Value , por lo que el valor de la Value propiedad se puede
especificar directamente dentro de esas etiquetas. Esa propiedad es de tipo VisualStateGroupList :

<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>

</VisualStateGroupList>
</Setter>
</Style>

Dentro de esas etiquetas puede incluir uno o varios VisualStateGroup objetos:

<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">

</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

El resto del marcado de VSM es igual que antes.


Esta es la página de VSM en estilo que muestra el marcado de VSM completo:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VsmDemos.VsmInStylePage"
Title="VSM in Style">
<StackLayout>
<StackLayout.Resources>
<Style TargetType="Entry">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="FontSize" Value="18" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Lime" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Property="FontSize" Value="36" />
<Setter Property="BackgroundColor" Value="Lime" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Pink" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

<Style TargetType="Label">
<Setter Property="Margin" Value="20, 30, 20, 0" />
<Setter Property="FontSize" Value="Large" />
</Style>
</StackLayout.Resources>

<Label Text="Normal Entry:" />


<Entry />
<Label Text="Entry with VSM: " />
<Entry>
<Entry.Triggers>
<DataTrigger TargetType="Entry"
Binding="{Binding Source={x:Reference entry3},
Path=Text.Length}"
Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label Text="Entry to enable 2nd Entry:" />
<Entry x:Name="entry3"
Text=""
Placeholder="Type something to enable 2nd Entry" />
</StackLayout>
</ContentPage>

Ahora todas las Entry vistas de esta página responden de la misma manera a sus Estados visuales.
Observe también que el estado "Focused" ahora incluye un segundo Setter que proporciona cada Entry
fondo de verde lima también cuando tiene el foco de entrada:
Estados visuales en :::no-loc(Xamarin.Forms):::
En la tabla siguiente se enumeran los Estados visuales que se definen en :::no-loc(Xamarin.Forms)::: :

C L A SS STAT ES M Á S IN F O RM A C IÓ N

Button Pressed Estados visuales del botón

CheckBox IsChecked Estados visuales de la casilla

CarouselView DefaultItem , CurrentItem , Estados visuales de CarouselView


PreviousItem , NextItem

ImageButton Pressed Estados visuales de ImageButton

RadioButton IsChecked Estados visuales de RadioButton

Switch On , Off Cambiar Estados visuales

VisualElement Normal , Disabled , Focused , Estados comunes


Selected

Se puede tener acceso a cada uno de estos Estados a través del grupo de Estados visuales denominado
CommonStates .

Además, CollectionView implementa el Selected Estado. Para obtener más información, vea cambiar el
color de los elementos seleccionados.

Establecer el estado en varios elementos


En los ejemplos anteriores, los Estados visuales se adjuntaron y funcionaban en elementos únicos. Sin
embargo, también es posible crear Estados visuales que estén asociados a un único elemento, pero que
establezcan propiedades en otros elementos dentro del mismo ámbito. Esto evita tener que repetir Estados
visuales en cada elemento en el que operan los Estados.
El Setter tipo tiene una TargetName propiedad, de tipo string , que representa el elemento de destino
que Setter manipulará para un estado visual. Cuando TargetName se define la propiedad, Setter
establece el Property del elemento definido en TargetName a Value :
<Setter TargetName="label"
Property="Label.TextColor"
Value="Red" />

En este ejemplo, un Label denominado tendrá label su TextColor propiedad establecida en Red . Al
establecer la TargetName propiedad, debe especificar la ruta de acceso completa a la propiedad en
Property . Por consiguiente, para establecer la TextColor propiedad en Label , Property se especifica
como Label.TextColor .

NOTE
Cualquier propiedad a la que hace referencia un Setter objeto debe estar respaldada por una propiedad enlazable.

En la página VSM with Setter TargetName en el ejemplo VsmDemos se muestra cómo establecer el
estado en varios elementos, a partir de un único grupo de Estados visuales. El archivo XAML se compone de
un StackLayout que contiene un Label elemento, un Entry y un Button :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VsmDemos.VsmSetterTargetNamePage"
Title="VSM with Setter TargetName">
<StackLayout Margin="10">
<Label Text="What is the capital of France?" />
<Entry x:Name="entry"
Placeholder="Enter answer" />
<Button Text="Reveal answer">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
<Setter TargetName="entry"
Property="Entry.Text"
Value="Paris" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</StackLayout>
</ContentPage>

El marcado de VSM está asociado a StackLayout . Hay dos Estados mutuamente excluyentes, denominados
"normal" y "pressed", donde cada estado contiene VisualState etiquetas.
El estado "normal" está activo cuando Button no se presiona y se puede escribir una respuesta a la
pregunta:

El estado "pressed" se activa cuando Button se presiona:


"Pressed" VisualState especifica que cuando Button se presiona, su Scale propiedad se cambiará del
valor predeterminado de 1 a 0,8. Además, el Entry nombre tendrá entry su Text propiedad establecida
en París. Por lo tanto, el resultado es que, cuando Button se presiona, se cambia la escala para que sea
ligeramente menor y Entry muestra París. A continuación, cuando Button se libera, se vuelve a escalar a
su valor predeterminado de 1 y Entry muestra cualquier texto escrito anteriormente.

IMPORTANT
Actualmente no se admiten las rutas de acceso de propiedades en Setter los elementos que especifican la
TargetName propiedad.

Definir sus propios Estados visuales


Cada clase que deriva de VisualElement admite los Estados comunes "normal", "Focused" y "Disabled".
Además, la CollectionView clase admite el estado "seleccionado". Internamente, la VisualElement clase
detecta cuándo está habilitada o deshabilitada, o enfocada o no enfocada, y llama al método estático [
VisualStateManager.GoToState ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualStateManager. GoToState ( :::no-
loc(Xamarin.Forms)::: . Método VisualElement, System. String)):

VisualStateManager.GoToState(this, "Focused");

Este es el único código de Visual State Manager que encontrará en la VisualElement clase. Dado que
GoToState se llama a para cada objeto basado en cada clase que se deriva de VisualElement , puede usar el
administrador de estado visual con cualquier VisualElement objeto para responder a estos cambios.
Curiosamente, no se hace referencia explícitamente al nombre del grupo de Estados visuales
"CommonStates" en VisualElement . El nombre de grupo no forma parte de la API para el administrador de
estado visual. En uno de los dos programas de ejemplo mostrados hasta el momento, puede cambiar el
nombre del grupo de "CommonStates" a cualquier otro y el programa seguirá funcionando. El nombre de
grupo es simplemente una descripción general de los Estados de ese grupo. Se entiende implícitamente
que los Estados visuales de cualquier grupo se excluyen mutuamente: un estado y solo un estado es actual
en cualquier momento.
Si desea implementar sus propios Estados visuales, deberá llamar a VisualStateManager.GoToState desde el
código. Lo más frecuente es que realice esta llamada desde el archivo de código subyacente de la clase de
página.
En la página de validación de VSM en el ejemplo VsmDemos se muestra cómo usar el administrador de
estado visual en conexión con la validación de entrada. El archivo XAML está compuesto de un StackLayout
que contiene dos Label elementos, un Entry y un Button :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VsmDemos.VsmValidationPage"
Title="VSM Validation">
<StackLayout x:Name="stackLayout"
Padding="10, 10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValidityStates">
<VisualState Name="Valid">
<VisualState.Setters>
<Setter TargetName="helpLabel"
Property="Label.TextColor"
Value="Transparent" />
<Setter TargetName="entry"
Property="Entry.BackgroundColor"
Value="Lime" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Invalid">
<VisualState.Setters>
<Setter TargetName="entry"
Property="Entry.BackgroundColor"
Value="Pink" />
<Setter TargetName="submitButton"
Property="Button.IsEnabled"
Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label Text="Enter a U.S. phone number:"
FontSize="Large" />
<Entry x:Name="entry"
Placeholder="555-555-5555"
FontSize="Large"
Margin="30, 0, 0, 0"
TextChanged="OnTextChanged" />
<Label x:Name="helpLabel"
Text="Phone number must be of the form 555-555-5555, and not begin with a 0 or 1" />
<Button x:Name="submitButton"
Text="Submit"
FontSize="Large"
Margin="0, 20"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>

El marcado de VSM se adjunta a StackLayout (con nombre stackLayout ). Hay dos Estados mutuamente
excluyentes, denominados "Valid" y "Invalid", donde cada estado contiene VisualState etiquetas.
Si no Entry contiene un número de teléfono válido, el estado actual es "no válido", por lo que Entry tiene
un fondo rosa, el segundo Label es visible y el Button está deshabilitado:

Cuando se escribe un número de teléfono válido, el estado actual se convierte en "válido". El Entry obtiene
un fondo de Lima, el segundo Label desaparece y Button ahora está habilitado:
El archivo de código subyacente es responsable de controlar el TextChanged evento desde Entry . El
controlador utiliza una expresión regular para determinar si la cadena de entrada es válida o no. El método
en el archivo de código subyacente denominado GoToState llama al VisualStateManager.GoToState método
estático para stackLayout :

public partial class VsmValidationPage : ContentPage


{
public VsmValidationPage()
{
InitializeComponent();

GoToState(false);
}

void OnTextChanged(object sender, TextChangedEventArgs args)


{
bool isValid = Regex.IsMatch(args.NewTextValue, @"^[2-9]\d{2}-\d{3}-\d{4}$");
GoToState(isValid);
}

void GoToState(bool isValid)


{
string visualState = isValid ? "Valid" : "Invalid";
VisualStateManager.GoToState(stackLayout, visualState);
}
}

Observe también que GoToState se llama al método desde el constructor para inicializar el estado. Siempre
debe haber un estado actual. Pero en el código no hay ninguna referencia al nombre del grupo de Estados
visuales, aunque se hace referencia a él en el XAML como "ValidationStates" para fines de claridad.
Observe que el archivo de código subyacente solo debe tener en cuenta el objeto en la página que define
los Estados visuales y para llamar a VisualStateManager.GoToState para este objeto. Esto se debe a que
ambos Estados visuales tienen como destino varios objetos en la página.
Es posible que se pregunte: Si el archivo de código subyacente debe hacer referencia al objeto en la página
que define los Estados visuales, ¿por qué no se puede acceder al archivo de código subyacente
simplemente a este y a otros objetos directamente? Seguramente podría. Sin embargo, la ventaja de utilizar
el VSM es que puede controlar el modo en que los elementos visuales reaccionan a un estado diferente
completamente en XAML, lo que mantiene todo el diseño de la interfaz de usuario en una ubicación. Esto
evita establecer la apariencia visual al obtener acceso a los elementos visuales directamente desde el código
subyacente.

Desencadenadores de estado visual


Los Estados visuales admiten desencadenadores de estado, que son un grupo especializado de
desencadenadores que definen las condiciones en las que VisualState se debe aplicar.
Los desencadenadores de estado se agregan a la colección StateTriggers de una clase VisualState . Esta
colección puede contener un único desencadenador de estado o varios desencadenadores de estado. Se
aplicará una clase VisualState cuando cualquier desencadenador de estado de la colección esté activo.
Al usar desencadenadores de estado para controlar los estados visuales, :::no-loc(Xamarin.Forms)::: usa las
siguientes reglas de prioridad para determinar qué desencadenador, y la clase VisualState
correspondiente, se activará:
1. Cualquier desencadenador derivado de StateTriggerBase .
2. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowWidth .
3. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowHeight .
Si hay varios desencadenadores activos simultáneamente (por ejemplo, dos desencadenadores
personalizados), tiene prioridad el primer desencadenador declarado en el marcado.
Para obtener más información sobre desencadenadores de estado, vea Desencadenadores de estado.

Usar el administrador de estado visual para el diseño adaptable


:::no-loc(Xamarin.Forms):::Normalmente, una aplicación que se ejecuta en un teléfono puede verse en una
relación de aspecto vertical u horizontal, y :::no-loc(Xamarin.Forms)::: se puede cambiar el tamaño de un
programa que se ejecuta en el escritorio para que asuma muchos tamaños diferentes y relaciones de
aspecto. Una aplicación bien diseñada podría mostrar su contenido de forma diferente para estos diversos
factores de página o de formulario de ventana.
A veces, esta técnica se conoce como diseño adaptable. Dado que el diseño adaptable solo implica los
objetos visuales de un programa, es una aplicación ideal de Visual State Manager.
Un ejemplo sencillo es una aplicación que muestra una pequeña colección de botones que afectan al
contenido de la aplicación. En el modo vertical, estos botones se pueden mostrar en una fila horizontal en la
parte superior de la página:

En el modo horizontal, la matriz de botones puede moverse a un lado y mostrarse en una columna:
Desde la parte superior a la inferior, el programa se ejecuta en el Plataforma universal de Windows, Android
e iOS.
La página de diseño adaptable de VSM en el ejemplo VsmDemos define un grupo denominado
"OrientationStates" con dos Estados visuales denominados "vertical" y "horizontal". (Un enfoque más
complejo podría basarse en varios anchos de página o de ventana diferentes).
El marcado de VSM se produce en cuatro lugares en el archivo XAML. El StackLayout denominado
mainStack contiene el menú y el contenido, que es un Image elemento. StackLayout Debe tener una
orientación vertical en modo vertical y una orientación horizontal en modo horizontal:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VsmDemos.VsmAdaptiveLayoutPage"
Title="VSM Adaptive Layout">

<StackLayout x:Name="mainStack">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OrientationStates">
<VisualState Name="Portrait">
<VisualState.Setters>
<Setter Property="Orientation" Value="Vertical" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Landscape">
<VisualState.Setters>
<Setter Property="Orientation" Value="Horizontal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ScrollView x:Name="menuScroll">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OrientationStates">
<VisualState Name="Portrait">
<VisualState.Setters>
<Setter Property="Orientation" Value="Horizontal" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Landscape">
<VisualState.Setters>
<Setter Property="Orientation" Value="Vertical" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<StackLayout x:Name="menuStack">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OrientationStates">
<VisualState Name="Portrait">
<VisualState.Setters>
<Setter Property="Orientation" Value="Horizontal" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Landscape">
<VisualState.Setters>
<Setter Property="Orientation" Value="Vertical" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<StackLayout.Resources>
<Style TargetType="Button">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup Name="OrientationStates">
<VisualState Name="Portrait">
<VisualState.Setters>
<Setter Property="HorizontalOptions"
Value="CenterAndExpand" />
<Setter Property="Margin" Value="10, 5" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Landscape">
<VisualState.Setters>
<Setter Property="VerticalOptions" Value="CenterAndExpand"
/>
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="Margin" Value="10" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</StackLayout.Resources>

<Button Text="Banana"
Command="{Binding SelectedCommand}"
CommandParameter="Banana.jpg" />
<Button Text="Face Palm"
Command="{Binding SelectedCommand}"
CommandParameter="FacePalm.jpg" />
<Button Text="Monkey"
Command="{Binding SelectedCommand}"
CommandParameter="monkey.png" />
<Button Text="Seated Monkey"
Command="{Binding SelectedCommand}"
CommandParameter="SeatedMonkey.jpg" />
</StackLayout>
</ScrollView>

<Image x:Name="image"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand" />
</StackLayout>
</ContentPage>

El ScrollView denominado interno menuScroll y el StackLayout denominado menuStack implementan el


menú de botones. La orientación de estos diseños es opuesta a mainStack . El menú debe ser horizontal en
modo vertical y vertical en modo horizontal.
La cuarta sección del marcado de VSM está en un estilo implícito para los propios botones. Este marcado
establece VerticalOptions HorizontalOptions Margin las propiedades, y específicas de las orientaciones
vertical y horizontal.
El archivo de código subyacente establece la BindingContext propiedad de menuStack para implementar
Button comandos y también adjunta un controlador al SizeChanged evento de la página:

public partial class VsmAdaptiveLayoutPage : ContentPage


{
public VsmAdaptiveLayoutPage ()
{
InitializeComponent ();

SizeChanged += (sender, args) =>


{
string visualState = Width > Height ? "Landscape" : "Portrait";
VisualStateManager.GoToState(mainStack, visualState);
VisualStateManager.GoToState(menuScroll, visualState);
VisualStateManager.GoToState(menuStack, visualState);

foreach (View child in menuStack.Children)


{
VisualStateManager.GoToState(child, visualState);
}
};

SelectedCommand = new Command<string>((filename) =>


{
image.Source = ImageSource.FromResource("VsmDemos.Images." + filename);
});

menuStack.BindingContext = this;
}

public ICommand SelectedCommand { private set; get; }


}

El SizeChanged controlador llama a VisualStateManager.GoToState para los StackLayout dos ScrollView


elementos y y, a continuación, recorre en bucle los elementos secundarios de menuStack para llamar a
VisualStateManager.GoToState para los Button elementos.

Puede parecer que si el archivo de código subyacente puede controlar los cambios de orientación más
directamente estableciendo las propiedades de los elementos en el archivo XAML, pero el administrador de
estado visual es definitivamente un enfoque más estructurado. Todos los objetos visuales se guardan en el
archivo XAML, donde resultan más fáciles de examinar, mantener y modificar.
Visual State Manager con Xamarin. University
:::no-loc(Xamarin.Forms)::: 3,0 vídeo de Visual State Manager

Vínculos relacionados
VsmDemos
Desencadenadores de estado
Elegir un :::no-loc(Xamarin.Forms)::: diseño
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: las clases de diseño permiten organizar y agrupar los controles de interfaz de usuario en
la aplicación. La elección de una clase de diseño requiere saber cómo el diseño coloca sus elementos secundarios y
cómo el diseño dimensiona sus elementos secundarios. Además, puede ser necesario anidar diseños para crear el
diseño deseado.
En la imagen siguiente se muestran los diseños típicos que se pueden lograr con las :::no-loc(Xamarin.Forms):::
clases de diseño principales:

StackLayout
Un StackLayout organiza los elementos de una pila unidimensional, ya sea horizontal o verticalmente. La
Orientation propiedad especifica la dirección de los elementos y la orientación predeterminada es Vertical .
StackLayout normalmente se usa para organizar una subsección de la interfaz de usuario en una página.
En el código XAML siguiente se muestra cómo crear un StackLayout objeto vertical que contiene tres Label
objetos:

<StackLayout Margin="20,35,20,25">
<Label Text="The StackLayout has its Margin property set, to control the rendering position of the
StackLayout." />
<Label Text="The Padding property can be set to specify the distance between the StackLayout and its
children." />
<Label Text="The Spacing property can be set to specify the distance between views in the StackLayout." />
</StackLayout>

En StackLayout , si el tamaño de un elemento no se establece explícitamente, se expande para rellenar el ancho


disponible o el alto si la Orientation propiedad está establecida en Horizontal .
A StackLayout menudo se usa como diseño primario, que contiene otros diseños secundarios. Sin embargo,
StackLayout no se debe utilizar para reproducir un Grid diseño utilizando una combinación de StackLayout
objetos. En el código siguiente se muestra un ejemplo de esta mala práctica:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Details.HomePage"
Padding="0,20,0,0">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</StackLayout>
</StackLayout>
</ContentPage>

Es una pérdida de tiempo porque se realizan cálculos de diseño innecesarios. En su lugar, el diseño deseado puede
lograrse mejor mediante el uso de Grid .

TIP
Al utilizar StackLayout , asegúrese de que solo un elemento secundario está establecido en LayoutOptions.Expands .
Esta propiedad garantiza que el elemento secundario especificado ocupa el mayor espacio que el StackLayout puede
asignarle y es poco rentable realizar estos cálculos más de una vez.

Para obtener más información, vea :::no-loc(Xamarin.Forms)::: StackLayout.

Cuadrícula
Grid Se usa para mostrar elementos en filas y columnas, que pueden tener tamaños proporcionales o absolutos.
Las filas y columnas de una cuadrícula se especifican con las RowDefinitions ColumnDefinitions propiedades y.
Para colocar los elementos en Grid celdas específicas, use Grid.Column las Grid.Row propiedades adjuntas y.
Para que los elementos abarquen varias filas y columnas, utilice Grid.RowSpan y Grid.ColumnSpan las propiedades
adjuntas.

NOTE
Un Grid diseño no se debe confundir con las tablas y no está diseñado para presentar datos tabulares. A diferencia de las
tablas HTML, una Grid está pensada para diseñar el contenido. Para Mostrar datos tabulares, considere la posibilidad de
usar un control ListView, CollectionViewo TableView.

En el siguiente código XAML se muestra cómo crear un Grid con dos filas y dos columnas:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Text="Column 0, Row 0"
WidthRequest="200" />
<Label Grid.Column="1"
Text="Column 1, Row 0" />
<Label Grid.Row="1"
Text="Column 0, Row 1" />
<Label Grid.Column="1"
Grid.Row="1"
Text="Column 1, Row 1" />
</Grid>

En este ejemplo, el ajuste de tamaño funciona de la siguiente manera:


Cada fila tiene un alto explícito de 50 unidades independientes del dispositivo.
El ancho de la primera columna se establece en Auto y, por tanto, es tan ancha como sea necesario para sus
elementos secundarios. En este caso, 200 unidades independientes del dispositivo de ancho para acomodar el
ancho de la primera Label .
El espacio se puede distribuir dentro de una columna o fila mediante el ajuste automático de tamaño, lo que
permite que las columnas y las filas se ajusten a su contenido. Esto se logra estableciendo el alto de un
RowDefinition , o el ancho de un ColumnDefinition , en Auto . El ajuste de tamaño proporcional también se
puede usar para distribuir el espacio disponible entre las filas y las columnas de la cuadrícula mediante
proporciones ponderadas. Esto se logra estableciendo el alto de un RowDefinition , o el ancho de
ColumnDefinition , en un valor que usa el * operador.

Cau t i on

Intente asegurarse de que el tamaño de las filas y columnas es el máximo posible Auto . Cada fila o columna de
tamaño automático hará que el motor de diseño tenga que realizar cálculos de diseño adicionales. En su lugar, use
filas y columnas de tamaño fijo si es posible. También puede establecer filas y columnas para ocupar una cantidad
proporcional de espacio con el GridUnitType.Star valor de enumeración.
Para obtener más información, vea :::no-loc(Xamarin.Forms)::: Grid.

FlexLayout
Un FlexLayout es similar a un StackLayout en que muestra los elementos secundarios de forma horizontal o
vertical en una pila. Sin embargo, FlexLayout también puede ajustar sus elementos secundarios si hay
demasiados para caber en una sola fila o columna, y también permite un control más granular del tamaño, la
orientación y la alineación de sus elementos secundarios.
En el código XAML siguiente se muestra cómo crear un FlexLayout que muestra sus vistas en una sola columna:

<FlexLayout Direction="Column"
AlignItems="Center"
JustifyContent="SpaceEvenly">
<Label Text="FlexLayout in Action" />
<Button Text="Button" />
<Label Text="Another Label" />
</FlexLayout>
En este ejemplo, el diseño funciona de la siguiente manera:
La Direction propiedad se establece en Column , lo que hace que los elementos secundarios de FlexLayout se
organicen en una sola columna de elementos.
La AlignItems propiedad se establece en Center , lo que hace que cada elemento se Centre horizontalmente.
La JustifyContent propiedad se establece en SpaceEvenly , que asigna todo el espacio vertical sobrante por
igual entre todos los elementos y por encima del primer elemento, y por debajo del último elemento.
Para obtener más información, vea :::no-loc(Xamarin.Forms)::: FlexLayout.

RelativeLayout
RelativeLayout Se utiliza para colocar y ajustar el tamaño de los elementos en relación con las propiedades del
diseño o de los elementos del mismo nivel. De forma predeterminada, un elemento se coloca en la esquina
superior izquierda del diseño. RelativeLayout Se puede usar para crear interfaces de IU que escalen
proporcionalmente entre tamaños de dispositivo.
Dentro de RelativeLayout , las posiciones y los tamaños se especifican como restricciones. Las restricciones tienen
Factor propiedades y Constant , que se pueden usar para definir posiciones y tamaños como múltiplos (o
fracciones) de propiedades de otros objetos, además de una constante. Además, las constantes pueden ser
negativas.

NOTE
Un RelativeLayout admite la colocación de elementos fuera de sus propios límites.

En el código XAML siguiente se muestra cómo organizar los elementos en RelativeLayout :


<RelativeLayout>
<BoxView Color="Blue"
HeightRequest="50"
WidthRequest="50"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=0}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=0}" />
<BoxView Color="Red"
HeightRequest="50"
WidthRequest="50"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=.85}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=0}" />
<BoxView x:Name="pole"
Color="Gray"
WidthRequest="15"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=.75}"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=.45}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=.25}" />
<BoxView Color="Green"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=.10, Constant=10}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=.2, Constant=20}"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, ElementName=pole,
Property=X, Constant=15}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=pole,
Property=Y, Constant=0}" />
</RelativeLayout>

En este ejemplo, el diseño funciona de la siguiente manera:


El azul BoxView recibe un tamaño explícito de unidades independientes del dispositivo 50x50. Se coloca en la
esquina superior izquierda del diseño, que es la posición predeterminada.
Al rojo BoxView se le asigna un tamaño explícito de unidades independientes del dispositivo 50x50. Se coloca
en la esquina superior derecha del diseño.
Al gris BoxView se le asigna un ancho explícito de 15 unidades independientes del dispositivo, y su alto se
establece en el 75% del alto de su elemento primario.
El color verde BoxView no tiene un tamaño explícito. Su posición se establece en relación con el BoxView
nombre pole .

WARNING
Evite el uso de un RelativeLayout siempre que sea posible. Como resultado, la CPU tendrá que realizar mucho más
trabajo.

Para obtener más información, vea :::no-loc(Xamarin.Forms)::: RelativeLayout.

AbsoluteLayout
AbsoluteLayout Se utiliza para colocar y ajustar el tamaño de los elementos mediante valores explícitos o valores
relativos al tamaño del diseño. La posición se especifica en la esquina superior izquierda del elemento secundario
en relación con la esquina superior izquierda de AbsoluteLayout .
AbsoluteLayoutDebe considerarse como un diseño especial para usarse solo cuando se puede imponer un tamaño
en los elementos secundarios o cuando el tamaño del elemento no afecta a la posición de otros elementos
secundarios. Un uso estándar de este diseño es crear una superposición, que abarca la página con otros controles,
quizás para impedir que el usuario interactúe con los controles normales de la página.

IMPORTANT
Las propiedades HorizontalOptions y VerticalOptions no tienen ningún efecto en los elementos secundarios de un
AbsoluteLayout .

Dentro de AbsoluteLayout , la AbsoluteLayout.LayoutBounds propiedad adjunta se utiliza para especificar la


posición horizontal, la posición vertical, el ancho y el alto de un elemento. Además, la AbsoluteLayout.LayoutFlags
propiedad adjunta especifica cómo se interpretarán los límites del diseño.
En el siguiente código XAML se muestra cómo organizar los elementos de AbsoluteLayout :

<AbsoluteLayout Margin="40">
<BoxView Color="Red"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5, 0, 100, 100"
Rotation="30" />
<BoxView Color="Green"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5, 0, 100, 100"
Rotation="60" />
<BoxView Color="Blue"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5, 0, 100, 100" />
</AbsoluteLayout>

En este ejemplo, el diseño funciona de la siguiente manera:


BoxView A cada una se le asigna un tamaño explícito de 100x100 y se muestra en la misma posición, centrado
horizontalmente.
El color rojo BoxView gira 30 grados y el verde BoxView gira 60 grados.
En cada BoxView , la AbsoluteLayout.LayoutFlags propiedad adjunta se establece en PositionProportional , lo
que indica que la posición es proporcional al espacio restante después de que se tenga en cuenta el ancho y el
alto.
Cau t i on

Evite utilizar la AbsoluteLayout.AutoSize propiedad siempre que sea posible, ya que hará que el motor de diseño
realice cálculos de diseño adicionales.
Para obtener más información, vea :::no-loc(Xamarin.Forms)::: AbsoluteLayout.

Transparencia de entrada
Cada elemento visual tiene una InputTransparent propiedad que se utiliza para definir si el elemento recibe la
entrada. Su valor predeterminado es false , asegurándose de que el elemento recibe la entrada.
Cuando esta propiedad se establece en una clase de diseño, su valor se transfiere a los elementos secundarios. Por
consiguiente, si InputTransparent se establece la propiedad en true en una clase de diseño, todos los elementos
del diseño no recibirán una entrada.

Rendimiento del diseño


Para obtener el mejor rendimiento posible del diseño, siga las instrucciones que se indican en optimizar el
rendimiento del diseño.
Además, el rendimiento de la representación de páginas también se puede mejorar mediante la compresión de
diseño, que quita los diseños especificados del árbol visual. Para obtener más información, vea compresión de
diseño.

Vínculos relacionados
Diseño (ejemplo)
:::no-loc(Xamarin.Forms)::: Diseños (vídeo)
:::no-loc(Xamarin.Forms)::: StackLayout
:::no-loc(Xamarin.Forms)::: Cuadrícula
:::no-loc(Xamarin.Forms)::: FlexLayout
:::no-loc(Xamarin.Forms)::: AbsoluteLayout
:::no-loc(Xamarin.Forms)::: RelativeLayout
Optimización del rendimiento del diseño
Compresión de diseño
Xamarin.Forms AbsoluteLayout
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo

AbsoluteLayout Se utiliza para colocar y cambiar el tamaño de los elementos secundarios usando valores
explícitos. La posición se especifica en la esquina superior izquierda del elemento secundario con respecto a la
esquina superior izquierda de AbsoluteLayout , en unidades independientes del dispositivo. AbsoluteLayout
también implementa una característica de posicionamiento y ajuste de tamaño proporcional. Además, a
diferencia de otras clases de diseño, AbsoluteLayout puede colocar los elementos secundarios para que se
superpongan.
AbsoluteLayout Debe considerarse como un diseño especial para usarse solo cuando se puede imponer un
tamaño en los elementos secundarios o cuando el tamaño del elemento no afecta a la posición de otros
elementos secundarios.
La AbsoluteLayout clase define las siguientes propiedades:
LayoutBounds , de tipo Rectangle , que es una propiedad adjunta que representa la posición y el tamaño de un
elemento secundario. El valor predeterminado de esta propiedad es (0,0, tamaño automático, ajuste
automático de tamaño).
LayoutFlags , de tipo AbsoluteLayoutFlags , que es una propiedad adjunta que indica si las propiedades de los
límites de diseño utilizadas para colocar y ajustar el tamaño del elemento secundario se interpretan
proporcionalmente. El valor predeterminado de esta propiedad es AbsoluteLayoutFlags.None .
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos y con estilo. Para obtener más información sobre las propiedades adjuntas,
consulte Xamarin.Forms propiedades adjuntas.
La AbsoluteLayout clase se deriva de la Layout<T> clase, que define una Children propiedad de tipo IList<T> .
La Children propiedad es ContentProperty de la Layout<T> clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

TIP
Para obtener el mejor rendimiento posible del diseño, siga las instrucciones que se indican en optimizar el rendimiento del
diseño.

Posición y tamaño secundarios


La posición y el tamaño de los elementos secundarios en un AbsoluteLayout se define estableciendo la
AbsoluteLayout.LayoutBounds propiedad adjunta de cada elemento secundario, usando valores absolutos o
valores proporcionales. Los valores absolutos y proporcionales se pueden mezclar para los elementos
secundarios cuando la posición debe escalarse, pero el tamaño debe permanecer fijo, o viceversa. Para obtener
información sobre los valores absolutos, vea posicionamiento absoluto y ajuste de tamaño. Para obtener
información sobre los valores proporcionales, vea posicionamiento proporcional y ajuste de tamaño.
La AbsoluteLayout.LayoutBounds propiedad adjunta se puede establecer utilizando dos formatos,
independientemente de si se usan valores absolutos o proporcionados:
x, y . Con este formato, los x y valores y indican la posición de la esquina superior izquierda del elemento
secundario con respecto a su elemento primario. El elemento secundario no tiene restricciones y su propio
tamaño.
x, y, width, height . Con este formato, los x y valores y indican la posición de la esquina superior
izquierda del elemento secundario con respecto a su elemento primario, mientras que los width height
valores y indican el tamaño del elemento secundario.
Para especificar que un elemento secundario tiene un tamaño horizontal o vertical, o ambos, establezca los
width valores y/o height en la AbsoluteLayout.AutoSize propiedad. Sin embargo, el uso excesivo de esta
propiedad puede perjudicar el rendimiento de la aplicación, ya que hace que el motor de diseño realice cálculos
de diseño adicionales.

IMPORTANT
Las HorizontalOptions VerticalOptions propiedades y no tienen ningún efecto en los elementos secundarios de
AbsoluteLayout .

Posicionamiento y ajuste de tamaño absolutos


De forma predeterminada, AbsoluteLayout coloca y ajusta el tamaño de los elementos secundarios usando
valores absolutos, que se especifican en unidades independientes del dispositivo, que definen explícitamente
dónde se deben colocar los elementos secundarios en el diseño. Esto se logra mediante la adición de elementos
secundarios a la Children colección de AbsoluteLayout y el establecimiento de la AbsoluteLayout.LayoutBounds
propiedad adjunta en cada elemento secundario en la posición absoluta y/o los valores de tamaño.

WARNING
El uso de valores absolutos para el posicionamiento y el ajuste de tamaño de los elementos secundarios puede ser
problemático, ya que los distintos dispositivos tienen diferentes tamaños de pantalla y resoluciones. Por lo tanto, las
coordenadas del centro de la pantalla en un dispositivo pueden estar desplazadas en otros dispositivos.

En el código XAML siguiente AbsoluteLayout se muestra un cuyos elementos secundarios se colocan usando
valores absolutos:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AbsoluteLayoutDemos.Views.StylishHeaderDemoPage"
Title="Stylish header demo">
<AbsoluteLayout Margin="20">
<BoxView Color="Silver"
AbsoluteLayout.LayoutBounds="0, 10, 200, 5" />
<BoxView Color="Silver"
AbsoluteLayout.LayoutBounds="0, 20, 200, 5" />
<BoxView Color="Silver"
AbsoluteLayout.LayoutBounds="10, 0, 5, 65" />
<BoxView Color="Silver"
AbsoluteLayout.LayoutBounds="20, 0, 5, 65" />
<Label Text="Stylish Header"
FontSize="24"
AbsoluteLayout.LayoutBounds="30, 25" />
</AbsoluteLayout>
</ContentPage>

En este ejemplo, la posición de cada BoxView objeto se define utilizando los dos primeros valores absolutos que
se especifican en la AbsoluteLayout.LayoutBounds propiedad adjunta. El tamaño de cada BoxView se define
mediante los valores tercero y adelante. La posición del Label objeto se define utilizando los dos valores
absolutos que se especifican en la AbsoluteLayout.LayoutBounds propiedad adjunta. Los valores de tamaño no se
especifican para y Label , por tanto, no están restringidos y tienen el mismo tamaño. En todos los casos, los
valores absolutos representan unidades independientes del dispositivo.
En la captura de pantalla siguiente se muestra el diseño resultante:

El código de C# equivalente se muestra a continuación:


public class StylishHeaderDemoPageCS : ContentPage
{
public StylishHeaderDemoPageCS()
{
AbsoluteLayout absoluteLayout = new AbsoluteLayout
{
Margin = new Thickness(20)
};

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

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

Title = "Stylish header demo";


Content = absoluteLayout;
}
}

En este ejemplo, la posición y el tamaño de cada BoxView se definen mediante un Rectangle objeto. La posición
de Label se define utilizando un Point objeto.
En C#, también es posible establecer la posición y el tamaño de un elemento secundario de una AbsoluteLayout
después de que se haya agregado a Children la colección, utilizando el AbsoluteLayout.SetLayoutBounds método.
El primer argumento de este método es el elemento secundario y el segundo es un Rectangle objeto.

NOTE
AbsoluteLayout Que usa valores absolutos puede colocar y ajustar el tamaño de los elementos secundarios para que no
quepan dentro de los límites del diseño.

Posicionamiento proporcional y tamaño


AbsoluteLayout Puede colocar y cambiar el tamaño de los elementos secundarios usando valores proporcionales.
Para ello, se agregan elementos secundarios a la Children colección de AbsoluteLayout y se establece la
AbsoluteLayout.LayoutBounds propiedad adjunta en cada elemento secundario en la posición proporcional o en
los valores de tamaño del intervalo 0-1. Los valores de posición y tamaño se hacen proporcionales estableciendo
la AbsoluteLayout.LayoutFlags propiedad adjunta en cada elemento secundario.
La AbsoluteLayout.LayoutFlags propiedad adjunta, de tipo AbsoluteLayoutFlags , permite establecer una marca
que indica que los valores de posición y tamaño de los límites del diseño de un elemento secundario son
proporcionales al tamaño de AbsoluteLayout . Al diseñar un elemento secundario, AbsoluteLayout escala los
valores de posición y tamaño de forma adecuada a cualquier tamaño de dispositivo.
La AbsoluteLayoutFlags enumeración define los siguientes miembros:
None , indica que los valores se interpretarán como absolutos. Este es el valor predeterminado de la
AbsoluteLayout.LayoutFlags propiedad adjunta.
XProportional , indica que el x valor se interpretará como proporcional, al mismo tiempo que se tratan todos
los demás valores como absolutos.
YProportional , indica que el y valor se interpretará como proporcional, al mismo tiempo que se tratan todos
los demás valores como absolutos.
WidthProportional , indica que el width valor se interpretará como proporcional, al mismo tiempo que se
tratan todos los demás valores como absolutos.
HeightProportional , indica que el height valor se interpretará como proporcional, al mismo tiempo que se
tratan todos los demás valores como absolutos.
PositionProportional , indica que los x valores y se y interpretarán como proporcional, mientras que los
valores de tamaño se interpretan como absolutos.
SizeProportional , indica que los width valores y se height interpretarán como proporcional, mientras que
los valores de posición se interpretan como absolutos.
All , indica que todos los valores se interpretarán como proporcionales.

TIP
La AbsoluteLayoutFlags enumeración es una Flags enumeración, lo que significa que los miembros de enumeración se
pueden combinar. Esto se logra en XAML con una lista separada por comas y en C# con el operador OR bit a bit.

Por ejemplo, si usa la SizeProportional marca y establece el ancho de un elemento secundario en 0,25 y el alto
en 0,1, el elemento secundario será un cuarto del ancho de AbsoluteLayout y una décima parte del alto. La
PositionProportional marca es similar. Una posición de (0,0) coloca el elemento secundario en la esquina
superior izquierda, mientras que una posición de (1,1) coloca el elemento secundario en la esquina inferior
derecha y una posición de (0.5, 0.5) centra el elemento secundario dentro de AbsoluteLayout .
En el código XAML siguiente AbsoluteLayout se muestra un cuyos elementos secundarios se colocan usando
valores proporcionales:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AbsoluteLayoutDemos.Views.ProportionalDemoPage"
Title="Proportional demo">
<AbsoluteLayout>
<BoxView Color="Blue"
AbsoluteLayout.LayoutBounds="0.5,0,100,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Green"
AbsoluteLayout.LayoutBounds="0,0.5,25,100"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Red"
AbsoluteLayout.LayoutBounds="1,0.5,25,100"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Black"
AbsoluteLayout.LayoutBounds="0.5,1,100,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<Label Text="Centered text"
AbsoluteLayout.LayoutBounds="0.5,0.5,110,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
</AbsoluteLayout>
</ContentPage>
En este ejemplo, cada elemento secundario se coloca usando valores proporcionales pero tiene un tamaño
absoluto. Esto se logra estableciendo la AbsoluteLayout.LayoutFlags propiedad adjunta de cada elemento
secundario en PositionProportional . Los dos primeros valores que se especifican en la
AbsoluteLayout.LayoutBounds propiedad adjunta, para cada elemento secundario, definen la posición mediante
valores proporcionales. El tamaño de cada elemento secundario se define con los valores absolutos tercero y
adelante, mediante unidades independientes del dispositivo.
En la captura de pantalla siguiente se muestra el diseño resultante:

El código de C# equivalente se muestra a continuación:


public class ProportionalDemoPageCS : ContentPage
{
public ProportionalDemoPageCS()
{
BoxView blue = new BoxView { Color = Color.Blue };
AbsoluteLayout.SetLayoutBounds(blue, new Rectangle(0.5, 0, 100, 25));
AbsoluteLayout.SetLayoutFlags(blue, AbsoluteLayoutFlags.PositionProportional);

BoxView green = new BoxView { Color = Color.Green };


AbsoluteLayout.SetLayoutBounds(green, new Rectangle(0, 0.5, 25, 100));
AbsoluteLayout.SetLayoutFlags(green, AbsoluteLayoutFlags.PositionProportional);

BoxView red = new BoxView { Color = Color.Red };


AbsoluteLayout.SetLayoutBounds(red, new Rectangle(1, 0.5, 25, 100));
AbsoluteLayout.SetLayoutFlags(red, AbsoluteLayoutFlags.PositionProportional);

BoxView black = new BoxView { Color = Color.Black };


AbsoluteLayout.SetLayoutBounds(black, new Rectangle(0.5, 1, 100, 25));
AbsoluteLayout.SetLayoutFlags(black, AbsoluteLayoutFlags.PositionProportional);

Label label = new Label { Text = "Centered text" };


AbsoluteLayout.SetLayoutBounds(label, new Rectangle(0.5, 0.5, 110, 25));
AbsoluteLayout.SetLayoutFlags(label, AbsoluteLayoutFlags.PositionProportional);

Title = "Proportional demo";


Content = new AbsoluteLayout
{
Children = { blue, green, red, black, label }
};
}
}

En este ejemplo, la posición y el tamaño de cada elemento secundario se establece con el


AbsoluteLayout.SetLayoutBounds método. El primer argumento del método es el elemento secundario y el
segundo es un Rectangle objeto. La posición de cada elemento secundario se establece con valores
proporcionales, mientras que el tamaño de cada elemento secundario se establece con valores absolutos,
mediante unidades independientes del dispositivo.

NOTE
Un AbsoluteLayout que usa valores proporcionales puede colocar y cambiar el tamaño de los elementos secundarios
para que no quepan dentro de los límites del diseño usando valores fuera del intervalo de 0-1.

Vínculos relacionados
Demostraciones de AbsoluteLayout (ejemplo)
Xamarin.Forms Propiedades adjuntas
Elegir un Xamarin.Forms diseño
Mejorar el rendimiento de las Xamarin.Forms aplicaciones
:::no-loc(Xamarin.Forms):::FlexLayout
18/12/2020 • 43 minutes to read • Edit Online

Descargar el ejemplo
Use FlexLayout para apilar o ajustar una colección de vistas secundarias.
:::no-loc(Xamarin.Forms)::: FlexLayout Es nuevo en la :::no-loc(Xamarin.Forms)::: versión 3,0. Se basa en el módulo
de diseño de caja flexiblede CSS, conocido normalmente como diseño flexible o flexible , por lo que se llama
porque incluye muchas opciones flexibles para organizar los elementos secundarios dentro del diseño.
FlexLayout es similar a :::no-loc(Xamarin.Forms)::: StackLayout en que puede organizar sus elementos
secundarios horizontal y verticalmente en una pila. Sin embargo, FlexLayout también es capaz de ajustar sus
elementos secundarios si hay demasiados para caber en una sola fila o columna, y también tiene muchas
opciones para la orientación, la alineación y la adaptación a distintos tamaños de pantalla.
FlexLayout deriva de Layout<View> y hereda una Children propiedad de tipo IList<View> .
FlexLayout define seis propiedades públicas enlazables y cinco propiedades enlazables asociadas que afectan al
tamaño, la orientación y la alineación de sus elementos secundarios. (Si no está familiarizado con las propiedades
enlazables conectadas, vea el artículo propiedades adjuntas ). Estas propiedades se describen en detalle en las
secciones siguientes en las propiedades enlazables en detalle y en las propiedades enlazables asociadas
en detalle . Sin embargo, en este artículo se empieza con una sección sobre algunos escenarios de uso
comunes de FlexLayout que describen muchas de estas propiedades de forma más eficaz. Al final del artículo,
verá cómo combinar FlexLayout con hojas de estilos CSS.

Escenarios de uso comunes


El programa de ejemplo FlexLayoutDemos contiene varias páginas que muestran algunos usos comunes de
FlexLayout y permiten experimentar con sus propiedades.

Usar FlexLayout para una pila simple


En la página pila simple se muestra cómo FlexLayout puede sustituir por un, StackLayout pero con el marcado
más sencillo. Todo lo que se muestra en este ejemplo se define en la página XAML. FlexLayout Contiene cuatro
elementos secundarios:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FlexLayoutDemos"
x:Class="FlexLayoutDemos.SimpleStackPage"
Title="Simple Stack">

<FlexLayout Direction="Column"
AlignItems="Center"
JustifyContent="SpaceEvenly">

<Label Text="FlexLayout in Action"


FontSize="Large" />

<Image Source="{local:ImageResource FlexLayoutDemos.Images.SeatedMonkey.jpg}" />

<Button Text="Do-Nothing Button" />

<Label Text="Another Label" />


</FlexLayout>
</ContentPage>

Esta es la página que se ejecuta en iOS, Android y el Plataforma universal de Windows:

Se muestran tres propiedades de FlexLayout en el archivo SimpleStackPage. Xaml :


La Direction propiedad se establece en un valor de la FlexDirection enumeración. De manera
predeterminada, es Row . Establecer la propiedad en Column hace que los elementos secundarios de
FlexLayout se organicen en una sola columna de elementos.

Cuando los elementos de un FlexLayout se organizan en una columna, FlexLayout se dice que tiene un
eje principal vertical y un eje cruzado horizontal.
La AlignItems propiedad es de tipo FlexAlignItems y especifica cómo se alinean los elementos en el eje
cruzado. La Center opción hace que cada elemento se Centre horizontalmente.
Si usaba un StackLayout en lugar de un FlexLayout para esta tarea, centraría todos los elementos
asignando la HorizontalOptions propiedad de cada elemento a Center . La HorizontalOptions propiedad
no funciona para los elementos secundarios de un FlexLayout , pero la AlignItems propiedad única
alcanza el mismo objetivo. Si es necesario, puede usar la AlignSelf propiedad enlazable adjunta para
invalidar la AlignItems propiedad de elementos individuales:
<Label Text="FlexLayout in Action"
FontSize="Large"
FlexLayout.AlignSelf="Start" />

Con ese cambio, este Label se coloca en el borde izquierdo de FlexLayout cuando el orden de lectura es
de izquierda a derecha.
La JustifyContent propiedad es de tipo FlexJustify y especifica cómo se organizan los elementos en el
eje principal. La SpaceEvenly opción asigna todos los espacios verticales sobrantes entre todos los
elementos y por encima del primer elemento, y por debajo del último elemento.
Si estaba usando, debe StackLayout asignar la VerticalOptions propiedad de cada elemento a
CenterAndExpand para lograr un efecto similar. Sin embargo CenterAndExpand , la opción asignaría el doble
de espacio entre cada elemento que antes del primer elemento y después del último elemento. Puede
imitar la CenterAndExpand opción de estableciendo VerticalOptions la JustifyContent propiedad de
FlexLayout en SpaceAround .

Estas FlexLayout propiedades se describen con más detalle en la sección las propiedades enlazables en
detalle a continuación.
Usar FlexLayout para ajustar elementos
En la página de ajuste de fotografía del ejemplo FlexLayoutDemos se muestra cómo FlexLayout puede
ajustar sus elementos secundarios a filas o columnas adicionales. El archivo XAML crea una instancia FlexLayout
de y le asigna dos propiedades:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlexLayoutDemos.PhotoWrappingPage"
Title="Photo Wrapping">
<Grid>
<ScrollView>
<FlexLayout x:Name="flexLayout"
Wrap="Wrap"
JustifyContent="SpaceAround" />
</ScrollView>

<ActivityIndicator x:Name="activityIndicator"
IsRunning="True"
VerticalOptions="Center" />
</Grid>
</ContentPage>

La Direction propiedad de este FlexLayout no se establece, por lo que tiene la configuración predeterminada de
Row , lo que significa que los elementos secundarios están organizados en filas y el eje principal es horizontal.
La Wrap propiedad es de un tipo de enumeración FlexWrap . Si hay demasiados elementos para caber en una fila,
este valor de propiedad hace que los elementos se ajusten a la fila siguiente.
Observe que FlexLayout es un elemento secundario de ScrollView . Si hay demasiadas filas para caber en la
página, el ScrollView tiene una propiedad predeterminada Orientation de Vertical y permite el
desplazamiento vertical.
La JustifyContent propiedad asigna espacio sobrante en el eje principal (el eje horizontal) para que cada
elemento esté rodeado por la misma cantidad de espacio en blanco.
El archivo de código subyacente obtiene acceso a una colección de fotos de ejemplo y las agrega a la Children
colección de FlexLayout :
public partial class PhotoWrappingPage : ContentPage
{
// Class for deserializing JSON list of sample bitmaps
[DataContract]
class ImageList
{
[DataMember(Name = "photos")]
public List<string> Photos = null;
}

public PhotoWrappingPage ()
{
InitializeComponent ();

LoadBitmapCollection();
}

async void LoadBitmapCollection()


{
using (WebClient webClient = new WebClient())
{
try
{
// Download the list of stock photos
Uri uri = new Uri("https://raw.githubusercontent.com/xamarin/docs-
archive/master/Images/stock/small/stock.json");
byte[] data = await webClient.DownloadDataTaskAsync(uri);

// Convert to a Stream object


using (Stream stream = new MemoryStream(data))
{
// Deserialize the JSON into an ImageList object
var jsonSerializer = new DataContractJsonSerializer(typeof(ImageList));
ImageList imageList = (ImageList)jsonSerializer.ReadObject(stream);

// Create an Image object for each bitmap


foreach (string filepath in imageList.Photos)
{
Image image = new Image
{
Source = ImageSource.FromUri(new Uri(filepath))
};
flexLayout.Children.Add(image);
}
}
}
catch
{
flexLayout.Children.Add(new Label
{
Text = "Cannot access list of bitmap files"
});
}
}

activityIndicator.IsRunning = false;
activityIndicator.IsVisible = false;
}
}

Este es el programa que se ejecuta, desplazado progresivamente de arriba abajo:


Diseño de página con FlexLayout
Hay un diseño estándar en el diseño web denominado The Santo permite porque es un formato de diseño que es
muy conveniente, pero que a menudo es difícil de comprender con la perfección. El diseño está compuesto de un
encabezado en la parte superior de la página y un pie de página en la parte inferior, que se extiende hasta el
ancho completo de la página. Ocupar el centro de la página es el contenido principal, pero a menudo con un
menú en columnas a la izquierda del contenido y la información complementaria (a veces denominada área de
retirada ) a la derecha. En la sección 5.4.1 de la especificación de diseño de caja flexible de CSS se describe cómo
se puede realizar el diseño Santo permite con un cuadro flexible.
La página de diseño de Santa permite del ejemplo FlexLayoutDemos muestra una implementación sencilla
de este diseño mediante un FlexLayout anidado en otro. Dado que esta página está diseñada para un teléfono en
modo vertical, las áreas situadas a la izquierda y a la derecha del área de contenido son solo de 50 píxeles de
ancho:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlexLayoutDemos.HolyGrailLayoutPage"
Title="Holy Grail Layout">

<FlexLayout Direction="Column">

<!-- Header -->


<Label Text="HEADER"
FontSize="Large"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center" />

<!-- Body -->


<FlexLayout FlexLayout.Grow="1">

<!-- Content -->


<Label Text="CONTENT"
FontSize="Large"
BackgroundColor="Gray"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FlexLayout.Grow="1" />

<!-- Navigation items-->


<BoxView FlexLayout.Basis="50"
FlexLayout.Order="-1"
Color="Blue" />

<!-- Aside items -->


<BoxView FlexLayout.Basis="50"
Color="Green" />

</FlexLayout>

<!-- Footer -->


<Label Text="FOOTER"
FontSize="Large"
BackgroundColor="Pink"
HorizontalTextAlignment="Center" />
</FlexLayout>
</ContentPage>

Aquí se ejecuta:

Las áreas de navegación y de reserva se representan con a BoxView la izquierda y a la derecha.


La primera del FlexLayout archivo XAML tiene un eje principal vertical y contiene tres elementos secundarios
organizados en una columna. Estos son el encabezado, el cuerpo de la página y el pie de página. El anidado
FlexLayout tiene un eje principal horizontal con tres elementos secundarios organizados en una fila.

En este programa se muestran tres propiedades enlazables asociadas:


La Order propiedad enlazable adjunta está establecida en la primera BoxView . Esta propiedad es un
entero con un valor predeterminado de 0. Puede usar esta propiedad para cambiar el orden de diseño. Por
lo general, los desarrolladores prefieren que el contenido de la página aparezca en el marcado antes que
los elementos de navegación y los elementos. Si se establece la Order propiedad en el primero BoxView
en un valor menor que el de los demás elementos del mismo nivel, aparecerá como el primer elemento de
la fila. Del mismo modo, puede asegurarse de que un elemento aparece en último lugar si establece la
Order propiedad en un valor mayor que sus elementos relacionados.

La Basis propiedad enlazable adjunta está establecida en los dos BoxView elementos para darles un
ancho de 50 píxeles. Esta propiedad es de tipo FlexBasis , una estructura que define una propiedad
estática de tipo FlexBasis denominado Auto , que es el valor predeterminado. Puede usar Basis para
especificar un tamaño de píxel o un porcentaje que indique cuánto espacio ocupa el elemento en el eje
principal. Se denomina base porque especifica un tamaño de elemento que es la base de todos los diseños
posteriores.
La Grow propiedad se establece en el objeto anidado Layout y en el Label elemento secundario que
representa el contenido. Esta propiedad es de tipo float y su valor predeterminado es 0. Cuando se
establece en un valor positivo, todo el espacio restante a lo largo del eje principal se asigna a ese elemento
y a los elementos del mismo nivel que los valores positivos de Grow . El espacio se asigna
proporcionalmente a los valores, algo parecido a la especificación de estrella en un Grid .
La primera Grow propiedad adjunta se establece en el anidado FlexLayout , lo que indica que FlexLayout
se va a ocupar todo el espacio vertical sin usar dentro del exterior FlexLayout . La segunda Grow
propiedad adjunta se establece en Label que representa el contenido, lo que indica que este contenido va
a ocupar todo el espacio horizontal sin usar dentro del interior FlexLayout .
También hay una Shrink propiedad enlazable adjunta similar que puede usar cuando el tamaño de los
elementos secundarios supera el tamaño de, FlexLayout pero no se desea el ajuste.
Catalogar elementos con FlexLayout
La página de elementos de catálogo en el ejemplo FlexLayoutDemos es similar a la del ejemplo 1 de la
sección 1,1 de la especificación del cuadro de diseño Flex de CSS , con la diferencia de que muestra una serie de
imágenes desplazables horizontalmente y descripciones de tres Monkeys:
Cada uno de los tres Monkeys es un FlexLayout contenido en un Frame que recibe un alto y ancho explícitos, y
que también es un elemento secundario de un mayor FlexLayout . En este archivo XAML, la mayoría de las
propiedades de los FlexLayout elementos secundarios se especifican en estilos, excepto uno de los cuales es un
estilo implícito:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FlexLayoutDemos"
x:Class="FlexLayoutDemos.CatalogItemsPage"
Title="Catalog Items">
<ContentPage.Resources>
<Style TargetType="Frame">
<Setter Property="BackgroundColor" Value="LightYellow" />
<Setter Property="BorderColor" Value="Blue" />
<Setter Property="Margin" Value="10" />
<Setter Property="CornerRadius" Value="15" />
</Style>

<Style TargetType="Label">
<Setter Property="Margin" Value="0, 4" />
</Style>

<Style x:Key="headerLabel" TargetType="Label">


<Setter Property="Margin" Value="0, 8" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Blue" />
</Style>

<Style TargetType="Image">
<Setter Property="FlexLayout.Order" Value="-1" />
<Setter Property="FlexLayout.AlignSelf" Value="Center" />
</Style>

<Style TargetType="Button">
<Setter Property="Text" Value="LEARN MORE" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="White" />
<Setter Property="BackgroundColor" Value="Green" />
<Setter Property="BorderRadius" Value="20" />
</Style>
</ContentPage.Resources>

<ScrollView Orientation="Both">
<FlexLayout>
<Frame WidthRequest="300"
HeightRequest="480">

<FlexLayout Direction="Column">
<Label Text="Seated Monkey"
Style="{StaticResource headerLabel}" />
<Label Text="This monkey is laid back and relaxed, and likes to watch the world go by."
/>
<Label Text=" &#x2022; Doesn't make a lot of noise" />
<Label Text=" &#x2022; Often smiles mysteriously" />
<Label Text=" &#x2022; Sleeps sitting up" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.SeatedMonkey.jpg}"
WidthRequest="180"
HeightRequest="180" />
<Label FlexLayout.Grow="1" />
<Button />
</FlexLayout>
</Frame>

<Frame WidthRequest="300"
HeightRequest="480">

<FlexLayout Direction="Column">
<FlexLayout Direction="Column">
<Label Text="Banana Monkey"
Style="{StaticResource headerLabel}" />
<Label Text="Watch this monkey eat a giant banana." />
<Label Text=" &#x2022; More fun than a barrel of monkeys" />
<Label Text=" &#x2022; Banana not included" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.Banana.jpg}"
WidthRequest="240"
HeightRequest="180" />
<Label FlexLayout.Grow="1" />
<Button />
</FlexLayout>
</Frame>

<Frame WidthRequest="300"
HeightRequest="480">

<FlexLayout Direction="Column">
<Label Text="Face-Palm Monkey"
Style="{StaticResource headerLabel}" />
<Label Text="This monkey reacts appropriately to ridiculous assertions and actions." />
<Label Text=" &#x2022; Cynical but not unfriendly" />
<Label Text=" &#x2022; Seven varieties of grimaces" />
<Label Text=" &#x2022; Doesn't laugh at your jokes" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.FacePalm.jpg}"
WidthRequest="180"
HeightRequest="180" />
<Label FlexLayout.Grow="1" />
<Button />
</FlexLayout>
</Frame>
</FlexLayout>
</ScrollView>
</ContentPage>

El estilo implícito para Image incluye la configuración de dos propiedades enlazables adjuntas de Flexlayout :

<Style TargetType="Image">
<Setter Property="FlexLayout.Order" Value="-1" />
<Setter Property="FlexLayout.AlignSelf" Value="Center" />
</Style>

El Order valor – 1 hace que el Image elemento se muestre en primer lugar en cada una de las FlexLayout vistas
anidadas, independientemente de su posición dentro de la colección de elementos secundarios. La AlignSelf
propiedad de Center hace que Image se Centre dentro de FlexLayout . Esto invalida el valor de la AlignItems
propiedad, que tiene un valor predeterminado de Stretch , lo que significa que los Label Button elementos
secundarios y se ajustan al ancho completo de FlexLayout .
Dentro de cada una de las tres FlexLayout vistas, un espacio en blanco Label precede a Button , pero tiene un
Grow valor de 1. Esto significa que todo el espacio vertical adicional se asigna a este espacio en blanco Label , lo
que realmente envía el Button a la parte inferior.

Detalles de las propiedades enlazables


Ahora que ha visto algunas aplicaciones comunes de FlexLayout , las propiedades de FlexLayout se pueden
explorar con más detalle. FlexLayout define seis propiedades enlazables que se establecen en el FlexLayout
propio, ya sea en código o XAML, para controlar la orientación y la alineación. (Una de estas propiedades,
Position , no se trata en este artículo).

Puede experimentar con las cinco propiedades enlazables restantes mediante la página experimento del ejemplo
FlexLayoutDemos . Esta página permite agregar o quitar elementos secundarios de FlexLayout y establecer
combinaciones de las cinco propiedades enlazables. Todos los elementos secundarios de FlexLayout son Label
vistas de varios colores y tamaños, con la Text propiedad establecida en un número correspondiente a su
posición en la Children colección.
Cuando se inicia el programa, cinco Picker vistas muestran los valores predeterminados de estas cinco
FlexLayout propiedades. FlexLayout Hacia la parte inferior de la pantalla contiene tres elementos secundarios:

Cada una de las Label vistas tiene un fondo gris que muestra el espacio asignado a ese Label dentro de
FlexLayout . El fondo del FlexLayout propio es Alicia azul. Ocupa todo el área inferior de la página, excepto un
margen pequeño a la izquierda y a la derecha.
Propiedad direction
La Direction propiedad es de tipo FlexDirection , una enumeración con cuatro miembros:
Column
ColumnReverse (o "columna-inversa" en XAML)
Row , la opción predeterminada
RowReverse (o "Row-REVERSE" en XAML)

En XAML, puede especificar el valor de esta propiedad mediante los nombres de miembro de enumeración en
minúsculas, mayúsculas o minúsculas, o puede usar dos cadenas adicionales que se muestran entre paréntesis
que son iguales que los indicadores de CSS. (Las cadenas "Column-REVERSE" y "Row-REVERSE" se definen en la
FlexDirectionTypeConverter clase utilizada por el analizador de XAML).

Esta es la página del experimento que muestra (de izquierda a derecha), la Row dirección, la Column dirección y
la ColumnReverse Dirección:
Tenga en cuenta que para las Reverse Opciones, los elementos se inician en la parte derecha o inferior.
La propiedad Wrap
La Wrap propiedad es de tipo FlexWrap , una enumeración con tres miembros:
NoWrap , la opción predeterminada
Wrap
Reverse (o "ajustar-invertir" en XAML)

De izquierda a derecha, estas pantallas muestran las NoWrap Wrap Opciones, y Reverse para 12 elementos
secundarios:

Cuando la Wrap propiedad se establece en NoWrap y el eje principal está restringido (como en este programa), y
el eje principal no es ancho o lo suficientemente alto como para ajustarse a todos los elementos secundarios,
FlexLayout intenta reducir los elementos como se muestra en la captura de pantalla de iOS. Puede controlar la
reducción de los elementos con la Shrink propiedad enlazable asociada.
La propiedad JustifyContent
La JustifyContent propiedad es de tipo FlexJustify , una enumeración con seis miembros:
Start (o "Flex-Start" en XAML), el valor predeterminado
Center
End (o "Flex-end" en XAML)
SpaceBetween (o "espacio entre" en XAML)
SpaceAround (o "espacio alrededor" en XAML)
SpaceEvenly

Esta propiedad especifica cómo se espacian los elementos en el eje principal, que es el eje horizontal en este
ejemplo:

En las tres capturas de pantallas, la Wrap propiedad se establece en Wrap . El Start valor predeterminado se
muestra en la captura de pantalla anterior de Android. La captura de pantalla de iOS muestra la Center opción:
todos los elementos se mueven al centro. Las otras tres opciones a partir de la palabra Space asignan el espacio
adicional no ocupado por los elementos. SpaceBetween asigna el espacio equitativamente entre los elementos;
SpaceAround coloca el mismo espacio alrededor de cada elemento, mientras que SpaceEvenly coloca el mismo
espacio entre cada elemento y antes del primer elemento y después del último elemento de la fila.
La propiedad AlignItems
La AlignItems propiedad es de tipo FlexAlignItems , una enumeración con cuatro miembros:
Stretch , la opción predeterminada
Center
Start (o "Flex-Start" en XAML)
End (o "Flex-end" en XAML)
Esta es una de las dos propiedades (otra AlignContent ) que indica cómo se alinean los elementos secundarios en
el eje cruzado. Dentro de cada fila, los elementos secundarios se ajustan (como se muestra en la captura de
pantalla anterior) o se alinean en el inicio, el centro o el final de cada elemento, tal como se muestra en las tres
capturas de pantalla siguientes:
En la captura de pantalla de iOS, la parte superior de todos los elementos secundarios está alineada. En las
capturas de pantallas de Android, los elementos se centran verticalmente en función del elemento secundario más
alto. En la captura de pantalla de UWP, se alinean las parte inferior de todos los elementos.
Para cualquier elemento individual, el AlignItems valor se puede invalidar con la AlignSelf propiedad enlazable
asociada.
La propiedad AlignContent
La AlignContent propiedad es de tipo FlexAlignContent , una enumeración con siete miembros:
Stretch , la opción predeterminada
Center
Start (o "Flex-Start" en XAML)
End (o "Flex-end" en XAML)
SpaceBetween (o "espacio entre" en XAML)
SpaceAround (o "espacio alrededor" en XAML)
SpaceEvenly

Al igual AlignItems que, la AlignContent propiedad también alinea los elementos secundarios en el eje cruzado,
pero afecta a las filas o columnas completas:

En la captura de pantalla de iOS, ambas filas se encuentran en la parte superior; en la captura de pantalla de
Android, se encuentran en el centro; y, en la captura de pantalla de UWP, se encuentran en la parte inferior. Las
filas también se pueden espaciar de varias maneras:

AlignContent No tiene ningún efecto cuando solo hay una fila o columna.

Propiedades enlazables adjuntas en detalle


FlexLayout define cinco propiedades enlazables adjuntas. Estas propiedades se establecen en los elementos
secundarios de FlexLayout y solo pertenecen a ese elemento secundario determinado.
La propiedad AlignSelf
La AlignSelf propiedad enlazable asociada es de tipo FlexAlignSelf , una enumeración con cinco miembros:
Auto , la opción predeterminada
Stretch
Center
Start (o "Flex-Start" en XAML)
End (o "Flex-end" en XAML)

Para cualquier elemento secundario individual de FlexLayout , este valor de propiedad invalida la AlignItems
propiedad establecida en el FlexLayout propio. La configuración predeterminada de Auto significa usar el
AlignItems valor.

En el caso de un Label elemento denominado label (o ejemplo), puede establecer la AlignSelf propiedad en el
código como se indica a continuación:

FlexLayout.SetAlignSelf(label, FlexAlignSelf.Center);

Observe que no hay ninguna referencia al FlexLayout elemento primario de Label . En XAML, establezca la
propiedad de la siguiente manera:

<Label ... FlexLayout.AlignSelf="Center" ... />

La propiedad Order
La Order propiedad es de tipo int . El valor predeterminado es 0.
La Order propiedad permite cambiar el orden en el que se organizan los elementos secundarios de FlexLayout .
Normalmente, los elementos secundarios de un FlexLayout objeto están organizados en el mismo orden en que
aparecen en la Children colección. Puede invalidar este orden estableciendo la Order propiedad enlazable
adjunta en un valor entero distinto de cero en uno o varios elementos secundarios. FlexLayout A continuación,
organiza sus elementos secundarios en función del valor de la Order propiedad en cada elemento secundario,
pero los elementos secundarios con la misma Order configuración se organizan en el orden en que aparecen en
la Children colección.
Propiedad base
La Basis propiedad enlazable asociada indica la cantidad de espacio que se asigna a un elemento secundario de
FlexLayout en el eje principal. El tamaño especificado por la Basis propiedad es el tamaño a lo largo del eje
principal del elemento primario FlexLayout . Por consiguiente, Basis indica el ancho de un elemento secundario
cuando los elementos secundarios están organizados en filas o el alto cuando los elementos secundarios se
organizan en columnas.
La Basis propiedad es de tipo FlexBasis , una estructura. El tamaño se puede especificar en unidades
independientes del dispositivo o como un porcentaje del tamaño de FlexLayout . El valor predeterminado de la
Basis propiedad es la propiedad estática FlexBasis.Auto , lo que significa que se utiliza el ancho o el alto
solicitados del secundario.
En el código, puede establecer la Basis propiedad de un Label con nombre label en unidades independientes
del dispositivo 40 como esta:

FlexLayout.SetBasis(label, new FlexBasis(40, false));

El segundo argumento del FlexBasis constructor se denomina isRelative e indica si el tamaño es relativo (
true ) o absoluto ( false ). El argumento tiene un valor predeterminado de false , por lo que también puede
usar el código siguiente:

FlexLayout.SetBasis(label, new FlexBasis(40));

Se define una conversión implícita de float a FlexBasis , por lo que puede simplificarla aún más:

FlexLayout.SetBasis(label, 40);

Puede establecer el tamaño en el 25% del elemento primario de la siguiente FlexLayout manera:

FlexLayout.SetBasis(label, new FlexBasis(0.25f, true));

Este valor fraccionario debe estar en el intervalo de 0 a 1.


En XAML, puede usar un número para un tamaño en unidades independientes del dispositivo:

<Label ... FlexLayout.Basis="40" ... />

O bien, puede especificar un porcentaje en el intervalo de 0% a 100%:

<Label ... FlexLayout.Basis="25%" ... />

La página experimento base del ejemplo FlexLayoutDemos le permite experimentar con la Basis propiedad.
En la página se muestra una columna ajustada de cinco Label elementos con colores de fondo y de primer plano
alternativos. Dos Slider elementos permiten especificar Basis valores para el segundo y el cuarto Label :

La captura de pantalla de iOS de la izquierda muestra los dos Label elementos que se proporcionan con alto en
unidades independientes del dispositivo. La pantalla de Android muestra que se les proporciona alto que son una
fracción del alto total de FlexLayout . Si Basis se establece en 100%, el elemento secundario es el alto de
FlexLayout , y se ajustará a la columna siguiente y ocupará el alto completo de esa columna, como muestra la
captura de pantalla de UWP: aparece como si los cinco elementos secundarios estuvieran organizados en una fila,
pero en realidad están dispuestos en cinco columnas.
La propiedad Grow
La Grow propiedad enlazable asociada es de tipo int . El valor predeterminado es 0 y el valor debe ser mayor o
igual que 0.
La Grow propiedad desempeña un rol cuando la Wrap propiedad se establece en NoWrap y la fila de elementos
secundarios tiene un ancho total menor que el ancho de FlexLayout , o bien la columna de elementos
secundarios tiene un alto más corto que el FlexLayout . La Grow propiedad indica cómo prorratear el espacio
sobrante entre los elementos secundarios.
En la página aumentar experimento , Label se organizan cinco elementos de colores alternos en una columna
y dos Slider elementos permiten ajustar la Grow propiedad del segundo y cuarto Label . La captura de pantalla
de iOS en el extremo izquierdo muestra las propiedades predeterminadas Grow de 0:

Si a un elemento secundario se le asigna un Grow valor positivo, ese elemento secundario ocupa todo el espacio
restante, como se muestra en la captura de pantalla de Android. Este espacio también puede asignarse entre dos o
más elementos secundarios. En la captura de pantalla de UWP, la Grow propiedad de la segunda Label está
establecida en 0,5, mientras que la Grow propiedad del cuarto Label es 1,5, que proporciona el cuarto Label de
las tres veces como gran parte del espacio sobrante como segundo Label .
La forma en que la vista secundaria utiliza ese espacio depende del tipo de elemento secundario. En el caso de
Label , el texto se puede colocar en el espacio total de Label mediante las propiedades HorizontalTextAlignment
y VerticalTextAlignment .
Propiedad Shrink
La Shrink propiedad enlazable asociada es de tipo int . El valor predeterminado es 1 y el valor debe ser mayor
o igual que 0.
La Shrink propiedad desempeña un rol cuando la Wrap propiedad se establece en NoWrap y el ancho agregado
de una fila de elementos secundarios es mayor que el ancho de FlexLayout , o el alto agregado de una sola
columna de elementos secundarios es mayor que el alto de FlexLayout . Normalmente, mostrará FlexLayout
estos elementos secundarios restringiendo sus tamaños. La Shrink propiedad puede indicar a qué elementos
secundarios se les da prioridad en su tamaño completo.
La página reducir experimento crea un FlexLayout con una sola fila de cinco Label elementos secundarios
que requieren más espacio que el FlexLayout ancho. La captura de pantalla de iOS de la izquierda muestra todos
los Label elementos con valores predeterminados de 1:

En la captura de pantalla de Android, el Shrink valor del segundo Label se establece en 0 y Label se muestra
en su ancho completo. Además, a la cuarta Label se le asigna un Shrink valor mayor que uno y se ha reducido.
La captura de pantalla de UWP muestra que los dos Label elementos reciben un Shrink valor de 0 para permitir
que se muestren en su tamaño completo, si es posible.
Puede establecer los Grow Shrink valores y para dar cabida a situaciones en las que los tamaños de los
elementos secundarios agregados pueden ser a veces menor o igual que el tamaño de FlexLayout .

Estilos CSS con FlexLayout


Puede usar la característica de estilo CSS que se presentó con :::no-loc(Xamarin.Forms)::: 3,0 en conexión con
FlexLayout . La página elementos del catálogo CSS del ejemplo FlexLayoutDemos duplica el diseño de la
página elementos del catálogo , pero con una hoja de estilos CSS para muchos de los estilos:
El archivo CatalogItemsPage. Xaml original tiene cinco Style definiciones en su Resources sección con 15
Setter objetos. En el archivo CssCatalogItemsPage. Xaml , se ha reducido a dos Style definiciones con solo
cuatro Setter objetos. Estos estilos complementan la hoja de estilos CSS para las propiedades que :::no-
loc(Xamarin.Forms)::: actualmente no admite la característica de estilo CSS:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FlexLayoutDemos"
x:Class="FlexLayoutDemos.CssCatalogItemsPage"
Title="CSS Catalog Items">
<ContentPage.Resources>
<StyleSheet Source="CatalogItemsStyles.css" />

<Style TargetType="Frame">
<Setter Property="BorderColor" Value="Blue" />
<Setter Property="CornerRadius" Value="15" />
</Style>

<Style TargetType="Button">
<Setter Property="Text" Value="LEARN MORE" />
<Setter Property="BorderRadius" Value="20" />
</Style>
</ContentPage.Resources>

<ScrollView Orientation="Both">
<FlexLayout>
<Frame>
<FlexLayout Direction="Column">
<Label Text="Seated Monkey" StyleClass="header" />
<Label Text="This monkey is laid back and relaxed, and likes to watch the world go by."
/>
<Label Text=" &#x2022; Doesn't make a lot of noise" />
<Label Text=" &#x2022; Often smiles mysteriously" />
<Label Text=" &#x2022; Sleeps sitting up" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.SeatedMonkey.jpg}" />
<Label StyleClass="empty" />
<Button />
</FlexLayout>
</Frame>

<Frame>
<FlexLayout Direction="Column">
<Label Text="Banana Monkey" StyleClass="header" />
<Label Text="Watch this monkey eat a giant banana." />
<Label Text=" &#x2022; More fun than a barrel of monkeys" />
<Label Text=" &#x2022; Banana not included" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.Banana.jpg}" />
<Label StyleClass="empty" />
<Button />
</FlexLayout>
</Frame>

<Frame>
<FlexLayout Direction="Column">
<Label Text="Face-Palm Monkey" StyleClass="header" />
<Label Text="This monkey reacts appropriately to ridiculous assertions and actions." />
<Label Text=" &#x2022; Cynical but not unfriendly" />
<Label Text=" &#x2022; Seven varieties of grimaces" />
<Label Text=" &#x2022; Doesn't laugh at your jokes" />
<Image Source="{local:ImageResource FlexLayoutDemos.Images.FacePalm.jpg}" />
<Label StyleClass="empty" />
<Button />
</FlexLayout>
</Frame>
</FlexLayout>
</ScrollView>
</ContentPage>

En la primera línea de la sección se hace referencia a la hoja de estilos CSS Resources :


<StyleSheet Source="CatalogItemsStyles.css" />

Observe también que dos elementos de cada uno de los tres elementos incluyen StyleClass valores de
configuración:

<Label Text="Seated Monkey" StyleClass="header" />


···
<Label StyleClass="empty" />

Estos se refieren a los selectores de la hoja de estilos CatalogItemsStyles. CSS :

frame {
width: 300;
height: 480;
background-color: lightyellow;
margin: 10;
}

label {
margin: 4 0;
}

label.header {
margin: 8 0;
font-size: large;
color: blue;
}

label.empty {
flex-grow: 1;
}

image {
height: 180;
order: -1;
align-self: center;
}

button {
font-size: large;
color: white;
background-color: green;
}

FlexLayout Aquí se hace referencia a varias propiedades enlazables adjuntas. En el label.empty selector, verá el
flex-grow atributo, cuyo estilo es un vacío Label para proporcionar espacio en blanco por encima de Button . El
image selector contiene un order atributo y un align-self atributo, que se corresponden con FlexLayout las
propiedades enlazables adjuntas.
Ha visto que puede establecer las propiedades directamente en FlexLayout y puede establecer las propiedades
enlazables asociadas en los elementos secundarios de FlexLayout . O bien, puede establecer estas propiedades
indirectamente mediante estilos tradicionales basados en XAML o estilos CSS. Lo importante es saber y
comprender estas propiedades. Estas propiedades son lo que hace que sea FlexLayout realmente flexible.

FlexLayout con Xamarin. University


:::no-loc(Xamarin.Forms)::: vídeo de diseño de Flex 3,0
Vínculos relacionados
FlexLayoutDemos
:::no-loc(Xamarin.Forms)::: Cuadrícula
18/12/2020 • 25 minutes to read • Edit Online

Descargar el ejemplo

Grid Es un diseño que organiza sus elementos secundarios en filas y columnas, que pueden tener tamaños
proporcionales o absolutos. De forma predeterminada, un Grid contiene una fila y una columna. Además, Grid
se puede utilizar como un diseño primario que contiene otros diseños secundarios.
El Grid diseño no se debe confundir con las tablas y no está diseñado para presentar datos tabulares. A
diferencia de las tablas HTML, una Grid está pensada para diseñar el contenido. Para Mostrar datos tabulares,
considere la posibilidad de usar un control ListView, CollectionViewo TableView.
La Grid clase define las siguientes propiedades:
Column , de tipo int , que es una propiedad adjunta que indica la alineación de las columnas de una vista
dentro de un elemento primario Grid . El valor predeterminado de esta propiedad es 0. Una devolución de
llamada de validación garantiza que, cuando se establece la propiedad, su valor es mayor o igual que 0.
ColumnDefinitions , de tipo ColumnDefinitionCollection , es una lista de ColumnDefinition objetos que definen
el ancho de las columnas de la cuadrícula.
ColumnSpacing , de tipo double , indica la distancia entre las columnas de la cuadrícula. El valor
predeterminado de esta propiedad es 6 unidades independientes del dispositivo.
ColumnSpan , de tipo int , que es una propiedad adjunta que indica el número total de columnas que una
vista abarca dentro de un elemento primario Grid . El valor predeterminado de esta propiedad es 1. Una
devolución de llamada de validación garantiza que, cuando se establece la propiedad, su valor es mayor o
igual que 1.
Row , de tipo int , que es una propiedad adjunta que indica la alineación de filas de una vista dentro de un
elemento primario Grid . El valor predeterminado de esta propiedad es 0. Una devolución de llamada de
validación garantiza que, cuando se establece la propiedad, su valor es mayor o igual que 0.
RowDefinitions , de tipo RowDefinitionCollection , es una lista de RowDefintion objetos que definen el alto de
las filas de la cuadrícula.
RowSpacing , de tipo double , indica la distancia entre las filas de la cuadrícula. El valor predeterminado de esta
propiedad es 6 unidades independientes del dispositivo.
RowSpan , de tipo int , que es una propiedad adjunta que indica el número total de filas que una vista abarca
dentro de un elemento primario Grid . El valor predeterminado de esta propiedad es 1. Una devolución de
llamada de validación garantiza que, cuando se establece la propiedad, su valor es mayor o igual que 1.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos y con estilo.
La Gridclase se deriva de la Layout<T> clase, que define una Children propiedad de tipo IList<T> . La
Children propiedad es ContentProperty de la Layout<T> clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

TIP
Para obtener el mejor rendimiento posible del diseño, siga las instrucciones que se indican en optimizar el rendimiento del
diseño.

Filas y columnas
De forma predeterminada, un Grid contiene una fila y una columna:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridTutorial.MainPage">
<Grid Margin="20,35,20,20">
<Label Text="By default, a Grid contains one row and one column." />
</Grid>
</ContentPage>

En este ejemplo, Grid contiene un único elemento secundario Label que se coloca automáticamente en una
única ubicación:

El comportamiento de diseño de Grid se puede definir con las RowDefinitions ColumnDefinitions propiedades
y, que son colecciones de RowDefinition ColumnDefinition objetos y, respectivamente. Estas colecciones definen
las características de fila y columna de un Grid , y deben contener un RowDefinition objeto para cada fila de
Grid , y un ColumnDefinition objeto para cada columna en Grid .

La RowDefinition clase define una Height propiedad, de tipo GridLength , y la ColumnDefinition clase define
una Width propiedad, de tipo GridLength . El GridLength struct especifica un alto de fila o un ancho de columna
en cuanto a la GridUnitType enumeración, que tiene tres miembros:
Absolute : el alto de fila o el ancho de columna es un valor en unidades independientes del dispositivo (un
número en XAML).
Auto : el alto de fila o el ancho de columna se ajusta automáticamente según el contenido de la celda ( Auto
en XAML).
Star – el alto de fila o el ancho de columna sobrante se asigna proporcionalmente (un número seguido *
de en XAML).
Una Grid fila con una Height propiedad de Auto restringe el alto de las vistas en esa fila de la misma manera
que una vertical StackLayout . Del mismo modo, una columna con una Width propiedad de funciona de forma
Auto muy parecida a una horizontal StackLayout .

Cau t i on

Intente asegurarse de que el tamaño de las filas y columnas es el máximo posible Auto . Cada fila o columna de
tamaño automático hará que el motor de diseño tenga que realizar cálculos de diseño adicionales. En su lugar,
use filas y columnas de tamaño fijo si es posible. También puede establecer filas y columnas para ocupar una
cantidad proporcional de espacio con el GridUnitType.Star valor de enumeración.
En el siguiente código XAML se muestra cómo crear un Grid con tres filas y dos columnas:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.BasicGridPage"
Title="Basic Grid demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>

En este ejemplo, Grid tiene un alto general que es el alto de la página. Grid Sabe que el alto de la tercera fila es
100 unidades independientes del dispositivo. Resta ese alto de su propio alto y asigna el alto restante
proporcionalmente entre la primera y la segunda fila en función del número anterior a la estrella. En este
ejemplo, el alto de la primera fila es el doble de la de la segunda fila.
ColumnDefinition Ambos objetos establecen el Width en * , que es igual que 1* , lo que significa que el ancho
de la pantalla se divide equitativamente por debajo de las dos columnas.

IMPORTANT
El valor predeterminado de la RowDefinition.Height propiedad es * . Del mismo modo, el valor predeterminado de la
ColumnDefinition.Width propiedad es * . Por lo tanto, no es necesario establecer estas propiedades en los casos en los
que estos valores predeterminados son aceptables.

Las vistas secundarias se pueden colocar en Grid celdas específicas con Grid.Column y las Grid.Row
propiedades adjuntas. Además, para que las vistas secundarias abarquen varias filas y columnas, use
Grid.RowSpan y las Grid.ColumnSpan propiedades adjuntas.

En el código XAML siguiente se muestra la misma Grid definición y también se colocan las vistas secundarias en
Grid celdas específicas:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.BasicGridPage"
Title="Basic Grid demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<BoxView Color="Green" />
<Label Text="Row 0, Column 0"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Column="1"
Color="Blue" />
<Label Grid.Column="1"
Text="Row 0, Column 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Color="Teal" />
<Label Grid.Row="1"
Text="Row 1, Column 0"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="Purple" />
<Label Grid.Row="1"
Grid.Column="1"
Text="Row1, Column 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Grid.ColumnSpan="2"
Color="Red" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="Row 2, Columns 0 and 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</ContentPage>

NOTE
Las Grid.Row Grid.Column propiedades y se indizan desde 0, por lo que se Grid.Row="2" refiere a la tercera fila
mientras se Grid.Column="1" hace referencia a la segunda columna. Además, ambas propiedades tienen un valor
predeterminado de 0, por lo que no es necesario establecer en vistas secundarias que ocupen la primera fila o la primera
columna de un Grid .

En este ejemplo, las tres Grid filas están ocupadas por las BoxView Label vistas y. La tercera fila tiene un alto de
100 unidades independientes del dispositivo, con las dos primeras filas que ocupan el espacio restante (la
primera fila es dos veces más alta que la segunda fila). Las dos columnas tienen el mismo ancho y lo dividen
Grid en la mitad. El BoxView de la tercera fila abarca ambas columnas.
Además, las vistas secundarias de una Grid pueden compartir celdas. El orden en el que los elementos
secundarios aparecen en el código XAML es el orden en el que se colocan los elementos secundarios Grid . En el
ejemplo anterior, los Label objetos solo están visibles porque se representan en la parte superior de los
BoxView objetos. Los Label objetos no estarán visibles si los BoxView objetos se representaban encima de ellos.

El código de C# equivalente es el siguiente:

public class BasicGridPageCS : ContentPage


{
public BasicGridPageCS()
{
Grid grid = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(2, GridUnitType.Star) },
new RowDefinition(),
new RowDefinition { Height = new GridLength(100) }
},
ColumnDefinitions =
{
new ColumnDefinition(),
new ColumnDefinition()
}
};

// Row 0
// The BoxView and Label are in row 0 and column 0, and so only needs to be added to the
// Grid.Children collection to get default row and column settings.
grid.Children.Add(new BoxView
{
Color = Color.Green
});
grid.Children.Add(new Label
{
Text = "Row 0, Column 0",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
});

// This BoxView and Label are in row 0 and column 1, which are specified as arguments
// to the Add method.
grid.Children.Add(new BoxView
{
Color = Color.Blue
}, 1, 0);
}, 1, 0);
grid.Children.Add(new Label
{
Text = "Row 0, Column 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 0);

// Row 1
// This BoxView and Label are in row 1 and column 0, which are specified as arguments
// to the Add method overload.
grid.Children.Add(new BoxView
{
Color = Color.Teal
}, 0, 1, 1, 2);
grid.Children.Add(new Label
{
Text = "Row 1, Column 0",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 0, 1, 1, 2); // These arguments indicate that that the child element goes in the column starting
at 0 but ending before 1.
// They also indicate that the child element goes in the row starting at 1 but ending
before 2.

grid.Children.Add(new BoxView
{
Color = Color.Purple
}, 1, 2, 1, 2);
grid.Children.Add(new Label
{
Text = "Row1, Column 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 2, 1, 2);

// Row 2
// Alternatively, the BoxView and Label can be positioned in cells with the Grid.SetRow
// and Grid.SetColumn methods.
BoxView boxView = new BoxView { Color = Color.Red };
Grid.SetRow(boxView, 2);
Grid.SetColumnSpan(boxView, 2);
Label label = new Label
{
Text = "Row 2, Column 0 and 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
Grid.SetRow(label, 2);
Grid.SetColumnSpan(label, 2);

grid.Children.Add(boxView);
grid.Children.Add(label);

Title = "Basic Grid demo";


Content = grid;
}
}

En el código, para especificar el alto de un RowDefinition objeto y el ancho de un ColumnDefinition objeto, se


usan valores de la GridLength estructura, a menudo en combinación con la GridUnitType enumeración.
El código de ejemplo anterior también muestra varios enfoques diferentes para agregar elementos secundarios a
Grid y especificar las celdas en las que residen. Cuando se usa la Add sobrecarga que especifica los argumentos
izquierdo , derecho , superior e inferior , mientras que los argumentos izquierdo y superior siempre hacen
referencia a las celdas de Grid , los argumentos derecho e inferior parecen hacer referencia a las celdas que
están fuera de Grid . Esto se debe a que el argumento derecho siempre debe ser mayor que el argumento
izquierdo y el argumento inferior siempre debe ser mayor que el argumento superior . En el ejemplo siguiente,
en el que se supone que es 2x2 Grid , se muestra código equivalente mediante las dos Add sobrecargas:

// left, top
grid.Children.Add(topLeft, 0, 0); // first column, first row
grid.Children.Add(topRight, 1, 0); // second column, first tow
grid.Children.Add(bottomLeft, 0, 1); // first column, second row
grid.Children.Add(bottomRight, 1, 1); // second column, second row

// left, right, top, bottom


grid.Children.Add(topLeft, 0, 1, 0, 1); // first column, first row
grid.Children.Add(topRight, 1, 2, 0, 1); // second column, first tow
grid.Children.Add(bottomLeft, 0, 1, 1, 2); // first column, second row
grid.Children.Add(bottomRight, 1, 2, 1, 2); // second column, second row

NOTE
Además, las vistas secundarias se pueden agregar a un Grid con AddHorizontal los AddVertical métodos y, que
agregan elementos secundarios a una sola fila o a una sola columna Grid . Grid A continuación, se expande en filas o
columnas a medida que se realizan estas llamadas, así como colocar automáticamente los elementos secundarios en las
celdas correctas.

Simplificar definiciones de filas y columnas


En XAML, se pueden especificar las características de fila y columna de una Grid mediante una sintaxis
simplificada que evita tener que RowDefinition definir ColumnDefinition objetos y para cada fila y columna. En
su lugar, RowDefinitions las ColumnDefinitions propiedades y se pueden establecer en cadenas que contengan
valores delimitados por comas GridUnitType , de los que los convertidores de tipos estén integrados en :::no-
loc(Xamarin.Forms)::: Create RowDefinition y ColumnDefinition Objects:

<Grid RowDefinitions="1*, Auto, 25, 14, 20"


ColumnDefinitions="*, 2*, Auto, 300">
...
</Grid>

En este ejemplo, Grid tiene cinco filas y cuatro columnas. Las filas tercero, y quinta se establecen en altos
absolutos, con el ajuste automático del tamaño de la segunda fila con su contenido. A continuación, el alto
restante se asigna a la primera fila.
La columna y se establece en un ancho absoluto, con el ajuste automático del tamaño de la tercera columna en su
contenido. El ancho restante se asigna proporcionalmente entre la primera y la segunda columna en función del
número antes de la estrella. En este ejemplo, el ancho de la segunda columna es dos veces el de la primera
columna (porque * es idéntico a 1* ).

Espacio entre filas y columnas


De forma predeterminada, Grid las filas están separadas por 6 unidades de espacio independientes del
dispositivo. Del mismo modo, Grid las columnas se separan mediante 6 unidades de espacio independientes del
dispositivo. Estos valores predeterminados se pueden cambiar estableciendo RowSpacing las ColumnSpacing
propiedades y, respectivamente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.GridSpacingPage"
Title="Grid spacing demo">
<Grid RowSpacing="0"
ColumnSpacing="0">
..
</Grid>
</ContentPage>

En este ejemplo se crea un Grid que no tiene espacio entre sus filas y columnas:

TIP
Las RowSpacing ColumnSpacing propiedades y se pueden establecer en valores negativos para que el contenido de la
celda se superponga.

El código de C# equivalente es el siguiente:

public GridSpacingPageCS()
{
Grid grid = new Grid
{
RowSpacing = 0,
ColumnSpacing = 0,
// ...
};
// ...

Content = grid;
}

Asociación
Las vistas secundarias de una Grid se pueden colocar en sus celdas por las HorizontalOptions VerticalOptions
propiedades y. Estas propiedades se pueden establecer en los siguientes campos del LayoutOptions struct:
Start
Center
End
Fill

IMPORTANT
Los AndExpands campos del LayoutOptions struct solo se pueden aplicar a StackLayout objetos.

El código XAML siguiente crea un Grid con nueve celdas de igual tamaño y coloca un Label en cada celda con
una alineación diferente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.GridAlignmentPage"
Title="Grid alignment demo">
<Grid RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<BoxView Color="AliceBlue" />


<Label Text="Upper left"
HorizontalOptions="Start"
VerticalOptions="Start" />
<BoxView Grid.Column="1"
Color="LightSkyBlue" />
<Label Grid.Column="1"
Text="Upper center"
HorizontalOptions="Center"
VerticalOptions="Start"/>
<BoxView Grid.Column="2"
Color="CadetBlue" />
<Label Grid.Column="2"
Text="Upper right"
HorizontalOptions="End"
VerticalOptions="Start" />
<BoxView Grid.Row="1"
Color="CornflowerBlue" />
<Label Grid.Row="1"
Text="Center left"
HorizontalOptions="Start"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="DodgerBlue" />
<Label Grid.Row="1"
Grid.Column="1"
Text="Center center"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="2"
Color="DarkSlateBlue" />
<Label Grid.Row="1"
Grid.Column="2"
Text="Center right"
HorizontalOptions="End"
VerticalOptions="Center" />
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Color="SteelBlue" />
<Label Grid.Row="2"
Text="Lower left"
HorizontalOptions="Start"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="1"
Color="LightBlue" />
<Label Grid.Row="2"
Grid.Column="1"
Text="Lower center"
HorizontalOptions="Center"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="2"
Color="BlueViolet" />
<Label Grid.Row="2"
Grid.Column="2"
Text="Lower right"
HorizontalOptions="End"
VerticalOptions="End" />
</Grid>
</ContentPage>

En este ejemplo, los Label objetos de cada fila se alinean de manera idéntica verticalmente, pero usan diferentes
alineaciones horizontales. Como alternativa, se puede considerar que los Label objetos de cada columna están
alineados de forma idéntica horizontalmente, pero con diferentes alineaciones verticales:

El código de C# equivalente es el siguiente:

public class GridAlignmentPageCS : ContentPage


{
public GridAlignmentPageCS()
{
Grid grid = new Grid
{
RowSpacing = 0,
ColumnSpacing = 0,
RowDefinitions =
{
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
},
},
ColumnDefinitions =
{
new ColumnDefinition(),
new ColumnDefinition(),
new ColumnDefinition()
}
};

// Row 0
grid.Children.Add(new BoxView
{
Color = Color.AliceBlue
});
grid.Children.Add(new Label
{
Text = "Upper left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
});

grid.Children.Add(new BoxView
{
Color = Color.LightSkyBlue
}, 1, 0);
grid.Children.Add(new Label
{
Text = "Upper center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Start
}, 1, 0);

grid.Children.Add(new BoxView
{
Color = Color.CadetBlue
}, 2, 0);
grid.Children.Add(new Label
{
Text = "Upper right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Start
}, 2, 0);

// Row 1
grid.Children.Add(new BoxView
{
Color = Color.CornflowerBlue
}, 0, 1);
grid.Children.Add(new Label
{
Text = "Center left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center
}, 0, 1);

grid.Children.Add(new BoxView
{
Color = Color.DodgerBlue
}, 1, 1);
grid.Children.Add(new Label
{
Text = "Center center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 1);

grid.Children.Add(new BoxView
{
Color = Color.DarkSlateBlue
}, 2, 1);
}, 2, 1);
grid.Children.Add(new Label
{
Text = "Center right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
}, 2, 1);

// Row 2
grid.Children.Add(new BoxView
{
Color = Color.SteelBlue
}, 0, 2);
grid.Children.Add(new Label
{
Text = "Lower left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.End
}, 0, 2);

grid.Children.Add(new BoxView
{
Color = Color.LightBlue
}, 1, 2);
grid.Children.Add(new Label
{
Text = "Lower center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.End
}, 1, 2);

grid.Children.Add(new BoxView
{
Color = Color.BlueViolet
}, 2, 2);
grid.Children.Add(new Label
{
Text = "Lower right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.End
}, 2, 2);

Title = "Grid alignment demo";


Content = grid;
}
}

Objetos de cuadrícula anidados


Grid Se puede utilizar como un diseño primario que contiene objetos secundarios anidados Grid u otros
diseños secundarios. Al anidar Grid objetos,, Grid.Row , Grid.Column Grid.RowSpan y Grid.ColumnSpan las
propiedades adjuntas siempre hacen referencia a la posición de las vistas dentro de su elemento primario Grid .
En el código XAML siguiente se muestra un ejemplo de anidamiento de Grid objetos:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:GridDemos.Converters"
x:Class="GridDemos.Views.ColorSlidersGridPage"
Title="Nested Grids demo">

<ContentPage.Resources>
<converters:DoubleToIntConverter x:Key="doubleToInt" />

<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment"
Value="Center" />
</Style>
</ContentPage.Resources>

<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<BoxView x:Name="boxView"
Color="Black" />
<Grid Grid.Row="1"
Margin="20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Slider x:Name="redSlider"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="1"
Text="{Binding Source={x:Reference redSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0}'}" />
<Slider x:Name="greenSlider"
Grid.Row="2"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="3"
Text="{Binding Source={x:Reference greenSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Green = {0}'}" />
<Slider x:Name="blueSlider"
Grid.Row="4"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="5"
Text="{Binding Source={x:Reference blueSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Blue = {0}'}" />
</Grid>
</Grid>
</ContentPage>

En este ejemplo, el Grid diseño raíz contiene un BoxView en su primera fila y un elemento secundario Grid en
la segunda fila. El elemento secundario Grid contiene Slider objetos que manipulan el color que muestra
BoxView , y los Label objetos que muestran el valor de cada uno de ellos Slider :
IMPORTANT
Cuanto más profundo sea el anidamiento de Grid objetos y otros diseños, más los diseños anidados afectarán al
rendimiento. Para obtener más información, vea elegir el diseño correcto.

El código de C# equivalente es el siguiente:

public class ColorSlidersGridPageCS : ContentPage


{
BoxView boxView;
Slider redSlider;
Slider greenSlider;
Slider blueSlider;

public ColorSlidersGridPageCS()
{
// Create an implicit style for the Labels
Style labelStyle = new Style(typeof(Label))
{
Setters =
{
new Setter { Property = Label.HorizontalTextAlignmentProperty, Value = TextAlignment.Center }
}
};
Resources.Add(labelStyle);

// Root page layout


Grid rootGrid = new Grid
{
RowDefinitions =
{
new RowDefinition(),
new RowDefinition()
}
};

boxView = new BoxView { Color = Color.Black };


rootGrid.Children.Add(boxView);

// Child page layout


Grid childGrid = new Grid
{
Margin = new Thickness(20),
RowDefinitions =
{
{
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
}
};

DoubleToIntConverter doubleToInt = new DoubleToIntConverter();

redSlider = new Slider();


redSlider.ValueChanged += OnSliderValueChanged;
childGrid.Children.Add(redSlider);

Label redLabel = new Label();


redLabel.SetBinding(Label.TextProperty, new Binding("Value", converter: doubleToInt,
converterParameter: "255", stringFormat: "Red = {0}", source: redSlider));
Grid.SetRow(redLabel, 1);
childGrid.Children.Add(redLabel);

greenSlider = new Slider();


greenSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(greenSlider, 2);
childGrid.Children.Add(greenSlider);

Label greenLabel = new Label();


greenLabel.SetBinding(Label.TextProperty, new Binding("Value", converter: doubleToInt,
converterParameter: "255", stringFormat: "Green = {0}", source: greenSlider));
Grid.SetRow(greenLabel, 3);
childGrid.Children.Add(greenLabel);

blueSlider = new Slider();


blueSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(blueSlider, 4);
childGrid.Children.Add(blueSlider);

Label blueLabel = new Label();


blueLabel.SetBinding(Label.TextProperty, new Binding("Value", converter: doubleToInt,
converterParameter: "255", stringFormat: "Blue = {0}", source: blueSlider));
Grid.SetRow(blueLabel, 5);
childGrid.Children.Add(blueLabel);

// Place the child Grid in the root Grid


rootGrid.Children.Add(childGrid, 0, 1);

Title = "Nested Grids demo";


Content = rootGrid;
}

void OnSliderValueChanged(object sender, ValueChangedEventArgs e)


{
boxView.Color = new Color(redSlider.Value, greenSlider.Value, blueSlider.Value);
}
}

Vínculos relacionados
Demostraciones de cuadrícula (ejemplo)
Opciones de diseño en :::no-loc(Xamarin.Forms):::
Elegir un :::no-loc(Xamarin.Forms)::: diseño
Mejorar el rendimiento de las :::no-loc(Xamarin.Forms)::: aplicaciones
Xamarin.Forms RelativeLayout
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo

RelativeLayout Se utiliza para colocar y cambiar el tamaño de los elementos secundarios en relación con las
propiedades del diseño o de los elementos del mismo nivel. Esto permite que las interfaces de la creación se
escalen proporcionalmente entre los tamaños de los dispositivos. Además, a diferencia de otras clases de diseño,
RelativeLayout puede colocar los elementos secundarios para que se superpongan.

La RelativeLayout clase define las siguientes propiedades:


XConstraint , de tipo Constraint , que es una propiedad adjunta que representa la restricción en la posición X
del elemento secundario.
YConstraint , de tipo Constraint , que es una propiedad adjunta que representa la restricción en la posición Y
del elemento secundario.
WidthConstraint , de tipo Constraint , que es una propiedad adjunta que representa la restricción en el ancho
del elemento secundario.
HeightConstraint , de tipo Constraint , que es una propiedad adjunta que representa la restricción en el alto
del elemento secundario.
BoundsConstraint , de tipo BoundsConstraint , que es una propiedad adjunta que representa la restricción en la
posición y el tamaño del elemento secundario. Esta propiedad no se puede consumir fácilmente desde XAML.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos y con estilo. Para obtener más información sobre las propiedades adjuntas,
consulte Xamarin.Forms propiedades adjuntas.

NOTE
El ancho y el alto de un elemento secundario de un RelativeLayout también se pueden especificar a través de las
propiedades y del elemento secundario WidthRequest HeightRequest , en lugar de las WidthConstraint
HeightConstraint propiedades adjuntas y.

La RelativeLayout clase se deriva de la Layout<T> clase, que define una Children propiedad de tipo IList<T> .
La Children propiedad es ContentProperty de la Layout<T> clase y, por tanto, no es necesario establecer
explícitamente desde XAML.
TIP
Evite el uso de un RelativeLayout siempre que sea posible. Como resultado, la CPU tendrá que realizar mucho más
trabajo.

Restricciones
Dentro de RelativeLayout , la posición y el tamaño de los elementos secundarios se especifican como
restricciones usando valores absolutos o valores relativos. Cuando no se especifican restricciones, se colocará un
elemento secundario en la esquina superior izquierda del diseño.
En la tabla siguiente se muestra cómo especificar restricciones en XAML y C#:

XA M L C#

Valores absolutos Las restricciones absolutas se Las restricciones absolutas se


especifican mediante el establecimiento especifican mediante el
de las RelativeLayout propiedades Constraint.Constant método o
adjuntas en double valores. mediante la Children.Add sobrecarga
que requiere un Func<Rectangle>
argumento.

Valores relativos Las restricciones relativas se especifican Las restricciones relativas se especifican
mediante el establecimiento de las mediante Constraint objetos
RelativeLayout propiedades devueltos por los métodos de la
adjuntas en Constraint objetos Constraint clase.
devueltos por la
ConstraintExpression extensión de
marcado.

Para obtener más información sobre cómo especificar restricciones mediante valores absolutos, vea
posicionamiento absoluto y ajuste de tamaño. Para obtener más información sobre cómo especificar restricciones
usando valores relativos, vea posicionamiento relativo y ajuste de tamaño.
En C#, se pueden agregar elementos secundarios a RelativeLayout tres Add sobrecargas. La primera sobrecarga
requiere Expression<Func<Rectangle>> que especifique la posición y el tamaño de un elemento secundario. La
segunda sobrecarga requiere Expression<Func<double>> objetos opcionales para los x y argumentos,, width y
height . La tercera sobrecarga requiere Constraint objetos opcionales para los x y argumentos,, width y
height .

Es posible cambiar la posición y el tamaño de un elemento secundario en RelativeLayout con los


SetXConstraint métodos, SetYConstraint , SetWidthConstraint y SetHeightConstraint . El primer argumento de
cada uno de estos métodos es el elemento secundario y el segundo es un Constraint objeto. Además, el
SetBoundsConstraint método también se puede utilizar para cambiar la posición y el tamaño de un elemento
secundario. El primer argumento de este método es el elemento secundario y el segundo es un BoundsConstraint
objeto.

Posicionamiento y ajuste de tamaño absolutos


RelativeLayout Puede colocar y cambiar el tamaño de los elementos secundarios usando valores absolutos, que
se especifican en unidades independientes del dispositivo, que definen explícitamente dónde se deben colocar los
elementos secundarios en el diseño. Para ello, se agregan elementos secundarios a la Children colección de
RelativeLayout y se establece la XConstraint YConstraint propiedad,, WidthConstraint y HeightConstraint las
propiedades adjuntas en cada elemento secundario en la posición absoluta y/o los valores de tamaño.
WARNING
El uso de valores absolutos para el posicionamiento y el ajuste de tamaño de los elementos secundarios puede ser
problemático, ya que los distintos dispositivos tienen diferentes tamaños de pantalla y resoluciones. Por lo tanto, las
coordenadas del centro de la pantalla en un dispositivo pueden estar desplazadas en otros dispositivos.

En el código XAML siguiente RelativeLayout se muestra un cuyos elementos secundarios se colocan usando
valores absolutos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="RelativeLayoutDemos.Views.StylishHeaderDemoPage"
Title="Stylish header demo">
<RelativeLayout Margin="20">
<BoxView Color="Silver"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="10"
RelativeLayout.WidthConstraint="200"
RelativeLayout.HeightConstraint="5" />
<BoxView Color="Silver"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="20"
RelativeLayout.WidthConstraint="200"
RelativeLayout.HeightConstraint="5" />
<BoxView Color="Silver"
RelativeLayout.XConstraint="10"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="5"
RelativeLayout.HeightConstraint="65" />
<BoxView Color="Silver"
RelativeLayout.XConstraint="20"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="5"
RelativeLayout.HeightConstraint="65" />
<Label Text="Stylish header"
FontSize="24"
RelativeLayout.XConstraint="30"
RelativeLayout.YConstraint="25" />
</RelativeLayout>
</ContentPage>

En este ejemplo, la posición de cada BoxView objeto se define usando los valores especificados en XConstraint
las YConstraint propiedades adjuntas y. El tamaño de cada BoxView se define utilizando los valores
especificados en WidthConstraint las HeightConstraint propiedades adjuntas y. La posición del Label objeto
también se define con los valores especificados en las XConstraint YConstraint propiedades adjuntas y. Sin
embargo, los valores de tamaño no se especifican para y Label , por tanto, no están restringidos y tienen un
tamaño propio. En todos los casos, los valores absolutos representan unidades independientes del dispositivo.
En las capturas de pantalla siguientes se muestra el diseño resultante:

El código de C# equivalente se muestra a continuación:


public class StylishHeaderDemoPageCS : ContentPage
{
public StylishHeaderDemoPageCS()
{
RelativeLayout relativeLayout = new RelativeLayout
{
Margin = new Thickness(20)
};

relativeLayout.Children.Add(new BoxView
{
Color = Color.Silver
}, () => new Rectangle(0, 10, 200, 5));

relativeLayout.Children.Add(new BoxView
{
Color = Color.Silver
}, () => new Rectangle(0, 20, 200, 5));

relativeLayout.Children.Add(new BoxView
{
Color = Color.Silver
}, () => new Rectangle(10, 0, 5, 65));

relativeLayout.Children.Add(new BoxView
{
Color = Color.Silver
}, () => new Rectangle(20, 0, 5, 65));

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

Title = "Stylish header demo";


Content = relativeLayout;
}
}

En este ejemplo, BoxView los objetos se agregan a RelativeLayout mediante una Add sobrecarga que requiere
que Expression<Func<Rectangle>> especifique la posición y el tamaño de cada elemento secundario. La posición
de Label se define utilizando una Add sobrecarga que requiere objetos opcionales Constraint , en este caso
creados por el Constraint.Constant método.

NOTE
RelativeLayout Que usa valores absolutos puede colocar y ajustar el tamaño de los elementos secundarios para que no
quepan dentro de los límites del diseño.

Posicionamiento y ajuste de tamaño relativos


RelativeLayout Puede colocar y cambiar el tamaño de los elementos secundarios con valores relativos a las
propiedades del diseño, o a los elementos relacionados. Para ello, se agregan elementos secundarios a la
Children colección de RelativeLayout y se establece la XConstraint YConstraint propiedad,, WidthConstraint
y HeightConstraint las propiedades adjuntas en cada elemento secundario en valores relativos mediante
Constraint objetos.

Las restricciones pueden ser una constante, relativa a un elemento primario o relativa a un elemento relacionado.
El tipo de restricción se representa mediante la ConstraintType enumeración, que define los siguientes
miembros:
RelativeToParent , que indica una restricción relativa a un elemento primario.
RelativeToView , que indica una restricción relativa a una vista (o al mismo nivel).
Constant , que indica una restricción Constant.

Extensión de marcado de restricción


En XAML, un Constraint objeto se puede crear mediante la ConstraintExpression extensión de marcado. Esta
extensión de marcado se usa normalmente para relacionar la posición y el tamaño de un elemento secundario
dentro de un RelativeLayout con su elemento primario o con un elemento relacionado.
La ConstraintExpression clase define las siguientes propiedades:
Constant , de tipo double , que representa el valor constante de restricción.
ElementName , de tipo string , que representa el nombre de un elemento de origen en el que se va a calcular
la restricción.
Factor , de tipo double , que representa el factor por el que se va a escalar una dimensión restringida, en
relación con el elemento de origen. El valor predeterminado de esta propiedad es 1.
Property , de tipo string , que representa el nombre de la propiedad en el elemento de origen que se va a
utilizar en el cálculo de la restricción.
Type , de tipo ConstraintType , que representa el tipo de la restricción.

Para más información sobre las extensiones de marcado de Xamarin.Forms, vea Extensiones de marcado para el
lenguaje XAML.
En el código XAML siguiente RelativeLayout se muestra un cuyos elementos secundarios están restringidos por
la ConstraintExpression extensión de marcado:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="RelativeLayoutDemos.Views.RelativePositioningAndSizingDemoPage"
Title="RelativeLayout demo">
<RelativeLayout>
<BoxView Color="Red"
RelativeLayout.XConstraint="{ConstraintExpression Type=Constant, Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}" />
<BoxView Color="Green"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Constant=-40}"
RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}" />
<BoxView Color="Blue"
RelativeLayout.XConstraint="{ConstraintExpression Type=Constant, Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Constant=-40}" />
<BoxView Color="Yellow"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Constant=-40}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Constant=-40}" />

<!-- Centered and 1/3 width and height of parent -->


<BoxView x:Name="oneThird"
Color="Silver"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=0.33}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height,
Factor=0.33}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height, Factor=0.33}" />

<!-- 1/3 width and height of previous -->


<BoxView Color="Black"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, ElementName=oneThird,
Property=X}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=oneThird,
Property=Y}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView,
ElementName=oneThird, Property=Width, Factor=0.33}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView,
ElementName=oneThird, Property=Height, Factor=0.33}" />
</RelativeLayout>
</ContentPage>

En este ejemplo, la posición de cada BoxView objeto se define estableciendo las XConstraint YConstraint
propiedades adjuntas y. El primero BoxView tiene sus XConstraint YConstraint propiedades y adjuntas
establecidas en constantes, que son valores absolutos. El resto de los BoxView objetos tienen su posición
establecida mediante al menos un valor relativo. Por ejemplo, el BoxView objeto amarillo establece la
XConstraint propiedad adjunta en el ancho de su elemento primario (el RelativeLayout ) menos 40. Del mismo
modo, BoxView establece la YConstraint propiedad adjunta en el alto de su elemento primario menos 40. Esto
garantiza que el amarillo BoxView aparezca en la esquina inferior derecha de la pantalla.

NOTE
BoxView los objetos que no especifican un tamaño se ajustan automáticamente a 40 x 40 por Xamarin.Forms .

El Silver BoxView denominado oneThird se coloca centralmente, con respecto a su elemento primario. También
se ajusta en relación con su elemento primario, siendo un tercio de su ancho y alto. Esto se logra estableciendo
las XConstraint WidthConstraint propiedades adjuntas y en el ancho del elemento primario (el RelativeLayout
), multiplicado por 0,33. Del mismo modo, las YConstraint HeightConstraint propiedades adjuntas y se
establecen en el alto del elemento primario, multiplicado por 0,33.
El color negro BoxView está colocado y tiene un tamaño relativo a oneThird BoxView . Esto se logra
estableciendo sus XConstraint propiedades y YConstraint adjuntas en X los Y valores y, respectivamente, del
elemento relacionado. Del mismo modo, su tamaño se establece en un tercio del ancho y el alto de su elemento
relacionado. Esto se logra estableciendo sus WidthConstraint propiedades y HeightConstraint adjuntas en
Width los Height valores y del elemento relacionado, respectivamente, que se multiplican por 0,33.

En la captura de pantalla siguiente se muestra el diseño resultante:

Objetos de restricción
La Constraint clase define los siguientes métodos estáticos públicos, que devuelven Constraint objetos:
Constant , que restringe un elemento secundario a un tamaño especificado con un double .
FromExpression , que restringe un elemento secundario mediante una expresión lambda.
RelativeToParent , que restringe un elemento secundario en relación con el tamaño de su elemento primario.
RelativeToView , que restringe un elemento secundario en relación con el tamaño de una vista.

Además, la BoundsConstraint clase define un método único, FromExpression , que devuelve un BoundsConstraint
que restringe la posición y el tamaño de un elemento secundario con un Expression<Func<Rectangle>> . Este
método se puede utilizar para establecer la BoundsConstraint propiedad adjunta.
En el siguiente código de C# RelativeLayout se muestra un cuyos elementos secundarios están restringidos por
Constraint objetos:
public class RelativePositioningAndSizingDemoPageCS : ContentPage
{
public RelativePositioningAndSizingDemoPageCS()
{
RelativeLayout relativeLayout = new RelativeLayout();

// Four BoxView's
relativeLayout.Children.Add(
new BoxView { Color = Color.Red },
Constraint.Constant(0),
Constraint.Constant(0));

relativeLayout.Children.Add(
new BoxView { Color = Color.Green },
Constraint.RelativeToParent((parent) =>
{
return parent.Width - 40;
}), Constraint.Constant(0));

relativeLayout.Children.Add(
new BoxView { Color = Color.Blue },
Constraint.Constant(0),
Constraint.RelativeToParent((parent) =>
{
return parent.Height - 40;
}));

relativeLayout.Children.Add(
new BoxView { Color = Color.Yellow },
Constraint.RelativeToParent((parent) =>
{
return parent.Width - 40;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height - 40;
}));

// Centered and 1/3 width and height of parent


BoxView silverBoxView = new BoxView { Color = Color.Silver };
relativeLayout.Children.Add(
silverBoxView,
Constraint.RelativeToParent((parent) =>
{
return parent.Width * 0.33;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height * 0.33;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Width * 0.33;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height * 0.33;
}));

// 1/3 width and height of previous


relativeLayout.Children.Add(
new BoxView { Color = Color.Black },
Constraint.RelativeToView(silverBoxView, (parent, sibling) =>
{
return sibling.X;
}),
Constraint.RelativeToView(silverBoxView, (parent, sibling) =>
{
return sibling.Y;
return sibling.Y;
}),
Constraint.RelativeToView(silverBoxView, (parent, sibling) =>
{
return sibling.Width * 0.33;
}),
Constraint.RelativeToView(silverBoxView, (parent, sibling) =>
{
return sibling.Height * 0.33;
}));

Title = "RelativeLayout demo";


Content = relativeLayout;
}
}

En este ejemplo, los elementos secundarios se agregan a RelativeLayout mediante la Add sobrecarga que
requiere un Constraint objeto opcional para los x y argumentos,, width y height .

NOTE
RelativeLayout Que usa valores relativos puede colocar y cambiar el tamaño de los elementos secundarios para que no
quepan dentro de los límites del diseño.

Vínculos relacionados
Demostraciones de RelativeLayout (ejemplo)
Xamarin.Forms Propiedades adjuntas
Extensiones de marcado XAML
Elegir un Xamarin.Forms diseño
Mejorar el rendimiento de las Xamarin.Forms aplicaciones
:::no-loc(Xamarin.Forms)::: StackLayout
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo

Un StackLayout organiza las vistas secundarias en una pila unidimensional, ya sea horizontal o verticalmente.
De forma predeterminada, una StackLayout está orientada verticalmente. Además, StackLayout se puede
utilizar como un diseño primario que contiene otros diseños secundarios.
La StackLayout clase define las siguientes propiedades:
Orientation , de tipo StackOrientation , representa la dirección en la que se colocan las vistas secundarias. El
valor predeterminado de esta propiedad es Vertical .
Spacing , de tipo double , indica la cantidad de espacio entre cada vista secundaria. El valor predeterminado
de esta propiedad es de seis unidades independientes del dispositivo.
Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que las propiedades pueden
ser destinos de enlaces de datos y con estilo.
La StackLayoutclase se deriva de la Layout<T> clase, que define una Children propiedad de tipo IList<T> . La
Children propiedad es ContentProperty de la Layout<T> clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

TIP
Para obtener el mejor rendimiento posible del diseño, siga las instrucciones que se indican en optimizar el rendimiento del
diseño.

Orientación vertical
En el código XAML siguiente se muestra cómo crear una orientada verticalmente StackLayout que contiene
diferentes vistas secundarias:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.VerticalStackLayoutPage"
Title="Vertical StackLayout demo">
<StackLayout Margin="20">
<Label Text="Primary colors" />
<BoxView Color="Red" />
<BoxView Color="Yellow" />
<BoxView Color="Blue" />
<Label Text="Secondary colors" />
<BoxView Color="Green" />
<BoxView Color="Orange" />
<BoxView Color="Purple" />
</StackLayout>
</ContentPage>

En este ejemplo se crea un StackLayout objeto vertical que contiene Label BoxView objetos y. De forma
predeterminada, hay seis unidades de espacio independientes del dispositivo entre las vistas secundarias:

El código de C# equivalente es el siguiente:

public class VerticalStackLayoutPageCS : ContentPage


{
public VerticalStackLayoutPageCS()
{
Title = "Vertical StackLayout demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Children =
{
new Label { Text = "Primary colors" },
new BoxView { Color = Color.Red },
new BoxView { Color = Color.Yellow },
new BoxView { Color = Color.Blue },
new Label { Text = "Secondary colors" },
new BoxView { Color = Color.Green },
new BoxView { Color = Color.Orange },
new BoxView { Color = Color.Purple }
}
};
}
}

NOTE
El valor de la Margin propiedad representa la distancia entre un elemento y sus elementos adyacentes. Para obtener más
información, vea Márgenes y relleno.
Orientación horizontal
En el siguiente código XAML se muestra cómo crear una orientada horizontalmente estableciendo StackLayout
su Orientation propiedad en Horizontal :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.HorizontalStackLayoutPage"
Title="Horizontal StackLayout demo">
<StackLayout Margin="20"
Orientation="Horizontal"
HorizontalOptions="Center">
<BoxView Color="Red" />
<BoxView Color="Yellow" />
<BoxView Color="Blue" />
<BoxView Color="Green" />
<BoxView Color="Orange" />
<BoxView Color="Purple" />
</StackLayout>
</ContentPage>

En este ejemplo se crea StackLayout un BoxView objeto que contiene objetos, con seis unidades de espacio
independientes del dispositivo entre las vistas secundarias:

El código de C# equivalente es el siguiente:


public HorizontalStackLayoutPageCS()
{
Title = "Horizontal StackLayout demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.Center,
Children =
{
new BoxView { Color = Color.Red },
new BoxView { Color = Color.Yellow },
new BoxView { Color = Color.Blue },
new BoxView { Color = Color.Green },
new BoxView { Color = Color.Orange },
new BoxView { Color = Color.Purple }
}
};
}

Espacio entre vistas secundarias


El espaciado entre las vistas secundarias de un StackLayout se puede cambiar estableciendo la Spacing
propiedad en un double valor:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.StackLayoutSpacingPage"
Title="StackLayout Spacing demo">
<StackLayout Margin="20"
Spacing="0">
<Label Text="Primary colors" />
<BoxView Color="Red" />
<BoxView Color="Yellow" />
<BoxView Color="Blue" />
<Label Text="Secondary colors" />
<BoxView Color="Green" />
<BoxView Color="Orange" />
<BoxView Color="Purple" />
</StackLayout>
</ContentPage>

En este ejemplo se crea un objeto vertical que StackLayout contiene Label BoxView objetos y que no tienen
espaciado entre ellos:

TIP
La Spacing propiedad se puede establecer en valores negativos para que las vistas secundarias se superpongan.

El código de C# equivalente es el siguiente:


public class StackLayoutSpacingPageCS : ContentPage
{
public StackLayoutSpacingPageCS()
{
Title = "StackLayout Spacing demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Spacing = 0,
Children =
{
new Label { Text = "Primary colors" },
new BoxView { Color = Color.Red },
new BoxView { Color = Color.Yellow },
new BoxView { Color = Color.Blue },
new Label { Text = "Secondary colors" },
new BoxView { Color = Color.Green },
new BoxView { Color = Color.Orange },
new BoxView { Color = Color.Purple }
}
};
}
}

Posición y tamaño de las vistas secundarias


El tamaño y la posición de las vistas secundarias dentro de StackLayout depende de los valores de las
propiedades y de las vistas secundarias HeightRequest WidthRequest , así como de los valores de sus
HorizontalOptions VerticalOptions propiedades y. En una StackLayout vista vertical, las vistas secundarias se
expanden para rellenar el ancho disponible cuando su tamaño no se establece explícitamente. Del mismo modo,
en una StackLayout vista horizontal, las vistas secundarias se expanden para rellenar el alto disponible cuando
su tamaño no se establece explícitamente.
Las HorizontalOptions VerticalOptions propiedades y de un StackLayout , y sus vistas secundarias, se pueden
establecer en campos del LayoutOptions struct, que encapsula dos preferencias de diseño:
La alineación determina la posición y el tamaño de una vista secundaria dentro de su diseño primario.
La expansión indica si la vista secundaria debe utilizar espacio adicional, si está disponible.

TIP
No establezca las HorizontalOptions VerticalOptions propiedades y de a StackLayout menos que necesite. Los
valores predeterminados de LayoutOptions.Fill y LayoutOptions.FillAndExpand permiten la optimización de diseño
óptima. Cambiar estas propiedades tiene un costo y consume memoria, incluso cuando se vuelven a establecer en los
valores predeterminados.

Asociación
En el siguiente ejemplo de XAML se establecen las preferencias de alineación en cada vista secundaria del
StackLayout :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.AlignmentPage"
Title="Alignment demo">
<StackLayout Margin="20">
<Label Text="Start"
BackgroundColor="Gray"
HorizontalOptions="Start" />
<Label Text="Center"
BackgroundColor="Gray"
HorizontalOptions="Center" />
<Label Text="End"
BackgroundColor="Gray"
HorizontalOptions="End" />
<Label Text="Fill"
BackgroundColor="Gray"
HorizontalOptions="Fill" />
</StackLayout>
</ContentPage>

En este ejemplo, las preferencias de alineación se establecen en los Label objetos para controlar su posición
dentro de StackLayout . Los Start Center campos,, End y Fill se utilizan para definir la alineación de los
Label objetos en el elemento primario StackLayout :

Un elemento StackLayout solo respeta las preferencias de alineación de las vistas secundarias que tienen la
dirección opuesta a la orientación del StackLayout . Por lo tanto, las vistas secundarias de Label dentro del
StackLayout con orientación vertical establecen sus propiedades HorizontalOptions en uno de los campos de
alineación siguientes:
Start , que coloca el control Label en el lado izquierdo de StackLayout .
Center , que centra Label en el StackLayout .
End , que coloca Label en el lado derecho de StackLayout .
Fill , que garantiza que Label llena el ancho del StackLayout .

El código de C# equivalente es el siguiente:


public class AlignmentPageCS : ContentPage
{
public AlignmentPageCS()
{
Title = "Alignment demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Children =
{
new Label { Text = "Start", BackgroundColor = Color.Gray, HorizontalOptions =
LayoutOptions.Start },
new Label { Text = "Center", BackgroundColor = Color.Gray, HorizontalOptions =
LayoutOptions.Center },
new Label { Text = "End", BackgroundColor = Color.Gray, HorizontalOptions =
LayoutOptions.End },
new Label { Text = "Fill", BackgroundColor = Color.Gray, HorizontalOptions =
LayoutOptions.Fill }
}
};
}
}

Expansión
En el siguiente ejemplo de XAML se establecen las preferencias de expansión en cada Label de StackLayout :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.ExpansionPage"
Title="Expansion demo">
<StackLayout Margin="20">
<BoxView BackgroundColor="Red"
HeightRequest="1" />
<Label Text="Start"
BackgroundColor="Gray"
VerticalOptions="StartAndExpand" />
<BoxView BackgroundColor="Red"
HeightRequest="1" />
<Label Text="Center"
BackgroundColor="Gray"
VerticalOptions="CenterAndExpand" />
<BoxView BackgroundColor="Red"
HeightRequest="1" />
<Label Text="End"
BackgroundColor="Gray"
VerticalOptions="EndAndExpand" />
<BoxView BackgroundColor="Red"
HeightRequest="1" />
<Label Text="Fill"
BackgroundColor="Gray"
VerticalOptions="FillAndExpand" />
<BoxView BackgroundColor="Red"
HeightRequest="1" />
</StackLayout>
</ContentPage>

En este ejemplo, se establecen las preferencias de expansión en los Label objetos para controlar su tamaño
dentro de StackLayout . Los StartAndExpand CenterAndExpand campos,, EndAndExpand y FillAndExpand se usan
para definir las preferencias de alineación y si el Label ocupará más espacio si está disponible en el elemento
primario StackLayout :
Un elemento StackLayout solo puede expandir las vistas secundarias en la dirección de su orientación. Por lo
tanto, un StackLayout con orientación vertical puede expandir las vistas secundarias de Label que establecen
sus propiedades VerticalOptions en uno de los campos de alineación. Esto significa que, para la alineación
vertical, cada Label ocupa la misma cantidad de espacio en el StackLayout . Aun así, solo el último elemento
Label , que establece su propiedad VerticalOptions en FillAndExpand , tiene un tamaño diferente.

TIP
Al utilizar StackLayout , asegúrese de que solo una vista secundaria está establecida en LayoutOptions.Expands . Esta
propiedad garantiza que el elemento secundario especificado ocupa el mayor espacio que el StackLayout puede
asignarle y es poco rentable realizar estos cálculos más de una vez.

El código de C# equivalente es el siguiente:

public ExpansionPageCS()
{
Title = "Expansion demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Children =
{
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "StartAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.StartAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "CenterAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.CenterAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "EndAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.EndAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "FillAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.FillAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 }
}
};
}
IMPORTANT
Cuando se usa todo el espacio de un elemento StackLayout , las preferencias de expansión no tienen ningún efecto.

Para obtener más información acerca de la alineación y la expansión, vea Opciones de diseño en :::no-
loc(Xamarin.Forms)::: .

Objetos StackLayout anidados


StackLayout Se puede utilizar como un diseño primario que contiene objetos secundarios anidados StackLayout
u otros diseños secundarios.
En el código XAML siguiente se muestra un ejemplo de anidamiento de StackLayout objetos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.CombinedStackLayoutPage"
Title="Combined StackLayouts demo">
<StackLayout Margin="20">
...
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Red" />
<Label Text="Red"
FontSize="Large"
VerticalOptions="Center" />
</StackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Yellow" />
<Label Text="Yellow"
FontSize="Large"
VerticalOptions="Center" />
</StackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Blue" />
<Label Text="Blue"
FontSize="Large"
VerticalOptions="Center" />
</StackLayout>
</Frame>
...
</StackLayout>
</ContentPage>

En este ejemplo, el elemento primario StackLayout contiene objetos anidados dentro de los StackLayout Frame
objetos. El elemento primario StackLayout está orientado verticalmente, mientras que los StackLayout objetos
secundarios se orientan horizontalmente:
IMPORTANT
Cuanto más profundo sea el anidamiento de StackLayout objetos y otros diseños, más los diseños anidados afectarán al
rendimiento. Para obtener más información, vea elegir el diseño correcto.

El código de C# equivalente es el siguiente:


public class CombinedStackLayoutPageCS : ContentPage
{
public CombinedStackLayoutPageCS()
{
Title = "Combined StackLayouts demo";
Content = new StackLayout
{
Margin = new Thickness(20),
Children =
{
new Label { Text = "Primary colors" },
new Frame
{
BorderColor = Color.Black,
Padding = new Thickness(5),
Content = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Spacing = 15,
Children =
{
new BoxView { Color = Color.Red },
new Label { Text = "Red", FontSize = Device.GetNamedSize(NamedSize.Large,
typeof(Label)), VerticalOptions = LayoutOptions.Center }
}
}
},
new Frame
{
BorderColor = Color.Black,
Padding = new Thickness(5),
Content = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Spacing = 15,
Children =
{
new BoxView { Color = Color.Yellow },
new Label { Text = "Yellow", FontSize = Device.GetNamedSize(NamedSize.Large,
typeof(Label)), VerticalOptions = LayoutOptions.Center }
}
}
},
new Frame
{
BorderColor = Color.Black,
Padding = new Thickness(5),
Content = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Spacing = 15,
Children =
{
new BoxView { Color = Color.Blue },
new Label { Text = "Blue", FontSize = Device.GetNamedSize(NamedSize.Large,
typeof(Label)), VerticalOptions = LayoutOptions.Center }
}
}
},
// ...
}
};
}
}
Vínculos relacionados
Demostraciones de StackLayout (ejemplo)
Opciones de diseño en :::no-loc(Xamarin.Forms):::
Elegir un :::no-loc(Xamarin.Forms)::: diseño
Mejorar el rendimiento de las :::no-loc(Xamarin.Forms)::: aplicaciones
:::no-loc(Xamarin.Forms)::: ContentView
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: ContentView clase es un tipo de Layout que contiene un único elemento secundario
y se utiliza normalmente para crear controles personalizados y reutilizables. La ContentView clase hereda de
TemplatedView . En este artículo, y en el ejemplo asociado, se explica cómo crear un CardView control
personalizado basado en la ContentView clase.
En la captura de pantalla siguiente se muestra un CardView control que se deriva de la ContentView clase:

La ContentView clase define una propiedad única:


Content es un View objeto. Esta propiedad está respaldada por un BindableProperty objeto, por lo que
puede ser el destino de los enlaces de datos.
ContentView También hereda una propiedad de la TemplatedView clase:
ControlTemplate es un ControlTemplate que puede definir o invalidar la apariencia del control.
Para obtener más información sobre la ControlTemplate propiedad, vea personalizar el aspecto con una
ControlTemplate.

Crear un control personalizado


La ContentView clase ofrece poca funcionalidad, pero se puede usar para crear un control personalizado. El
proyecto de ejemplo define un CardView elemento de la interfaz de usuario de control-a que muestra una
imagen, un título y una descripción en un diseño de tipo tarjeta.
El proceso para crear un control personalizado es:
1. Cree una nueva clase con la ContentView plantilla en Visual Studio 2019.
2. Defina cualquier propiedad o evento único en el archivo de código subyacente para el nuevo control
personalizado.
3. Cree la interfaz de usuario para el control personalizado.
NOTE
Es posible crear un control personalizado cuyo diseño se define en el código en lugar de XAML. Para simplificar, la aplicación
de ejemplo solo define una CardView clase única con un diseño XAML. Sin embargo, la aplicación de ejemplo contiene una
clase CardViewCodePage que muestra el proceso de consumo del control personalizado en el código.

Crear propiedades de código subyacente


El CardView control personalizado define las siguientes propiedades:
CardTitle : un objeto que representa el título que se muestra en la tarjeta.
string
CardDescription : un string objeto que representa la descripción que se muestra en la tarjeta.
IconImageSource : un ImageSource objeto que representa la imagen que se muestra en la tarjeta.
IconBackgroundColor : un Color objeto que representa el color de fondo de la imagen que se muestra en la
tarjeta.
BorderColor : un Color objeto que representa el color del borde de la tarjeta, el borde de la imagen y la línea
del divisor.
CardColor : un Color objeto que representa el color de fondo de la tarjeta.

NOTE
La BorderColor propiedad afecta a varios elementos con fines de demostración. Esta propiedad podría dividirse en tres
propiedades si es necesario.

Cada propiedad está respaldada por una BindableProperty instancia de. La copia de seguridad BindableProperty
permite aplicar estilo a cada propiedad y enlazarla mediante el patrón MVVM.
En el ejemplo siguiente se muestra cómo crear una copia de seguridad BindableProperty :

public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(


"CardTitle", // the name of the bindable property
typeof(string), // the bindable property type
typeof(CardView), // the parent object type
string.Empty); // the default value for the property

La propiedad personalizada usa los GetValue SetValue métodos y para obtener y establecer los
BindableProperty valores del objeto:

public string CardTitle


{
get => (string)GetValue(CardView.CardTitleProperty);
set => SetValue(CardView.CardTitleProperty, value);
}

Para obtener más información sobre los BindableProperty objetos, vea propiedades enlazables.
Definir la interfaz de usuario
La interfaz de usuario del control personalizado usa ContentView como elemento raíz para el CardView control.
En el ejemplo siguiente se muestra el CardView código XAML:
<ContentView ...
x:Name="this"
x:Class="CardViewDemo.Controls.CardView">
<Frame BindingContext="{x:Reference this}"
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
...>
<Grid>
...
<Frame BorderColor="{Binding BorderColor, FallbackValue='Black'}"
BackgroundColor="{Binding IconBackgroundColor, FallbackValue='Grey'}"
...>
<Image Source="{Binding IconImageSource}"
.. />
</Frame>
<Label Text="{Binding CardTitle, FallbackValue='Card Title'}"
... />
<BoxView BackgroundColor="{Binding BorderColor, FallbackValue='Black'}"
... />
<Label Text="{Binding CardDescription, FallbackValue='Card description text.'}"
... />
</Grid>
</Frame>
</ContentView>

El ContentView elemento establece la x:Name propiedad en this , que se puede utilizar para tener acceso al objeto
enlazado a la CardView instancia. Los elementos del diseño establecen enlaces en sus propiedades a los valores
definidos en el objeto enlazado.
Para obtener más información sobre el enlace de datos, consulte Enlace de datos de :::no-loc(Xamarin.Forms):::.

NOTE
La FallbackValue propiedad proporciona un valor predeterminado en caso de que el enlace sea null . Esto también
permite que el controlador de vista previa de XAML de Visual Studio represente el CardView control.

Crear una instancia de un control personalizado


Se debe agregar una referencia al espacio de nombres del control personalizado a una página que cree instancias
del control personalizado. En el ejemplo siguiente se muestra una referencia de espacio de nombres denominada
controles agregados a una ContentPage instancia en XAML:

<ContentPage ...
xmlns:controls="clr-namespace:CardViewDemo.Controls" >

Una vez agregada la referencia CardView , se pueden crear instancias en XAML y se han definido sus propiedades:

<controls:CardView BorderColor="DarkGray"
CardTitle="Slavko Vlasic"
CardDescription="Lorem ipsum dolor sit..."
IconBackgroundColor="SlateGray"
IconImageSource="user.png"/>

CardView También se puede crear una instancia de un en el código:


CardView card = new CardView
{
BorderColor = Color.DarkGray,
CardTitle = "Slavko Vlasic",
CardDescription = "Lorem ipsum dolor sit...",
IconBackgroundColor = Color.SlateGray,
IconImageSource = ImageSource.FromFile("user.png")
};

Personalización de la apariencia con un ControlTemplate


Un control personalizado que se deriva de la ContentView clase puede definir la apariencia mediante XAML,
código o no puede definir la apariencia en absoluto. Independientemente de cómo se defina la apariencia, un
ControlTemplate objeto puede invalidar la apariencia con un diseño personalizado.

El CardView diseño podría ocupar demasiado espacio en algunos casos de uso. Un ControlTemplate puede
invalidar el CardView diseño para proporcionar una vista más compacta, adecuada para una lista comprimida:

<ContentPage.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="CardViewCompressed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0"
Grid.Column="0"
Source="{TemplateBinding IconImageSource}"
BackgroundColor="{TemplateBinding IconBackgroundColor}"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill"
HorizontalOptions="Center"
VerticalOptions="Center"/>
<StackLayout Grid.Row="0"
Grid.Column="1">
<Label Text="{TemplateBinding CardTitle}"
FontAttributes="Bold" />
<Label Text="{TemplateBinding CardDescription}" />
</StackLayout>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</ContentPage.Resources>

El enlace de datos de un ControlTemplate utiliza la TemplateBinding extensión de marcado para especificar


enlaces. ControlTemplate A continuación, la propiedad se puede establecer en el objeto ControlTemplate definido
mediante su x:Key valor. En el ejemplo siguiente se muestra la ControlTemplate propiedad establecida en una
CardView instancia de:

<controls:CardView ControlTemplate="{StaticResource CardViewCompressed}"/>

Las capturas de pantallas siguientes muestran una CardView instancia estándar y cuyo se ha CardView
ControlTemplate invalidado:
Para obtener más información sobre las plantillas de control, consulte Plantillas de control de :::no-
loc(Xamarin.Forms):::.

Vínculos relacionados
Aplicación de ejemplo ContentView
Enlace de datos de :::no-loc(Xamarin.Forms):::
Propiedades enlazables.
Plantillas de control de :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: Grama
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
La :::no-loc(Xamarin.Forms)::: Frame clase es un diseño que se usa para ajustar una vista con un borde que se
puede configurar con color, sombra y otras opciones. Los marcos se suelen usar para crear bordes alrededor de los
controles, pero se pueden usar para crear una interfaz de usuario más compleja. Para obtener más información,
consulte uso avanzado de fotogramas.
En la captura de pantalla siguiente se muestran Frame los controles de iOS y Android:

La clase Frame define las propiedades siguientes:


BorderColor es un Color valor que determina el color del Frame borde.
CornerRadius es un float valor que determina el radio redondeado de la esquina.
HasShadow es un bool valor que determina si el marco tiene una sombra paralela.

Estas propiedades están respaldadas por BindableProperty objetos, lo que significa que Frame puede ser el
destino de los enlaces de datos.

NOTE
El HasShadow comportamiento de la propiedad depende de la plataforma. El valor predeterminado es true en todas las
plataformas. Sin embargo, en las sombras paralelas de UWP no se representan. Las sombras paralelas se representan en
Android y en iOS, pero las sombras paralelas en iOS son más oscuras y ocupan más espacio.

Crear un marco
Frame Se puede crear una instancia de en XAML. El Frame objeto predeterminado tiene un fondo blanco, una
sombra paralela y ningún borde. Un Frame objeto normalmente ajusta otro control. En el ejemplo siguiente se
muestra un Frame contenedor predeterminado de un Label objeto:

<Frame>
<Label Text="Example" />
</Frame>

Frame También se puede crear en el código:


Frame defaultFrame = new Frame
{
Content = new Label { Text = "Example" }
};

Frame los objetos se pueden personalizar con esquinas redondeadas, bordes coloreados y sombras paralelas
estableciendo propiedades en el código XAML. En el ejemplo siguiente se muestra un Frame objeto personalizado:

<Frame BorderColor="Orange"
CornerRadius="10"
HasShadow="True">
<Label Text="Example" />
</Frame>

Estas propiedades de instancia también se pueden establecer en el código:

Frame frame = new Frame


{
BorderColor = Color.Orange,
CornerRadius = 10,
HasShadow = true,
Content = new Label { Text = "Example" }
};

Uso avanzado de fotogramas


La Frame clase hereda de ContentView , lo que significa que puede contener cualquier tipo de View objeto,
incluidos los Layout objetos. Esta capacidad permite que Frame se utilice para crear objetos de interfaz de usuario
complejos, como tarjetas.
Crear una tarjeta con un marco
La combinación Frame de un objeto con un Layout objeto como un StackLayout objeto permite la creación de
interfaces de usuario más complejas. La siguiente captura de pantalla muestra una tarjeta de ejemplo, creada con
un Frame objeto:

En el siguiente código XAML se muestra cómo crear una tarjeta con la Frame clase:

<Frame BorderColor="Gray"
CornerRadius="5"
Padding="8">
<StackLayout>
<Label Text="Card Example"
FontSize="Medium"
FontAttributes="Bold" />
<BoxView Color="Gray"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Text="Frames can wrap more complex layouts to create more complex UI components, such as this
card!"/>
</StackLayout>
</Frame>
También se puede crear una tarjeta en el código:

Frame cardFrame = new Frame


{
BorderColor = Color.Gray,
CornerRadius = 5,
Padding = 8,
Content = new StackLayout
{
Children =
{
new Label
{
Text = "Card Example",
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
FontAttributes = FontAttributes.Bold
},
new BoxView
{
Color = Color.Gray,
HeightRequest = 2,
HorizontalOptions = LayoutOptions.Fill
},
new Label
{
Text = "Frames can wrap more complex layouts to create more complex UI components, such as
this card!"
}
}
}
};

Elementos Round
La CornerRadius propiedad del Frame control se puede utilizar para crear una imagen de círculo. En la captura de
pantalla siguiente se muestra un ejemplo de una imagen redondeada creada mediante un Frame objeto:

En el código XAML siguiente se muestra cómo crear una imagen de círculo en XAML:

<Frame Margin="10"
BorderColor="Black"
CornerRadius="50"
HeightRequest="60"
WidthRequest="60"
IsClippedToBounds="True"
HorizontalOptions="Center"
VerticalOptions="Center">
<Image Source="outdoors.jpg"
Aspect="AspectFill"
Margin="-20"
HeightRequest="100"
WidthRequest="100" />
</Frame>

También se puede crear una imagen circular en el código:


Frame circleImageFrame = new Frame
{
Margin = 10,
BorderColor = Color.Black,
CornerRadius = 50,
HeightRequest = 60,
WidthRequest = 60,
IsClippedToBounds = true,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = new Image
{
Source = ImageSource.FromFile("outdoors.jpg"),
Aspect = Aspect.AspectFill,
Margin = -20,
HeightRequest = 100,
WidthRequest = 100
}
};

La imagen de outdoors.jpg se debe agregar a cada proyecto de plataforma y la manera en que se logra esto varía
según la plataforma. Para obtener más información, vea imágenes :::no-loc(Xamarin.Forms)::: en .

NOTE
Las esquinas redondeadas se comportan de manera ligeramente diferente en las distintas plataformas. El Image objeto
Margin debe ser la mitad de la diferencia entre el ancho de la imagen y el ancho del marco primario, y debe ser negativo
para centrar la imagen uniformemente dentro del Frame objeto. Sin embargo, no se garantiza el ancho y el alto solicitados,
por lo que Margin HeightRequest es posible que sea necesario modificar las propiedades, y en WidthRequest función
del tamaño de la imagen y otras opciones de diseño.

Vínculos relacionados
Demostraciones de fotogramas
Imágenes en :::no-loc(Xamarin.Forms):::
:::no-loc(Xamarin.Forms)::: ScrollView
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo

ScrollView es un diseño capaz de desplazarse por el contenido. La ScrollView clase se deriva de la Layout clase
y, de forma predeterminada, desplaza el contenido verticalmente. Un ScrollView solo puede tener un único
elemento secundario, aunque puede ser otros diseños.

WARNING
ScrollView los objetos no deben estar anidados. Además, ScrollView los objetos no se deben anidar con otros
controles que proporcionen desplazamiento, como CollectionView , ListView y WebView .

ScrollView define las siguientes propiedades:


Content , de tipo View , representa el contenido que se va a mostrar en ScrollView .
ContentSize , de tipo Size , representa el tamaño del contenido. Se trata de una propiedad de solo lectura.
HorizontalScrollBarVisibility , de tipo ScrollBarVisibility , representa cuando la barra de desplazamiento
horizontal está visible.
Orientation , de tipo ScrollOrientation , representa la dirección de desplazamiento de ScrollView . El valor
predeterminado de esta propiedad es Vertical .
ScrollX , de tipo double , indica la posición de desplazamiento X actual. El valor predeterminado de esta
propiedad de solo lectura es 0.
ScrollY , de tipo double , indica la posición de desplazamiento actual. El valor predeterminado de esta
propiedad de solo lectura es 0.
VerticalScrollBarVisibility , de tipo ScrollBarVisibility , representa cuando la barra de desplazamiento
vertical está visible.
Estas propiedades están respaldadas por BindableProperty objetos, con la excepción de la Content propiedad, lo
que significa que pueden ser destinos de enlaces de datos y con estilo.
La Content propiedad es ContentProperty de la ScrollView clase y, por tanto, no es necesario establecer
explícitamente desde XAML.

TIP
Para obtener el mejor rendimiento posible del diseño, siga las instrucciones que se indican en optimizar el rendimiento del
diseño.
ScrollView como un diseño raíz
Un ScrollView solo puede tener un único elemento secundario, que puede ser otros diseños. Por lo tanto, es
habitual que ScrollView sea el diseño raíz en una página. Para desplazar el contenido secundario, ScrollView
calcula la diferencia entre el alto de su contenido y su propio alto. Esta diferencia es la cantidad en la que
ScrollView puede desplazarse por el contenido.

StackLayout A menudo será el elemento secundario de ScrollView . En este escenario, el ScrollView hace que
StackLayout sea tan alto como la suma de los altos de sus elementos secundarios. A continuación, el ScrollView
puede determinar la cantidad que se puede desplazar su contenido. Para obtener más información sobre
StackLayout , vea :::no-loc(Xamarin.Forms)::: StackLayout.

Cau t i on

En un vertical ScrollView , evite establecer la VerticalOptions propiedad en Start , Center o End . Al hacerlo
ScrollView , se indica a que solo sea tan alto como sea necesario, que podría ser cero. Aunque :::no-
loc(Xamarin.Forms)::: protege contra esta eventualidad, es mejor evitar el código que sugiere algo que no quiere
que suceda.
El siguiente ejemplo de XAML tiene ScrollView como diseño raíz en una página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ScrollViewDemos"
x:Class="ScrollViewDemos.Views.ColorListPage"
Title="ScrollView demo">
<ScrollView>
<StackLayout BindableLayout.ItemsSource="{x:Static local:NamedColor.All}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
HeightRequest="32"
WidthRequest="32"
VerticalOptions="Center" />
<Label Text="{Binding FriendlyName}"
FontSize="24"
VerticalOptions="Center" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage>

En este ejemplo, ScrollView su contenido se establece en un StackLayout que usa un diseño enlazable para
mostrar los Color campos definidos por :::no-loc(Xamarin.Forms)::: . De forma predeterminada, un se ScrollView
desplaza verticalmente, lo que revela más contenido:
El código de C# equivalente es el siguiente:

public class ColorListPageCode : ContentPage


{
public ColorListPageCode()
{
DataTemplate dataTemplate = new DataTemplate(() =>
{
BoxView boxView = new BoxView
{
HeightRequest = 32,
WidthRequest = 32,
VerticalOptions = LayoutOptions.Center
};
boxView.SetBinding(BoxView.ColorProperty, "Color");

Label label = new Label


{
FontSize = 24,
VerticalOptions = LayoutOptions.Center
};
label.SetBinding(Label.TextProperty, "FriendlyName");

StackLayout horizontalStackLayout = new StackLayout


{
Orientation = StackOrientation.Horizontal,
Children = { boxView, label }
};
return horizontalStackLayout;
});

StackLayout stackLayout = new StackLayout();


BindableLayout.SetItemsSource(stackLayout, NamedColor.All);
BindableLayout.SetItemTemplate(stackLayout, dataTemplate);

ScrollView scrollView = new ScrollView { Content = stackLayout };

Title = "ScrollView demo";


Content = scrollView;
}
}

Para obtener más información sobre los diseños enlazables, vea diseños enlazables en :::no-loc(Xamarin.Forms)::: .
ScrollView como diseño secundario
ScrollView Puede ser un diseño secundario para un diseño primario diferente.
ScrollView A menudo será el elemento secundario de StackLayout . Un ScrollView requiere un alto específico
para calcular la diferencia entre el alto de su contenido y su propio alto, con la diferencia de que ScrollView puede
desplazarse por el contenido. Cuando ScrollView es el elemento secundario de StackLayout , no recibe un alto
específico. StackLayout Quiere que ScrollView sea lo más corto posible, que es el alto del ScrollView contenido
o cero. Para controlar este escenario, la VerticalOptions propiedad de ScrollView debe establecerse en
FillAndExpand . Esto hará que el StackLayout elemento proporcione el ScrollView espacio adicional no requerido
por los demás elementos secundarios y ScrollView , a continuación, tendrá un alto específico.
El siguiente ejemplo de XAML tiene ScrollView como diseño secundario a StackLayout :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScrollViewDemos.Views.BlackCatPage"
Title="ScrollView as a child layout demo">
<StackLayout Margin="20">
<Label Text="THE BLACK CAT by Edgar Allan Poe"
FontSize="Medium"
FontAttributes="Bold"
HorizontalOptions="Center" />
<ScrollView VerticalOptions="FillAndExpand">
<StackLayout>
<Label Text="FOR the most wild, yet most homely narrative which I am about to pen, I neither
expect nor solicit belief. Mad indeed would I be to expect it, in a case where my very senses reject their own
evidence. Yet, mad am I not -- and very surely do I not dream. But to-morrow I die, and to-day I would
unburthen my soul. My immediate purpose is to place before the world, plainly, succinctly, and without
comment, a series of mere household events. In their consequences, these events have terrified -- have
tortured -- have destroyed me. Yet I will not attempt to expound them. To me, they have presented little but
Horror -- to many they will seem less terrible than barroques. Hereafter, perhaps, some intellect may be found
which will reduce my phantasm to the common-place -- some intellect more calm, more logical, and far less
excitable than my own, which will perceive, in the circumstances I detail with awe, nothing more than an
ordinary succession of very natural causes and effects." />
<!-- More Label objects go here -->
</StackLayout>
</ScrollView>
</StackLayout>
</ContentPage>

En este ejemplo, hay dos StackLayout objetos. El primero StackLayout es el objeto de diseño raíz, que tiene un
Label objeto y un ScrollView como sus elementos secundarios. ScrollView Tiene StackLayout como contenido,
con el StackLayout que contiene varios Label objetos. Esta disposición garantiza que la primera Label esté
siempre en pantalla, mientras que el texto mostrado por los demás Label objetos se puede desplazar:
El código de C# equivalente es el siguiente:

public class BlackCatPageCS : ContentPage


{
public BlackCatPageCS()
{
Label titleLabel = new Label
{
Text = "THE BLACK CAT by Edgar Allan Poe",
// More properties set here to define the Label appearance
};

ScrollView scrollView = new ScrollView


{
VerticalOptions = LayoutOptions.FillAndExpand,
Content = new StackLayout
{
Children =
{
new Label
{
Text = "FOR the most wild, yet most homely narrative which I am about to pen, I
neither expect nor solicit belief. Mad indeed would I be to expect it, in a case where my very senses reject
their own evidence. Yet, mad am I not -- and very surely do I not dream. But to-morrow I die, and to-day I
would unburthen my soul. My immediate purpose is to place before the world, plainly, succinctly, and without
comment, a series of mere household events. In their consequences, these events have terrified -- have
tortured -- have destroyed me. Yet I will not attempt to expound them. To me, they have presented little but
Horror -- to many they will seem less terrible than barroques. Hereafter, perhaps, some intellect may be found
which will reduce my phantasm to the common-place -- some intellect more calm, more logical, and far less
excitable than my own, which will perceive, in the circumstances I detail with awe, nothing more than an
ordinary succession of very natural causes and effects.",
},
// More Label objects go here
}
}
};

Title = "ScrollView as a child layout demo";


Content = new StackLayout
{
Margin = new Thickness(20),
Children = { titleLabel, scrollView }
};
}
}
Orientación
ScrollView tiene una Orientation propiedad, que representa la dirección de desplazamiento de ScrollView . Esta
propiedad es de tipo ScrollOrientation , que define los siguientes miembros:
Vertical indica que ScrollView se desplazará verticalmente. Este miembro es el valor predeterminado de la
Orientation propiedad.
Horizontal indica que ScrollView se desplazará horizontalmente.
Both indica que ScrollView se desplazará horizontal y verticalmente.
Neither indica que ScrollView no se desplazará.

TIP
El desplazamiento se puede deshabilitar estableciendo la Orientation propiedad en Neither .

Detectar desplazamiento
ScrollView define un Scrolled evento que se desencadena para indicar que se ha producido el desplazamiento.
El ScrolledEventArgs objeto que acompaña al Scrolled evento tiene ScrollX propiedades y ScrollY , ambos de
tipo double .

IMPORTANT
Las ScrolledEventArgs.ScrollX ScrolledEventArgs.ScrollY propiedades y pueden tener valores negativos, debido al
efecto de rebote que se produce al desplazarse de nuevo al inicio de ScrollView .

En el ejemplo de XAML siguiente se muestra un ScrollView que establece un controlador de eventos para el
Scrolled evento:

<ScrollView Scrolled="OnScrollViewScrolled">
...
</ScrollView>

El código de C# equivalente es el siguiente:

ScrollView scrollView = new ScrollView();


scrollView.Scrolled += OnScrollViewScrolled;

En este ejemplo, el OnScrollViewScrolled controlador de eventos se ejecuta cuando se Scrolled activa el evento:

void OnScrollViewScrolled(object sender, ScrolledEventArgs e)


{
Console.WriteLine($"ScrollX: {e.ScrollX}, ScrollY: {e.ScrollY}");
}

En este ejemplo, el OnScrollViewScrolled controlador de eventos genera los valores del ScrolledEventArgs objeto
que acompaña al evento.
NOTE
El Scrolled evento se desencadena para los desplazamientos iniciados por el usuario y para los desplazamientos mediante
programación.

Desplazarse mediante programación


ScrollView define dos ScrollToAsync métodos que desplazan de forma asincrónica ScrollView . Una de las
sobrecargas se desplaza hasta una posición especificada en ScrollView , mientras que la otra desplaza un
elemento especificado en la vista. Ambas sobrecargas tienen un argumento adicional que se puede usar para
indicar si se debe animar el desplazamiento.

IMPORTANT
Los ScrollToAsync métodos no provocarán un desplazamiento cuando la ScrollView.Orientation propiedad esté
establecida en Neither .

Desplazar una posición en la vista


ScrollView Se puede desplazar una posición dentro de con el ScrollToAsync método que acepta los double x
y argumentos y. Dado un ScrollView objeto vertical denominado scrollView , en el ejemplo siguiente se
muestra cómo desplazarse hasta 150 unidades independientes del dispositivo desde la parte superior del
ScrollView :

await scrollView.ScrollToAsync(0, 150, true);

El tercer argumento de ScrollToAsync es el animated argumento, que determina si se muestra una animación de
desplazamiento cuando se desplaza mediante programación un ScrollView .
Desplazar un elemento a la vista
Un elemento dentro de un ScrollView se puede desplazar en la vista con el ScrollToAsync método que acepta los
Element ScrollToPosition argumentos y. Dado un ScrollView elemento con nombre vertical scrollView y un
Label denominado, en label el ejemplo siguiente se muestra cómo desplazar un elemento a la vista:

await scrollView.ScrollToAsync(label, ScrollToPosition.End, true);

El tercer argumento de ScrollToAsync es el animated argumento, que determina si se muestra una animación de
desplazamiento cuando se desplaza mediante programación un ScrollView .
Al desplazar un elemento en la vista, la posición exacta del elemento después del desplazamiento se puede
establecer con el segundo argumento, position , del ScrollToAsync método. Este argumento acepta un
ScrollToPosition miembro de enumeración:

MakeVisible indica que el elemento debe desplazarse hasta que esté visible en ScrollView .
Start indica que el elemento se debe desplazar al principio de ScrollView .
Center indica que el elemento se debe desplazar al centro de ScrollView .
End indica que el elemento se debe desplazar hasta el final de ScrollView .

Visibilidad de la barra de desplazamiento


ScrollView define HorizontalScrollBarVisibility VerticalScrollBarVisibility las propiedades y, que están
respaldadas por propiedades enlazables. Estas propiedades obtienen o establecen un ScrollBarVisibility valor
de enumeración que representa si está visible la barra de desplazamiento horizontal o vertical. La enumeración
ScrollBarVisibility define los miembros siguientes:

Default indica el comportamiento predeterminado de la barra de desplazamiento para la plataforma y es el


valor predeterminado de las HorizontalScrollBarVisibility VerticalScrollBarVisibility propiedades y.
Always indica que las barras de desplazamiento serán visibles, incluso cuando el contenido quepa en la vista.
Never indica que las barras de desplazamiento no estarán visibles, incluso si el contenido no cabe en la vista.

Vínculos relacionados
Demostraciones de ScrollView (ejemplo)
:::no-loc(Xamarin.Forms)::: StackLayout
Diseños enlazables en :::no-loc(Xamarin.Forms):::
Diseños enlazables en :::no-loc(Xamarin.Forms):::
18/12/2020 • 13 minutes to read • Edit Online

Descargar el ejemplo
Los diseños enlazables habilitan cualquier clase de diseño que derive de la Layout<T> clase para generar su
contenido enlazando con una colección de elementos, con la opción de establecer la apariencia de cada
elemento con DataTemplate . Los diseños enlazables se proporcionan mediante la BindableLayout clase, que
expone las siguientes propiedades adjuntas:
ItemsSource : especifica la colección de IEnumerable elementos que se mostrarán en el diseño.
ItemTemplate : especifica el DataTemplate que se va a aplicar a cada elemento de la colección de elementos
mostrados por el diseño.
ItemTemplateSelector : especifica el DataTemplateSelector que se usará para elegir un DataTemplate para un
elemento en tiempo de ejecución.

NOTE
La ItemTemplate propiedad tiene prioridad cuando ItemTemplate ItemTemplateSelector se establecen las
propiedades y.

Además, la BindableLayout clase expone las siguientes propiedades enlazables:


EmptyView : especifica la string vista o que se mostrará cuando la ItemsSource propiedad sea o null
cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. El valor
predeterminado es null .
EmptyViewTemplate : especifica el DataTemplate que se mostrará cuando la ItemsSource propiedad sea null
o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. El valor
predeterminado es null .

NOTE
La EmptyViewTemplate propiedad tiene prioridad cuando EmptyView EmptyViewTemplate se establecen las
propiedades y.

Todas estas propiedades se pueden adjuntar a AbsoluteLayout las FlexLayout clases,, Grid , RelativeLayout y
StackLayout , que se derivan de la Layout<T> clase.

La Layout<T> clase expone una Children colección, a la que se agregan los elementos secundarios de un
diseño. Cuando la BinableLayout.ItemsSource propiedad se establece en una colección de elementos y se
adjunta a una Layout<T> clase derivada de, cada elemento de la colección se agrega a la Layout<T>.Children
colección para que la muestre el diseño. La Layout<T> clase derivada de actualizará sus vistas secundarias
cuando cambie la colección subyacente. Para obtener más información sobre el :::no-loc(Xamarin.Forms)::: ciclo
de diseño, vea crear un diseño personalizado.
Los diseños enlazables solo deben usarse cuando la colección de elementos que se va a mostrar es pequeña, y
no se requiere desplazamiento y selección. Aunque el desplazamiento se puede proporcionar encapsulando un
diseño enlazable en una ScrollView , no se recomienda que los diseños enlazables carezcan de la virtualización
de la interfaz de usuario. Cuando se requiere desplazamiento, ListView se debe usar una vista desplazable que
incluya la virtualización de la interfaz de usuario, como o CollectionView . Si no se observa esta recomendación,
pueden producirse problemas de rendimiento.

IMPORTANT
Aunque es técnicamente posible asociar un diseño enlazable a cualquier clase de diseño que deriva de la Layout<T>
clase, no siempre es práctico hacerlo, especialmente en el caso de las AbsoluteLayout clases, Grid y RelativeLayout
. Por ejemplo, considere el escenario en el que desea mostrar una colección de datos en Grid con un diseño enlazable,
donde cada elemento de la colección es un objeto que contiene varias propiedades. Cada fila de Grid debe mostrar un
objeto de la colección, donde cada columna de Grid muestra una de las propiedades del objeto. Dado que
DataTemplate para el diseño enlazable solo puede contener un único objeto, es necesario que ese objeto sea una clase
de diseño que contenga varias vistas que muestren una de las propiedades del objeto en una Grid columna específica.
Aunque este escenario se puede realizar con diseños enlazables, da como resultado un elemento primario Grid que
contiene un elemento secundario Grid para cada elemento de la colección enlazada, lo que supone un uso muy ineficaz
y problemático del Grid diseño.

Rellenar un diseño enlazable con datos


Un diseño enlazable se rellena con datos estableciendo su ItemsSource propiedad en cualquier colección que
implemente IEnumerable y asócielo a una Layout<T> clase derivada de:

<Grid BindableLayout.ItemsSource="{Binding Items}" />

El código de C# equivalente es el siguiente:

IEnumerable<string> items = ...;


var grid = new Grid();
BindableLayout.SetItemsSource(grid, items);

Cuando BindableLayout.ItemsSource se establece la propiedad adjunta en un diseño, pero


BindableLayout.ItemTemplate no se establece la propiedad adjunta, se mostrarán todos los elementos de la
colección en un objeto IEnumerable Label creado por la BindableLayout clase.

Definir la apariencia del elemento


La apariencia de cada elemento en el diseño enlazable se puede definir estableciendo la
BindableLayout.ItemTemplate propiedad adjunta en un DataTemplate :

<StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"


Orientation="Horizontal"
...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<controls:CircleImage Source="{Binding}"
Aspect="AspectFill"
WidthRequest="44"
HeightRequest="44"
... />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>

El código de C# equivalente es el siguiente:


DataTemplate circleImageTemplate = ...;
var stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, circleImageTemplate);

En este ejemplo, se mostrarán todos los elementos de la TopFollowers colección mediante una CircleImage
vista definida en DataTemplate :

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de :::no-
loc(Xamarin.Forms):::.

Elección de la apariencia del elemento en tiempo de ejecución


La apariencia de cada elemento en el diseño enlazable se puede elegir en tiempo de ejecución, en función del
valor del elemento, estableciendo la BindableLayout.ItemTemplateSelector propiedad adjunta en un
DataTemplateSelector :

<FlexLayout BindableLayout.ItemsSource="{Binding User.FavoriteTech}"


BindableLayout.ItemTemplateSelector="{StaticResource TechItemTemplateSelector}"
... />

El código de C# equivalente es el siguiente:

DataTemplateSelector dataTemplateSelector = new TechItemTemplateSelector { ... };


var flexLayout = new FlexLayout();
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);

El DataTemplateSelector usado en la aplicación de ejemplo se muestra en el ejemplo siguiente:

public class TechItemTemplateSelector : DataTemplateSelector


{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate XamarinFormsTemplate { get; set; }

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)


{
return (string)item == ":::no-loc(Xamarin.Forms):::" ? XamarinFormsTemplate : DefaultTemplate;
}
}

La TechItemTemplateSelector clase define DefaultTemplate XamarinFormsTemplate DataTemplate las propiedades


y que se establecen en distintas plantillas de datos. El OnSelectTemplate método devuelve XamarinFormsTemplate
, que muestra un elemento en rojo oscuro con un corazón junto a él, cuando el elemento es igual a " :::no-
loc(Xamarin.Forms)::: ". Cuando el elemento no es igual a " :::no-loc(Xamarin.Forms)::: ", el OnSelectTemplate
método devuelve DefaultTemplate , que muestra un elemento con el color predeterminado de Label :
Para más información sobre los selectores de plantilla de datos, consulte creación de un :::no-
loc(Xamarin.Forms)::: DataTemplateSelector.

Mostrar una cadena cuando los datos no estén disponibles


La EmptyView propiedad se puede establecer en una cadena, que se mostrará Label cuando la ItemsSource
propiedad sea null , o cuando la colección especificada por la ItemsSource propiedad sea null o esté vacía.
En el código XAML siguiente se muestra un ejemplo de este escenario:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}"


BindableLayout.EmptyView="No achievements">
...
</StackLayout>

El resultado es que cuando la colección enlazada a datos es null , se muestra la cadena establecida como el
valor de la EmptyView propiedad:

Mostrar vistas cuando los datos no están disponibles


La EmptyView propiedad se puede establecer en una vista, que se mostrará cuando la ItemsSource propiedad
sea null o cuando la colección especificada por la ItemsSource propiedad sea o esté null vacía. Puede ser
una vista única o una vista que contiene varias vistas secundarias. En el ejemplo de XAML siguiente EmptyView
se muestra la propiedad establecida en una vista que contiene varias vistas secundarias:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">


<BindableLayout.EmptyView>
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontSize="{StaticResource smallTextSize}" />
<Label Text="Try harder and return later?"
FontAttributes="Italic"
FontSize="{StaticResource smallTextSize}" />
</StackLayout>
</BindableLayout.EmptyView>
...
</StackLayout>

El resultado es que cuando la colección enlazada a datos es null , StackLayout se muestran y sus vistas
secundarias.
Del mismo modo, EmptyViewTemplate se puede establecer en DataTemplate , que se mostrará cuando la
ItemsSource propiedad sea null , o cuando la colección especificada por la ItemsSource propiedad sea o esté
null vacía. DataTemplate Puede contener una vista única o una vista que contiene varias vistas secundarias.
Además, el BindingContext de se EmptyViewTemplate heredará del BindingContext de BindableLayout . En el
siguiente ejemplo de XAML EmptyViewTemplate se muestra la propiedad establecida en un DataTemplate que
contiene una sola vista:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">


<BindableLayout.EmptyViewTemplate>
<DataTemplate>
<Label Text="{Binding Source={x:Reference usernameLabel}, Path=Text, StringFormat='{0} has no
achievements.'}" />
</DataTemplate>
</BindableLayout.EmptyViewTemplate>
...
</StackLayout>

El resultado es que cuando la colección enlazada a datos es null , Label DataTemplate se muestra en el:

NOTE
La EmptyViewTemplate propiedad no se puede establecer mediante DataTemplateSelector .

Elegir un EmptyView en tiempo de ejecución


Las vistas que se mostrarán como EmptyView cuando los datos no estén disponibles, se pueden definir como
ContentView objetos en un ResourceDictionary . EmptyView A continuación, la propiedad se puede establecer en
un específico ContentView , en función de alguna lógica de negocios, en tiempo de ejecución. En el código XAML
siguiente se muestra un ejemplo de este escenario:
<ContentPage ...>
<ContentPage.Resources>
...
<ContentView x:Key="BasicEmptyView">
<StackLayout>
<Label Text="No achievements."
FontSize="14" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontSize="14" />
<Label Text="Try harder and return later?"
FontAttributes="Italic"
FontSize="14" />
</StackLayout>
</ContentView>
</ContentPage.Resources>

<StackLayout>
...
<Switch Toggled="OnEmptyViewSwitchToggled" />

<StackLayout x:Name="stackLayout"
BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
...
</StackLayout>
</StackLayout>
</ContentPage>

El XAML define dos ContentView objetos en el nivel de página ResourceDictionary , con el Switch objeto que
controla qué ContentView objeto se establecerá como el valor de la EmptyView propiedad. Cuando Switch se
alterna, el controlador de OnEmptyViewSwitchToggled eventos ejecuta el ToggleEmptyView método:

void ToggleEmptyView(bool isToggled)


{
object view = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
BindableLayout.SetEmptyView(stackLayout, view);
}

El método establece la EmptyView propiedad del stackLayout objeto en uno de los dos
ToggleEmptyView
ContentView objetos almacenados en ResourceDictionary , basándose en el valor de la Switch.IsToggled
propiedad. A continuación, cuando la colección enlazada a datos es null , ContentView se muestra el objeto
establecido como la EmptyView propiedad:

Vínculos relacionados
Demostración de diseño enlazable (ejemplo)
Creación de un diseño personalizado
:::no-loc(Xamarin.Forms)::: Plantillas de datos
Creación de un :::no-loc(Xamarin.Forms)::: DataTemplateSelector
Crear un diseño personalizado en :::no-
loc(Xamarin.Forms):::
18/12/2020 • 29 minutes to read • Edit Online

Descargar el ejemplo
:::no-loc(Xamarin.Forms)::: define cinco clases de diseño: StackLayout, AbsoluteLayout, RelativeLayout, Grid y
FlexLayout, y cada una de ellas organiza sus elementos secundarios de manera diferente. Sin embargo, a veces es
necesario organizar el contenido de la página mediante un diseño no proporcionado por :::no-
loc(Xamarin.Forms)::: . En este artículo se explica cómo escribir una clase de diseño personalizada y se muestra
una clase WrapLayout con distinción de orientación que organiza sus elementos secundarios horizontalmente por
la página y, a continuación, ajusta la presentación de los elementos secundarios posteriores a filas adicionales.
En :::no-loc(Xamarin.Forms)::: , todas las clases de diseño se derivan de la Layout<T> clase y restringen el tipo
genérico a View y sus tipos derivados. A su vez, la Layout<T> clase se deriva de la Layout clase, que proporciona
el mecanismo para colocar y ajustar el tamaño de los elementos secundarios.
Cada elemento visual es responsable de determinar su propio tamaño preferido, lo que se conoce como el
tamaño solicitado . Page Layout Layout<View> los tipos derivados, y son responsables de determinar la ubicación
y el tamaño de sus elementos secundarios, o secundarios, relativos a ellos mismos. Por lo tanto, el diseño implica
una relación de elementos primarios y secundarios, donde el elemento primario determina cuál debe ser el
tamaño de sus elementos secundarios, pero intentará dar cabida al tamaño solicitado del elemento secundario.
Se requiere un conocimiento exhaustivo de los :::no-loc(Xamarin.Forms)::: ciclos de diseño e invalidación para crear
un diseño personalizado. Ahora se analizarán estos ciclos.

Layout
El diseño comienza en la parte superior del árbol visual con una página y continúa a través de todas las ramas del
árbol visual para abarcar todos los elementos visuales de una página. Los elementos que son elementos primarios
de otros elementos son responsables de ajustar el tamaño y la posición de sus elementos secundarios en relación
con ellos mismos.
La VisualElement clase define un [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Measure (System.
Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) que mide un elemento para las operaciones
de diseño y un [ Layout ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Layout ( :::no-loc(Xamarin.Forms)::: .
Rectangle)) que especifica el área rectangular en la que se representará el elemento. Cuando se inicia una
aplicación y se muestra la primera página, un ciclo de diseño que consta de las primeras Measure llamadas y, a
continuación, llama a Layout , comienza en el Page objeto:
1. Durante el ciclo de diseño, cada elemento primario es responsable de llamar al Measure método en sus
elementos secundarios.
2. Una vez que se han medido los elementos secundarios, cada elemento primario es responsable de llamar al
Layout método en sus elementos secundarios.

Este ciclo garantiza que todos los elementos visuales de la página reciban llamadas a los Measure Layout
métodos y. El proceso se muestra en el diagrama siguiente:
NOTE
Tenga en cuenta que los ciclos de diseño también pueden producirse en un subconjunto del árbol visual si algo cambia para
afectar al diseño. Esto incluye los elementos que se agregan o quitan de una colección, como en StackLayout , un cambio
en la IsVisible propiedad de un elemento o un cambio en el tamaño de un elemento.

Cada :::no-loc(Xamarin.Forms)::: clase que tiene una Content propiedad o Children tiene un método
reemplazable LayoutChildren . Las clases de diseño personalizadas que derivan de Layout<View> deben invalidar
este método y asegurarse de que [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Measure (System.
Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) y [ Layout ] (XREF: :::no-loc(Xamarin.Forms)::: .
VisualElement. Layout ( :::no-loc(Xamarin.Forms)::: . Rectangle)), se llama a los métodos en todos los elementos
secundarios del elemento para proporcionar el diseño personalizado deseado.
Además, cada clase que deriva de Layout o Layout<View> debe invalidar el OnMeasure método, que es donde una
clase de diseño determina el tamaño que debe realizar las llamadas a [ Measure ] (XREF: :::no-loc(Xamarin.Forms):::
. VisualElement. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) de sus
elementos secundarios.

NOTE
Los elementos determinan su tamaño en función de las restricciones , que indican la cantidad de espacio disponible para un
elemento dentro del elemento primario del elemento. Restricciones que se pasan a [ Measure ] (XREF: :::no-
loc(Xamarin.Forms)::: . VisualElement. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags))
y OnMeasure los métodos pueden oscilar entre 0 y Double.PositiveInfinity . Un elemento está restringido , o
totalmente restringido , cuando recibe una llamada a su [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement.
Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) con argumentos no infinitos: el
elemento está restringido a un tamaño determinado. Un elemento no está restringido , o parcialmente restringido , cuando
recibe una llamada a su Measure método con al menos un argumento igual a Double.PositiveInfinity : se puede
considerar que la restricción infinita indica el ajuste automático de tamaño.

Invalidación
La invalidación es el proceso por el que un cambio en un elemento de una página desencadena un nuevo ciclo de
diseño. Los elementos se consideran no válidos cuando ya no tienen el tamaño o la posición correctos. Por
ejemplo, si FontSize se cambia la propiedad de un Button , Button se dice que no es válido porque ya no tendrá
el tamaño correcto. Al cambiar el tamaño Button , el puede tener un efecto de rizo de los cambios en el diseño a
través del resto de una página.
Los elementos se invalidan mediante la invocación del InvalidateMeasure método, por lo general cuando una
propiedad del elemento cambia, lo que podría dar lugar a un nuevo tamaño del elemento. Este método
desencadena el MeasureInvalidated evento, que el elemento primario del elemento controla para desencadenar
un nuevo ciclo de diseño.
La Layout clase establece un controlador para el MeasureInvalidated evento en cada elemento secundario
agregado a su Content propiedad o Children colección y Desasocia el controlador cuando se quita el elemento
secundario. Por lo tanto, todos los elementos del árbol visual que tienen elementos secundarios se avisan cuando
uno de sus elementos secundarios cambia de tamaño. En el diagrama siguiente se muestra cómo un cambio en el
tamaño de un elemento en el árbol visual puede producir cambios que se rellenen en el árbol:

Sin embargo, la Layout clase intenta restringir el impacto de un cambio en el tamaño de un elemento secundario
en el diseño de una página. Si el diseño tiene restricciones de tamaño, un cambio de tamaño secundario no afecta
a nada más alto que el diseño primario en el árbol visual. Sin embargo, normalmente un cambio en el tamaño de
un diseño afecta al modo en que el diseño organiza sus elementos secundarios. Por lo tanto, cualquier cambio en
el tamaño de un diseño iniciará un ciclo de diseño para el diseño y el diseño recibirá las llamadas a sus OnMeasure
LayoutChildren métodos y.

La Layoutclase también define un InvalidateLayout método que tiene un propósito similar al


InvalidateMeasure método. El InvalidateLayout método se debe invocar cada vez que se realiza un cambio que
afecta al modo en que el diseño coloca y ajusta el tamaño de sus elementos secundarios. Por ejemplo, la Layout
clase invoca el InvalidateLayout método cada vez que se agrega o se quita un elemento secundario de un diseño.
InvalidateLayout Se puede invalidar para implementar una memoria caché con el fin de minimizar las
invocaciones repetitivas de [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Measure (System.
Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) de los elementos secundarios del diseño. Al
invalidar el InvalidateLayout método, se proporcionará una notificación de Cuándo se agregan o quitan
elementos secundarios en el diseño. Del mismo modo, el OnChildMeasureInvalidated método se puede invalidar
para proporcionar una notificación cuando cambia el tamaño de uno de los elementos secundarios del diseño. En
el caso de las invalidaciones de método, un diseño personalizado debe responder borrando la memoria caché.
Para obtener más información, consulte calcular y almacenar en caché datos de diseño.

Crear un diseño personalizado


El proceso para crear un diseño personalizado es el siguiente:
1. Cree una clase que se derive de la clase Layout<View> . Para obtener más información, vea crear un
WrapLayout.
2. [ opcional ] Agregue propiedades, respaldadas por propiedades enlazables, para los parámetros que se
deben establecer en la clase de diseño. Para obtener más información, vea Agregar propiedades
respaldadas por propiedades enlazables.
3. Invalide el OnMeasure método para invocar [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement.
Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) en todos los
elementos secundarios del diseño y devuelven un tamaño solicitado para el diseño. Para obtener más
información, vea invalidar el método de la acción.
4. Invalide el LayoutChildren método para invocar [ Layout ] (XREF: :::no-loc(Xamarin.Forms)::: .
VisualElement. Layout ( :::no-loc(Xamarin.Forms)::: . Rectangle)) en todos los elementos secundarios del
diseño. Error al invocar [ Layout ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Layout ( :::no-
loc(Xamarin.Forms)::: . Rectangle)) en cada elemento secundario de un diseño, el elemento secundario no
recibirá nunca un tamaño o una posición correctos y, por lo tanto, el elemento secundario no estará visible
en la página. Para obtener más información, vea invalidar el método LayoutChildren.

NOTE
Al enumerar los elementos secundarios en OnMeasure y LayoutChildren invalida, omita cualquier elemento
secundario cuya IsVisible propiedad esté establecida en false . Esto garantizará que el diseño personalizado
no deja espacio para los elementos secundarios invisibles.

5. [ opcional ] Invalide el InvalidateLayout método para recibir una notificación cuando se agreguen o quiten
elementos secundarios del diseño. Para obtener más información, vea invalidar el método InvalidateLayout.
6. [ opcional ] Invalide el OnChildMeasureInvalidated método para recibir una notificación cuando uno de los
elementos secundarios del diseño cambie de tamaño. Para obtener más información, vea invalidar el
método OnChildMeasureInvalidated.

NOTE
Tenga en cuenta que la OnMeasure invalidación no se invocará si el tamaño del diseño se rige por su elemento primario, en
lugar de sus elementos secundarios. Sin embargo, se invocará la invalidación si una o ambas restricciones son infinitas, o si la
clase de diseño tiene valores no predeterminados HorizontalOptions o de VerticalOptions propiedad. Por esta razón,
la LayoutChildren invalidación no puede depender de los tamaños secundarios obtenidos durante la OnMeasure llamada
al método. En su lugar, LayoutChildren debe invocar [ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement.
Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . MeasureFlags)) en los elementos secundarios del
diseño, antes de invocar [ Layout ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Layout ( :::no-loc(Xamarin.Forms)::: .
Rectangle)). Como alternativa, el tamaño de los elementos secundarios obtenidos en la OnMeasure invalidación se puede
almacenar en caché para evitar las Measure invocaciones posteriores en la LayoutChildren invalidación, pero la clase de
diseño deberá saber cuándo es necesario obtener los tamaños de nuevo. Para obtener más información, consulte calcular y
almacenar en caché datos de diseño.

A continuación, la clase de diseño se puede consumir agregándola a un Page y agregando elementos secundarios
al diseño. Para obtener más información, vea consumir The WrapLayout.
Creación de un WrapLayout
La aplicación de ejemplo muestra una clase con distinción de orientación WrapLayout que organiza los elementos
secundarios horizontalmente por la página y, a continuación, ajusta la presentación de los elementos secundarios
posteriores a filas adicionales.
La WrapLayout clase asigna la misma cantidad de espacio para cada elemento secundario, conocido como el
tamaño de celda , en función del tamaño máximo de los elementos secundarios. Los elementos secundarios
menores que el tamaño de la celda se pueden colocar dentro de la celda en función de sus HorizontalOptions
VerticalOptions valores de propiedad y.

La WrapLayout definición de clase se muestra en el ejemplo de código siguiente:

public class WrapLayout : Layout<View>


{
Dictionary<Size, LayoutData> layoutDataCache = new Dictionary<Size, LayoutData>();
...
}

Calcular y almacenar en caché datos de diseño


La LayoutData estructura almacena los datos sobre una colección de elementos secundarios en una serie de
propiedades:
VisibleChildCount : el número de elementos secundarios que están visibles en el diseño.
CellSize : el tamaño máximo de todos los elementos secundarios, ajustado al tamaño del diseño.
Rows : el número de filas.
Columns : el número de columnas.

El layoutDataCachecampo se usa para almacenar varios LayoutData valores. Cuando se inicia la aplicación, se
LayoutData almacenan en caché dos objetos en el layoutDataCache Diccionario para la orientación actual: uno
para los argumentos de restricción para la OnMeasure invalidación y otro para los width height argumentos y
para la LayoutChildren invalidación. Al girar el dispositivo en orientación horizontal, se OnMeasure invocará de
nuevo la invalidación y la LayoutChildren invalidación, lo que provocará que se LayoutData almacenen en el
Diccionario otros dos objetos. Sin embargo, al devolver el dispositivo a la orientación vertical, no es necesario
realizar más cálculos porque layoutDataCache ya tiene los datos necesarios.
En el ejemplo de código siguiente se muestra el GetLayoutData método, que calcula las propiedades de
LayoutData estructuradas en función de un tamaño determinado:

LayoutData GetLayoutData(double width, double height)


{
Size size = new Size(width, height);

// Check if cached information is available.


if (layoutDataCache.ContainsKey(size))
{
return layoutDataCache[size];
}

int visibleChildCount = 0;
Size maxChildSize = new Size();
int rows = 0;
int columns = 0;
LayoutData layoutData = new LayoutData();

// Enumerate through all the children.


foreach (View child in Children)
{
// Skip invisible children.
if (!child.IsVisible)
continue;

// Count the visible children.


visibleChildCount++;

// Get the child's requested size.


SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);
// Accumulate the maximum child size.
maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
}

if (visibleChildCount != 0)
{
// Calculate the number of rows and columns.
if (Double.IsPositiveInfinity(width))
{
columns = visibleChildCount;
rows = 1;
}
else
{
columns = (int)((width + ColumnSpacing) / (maxChildSize.Width + ColumnSpacing));
columns = Math.Max(1, columns);
rows = (visibleChildCount + columns - 1) / columns;
}

// Now maximize the cell size based on the layout size.


Size cellSize = new Size();

if (Double.IsPositiveInfinity(width))
cellSize.Width = maxChildSize.Width;
else
cellSize.Width = (width - ColumnSpacing * (columns - 1)) / columns;

if (Double.IsPositiveInfinity(height))
cellSize.Height = maxChildSize.Height;
else
cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;

layoutData = new LayoutData(visibleChildCount, cellSize, rows, columns);


}

layoutDataCache.Add(size, layoutData);
return layoutData;
}

El GetLayoutData método realiza las operaciones siguientes:


Determina si un valor calculado LayoutData ya está en la memoria caché y lo devuelve si está disponible.
De lo contrario, enumera todos los elementos secundarios, invocando el Measure método en cada elemento
secundario con un ancho y un alto infinitos, y determina el tamaño máximo de los elementos secundarios.
Siempre que haya al menos un elemento secundario visible, calcula el número de filas y columnas necesarias y,
a continuación, calcula el tamaño de la celda para los elementos secundarios en función de las dimensiones de
WrapLayout . Tenga en cuenta que el tamaño de la celda suele ser ligeramente más grande que el tamaño
máximo de los elementos secundarios, pero también puede ser menor si el elemento secundario es lo
suficientemente amplio como WrapLayout para el elemento secundario más amplio o lo suficientemente alto
para el elemento secundario más alto.
Almacena el nuevo LayoutData valor en la memoria caché.
Agregar propiedades respaldadas por propiedades enlazables
La WrapLayout clase define ColumnSpacing RowSpacing las propiedades y, cuyos valores se usan para separar las
filas y las columnas del diseño, y que están respaldadas por propiedades enlazables. Las propiedades enlazables
se muestran en el ejemplo de código siguiente:
public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
"ColumnSpacing",
typeof(double),
typeof(WrapLayout),
5.0,
propertyChanged: (bindable, oldvalue, newvalue) =>
{
((WrapLayout)bindable).InvalidateLayout();
});

public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(


"RowSpacing",
typeof(double),
typeof(WrapLayout),
5.0,
propertyChanged: (bindable, oldvalue, newvalue) =>
{
((WrapLayout)bindable).InvalidateLayout();
});

El controlador modificado por la propiedad de cada propiedad enlazable invoca la InvalidateLayout invalidación
del método para desencadenar un nuevo paso de diseño en WrapLayout . Para obtener más información, vea
invalidar el método InvalidateLayout e invalidar el método OnChildMeasureInvalidated.
Invalidar el método de la acción
La OnMeasure invalidación se muestra en el ejemplo de código siguiente:

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)


{
LayoutData layoutData = GetLayoutData(widthConstraint, heightConstraint);
if (layoutData.VisibleChildCount == 0)
{
return new SizeRequest();
}

Size totalSize = new Size(layoutData.CellSize.Width * layoutData.Columns + ColumnSpacing *


(layoutData.Columns - 1),
layoutData.CellSize.Height * layoutData.Rows + RowSpacing * (layoutData.Rows - 1));
return new SizeRequest(totalSize);
}

La invalidación invoca el GetLayoutData método y construye un SizeRequest objeto a partir de los datos
devueltos, a la vez que también tiene en cuenta los valores de las RowSpacing ColumnSpacing propiedades y. Para
obtener más información sobre el GetLayoutData método, consulte calcular y almacenar en caché datos de diseño.

IMPORTANT
[ Measure ] (XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Measure (System. Double, System. Double, :::no-
loc(Xamarin.Forms)::: . MeasureFlags)) y OnMeasure los métodos no deben solicitar nunca una dimensión infinita
devolviendo un SizeRequest valor con una propiedad establecida en Double.PositiveInfinity . Sin embargo, al menos
uno de los argumentos de restricción en OnMeasure puede ser Double.PositiveInfinity .

Invalidar el método LayoutChildren


La LayoutChildren invalidación se muestra en el ejemplo de código siguiente:
protected override void LayoutChildren(double x, double y, double width, double height)
{
LayoutData layoutData = GetLayoutData(width, height);

if (layoutData.VisibleChildCount == 0)
{
return;
}

double xChild = x;
double yChild = y;
int row = 0;
int column = 0;

foreach (View child in Children)


{
if (!child.IsVisible)
{
continue;
}

LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), layoutData.CellSize));


if (++column == layoutData.Columns)
{
column = 0;
row++;
xChild = x;
yChild += RowSpacing + layoutData.CellSize.Height;
}
else
{
xChild += ColumnSpacing + layoutData.CellSize.Width;
}
}
}

La invalidación comienza con una llamada al GetLayoutData método y, a continuación, enumera todos los
elementos secundarios para ajustar su tamaño y colocarlos dentro de la celda de cada elemento secundario. Esto
se logra invocando [ LayoutChildIntoBoundingRegion ] (XREF: :::no-loc(Xamarin.Forms)::: . Layout.
LayoutChildIntoBoundingRegion ( :::no-loc(Xamarin.Forms)::: . VisualElement, :::no-loc(Xamarin.Forms)::: .
Rectangle)), que se usa para colocar un elemento secundario dentro de un rectángulo basándose en sus
HorizontalOptions VerticalOptions valores de propiedad y. Esto es equivalente a hacer una llamada a [ Layout ]
(XREF: :::no-loc(Xamarin.Forms)::: . VisualElement. Layout ( :::no-loc(Xamarin.Forms)::: . Rectangle)).

NOTE
Tenga en cuenta que el rectángulo que se pasa al LayoutChildIntoBoundingRegion método incluye todo el área en la que
puede residir el elemento secundario.

Para obtener más información sobre el GetLayoutData método, consulte calcular y almacenar en caché datos de
diseño.
Invalidar el método InvalidateLayout
La InvalidateLayout invalidación se invoca cuando se agregan o quitan elementos secundarios en el diseño, o
cuando una de las WrapLayout propiedades cambia de valor, como se muestra en el ejemplo de código siguiente:
protected override void InvalidateLayout()
{
base.InvalidateLayout();
layoutInfoCache.Clear();
}

La invalidación invalida el diseño y descarta toda la información de diseño almacenada en caché.

NOTE
Para detener la Layout clase que invoca al InvalidateLayout método cada vez que se agrega o se quita un elemento
secundario de un diseño, invalide [ ShouldInvalidateOnChildAdded ] (XREF: :::no-loc(Xamarin.Forms)::: . Layout.
ShouldInvalidateOnChildAdded ( :::no-loc(Xamarin.Forms)::: . View)) y [ ShouldInvalidateOnChildRemoved ] (XREF: :::no-
loc(Xamarin.Forms)::: . Layout. ShouldInvalidateOnChildRemoved ( :::no-loc(Xamarin.Forms)::: . View)) y devuelven false . A
continuación, la clase de diseño puede implementar un proceso personalizado cuando se agregan o quitan elementos
secundarios.

Invalidar el método OnChildMeasureInvalidated


La OnChildMeasureInvalidated invalidación se invoca cuando uno de los elementos secundarios del diseño cambia
de tamaño y se muestra en el ejemplo de código siguiente:

protected override void OnChildMeasureInvalidated()


{
base.OnChildMeasureInvalidated();
layoutInfoCache.Clear();
}

La invalidación invalida el diseño secundario y descarta toda la información de diseño almacenada en caché.
Consumir el WrapLayout
La WrapLayout clase se puede consumir colocándolo en un Page tipo derivado, tal y como se muestra en el
siguiente ejemplo de código XAML:

<ContentPage ... xmlns:local="clr-namespace:ImageWrapLayout">


<ScrollView Margin="0,20,0,20">
<local:WrapLayout x:Name="wrapLayout" />
</ScrollView>
</ContentPage>

El código de C# equivalente se muestra a continuación:


public class ImageWrapLayoutPageCS : ContentPage
{
WrapLayout wrapLayout;

public ImageWrapLayoutPageCS()
{
wrapLayout = new WrapLayout();

Content = new ScrollView


{
Margin = new Thickness(0, 20, 0, 20),
Content = wrapLayout
};
}
...
}

Después, los elementos secundarios se pueden agregar al WrapLayout según sea necesario. En el ejemplo de
código siguiente Image se muestran los elementos que se agregan a WrapLayout :

protected override async void OnAppearing()


{
base.OnAppearing();

var images = await GetImageListAsync();


if (images != null)
{
foreach (var photo in images.Photos)
{
var image = new Image
{
Source = ImageSource.FromUri(new Uri(photo))
};
wrapLayout.Children.Add(image);
}
}
}

async Task<ImageList> GetImageListAsync()


{
try
{
string requestUri = "https://raw.githubusercontent.com/xamarin/docs-
archive/master/Images/stock/small/stock.json";
string result = await _client.GetStringAsync(requestUri);
return JsonConvert.DeserializeObject<ImageList>(result);
}
catch (Exception ex)
{
Debug.WriteLine($"\tERROR: {ex.Message}");
}

return null;
}

Cuando aparece la página que contiene el WrapLayout , la aplicación de ejemplo obtiene acceso de forma
asincrónica a un archivo JSON remoto que contiene una lista de fotografías, crea un Image elemento para cada
foto y lo agrega a WrapLayout . El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:
Las siguientes capturas de pantallas muestran el WrapLayout después de que se haya girado a la orientación
horizontal:
El número de columnas de cada fila depende del tamaño de la fotografía, el ancho de la pantalla y el número de
píxeles por unidad independiente del dispositivo. Los Image elementos cargan las fotos de forma asincrónica y,
por lo tanto, la WrapLayout clase recibirá llamadas frecuentes a su LayoutChildren método, ya que cada Image
elemento recibe un nuevo tamaño basado en la foto cargada.

Vínculos relacionados
WrapLayout (ejemplo)
Diseños personalizados
Crear diseños personalizados en :::no-loc(Xamarin.Forms)::: (vídeo)
Layout<T>
Diseño
VisualElement
Orientación del dispositivo
18/12/2020 • 17 minutes to read • Edit Online

Descargar el ejemplo
Es importante tener en cuenta cómo se utilizará la aplicación y cómo se puede incorporar la orientación horizontal
para mejorar la experiencia del usuario. Los diseños individuales se pueden diseñar para acomodar varias
orientaciones y el mejor uso del espacio disponible. En el nivel de aplicación, el giro se puede deshabilitar o
habilitar.

Controlar la orientación
Al usar :::no-loc(Xamarin.Forms)::: , el método admitido para controlar la orientación del dispositivo es usar la
configuración para cada proyecto individual.
iOS
En iOS, la orientación del dispositivo se configura para las aplicaciones que usan el archivo info. plist . Use las
opciones del IDE de la parte superior de este documento para seleccionar las instrucciones que le gustaría ver:
Visual Studio
Visual Studio para Mac
En Visual Studio, abra el proyecto de iOS y Abra info. plist . El archivo se abrirá en un panel de configuración,
comenzando por la pestaña información de implementación de iPhone:

Android
Para controlar la orientación en Android, Abra MainActivity.CS y establezca la orientación mediante el atributo
que decora la MainActivity clase:
namespace MyRotatingApp.Droid
{
[Activity (Label = "MyRotatingApp.Droid", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher
= true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation =
ScreenOrientation.Landscape)] //This is what controls orientation
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate (Bundle bundle)
...

Xamarin. Android admite varias opciones para especificar la orientación:


Horizontal – obliga a que la orientación de la aplicación sea horizontal, independientemente de los datos del
sensor.
Ver tical – obliga a que la orientación de la aplicación sea vertical, independientemente de los datos del sensor.
Usuario – de hace que la aplicación se presente con la orientación preferida del usuario.
Detrás – de hace que la orientación de la aplicación sea la misma que la orientación de la actividad subyacente.
Sensor – de hace que el sensor determine la orientación de la aplicación, incluso si el usuario ha deshabilitado
la rotación automática.
SensorLandscape – hace que la aplicación utilice la orientación horizontal mientras usa datos de sensor para
cambiar la dirección en la que está orientada la pantalla (de modo que la pantalla no se vea como boca abajo).
SensorPor trait – hace que la aplicación use la orientación vertical al usar los datos del sensor para cambiar la
dirección de cara de la pantalla (de modo que la pantalla no se vea como boca abajo).
ReverseLandscape – hace que la aplicación utilice la orientación horizontal, orientada a la dirección opuesta de
normal, de modo que aparezca "al revés".
ReversePor trait – hace que la aplicación use la orientación vertical, orientada a la dirección opuesta de la
habitual, para que aparezca "al revés".
FullSensor – hace que la aplicación se base en datos del sensor para seleccionar la orientación correcta (fuera
del posible 4).
FullUser – hace que la aplicación use las preferencias de orientación del usuario. Si está habilitada la rotación
automática, se pueden usar las cuatro orientaciones.
UserLandscape – [ No compatible ] hace que la aplicación utilice la orientación horizontal, a menos que el
usuario tenga habilitada la rotación automática, en cuyo caso usará el sensor para determinar la orientación.
Esta opción interrumpirá la compilación.
UserPor trait – [ No compatible ] hace que la aplicación utilice la orientación vertical, a menos que el usuario
tenga habilitada la rotación automática, en cuyo caso usará el sensor para determinar la orientación. Esta opción
interrumpirá la compilación.
Bloqueado – [ No compatible ] hace que la aplicación use la orientación de la pantalla, sea cual sea el inicio, sin
responder a los cambios en la orientación física del dispositivo. Esta opción interrumpirá la compilación.
Tenga en cuenta que las API nativas de Android proporcionan un gran control sobre cómo se administra la
orientación, incluidas las opciones que contradicen explícitamente las preferencias expresadas del usuario.
Plataforma universal de Windows
En el Plataforma universal de Windows (UWP), las orientaciones admitidas se establecen en el archivo Package.
appxmanifest . Al abrir el manifiesto, se mostrará un panel de configuración en el que se pueden seleccionar las
orientaciones admitidas.

Reaccionar a los cambios de orientación


:::no-loc(Xamarin.Forms)::: no ofrece ningún evento nativo para notificar a la aplicación los cambios de orientación
en el código compartido. Sin embargo, :::no-loc(Xamarin.Essentials)::: contiene una DeviceDisplay clase [] que
proporciona notificaciones de cambios de orientación.
Para detectar las orientaciones sin :::no-loc(Xamarin.Essentials)::: , supervise el SizeChanged evento de Page , que se
activa cuando cambia el ancho o el alto de los Page cambios. Cuando el ancho de Page es mayor que el alto, el
dispositivo está en modo horizontal. Para obtener más información, vea Mostrar una imagen en función de la
orientación de la pantalla.
Como alternativa, es posible invalidar el OnSizeAllocated método en un Page , insertando allí cualquier lógica de
cambio de diseño. OnSizeAllocated Se llama al método cada vez que Page se asigna un nuevo tamaño a un, lo que
sucede siempre que se gira el dispositivo. Tenga en cuenta que la implementación base de OnSizeAllocated realiza
funciones de diseño importantes, por lo que es importante llamar a la implementación base en la invalidación:

protected override void OnSizeAllocated(double width, double height)


{
base.OnSizeAllocated(width, height); //must be called
}

Si no se lleva a cabo este paso, se producirá una página que no funciona.


Tenga en cuenta que OnSizeAllocated se puede llamar al método varias veces cuando se gira un dispositivo.
Cambiar el diseño cada vez es una pérdida de recursos y puede dar lugar a parpadeos. Considere la posibilidad de
usar una variable de instancia dentro de la página para hacer un seguimiento de si la orientación está en horizontal
o en vertical, y volver a dibujar solo cuando se produzca un cambio:

private double width = 0;


private double height = 0;

protected override void OnSizeAllocated(double width, double height)


{
base.OnSizeAllocated(width, height); //must be called
if (this.width != width || this.height != height)
{
this.width = width;
this.height = height;
//reconfigure layout
}
}

Una vez que se ha detectado un cambio en la orientación del dispositivo, puede que desee agregar o quitar vistas
adicionales de la interfaz de usuario o de la interfaz de usuario para reaccionar ante el cambio en el espacio
disponible. Por ejemplo, considere la calculadora integrada en cada plataforma en vertical:
y horizontal:
Tenga en cuenta que las aplicaciones aprovechan el espacio disponible agregando más funcionalidad en horizontal.

Diseño dinámico
Es posible diseñar interfaces mediante diseños integrados para que se transfieran correctamente cuando se gire el
dispositivo. Al diseñar interfaces que seguirán siendo atractivas al responder a los cambios de orientación, tenga en
cuenta las siguientes reglas generales:
Preste atención a las proporciones – los cambios de orientación pueden ocasionar problemas cuando se
realizan determinadas suposiciones con respecto a las proporciones. Por ejemplo, una vista que tendría una
gran cantidad de espacio en 1/3 del espacio vertical de una pantalla en vertical puede no ajustarse a 1/3 del
espacio vertical en horizontal.
Tenga cuidado con los valores – absolutos los valores absolutos (píxel) que tienen sentido en vertical
pueden no tener sentido en el panorama. Cuando sean necesarios valores absolutos, utilice diseños anidados
para aislar su impacto. Por ejemplo, sería razonable usar valores absolutos en un TableView ItemTemplate
cuando la plantilla de elemento tenga un alto uniforme garantizado.
Las reglas anteriores también se aplican al implementar interfaces para varios tamaños de pantalla y generalmente
se consideran prácticas recomendadas. En el resto de esta guía se explican ejemplos específicos de diseños que
responden con cada uno de los diseños principales de :::no-loc(Xamarin.Forms)::: .

NOTE
Para mayor claridad, en las secciones siguientes se muestra cómo implementar diseños que respondan con solo un tipo de
Layout cada vez. En la práctica, a menudo es más sencillo mezclar Layout s para lograr un diseño deseado con el más
sencillo o intuitivo Layout para cada componente.

StackLayout
Considere la siguiente aplicación, que se muestra en vertical:
y horizontal:
Esto se logra con el código XAML siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.StackLayoutPageXaml"
Title="Stack Photo Editor - XAML">
<ContentPage.Content>
<StackLayout Spacing="10" Padding="5" Orientation="Vertical"
x:Name="outerStack"> <!-- can change orientation to make responsive -->
<ScrollView>
<StackLayout Spacing="5" HorizontalOptions="FillAndExpand"
WidthRequest="1000">
<StackLayout Orientation="Horizontal">
<Label Text="Name: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer.jpg"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Date: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="07/05/2015"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Tags:" WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer, tiger"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button Text="Save" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
<Image Source="deer.jpg" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Algunos de C# se usan para cambiar la orientación de en outerStack función de la orientación del dispositivo:

protected override void OnSizeAllocated (double width, double height){


base.OnSizeAllocated (width, height);
if (width != this.width || height != this.height) {
this.width = width;
this.height = height;
if (width > height) {
outerStack.Orientation = StackOrientation.Horizontal;
} else {
outerStack.Orientation = StackOrientation.Vertical;
}
}
}

Tenga en cuenta lo siguiente:


outerStack se ajusta para presentar la imagen y los controles como una pila horizontal o vertical, dependiendo
de la orientación, para aprovechar mejor el espacio disponible.
AbsoluteLayout
Considere la siguiente aplicación, que se muestra en vertical:
y horizontal:
Esto se logra con el código XAML siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.AbsoluteLayoutPageXaml"
Title="AbsoluteLayout - XAML" BackgroundImageSource="deer.jpg">
<ContentPage.Content>
<AbsoluteLayout>
<ScrollView AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="PositionProportional,SizeProportional">
<AbsoluteLayout>
<Image Source="deer.jpg"
AbsoluteLayout.LayoutBounds=".5,0,300,300"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="#CC1A7019" AbsoluteLayout.LayoutBounds=".5
300,.7,50" AbsoluteLayout.LayoutFlags="XProportional
WidthProportional" />
<Label Text="deer.jpg" AbsoluteLayout.LayoutBounds = ".5
310,1, 50" AbsoluteLayout.LayoutFlags="XProportional
WidthProportional" HorizontalTextAlignment="Center" TextColor="White" />
</AbsoluteLayout>
</ScrollView>
<Button Text="Previous" AbsoluteLayout.LayoutBounds="0,1,.5,60"
AbsoluteLayout.LayoutFlags="PositionProportional
WidthProportional"
BackgroundColor="White" TextColor="Green" BorderRadius="0" />
<Button Text="Next" AbsoluteLayout.LayoutBounds="1,1,.5,60"
AbsoluteLayout.LayoutFlags="PositionProportional
WidthProportional" BackgroundColor="White"
TextColor="Green" BorderRadius="0" />
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>

Tenga en cuenta lo siguiente:


Debido a la forma en que se ha diseñado la página, no es necesario que el código de procedimiento presente la
capacidad de respuesta.
ScrollView Se usa para permitir que la etiqueta esté visible incluso cuando el alto de la pantalla sea menor que
la suma de la altura fija de los botones y la imagen.
RelativeLayout
Considere la siguiente aplicación, que se muestra en vertical:
y horizontal:
Esto se logra con el código XAML siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.RelativeLayoutPageXaml"
Title="RelativeLayout - XAML"
BackgroundImageSource="deer.jpg">
<ContentPage.Content>
<RelativeLayout x:Name="outerLayout">
<BoxView BackgroundColor="#AA1A7019"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=0}" />
<ScrollView
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=0}">
<RelativeLayout>
<Image Source="deer.jpg" x:Name="imageDeer"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.8}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.1}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=10}" />
<Label Text="deer.jpg" HorizontalTextAlignment="Center"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=75}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToView,ElementName=imageDeer,Property=Height,Factor=1,Constant=20}" />
</RelativeLayout>

</ScrollView>

<Button Text="Previous" BackgroundColor="White" TextColor="Green" BorderRadius="0"


RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=60}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
/>
<Button Text="Next" BackgroundColor="White" TextColor="Green" BorderRadius="0"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=60}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
Type=RelativeToParent,Property=Width,Factor=.5}"
/>
</RelativeLayout>
</ContentPage.Content>
</ContentPage>

Tenga en cuenta lo siguiente:


Debido a la forma en que se ha diseñado la página, no es necesario que el código de procedimiento presente la
capacidad de respuesta.
ScrollView Se usa para permitir que la etiqueta esté visible incluso cuando el alto de la pantalla sea menor que
la suma de la altura fija de los botones y la imagen.
Cuadrícula
Considere la siguiente aplicación, que se muestra en vertical:

y horizontal:
Esto se logra con el código XAML siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.GridPageXaml"
Title="Grid - XAML">
<ContentPage.Content>
<Grid x:Name="outerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Grid x:Name="innerGrid" Grid.Row="0" Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="deer.jpg" Grid.Row="0" Grid.Column="0" HeightRequest="300" WidthRequest="300" />
<Grid x:Name="controlsGrid" Grid.Row="0" Grid.Column="1" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Name:" Grid.Row="0" Grid.Column="0" />
<Label Text="Date:" Grid.Row="1" Grid.Column="0" />
<Label Text="Tags:" Grid.Row="2" Grid.Column="0" />
<Entry Grid.Row="0" Grid.Column="1" />
<Entry Grid.Row="1" Grid.Column="1" />
<Entry Grid.Row="2" Grid.Column="1" />
</Grid>
</Grid>
<Grid x:Name="buttonsGrid" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Previous" Grid.Column="0" />
<Button Text="Save" Grid.Column="1" />
<Button Text="Next" Grid.Column="2" />
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>

Junto con el siguiente código de procedimientos para controlar los cambios de giro:
private double width;
private double height;

protected override void OnSizeAllocated (double width, double height){


base.OnSizeAllocated (width, height);
if (width != this.width || height != this.height) {
this.width = width;
this.height = height;
if (width > height) {
innerGrid.RowDefinitions.Clear();
innerGrid.ColumnDefinitions.Clear ();
innerGrid.RowDefinitions.Add (new RowDefinition{ Height = new GridLength (1, GridUnitType.Star) });
innerGrid.ColumnDefinitions.Add (new ColumnDefinition { Width = new GridLength (1,
GridUnitType.Star) });
innerGrid.ColumnDefinitions.Add (new ColumnDefinition { Width = new GridLength (1,
GridUnitType.Star) });
innerGrid.Children.Remove (controlsGrid);
innerGrid.Children.Add (controlsGrid, 1, 0);
} else {
innerGrid.RowDefinitions.Clear();
innerGrid.ColumnDefinitions.Clear ();
innerGrid.ColumnDefinitions.Add (new ColumnDefinition{ Width = new GridLength (1,
GridUnitType.Star) });
innerGrid.RowDefinitions.Add (new RowDefinition { Height = new GridLength (1, GridUnitType.Auto)
});
innerGrid.RowDefinitions.Add (new RowDefinition { Height = new GridLength (1, GridUnitType.Star)
});
innerGrid.Children.Remove (controlsGrid);
innerGrid.Children.Add (controlsGrid, 0, 1);
}
}
}

Tenga en cuenta lo siguiente:


Debido a la forma en que se ha diseñado la página, hay un método para cambiar la posición de la cuadrícula de
los controles.

Vínculos relacionados
Diseño (ejemplo)
Ejemplo de BusinessTumble (ejemplo)
Diseño con capacidad de respuesta (ejemplo)
Mostrar una imagen en función de la orientación de la pantalla
Opciones de diseño en :::no-loc(Xamarin.Forms):::
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
Cada :::no-loc(Xamarin.Forms)::: vista tiene las propiedades HorizontalOptions y VerticalOptions, de tipo
LayoutOptions. En este artículo se explica el efecto que tiene cada valor de LayoutOptions en la alineación y la
expansión de una vista.

Información general
La LayoutOptions estructura encapsula dos preferencias de diseño:
Alignment : la alineación preferida de la vista, que determina su posición y tamaño dentro de su diseño
primario.
Expansión : solo lo usa un StackLayout y indica si la vista debe utilizar espacio adicional, si está disponible.
Estas preferencias de diseño se pueden aplicar a un View , en relación con su elemento primario, estableciendo la
HorizontalOptions VerticalOptions propiedad o de View en uno de los campos públicos de la LayoutOptions
estructura. Los campos públicos son los siguientes:
Start
Center
End
Fill
StartAndExpand
CenterAndExpand
EndAndExpand
FillAndExpand

Los Start Center campos,, End y Fill se utilizan para definir la alineación de la vista dentro del diseño
primario:
Para la alineación horizontal, Start coloca el View en el lado izquierdo del diseño primario y, para la
alineación vertical, coloca View en la parte superior del diseño primario.
Para la alineación horizontal y vertical, Center centra horizontal o verticalmente el View .
Para la alineación horizontal, End coloca el View en el lado derecho del diseño primario y, para la alineación
vertical, coloca View en la parte inferior del diseño primario.
En el caso de la alineación horizontal, Fill garantiza que el View ancho del diseño primario y para la
alineación vertical, garantiza que View rellene el alto del diseño primario.
Los StartAndExpand CenterAndExpand valores,, EndAndExpand y FillAndExpand se usan para definir la preferencia
de alineación y si la vista ocupará más espacio si está disponible en el elemento primario StackLayout .

NOTE
El valor predeterminado de las propiedades HorizontalOptions y VerticalOptions de una vista es
LayoutOptions.Fill .
Asociación
La alineación controla cómo se coloca una vista dentro de su diseño primario cuando el diseño primario contiene
espacio no utilizado (es decir, el diseño primario es mayor que el tamaño combinado de todos sus elementos
secundarios).
Un StackLayout solo respeta los Start Center campos,, End y Fill LayoutOptions en las vistas secundarias
que están en la dirección opuesta a la StackLayout orientación. Por lo tanto, las vistas secundarias dentro de una
orientación vertical StackLayout pueden establecer sus HorizontalOptions propiedades en uno de los Start
Center campos,, End o Fill . Del mismo modo, las vistas secundarias dentro de una orientación horizontal
StackLayout pueden establecer sus VerticalOptions propiedades en uno de los Start Center campos,, End o
Fill .

No StackLayout respeta los Start Center campos,, End y en las Fill LayoutOptions vistas secundarias que se
encuentran en la misma dirección que la StackLayout orientación. Por consiguiente, una orientada verticalmente
StackLayout omite Start los Center campos,, End o Fill si se establecen en las VerticalOptions propiedades
de las vistas secundarias. De forma similar, una orientación horizontal StackLayout omite los Start Center
campos,, End o Fill si se establecen en las HorizontalOptions propiedades de las vistas secundarias.

NOTE
LayoutOptions.Fill generalmente invalida las solicitudes de tamaño especificadas mediante HeightRequest las
WidthRequest propiedades y.

En el siguiente ejemplo de código XAML se muestra una orientación vertical StackLayout en la que cada elemento
secundario Label establece su HorizontalOptions propiedad en uno de los cuatro campos de alineación de la
LayoutOptions estructura:

<StackLayout Margin="0,20,0,0">
...
<Label Text="Start" BackgroundColor="Gray" HorizontalOptions="Start" />
<Label Text="Center" BackgroundColor="Gray" HorizontalOptions="Center" />
<Label Text="End" BackgroundColor="Gray" HorizontalOptions="End" />
<Label Text="Fill" BackgroundColor="Gray" HorizontalOptions="Fill" />
</StackLayout>

El código de C# equivalente se muestra a continuación:

Content = new StackLayout


{
Margin = new Thickness(0, 20, 0, 0),
Children = {
...
new Label { Text = "Start", BackgroundColor = Color.Gray, HorizontalOptions = LayoutOptions.Start },
new Label { Text = "Center", BackgroundColor = Color.Gray, HorizontalOptions = LayoutOptions.Center },
new Label { Text = "End", BackgroundColor = Color.Gray, HorizontalOptions = LayoutOptions.End },
new Label { Text = "Fill", BackgroundColor = Color.Gray, HorizontalOptions = LayoutOptions.Fill }
}
};

El código da como resultado el diseño que se muestra en las siguientes capturas de pantallas:
Expansión
La expansión controla si una vista ocupará más espacio, si está disponible, dentro de StackLayout . Si
StackLayout contiene espacio sin usar (es decir, el valor de StackLayout es mayor que el tamaño combinado de
todos sus elementos secundarios), el espacio no utilizado se comparte por igual entre todas las vistas secundarias
que solicitan la expansión estableciendo sus HorizontalOptions VerticalOptions propiedades o en un
LayoutOptions campo que utiliza el AndExpand sufijo. Tenga en cuenta que cuando se usa todo el espacio de
StackLayout , las opciones de expansión no tienen ningún efecto.

Un elemento StackLayout solo puede expandir las vistas secundarias en la dirección de su orientación. Por lo
tanto, una orientación vertical StackLayout puede expandir vistas secundarias que establecen sus
VerticalOptions propiedades en uno de los StartAndExpand CenterAndExpand campos,, EndAndExpand o
FillAndExpand , si StackLayout contiene espacio sin usar. Del mismo modo, una orientación horizontal
StackLayout puede expandir vistas secundarias que establecen sus HorizontalOptions propiedades en uno de los
StartAndExpand CenterAndExpand campos,, EndAndExpand o FillAndExpand , si StackLayout contiene espacio sin
usar.
StackLayout No puede expandir las vistas secundarias en la dirección opuesta a su orientación. Por lo tanto, en
una orientación vertical StackLayout , establecer la HorizontalOptions propiedad en una vista secundaria en
StartAndExpand tiene el mismo efecto que establecer la propiedad en Start .

NOTE
Tenga en cuenta que al habilitar la expansión no se cambia el tamaño de una vista a menos que use
LayoutOptions.FillAndExpand .

En el siguiente ejemplo de código XAML se muestra una orientación vertical StackLayout en la que cada elemento
secundario Label establece su VerticalOptions propiedad en uno de los cuatro campos de expansión de la
LayoutOptions estructura:

<StackLayout Margin="0,20,0,0">
...
<BoxView BackgroundColor="Red" HeightRequest="1" />
<Label Text="Start" BackgroundColor="Gray" VerticalOptions="StartAndExpand" />
<BoxView BackgroundColor="Red" HeightRequest="1" />
<Label Text="Center" BackgroundColor="Gray" VerticalOptions="CenterAndExpand" />
<BoxView BackgroundColor="Red" HeightRequest="1" />
<Label Text="End" BackgroundColor="Gray" VerticalOptions="EndAndExpand" />
<BoxView BackgroundColor="Red" HeightRequest="1" />
<Label Text="Fill" BackgroundColor="Gray" VerticalOptions="FillAndExpand" />
<BoxView BackgroundColor="Red" HeightRequest="1" />
</StackLayout>

El código de C# equivalente se muestra a continuación:

Content = new StackLayout


{
Margin = new Thickness(0, 20, 0, 0),
Children = {
...
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "StartAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.StartAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "CenterAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.CenterAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "EndAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.EndAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 },
new Label { Text = "FillAndExpand", BackgroundColor = Color.Gray, VerticalOptions =
LayoutOptions.FillAndExpand },
new BoxView { BackgroundColor = Color.Red, HeightRequest = 1 }
}
};

El código da como resultado el diseño que se muestra en las siguientes capturas de pantallas:
Cada Label ocupa la misma cantidad de espacio dentro de StackLayout . Aun así, solo el último elemento Label ,
que establece su propiedad VerticalOptions en FillAndExpand , tiene un tamaño diferente. Además, cada uno
Label está separado por un rojo pequeño BoxView , lo que permite que el espacio que Label ocupa se vea
fácilmente.

Resumen
En este artículo se ha explicado el efecto que LayoutOptions tiene cada valor de estructura en la alineación y la
expansión de una vista, con respecto a su elemento primario. Los Start Center campos,, End y Fill se utilizan
para definir la alineación de la vista dentro del diseño primario, y los StartAndExpand CenterAndExpand campos,,
EndAndExpand y FillAndExpand se usan para definir las preferencias de alineación y para determinar si la vista
ocupará más espacio, si está disponible, dentro de StackLayout .

Vínculos relacionados
LayoutOptions (ejemplo)
LayoutOptions
Compresión de diseño
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
La compresión de diseño quita los diseños especificados del árbol visual para intentar mejorar el rendimiento de
la representación de páginas. En este artículo se explica cómo habilitar la compresión del diseño y las ventajas que
puede llevar a cabo.

Información general
:::no-loc(Xamarin.Forms)::: realiza el diseño mediante dos series de llamadas a métodos recursivos:
El diseño comienza en la parte superior del árbol visual con una página y continúa a través de todas las ramas
del árbol visual para abarcar todos los elementos visuales de una página. Los elementos que son elementos
primarios de otros elementos son responsables de ajustar el tamaño y la posición de sus elementos
secundarios en relación con ellos mismos.
La invalidación es el proceso por el que un cambio en un elemento de una página desencadena un nuevo ciclo
de diseño. Los elementos se consideran no válidos cuando ya no tienen el tamaño o la posición correctos. Cada
elemento del árbol visual que tiene elementos secundarios recibe una alerta cuando uno de sus elementos
secundarios cambia de tamaño. Por lo tanto, un cambio en el tamaño de un elemento en el árbol visual puede
producir cambios que se hagan en el árbol.
Para obtener más información sobre cómo :::no-loc(Xamarin.Forms)::: realiza el diseño, vea crear un diseño
personalizado.
El resultado del proceso de diseño es una jerarquía de controles nativos. Sin embargo, esta jerarquía incluye
representadores de contenedores adicionales y contenedores para los representadores de plataforma, lo que
aumenta aún más el anidamiento de la jerarquía de vistas. Cuanto más profundo sea el nivel de anidamiento,
mayor será la cantidad de trabajo que debe :::no-loc(Xamarin.Forms)::: realizarse para mostrar una página. En el
caso de los diseños complejos, la jerarquía de vistas puede ser tanto profunda como amplia, con varios niveles de
anidamiento.
Por ejemplo, considere el siguiente botón de la aplicación de ejemplo para iniciar sesión en Facebook:

Este botón se especifica como un control personalizado con la siguiente jerarquía de vistas XAML:
<ContentView ...>
<StackLayout>
<StackLayout ...>
<AbsoluteLayout ...>
<Button ... />
<Image ... />
<Image ... />
<BoxView ... />
<Label ... />
<Button ... />
</AbsoluteLayout>
</StackLayout>
<Label ... />
</StackLayout>
</ContentView>

La jerarquía de vista anidada resultante se puede examinar con Xamarin inspector. En Android, la jerarquía de
vistas anidadas contiene 17 vistas:

La compresión de diseño, que está disponible para :::no-loc(Xamarin.Forms)::: las aplicaciones en las plataformas
iOS y Android, pretende aplanar el anidamiento de la vista quitando los diseños especificados del árbol visual, lo
que puede mejorar el rendimiento de la representación de páginas. La ventaja de rendimiento que se entrega
varía en función de la complejidad de una página, la versión del sistema operativo que se está usando y el
dispositivo en el que se ejecuta la aplicación. Sin embargo, las mejoras de rendimiento más importantes se
apreciarán en los dispositivos más antiguos.

NOTE
Aunque este artículo se centra en los resultados de aplicar la compresión de diseño en Android, es igualmente aplicable a
iOS.

Compresión de diseño
En XAML, la compresión de diseño se puede habilitar estableciendo la CompressedLayout.IsHeadless propiedad
adjunta en true en una clase de diseño:

<StackLayout CompressedLayout.IsHeadless="true">
...
</StackLayout>
Como alternativa, se puede habilitar en C# especificando la instancia de diseño como el primer argumento del
CompressedLayout.SetIsHeadless método:

CompressedLayout.SetIsHeadless(stackLayout, true);

IMPORTANT
Dado que la compresión de diseño quita un diseño del árbol visual, no es adecuado para los diseños que tienen una
apariencia visual o que obtienen una entrada táctil. Por lo tanto, los diseños que establecen VisualElement propiedades
(como,,, BackgroundColor IsVisible Rotation Scale TranslationX y TranslationY o que aceptan gestos) no
son candidatos para la compresión del diseño. Sin embargo, la habilitación de la compresión de diseño en un diseño que
establece las propiedades de apariencia visual o que acepta gestos no producirá un error de compilación o en tiempo de
ejecución. En su lugar, se aplicará la compresión de diseño y las propiedades de apariencia visual y el reconocimiento de
gestos producirán un error en modo silencioso.

En el caso del botón Facebook, la compresión de diseño se puede habilitar en las tres clases de diseño:

<StackLayout CompressedLayout.IsHeadless="true">
<StackLayout CompressedLayout.IsHeadless="true" ...>
<AbsoluteLayout CompressedLayout.IsHeadless="true" ...>
...
</AbsoluteLayout>
</StackLayout>
...
</StackLayout>

En Android, esto da como resultado una jerarquía de vista anidada de 14 vistas:

En comparación con la jerarquía de vista anidada original de 17 vistas, esto representa una reducción en el
número de vistas de 17%. Aunque esta reducción puede parecer insignificante, la reducción de la vista en toda la
página puede ser más significativa.
Representadores rápidos
Los representadores rápidos reducen los costos de inflación y representación de :::no-loc(Xamarin.Forms)::: los
controles en Android mediante el acoplamiento de la jerarquía de vista nativa resultante. Esto mejora aún más el
rendimiento al crear menos objetos, lo que a su vez da como resultado un árbol visual menos complejo y un
menor uso de memoria. Para obtener más información acerca de los representadores rápidos, vea
representadores rápidos.
En el caso del botón de Facebook de la aplicación de ejemplo, la combinación de la compresión de diseño y los
representadores rápidos produce una jerarquía de vistas anidadas de 8 vistas:
En comparación con la jerarquía de vista anidada original de 17 vistas, esto representa una reducción del 52%.
La aplicación de ejemplo contiene una página extraída de una aplicación real. Sin la compresión de diseño y los
representadores rápidos, la página genera una jerarquía de vistas anidada de 130 vistas en Android. Habilitar los
representadores rápidos y la compresión de diseño en las clases de diseño adecuadas reduce la jerarquía de vista
anidada a 70 vistas, una reducción del 46%.

Resumen
La compresión de diseño quita los diseños especificados del árbol visual para intentar mejorar el rendimiento de
la representación de páginas. La ventaja de rendimiento que esto ofrece varía según la complejidad de una página,
la versión del sistema operativo que se va a usar y el dispositivo en el que se ejecuta la aplicación. Sin embargo,
las mejoras de rendimiento más importantes se apreciarán en los dispositivos más antiguos.

Vínculos relacionados
Creación de un diseño personalizado
Representadores rápidos
LayoutCompression (ejemplo)
Margen y relleno
18/12/2020 • 4 minutes to read • Edit Online

Las propiedades margin y padding controlan el comportamiento de diseño cuando se representa un elemento
en la interfaz de usuario. En este artículo se muestra la diferencia entre las dos propiedades y cómo establecerlas.

Información general
Margin y padding son conceptos de diseño relacionados:
La Margin propiedad representa la distancia entre un elemento y sus elementos adyacentes, y se usa para
controlar la posición de representación del elemento y la posición de representación de sus vecinos. Margin
los valores se pueden especificar en las clases de diseño y de vista .
La Padding propiedad representa la distancia entre un elemento y sus elementos secundarios, y se utiliza
para separar el control de su propio contenido. Padding los valores se pueden especificar en las clases de
diseño .
En el siguiente diagrama se ilustran los dos conceptos:

Tenga en cuenta que Margin los valores son aditivos. Por consiguiente, si dos elementos adyacentes especifican
un margen de 20 píxeles, la distancia entre los elementos será de 40 píxeles. Además, los márgenes y el relleno
son aditivos cuando se aplican ambos, en que la distancia entre un elemento y cualquier contenido será el
margen más el relleno.
Especificar un grosor
Las Margin Padding propiedades y son de tipo Thickness . Existen tres posibilidades al crear una Thickness
estructura:
Cree una Thickness estructura definida por un único valor uniforme. El valor Single se aplica a los lados
izquierdo, superior, derecho e inferior del elemento.
Cree una Thickness estructura definida por valores horizontales y verticales. El valor horizontal se aplica
simétricamente a los lados izquierdo y derecho del elemento, con el valor vertical que se aplica de forma
simétrica a los lados superior e inferior del elemento.
Cree una Thickness estructura definida por cuatro valores distintos que se apliquen a los lados izquierdo,
superior, derecho e inferior del elemento.
En el siguiente ejemplo de código XAML se muestran las tres posibilidades:

<StackLayout Padding="0,20,0,0">
<Label Text="Xamarin.Forms" Margin="20" />
<Label Text="Xamarin.iOS" Margin="10, 15" />
<Label Text="Xamarin.Android" Margin="0, 20, 15, 5" />
</StackLayout>

El código de C# equivalente se muestra en el ejemplo de código siguiente:

var stackLayout = new StackLayout {


Padding = new Thickness(0,20,0,0),
Children = {
new Label { Text = "Xamarin.Forms", Margin = new Thickness (20) },
new Label { Text = "Xamarin.iOS", Margin = new Thickness (10, 25) },
new Label { Text = "Xamarin.Android", Margin = new Thickness (0, 20, 15, 5) }
}
};

NOTE
Thickness los valores pueden ser negativos, lo que suele recortar o sobredibujar el contenido.

Resumen
En este artículo se ha mostrado la diferencia entre las Margin propiedades y y Padding Cómo establecerlas. Las
propiedades controlan el comportamiento de diseño cuando se representa un elemento en la interfaz de usuario.

Vínculos relacionados
Márgen
Relleno
Thickness
Diseño para tabletas y aplicaciones de escritorio
18/12/2020 • 5 minutes to read • Edit Online

Xamarin.Formsadmite todos los tipos de dispositivos disponibles en las plataformas compatibles, por lo que
además de los teléfonos, las aplicaciones también se pueden ejecutar en:
iPad
Tabletas Android,
Tabletas y equipos de escritorio con Windows (que ejecutan Windows 10).
En esta página se describe brevemente:
los tipos de dispositivoadmitidos y
Cómo optimizar los diseños para tabletas en comparación con los teléfonos.

Tipos de dispositivos
Los dispositivos de pantalla más grandes están disponibles para todas las plataformas admitidas por
Xamarin.Forms .
iPad (iOS )
La Xamarin.Forms plantilla incluye automáticamente la compatibilidad con iPad mediante la configuración de la
opción info. plist > dispositivos en universal (lo que significa que se admiten tanto iPhone como iPad).
Para proporcionar una experiencia de inicio agradable y asegurarse de que se utiliza la resolución de pantalla
completa en todos los dispositivos, debe asegurarse de que se proporciona una pantalla de inicio específica de iPad
(con un guion gráfico). Esto garantiza que la aplicación se represente correctamente en los dispositivos iPad mini,
iPad y iPad Pro.
Antes de iOS 9, todas las aplicaciones requerían la pantalla completa en el dispositivo, pero algunos iPad ahora
pueden realizar varias tareas de pantalla dividida. Esto significa que la aplicación podría ocupar simplemente una
columna delgada en el lateral de la pantalla, el 50% del ancho de la pantalla o toda la pantalla.
La funcionalidad de pantalla dividida significa que debe diseñar la aplicación para que funcione bien con tan solo
320 píxeles de ancho o hasta 1366 píxeles de ancho.
Tabletas Android
El ecosistema de Android tiene una gran cantidad de tamaños de pantalla compatibles, desde pequeños teléfonos
hasta grandes tabletas. Xamarin.Formspuede admitir todos los tamaños de pantalla, pero, al igual que con las otras
plataformas, puede que desee ajustar la interfaz de usuario para dispositivos de mayor tamaño.
Cuando se admiten muchas resoluciones de pantalla diferentes, puede proporcionar los recursos de imagen nativa
en distintos tamaños para optimizar la experiencia del usuario. Revise la documentación de los recursos de Android
(y, en particular, cree recursos para diferentes tamaños de pantalla) para más información sobre cómo estructurar
las carpetas y los nombres de archivo en el proyecto de aplicación de Android para incluir recursos de imagen
optimizados en la aplicación.
Tabletas y escritorios de Windows
Para admitir tabletas y equipos de escritorio que ejecuten Windows, deberá usar la compatibilidad con Windows
UWP, que crea aplicaciones universales que se ejecutan en Windows 10.
Se puede cambiar el tamaño de las aplicaciones que se ejecutan en tabletas y escritorios de Windows a
dimensiones arbitrarias, además de ejecutarse en pantalla completa.
Optimizar para tabletas y equipos de escritorio
Puede ajustar la Xamarin.Forms interfaz de usuario en función de si se usa un dispositivo de teléfono o tableta o
escritorio. Esto significa que puede optimizar la experiencia del usuario en dispositivos de gran tamaño, como
tabletas y equipos de escritorio.
Dispositivo. expresión
Puede usar la Device clase para cambiar el comportamiento de la aplicación o la interfaz de usuario. Mediante la
Device.Idiom enumeración puede

if (Device.Idiom == TargetIdiom.Phone)
{
HeroImage.Source = ImageSource.FromFile("hero.jpg");
} else {
HeroImage.Source = ImageSource.FromFile("herotablet.jpg");
}

Este enfoque se puede expandir para realizar cambios significativos en los diseños de página individuales o incluso
para representar páginas completamente diferentes en pantallas más grandes.
Aprovechar MasterDetailPage
MasterDetailPage Es ideal para pantallas de mayor tamaño, especialmente en el iPad donde usa
UISplitViewController para proporcionar una experiencia de iOS nativa.

Revise esta entrada de blog de Xamarin para ver cómo puede adaptar la interfaz de usuario para que los teléfonos
usen un diseño y las pantallas más grandes puedan usar otro (con MasterDetailPage ).

Vínculos relacionados
Blog de Xamarin
Ejemplo de mis compradores
Características de la plataforma Android
18/12/2020 • 6 minutes to read • Edit Online

El desarrollo Xamarin.Forms de aplicaciones para Android requiere Visual Studio. La Página plataformas
compatibles contiene más información sobre los requisitos previos.

Características específicas de las plataformas


Las características específicas de la plataforma permiten consumir funcionalidad que solo está disponible en una
plataforma específica, sin necesidad de implementar representadores o efectos personalizados.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms vistas, páginas y
diseños en Android:
Controlar el orden Z de los elementos visuales para determinar el orden de dibujo. Para obtener más
información, consulte elevación de VisualElement en Android.
Deshabilitar el modo de color heredado en un compatible VisualElement . Para obtener más información,
consulte el modo de color heredado VisualElement en Android.
Se proporciona la siguiente funcionalidad específica de la plataforma para las Xamarin.Forms vistas en Android:
Usar los valores predeterminados de relleno y sombra de los botones de Android. Para obtener más
información, consulte botón relleno y sombras en Android.
Establecer las opciones del editor de métodos de entrada para el teclado en pantalla para un Entry . Para
obtener más información, consulte Opciones del editor de métodos de entrada de entrada en Android.
Habilitar una sombra paralela en un ImageButton . Para obtener más información, vea sombras Drop Shadows
en Android.
Habilitar el desplazamiento rápido en un ListView . Para obtener más información, consulte desplazamiento
rápido de ListView en Android.
Control de la transición que se utiliza al abrir SwipeView . Para obtener más información, consulte el modo de
transición de SwipeView.
Controlar si un WebView puede mostrar contenido mixto. Para obtener más información, vea contenido mixto
de WebView en Android.
Habilitar zoom en un WebView . Para obtener más información, vea zoom de vista previa en Android.
La siguiente funcionalidad específica de la plataforma se proporciona para Xamarin.Forms las celdas en Android:
Habilitar ViewCell el modo heredado de acciones de contexto, de modo que el menú acciones de contexto no
se actualice cuando el elemento seleccionado en un ListView cambie. Para obtener más información, vea
acciones de contexto de ViewCell en Android.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms las páginas en Android:
Establecer el alto de la barra de navegación en un NavigationPage . Para obtener más información, consulte
alto de la barra NavigationPage en Android.
Deshabilitar animaciones de transición al navegar por las páginas de un TabbedPage . Para obtener más
información, vea animaciones de transición de páginas de TabbedPage en Android.
Habilitar pasar el dedo entre las páginas de un TabbedPage . Para obtener más información, vea la Página de
TabbedPage deslizante en Android.
Establecer la ubicación y el color de la barra de herramientas en un TabbedPage . Para obtener más
información, vea selección de ubicación y color de la barra de herramientas TabbedPage en Android.
Se proporciona la siguiente funcionalidad específica de la plataforma para la Xamarin.Forms Application clase
en Android:
Establecer el modo de funcionamiento de un teclado en pantalla. Para obtener más información, vea modo de
entrada de teclado en Android.
Deshabilitar los Disappearing eventos de ciclo de vida de la Appearing página y en pausar y reanudar
respectivamente para las aplicaciones que usan AppCompat. Para obtener más información, vea eventos del
ciclo de vida de la página en Android.

Compatibilidad con plataformas


Originalmente, el Xamarin.Forms proyecto de Android predeterminado usaba un estilo anterior de representación
de controles que era común antes de Android 5,0. Las aplicaciones compiladas con la plantilla tienen
FormsApplicationActivity como la clase base de su actividad principal.

Diseño de materiales a través de AppCompat


Xamarin.Forms Los proyectos de Android ahora usan FormsAppCompatActivity como la clase base de su actividad
principal. Esta clase usa las características de AppCompat proporcionadas por Android para implementar temas
de diseño de materiales.
Para agregar temas de diseño de materiales al Xamarin.Forms proyecto de Android, siga las instrucciones de
instalación para la compatibilidad con AppCompat
Este es el ejemplo todo con el valor predeterminado FormsApplicationActivity :

Y este es el mismo código después de actualizar el proyecto para usar FormsAppCompatActivity (y agregar la
información de tema adicional):
NOTE
Al usar FormsAppCompatActivity , las clases base para algunos representadores personalizados de Android serán
diferentes.

Migración de AndroidX
AndroidX reemplaza a la biblioteca de compatibilidad de Android. Para obtener información sobre AndroidX y
cómo migrar una Xamarin.Forms aplicación para usar las bibliotecas de AndroidX, consulte migración
Xamarin.Forms de AndroidX en .

Vínculos relacionados
Agregar compatibilidad con el diseño de materiales
Migración de AndroidX enXamarin.Forms
18/12/2020 • 6 minutes to read • Edit Online

AndroidX reemplaza a la biblioteca de compatibilidad de Android. En este artículo se explica por qué existe
AndroidX, cómo afecta Xamarin.Forms y cómo migrar la aplicación para usar las bibliotecas de AndroidX.

Historial de AndroidX
La biblioteca de compatibilidad de Android se creó para proporcionar las nuevas características de las versiones
anteriores de Android. Se trata de una capa de compatibilidad que permite a los desarrolladores usar funciones
que pueden no existir en todas las versiones del sistema operativo Android y que tienen reservas correctas para
versiones anteriores. La biblioteca de compatibilidad también incluye clases auxiliares y auxiliares, herramientas de
depuración y utilidad, y clases sofisticadas que dependen de otras clases de la biblioteca de compatibilidad para
que funcionen.
Aunque la biblioteca de soporte técnico era originalmente un solo binario, ha crecido y ha evolucionado a un
conjunto de bibliotecas, que son casi esenciales para el desarrollo de aplicaciones modernas. Estas son algunas de
las características más utilizadas de la biblioteca de soporte:
La Fragment clase de soporte.
Que se RecyclerView usa para administrar listas largas.
Multidex admite aplicaciones con más de 65.536 métodos.
La clase ActivityCompat .
AndroidX es un sustituto de la biblioteca de soporte técnico, que ya no se mantiene, todo el desarrollo de nuevas
bibliotecas se producirá en la biblioteca de AndroidX. AndroidX es una biblioteca rediseñada que usa el control de
versiones semántico, nombres de paquetes más claros y una mejor compatibilidad para patrones de arquitectura
de aplicaciones comunes. AndroidX versión 1.0.0 es el binario equivalente a la versión 28.0.0 de la biblioteca de
compatibilidad. Para obtener una lista completa de las asignaciones de clases de la biblioteca de compatibilidad a
AndroidX, consulte compatibilidad de las asignaciones de clases de la biblioteca en Developer.Android.com.
Google creó un proceso de migración denominado Jetifier con AndroidX. El Jetifier inspecciona el código de bytes
jar durante el proceso de compilación y reasigna las referencias de la biblioteca de compatibilidad, tanto en el
código de la aplicación como en las dependencias, a su AndroidX equivalente.
En una Xamarin.Forms aplicación, al igual que en una aplicación de Java de Android, las dependencias jar se deben
migrar a AndroidX. Sin embargo, también se deben migrar los enlaces de Xamarin para que apunten a los
archivos JAR subyacentes y correctos. Xamarin.Formsse ha agregado compatibilidad con la migración automática
de AndroidX en la versión 4,5.
Para obtener más información sobre AndroidX, consulte información general sobre AndroidX en
Developer.Android.com.

Migración automática enXamarin.Forms


Para migrar automáticamente a AndroidX, un Xamarin.Forms proyecto debe:
Versión 29 de la API de destino, o superior.
Use la Xamarin.Forms versión 4,5 o posterior.
Una vez que haya confirmado estos valores en el proyecto, compile la aplicación de Android en Visual Studio
2019. Durante el proceso de compilación, se inspecciona el lenguaje intermedio (IL) y las dependencias de la
biblioteca de soporte y los enlaces se intercambian con las dependencias de AndroidX. Si la aplicación tiene todas
las dependencias de AndroidX necesarias para compilar, observará que no hay ninguna diferencia en el proceso de
compilación.

NOTE
Debe mantener las referencias a la biblioteca de soporte en el proyecto. Se usan para compilar la aplicación antes de que el
proceso de migración Inspeccione el IL resultante y transforme las dependencias.

Si se detectan dependencias de AndroidX que no forman parte del proyecto, se genera un error de compilación
que indica qué paquetes de AndroidX faltan. A continuación se muestra un ejemplo de error de compilación:

Could not find 37 AndroidX assemblies, make sure to install the following NuGet packages:
- Xamarin.AndroidX.Lifecycle.LiveData
- Xamarin.AndroidX.Browser
- Xamarin.Google.Android.Material
- Xamarin.AndroidX.Legacy.Supportv4
You can also copy and paste the following snippit into your .csproj file:
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.1.0-rc1" />
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.0.0-rc1" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0-rc1" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0-rc1" />

Los paquetes de NuGet que faltan se pueden instalar a través del administrador de paquetes NuGet en Visual
Studio, o bien se pueden instalar editando el archivo Android. csproj para incluir los PackageReference elementos
XML que se muestran en el error.
Una vez que se resuelven los paquetes que faltan, al volver a compilar el proyecto se cargan los paquetes que
faltan y el proyecto se compila mediante dependencias de AndroidX en lugar de admitir dependencias de
biblioteca.

NOTE
Si el proyecto y las dependencias del proyecto no hacen referencia a las bibliotecas de compatibilidad con Android, el
proceso de migración no realiza ninguna acción y no se ejecuta.

Vínculos relacionados
Introducción a la biblioteca de compatibilidad de Android en Developer.Android.com
Información general de AndroidX en Developer.Android.com
Agregar AppCompat y el diseño de material
18/12/2020 • 4 minutes to read • Edit Online

Siga estos pasos para convertir Xamarin.Forms aplicaciones Android existentes para usar AppCompat y el diseño
de material.

Información general
En estas instrucciones se explica cómo actualizar las Xamarin.Forms aplicaciones Android existentes para usar la
biblioteca AppCompat y habilitar el diseño de material en la versión de Android de las Xamarin.Forms
aplicaciones.
1. actualizarXamarin.Forms
Asegúrese de que la solución usa Xamarin.Forms 2,0 o posterior. Actualice elXamarin.Forms Paquete NuGet en 2,0
si es necesario.
2. comprobar la versión de Android
Asegúrese de que la plataforma de destino del proyecto de Android es Android 6,0 (Marshmallow). Compruebe las
Opciones del > del proyecto de Android > Compilar > configuración general para asegurarse de que el
marco de trabajo de corrent está seleccionado:

3. agregar nuevos temas para admitir el diseño de materiales


Cree los tres archivos siguientes en el proyecto de Android y péguelo en el contenido siguiente. Google
proporciona una Guía de estilo y un generador de paletas de colores para ayudarle a elegir una combinación de
colores alternativa para la especificada.
Recursos/valores/colors.xml

<resources>
<color name="primary">#2196F3</color>
<color name="primaryDark">#1976D2</color>
<color name="accent">#FFC107</color>
<color name="window_background">#F5F5F5</color>
</resources>

Recursos/valores/style.xml
<resources>
<style name="MyTheme" parent="MyTheme.Base">
</style>
<style name="MyTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@color/window_background</item>
<item name="windowActionModeOverlay">true</item>
</style>
</resources>

Se debe incluir un estilo adicional en la carpeta Values-V21 para aplicar propiedades específicas cuando se
ejecuta en un círculo de Android y versiones más recientes.
Recursos/valores: V21/style.xml

<resources>
<style name="MyTheme" parent="MyTheme.Base">
<!--If you are using MasterDetailPage you will want to set these, else you can leave them out-->
<!--<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>-->
</style>
</resources>

4. actualizar AndroidManifest.xml
Para asegurarse de que se usa esta nueva información de tema, establezca tema en el archivo archivo
AndroidManifest agregando android:theme="@style/MyTheme" (deje el resto del código XML tal como estaba).
Propiedades/AndroidManifest.xml

...
<application android:label="AppName" android:icon="@drawable/icon"
android:theme="@style/MyTheme">
...

5. proporcionar diseños de pestaña y barra de herramientas


Cree archivos . axml y Toolbar. axml en el directorio Resources/layout y péguelo en el contenido siguiente:
Resources/layout/Tab. axml

<android.support.design.widget.TabLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:tabIndicatorColor="@android:color/white"
app:tabGravity="fill"
app:tabMode="fixed" />

Se han establecido algunas propiedades para las pestañas, incluidas la gravedad de la pestaña en fill y el modo
en fixed . Si tiene muchas pestañas que quiere cambiar a desplazable: Lea la documentación de Android
TabLayout para obtener más información.
Resources/layout/Toolbar. axml
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways" />

En estos archivos, vamos a crear un tema específico para la barra de herramientas que puede variar en la
aplicación. Para obtener más información, consulte la entrada de blog de la barra de herramientas Hello .
6. actualizar el MainActivity
En Xamarin.Forms las aplicaciones existentes, la clase MainActivity.CS heredará de FormsApplicationActivity .
Debe reemplazarse por FormsAppCompatActivity para habilitar la nueva funcionalidad.
MainActivity.cs

public class MainActivity : FormsAppCompatActivity // was FormsApplicationActivity

Por último, "conectar" los nuevos diseños del paso 5 en el OnCreate método, como se muestra aquí:

protected override void OnCreate(Bundle bundle)


{
// set the layout resources first
FormsAppCompatActivity.ToolbarResource = Resource.Layout.Toolbar;
FormsAppCompatActivity.TabLayoutResource = Resource.Layout.Tabbar;

// then call base.OnCreate and the Xamarin.Forms methods


base.OnCreate(bundle);
Forms.Init(this, bundle);
LoadApplication(new App());
}
Relleno de botones y sombras en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android controla si :::no-loc(Xamarin.Forms)::: los botones usan los valores predeterminados de
relleno y sombra de los botones de Android. Se consume en XAML estableciendo Button.UseDefaultPadding y
Button.UseDefaultShadow las propiedades adjuntas en boolean valores:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
...
<Button ...
android:Button.UseDefaultPadding="true"
android:Button.UseDefaultShadow="true" />
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

button.On<Android>().SetUseDefaultPadding(true).SetUseDefaultShadow(true);

El Button.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. [
Button.SetUseDefaultPadding ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. Button.
SetUseDefaultPadding ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . Button}, System. Boolean)) y [
Button.SetUseDefaultShadow ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. Button.
SetUseDefaultShadow ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . Los métodos Button}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se usan para controlar si
los :::no-loc(Xamarin.Forms)::: botones usan los valores de relleno y sombra predeterminados de los botones de
Android. Además, [ Button.UseDefaultPadding ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. Button. UseDefaultPadding ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . Button})) y [
Button.UseDefaultShadow ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. Button.
UseDefaultShadow ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . Button})). los métodos se pueden usar para devolver si
un botón usa el valor de relleno predeterminado y el valor de sombra predeterminado, respectivamente.
El resultado es que los :::no-loc(Xamarin.Forms)::: botones pueden usar los valores predeterminados de relleno y
sombra de los botones de Android:
Tenga en cuenta que en la captura de pantalla anterior cada una Button tiene definiciones idénticas, salvo que el
lado derecho Button usa los valores predeterminados de relleno y sombra de los botones de Android.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Opciones del editor de métodos de entrada de
entrada en Android
18/12/2020 • 4 minutes to read • Edit Online

Descargar el ejemplo
Este conjunto específico de la plataforma Android establece las opciones del editor de métodos de entrada (IME)
para el teclado en pantalla para un Entry . Esto incluye establecer el botón acción del usuario en la esquina
inferior del teclado en pantalla y las interacciones con Entry . Se consume en XAML estableciendo la
Entry.ImeOptions propiedad adjunta en un valor de la ImeFlags enumeración:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout ...>
<Entry ... android:Entry.ImeOptions="Send" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

entry.On<Android>().SetImeOptions(ImeFlags.Send);

El Entry.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. [
Entry.SetImeOptions ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. entry.
SetImeOptions ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . Entry}, :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. AndroidSpecific. ImeFlags)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se usa para establecer la
opción de acción del método de entrada para el teclado en pantalla para el Entry , con la ImeFlags enumeración
que proporciona los valores siguientes:
Default : indica que no se requiere ninguna clave de acción específica y que el control subyacente producirá su
propia si es posible. Será Next o Done .
None : indica que no habrá ninguna clave de acción disponible.
Go : indica que la clave de acción llevará a cabo una operación de "Go", que tomará al usuario el destino del
texto que escribió.
Search : indica que la clave de acción realiza una operación de "búsqueda", tomando al usuario el resultado de
buscar el texto que ha escrito.
Send : indica que la clave de acción llevará a cabo una operación de "envío", entregando el texto a su destino.
Next : indica que la clave de acción llevará a cabo una operación "siguiente", que tomará el usuario en el
siguiente campo que aceptará texto.
Done : indica que la clave de acción llevará a cabo una operación "Done", cerrando el teclado en pantalla.
Previous : indica que la clave de acción realizará una operación "anterior" y tomará el usuario en el campo
anterior que aceptará texto.
ImeMaskAction : la máscara para seleccionar las opciones de acción.
NoPersonalizedLearning : indica que el corrector ortográfico no aprenderá del usuario ni sugerirá correcciones
en función de lo que el usuario haya escrito previamente.
NoFullscreen : indica que la interfaz de usuario no debe ir a pantalla completa.
NoExtractUi : indica que no se mostrará ninguna interfaz de usuario para el texto extraído.
NoAccessoryAction : indica que no se mostrará ninguna interfaz de usuario para las acciones personalizadas.

El resultado es que ImeFlags se aplica un valor especificado al teclado en pantalla para Entry , que establece las
opciones del editor de métodos de entrada:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Sombras paralelas de colocación en Android
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para habilitar una sombra paralela en un ImageButton . Se consume en
XAML estableciendo la ImageButton.IsShadowEnabled propiedad enlazable en true , junto con una serie de
propiedades adicionales enlazables opcionales que controlan la sombra paralela:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ImageButton ...
Source="XamarinLogo.png"
BackgroundColor="GhostWhite"
android:ImageButton.IsShadowEnabled="true"
android:ImageButton.ShadowColor="Gray"
android:ImageButton.ShadowRadius="12">
<android:ImageButton.ShadowOffset>
<Size>
<x:Arguments>
<x:Double>10</x:Double>
<x:Double>10</x:Double>
</x:Arguments>
</Size>
</android:ImageButton.ShadowOffset>
</ImageButton>
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

var imageButton = new :::no-loc(Xamarin.Forms):::.ImageButton { Source = "XamarinLogo.png", BackgroundColor =


Color.GhostWhite, ... };
imageButton.On<Android>()
.SetIsShadowEnabled(true)
.SetShadowColor(Color.Gray)
.SetShadowOffset(new Size(10, 10))
.SetShadowRadius(12);

IMPORTANT
Se dibuja una sombra paralela como parte del ImageButton fondo y el fondo solo se dibuja si BackgroundColor se
establece la propiedad. Por lo tanto, no se dibujará una sombra paralela si ImageButton.BackgroundColor no se establece
la propiedad.

El método especifica que este específico de la plataforma solo se ejecutará en Android. El


ImageButton.On<Android>
ImageButton.SetIsShadowEnabled método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific
espacio de nombres, se utiliza para controlar si una sombra paralela está habilitada en ImageButton . Además, se
pueden invocar los métodos siguientes para controlar la sombra paralela:
SetShadowColor : establece el color de la sombra paralela. El color predeterminado es Color.Default .
SetShadowOffset : establece el desplazamiento de la sombra paralela. El desplazamiento cambia la dirección en
la que se proyecta la sombra y se especifica como un Size valor. Los Size valores de la estructura se expresan
en unidades independientes del dispositivo, donde el primer valor es la distancia a la izquierda (valor negativo)
o a la derecha (valor positivo) y el segundo valor es la distancia por encima (valor negativo) o inferior (valor
positivo). El valor predeterminado de esta propiedad es (0,0, 0,0), lo que hace que se convierta la sombra en
torno a cada lado de ImageButton .
SetShadowRadius : establece el radio de desenfoque que se usa para representar la sombra paralela. El valor de
radio predeterminado es 10,0.

NOTE
El estado de una sombra paralela se puede consultar mediante una llamada a los GetIsShadowEnabled GetShadowColor
métodos,, GetShadowOffset y GetShadowRadius .

El resultado es que se puede habilitar una sombra paralela en un ImageButton :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Desplazamiento rápido de ListView en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para habilitar el desplazamiento rápido a través de los datos de un
ListView . Se consume en XAML estableciendo la ListView.IsFastScrollEnabled propiedad adjunta en un
boolean valor:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
...
<ListView ItemsSource="{Binding GroupedEmployees}"
GroupDisplayBinding="{Binding Key}"
IsGroupingEnabled="true"
android:ListView.IsFastScrollEnabled="true">
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

var listView = new :::no-loc(Xamarin.Forms):::.ListView { IsGroupingEnabled = true, ... };


listView.SetBinding(ItemsView<Cell>.ItemsSourceProperty, "GroupedEmployees");
listView.GroupDisplayBinding = new Binding("Key");
listView.On<Android>().SetIsFastScrollEnabled(true);

El ListView.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. El
ListView.SetIsFastScrollEnabled método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se utiliza para habilitar el
desplazamiento rápido a través de los datos de un ListView . Además, el SetIsFastScrollEnabled método se
puede utilizar para alternar el desplazamiento rápido llamando al IsFastScrollEnabled método para devolver si
está habilitado el desplazamiento rápido:

listView.On<Android>().SetIsFastScrollEnabled(!listView.On<Android>().IsFastScrollEnabled());

El resultado es que se puede habilitar el desplazamiento rápido a través de los datos de un ListView , lo que
cambia el tamaño del control de desplazamiento:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Alto de la barra de NavigationPage en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma Android establece el alto de la barra de navegación en un NavigationPage . Se
consume en XAML estableciendo la NavigationPage.BarHeight propiedad Bindable en un valor entero:

<NavigationPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat;assembly=:::no-
loc(Xamarin.Forms):::.Core"
android:NavigationPage.BarHeight="450">
...
</NavigationPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat;
...

public class AndroidNavigationPageCS : :::no-loc(Xamarin.Forms):::.NavigationPage


{
public AndroidNavigationPageCS()
{
On<Android>().SetBarHeight(450);
}
}

El NavigationPage.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android
de compatibilidad de aplicaciones. [ NavigationPage.SetBarHeight ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. AndroidSpecific. AppCompat. NavigationPage. SetBarHeight ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . NavigationPage}, System. Int32)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat espacio de nombres, se usa para
establecer el alto de la barra de navegación en NavigationPage . Además, [ NavigationPage.GetBarHeight ] (XREF:
:::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. AppCompat. NavigationPage. GetBarHeight (
:::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
Android, :::no-loc(Xamarin.Forms)::: . NavigationPage})) que se puede usar para devolver el alto de la barra de
navegación en NavigationPage .
El resultado es que se puede establecer el alto de la barra de navegación en un NavigationPage :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Eventos del ciclo de vida de la página en Android
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para deshabilitar los Disappearing Appearing eventos de página y en la
pausa y reanudación de la aplicación, respectivamente, para las aplicaciones que usan AppCompat. Además,
incluye la posibilidad de controlar si se muestra el teclado en pantalla al reanudar, en caso de que se muestre en
pausa, siempre que el modo de funcionamiento del teclado en pantalla esté establecido en
WindowSoftInputModeAdjust.Resize .

NOTE
Tenga en cuenta que estos eventos están habilitados de forma predeterminada para conservar el comportamiento existente
de las aplicaciones que se basan en los eventos. La deshabilitación de estos eventos hace que el ciclo de eventos AppCompat
coincida con el ciclo de eventos anterior a la AppCompat.

Este específico de la plataforma se puede consumir en XAML estableciendo las


Application.SendDisappearingEventOnPause Application.SendAppearingEventOnResume propiedades, y
Application.ShouldPreserveKeyboardOnResume asociadas a boolean los valores:

<Application ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
xmlns:androidAppCompat="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat;assembly=:::no-
loc(Xamarin.Forms):::.Core"
android:Application.WindowSoftInputModeAdjust="Resize"
androidAppCompat:Application.SendDisappearingEventOnPause="false"
androidAppCompat:Application.SendAppearingEventOnResume="false"
androidAppCompat:Application.ShouldPreserveKeyboardOnResume="true">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat;
...

:::no-loc(Xamarin.Forms):::.Application.Current.On<Android>()
.UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize)
.SendDisappearingEventOnPause(false)
.SendAppearingEventOnResume(false)
.ShouldPreserveKeyboardOnResume(true);

El Application.Current.On<Android> método especifica que este específico de la plataforma solo se ejecutará en


Android. [ Application.SendDisappearingEventOnPause ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. AppCompat. Application. SendDisappearingEventOnPause ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . Application}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat espacio de nombres, se usa para
habilitar o deshabilitar la activación del Disappearing evento de página, cuando la aplicación entra en el fondo. [
Application.SendAppearingEventOnResume ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. AppCompat. Application. SendAppearingEventOnResume ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . Application}, System. Boolean)) se usa para habilitar o deshabilitar la activación del
Appearing evento de página cuando la aplicación se reanuda desde el fondo. [
Application.ShouldPreserveKeyboardOnResume ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. AppCompat. Application. ShouldPreserveKeyboardOnResume ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . Application}, System. Boolean)) el método se usa para controlar si se muestra el teclado en
pantalla al reanudar, en caso de que se muestre en pausa, siempre que el modo de funcionamiento del teclado en
pantalla esté establecido en WindowSoftInputModeAdjust.Resize .
El resultado es que los Disappearing Appearing eventos de página y no se activarán en la pausa y la reanudación
de la aplicación, respectivamente, y que si se mostró el teclado en pantalla cuando se pausó la aplicación, también
se mostrará cuando se reanude la aplicación:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Modo de entrada de teclado de software en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para establecer el modo de funcionamiento de un área de entrada de
teclado en pantalla y se utiliza en XAML estableciendo la Application.WindowSoftInputModeAdjust propiedad adjunta
en un valor de la WindowSoftInputModeAdjust enumeración:

<Application ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
android:Application.WindowSoftInputModeAdjust="Resize">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

App.Current.On<Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);

El Application.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. [
Application.UseWindowSoftInputModeAdjust ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. Application. UseWindowSoftInputModeAdjust ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . Aplicación}, :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
WindowSoftInputModeAdjust)), en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio
de nombres, se usa para establecer el modo de funcionamiento del área de entrada de teclado en pantalla, con la
WindowSoftInputModeAdjust enumeración que proporciona dos valores: Pan y Resize . El Pan valor usa la
AdjustPan opción de ajuste, que no cambia el tamaño de la ventana cuando un control de entrada tiene el foco. En
su lugar, el contenido de la ventana se panorámica para que el foco actual no se oculte por el teclado en pantalla. El
Resize valor usa la AdjustResize opción de ajuste, que cambia el tamaño de la ventana cuando un control de
entrada tiene el foco, para dejar espacio para el teclado en pantalla.
El resultado es que el modo de funcionamiento del área de entrada de teclado en pantalla se puede establecer
cuando un control de entrada tiene el foco:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
SwipeView deslizar el modo de transición en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma Android controla la transición que se usa al abrir SwipeView . Se consume
en XAML estableciendo la SwipeView.SwipeTransitionMode propiedad Bindable en un valor de la
SwipeTransitionMode enumeración:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core" >
<StackLayout>
<SwipeView android:SwipeView.SwipeTransitionMode="Drag">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

SwipeView swipeView = new :::no-loc(Xamarin.Forms):::.SwipeView();


swipeView.On<Android>().SetSwipeTransitionMode(SwipeTransitionMode.Drag);
// ...

El SwipeView.On<Android>método especifica que este específico de la plataforma solo se ejecutará en Android. El


SwipeView.SetSwipeTransitionMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar la transición que se utiliza al abrir SwipeView . La
SwipeTransitionMode enumeración proporciona dos valores posibles:

Reveal indica que los elementos que se van a deslizar se revelarán cuando se deslice el contenido por el dedo
SwipeView y es el valor predeterminado de la SwipeView.SwipeTransitionMode propiedad.
Drag indica que los elementos que se van a deslizar se arrastrarán hasta la vista a medida que SwipeView se
deslice el contenido.
Además, el SwipeView.GetSwipeTransitionMode método se puede utilizar para devolver el SwipeTransitionMode que
se aplica a SwipeView .
El resultado es que un SwipeTransitionMode valor especificado se aplica a SwipeView , que controla la transición
que se utiliza al abrir SwipeView :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
Página deslizante de TabbedPage en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para habilitar el deslizamiento con un dedo horizontal entre las páginas
de un TabbedPage . Se consume en XAML estableciendo la TabbedPage.IsSwipePagingEnabled propiedad adjunta en
un boolean valor:

<TabbedPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
android:TabbedPage.OffscreenPageLimit="2"
android:TabbedPage.IsSwipePagingEnabled="true">
...
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

On<Android>().SetOffscreenPageLimit(2)
.SetIsSwipePagingEnabled(true);

El TabbedPage.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. [
TabbedPage.SetIsSwipePagingEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
TabbedPage. SetIsSwipePagingEnabled ( :::no-loc(Xamarin.Forms)::: . BindableObject, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se usa para habilitar el
deslizamiento entre las páginas de un TabbedPage . Además, la TabbedPage clase del espacio de
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific nombres también tiene [ EnableSwipePaging ]
(XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. TabbedPage. EnableSwipePaging ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
Android, :::no-loc(Xamarin.Forms)::: . TabbedPage})) que habilita este método específico de la plataforma y un [
DisableSwipePaging ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. TabbedPage.
DisableSwipePaging ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . TabbedPage})) que deshabilita este específico de la
plataforma. La TabbedPage.OffscreenPageLimit propiedad adjunta y [ SetOffscreenPageLimit ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. TabbedPage. SetOffscreenPageLimit ( :::no-
loc(Xamarin.Forms)::: . BindableObject, System. Int32)), se usan para establecer el número de páginas que deben
conservarse en un estado de inactividad en cualquier lado de la página actual.
El resultado es que está habilitada la paginación de deslizamiento a través de las páginas que muestra un
TabbedPage :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Animaciones de transición de páginas de
TabbedPage en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para deshabilitar animaciones de transición al navegar por las páginas,
ya sea mediante programación o cuando se usa la barra de pestañas, en TabbedPage . Se consume en XAML
estableciendo la TabbedPage.IsSmoothScrollEnabled propiedad Bindable en false :

<TabbedPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
android:TabbedPage.IsSmoothScrollEnabled="false">
...
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

On<Android>().SetIsSmoothScrollEnabled(false);

El TabbedPage.On<Android>método especifica que este específico de la plataforma solo se ejecutará en Android. El


TabbedPage.SetIsSmoothScrollEnabled método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se utiliza para controlar
si se mostrarán animaciones de transición al navegar entre las páginas de un TabbedPage . Además, la TabbedPage
clase del espacio de :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific nombres también tiene
los siguientes métodos:
IsSmoothScrollEnabled , que se usa para recuperar si se mostrarán animaciones de transición al navegar entre
las páginas de un TabbedPage .
EnableSmoothScroll , que se usa para habilitar animaciones de transición al navegar entre las páginas de un
TabbedPage .
DisableSmoothScroll , que se usa para deshabilitar las animaciones de transición al navegar entre las páginas de
un TabbedPage .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Selección de ubicación y color de la barra de
herramientas TabbedPage en Android
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo

IMPORTANT
Las características específicas de la plataforma que establecen el color de la barra de herramientas en un TabbedPage están
ahora obsoletas y se han reemplazado por las SelectedTabColor UnselectedTabColor propiedades y. Para obtener más
información, vea crear un TabbedPage.

Estas características específicas de la plataforma se usan para establecer la posición y el color de la barra de
herramientas en un TabbedPage . Se consumen en XAML estableciendo la TabbedPage.ToolbarPlacement propiedad
adjunta en un valor de la ToolbarPlacement enumeración y TabbedPage.BarItemColor y las
TabbedPage.BarSelectedItemColor propiedades adjuntas a un Color :

<TabbedPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
android:TabbedPage.ToolbarPlacement="Bottom"
android:TabbedPage.BarItemColor="Black"
android:TabbedPage.BarSelectedItemColor="Red">
...
</TabbedPage>

Como alternativa, se pueden usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

On<Android>().SetToolbarPlacement(ToolbarPlacement.Bottom)
.SetBarItemColor(Color.Black)
.SetBarSelectedItemColor(Color.Red);

El TabbedPage.On<Android> método especifica que estas características específicas de la plataforma solo se


ejecutarán en Android. [ TabbedPage.SetToolbarPlacement ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. AndroidSpecific. TabbedPage. SetToolbarPlacement ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . TabbedPage}, :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
ToolbarPlacement)), en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres,
se usa para establecer la posición de la barra de herramientas en un TabbedPage , con la ToolbarPlacement
enumeración que proporciona los valores siguientes:
Default : indica que la barra de herramientas se coloca en la ubicación predeterminada de la página. Esta es la
parte superior de la página en teléfonos y la parte inferior de la página en otras expresiones de dispositivo.
Top : indica que la barra de herramientas se coloca en la parte superior de la página.
Bottom : indica que la barra de herramientas se coloca en la parte inferior de la página.
Además, [ TabbedPage.SetBarItemColor ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
TabbedPage. SetBarItemColor ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . TabbedPage}, :::no-
loc(Xamarin.Forms)::: . Color)) y [ TabbedPage.SetBarSelectedItemColor ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. AndroidSpecific. TabbedPage. SetBarSelectedItemColor ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . TabbedPage}, :::no-loc(Xamarin.Forms)::: . Color)) para establecer el color de los elementos
de la barra de herramientas y los elementos de la barra de herramientas seleccionados, respectivamente.

NOTE
[ GetToolbarPlacement ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. TabbedPage.
GetToolbarPlacement ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . TabbedPage})), [ GetBarItemColor ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. TabbedPage. GetBarItemColor ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: .
TabbedPage})) y [ GetBarSelectedItemColor ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
TabbedPage. GetBarSelectedItemColor ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . TabbedPage})). los métodos se pueden
usar para recuperar la posición y el color de la TabbedPage barra de herramientas.

El resultado es que la ubicación de la barra de herramientas, el color de los elementos de la barra de herramientas
y el color del elemento de la barra de herramientas seleccionado se pueden establecer en un TabbedPage :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Acciones de contexto ViewCell en Android
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
De forma predeterminada :::no-loc(Xamarin.Forms)::: , en 4,3, cuando un ViewCell en una aplicación de Android
define acciones de contexto para cada elemento en ListView , el menú acciones de contexto se actualiza cuando
se cambia el elemento seleccionado en el ListView . Sin embargo, en las versiones anteriores del :::no-
loc(Xamarin.Forms)::: menú acciones de contexto no se actualizaba y este comportamiento se denomina ViewCell
modo heredado. Este modo heredado puede producir un comportamiento incorrecto si ListView usa un
DataTemplateSelector para establecer su a ItemTemplate partir de DataTemplate objetos que definen diferentes
acciones de contexto.
Esta opción específica de la plataforma Android habilita el ViewCell modo heredado del menú acciones de
contexto, por compatibilidad con versiones anteriores, para que el menú acciones de contexto no se actualice
cuando el elemento seleccionado en un ListView cambie. Se consume en XAML estableciendo la
ViewCell.IsContextActionsLegacyModeEnabled propiedad Bindable en true :

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell android:ViewCell.IsContextActionsLegacyModeEnabled="true">
<ViewCell.ContextActions>
<MenuItem Text="{Binding Item1Text}" />
<MenuItem Text="{Binding Item2Text}" />
</ViewCell.ContextActions>
<Label Text="{Binding Text}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

viewCell.On<Android>().SetIsContextActionsLegacyModeEnabled(true);

El ViewCell.On<Android>método especifica que este específico de la plataforma solo se ejecutará en Android. El


ViewCell.SetIsContextActionsLegacyModeEnabled método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se usa para habilitar el
ViewCell modo heredado del menú acciones de contexto, de modo que el menú acciones de contexto no se
actualice cuando el elemento seleccionado en un ListView cambie. Además, se
ViewCell.GetIsContextActionsLegacyModeEnabled puede usar el método para devolver si está habilitado el modo
heredado de acciones de contexto.
Las siguientes capturas de pantallas muestran ViewCell las acciones de contexto modo heredado habilitado:

En este modo, los elementos de menú de acción de contexto mostrados son idénticos para la celda 1 y la celda 2, a
pesar de que se estén definiendo distintos elementos de menú contextual para la celda 2.
Las capturas de pantallas siguientes muestran las ViewCell acciones de contexto modo heredado deshabilitado,
que es el :::no-loc(Xamarin.Forms)::: comportamiento predeterminado:

En este modo, se muestran los elementos de menú de acción de contexto correctos para las celdas 1 y 2.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Elevación de VisualElement en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma Android específica se usa para controlar la elevación, o el orden Z, de los elementos visuales en las
aplicaciones que tienen como destino la API 21 o posterior. La elevación de un elemento visual determina su orden
de dibujo, con elementos visuales con valores Z más altos occluding elementos visuales con valores Z inferiores.
Se consume en XAML estableciendo la VisualElement.Elevation propiedad adjunta en un boolean valor:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
Title="Elevation">
<StackLayout>
<Grid>
<Button Text="Button Beneath BoxView" />
<BoxView Color="Red" Opacity="0.2" HeightRequest="50" />
</Grid>
<Grid Margin="0,20,0,0">
<Button Text="Button Above BoxView - Click Me" android:VisualElement.Elevation="10"/>
<BoxView Color="Red" Opacity="0.2" HeightRequest="50" />
</Grid>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:


using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

public class AndroidElevationPageCS : ContentPage


{
public AndroidElevationPageCS()
{
...
var aboveButton = new Button { Text = "Button Above BoxView - Click Me" };
aboveButton.On<Android>().SetElevation(10);

Content = new StackLayout


{
Children =
{
new Grid
{
Children =
{
new Button { Text = "Button Beneath BoxView" },
new BoxView { Color = Color.Red, Opacity = 0.2, HeightRequest = 50 }
}
},
new Grid
{
Margin = new Thickness(0,20,0,0),
Children =
{
aboveButton,
new BoxView { Color = Color.Red, Opacity = 0.2, HeightRequest = 50 }
}
}
}
};
}
}

El Button.On<Android>método especifica que este específico de la plataforma solo se ejecutará en Android. El


VisualElement.SetElevation método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific
espacio de nombres, se usa para establecer la elevación del elemento visual en un valor que acepta valores NULL
float . Además, VisualElement.GetElevation se puede usar el método para recuperar el valor de elevación de un
elemento visual.
El resultado es que la elevación de los elementos visuales se puede controlar para que los elementos visuales con
valores Z más altos tapaba elementos visuales con valores Z inferiores. Por lo tanto, en este ejemplo, el segundo
Button se representa encima de BoxView porque tiene un valor de elevación superior:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Modo de color heredado de VisualElement en
Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Algunas de las :::no-loc(Xamarin.Forms)::: vistas presentan un modo de color heredado. En este modo, cuando la
IsEnabled propiedad de la vista está establecida en false , la vista invalidará los colores establecidos por el
usuario con los colores nativos predeterminados para el Estado deshabilitado. Por compatibilidad con versiones
anteriores, este modo de color heredado sigue siendo el comportamiento predeterminado para las vistas
admitidas.
Este modo específico de la plataforma Android deshabilita este modo de color heredado, de modo que los colores
establecidos en una vista por parte del usuario permanecen incluso cuando la vista está deshabilitada. Se consume
en XAML estableciendo la VisualElement.IsLegacyColorModeEnabled propiedad adjunta en false :

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
...
<Button Text="Button"
TextColor="Blue"
BackgroundColor="Bisque"
android:VisualElement.IsLegacyColorModeEnabled="False" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

_legacyColorModeDisabledButton.On<Android>().SetIsLegacyColorModeEnabled(false);

El VisualElement.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android.
[ VisualElement.SetIsLegacyColorModeEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
AndroidSpecific. VisualElement. SetIsLegacyColorModeEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-
loc(Xamarin.Forms)::: . VisualElement}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se utiliza para controlar
si el modo de color heredado está deshabilitado. Además, [ VisualElement.GetIsLegacyColorModeEnabled ] (XREF:
:::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. VisualElement. GetIsLegacyColorModeEnabled
( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
Android, :::no-loc(Xamarin.Forms)::: . VisualElement})) que se puede usar para devolver si el modo de color
heredado está deshabilitado.
El resultado es que el modo de color heredado se puede deshabilitar, de modo que los colores establecidos en una
vista por parte del usuario permanezcan incluso cuando la vista esté deshabilitada:
NOTE
Al establecer VisualStateGroup en una vista, el modo de color heredado se omite por completo. Para obtener más
información sobre los Estados visuales, consulte el :::no-loc(Xamarin.Forms)::: Visual State Manager.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Contenido mixto de WebView en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma Android determina si WebView puede mostrar contenido mixto en
aplicaciones destinadas a la API 21 o posterior. El contenido mixto es contenido que se carga inicialmente a través
de una conexión HTTPS, pero que carga recursos (como imágenes, audio, vídeo, hojas de estilos y scripts) a través
de una conexión HTTP. Se consume en XAML estableciendo la WebView.MixedContentMode propiedad adjunta en un
valor de la MixedContentHandling enumeración:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<WebView ... android:WebView.MixedContentMode="AlwaysAllow" />
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

webView.On<Android>().SetMixedContentMode(MixedContentHandling.AlwaysAllow);

El WebView.On<Android> método especifica que este específico de la plataforma solo se ejecutará en Android. [
WebView.SetMixedContentMode ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific.
Webview. SetMixedContentMode ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Android, :::no-loc(Xamarin.Forms)::: . WebView}, :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. AndroidSpecific. MixedContentHandling)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de nombres, se utiliza para controlar
si se puede mostrar contenido mixto, con la MixedContentHandling enumeración que proporciona tres valores
posibles:
AlwaysAllow : indica que WebView permitirá a un origen https cargar contenido de un origen http.
NeverAllow : indica que WebView no permitirá que un origen https cargue contenido de un origen http.
CompatibilityMode : indica que el WebView intentará ser compatible con el enfoque del explorador Web del
dispositivo más reciente. Es posible que se pueda cargar algún contenido HTTP mediante un origen HTTPS y
que se bloqueen otros tipos de contenido. Los tipos de contenido bloqueados o permitidos pueden cambiar con
cada versión del sistema operativo.
El resultado es que un MixedContentHandling valor especificado se aplica a WebView , que controla si se puede
mostrar contenido mixto:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
Zoom de WebView en Android
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma Android permite el aumento del zoom y un control de zoom en un WebView . Se
consume en XAML estableciendo las WebView.EnableZoomControls WebView.DisplayZoomControls propiedades
enlazables y en boolean valores:

<ContentPage ...
xmlns:android="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<WebView Source="https://www.xamarin.com"
android:WebView.EnableZoomControls="true"
android:WebView.DisplayZoomControls="true" />
</ContentPage>

La WebView.EnableZoomControls propiedad enlazable controla si está habilitada la opción de reducción del zoom en
la WebView , y la WebView.DisplayZoomControls propiedad enlazable controla si los controles de zoom están
superpuestos en WebView .
Como alternativa, se puede usar la plataforma específica de C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific;
...

webView.On<Android>()
.EnableZoomControls(true)
.DisplayZoomControls(true);

El WebView.On<Android>método especifica que este específico de la plataforma solo se ejecutará en Android. El


WebView.EnableZoomControls método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific
espacio de nombres, se utiliza para controlar si está habilitada la opción de alejar zoom en WebView . El
WebView.DisplayZoomControls método, en el mismo espacio de nombres, se utiliza para controlar si los controles de
zoom están superpuestos en WebView . Además, los WebView.ZoomControlsEnabled métodos y
WebView.ZoomControlsDisplayed se pueden usar para devolver si los controles de zoom y zoom están habilitados,
respectivamente.
El resultado es que se puede habilitar el aumento del zoom en un WebView control y los controles de zoom se
pueden superponer en el WebView :
IMPORTANT
Los controles de zoom deben estar habilitados y mostrados, a través de las propiedades o métodos enlazables
correspondientes, para superponerse en un WebView .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de AndroidSpecific
AndroidSpecific. AppCompat API
características de la plataforma iOS
enXamarin.Forms
18/12/2020 • 8 minutes to read • Edit Online

El desarrollo Xamarin.Forms de aplicaciones para iOS requiere Visual Studio. La Página plataformas compatibles
contiene más información sobre los requisitos previos.

Características específicas de las plataformas


Las características específicas de la plataforma permiten consumir funcionalidad que solo está disponible en una
plataforma específica, sin necesidad de implementar representadores o efectos personalizados.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms vistas, páginas y
diseños en iOS:
Desenfocar la compatibilidad con Any VisualElement . Para obtener más información, consulte VisualElement
Blur in iOS.
Deshabilitar el modo de color heredado en un compatible VisualElement . Para obtener más información,
consulte el modo de color heredado VisualElement en iOS.
Habilitar una sombra paralela en un VisualElement . Para obtener más información, consulte VisualElement
Drop Shadows on IOS.
Habilitar un VisualElement objeto para que se convierta en el primer respondedor para tocar eventos. Para
obtener más información, consulte VisualElement First respondedor.
Se proporciona la siguiente funcionalidad específica de la plataforma para las Xamarin.Forms vistas en iOS:
Establecer el Cell color de fondo. Para obtener más información, vea color de fondo de celda en iOS.
Controlar cuándo se realiza la selección de elementos en DatePicker . Para obtener más información, consulte
selección de elementos del DatePicker en iOS.
Asegurarse de que el texto introducido quepa en un ajustando Entry el tamaño de la fuente. Para obtener
más información, vea tamaño de fuente de entrada en iOS.
Establecer el color del cursor en un Entry . Para obtener más información, vea color cursor entry in iOS.
Controlar si ListView las celdas de encabezado flotan durante el desplazamiento. Para obtener más
información, consulte estilo de encabezado de grupo de ListView en iOS.
Controlar si se deshabilitan las animaciones de filas cuando ListView se actualiza la colección de elementos.
Para obtener más información, consulte animaciones de filas de ListView en iOS.
Establecer el estilo del separador en un ListView . Para obtener más información, consulte estilo de separador
de ListView en iOS.
Controlar cuándo se realiza la selección de elementos en Picker . Para obtener más información, consulte
selección de elementos de selector en iOS.
Controlar si un SearchBar tiene un fondo. Para obtener más información, consulte estilo de barra en iOS.
Habilitar la Slider.Value propiedad para establecerla punteando en una posición de la Slider barra, en lugar
de tener que arrastrar el Slider control. Para obtener más información, consulte Slide Thumb TAP on IOS.
Control de la transición que se utiliza al abrir SwipeView . Para obtener más información, consulte el modo de
transición de SwipeView.
Controlar cuándo se realiza la selección de elementos en TimePicker . Para obtener más información, vea
selección de elementos de TimePicker en iOS.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms las páginas de iOS:
Controlar si la página de detalles de una MasterDetailPage tiene una sombra aplicada, al revelar la página
maestra. Para obtener más información, consulte MasterDetailPage Shadow.
Ocultar el separador de barra de navegación en un NavigationPage . Para obtener más información, consulte
separador de la barra NavigationPage en iOS.
Controlar si la barra de navegación es traslúcida. Para obtener más información, consulte barra de navegación
translucidez en iOS.
Controlar si el color del texto de la barra de estado en un NavigationPage se ajusta para que coincida con la
luminosidad de la barra de navegación. Para obtener más información, consulte el modo de color de texto de
la barra NavigationPage en iOS.
Controlar si el título de la página se muestra como un título grande en la barra de navegación de la página.
Para obtener más información, vea títulos de páginas grandes en iOS.
Establecer la visibilidad del indicador de inicio en un Page . Para obtener más información, consulte visibilidad
del indicador de inicio en iOS.
Establecer la visibilidad de la barra de estado en un Page . Para obtener más información, vea visibilidad de la
barra de estado de la página en iOS.
Asegurarse de que el contenido de la página se coloca en un área de la pantalla que es segura para todos los
dispositivos iOS. Para obtener más información, consulte Guía de diseño de área segura en iOS.
Establecer el estilo de presentación de las páginas modales. Para obtener más información, consulte el estilo de
presentación de página modal.
Establecer el modo translucidez de la barra de pestañas en un TabbedPage . Para obtener más información, vea
TabbedPage translúcida con tabulación en iOS.
Se proporciona la siguiente funcionalidad específica de la plataforma para los Xamarin.Forms diseños en iOS:
Controlar si un ScrollView control controla un gesto táctil o lo pasa a su contenido. Para obtener más
información, consulte ScrollView Content toques in iOS.
Se proporciona la siguiente funcionalidad específica de la plataforma para la Xamarin.Forms Application clase en
iOS:
Deshabilitar el ajuste de escala de accesibilidad para tamaños de fuente con nombre. Para obtener más
información, vea escalado de accesibilidad para tamaños de fuente con nombre en iOS.
Habilitar las actualizaciones de diseño y representación de controles que se van a realizar en el subproceso
principal. Para obtener más información, vea actualizaciones principales del control de subprocesos en iOS.
Habilitar un PanGestureRecognizer en una vista de desplazamiento para capturar y compartir el gesto de
panorámica con la vista de desplazamiento. Para obtener más información, consulte reconocimiento
simultáneo de gestos de pan en iOS.

formato específico de iOS


Xamarin.Formspermite establecer los colores y estilos de la interfaz de usuario multiplataforma, pero hay otras
opciones para configurar el tema de iOS con las API de la plataforma en el proyecto de iOS.
Obtenga más información sobre cómo dar formato a la interfaz de usuario con las API específicas de iOS, como la
configuración de info. plist y la UIAppearance API.
Otras características de iOS
Con los representadores personalizados, DependencyServicey MessagingCenter, es posible incorporar una
amplia variedad de funciones nativas en Xamarin.Forms aplicaciones para iOS.
Escalado de accesibilidad para tamaños de fuente
con nombre en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma iOS deshabilita el escalado de accesibilidad para tamaños de fuente con nombre.
Se consume en XAML estableciendo la Application.EnableAccessibilityScalingForNamedFontSizes propiedad
Bindable en false :

<Application ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Application.EnableAccessibilityScalingForNamedFontSizes="false">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

:::no-loc(Xamarin.Forms):::.Application.Current.On<iOS>
().SetEnableAccessibilityScalingForNamedFontSizes(false);

El Application.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Application.SetEnableAccessibilityScalingForNamedFontSizes método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se usa para deshabilitar los
tamaños de fuente con nombre que se escalan mediante la configuración de accesibilidad de iOS. Además, se
Application.GetEnableAccessibilityScalingForNamedFontSizes puede usar el método para devolver si los tamaños
de fuente con nombre se escalan mediante la configuración de accesibilidad de iOS.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Color de fondo de celda en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este valor específico de la plataforma iOS establece el color de fondo predeterminado de Cell las instancias. Se
consume en XAML estableciendo la Cell.DefaultBackgroundColor propiedad Bindable en Color :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ListView ItemsSource="{Binding GroupedEmployees}"
IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell ios:Cell.DefaultBackgroundColor="Teal">
<Label Margin="10,10"
Text="{Binding Key}"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

var viewCell = new ViewCell { View = ... };


viewCell.On<iOS>().SetDefaultBackgroundColor(Color.Teal);

El ListView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Cell.SetDefaultBackgroundColor método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, establece el color de fondo de la celda en un especificado Color . Además,
Cell.DefaultBackgroundColor se puede utilizar el método para recuperar el color de fondo de la celda actual.

El resultado es que el color de fondo de un Cell se puede establecer en un específico Color :


Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Selección de elementos del DatePicker en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Estos controles específicos de la plataforma iOS cuando se selecciona el elemento en un objeto DatePicker , lo
que permite al usuario especificar la selección de elementos que se produce al examinar elementos del control o
solo una vez que se presiona el botón listo . Se consume en XAML estableciendo la DatePicker.UpdateMode
propiedad adjunta en un valor de la UpdateMode enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<DatePicker MinimumDate="01/01/2020"
MaximumDate="12/31/2020"
ios:DatePicker.UpdateMode="WhenFinished" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

datePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);

El DatePicker.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


DatePicker.SetUpdateMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio
de nombres, se utiliza para controlar cuándo se produce la selección del elemento, con la UpdateMode enumeración
que proporciona dos valores posibles:
Immediately : la selección de elementos se produce cuando el usuario examina los elementos de DatePicker .
Éste es el comportamiento predeterminado en :::no-loc(Xamarin.Forms)::: .
WhenFinished : la selección de elementos solo se produce una vez que el usuario ha presionado el botón Done
en el DatePicker .
Además, el SetUpdateModemétodo se puede utilizar para alternar los valores de enumeración llamando al
UpdateMode método, que devuelve la actual UpdateMode :

switch (datePicker.On<iOS>().UpdateMode())
{
case UpdateMode.Immediately:
datePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
datePicker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
}

El resultado es que UpdateMode se aplica un especificado a DatePicker , que controla cuándo se produce la
selección de elementos:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Color del cursor de entrada en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS establece el color del cursor de un Entry en un color especificado. Se consume
en XAML estableciendo la Entry.CursorColor propiedad Bindable en Color :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<Entry ... ios:Entry.CursorColor="LimeGreen" />
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

var entry = new :::no-loc(Xamarin.Forms):::.Entry();


entry.On<iOS>().SetCursorColor(Color.LimeGreen);

El Entry.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
Entry.SetCursorColor ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. entry.
SetCursorColor ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Entry}, :::no-loc(Xamarin.Forms)::: . Color)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, establece el color del cursor
en un especificado Color . Además, [ Entry.GetCursorColor ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. entry. GetCursorColor ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. Entry})) que se puede usar para recuperar el color actual del cursor.
El resultado es que el color del cursor de un Entry se puede establecer en un específico Color :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Tamaño de fuente de entrada en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para escalar el tamaño de fuente de un Entry para asegurarse de que el
texto introducido quepa en el control. Se consume en XAML estableciendo la Entry.AdjustsFontSizeToFitWidth
propiedad adjunta en un boolean valor:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
<StackLayout Margin="20">
<Entry x:Name="entry"
Placeholder="Enter text here to see the font size change"
FontSize="22"
ios:Entry.AdjustsFontSizeToFitWidth="true" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

entry.On<iOS>().EnableAdjustsFontSizeToFitWidth();

El Entry.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
Entry.EnableAdjustsFontSizeToFitWidth ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
entry. EnableAdjustsFontSizeToFitWidth ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Entry})), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se usa para escalar el tamaño
de fuente del texto de entrada para asegurarse de que quepa en el Entry . Además, la Entry clase del espacio de
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific nombres también tiene [
DisableAdjustsFontSizeToFitWidth ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. entry.
DisableAdjustsFontSizeToFitWidth ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Entry})) que deshabilita este
específico de la plataforma y un [ SetAdjustsFontSizeToFitWidth ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. entry. SetAdjustsFontSizeToFitWidth ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. Entry}, System. Boolean)) que se puede usar para alternar el escalado de tamaño de fuente llamando a [
AdjustsFontSizeToFitWidth ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. entry.
AdjustsFontSizeToFitWidth ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms):::
. PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Entry})):

entry.On<iOS>().SetAdjustsFontSizeToFitWidth(!entry.On<iOS>().AdjustsFontSizeToFitWidth());

El resultado es que se escala el tamaño de fuente del Entry control para asegurarse de que el texto introducido
quepa en el control:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Agregar formato específico de iOS
18/12/2020 • 4 minutes to read • Edit Online

Una manera de establecer el formato específico de iOS es crear un representador personalizado para un control y
establecer estilos y colores específicos de la plataforma para cada plataforma.
Otras opciones para controlar el Xamarin.Forms aspecto de la aplicación de iOS incluyen:
Configurar las opciones de presentación en info. plist
Establecer estilos de control a través de la UIAppearance API
Estas alternativas se describen a continuación.

Personalización de info. plist


El archivo info. plist le permite configurar algunos aspectos de renderering de una aplicación de iOS, como, por
ejemplo, cómo se muestra la barra de estado (y si).
Por ejemplo, el ejemplo todo usa el código siguiente para establecer el color de la barra de navegación y el color
del texto en todas las plataformas:

var nav = new NavigationPage (new TodoListPage ());


nav.BarBackgroundColor = Color.FromHex("91CA47");
nav.BarTextColor = Color.White;

El resultado se muestra en el siguiente fragmento de código de pantalla. Tenga en cuenta que los elementos de la
barra de estado son negros (esto no se puede establecer en Xamarin.Forms porque es una característica específica
de la plataforma).

Idealmente, la barra de estado también podría ser algo que podamos realizar directamente en el proyecto de iOS.
Agregue las siguientes entradas al archivo info. plist para forzar que la barra de estado esté en blanco:

o bien, edite el archivo info. plist correspondiente directamente para incluir:

<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

Ahora, cuando se ejecuta la aplicación, la barra de navegación es verde y su texto es blanco (debido al
Xamarin.Forms formato) y el texto de la barra de estado también está en blanco gracias a la configuración
específica de iOS:
API de UIAppearance
La UIAppearance API se puede usar para establecer propiedades visuales en muchos controles de iOS sin tener que
crear un representador personalizado.
La adición de una sola línea de código al método AppDelegate.CS FinishedLaunching puede aplicar estilo a todos
los controles de un tipo determinado mediante su Appearance propiedad. El código siguiente contiene dos
ejemplos: aplicar estilos a la barra de pestañas y control de modificador de forma global:
AppDelegate.CS en el proyecto de iOS

public override bool FinishedLaunching (UIApplication app, NSDictionary options)


{
// tab bar
UITabBar.Appearance.SelectedImageTintColor = UIColor.FromRGB(0x91, 0xCA, 0x47); // green
// switch
UISwitch.Appearance.OnTintColor = UIColor.FromRGB(0x91, 0xCA, 0x47); // green
// required Xamarin.Forms code
Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}

UITabBar
De forma predeterminada, el icono de la barra de pestañas seleccionada en un TabbedPage sería azul:

Para cambiar este comportamiento, establezca la UITabBar.Appearance propiedad:

UITabBar.Appearance.SelectedImageTintColor = UIColor.FromRGB(0x91, 0xCA, 0x47); // green

Esto hace que la pestaña seleccionada sea verde:

El uso de esta API le permite personalizar la apariencia del Xamarin.Forms TabbedPage en iOS con muy poco
código. Consulte la receta personalización de pestañas para obtener más información sobre el uso de un
representador personalizado para establecer una fuente específica para la pestaña.
UISwitch
El Switch control es otro ejemplo en el que se puede aplicar un estilo sencillo:

UISwitch.Appearance.OnTintColor = UIColor.FromRGB(0x91, 0xCA, 0x47); // green

Estas dos capturas de pantalla muestran el UISwitch control predeterminado a la izquierda y la versión
personalizada (valor Appearance ) de la derecha en el ejemplo de todo:
Otros controles
Muchos controles de interfaz de usuario de iOS pueden tener sus colores predeterminados y otros atributos
establecidos mediante la UIAppearance API.

Vínculos relacionados
UIAppearance
Personalizar pestañas
Estilo de presentación de página modal en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para establecer el estilo de presentación de una página modal y, además,
se puede usar para mostrar páginas modales con fondos transparentes. Se consume en XAML estableciendo la
Page.ModalPresentationStyle propiedad Bindable en un UIModalPresentationStyle valor de enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Page.ModalPresentationStyle="OverFullScreen">
...
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

public class iOSModalFormSheetPageCS : ContentPage


{
public iOSModalFormSheetPageCS()
{
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.OverFullScreen);
...
}
}

El Page.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Page.SetModalPresentationStyle método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se usa para establecer el estilo de Presentación modal en un Page especificando uno de los
siguientes UIModalPresentationStyle valores de enumeración:
FullScreen , que establece el estilo de Presentación modal para abarcar toda la pantalla. De forma
predeterminada, las páginas modales se muestran con este estilo de presentación.
FormSheet , que establece el estilo de Presentación modal en centrada en y menor que la pantalla.
Automatic , que establece el estilo de Presentación modal en el valor predeterminado elegido por el sistema.
Para la mayoría de los controladores de vista, UIKit asigna este a UIModalPresentationStyle.PageSheet , pero
algunos controladores de vistas del sistema pueden asignarlo a un estilo diferente.
OverFullScreen , que establece el estilo de Presentación modal para cubrir la pantalla.
PageSheet , que establece el estilo de Presentación modal para cubrir el contenido subyacente.

Además, GetModalPresentationStylese puede usar el método para recuperar el valor actual de la


UIModalPresentationStyle enumeración que se aplica a Page .
El resultado es que se puede establecer el estilo de Presentación modal en un Page :
NOTE
Las páginas que usan este tipo específico de la plataforma para establecer el estilo de Presentación modal deben usar la
navegación modal. Para obtener más información, vea :::no-loc(Xamarin.Forms)::: páginas modales.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Títulos de páginas grandes en iOS
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para mostrar el título de la página como un título grande en la barra de
navegación de un NavigationPage , para los dispositivos que usan iOS 11 o superior. Un título grande se alinea a
la izquierda y usa una fuente mayor, y pasa a un título estándar a medida que el usuario comienza a desplazar el
contenido, de modo que el estado real de la pantalla se utiliza de forma eficaz. Sin embargo, en la orientación
horizontal, el título volverá al centro de la barra de navegación para optimizar el diseño del contenido. Se consume
en XAML estableciendo la NavigationPage.PrefersLargeTitles propiedad adjunta en un boolean valor:

<NavigationPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
...
ios:NavigationPage.PrefersLargeTitles="true">
...
</NavigationPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

var navigationPage = new :::no-loc(Xamarin.Forms):::.NavigationPage(new iOSLargeTitlePageCS());


navigationPage.On<iOS>().SetPrefersLargeTitles(true);

El NavigationPage.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


NavigationPage.SetPrefersLargeTitle método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, controla si los títulos
grandes están habilitados.
Siempre que los títulos grandes estén habilitados en NavigationPage , todas las páginas de la pila de navegación
mostrarán títulos grandes. Este comportamiento se puede invalidar en las páginas si se establece la
Page.LargeTitleDisplay propiedad adjunta en un valor de la LargeTitleDisplayMode enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
Title="Large Title"
ios:Page.LargeTitleDisplay="Never">
...
</ContentPage>

Como alternativa, el comportamiento de la página se puede invalidar desde C# con la API fluida:
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

public class iOSLargeTitlePageCS : ContentPage


{
public iOSLargeTitlePageCS(ICommand restore)
{
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Never);
...
}
...
}

El Page.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Page.SetLargeTitleDisplay método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio
de nombres, controla el comportamiento del título grande en Page , con la LargeTitleDisplayMode enumeración
que proporciona tres valores posibles:
Always : fuerce la barra de navegación y el tamaño de fuente para usar el formato grande.
Automatic : Use el mismo estilo (grande o pequeño) que el elemento anterior en la pila de navegación.
Never : fuerza el uso de la barra de navegación normal de formato pequeño.

Además, el SetLargeTitleDisplay método se puede utilizar para alternar los valores de enumeración llamando al
LargeTitleDisplay método, que devuelve la actual LargeTitleDisplayMode :

switch (On<iOS>().LargeTitleDisplay())
{
case LargeTitleDisplayMode.Always:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Automatic);
break;
case LargeTitleDisplayMode.Automatic:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Never);
break;
case LargeTitleDisplayMode.Never:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Always);
break;
}

El resultado es que LargeTitleDisplayMode se aplica un especificado a Page , que controla el comportamiento del
título grande:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Estilo de encabezado de grupo de ListView en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma iOS determina si ListView las celdas de encabezado flotan durante el
desplazamiento. Se consume en XAML estableciendo la ListView.GroupHeaderStyle propiedad Bindable en un valor
de la GroupHeaderStyle enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ListView ... ios:ListView.GroupHeaderStyle="Grouped">
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

listView.On<iOS>().SetGroupHeaderStyle(GroupHeaderStyle.Grouped);

El ListView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


ListView.SetGroupHeaderStyle método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar si ListView las celdas de encabezado flotan durante el
desplazamiento. La GroupHeaderStyle enumeración proporciona dos valores posibles:
Plain : indica que las celdas de encabezado flotan cuando ListView se desplaza el valor (predeterminado).
Grouped : indica que las celdas de encabezado no flotan cuando ListView se desplaza.

Además, el ListView.GetGroupHeaderStyle método se puede utilizar para devolver el GroupHeaderStyle que se


aplica a ListView .
El resultado es que un GroupHeaderStyle valor especificado se aplica a ListView , que controla si las celdas de
encabezado flotan durante el desplazamiento:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Animaciones de filas de ListView en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma iOS indica si se deshabilitan las animaciones de filas cuando ListView se
actualiza la colección de elementos. Se consume en XAML estableciendo la ListView.RowAnimationsEnabled
propiedad Bindable en false :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ListView ... ios:ListView.RowAnimationsEnabled="false">
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

listView.On<iOS>().SetRowAnimationsEnabled(false);

El ListView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


ListView.SetRowAnimationsEnabled método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar si se deshabilitan las animaciones de filas cuando ListView se
actualiza la colección de elementos. Además, se ListView.GetRowAnimationsEnabled puede utilizar el método para
devolver si las animaciones de fila están deshabilitadas en ListView .

NOTE
ListView las animaciones de fila están habilitadas de forma predeterminada. Por lo tanto, una animación se produce
cuando se inserta una nueva fila en ListView .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Estilo de separador de ListView en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma iOS determina si el separador entre las celdas de un ListView usa el
ancho completo de ListView . Se consume en XAML estableciendo la ListView.SeparatorStyle propiedad adjunta
en un valor de la SeparatorStyle enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<ListView ... ios:ListView.SeparatorStyle="FullWidth">
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

listView.On<iOS>().SetSeparatorStyle(SeparatorStyle.FullWidth);

El ListView.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
ListView.SetSeparatorStyle ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. ListView.
SetSeparatorStyle ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . ListView}, :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. SeparatorStyle)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar si el
separador entre las celdas de ListView usa el ancho completo de ListView , con la SeparatorStyle enumeración
que proporciona dos valores posibles:
Default : indica el comportamiento predeterminado del separador de iOS. Éste es el comportamiento
predeterminado en :::no-loc(Xamarin.Forms)::: .
FullWidth : indica que los separadores se dibujarán desde un borde de ListView al otro.

El resultado es que un SeparatorStyle valor especificado se aplica a ListView , que controla el ancho del
separador entre las celdas:
NOTE
Una vez que el estilo del separador se ha establecido en FullWidth , no se puede volver a cambiar a Default en tiempo
de ejecución.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Actualizaciones principales del control de
subprocesos en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma iOS permite realizar actualizaciones del diseño y la representación del control en el
subproceso principal, en lugar de realizarse en un subproceso en segundo plano. Rara vez es necesario, pero en
algunos casos puede impedir bloqueos. Su consumido en XAML estableciendo la
Application.HandleControlUpdatesOnMainThread propiedad enlazable en true :

<Application ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Application.HandleControlUpdatesOnMainThread="true">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

:::no-loc(Xamarin.Forms):::.Application.Current.On<iOS>().SetHandleControlUpdatesOnMainThread(true);

El Application.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. El
Application.SetHandleControlUpdatesOnMainThread método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar si se
realizan actualizaciones de representación y diseño del control en el subproceso principal, en lugar de realizarse en
un subproceso en segundo plano. Además, se Application.GetHandleControlUpdatesOnMainThread puede usar el
método para devolver si se realizan actualizaciones de representación y diseño del control en el subproceso
principal.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
MasterDetailPage sombra en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma determina si la página de detalles de una MasterDetailPage tiene una
sombra aplicada, al revelar la página maestra. Se consume en XAML estableciendo la
MasterDetailPage.ApplyShadow propiedad Bindable en true :

<MasterDetailPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:MasterDetailPage.ApplyShadow="true">
...
</MasterDetailPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

public class iOSMasterDetailPageCS : MasterDetailPage


{
public iOSMasterDetailPageCS(ICommand restore)
{
On<iOS>().SetApplyShadow(true);
// ...
}
}

El MasterDetailPage.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. El
MasterDetailPage.SetApplyShadow método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar si la página de detalles de una MasterDetailPage tiene una sombra
aplicada, al revelar la página maestra. Además, el GetApplyShadow método se puede utilizar para determinar si la
sombra se aplica a la página de detalles de un MasterDetailPage .
El resultado es que la página de detalles de un MasterDetailPage puede tener una sombra aplicada, al revelar la
página maestra:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Separador de barra NavigationPage en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma iOS oculta la línea de separación y la sombra que se encuentra en la parte inferior
de la barra de navegación en un NavigationPage . Se consume en XAML estableciendo la
NavigationPage.HideNavigationBarSeparator propiedad Bindable en false :

<NavigationPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:NavigationPage.HideNavigationBarSeparator="true">

</NavigationPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;

public class iOSTitleViewNavigationPageCS : :::no-loc(Xamarin.Forms):::.NavigationPage


{
public iOSTitleViewNavigationPageCS()
{
On<iOS>().SetHideNavigationBarSeparator(true);
}
}

El NavigationPage.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
NavigationPage.SetHideNavigationBarSeparator ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
iOSSpecific. NavigationPage. SetHideNavigationBarSeparator ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. NavigationPage}, System. Boolean)), en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar si el separador de la barra de navegación está oculto. Además, [
NavigationPage.HideNavigationBarSeparator ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
iOSSpecific. NavigationPage. HideNavigationBarSeparator ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. NavigationPage})) que se puede usar para devolver si el separador de la barra de navegación está oculto.
El resultado es que se puede ocultar el separador de la barra de navegación de un NavigationPage :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Modo de color de texto de la barra NavigationPage
en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma determina si el color del texto de la barra de estado en un NavigationPage
se ajusta para que coincida con la luminosidad de la barra de navegación. Se consume en XAML estableciendo la
NavigationPage.StatusBarTextColorMode propiedad adjunta en un valor de la StatusBarTextColorMode enumeración:

<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace::::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-
loc(Xamarin.Forms):::.Core"
x:Class="PlatformSpecifics.iOSStatusBarTextColorModePage">
<MasterDetailPage.Master>
<ContentPage Title="Master Page Title" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage BarBackgroundColor="Blue" BarTextColor="White"
ios:NavigationPage.StatusBarTextColorMode="MatchNavigationBarTextLuminosity">
<x:Arguments>
<ContentPage>
<Label Text="Slide the master page to see the status bar text color mode change." />
</ContentPage>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

IsPresentedChanged += (sender, e) =>


{
var mdp = sender as MasterDetailPage;
if (mdp.IsPresented)
((:::no-loc(Xamarin.Forms):::.NavigationPage)mdp.Detail)
.On<iOS>()
.SetStatusBarTextColorMode(StatusBarTextColorMode.DoNotAdjust);
else
((:::no-loc(Xamarin.Forms):::.NavigationPage)mdp.Detail)
.On<iOS>()
.SetStatusBarTextColorMode(StatusBarTextColorMode.MatchNavigationBarTextLuminosity);
};

El NavigationPage.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
NavigationPage.SetStatusBarTextColorMode ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
NavigationPage. SetStatusBarTextColorMode ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . NavigationPage}, :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. StatusBarTextColorMode)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres controla si el color del texto
de la barra de estado en NavigationPage se ajusta para que coincida con la luminosidad de la barra de navegación,
y la StatusBarTextColorMode enumeración proporciona dos valores posibles:
DoNotAdjust : indica que no se debe ajustar el color del texto de la barra de estado.
MatchNavigationBarTextLuminosity : indica que el color del texto de la barra de Estado debe coincidir con la
luminosidad de la barra de navegación.
Además, [ GetStatusBarTextColorMode ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
NavigationPage. GetStatusBarTextColorMode ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . NavigationPage})) que se puede usar
para recuperar el valor actual de la StatusBarTextColorMode enumeración que se aplica a NavigationPage .
El resultado es que el color del texto de la barra de estado de un NavigationPage se puede ajustar para que
coincida con la luminosidad de la barra de navegación. En este ejemplo, el color del texto de la barra de estado
cambia a medida que el usuario cambia entre las Master Detail páginas y de un MasterDetailPage :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
NavigationPage bar translucidez en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para cambiar la transparencia de la barra de navegación en un
NavigationPage y se utiliza en XAML estableciendo la NavigationPage.IsNavigationBarTranslucent propiedad
adjunta en un boolean valor:

<NavigationPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
BackgroundColor="Blue"
ios:NavigationPage.IsNavigationBarTranslucent="true">
...
</NavigationPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

(App.Current.MainPage as :::no-loc(Xamarin.Forms):::.NavigationPage).BackgroundColor = Color.Blue;


(App.Current.MainPage as :::no-loc(Xamarin.Forms):::.NavigationPage).On<iOS>
().EnableTranslucentNavigationBar();

El NavigationPage.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
NavigationPage.EnableTranslucentNavigationBar ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
iOSSpecific. NavigationPage. EnableTranslucentNavigationBar ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. NavigationPage})), en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se
usa para que la barra de navegación sea translúcida. Además, la NavigationPage clase del espacio de
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific nombres también tiene [
DisableTranslucentNavigationBar ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
NavigationPage. DisableTranslucentNavigationBar ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration {
:::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . NavigationPage})) que restaura
la barra de navegación a su estado predeterminado y a [ SetIsNavigationBarTranslucent ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. NavigationPage. SetIsNavigationBarTranslucent ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS,
:::no-loc(Xamarin.Forms)::: . NavigationPage}, System. Boolean)) que se puede usar para alternar la transparencia de
la barra de navegación llamando a [ IsNavigationBarTranslucent ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. NavigationPage. IsNavigationBarTranslucent ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. NavigationPage})):

(App.Current.MainPage as :::no-loc(Xamarin.Forms):::.NavigationPage)
.On<iOS>()
.SetIsNavigationBarTranslucent(!(App.Current.MainPage as :::no-loc(Xamarin.Forms):::.NavigationPage).On<iOS>
().IsNavigationBarTranslucent());
El resultado es que se puede cambiar la transparencia de la barra de navegación:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Visibilidad del indicador de inicio en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS establece la visibilidad del indicador de inicio en un Page . Se consume en XAML
estableciendo la Page.PrefersHomeIndicatorAutoHidden propiedad Bindable en boolean :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Page.PrefersHomeIndicatorAutoHidden="true">
...
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

On<iOS>().SetPrefersHomeIndicatorAutoHidden(true);

El Page.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. [


Page.SetPrefersHomeIndicatorAutoHidden ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
Page. SetPrefersHomeIndicatorAutoHidden ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Page}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres controla la visibilidad del
indicador de inicio. Además, [ Page.PrefersHomeIndicatorAutoHidden ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. Page. PrefersHomeIndicatorAutoHidden ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms):::
. Page})) que se puede usar para recuperar la visibilidad del indicador de inicio.
El resultado es que se puede controlar la visibilidad del indicador de inicio en un Page :

NOTE
Este específico de la plataforma se puede aplicar a los ContentPage MasterDetailPage objetos,, NavigationPage y
TabbedPage .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Visibilidad de la barra de estado de la página en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para establecer la visibilidad de la barra de estado en un Page e incluye la
capacidad de controlar el modo en que la barra de estado entra o sale de Page . Se consume en XAML
estableciendo la Page.PrefersStatusBarHidden propiedad adjunta en un valor de la StatusBarHiddenMode
enumeración y, opcionalmente, la Page.PreferredStatusBarUpdateAnimation propiedad adjunta en un valor de la
UIStatusBarAnimation enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Page.PrefersStatusBarHidden="True"
ios:Page.PreferredStatusBarUpdateAnimation="Fade">
...
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

On<iOS>().SetPrefersStatusBarHidden(StatusBarHiddenMode.True)
.SetPreferredStatusBarUpdateAnimation(UIStatusBarAnimation.Fade);

El Page.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Page.SetPrefersStatusBarHidden método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se usa para establecer la visibilidad de la barra de estado en un Page especificando uno de
los StatusBarHiddenMode valores de enumeración: Default , True o False . Los StatusBarHiddenMode.True
StatusBarHiddenMode.False valores y establecen la visibilidad de la barra de estado independientemente de la
orientación del dispositivo y el StatusBarHiddenMode.Default valor oculta la barra de estado en un entorno de
compactación vertical.
El resultado es que se puede establecer la visibilidad de la barra de estado en una Page :
NOTE
En TabbedPage , el valor de StatusBarHiddenMode enumeración especificado también actualizará la barra de estado en
todas las páginas secundarias. En todos los demás Page tipos derivados de, el StatusBarHiddenMode valor de
enumeración especificado solo actualizará la barra de estado en la página actual.

El Page.SetPreferredStatusBarUpdateAnimation método se usa para establecer el modo en que la barra de estado


entra o sale Page especificando uno de los UIStatusBarAnimation valores de enumeración: None , Fade o Slide .
Si Fade Slide se especifica el valor de enumeración o, se ejecuta una animación de 0,25 segundos a medida que
la barra de estado entra o sale de Page .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Selección de elementos de selector en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Estos controles específicos de la plataforma iOS cuando se selecciona el elemento en un objeto Picker , lo que
permite al usuario especificar la selección de elementos que se produce al examinar elementos del control o solo
una vez que se presiona el botón listo . Se consume en XAML estableciendo la Picker.UpdateMode propiedad
adjunta en un valor de la UpdateMode enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<Picker ... Title="Select a monkey" ios:Picker.UpdateMode="WhenFinished">
...
</Picker>
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

picker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);

El Picker.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Picker.SetUpdateMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de
nombres, se utiliza para controlar cuándo se produce la selección del elemento, con la UpdateMode enumeración
que proporciona dos valores posibles:
Immediately : la selección de elementos se produce cuando el usuario examina los elementos de Picker . Éste
es el comportamiento predeterminado en :::no-loc(Xamarin.Forms)::: .
WhenFinished : la selección de elementos solo se produce una vez que el usuario ha presionado el botón Done
en el Picker .
Además, el SetUpdateModemétodo se puede utilizar para alternar los valores de enumeración llamando al
UpdateMode método, que devuelve la actual UpdateMode :

switch (picker.On<iOS>().UpdateMode())
{
case UpdateMode.Immediately:
picker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
picker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
}

El resultado es que UpdateMode se aplica un especificado a Picker , que controla cuándo se produce la selección
de elementos:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Guía de diseño de área segura en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para garantizar que el contenido de la página se coloca en un área de la
pantalla que es segura para todos los dispositivos que usan iOS 11 y versiones posteriores. En concreto, le ayudará
a asegurarse de que el contenido no se recorta con las esquinas redondeadas del dispositivo, el indicador de inicio
o la carcasa del sensor en un iPhone X. Se consume en XAML estableciendo la Page.UseSafeArea propiedad
adjunta en un boolean valor:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
Title="Safe Area"
ios:Page.UseSafeArea="true">
<StackLayout>
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

On<iOS>().SetUseSafeArea(true);

El Page.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


Page.SetUseSafeArea método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de
nombres, controla si está habilitada la guía de diseño de área segura.
El resultado es que el contenido de la página puede colocarse en un área de la pantalla que sea segura para todos
los iPhone:
NOTE
El área segura definida por Apple se usa en :::no-loc(Xamarin.Forms)::: para establecer la Page.Padding propiedad y
reemplazará los valores anteriores de esta propiedad que se hayan establecido.

El área segura se puede personalizar recuperando su Thickness valor con el Page.SafeAreaInsets método del
espacio de :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific nombres. Después, se puede modificar
según sea necesario y volver a asignarse a la Padding propiedad en la OnAppearing invalidación:

protected override void OnAppearing()


{
base.OnAppearing();

var safeInsets = On<iOS>().SafeAreaInsets();


safeInsets.Left = 20;
Padding = safeInsets;
}

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
El contenido de ScrollView se toca en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Un temporizador implícito se desencadena cuando un gesto táctil comienza en un ScrollView en iOS y el
ScrollView decide, en función de la acción del usuario dentro del intervalo del temporizador, si debe controlar el
gesto o pasarlo a su contenido. De forma predeterminada, iOS ScrollView retrasa los toques de contenido, pero
puede causar problemas en algunas circunstancias con el ScrollView contenido que no gana el gesto cuando
debería. Por lo tanto, este control es específico de la plataforma, tanto si ScrollView controla un gesto táctil como
si lo pasa a su contenido. Se consume en XAML estableciendo la ScrollView.ShouldDelayContentTouches propiedad
adjunta en un boolean valor:

<MasterDetailPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<MasterDetailPage.Master>
<ContentPage Title="Menu" BackgroundColor="Blue" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<ContentPage>
<ScrollView x:Name="scrollView" ios:ScrollView.ShouldDelayContentTouches="false">
<StackLayout Margin="0,20">
<Slider />
<Button Text="Toggle ScrollView DelayContentTouches" Clicked="OnButtonClicked" />
</StackLayout>
</ScrollView>
</ContentPage>
</MasterDetailPage.Detail>
</MasterDetailPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

scrollView.On<iOS>().SetShouldDelayContentTouches(false);

El ScrollView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


ScrollView.SetShouldDelayContentTouches método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar si un
control ScrollView controla un gesto táctil o lo pasa a su contenido. Además, el SetShouldDelayContentTouches
método se puede utilizar para alternar el retraso del contenido mediante una llamada al
ShouldDelayContentTouches método para devolver si se retrasa el contenido de los toques:

scrollView.On<iOS>().SetShouldDelayContentTouches(!scrollView.On<iOS>().ShouldDelayContentTouches());

El resultado es que ScrollView puede deshabilitar el retraso de la recepción de contenido que recibe los toques,
de modo que en este escenario Slider reciba el gesto en lugar de la Detail Página de MasterDetailPage :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Estilo barra en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma iOS determina si SearchBar tiene un fondo. Se consume en XAML
estableciendo la SearchBar.SearchBarStyle propiedad Bindable en un valor de la UISearchBarStyle enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<SearchBar ios:SearchBar.SearchBarStyle="Minimal"
Placeholder="Enter search term" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

SearchBar searchBar = new SearchBar { Placeholder = "Enter search term" };


searchBar.On<iOS>().SetSearchBarStyle(UISearchBarStyle.Minimal);

El SearchBar.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


SearchBar.SetSearchBarStyle método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar si SearchBar tiene un fondo. La UISearchBarStyle enumeración
proporciona tres valores posibles:
Default indica que tiene el estilo predeterminado. Este es el valor predeterminado de la
SearchBar
SearchBar.SearchBarStyle propiedad enlazable.
Prominent indica que SearchBar tiene un fondo translúcido y el campo de búsqueda es opaco.
Minimal indica que SearchBar no tiene ningún fondo y que el campo de búsqueda es translúcido.

Además, el SearchBar.GetSearchBarStyle método se puede utilizar para devolver el UISearchBarStyle que se aplica
a SearchBar .
El resultado es que un UISearchBarStyle miembro especificado se aplica a un SearchBar , que controla si
SearchBar tiene un fondo:

Las capturas de pantallas siguientes muestran los UISearchBarStyle miembros aplicados a los SearchBar objetos
que tienen su BackgroundColor propiedad establecida:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Reconocimiento simultáneo de gestos de pan en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Cuando un PanGestureRecognizer se adjunta a una vista dentro de una vista de desplazamiento, los movimientos
de la panorámica se capturan mediante PanGestureRecognizer y no se pasan a la vista de desplazamiento. Por lo
tanto, la vista de desplazamiento ya no se desplazará.
Este específico de la plataforma iOS permite PanGestureRecognizer a un en una vista de desplazamiento capturar y
compartir el gesto de panorámica con la vista de desplazamiento. Se consume en XAML estableciendo la
Application.PanGestureRecognizerShouldRecognizeSimultaneously propiedad adjunta en true :

<Application ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:Application.PanGestureRecognizerShouldRecognizeSimultaneously="true">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

:::no-loc(Xamarin.Forms):::.Application.Current.On<iOS>
().SetPanGestureRecognizerShouldRecognizeSimultaneously(true);

El Application.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
Application.SetPanGestureRecognizerShouldRecognizeSimultaneously ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. Application. SetPanGestureRecognizerShouldRecognizeSimultaneously ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS,
:::no-loc(Xamarin.Forms)::: . Application}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar si un
reconocedor de gestos panorámico en una vista de desplazamiento capturará el gesto de movimiento panorámico,
o capturará y compartirá el movimiento de panorámica con la vista de desplazamiento. Además, [
Application.GetPanGestureRecognizerShouldRecognizeSimultaneously ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. Application. GetPanGestureRecognizerShouldRecognizeSimultaneously ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS,
:::no-loc(Xamarin.Forms)::: . Application})) que se puede usar para devolver si el gesto de movimiento panorámico
se comparte con la vista de desplazamiento que contiene PanGestureRecognizer .
Por lo tanto, con esta opción específica de la plataforma habilitada, cuando un ListView contiene un
PanGestureRecognizer , tanto ListView como PanGestureRecognizer recibirán el gesto de pan y lo procesarán. Sin
embargo, con esta plataforma específica deshabilitada, cuando un ListView contiene PanGestureRecognizer , el
PanGestureRecognizer capturará el gesto de pan y lo procesará, y ListView no recibirá el movimiento de la
panorámica.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Puntear en el control deslizante de iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma iOS permite Slider.Value establecer la propiedad pulsando en una posición en la
Slider barra, en lugar de tener que arrastrar el Slider control. Se consume en XAML estableciendo la
Slider.UpdateOnTap propiedad Bindable en true :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout ...>
<Slider ... ios:Slider.UpdateOnTap="true" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

var slider = new :::no-loc(Xamarin.Forms):::.Slider();


slider.On<iOS>().SetUpdateOnTap(true);

El Slider.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
Slider.SetUpdateOnTap ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. Slider.
SetUpdateOnTap ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . Slider}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar si
una pulsación en la Slider barra establecerá la Slider.Value propiedad. Además, [ Slider.GetUpdateOnTap ]
(XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. Slider. GetUpdateOnTap ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS,
:::no-loc(Xamarin.Forms)::: . Slider})) que se puede usar para devolver si una pulsación en la Slider barra
establecerá la Slider.Value propiedad.
El resultado es que una pulsación en la Slider barra puede trasladar el Slider control de posición y establecer la
Slider.Value propiedad:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
SwipeView deslizar el modo de transición en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este control específico de la plataforma iOS controla la transición que se usa al abrir SwipeView . Se consume en
XAML estableciendo la SwipeView.SwipeTransitionMode propiedad Bindable en un valor de la SwipeTransitionMode
enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<SwipeView ios:SwipeView.SwipeTransitionMode="Drag">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<!-- Content -->
</SwipeView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

SwipeView swipeView = new :::no-loc(Xamarin.Forms):::.SwipeView();


swipeView.On<iOS>().SetSwipeTransitionMode(SwipeTransitionMode.Drag);
// ...

El SwipeView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


SwipeView.SetSwipeTransitionMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se utiliza para controlar la transición que se utiliza al abrir SwipeView . La
SwipeTransitionMode enumeración proporciona dos valores posibles:

Reveal indica que los elementos que se van a deslizar se revelarán cuando se deslice el contenido por el dedo
SwipeView y es el valor predeterminado de la SwipeView.SwipeTransitionMode propiedad.
Drag indica que los elementos que se van a deslizar se arrastrarán hasta la vista a medida que SwipeView se
deslice el contenido.
Además, el SwipeView.GetSwipeTransitionMode método se puede utilizar para devolver el SwipeTransitionMode que
se aplica a SwipeView .
El resultado es que un SwipeTransitionMode valor especificado se aplica a SwipeView , que controla la transición
que se utiliza al abrir SwipeView :
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
TabbedPage barra de pestañas translúcidas en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para establecer el modo translucidez de la barra de pestañas en un
TabbedPage . Se consume en XAML estableciendo la TabbedPage.TranslucencyMode propiedad Bindable en un
TranslucencyMode valor de enumeración:

<TabbedPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
ios:TabbedPage.TranslucencyMode="Opaque">
...
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

On<iOS>().SetTranslucencyMode(TranslucencyMode.Opaque);

El TabbedPage.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


TabbedPage.SetTranslucencyMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific
espacio de nombres, se usa para establecer el modo translucidez de la barra de pestañas en un TabbedPage
especificando uno de los siguientes TranslucencyMode valores de enumeración:
Default , que establece la barra de pestañas en su modo predeterminado translucidez. Este es el valor
predeterminado de la propiedad TabbedPage.TranslucencyMode .
Translucent , que establece que la barra de pestañas sea traslúcida.
Opaque , que establece que la barra de pestañas sea opaca.

Además, GetTranslucencyMode se puede usar el método para recuperar el valor actual de la TranslucencyMode
enumeración que se aplica a TabbedPage .
El resultado es que se puede establecer el modo translucidez de la barra de pestañas de un TabbedPage :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Selección de elementos de TimePicker en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Estos controles específicos de la plataforma iOS cuando se selecciona el elemento en un objeto TimePicker , lo
que permite al usuario especificar la selección de elementos que se produce al examinar elementos del control o
solo una vez que se presiona el botón listo . Se consume en XAML estableciendo la TimePicker.UpdateMode
propiedad adjunta en un valor de la UpdateMode enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<TimePicker Time="14:00:00"
ios:TimePicker.UpdateMode="WhenFinished" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

timePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);

El TimePicker.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


TimePicker.SetUpdateMode método, en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio
de nombres, se utiliza para controlar cuándo se produce la selección del elemento, con la UpdateMode enumeración
que proporciona dos valores posibles:
Immediately : la selección de elementos se produce cuando el usuario examina los elementos de TimePicker .
Éste es el comportamiento predeterminado en :::no-loc(Xamarin.Forms)::: .
WhenFinished : la selección de elementos solo se produce una vez que el usuario ha presionado el botón Done
en el TimePicker .
Además, el SetUpdateModemétodo se puede utilizar para alternar los valores de enumeración llamando al
UpdateMode método, que devuelve la actual UpdateMode :

switch (timePicker.On<iOS>().UpdateMode())
{
case UpdateMode.Immediately:
timePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
timePicker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
}

El resultado es que UpdateMode se aplica un especificado a TimePicker , que controla cuándo se produce la
selección de elementos:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
VisualElement desenfoque en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para desenfocar el contenido que se encuentra debajo de él y se puede
aplicar a cualquiera VisualElement . Se consume en XAML estableciendo la VisualElement.BlurEffect propiedad
adjunta en un valor de la BlurEffectStyle enumeración:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
...
<AbsoluteLayout HorizontalOptions="Center">
<Image Source="monkeyface.png" />
<BoxView x:Name="boxView" ios:VisualElement.BlurEffect="ExtraLight" HeightRequest="300"
WidthRequest="300" />
</AbsoluteLayout>
...
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

boxView.On<iOS>().UseBlurEffect(BlurEffectStyle.ExtraLight);

El BoxView.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. [


VisualElement.UseBlurEffect ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
VisualElement. UseBlurEffect ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. BlurEffectStyle)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se usa para aplicar el efecto
de desenfoque, y la BlurEffectStyle enumeración proporciona cuatro valores: None , ExtraLight , Light y
Dark .

El resultado es que BlurEffectStyle se aplica un especificado a la BoxView instancia de, que desenfoca el Image
superpuesto por debajo de él:
NOTE
Al agregar un efecto de desenfoque a un VisualElement , los eventos de toque seguirán recibiendo VisualElement .

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
VisualElement quitar sombras en iOS
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
Esta plataforma específica de iOS se usa para habilitar una sombra paralela en un VisualElement . Se consume en
XAML estableciendo la VisualElement.IsShadowEnabled propiedad adjunta en true , junto con una serie de
propiedades adjuntas adicionales opcionales que controlan la sombra paralela:

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout Margin="20">
<BoxView ...
ios:VisualElement.IsShadowEnabled="true"
ios:VisualElement.ShadowColor="Purple"
ios:VisualElement.ShadowOpacity="0.7"
ios:VisualElement.ShadowRadius="12">
<ios:VisualElement.ShadowOffset>
<Size>
<x:Arguments>
<x:Double>10</x:Double>
<x:Double>10</x:Double>
</x:Arguments>
</Size>
</ios:VisualElement.ShadowOffset>
</BoxView>
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

var boxView = new BoxView { Color = Color.Aqua, WidthRequest = 100, HeightRequest = 100 };
boxView.On<iOS>()
.SetIsShadowEnabled(true)
.SetShadowColor(Color.Purple)
.SetShadowOffset(new Size(10,10))
.SetShadowOpacity(0.7)
.SetShadowRadius(12);

El VisualElement.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
VisualElement.SetIsShadowEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
VisualElement. SetIsShadowEnabled ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, System. Boolean)),
en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar
si una sombra paralela está habilitada en VisualElement . Además, se pueden invocar los métodos siguientes para
controlar la sombra paralela:
[ SetShadowColor ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
SetShadowColor ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, :::no-loc(Xamarin.Forms)::: . Color):
establece el color de la sombra paralela. El color predeterminado es Color.Default .
[ SetShadowOffset ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
SetShadowOffset ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, :::no-loc(Xamarin.Forms)::: . Tamaño):
establece el desplazamiento de la sombra paralela. El desplazamiento cambia la dirección en la que se proyecta
la sombra y se especifica como un Size valor. Los Size valores de la estructura se expresan en unidades
independientes del dispositivo, donde el primer valor es la distancia a la izquierda (valor negativo) o a la
derecha (valor positivo) y el segundo valor es la distancia por encima (valor negativo) o inferior (valor positivo).
El valor predeterminado de esta propiedad es (0,0, 0,0), lo que hace que se convierta la sombra en torno a cada
lado de VisualElement .
[ SetShadowOpacity ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
SetShadowOpacity ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, System. Double)): establece la opacidad
de la sombra paralela, con el valor comprendido entre 0,0 (transparente) y 1,0 (opaco). El valor de opacidad
predeterminado es 0,5.
[ SetShadowRadius ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
SetShadowRadius ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, System. Double)): establece el radio de
desenfoque que se usa para representar la sombra paralela. El valor de radio predeterminado es 10,0.

NOTE
El estado de una sombra paralela se puede consultar llamando a [ GetIsShadowEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOSSpecific. VisualElement. GetIsShadowEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: .
VisualElement})), [ GetShadowColor ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
GetShadowColor ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement})), [ GetShadowOffset ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement. GetShadowOffset ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: .
VisualElement})), [ GetShadowOpacity ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement.
GetShadowOpacity ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement})) y [ GetShadowRadius ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement. GetShadowRadius ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: .
VisualElement})).

El resultado es que se puede habilitar una sombra paralela en un VisualElement :

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Modo de color heredado de VisualElement en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Algunas de las :::no-loc(Xamarin.Forms)::: vistas presentan un modo de color heredado. En este modo, cuando la
IsEnabled propiedad de la vista está establecida en false , la vista invalidará los colores establecidos por el
usuario con los colores nativos predeterminados para el Estado deshabilitado. Por compatibilidad con versiones
anteriores, este modo de color heredado sigue siendo el comportamiento predeterminado para las vistas
admitidas.
Este modo específico de la plataforma iOS deshabilita este modo de color heredado en una VisualElement , de
modo que los colores establecidos en una vista por parte del usuario permanecen incluso cuando la vista está
deshabilitada. Se consume en XAML estableciendo la VisualElement.IsLegacyColorModeEnabled propiedad adjunta
en false :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
...
<Button Text="Button"
TextColor="Blue"
BackgroundColor="Bisque"
ios:VisualElement.IsLegacyColorModeEnabled="False" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

_legacyColorModeDisabledButton.On<iOS>().SetIsLegacyColorModeEnabled(false);

El VisualElement.On<iOS> método especifica que este específico de la plataforma solo se ejecutará en iOS. [
VisualElement.SetIsLegacyColorModeEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific.
VisualElement. SetIsLegacyColorModeEnabled ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. iOS, :::no-loc(Xamarin.Forms)::: . VisualElement}, System. Boolean)),
en el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se utiliza para controlar
si el modo de color heredado está deshabilitado. Además, [ VisualElement.GetIsLegacyColorModeEnabled ] (XREF:
:::no-loc(Xamarin.Forms)::: . PlatformConfiguration. iOSSpecific. VisualElement. GetIsLegacyColorModeEnabled (
:::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
iOS, :::no-loc(Xamarin.Forms)::: . VisualElement})) que se puede usar para devolver si el modo de color heredado
está deshabilitado.
El resultado es que el modo de color heredado se puede deshabilitar, de modo que los colores establecidos en una
vista por parte del usuario permanezcan incluso cuando la vista esté deshabilitada:
NOTE
Al establecer VisualStateGroup en una vista, el modo de color heredado se omite por completo. Para obtener más
información sobre los Estados visuales, consulte el :::no-loc(Xamarin.Forms)::: Visual State Manager.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
VisualElement primer respondedor en iOS
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este tipo específico de la plataforma iOS permite VisualElement que un objeto se convierta en el primer
respondedor para tocar eventos, en lugar de la página que contiene el elemento. Se consume en XAML
estableciendo la VisualElement.CanBecomeFirstResponder propiedad Bindable en true :

<ContentPage ...
xmlns:ios="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<Entry Placeholder="Enter text" />
<Button ios:VisualElement.CanBecomeFirstResponder="True"
Text="OK" />
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific;
...

Entry entry = new Entry { Placeholder = "Enter text" };


Button button = new Button { Text = "OK" };
button.On<iOS>().SetCanBecomeFirstResponder(true);

El VisualElement.On<iOS>método especifica que este específico de la plataforma solo se ejecutará en iOS. El


VisualElement.SetCanBecomeFirstResponder método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres, se usa para establecer
VisualElement para que se convierta en el primer respondedor de los eventos Touch. Además, se
VisualElement.CanBecomeFirstResponder puede usar el método para devolver si VisualElement es el primer
respondedor a los eventos de toque.
El resultado es que un VisualElement puede convertirse en el primer respondedor para los eventos Touch, en
lugar de la página que contiene el elemento. Esto permite escenarios como aplicaciones de chat que no descartan
un teclado cuando Button se puntea.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de iOSSpecific
Características de la plataforma Windows
18/12/2020 • 5 minutes to read • Edit Online

El desarrollo Xamarin.Forms de aplicaciones para plataformas Windows requiere Visual Studio. La Página
plataformas compatibles contiene más información sobre los requisitos previos.

Características específicas de las plataformas


Las características específicas de la plataforma permiten consumir funcionalidad que solo está disponible en una
plataforma específica, sin necesidad de implementar representadores o efectos personalizados.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms vistas, páginas y
diseños en el plataforma universal de Windows (UWP):
Establecer una tecla de acceso para un VisualElement . Para obtener más información, consulte VisualElement
Access Keys in Windows.
Deshabilitar el modo de color heredado en un compatible VisualElement . Para obtener más información,
consulte modo de color heredado de VisualElement en Windows.
Se proporciona la siguiente funcionalidad específica de la plataforma para las Xamarin.Forms vistas en UWP:
Detectar el orden de lectura del contenido de texto en Entry Editor las instancias de, y Label . Para obtener
más información, vea InputView el orden de lectura en Windows.
Habilitar la compatibilidad con gestos de TAP en un ListView . Para obtener más información, vea controles
SelectionMode en Windows.
Habilitar la dirección de extracción de un RefreshView que se va a cambiar. Para obtener más información,
consulte dirección de extracción de RefreshView en Windows.
Habilitación de SearchBar para interactuar con el motor de revisión ortográfica. Para obtener más
información, consulte barra spell check en Windows.
Habilitación de WebView para mostrar las alertas de JavaScript en un cuadro de diálogo de mensaje de UWP.
Para obtener más información, vea alertas de JavaScript de WebView en Windows.
Se proporciona la siguiente funcionalidad específica de la plataforma para Xamarin.Forms las páginas en UWP:
Contraer la MasterDetailPage barra de navegación. Para obtener más información, consulte barra de
navegación MasterDetailPage en Windows.
Establecer opciones de colocación de la barra de herramientas. Para obtener más información, vea Ubicación
de la barra de herramientas de página en Windows.
Habilitar iconos de página para mostrar en una TabbedPage barra de herramientas. Para más información,
consulte Iconos de TabbedPage en Windows.
Se proporciona la siguiente funcionalidad específica de la plataforma para la Xamarin.Forms Application clase en
UWP:
Especificar el directorio del proyecto desde el que se cargarán los recursos de imagen. Para obtener más
información, consulte Directorio de imagen predeterminado en Windows.

Compatibilidad con plataformas


Las Xamarin.Forms plantillas disponibles en Visual Studio contienen un proyecto plataforma universal de
Windows (UWP).

NOTE
Xamarin.Forms1. x y 2. x admiten Windows Phone 8 Silverlight, _Windows Phone 8,1_y Windows 8.1 desarrollo de
aplicaciones. Sin embargo, estos tipos de proyecto están desusados.

Introducción
Vaya a archivo > nuevo proyecto de > en Visual Studio y elija una de las plantillas multiplataforma >
aplicación en blanco ( Xamarin.Forms ) para comenzar.
Las Xamarin.Forms soluciones anteriores, o las creadas en MacOS, no tendrán todos los proyectos de Windows
enumerados anteriormente (pero tienen que agregarse manualmente). Si la plataforma de Windows que desea
destinar todavía no está en la solución, consulte las instrucciones de configuración para agregar los tipos de
proyecto de Windows deseados.

Ejemplos
Todos los ejemplos del libro de Charles Petzold que crean Mobile Apps Xamarin.Forms con los proyectos de
include plataforma universal de Windows (para Windows 10).
La aplicación de demostración "Scott Hanselman" está disponible por separado y también incluye proyectos de
Apple Watch y desgaste de Android (con Xamarin. iOS y Xamarin. Android, respectivamente, no se Xamarin.Forms
ejecuta en esas plataformas).

Vínculos relacionados
Configurar proyectos de Windows
Directorio de imágenes predeterminado en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este Plataforma universal de Windows específico de la plataforma define el directorio del proyecto desde el que se
cargarán los recursos de imagen. Se consume en XAML estableciendo el Application.ImageDirectory en un
string que representa el directorio del proyecto que contiene los recursos de imagen:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
...
windows:Application.ImageDirectory="Assets">
...
</Application>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...
Application.Current.On<Windows>().SetImageDirectory("Assets");

El Application.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el


plataforma universal de Windows. El Application.SetImageDirectory método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para especificar el
directorio del proyecto desde el que se cargarán las imágenes. Además, GetImageDirectory se puede usar el
método para devolver un string que representa el directorio del proyecto que contiene los recursos de la
imagen de aplicación.
El resultado es que todas las imágenes utilizadas en una aplicación se cargarán desde el directorio del proyecto
especificado.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
InputView orden de lectura en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma habilita el orden de lectura (de izquierda a
derecha o de derecha a izquierda) del texto bidireccional en Entry Editor las instancias de, y Label que se
detectan de forma dinámica. Se consume en XAML estableciendo InputView.DetectReadingOrderFromContent (para
instancias de Entry y Editor ) o Label.DetectReadingOrderFromContent propiedad adjunta en un boolean valor:

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<Editor ... windows:InputView.DetectReadingOrderFromContent="true" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

editor.On<Windows>().SetDetectReadingOrderFromContent(true);

El Editor.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el plataforma
universal de Windows. [ InputView.SetDetectReadingOrderFromContent ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. InputView. SetDetectReadingOrderFromContent ( :::no-
loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
Windows, :::no-loc(Xamarin.Forms)::: . InputView}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se utiliza para controlar
si se detecta el orden de lectura del contenido de InputView . Además, se
InputView.SetDetectReadingOrderFromContent puede usar el método para alternar si se detecta el orden de lectura
del contenido mediante una llamada a [ InputView.GetDetectReadingOrderFromContent ] (XREF: :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. InputView. GetDetectReadingOrderFromContent (
:::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
Windows, :::no-loc(Xamarin.Forms)::: . InputView})) para devolver el valor actual:

editor.On<Windows>().SetDetectReadingOrderFromContent(!editor.On<Windows>
().GetDetectReadingOrderFromContent());

El resultado es que Entry las Editor Label instancias de, y pueden tener el orden de lectura de su contenido
detectado dinámicamente:
NOTE
A diferencia de la configuración de la FlowDirection propiedad, la lógica de las vistas que detectan el orden de lectura de
su contenido de texto no afectará a la alineación del texto dentro de la vista. En su lugar, ajusta el orden en el que se colocan
los bloques de texto bidireccional.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
ListView (SelectionMode) en Windows
18/12/2020 • 3 minutes to read • Edit Online

Descargar el ejemplo
En el Plataforma universal de Windows, de forma predeterminada, :::no-loc(Xamarin.Forms)::: ListView usa el
ItemClick evento nativo para responder a la interacción, en lugar del Tapped evento nativo. Esto proporciona
funcionalidad de accesibilidad para que el narrador de Windows y el teclado puedan interactuar con ListView .
Sin embargo, también representa todos los gestos de TAP dentro de ListView inoperativo.
Esta Plataforma universal de Windows controles específicos de la plataforma si los elementos de un ListView
pueden responder a los gestos de TAP y, por tanto, si el nativo ListView desencadena el ItemClick Tapped
evento o. Se consume en XAML estableciendo la ListView.SelectionMode propiedad adjunta en un valor de la
ListViewSelectionMode enumeración:

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<ListView ... windows:ListView.SelectionMode="Inaccessible">
...
</ListView>
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

listView.On<Windows>().SetSelectionMode(ListViewSelectionMode.Inaccessible);

El ListView.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el plataforma
universal de Windows. [ ListView.SetSelectionMode ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
WindowsSpecific. ListView. SetSelectionMode ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . ListView}, :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. ListViewSelectionMode)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se utiliza para controlar
si los elementos de un ListView pueden responder a los gestos de TAP, con la ListViewSelectionMode
enumeración que proporciona dos valores posibles:
Accessible : indica que ListView desencadenará el evento nativo ItemClick para controlar la interacción y,
por tanto, proporcionará la funcionalidad de accesibilidad. Por lo tanto, el narrador de Windows y el teclado
pueden interactuar con ListView . Sin embargo, los elementos del ListView no pueden responder a los gestos
de TAP. Éste es el comportamiento predeterminado de las ListView instancias en el plataforma universal de
Windows.
Inaccessible : indica que ListView desencadenará el evento nativo Tapped para controlar la interacción. Por
lo tanto, los elementos de ListView pueden responder a los gestos de TAP. Sin embargo, no hay ninguna
funcionalidad de accesibilidad y, por lo tanto, el narrador de Windows y el teclado no pueden interactuar con
ListView .
NOTE
Los Accessible Inaccessible modos de selección y son mutuamente excluyentes y tendrá que elegir entre un accesible
ListView o un ListView que pueda responder a los gestos de TAP.

Además, [ GetSelectionMode ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. ListView.


GetSelectionMode ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . ListView})) que se puede usar para devolver el objeto
actual ListViewSelectionMode .
El resultado es que ListViewSelectionMode se aplica un especificado a ListView , que controla si los elementos de
ListView pueden responder a los gestos de punteo y, por tanto, si el nativo ListView activa el ItemClick Tapped
evento o.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Barra de navegación MasterDetailPage en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma se usa para contraer la barra de navegación en
un MasterDetailPage y se consume en XAML estableciendo las MasterDetailPage.CollapseStyle
MasterDetailPage.CollapsedPaneWidth propiedades adjuntas y:

<MasterDetailPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
windows:MasterDetailPage.CollapseStyle="Partial"
windows:MasterDetailPage.CollapsedPaneWidth="48">
...
</MasterDetailPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

page.On<Windows>().SetCollapseStyle(CollapseStyle.Partial).CollapsedPaneWidth(148);

El MasterDetailPage.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en


Windows. [ Page.SetCollapseStyle ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific.
MasterDetailPage. SetCollapseStyle ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . MasterDetailPage}, :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. CollapseStyle)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para especificar el
estilo de contraer, con la CollapseStyle enumeración que proporciona dos valores: Full y Partial . [
MasterDetailPage.CollapsedPaneWidth ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific.
MasterDetailPage. CollapsedPaneWidth ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . El método MasterDetailPage},
System. Double)) se usa para especificar el ancho de una barra de navegación contraída parcialmente.
El resultado es que CollapseStyle se aplica un especificado a la MasterDetailPage instancia, pero también se
especifica el ancho:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Colocación de la barra de herramientas de página en
Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma se usa para cambiar la posición de una barra de
herramientas en un Page y se consume en XAML estableciendo la Page.ToolbarPlacement propiedad adjunta en
un valor de la ToolbarPlacement enumeración:

<TabbedPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
windows:Page.ToolbarPlacement="Bottom">
...
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

page.On<Windows>().SetToolbarPlacement(ToolbarPlacement.Bottom);

El Page.On<Windows>método especifica que este específico de la plataforma solo se ejecutará en Windows. [


Page.SetToolbarPlacement ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. Page.
SetToolbarPlacement ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . Page}, :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. ToolbarPlacement)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para establecer la
ubicación de la barra de herramientas, con la ToolbarPlacement enumeración que proporciona tres valores:
Default , Top y Bottom .

El resultado es que la ubicación especificada de la barra de herramientas se aplica a la Page instancia:


Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Configurar proyectos de Windows
18/12/2020 • 6 minutes to read • Edit Online

Agregar nuevos proyectos de Windows a una Xamarin.Forms solución existente


Xamarin.FormsLas soluciones anteriores (o las creadas en MacOS) no tendrán proyectos de aplicación
plataforma universal de Windows (UWP). Por lo tanto, tendrá que agregar manualmente un proyecto de UWP
para compilar una aplicación de Windows 10 (UWP).

Agregar una aplicación Plataforma universal de Windows


Se recomienda Visual Studio 2019 en Windows 10 para compilar aplicaciones para UWP. Para obtener más
información sobre el Plataforma universal de Windows, consulte Introducción al plataforma universal de
Windows.
UWP está disponible en Xamarin.Forms 2,1 y versiones posteriores, y Xamarin.Forms . Maps se admite en
Xamarin.Forms 2,2 y versiones posteriores.
Consulte la sección de solución de problemas para obtener sugerencias útiles.
Siga estas instrucciones para agregar una aplicación para UWP que se ejecutará en teléfonos, tabletas y
escritorios de Windows 10:
dimensional. Haga clic con el botón derecho en la solución y seleccione agregar > nuevo proyecto... y
agregue un proyecto aplicación vacía (Windows universal) :

dos. En el cuadro de diálogo nuevo proyecto de plataforma universal de Windows , seleccione las
versiones mínima y de destino de Windows 10 en las que se ejecutará la aplicación:

3. Haga clic con el botón derecho en el proyecto UWP y seleccione administrar paquetes NuGet... y agregue
el Xamarin.Forms paquete. Asegúrese de que los otros proyectos de la solución también se actualizan a la
misma versión del Xamarin.Forms paquete.
4. Asegúrese de que el nuevo proyecto de UWP se generará en la > de compilación Configuration Manager
ventana (probablemente no se haya producido de forma predeterminada). Marque las casillas compilar e
implementar del proyecto universal:
5. Haga clic con el botón derecho en el proyecto y seleccione agregar > referencia y cree una referencia al
Xamarin.Forms proyecto de aplicación (.net Standard o proyecto compartido).

1,8. En el proyecto de UWP, edite app.Xaml.CS para incluir la Init llamada de método dentro del método en
torno a la OnLaunched línea 52:

// under this line


rootFrame.NavigationFailed += OnNavigationFailed;
// add this line
Xamarin.Forms.Forms.Init (e); // requires the `e` parameter

7. En el proyecto de UWP, edite mainpage. Xaml quitando el Grid contenido dentro del Page elemento.
203. En mainpage. Xaml , agregue una nueva xmlns entrada para Xamarin.Forms.Platform.UWP :

xmlns:forms="using:Xamarin.Forms.Platform.UWP"

9. En mainpage. Xaml , cambie el <Page elemento raíz a <forms:WindowsPage :

<forms:WindowsPage
...
xmlns:forms="using:Xamarin.Forms.Platform.UWP"
...
</forms:WindowsPage>

7. En el proyecto de UWP, edite mainpage.Xaml.CS para quitar el : Page especificador de herencia para el
nombre de clase (dado que ahora heredará de WindowsPage debido al cambio realizado en el paso anterior):

public sealed partial class MainPage // REMOVE ": Page"

279. En mainpage.Xaml.CS , agregue la LoadApplication llamada en el MainPage constructor para iniciar la


Xamarin.Forms aplicación:
// below this existing line
this.InitializeComponent();
// add this line
LoadApplication(new YOUR_NAMESPACE.App());

NOTE
El argumento para el LoadApplication método es la Xamarin.Forms.Application instancia definida en el proyecto de
.net Standard.

305. Agregue los recursos locales (por ejemplo, archivos de imagen) de los proyectos de plataforma existentes
que son necesarios.

Solución de problemas
"Excepción de invocación de destino" cuando se usa "compilar con .NET Native cadena de herramientas"
Si la aplicación de UWP hace referencia a varios ensamblados (por ejemplo, bibliotecas de control de terceros o si
la propia aplicación se divide en varias bibliotecas), Xamarin.Forms es posible que no pueda cargar objetos de
esos ensamblados (como los representadores personalizados).
Esto puede ocurrir cuando se usa la cadena de herramientas compilar con .net Native , que es una opción
para las aplicaciones UWP en las propiedades > compilar > ventana general del proyecto.
Puede corregirlo mediante una sobrecarga específica de UWP de la Forms.Init llamada en app.Xaml.CS como
se muestra en el código siguiente (debe reemplazar ClassInOtherAssembly por una clase real a la que las
referencias de código):

// You'll need to add `using System.Reflection;`


List<Assembly> assembliesToInclude = new List<Assembly>();

// Now, add in all the assemblies your app uses


assembliesToInclude.Add(typeof (ClassInOtherAssembly).GetTypeInfo().Assembly);

// Also do this for all your other 3rd party libraries


Xamarin.Forms.Forms.Init(e, assembliesToInclude);
// replaces Xamarin.Forms.Forms.Init(e);

Agregue una entrada para cada ensamblado que ha agregado como referencia en el Explorador de soluciones, ya
sea mediante una referencia directa o un NuGet.
Servicios de dependencia y compilación de .NET Native
Las compilaciones de versión que usan la compilación de .NET Native pueden no resolver servicios de
dependencia definidos fuera del archivo ejecutable de la aplicación principal (por ejemplo, en un proyecto o
biblioteca independiente).
Use el DependencyService.Register<T>() método para registrar manualmente las clases de servicio de
dependencia. Según el ejemplo anterior, agregue el método de registro de la siguiente manera:

Xamarin.Forms.Forms.Init(e, assembliesToInclude);
Xamarin.Forms.DependencyService.Register<ClassInOtherAssembly>(); // add this
Dirección de extracción de RefreshView en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma permite cambiar la dirección de extracción de
un para que RefreshView coincida con la orientación del control desplazable que muestra los datos. Se consume
en XAML estableciendo la RefreshView.RefreshPullDirection propiedad Bindable en un valor de la
RefreshPullDirection enumeración:

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<RefreshView windows:RefreshView.RefreshPullDirection="LeftToRight"
IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<ScrollView>
...
</ScrollView>
</RefreshView>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...
refreshView.On<Windows>().SetRefreshPullDirection(RefreshPullDirection.LeftToRight);

El RefreshView.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el


plataforma universal de Windows. El RefreshView.SetRefreshPullDirection método, en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para establecer la
dirección de extracción de RefreshView , con la RefreshPullDirection enumeración que proporciona cuatro
valores posibles:
LeftToRight indica que una extracción de izquierda a derecha inicia una actualización.
TopToBottom indica que una extracción de la parte superior a la inferior inicia una actualización y es la
dirección de extracción predeterminada de un RefreshView .
RightToLeft indica que una extracción de derecha a izquierda inicia una actualización.
BottomToTop indica que una extracción de la parte inferior a la superior inicia una actualización.

Además, el GetRefreshPullDirection método se puede utilizar para devolver el actual RefreshPullDirection del
RefreshView .
El resultado es que RefreshPullDirection se aplica un especificado a RefreshView , para establecer la dirección de
extracción de forma que coincida con la orientación del control desplazable que muestra los datos. En la captura
de pantalla siguiente se muestra un RefreshView con una LeftToRight dirección de extracción:
NOTE
Al cambiar la dirección de extracción, la posición inicial del círculo de progreso gira automáticamente para que la flecha
comience en la posición adecuada para la dirección de extracción.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Spell Check barra en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma permite SearchBar a un interactuar con el
motor de revisión ortográfica. Se consume en XAML estableciendo la SearchBar.IsSpellCheckEnabled propiedad
adjunta en un boolean valor:

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<SearchBar ... windows:SearchBar.IsSpellCheckEnabled="true" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

searchBar.On<Windows>().SetIsSpellCheckEnabled(true);

El SearchBar.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el plataforma
universal de Windows. [ SearchBar.SetIsSpellCheckEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. barra. SetIsSpellCheckEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . Barra}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, activa y desactiva el
corrector ortográfico. Además, SearchBar.SetIsSpellCheckEnabled se puede usar el método para alternar el
corrector ortográfico llamando a [ SearchBar.GetIsSpellCheckEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. barra. GetIsSpellCheckEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . Barra})) para devolver si está habilitado el corrector ortográfico:

searchBar.On<Windows>().SetIsSpellCheckEnabled(!searchBar.On<Windows>().GetIsSpellCheckEnabled());

El resultado es que el texto que se escribe en la SearchBar se puede comprobar ortográficamente, con la
indicación de que se ha indicado una ortografía incorrecta para el usuario:
NOTE
La SearchBar clase del :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres
también EnableSpellCheck tiene DisableSpellCheck métodos y que se pueden utilizar para habilitar y deshabilitar el
corrector ortográfico en SearchBar , respectivamente.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Iconos de TabbedPage en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Esta Plataforma universal de Windows específica de la plataforma permite mostrar iconos de página en una
TabbedPage barra de herramientas y proporciona la capacidad de especificar el tamaño del icono opcionalmente.
Se utiliza en XAML estableciendo la TabbedPage.HeaderIconsEnabled propiedad adjunta en y estableciendo true
opcionalmente la TabbedPage.HeaderIconsSize propiedad adjunta en un Size valor:

<TabbedPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core"
windows:TabbedPage.HeaderIconsEnabled="true">
<windows:TabbedPage.HeaderIconsSize>
<Size>
<x:Arguments>
<x:Double>24</x:Double>
<x:Double>24</x:Double>
</x:Arguments>
</Size>
</windows:TabbedPage.HeaderIconsSize>
<ContentPage Title="Todo" IconImageSource="todo.png">
...
</ContentPage>
<ContentPage Title="Reminders" IconImageSource="reminders.png">
...
</ContentPage>
<ContentPage Title="Contacts" IconImageSource="contacts.png">
...
</ContentPage>
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

public class WindowsTabbedPageIconsCS : :::no-loc(Xamarin.Forms):::.TabbedPage


{
public WindowsTabbedPageIconsCS()
{
On<Windows>().SetHeaderIconsEnabled(true);
On<Windows>().SetHeaderIconsSize(new Size(24, 24));

Children.Add(new ContentPage { Title = "Todo", IconImageSource = "todo.png" });


Children.Add(new ContentPage { Title = "Reminders", IconImageSource = "reminders.png" });
Children.Add(new ContentPage { Title = "Contacts", IconImageSource = "contacts.png" });
}
}

El TabbedPage.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el


plataforma universal de Windows. [ TabbedPage.SetHeaderIconsEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. TabbedPage. SetHeaderIconsEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . TabbedPage}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para activar o
desactivar los iconos de encabezado. [ TabbedPage.SetHeaderIconsSize ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. TabbedPage. SetHeaderIconsSize ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . TabbedPage}, :::no-loc(Xamarin.Forms)::: . Size)) el método especifica opcionalmente el
tamaño del icono de encabezado con un Size valor.
Además, la TabbedPage clase del espacio de :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific
nombres también tiene un EnableHeaderIcons método que habilita los iconos de encabezado, un
DisableHeaderIcons método que deshabilita los iconos de encabezado y un IsHeaderIconsEnabled método que
devuelve un boolean valor que indica si los iconos de encabezado están habilitados.
El resultado es que los iconos de página se pueden mostrar en una TabbedPage barra de herramientas, con el
tamaño de icono establecido opcionalmente en un tamaño deseado:

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Claves de acceso de VisualElement en Windows
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
Las claves de acceso son métodos abreviados de teclado que mejoran la facilidad de uso y la accesibilidad de las
aplicaciones en el Plataforma universal de Windows (UWP) al proporcionar una manera intuitiva de que los
usuarios naveguen rápidamente e interactúen con la interfaz de usuario visible de la aplicación a través de un
teclado en lugar de hacerlo a través de un toque o un mouse. Son combinaciones de la tecla Alt y una o varias
claves alfanuméricas, normalmente presionadas secuencialmente. Los métodos abreviados de teclado se admiten
automáticamente para las teclas de acceso que utilizan un solo carácter alfanumérico.
Las sugerencias de teclas de acceso son notificaciones flotantes que se muestran junto a los controles que
incluyen claves de acceso. Cada sugerencia de clave de acceso contiene las claves alfanuméricas que activan el
control asociado. Cuando el usuario presiona la tecla Alt, se muestran las sugerencias de teclas de acceso.
Este UWP específico de la plataforma se usa para especificar una clave de acceso para VisualElement . Se utiliza
en XAML estableciendo la VisualElement.AccessKey propiedad adjunta en un valor alfanumérico y estableciendo
opcionalmente la VisualElement.AccessKeyPlacement propiedad adjunta en un valor de la AccessKeyPlacement
enumeración, la VisualElement.AccessKeyHorizontalOffset propiedad adjunta en un double y la
VisualElement.AccessKeyVerticalOffset propiedad adjunta en un double :

<TabbedPage ...
xmlns:windows="clr-
namespace:Xamarin.Forms.PlatformConfiguration.WindowsSpecific;assembly=Xamarin.Forms.Core">
<ContentPage Title="Page 1"
windows:VisualElement.AccessKey="1">
<StackLayout Margin="20">
...
<Switch windows:VisualElement.AccessKey="A" />
<Entry Placeholder="Enter text here"
windows:VisualElement.AccessKey="B" />
...
<Button Text="Access key F, placement top with offsets"
Margin="20"
Clicked="OnButtonClicked"
windows:VisualElement.AccessKey="F"
windows:VisualElement.AccessKeyPlacement="Top"
windows:VisualElement.AccessKeyHorizontalOffset="20"
windows:VisualElement.AccessKeyVerticalOffset="20" />
...
</StackLayout>
</ContentPage>
...
</TabbedPage>

Como alternativa, se puede usar desde C# con la API fluida:


using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
...

var page = new ContentPage { Title = "Page 1" };


page.On<Windows>().SetAccessKey("1");

var switchView = new Switch();


switchView.On<Windows>().SetAccessKey("A");
var entry = new Entry { Placeholder = "Enter text here" };
entry.On<Windows>().SetAccessKey("B");
...

var button4 = new Button { Text = "Access key F, placement top with offsets", Margin = new Thickness(20) };
button4.Clicked += OnButtonClicked;
button4.On<Windows>()
.SetAccessKey("F")
.SetAccessKeyPlacement(AccessKeyPlacement.Top)
.SetAccessKeyHorizontalOffset(20)
.SetAccessKeyVerticalOffset(20);
...

El VisualElement.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el


plataforma universal de Windows. [ VisualElement.SetAccessKey ] (XREF: Xamarin.Forms . PlatformConfiguration.
WindowsSpecific. VisualElement. SetAccessKey ( Xamarin.Forms . IPlatformElementConfiguration {
Xamarin.Forms . PlatformConfiguration. Windows, Xamarin.Forms . VisualElement}, System. String)), en el
Xamarin.Forms.PlatformConfiguration.WindowsSpecific espacio de nombres, se usa para establecer el valor de la
clave de acceso para VisualElement . [ VisualElement.SetAccessKeyPlacement ] (XREF: Xamarin.Forms .
PlatformConfiguration. WindowsSpecific. VisualElement. SetAccessKeyPlacement ( Xamarin.Forms .
IPlatformElementConfiguration { Xamarin.Forms . PlatformConfiguration. Windows, Xamarin.Forms .
VisualElement}, Xamarin.Forms . AccessKeyPlacement)), especifica opcionalmente la posición que se va a usar
para mostrar la sugerencia de clave de acceso, con la AccessKeyPlacement enumeración que proporciona los
siguientes valores posibles:
Auto : indica que el sistema operativo determinará la ubicación de la información sobre las teclas de acceso.
Top : indica que la sugerencia de tecla de acceso aparecerá encima del borde superior de VisualElement .
Bottom : indica que la sugerencia de tecla de acceso aparecerá debajo del borde inferior de VisualElement .
Right : indica que la sugerencia de tecla de acceso aparecerá a la derecha del borde derecho del
VisualElement .
Left : indica que la sugerencia de tecla de acceso aparecerá a la izquierda del borde izquierdo de
VisualElement .
Center : indica que la sugerencia de tecla de acceso aparecerá superpuesta en el centro del VisualElement .

NOTE
Normalmente, la Auto Ubicación de la sugerencia de teclas es suficiente, lo que incluye la compatibilidad con las interfaces
de usuario adaptables.

[ VisualElement.SetAccessKeyHorizontalOffset ] (XREF: Xamarin.Forms . PlatformConfiguration. WindowsSpecific.


VisualElement. SetAccessKeyHorizontalOffset ( Xamarin.Forms . IPlatformElementConfiguration { Xamarin.Forms .
PlatformConfiguration. Windows, Xamarin.Forms . VisualElement}, System. Double)) y [
VisualElement.SetAccessKeyVerticalOffset ] (XREF: Xamarin.Forms . PlatformConfiguration. WindowsSpecific.
VisualElement. SetAccessKeyVerticalOffset ( Xamarin.Forms . IPlatformElementConfiguration { Xamarin.Forms .
PlatformConfiguration. Windows, Xamarin.Forms . Los métodos VisualElement}, System. Double)) se pueden usar
para un control más granular de la ubicación de la información sobre las teclas de acceso. El argumento para el
SetAccessKeyHorizontalOffset método indica la distancia a la que se debe desplace la punta de la tecla de acceso
a la izquierda o a la derecha, y el argumento al SetAccessKeyVerticalOffset método indica hasta dónde se mueve
la punta de la tecla de acceso hacia arriba o hacia abajo.

NOTE
No se pueden establecer los desplazamientos de la sugerencia de clave de acceso cuando se establece la posición de la
clave de acceso Auto .

Además, [ GetAccessKey ] (XREF: Xamarin.Forms . PlatformConfiguration. WindowsSpecific. VisualElement.


GetAccessKey ( Xamarin.Forms . IPlatformElementConfiguration { Xamarin.Forms . PlatformConfiguration.
Windows, Xamarin.Forms . VisualElement})), [ GetAccessKeyPlacement ] (XREF: Xamarin.Forms .
PlatformConfiguration. WindowsSpecific. VisualElement. GetAccessKeyPlacement ( Xamarin.Forms .
IPlatformElementConfiguration { Xamarin.Forms . PlatformConfiguration. Windows, Xamarin.Forms .
VisualElement})), [ GetAccessKeyHorizontalOffset ] (XREF: Xamarin.Forms . PlatformConfiguration.
WindowsSpecific. VisualElement. GetAccessKeyHorizontalOffset ( Xamarin.Forms .
IPlatformElementConfiguration { Xamarin.Forms . PlatformConfiguration. Windows, Xamarin.Forms .
VisualElement})) y [ GetAccessKeyVerticalOffset ] (XREF: Xamarin.Forms . PlatformConfiguration.
WindowsSpecific. VisualElement. GetAccessKeyVerticalOffset ( Xamarin.Forms . IPlatformElementConfiguration {
Xamarin.Forms . PlatformConfiguration. Windows, Xamarin.Forms . VisualElement})). los métodos se pueden usar
para recuperar un valor de clave de acceso y su ubicación.
El resultado es que las sugerencias de teclas de acceso se pueden mostrar junto a cualquier VisualElement
instancia que defina las teclas de acceso presionando la tecla Alt:

Cuando un usuario activa una tecla de acceso, presione la tecla Alt seguida de la tecla de acceso para que se
ejecute la acción predeterminada de VisualElement . Por ejemplo, cuando un usuario activa la tecla de acceso en
un Switch , Switch se alterna. Cuando un usuario activa la tecla de acceso en un Entry , Entry obtiene el foco.
Cuando un usuario activa la tecla de acceso en una Button , se ejecuta el controlador de eventos para el
Clicked evento.

WARNING
De forma predeterminada, cuando se muestra un cuadro de diálogo modal, las teclas de acceso que se definen en la página
detrás del cuadro de diálogo todavía se pueden activar. Sin embargo, se puede escribir una lógica personalizada para
deshabilitar las claves de acceso en este escenario. Esto puede lograrse mediante el control del
Dispatcher.AcceleratorKeyActivated evento en la MainPage clase del proyecto de UWP y en el controlador de
eventos estableciendo la Handled propiedad de los argumentos de evento en true cuando se muestra un cuadro de
diálogo modal.

Para obtener más información acerca de las claves de acceso, consulte claves de acceso.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Modo de color heredado de VisualElement en
Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Algunas de las :::no-loc(Xamarin.Forms)::: vistas presentan un modo de color heredado. En este modo, cuando la
IsEnabled propiedad de la vista está establecida en false , la vista invalidará los colores establecidos por el
usuario con los colores nativos predeterminados para el Estado deshabilitado. Por compatibilidad con versiones
anteriores, este modo de color heredado sigue siendo el comportamiento predeterminado para las vistas
admitidas.
Esta Plataforma universal de Windows específica de la plataforma deshabilita este modo de color heredado, de
modo que los colores establecidos en una vista por parte del usuario permanecen incluso cuando la vista está
deshabilitada. Se consume en XAML estableciendo la VisualElement.IsLegacyColorModeEnabled propiedad adjunta
en false :

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
...
<Editor Text="Enter text here"
TextColor="Blue"
BackgroundColor="Bisque"
windows:VisualElement.IsLegacyColorModeEnabled="False" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

_legacyColorModeDisabledEditor.On<Windows>().SetIsLegacyColorModeEnabled(false);

El VisualElement.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en


Windows. [ VisualElement.SetIsLegacyColorModeEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: . PlatformConfiguration.
WindowsSpecific. VisualElement. SetIsLegacyColorModeEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . VisualElement}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se utiliza para controlar
si el modo de color heredado está deshabilitado. Además, [ VisualElement.GetIsLegacyColorModeEnabled ] (XREF:
:::no-loc(Xamarin.Forms)::: . PlatformConfiguration. WindowsSpecific. VisualElement.
GetIsLegacyColorModeEnabled ( :::no-loc(Xamarin.Forms)::: . IPlatformElementConfiguration { :::no-
loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-loc(Xamarin.Forms)::: . VisualElement})) que se puede
usar para devolver si el modo de color heredado está deshabilitado.
El resultado es que el modo de color heredado se puede deshabilitar, de modo que los colores establecidos en una
vista por parte del usuario permanezcan incluso cuando la vista esté deshabilitada:

NOTE
Al establecer VisualStateGroup en una vista, el modo de color heredado se omite por completo. Para obtener más
información sobre los Estados visuales, consulte el :::no-loc(Xamarin.Forms)::: Visual State Manager.

Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Alertas de JavaScript de WebView en Windows
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Este específico de la plataforma permite WebView a un para mostrar las alertas de JavaScript en un cuadro de
diálogo de mensaje de UWP. Se consume en XAML estableciendo la WebView.IsJavaScriptAlertEnabled propiedad
adjunta en un boolean valor:

<ContentPage ...
xmlns:windows="clr-namespace::::no-
loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;assembly=:::no-loc(Xamarin.Forms):::.Core">
<StackLayout>
<WebView ... windows:WebView.IsJavaScriptAlertEnabled="true" />
...
</StackLayout>
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific;
...

var webView = new :::no-loc(Xamarin.Forms):::.WebView


{
Source = new HtmlWebViewSource
{
Html = @"<html><body><button onclick=""window.alert('Hello World from JavaScript');"">Click Me</button>
</body></html>"
}
};
webView.On<Windows>().SetIsJavaScriptAlertEnabled(true);

El WebView.On<Windows> método especifica que este específico de la plataforma solo se ejecutará en el plataforma
universal de Windows. [ WebView.SetIsJavaScriptAlertEnabled ] (XREF: :::no-loc(Xamarin.Forms)::: .
PlatformConfiguration. WindowsSpecific. Webview. SetIsJavaScriptAlertEnabled ( :::no-loc(Xamarin.Forms)::: .
IPlatformElementConfiguration { :::no-loc(Xamarin.Forms)::: . PlatformConfiguration. Windows, :::no-
loc(Xamarin.Forms)::: . WebView}, System. Boolean)), en el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres, se utiliza para controlar
si se habilitan las alertas de JavaScript. Además, WebView.SetIsJavaScriptAlertEnabled se puede usar el método
para alternar las alertas de JavaScript llamando al IsJavaScriptAlertEnabled método para devolver si están
habilitadas:

_webView.On<Windows>().SetIsJavaScriptAlertEnabled(!_webView.On<Windows>().IsJavaScriptAlertEnabled());

El resultado es que las alertas de JavaScript se pueden mostrar en un cuadro de diálogo de mensaje de UWP:
Vínculos relacionados
PlatformSpecifics (ejemplo)
Creación funcionalidades específicas de plataforma
API de WindowsSpecific
Características específicas de las plataformas
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
Las características específicas de la plataforma permiten consumir funcionalidad que solo está disponible en una
plataforma específica, sin necesidad de implementar representadores o efectos personalizados.
El proceso de consumo de un específico de la plataforma a través de XAML, o a través de la API de código Fluent es
el siguiente:
1. Agregue una xmlns declaración o una Directiva para el
using
:::no-loc(Xamarin.Forms):::.PlatformConfiguration espacio de nombres.
2. Agregue una xmlns declaración o una using Directiva para el espacio de nombres que contiene la
funcionalidad específica de la plataforma:
a. En iOS, es el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.iOSSpecific espacio de nombres.
b. En Android, este es el :::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific espacio de
nombres. Para el AppCompat de Android, este es el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.AndroidSpecific.AppCompat espacio de nombres.
c. En el Plataforma universal de Windows, es el
:::no-loc(Xamarin.Forms):::.PlatformConfiguration.WindowsSpecific espacio de nombres.
3. Aplique la plataforma específica de la plataforma desde XAML o desde el código con la On<T> API fluida. El
valor de T puede ser los iOS Android tipos, o Windows del espacio de
:::no-loc(Xamarin.Forms):::.PlatformConfiguration nombres.

NOTE
Tenga en cuenta que si se intenta consumir una plataforma específica en una plataforma en la que no está disponible, no se
producirá un error. En su lugar, el código se ejecutará sin la aplicación específica de la plataforma.

Los específicos de la plataforma utilizados a través de la On<T> API de código Fluent devuelven
IPlatformElementConfiguration objetos. Esto permite invocar varias características específicas de la plataforma en
el mismo objeto con el método en cascada.
Para obtener más información sobre las características específicas de la plataforma que proporciona, consulte
características específicas de la plataforma :::no-loc(Xamarin.Forms)::: iOS, características específicasde la
plataforma Android y características específicas de la plataforma Windows.

Creación de características específicas de la plataforma


Los proveedores pueden crear sus propias características específicas de la plataforma con efectos. Un efecto
proporciona la funcionalidad específica, que se expone a través de un específico de la plataforma. El resultado es
un efecto que se puede consumir más fácilmente a través de XAML y a través de una API de código Fluent.
El proceso para crear un específico de plataforma es el siguiente:
1. Implemente la funcionalidad específica como efecto. Para obtener más información, vea crear un efecto.
2. Cree una clase específica de la plataforma que expondrá el efecto. Para obtener más información, vea crear una
clase Platform-Specific.
3. En la clase específica de la plataforma, implemente una propiedad adjunta para permitir el consumo de la
plataforma específica a través de XAML. Para obtener más información, vea Agregar una propiedad adjunta.
4. En la clase específica de la plataforma, implemente métodos de extensión para permitir el consumo de la
plataforma específica a través de una API de código Fluent. Para obtener más información, vea Agregar
métodos de extensión.
5. Modifique la implementación del efecto para que el efecto se aplique solo si se ha invocado la plataforma
específica en la misma plataforma que el efecto. Para obtener más información, vea crear el efecto.
El resultado de exponer un efecto como específico de la plataforma es que el efecto se puede consumir más
fácilmente a través de XAML y a través de una API de código Fluent.

NOTE
Está previsto que los proveedores utilicen esta técnica para crear sus propias características específicas de la plataforma, para
facilitar el consumo de los usuarios. Aunque los usuarios pueden optar por crear sus propias características específicas de la
plataforma, se debe tener en cuenta que requiere más código que la creación y el consumo de un efecto.

La aplicación de ejemplo muestra un Shadow específico de la plataforma que agrega una sombra al texto
mostrado por un Label control:

La aplicación de ejemplo implementa la Shadow plataforma específica en cada plataforma, para facilitar la
comprensión. Sin embargo, aparte de cada implementación de efecto específico de la plataforma, la
implementación de la clase Shadow es en gran medida idéntica para cada plataforma. Por lo tanto, esta guía se
centra en la implementación de la clase Shadow y en el efecto asociado en una sola plataforma.
Para obtener más información sobre los efectos, vea Personalizar controles con efectos.
Crear una clase específica de la plataforma
Una clase específica de la plataforma se crea como una public static clase:

namespace MyCompany.Forms.PlatformConfiguration.iOS
{
public static Shadow
{
...
}
}

En las secciones siguientes se describe la implementación de los Shadow efectos asociados y específicos de la
plataforma.
Agregar una propiedad adjunta
Una propiedad adjunta se debe agregar a la Shadow plataforma específica para permitir el consumo a través de
XAML:

namespace MyCompany.Forms.PlatformConfiguration.iOS
{
using System.Linq;
using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using FormsElement = :::no-loc(Xamarin.Forms):::.Label;
using FormsElement = :::no-loc(Xamarin.Forms):::.Label;

public static class Shadow


{
const string EffectName = "MyCompany.LabelShadowEffect";

public static readonly BindableProperty IsShadowedProperty =


BindableProperty.CreateAttached("IsShadowed",
typeof(bool),
typeof(Shadow),
false,
propertyChanged: OnIsShadowedPropertyChanged);

public static bool GetIsShadowed(BindableObject element)


{
return (bool)element.GetValue(IsShadowedProperty);
}

public static void SetIsShadowed(BindableObject element, bool value)


{
element.SetValue(IsShadowedProperty, value);
}

...

static void OnIsShadowedPropertyChanged(BindableObject element, object oldValue, object newValue)


{
if ((bool)newValue)
{
AttachEffect(element as FormsElement);
}
else
{
DetachEffect(element as FormsElement);
}
}

static void AttachEffect(FormsElement element)


{
IElementController controller = element;
if (controller == null || controller.EffectIsAttached(EffectName))
{
return;
}
element.Effects.Add(Effect.Resolve(EffectName));
}

static void DetachEffect(FormsElement element)


{
IElementController controller = element;
if (controller == null || !controller.EffectIsAttached(EffectName))
{
return;
}

var toRemove = element.Effects.FirstOrDefault(e => e.ResolveId ==


Effect.Resolve(EffectName).ResolveId);
if (toRemove != null)
{
element.Effects.Remove(toRemove);
}
}
}
}

La IsShadowed propiedad adjunta se usa para agregar el MyCompany.LabelShadowEffect efecto a y quitarlo de, el
control Shadow al que está asociada la clase. Esta propiedad asociada registra el método
OnIsShadowedPropertyChanged que se ejecutará cuando cambie el valor de la propiedad. A su vez, este método llama
AttachEffect al DetachEffect método o para agregar o quitar el efecto basado en el valor de la IsShadowed
propiedad adjunta. El efecto se agrega o se quita del control modificando la colección del control Effects .

NOTE
Tenga en cuenta que el efecto se resuelve especificando un valor que es una concatenación del nombre del grupo de
resolución y el identificador único que se especifica en la implementación del efecto. Para obtener más información, vea crear
un efecto.

Para más información sobre las propiedades adjuntas, consulte Propiedades asociadas.
Agregar métodos de extensión
Los métodos de extensión se deben agregar a la Shadow plataforma específica para permitir el consumo a través
de una API de código Fluent:

namespace MyCompany.Forms.PlatformConfiguration.iOS
{
using System.Linq;
using :::no-loc(Xamarin.Forms):::;
using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using FormsElement = :::no-loc(Xamarin.Forms):::.Label;

public static class Shadow


{
...
public static bool IsShadowed(this IPlatformElementConfiguration<iOS, FormsElement> config)
{
return GetIsShadowed(config.Element);
}

public static IPlatformElementConfiguration<iOS, FormsElement> SetIsShadowed(this


IPlatformElementConfiguration<iOS, FormsElement> config, bool value)
{
SetIsShadowed(config.Element, value);
return config;
}
...
}
}

Los IsShadowed métodos de extensión y invocan los descriptores de acceso get y set para la
SetIsShadowed
IsShadowed propiedad adjunta, respectivamente. Cada método de extensión funciona en el
IPlatformElementConfiguration<iOS, FormsElement> tipo, que especifica que el específico de la plataforma se puede
invocar en Label instancias de iOS.
Crear el efecto
El Shadow específico de la plataforma agrega el MyCompany.LabelShadowEffect a un Label y lo quita. En el ejemplo
de código siguiente se muestra la implementación LabelShadowEffect para el proyecto de iOS:
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace ShadowPlatformSpecific.iOS
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
UpdateShadow();
}

protected override void OnDetached()


{
}

protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)


{
base.OnElementPropertyChanged(args);

if (args.PropertyName == Shadow.IsShadowedProperty.PropertyName)
{
UpdateShadow();
}
}

void UpdateShadow()
{
try
{
if (((Label)Element).OnThisPlatform().IsShadowed())
{
Control.Layer.CornerRadius = 5;
Control.Layer.ShadowColor = UIColor.Black.CGColor;
Control.Layer.ShadowOffset = new CGSize(5, 5);
Control.Layer.ShadowOpacity = 1.0f;
}
else if (!((Label)Element).OnThisPlatform().IsShadowed())
{
Control.Layer.ShadowOpacity = 0;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
}
}

El UpdateShadow método establece Control.Layer las propiedades para crear la sombra, siempre que la
IsShadowed propiedad adjunta esté establecida en true y siempre que se haya Shadow invocado la plataforma
específica en la misma plataforma para la que se implementa el efecto. Esta comprobación se realiza con el
OnThisPlatform método.

Si el Shadow.IsShadowed valor de la propiedad adjunta cambia en tiempo de ejecución, el efecto debe responder
quitando la sombra. Por lo tanto, se usa una versión invalidada del OnElementPropertyChanged método para
responder al cambio de la propiedad enlazable llamando al UpdateShadow método.
Para obtener más información sobre la creación de un efecto, vea crear un efecto y pasar parámetros de efecto
como propiedades adjuntas.
Consumo de las características específicas de la plataforma
La Shadow plataforma específica de la plataforma se consume en XAML estableciendo la Shadow.IsShadowed
propiedad adjunta en un boolean valor:

<ContentPage xmlns:ios="clr-namespace:MyCompany.Forms.PlatformConfiguration.iOS" ...>


...
<Label Text="Label Shadow Effect" ios:Shadow.IsShadowed="true" ... />
...
</ContentPage>

Como alternativa, se puede usar desde C# con la API fluida:

using :::no-loc(Xamarin.Forms):::.PlatformConfiguration;
using MyCompany.Forms.PlatformConfiguration.iOS;

...

shadowLabel.On<iOS>().SetIsShadowed(true);

Vínculos relacionados
PlatformSpecifics (ejemplo)
ShadowPlatformSpecific (ejemplo)
Características específicas de la plataforma iOS
Características específicas de la plataforma Android
Características específicas de la plataforma Windows
Personalizar controles con efectos
Propiedades asociadas
API de PlatformConfiguration
:::no-loc(Xamarin.Forms)::: Clase de dispositivo
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
La Device clase contiene una serie de propiedades y métodos para ayudar a los desarrolladores a personalizar el
diseño y la funcionalidad para cada plataforma.
Además de los métodos y las propiedades para el código de destino en tipos y tamaños de hardware específicos,
la Device clase incluye métodos que se pueden usar para interactuar con los controles de interfaz de usuario de
los subprocesos en segundo plano. Para obtener más información, vea interactuar con la interfaz de usuario de
subprocesos en segundo plano.

Proporcionar valores específicos de la plataforma


Antes de :::no-loc(Xamarin.Forms)::: 2.3.4, la plataforma en la que se ejecutaba la aplicación se podía obtener
examinando la Device.OS propiedad y comparándola con los TargetPlatform.iOS TargetPlatform.Android
valores de TargetPlatform.WinPhone enumeración,, y TargetPlatform.Windows . De forma similar, una de las
Device.OnPlatform sobrecargas podría usarse para proporcionar valores específicos de la plataforma a un control.

Sin embargo, como :::no-loc(Xamarin.Forms)::: 2.3.4, estas API han quedado en desuso y se han reemplazado. La
Device clase contiene ahora constantes de cadena públicas que identifican las plataformas: Device.iOS ,
Device.Android , Device.WinPhone (en desuso), Device.WinRT (desusado), Device.UWP y Device.macOS . Del
mismo modo, las Device.OnPlatform sobrecargas se han reemplazado por las OnPlatform On API y.
En C#, se pueden proporcionar valores específicos de la plataforma mediante la creación de una switch
instrucción en la Device.RuntimePlatform propiedad y, a continuación, proporcionar case instrucciones para las
plataformas necesarias:

double top;
switch (Device.RuntimePlatform)
{
case Device.iOS:
top = 20;
break;
case Device.Android:
case Device.UWP:
default:
top = 0;
break;
}
layout.Margin = new Thickness(5, top, 5, 0);

Las OnPlatform On clases y proporcionan la misma funcionalidad en XAML:


<StackLayout>
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,20,0,0" />
<On Platform="Android, UWP" Value="0,0,0,0" />
</OnPlatform>
</StackLayout.Margin>
...
</StackLayout>

La OnPlatform clase es una clase genérica en la que se deben crear instancias con un x:TypeArguments atributo
que coincida con el tipo de destino. En la On clase, el Platform atributo puede aceptar un solo string valor o
varios valores delimitados por comas string .

IMPORTANT
Proporcionar un Platform valor de atributo incorrecto en la On clase no producirá un error. En su lugar, se ejecutará el
código sin que se aplique el valor específico de la plataforma.

Como alternativa, OnPlatform se puede usar la extensión de marcado en XAML para personalizar la apariencia de
la interfaz de usuario en cada plataforma. Para obtener más información, consulte extensión de marcadoen la
plataforma.

Dispositivo. expresión
La Device.Idiom propiedad se puede usar para modificar los diseños o la funcionalidad según el dispositivo en el
que se ejecuta la aplicación. La TargetIdiom enumeración contiene los siguientes valores:
Teléfono : dispositivos iPhone, iPod Touch y Android más estrechos que 600 DIP ^
Tableta : iPad, dispositivos Windows y dispositivos Android más anchos que 600 DIP ^
Escritorio : solo se devuelve en aplicaciones para UWP en equipos de escritorio de Windows 10 (se devuelve
Phone en dispositivos móviles de Windows, incluidos los escenarios de continuum).
TV : dispositivos de TV Tizen
Inspección : Tizen Watch Devices
No compatible : sin usar
^ DIP no es necesariamente el número de píxeles físicos
La Idiom propiedad es especialmente útil para compilar diseños que aprovechan pantallas más grandes, como
esto:

if (Device.Idiom == TargetIdiom.Phone) {
// layout views vertically
} else {
// layout views horizontally for a larger display (tablet or desktop)
}

La OnIdiom clase proporciona la misma funcionalidad en XAML:


<StackLayout>
<StackLayout.Margin>
<OnIdiom x:TypeArguments="Thickness">
<OnIdiom.Phone>0,20,0,0</OnIdiom.Phone>
<OnIdiom.Tablet>0,40,0,0</OnIdiom.Tablet>
<OnIdiom.Desktop>0,60,0,0</OnIdiom.Desktop>
</OnIdiom>
</StackLayout.Margin>
...
</StackLayout>

La OnIdiom clase es una clase genérica en la que se deben crear instancias con un x:TypeArguments atributo que
coincida con el tipo de destino.
Como alternativa, OnIdiom se puede usar la extensión de marcado en XAML para personalizar la apariencia de la
interfaz de usuario en función de la expresión del dispositivo en el que se ejecuta la aplicación. Para obtener más
información, vea extensión de marcado en idioma.

Device. FlowDirection
El Device.FlowDirection valor recupera un FlowDirection valor de enumeración que representa la dirección de
flujo actual utilizada por el dispositivo. La dirección de flujo es la dirección en la que el ojo humano lee los
elementos de la interfaz de usuario en la página. Los valores de la enumeración son:
LeftToRight
RightToLeft
MatchParent

En XAML, el Device.FlowDirection valor se puede recuperar mediante la x:Static extensión de marcado:

<ContentPage ... FlowDirection="{x:Static Device.FlowDirection}"> />

El código equivalente en C# es:

this.FlowDirection = Device.FlowDirection;

Para obtener más información acerca de la dirección de flujo, consulte localización de derecha a izquierda.

Device. Styles
La Styles propiedad contiene definiciones de estilo integradas que se pueden aplicar a algunos controles de la
propiedad (como Label ) Style . Los estilos disponibles son:
BodyStyle
CaptionStyle
ListItemDetailTextStyle
ListItemTextStyle
SubtitleStyle
TitleStyle

Device. GetNamedSize
GetNamedSize se puede usar al establecer FontSize en el código de C#:
myLabel.FontSize = Device.GetNamedSize (NamedSize.Small, myLabel);
someLabel.FontSize = Device.OnPlatform (
24, // hardcoded size
Device.GetNamedSize (NamedSize.Medium, someLabel),
Device.GetNamedSize (NamedSize.Large, someLabel)
);

Device.GetNamedColor
:::no-loc(Xamarin.Forms)::: 4,6 introduce compatibilidad con los colores con nombre. Un color con nombre es un
color que tiene un valor diferente en función del modo del sistema (por ejemplo, claro o oscuro) que está activo
en el dispositivo. En Android, se tiene acceso a los colores con nombre a través de la clase R. color . En iOS, los
colores con nombre se denominan colores del sistema. En el Plataforma universal de Windows, los colores con
nombre se denominan recursos de tema XAML.
El GetNamedColor método se puede usar para recuperar los colores con nombre en Android, iOS y UWP. El método
toma un string argumento y devuelve Color :

// Retrieve an Android named color


Color color = Device.GetNamedColor(NamedPlatformColor.HoloBlueBright);

Color.Default se devolverá cuando no se pueda encontrar un nombre de color o cuando GetNamedColor se


invoque en una plataforma no compatible.

NOTE
Dado GetNamedColor que el método devuelve un Color que es específico de una plataforma, normalmente se debe
utilizar junto con la Device.RuntimePlatform propiedad.

La NamedPlatformColor clase contiene las constantes que definen los colores con nombre para Android, iOS y
UWP:

A N DRO ID IO S MAC OS UW P

BackgroundDark Label AlternateSelectedControlTextColor


SystemAltHighColor

BackgroundLight Link ControlAccent SystemAltLowColor

Black OpaqueSeparator ControlBackgroundColor SystemAltMediumColor

DarkerGray PlaceholderText ControlColor SystemAltMediumHighColor

HoloBlueBright QuaternaryLabel DisabledControlTextColor SystemAltMediumLowColor

HoloBlueDark SecondaryLabel FindHighlightColor SystemBaseHighColor

HoloBlueLight Separator GridColor SystemBaseLowColor

HoloGreenDark SystemBlue HeaderTextColor SystemBaseMediumColor

HoloGreenLight SystemGray HighlightColor SystemBaseMediumHighColor


A N DRO ID IO S MAC OS UW P

HoloOrangeDark SystemGray2 KeyboardFocusIndicatorColor SystemBaseMediumLowColor

HoloOrangeLight SystemGray3 Label SystemChromeAltLowColor

HoloPurple SystemGray4 LabelColor SystemChromeBlackHighColor

HoloRedDark SystemGray5 Link SystemChromeBlackLowColor

HoloRedLight SystemGray6 LinkColor SystemChromeBlackMediumColor

TabIndicatorText SystemGreen PlaceholderText SystemChromeBlackMediumLowColor

Transparent SystemIndigo PlaceholderTextColor SystemChromeDisabledHighColor

White SystemOrange QuaternaryLabel SystemChromeDisabledLowColor

WidgetEditTextDark SystemPink QuaternaryLabelColor SystemChromeHighColor

SystemPurple SecondaryLabel SystemChromeLowColor

SystemRed SecondaryLabelColor SystemChromeMediumColor

SystemTeal SelectedContentBackgroundColorS ystemChromeMediumLowColor

SystemYellow SelectedControlColor SystemChromeWhiteColor

TertiaryLabel SelectedControlTextColor SystemListLowColor

SelectedMenuItemTextColor SystemListMediumColor

SelectedTextBackgroundColor

SelectedTextColor

Separator

SeparatorColor

ShadowColor

SystemBlue

SystemGray

SystemGreen

SystemIndigo
A N DRO ID IO S MAC OS UW P

SystemOrange

SystemPink

SystemPurple

SystemRed

SystemTeal

SystemYellow

TertiaryLabel

TertiaryLabelColor

TextBackgroundColor

TextColor

UnderPageBackgroundColor

UnemphasizedSelectedContentBackgroundColor

UnemphasizedSelectedTextBackgroundColor

UnemphasizedSelectedTextColor

WindowBackgroundColor

WindowFrameTextColor

Device. StartTimer
La Device clase también tiene un StartTimer método que proporciona una manera sencilla de desencadenar
tareas dependientes del tiempo que funcionan en :::no-loc(Xamarin.Forms)::: código común, incluida una biblioteca
.net Standard. Pase un TimeSpan para establecer el intervalo y volver true a mantener el temporizador en
ejecución o false detenerlo después de la invocación actual.

Device.StartTimer (new TimeSpan (0, 0, 60), () =>


{
// do something every 60 seconds
return true; // runs again, or false to stop
});

Si el código dentro del temporizador interactúa con la interfaz de usuario (por ejemplo, al establecer el texto de un
Label o mostrar una alerta), debe realizarse dentro de una BeginInvokeOnMainThread expresión (consulte a
continuación).
NOTE
Las System.Timers.Timer System.Threading.Timer clases y son .net Standard alternativas al uso del
Device.StartTimer método.

Interacción con la interfaz de usuario desde subprocesos en segundo


plano
La mayoría de los sistemas operativos, incluidos iOS, Android y el Plataforma universal de Windows, utilizan un
modelo de subprocesamiento único para el código que implica la interfaz de usuario. Este subproceso se llama a
menudo el subproceso principal o el subproceso de la interfaz de usuario. Una consecuencia de este modelo es
que todo el código que tiene acceso a los elementos de la interfaz de usuario se debe ejecutar en el subproceso
principal de la aplicación.
A veces, las aplicaciones utilizan subprocesos en segundo plano para realizar operaciones que pueden tardar
mucho tiempo, como recuperar datos de un servicio Web. Si el código que se ejecuta en un subproceso en
segundo plano necesita acceder a los elementos de la interfaz de usuario, debe ejecutar ese código en el
subproceso principal.
La Device clase incluye los siguientes static métodos que se pueden usar para interactuar con los elementos
de la interfaz de usuario de los subprocesos de fondo:

M ÉTO DO A RGUM EN TO S VA LO RES DEVUELTO S P RO P Ó SITO

BeginInvokeOnMainThread Action void Invoca un Action en el


subproceso principal y no
espera a que se complete.

InvokeOnMainThreadAsync<T> Func<T> Task<T> Invoca un elemento


Func<T> en el subproceso
principal y espera a que se
complete.

InvokeOnMainThreadAsync Action Task Invoca un elemento


Action en el subproceso
principal y espera a que se
complete.

InvokeOnMainThreadAsync<T> Func<Task<T>> Task<T> Invoca un elemento


Func<Task<T>> en el
subproceso principal y
espera a que se complete.

InvokeOnMainThreadAsync Func<Task> Task Invoca un elemento


Func<Task> en el
subproceso principal y
espera a que se complete.

GetMainThreadSynchronizationContextAsync Task<SynchronizationContext> Devuelve el elemento


SynchronizationContext
para el subproceso principal.

En el código siguiente se muestra un ejemplo del uso del BeginInvokeOnMainThread método:


Device.BeginInvokeOnMainThread (() =>
{
// interact with UI elements
});

Vínculos relacionados
Ejemplo de dispositivo
Ejemplo de estilos
API del dispositivo
:::no-loc(Xamarin.Forms)::: en proyectos nativos de
Xamarin
18/12/2020 • 24 minutes to read • Edit Online

Descargar el ejemplo
Normalmente, una :::no-loc(Xamarin.Forms)::: aplicación incluye una o varias páginas que se derivan de
ContentPage y estas páginas se comparten entre todas las plataformas de un proyecto de biblioteca de .net
Standard o un proyecto compartido. Sin embargo, los formularios nativos permiten ContentPage Agregar páginas
derivadas directamente a aplicaciones de Xamarin. iOS, Xamarin. Android y UWP nativas. En comparación con el
hecho de que el proyecto nativo consuma ContentPage páginas derivadas de un proyecto de biblioteca de .net
Standard o un proyecto compartido, la ventaja de agregar páginas directamente a proyectos nativos es que las
páginas se pueden extender con vistas nativas. Las vistas nativas se pueden denominar en XAML con x:Name y se
puede hacer referencia a ellas desde el código subyacente. Para obtener más información sobre las vistas nativas,
vea vistas nativas.
El proceso de consumo de una :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de en un proyecto nativo
es el siguiente:
1. Agregue el :::no-loc(Xamarin.Forms)::: paquete NuGet al proyecto nativo.
2. Agregue la ContentPage Página derivada de y las dependencias al proyecto nativo.
3. Llame al método Forms.Init .
4. Construya una instancia de la ContentPage Página derivada de y conviértala en el tipo nativo adecuado
mediante uno de los siguientes métodos de extensión: CreateViewController para iOS, CreateSupportFragment
para Android o CreateFrameworkElement para UWP.
5. Navegue a la representación de tipo nativo de la ContentPage Página derivada de mediante la API de
navegación nativa.
:::no-loc(Xamarin.Forms)::: se debe inicializar llamando al Forms.Init método antes de que un proyecto nativo
pueda construir una ContentPage Página derivada de. Elegir cuándo hacerlo depende principalmente del momento
en el que sea más conveniente en el flujo de la aplicación: puede realizarse durante el inicio de la aplicación o justo
antes de que ContentPage se construya la página derivada de. En este artículo y en las aplicaciones de ejemplo que
lo acompañan, Forms.Init se llama al método durante el inicio de la aplicación.

NOTE
La solución de aplicación de ejemplo NativeForms no contiene ningún :::no-loc(Xamarin.Forms)::: proyecto. En su lugar,
consta de un proyecto de Xamarin. iOS, un proyecto de Xamarin. Android y un proyecto de UWP. Cada proyecto es un
proyecto nativo que utiliza formularios nativos para consumir ContentPage páginas derivadas de. Sin embargo, no hay
ningún motivo por el que los proyectos nativos no hayan podido consumir ContentPage páginas derivadas de un proyecto
de biblioteca de .net Standard o un proyecto compartido.

Cuando se usan formularios nativos, las :::no-loc(Xamarin.Forms)::: características como DependencyService ,


MessagingCenter y el motor de enlace de datos siguen funcionando. Sin embargo, la navegación de página debe
realizarse mediante la API de navegación nativa.

iOS
En iOS, la FinishedLaunching invalidación en la AppDelegate clase suele ser el lugar en el que se realizan las tareas
relacionadas con el inicio de la aplicación. Se llama después de que se haya iniciado la aplicación y normalmente
se invalida para configurar la ventana principal y el controlador de vista. En el ejemplo de código siguiente se
muestra la AppDelegate clase en la aplicación de ejemplo:

[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public static AppDelegate Instance;
UIWindow _window;
AppNavigationController _navigation;

public static string FolderPath { get; private set; }

public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)


{
Forms.Init();

Instance = this;
_window = new UIWindow(UIScreen.MainScreen.Bounds);

UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
{
TextColor = UIColor.Black
});

FolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
UIViewController mainPage = new NotesPage().CreateViewController();
mainPage.Title = "Notes";

_navigation = new AppNavigationController(mainPage);


_window.RootViewController = _navigation;
_window.MakeKeyAndVisible();

return true;
}
// ...
}

El método FinishedLaunching realiza las siguientes tareas:


:::no-loc(Xamarin.Forms)::: se inicializa llamando al Forms.Init método.
AppDelegate En el campo se almacena una referencia a la clase static Instance . Esto es para proporcionar
un mecanismo para que otras clases llamen a métodos definidos en la AppDelegate clase.
UIWindow Se crea el, que es el contenedor principal de las vistas en aplicaciones nativas de iOS.
La FolderPath propiedad se inicializa en una ruta de acceso en el dispositivo donde se almacenarán los datos
de la nota.
La NotesPage clase, que es una :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de definida en XAML,
se construye y se convierte en UIViewController mediante el CreateViewController método de extensión.
La Title propiedad de UIViewController se establece, que se mostrará en el UINavigationBar .
AppNavigationController Se crea un para administrar la navegación jerárquica. Se trata de una clase de
controlador de navegación personalizada, que se deriva de UINavigationController . El
AppNavigationController objeto administra una pila de controladores de vista y el que se UIViewController
pasa al constructor se presentará inicialmente cuando AppNavigationController se cargue.
El AppNavigationController objeto se establece como el nivel superior UIViewController para UIWindow , y
UIWindow se establece como la ventana de clave de la aplicación y se hace visible.

Una vez FinishedLaunching ejecutado el método, se mostrará la interfaz de usuario definida en la :::no-
loc(Xamarin.Forms)::: NotesPage clase, como se muestra en la siguiente captura de pantalla:

La interacción con la interfaz de usuario, por ejemplo, al puntear en + Button , dará como resultado el siguiente
controlador de eventos en la NotesPage ejecución del código subyacente:

void OnNoteAddedClicked(object sender, EventArgs e)


{
AppDelegate.Instance.NavigateToNoteEntryPage(new Note());
}

El static AppDelegate.Instance campo permite AppDelegate.NavigateToNoteEntryPage invocar el método, que se


muestra en el ejemplo de código siguiente:

public void NavigateToNoteEntryPage(Note note)


{
var noteEntryPage = new NoteEntryPage
{
BindingContext = note
}.CreateViewController();
noteEntryPage.Title = "Note Entry";
_navigation.PushViewController(noteEntryPage, true);
}

El NavigateToNoteEntryPagemétodo convierte la :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de en una


UIViewController con el CreateViewController método de extensión y establece la Title propiedad de
UIViewController . UIViewController Después, se inserta en AppNavigationController el PushViewController
método. Por lo tanto, se mostrará la interfaz de usuario definida en la :::no-loc(Xamarin.Forms)::: NoteEntryPage
clase, como se muestra en la siguiente captura de pantalla:

Cuando NoteEntryPage se muestra, la navegación hacia atrás redirigirá la UIViewController para la NoteEntryPage
clase de AppNavigationController y devolverá el usuario al UIViewController de la NotesPage clase. Sin embargo,
si se extrae un UIViewController de la pila de navegación nativa de iOS, no se elimina automáticamente el
UIViewController objeto asociado y Page . Por lo tanto, la AppNavigationController clase invalida el
PopViewController método, para desechar los controladores de vista en la navegación hacia atrás:
public class AppNavigationController : UINavigationController
{
//...
public override UIViewController PopViewController(bool animated)
{
UIViewController topView = TopViewController;
if (topView != null)
{
// Dispose of ViewController on back navigation.
topView.Dispose();
}
return base.PopViewController(animated);
}
}

La PopViewController invalidación llama al Dispose método en el UIViewController objeto que se ha sacado de la


pila de navegación nativa de iOS. Si no se hace esto, se producirá el UIViewController huérfano y el Page objeto
asociado.

IMPORTANT
Los objetos huérfanos no se pueden recolectar como elementos no utilizados y, por tanto, se produce una fuga de memoria.

Android
En Android, la OnCreate invalidación en la MainActivity clase suele ser el lugar en el que se realizan las tareas
relacionadas con el inicio de la aplicación. En el ejemplo de código siguiente se muestra la MainActivity clase en
la aplicación de ejemplo:

public class MainActivity : AppCompatActivity


{
public static string FolderPath { get; private set; }

public static MainActivity Instance;

protected override void OnCreate(Bundle bundle)


{
base.OnCreate(bundle);

Forms.Init(this, bundle);
Instance = this;

SetContentView(Resource.Layout.Main);
var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
SupportActionBar.Title = "Notes";

FolderPath =
Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
Android.Support.V4.App.Fragment mainPage = new NotesPage().CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.Replace(Resource.Id.fragment_frame_layout, mainPage)
.Commit();
...
}
...
}

El método OnCreate realiza las siguientes tareas:


:::no-loc(Xamarin.Forms)::: se inicializa llamando al Forms.Init método.
MainActivity En el campo se almacena una referencia a la clase static Instance . Esto es para proporcionar
un mecanismo para que otras clases llamen a métodos definidos en la MainActivity clase.
El Activity contenido se establece desde un recurso de diseño. En la aplicación de ejemplo, el diseño está
compuesto de un LinearLayout que contiene un Toolbar y un FrameLayout para actuar como contenedor de
fragmentos.
Toolbar Se recupera y establece como la barra de acción para Activity , y se establece el título de la barra de
acciones.
La FolderPath propiedad se inicializa en una ruta de acceso en el dispositivo donde se almacenarán los datos
de la nota.
La NotesPage clase, que es una :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de definida en XAML,
se construye y se convierte en Fragment mediante el CreateSupportFragment método de extensión.
La SupportFragmentManager clase crea y confirma una transacción que reemplaza a la FrameLayout instancia
Fragment de por la NotesPage clase.

Para obtener más información acerca de los fragmentos, vea fragmentos.


Una vez OnCreate ejecutado el método, se mostrará la interfaz de usuario definida en la :::no-loc(Xamarin.Forms):::
NotesPage clase, como se muestra en la siguiente captura de pantalla:

La interacción con la interfaz de usuario, por ejemplo, al puntear en + Button , dará como resultado el siguiente
controlador de eventos en la NotesPage ejecución del código subyacente:

void OnNoteAddedClicked(object sender, EventArgs e)


{
MainActivity.Instance.NavigateToNoteEntryPage(new Note());
}

El static MainActivity.Instance campo permite MainActivity.NavigateToNoteEntryPage invocar el método, que se


muestra en el ejemplo de código siguiente:

public void NavigateToNoteEntryPage(Note note)


{
Android.Support.V4.App.Fragment noteEntryPage = new NoteEntryPage
{
BindingContext = note
}.CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.AddToBackStack(null)
.Replace(Resource.Id.fragment_frame_layout, noteEntryPage)
.Commit();
}

El NavigateToNoteEntryPage método convierte la :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de en una


Fragment con el CreateSupportFragment método de extensión y agrega Fragment a la pila de retroceso de
fragmento. Por lo tanto, se mostrará la interfaz de usuario definida en el :::no-loc(Xamarin.Forms)::: NoteEntryPage ,
tal y como se muestra en la siguiente captura de pantalla:

Cuando NoteEntryPage se muestra, al puntear en la flecha atrás, se mostrará el elemento de la Fragment


NoteEntryPage pila de retroceso de fragmento y se devolverá al usuario al Fragment de la NotesPage clase.
Habilitar la compatibilidad con la navegación hacia atrás
La SupportFragmentManager clase tiene un BackStackChanged evento que se activa cada vez que cambia el contenido
de la pila de retroceso de fragmento. El OnCreate método de la MainActivity clase contiene un controlador de
eventos anónimo para este evento:

SupportFragmentManager.BackStackChanged += (sender, e) =>


{
bool hasBack = SupportFragmentManager.BackStackEntryCount > 0;
SupportActionBar.SetHomeButtonEnabled(hasBack);
SupportActionBar.SetDisplayHomeAsUpEnabled(hasBack);
SupportActionBar.Title = hasBack ? "Note Entry" : "Notes";
};

Este controlador de eventos muestra un botón atrás en la barra de acciones siempre que haya una o más
Fragment instancias en la pila de retroceso de fragmento. La respuesta al puntear en el botón atrás se controla
mediante la OnOptionsItemSelected invalidación:

public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)


{
if (item.ItemId == global::Android.Resource.Id.Home && SupportFragmentManager.BackStackEntryCount > 0)
{
SupportFragmentManager.PopBackStack();
return true;
}
return base.OnOptionsItemSelected(item);
}

OnOptionsItemSelected Se llama a la invalidación cada vez que se selecciona un elemento en el menú opciones.
Esta implementación extrae el fragmento actual de la pila de retroceso de fragmento, siempre que se haya
seleccionado el botón atrás y haya una o más Fragment instancias en la pila de retroceso de fragmento.
Varias actividades
Cuando una aplicación se compone de varias actividades, ContentPage las páginas derivadas de se pueden
incrustar en cada una de las actividades. En este escenario, Forms.Init solo es necesario llamar al método en la
OnCreate invalidación del primer Activity que inserta un :::no-loc(Xamarin.Forms)::: ContentPage . Sin embargo,
esto tiene el impacto siguiente:
El valor de :::no-loc(Xamarin.Forms):::.Color.Accent se tomará del Activity que llamó al Forms.Init
método.
El valor de se :::no-loc(Xamarin.Forms):::.Application.Current asociará al Activity que llamó al Forms.Init
método.
Elegir un archivo
Al incrustar una ContentPage Página derivada de que usa un WebView que necesita admitir un botón "elegir
archivo" HTML, Activity deberá invalidar el OnActivityResult método:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)


{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResultCallbackRegistry.InvokeCallback(requestCode, resultCode, data);
}

UWP
En UWP, la App clase nativa es normalmente el lugar en el que se realizan las tareas relacionadas con el inicio de
la aplicación. :::no-loc(Xamarin.Forms)::: normalmente se inicializa, en :::no-loc(Xamarin.Forms)::: las aplicaciones
para UWP, en la OnLaunched invalidación de la App clase nativa, para pasar el LaunchActivatedEventArgs
argumento al Forms.Init método. Por este motivo, las aplicaciones de UWP nativas que consumen una :::no-
loc(Xamarin.Forms)::: ContentPage Página derivada de pueden llamar más fácilmente al Forms.Init método desde
el App.OnLaunched método.
De forma predeterminada, la App clase nativa inicia la MainPage clase como la primera página de la aplicación. En
el ejemplo de código siguiente se muestra la MainPage clase en la aplicación de ejemplo:

public sealed partial class MainPage : Page


{
NotesPage notesPage;
NoteEntryPage noteEntryPage;

public static MainPage Instance;


public static string FolderPath { get; private set; }

public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Enabled;
Instance = this;
FolderPath =
Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
notesPage = new Notes.UWP.Views.NotesPage();
this.Content = notesPage.CreateFrameworkElement();
// ...
}
// ...
}

El MainPage constructor realiza las siguientes tareas:


El almacenamiento en caché está habilitado para la página, por lo que no se crea un nuevo MainPage cuando
un usuario vuelve a la página.
MainPage En el campo se almacena una referencia a la clase static Instance . Esto es para proporcionar un
mecanismo para que otras clases llamen a métodos definidos en la MainPage clase.
La FolderPath propiedad se inicializa en una ruta de acceso en el dispositivo donde se almacenarán los datos
de la nota.
La NotesPage clase, que es una :::no-loc(Xamarin.Forms)::: ContentPage Página derivada de definida en XAML,
se construye y se convierte en FrameworkElement mediante el CreateFrameworkElement método de extensión y, a
continuación, se establece como el contenido de la MainPage clase.

Una vez MainPage ejecutado el constructor, se mostrará la interfaz de usuario definida en la :::no-
loc(Xamarin.Forms)::: NotesPage clase, como se muestra en la siguiente captura de pantalla:
La interacción con la interfaz de usuario, por ejemplo, al puntear en + Button , dará como resultado el siguiente
controlador de eventos en la NotesPage ejecución del código subyacente:

void OnNoteAddedClicked(object sender, EventArgs e)


{
MainPage.Instance.NavigateToNoteEntryPage(new Note());
}

El static MainPage.Instance campo permite MainPage.NavigateToNoteEntryPage invocar el método, que se


muestra en el ejemplo de código siguiente:

public void NavigateToNoteEntryPage(Note note)


{
noteEntryPage = new Notes.UWP.Views.NoteEntryPage
{
BindingContext = note
};
this.Frame.Navigate(noteEntryPage);
}

La navegación en UWP se realiza normalmente con el Frame.Navigate método, que toma un Page argumento.
:::no-loc(Xamarin.Forms)::: define un Frame.Navigate método de extensión que toma una ContentPage instancia de
página derivada de. Por lo tanto, cuando se NavigateToNoteEntryPage ejecute el método, se mostrará la interfaz de
usuario definida en el :::no-loc(Xamarin.Forms)::: NoteEntryPage , tal y como se muestra en la siguiente captura de
pantalla:

Cuando NoteEntryPage se muestra, al puntear en la flecha atrás se desactivará el elemento de la FrameworkElement


pila de NoteEntryPage retroceso de la aplicación y se devolverá al usuario al FrameworkElement de la NotesPage
clase.
Habilitar la compatibilidad con el cambio de tamaño de página
Cuando se cambia el tamaño de la ventana de la aplicación de UWP, :::no-loc(Xamarin.Forms)::: también se debe
cambiar el tamaño del contenido. Esto se logra registrando un controlador de eventos para el Loaded evento, en el
MainPage constructor:
public MainPage()
{
// ...
this.Loaded += OnMainPageLoaded;
// ...
}

El Loaded evento se desencadena cuando la página se presenta, se representa y está lista para la interacción y
ejecuta el OnMainPageLoaded método en respuesta:

void OnMainPageLoaded(object sender, RoutedEventArgs e)


{
this.Frame.SizeChanged += (o, args) =>
{
if (noteEntryPage != null)
noteEntryPage.Layout(new :::no-loc(Xamarin.Forms):::.Rectangle(0, 0, args.NewSize.Width,
args.NewSize.Height));
else
notesPage.Layout(new :::no-loc(Xamarin.Forms):::.Rectangle(0, 0, args.NewSize.Width,
args.NewSize.Height));
};
}

El OnMainPageLoaded método registra un controlador de eventos anónimo para el Frame.SizeChanged evento, que
se produce cuando las ActualHeight ActualWidth propiedades o cambian en Frame . En respuesta, :::no-
loc(Xamarin.Forms)::: se cambia el tamaño del contenido de la página activa mediante una llamada al Layout
método.
Habilitar la compatibilidad con la navegación hacia atrás
En UWP, las aplicaciones deben habilitar la navegación hacia atrás para todos los botones de retroceso de
hardware y software, en distintos factores de forma de dispositivo. Esto puede realizarse registrando un
controlador de eventos para el BackRequested evento, que se puede realizar en el MainPage constructor:

public MainPage()
{
// ...
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}

Cuando se inicia la aplicación, el GetForCurrentView método recupera el SystemNavigationManager objeto asociado


a la vista actual y, a continuación, registra un controlador de eventos para el BackRequested evento. La aplicación
solo recibe este evento si es la aplicación de primer plano y, en respuesta, llama al OnBackRequested controlador de
eventos:

void OnBackRequested(object sender, BackRequestedEventArgs e)


{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
noteEntryPage = null;
}
}

El OnBackRequestedcontrolador de eventos llama al GoBack método en el marco raíz de la aplicación y establece la


BackRequestedEventArgs.Handled propiedad en true para marcar el evento como controlado. Si no se marca el
evento como controlado, se podría omitir el evento.
La aplicación elige si mostrar un botón atrás en la barra de título. Esto se consigue estableciendo la
AppViewBackButtonVisibility propiedad en uno de los AppViewBackButtonVisibility valores de enumeración, en la
App clase:

void OnNavigated(object sender, NavigationEventArgs e)


{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}

El OnNavigated controlador de eventos, que se ejecuta en respuesta a la Navigated activación del evento, actualiza
la visibilidad del botón atrás de la barra de título cuando se produce la navegación por la página. Esto garantiza
que el botón atrás de la barra de título está visible si la pila de retroceso de la aplicación no está vacía o se quita de
la barra de título si la pila de retroceso en aplicación está vacía.
Para más información sobre la compatibilidad con la navegación hacia atrás en UWP, consulte navegación por el
historial de navegación y la navegación hacia atrás para aplicaciones UWP.

Vínculos relacionados
NativeForms (ejemplo)
Vistas nativas
Vistas nativas enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

A las vistas nativas de iOS, Android y Plataforma universal de Windows (UWP) se puede hacer referencia
directamente desde Xamarin.Forms . Las propiedades y los controladores de eventos se pueden establecer en las
vistas nativas y pueden interactuar con las Xamarin.Forms vistas.

Vistas nativas en XAML


Se puede hacer referencia directamente a las vistas nativas de iOS, Android y UWP desde Xamarin.Forms las
páginas creadas con XAML.

Vistas nativas en C#
Se puede hacer referencia directamente a las vistas nativas de iOS, Android y UWP desde Xamarin.Forms las
páginas creadas con C#.

Vínculos relacionados
Formularios nativos
Vistas nativas en XAML
18/12/2020 • 23 minutes to read • Edit Online

Descargar el ejemplo
Se puede hacer referencia directamente a las vistas nativas de iOS, Android y el Plataforma universal de Windows
desde :::no-loc(Xamarin.Forms)::: los archivos XAML. Las propiedades y los controladores de eventos se pueden
establecer en las vistas nativas y pueden interactuar con las :::no-loc(Xamarin.Forms)::: vistas. En este artículo se
muestra cómo consumir vistas nativas de :::no-loc(Xamarin.Forms)::: archivos XAML.
Para insertar una vista nativa en un :::no-loc(Xamarin.Forms)::: archivo XAML:
1. Agregue una xmlns declaración de espacio de nombres en el archivo XAML para el espacio de nombres que
contiene la vista nativa.
2. Cree una instancia de la vista nativa en el archivo XAML.

IMPORTANT
El código XAML compilado debe estar deshabilitado para cualquier página XAML que use vistas nativas. Esto se puede lograr
decorando la clase de código subyacente de la página XAML con el [XamlCompilation(XamlCompilationOptions.Skip)]
atributo. Para obtener más información sobre la compilación de XAML, vea compilación de XAML en :::no-
loc(Xamarin.Forms)::: .

Para hacer referencia a una vista nativa desde un archivo de código subyacente, debe usar un proyecto de recurso
compartido (SAP) y ajustar el código específico de la plataforma con directivas de compilación condicional. Para
obtener más información, vea referencia a las vistas nativas desde el código.

Consumir vistas nativas


En el ejemplo de código siguiente se muestra cómo consumir vistas nativas para cada plataforma a :::no-
loc(Xamarin.Forms)::: ContentPage :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
x:Class="NativeViews.NativeViewDemo">
<StackLayout Margin="20">
<ios:UILabel Text="Hello World" TextColor="{x:Static ios:UIColor.Red}" View.HorizontalOptions="Start"
/>
<androidWidget:TextView Text="Hello World" x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
/>
<win:TextBlock Text="Hello World" />
</StackLayout>
</ContentPage>

Además de especificar clr-namespace y assembly para un espacio de nombres de vista nativa, targetPlatform
también se debe especificar un. Debe establecerse en iOS , Android , UWP , Windows (que es equivalente a UWP ),
macOS , GTK , Tizen o WPF . En tiempo de ejecución, el analizador de XAML omitirá cualquier prefijo de espacio
de nombres XML que tenga un targetPlatform que no coincida con la plataforma en la que se ejecuta la
aplicación.
Cada declaración de espacio de nombres se puede utilizar para hacer referencia a cualquier clase o estructura del
espacio de nombres especificado. Por ejemplo, la ios declaración de espacio de nombres se puede utilizar para
hacer referencia a cualquier clase o estructura del espacio de nombres de iOS UIKit . Las propiedades de la vista
nativa se pueden establecer a través de XAML, pero los tipos de propiedad y de objeto deben coincidir. Por ejemplo,
la UILabel.TextColor propiedad se establece en UIColor.Red utilizando la x:Static extensión de marcado y el
ios espacio de nombres.

Las propiedades enlazables y las propiedades enlazables asociadas también se pueden establecer en vistas nativas
mediante la Class.BindableProperty="value" Sintaxis. Cada vista nativa se ajusta en una instancia específica de
NativeViewWrapper la plataforma, que deriva de la :::no-loc(Xamarin.Forms):::.View clase. Al establecer una
propiedad enlazable o una propiedad enlazable asociada en una vista nativa, se transfiere el valor de propiedad al
contenedor. Por ejemplo, se puede especificar un diseño horizontal centrado estableciendo
View.HorizontalOptions="Center" en la vista nativa.

NOTE
Tenga en cuenta que los estilos no se pueden usar con vistas nativas, ya que los estilos solo pueden tener como destino
propiedades respaldadas por BindableProperty objetos.

Los constructores de widgets de Android normalmente requieren el Context objeto Android como argumento, y
se puede hacer que esté disponible a través de una propiedad estática en la MainActivity clase. Por consiguiente,
al crear un widget de Android en XAML, el Context objeto se debe pasar generalmente al constructor del widget
mediante el x:Arguments atributo con una x:Static extensión de marcado. Para obtener más información, vea
pasar argumentos a vistas nativas.

NOTE
Tenga en cuenta que el nombre de una vista nativa con x:Name no es posible en un proyecto de biblioteca de .net Standard
o un proyecto de recursos compartidos (SAP). Si lo hace, se generará una variable del tipo nativo, lo que producirá un error
de compilación. Sin embargo, las vistas nativas se pueden encapsular en ContentView instancias y recuperar en el archivo
de código subyacente, siempre que se use un SAP. Para obtener más información, vea hacer referencia a la vista nativa desde
el código.

Enlaces nativos
El enlace de datos se utiliza para sincronizar una interfaz de usuario con su origen de datos y simplifica el modo en
que una :::no-loc(Xamarin.Forms)::: aplicación muestra e interactúa con sus datos. Siempre que el objeto de origen
implementa la INotifyPropertyChanged interfaz, el marco de enlace inserta automáticamente los cambios en el
objeto de origen en el objeto de destino , y los cambios en el objeto de destino se pueden insertar opcionalmente
en el objeto de origen .
Las propiedades de las vistas nativas también pueden usar el enlace de datos. En el ejemplo de código siguiente se
muestra el enlace de datos mediante las propiedades de las vistas nativas:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:local="clr-namespace:NativeSwitch"
x:Class="NativeSwitch.NativeSwitchPage">
<StackLayout Margin="20">
<Label Text="Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
<Entry Placeholder="This Entry is bound to the native switch" IsEnabled="{Binding IsSwitchOn}" />
<ios:UISwitch On="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=ValueChanged}"
OnTintColor="{x:Static ios:UIColor.Red}"
ThumbTintColor="{x:Static ios:UIColor.Blue}" />
<androidWidget:Switch x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
Checked="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=CheckedChange}"
Text="Enable Entry?" />
<win:ToggleSwitch Header="Enable Entry?"
OffContent="No"
OnContent="Yes"
IsOn="{Binding IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=Toggled}" />
</StackLayout>
</ContentPage>

La página contiene una Entry cuya IsEnabled propiedad se enlaza a la NativeSwitchPageViewModel.IsSwitchOn


propiedad. La BindingContext de la página se establece en una nueva instancia de la NativeSwitchPageViewModel
clase en el archivo de código subyacente, con la clase ViewModel que implementa la INotifyPropertyChanged
interfaz.
La página también contiene un conmutador nativo para cada plataforma. Cada conmutador nativo usa un TwoWay
enlace para actualizar el valor de la NativeSwitchPageViewModel.IsSwitchOn propiedad. Por lo tanto, cuando el
modificador está desactivado, el Entry está deshabilitado y cuando el modificador está activado, el Entry está
habilitado. Las siguientes capturas de pantallas muestran esta funcionalidad en cada plataforma:

Los enlaces bidireccionales se admiten automáticamente siempre que la propiedad nativa implementa
INotifyPropertyChanged , o admite Key-Value observando (KVO) en iOS o es DependencyProperty en UWP. Sin
embargo, muchas vistas nativas no admiten la notificación de cambio de propiedad. Para estas vistas, puede
especificar un UpdateSourceEventName valor de propiedad como parte de la expresión de enlace. Esta propiedad
debe establecerse en el nombre de un evento en la vista nativa que indica cuándo ha cambiado la propiedad de
destino. A continuación, cuando cambia el valor del modificador nativo, Binding se notifica a la clase que el
usuario ha cambiado el valor del modificador y el valor de la NativeSwitchPageViewModel.IsSwitchOn propiedad se
ha actualizado.
Pasar argumentos a vistas nativas
Los argumentos de constructor se pueden pasar a vistas nativas mediante el x:Arguments atributo con una
x:Static extensión de marcado. Además, se puede llamar a los métodos del generador de vistas nativas (
public static métodos que devuelven objetos o valores del mismo tipo que la clase o estructura que define los
métodos) especificando el nombre del método mediante el x:FactoryMethod atributo y sus argumentos mediante
el x:Arguments atributo.
En el ejemplo de código siguiente se muestran ambas técnicas:

<ContentPage ...
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidGraphics="clr-namespace:Android.Graphics;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:winText="clr-namespace:Windows.UI.Text;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:winui="clr-namespace:Windows.UI;assembly=Windows, Version=255.255.255.255, Culture=neutral,
PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows">
...
<ios:UILabel Text="Simple Native Color Picker" View.HorizontalOptions="Center">
<ios:UILabel.Font>
<ios:UIFont x:FactoryMethod="FromName">
<x:Arguments>
<x:String>Papyrus</x:String>
<x:Single>24</x:Single>
</x:Arguments>
</ios:UIFont>
</ios:UILabel.Font>
</ios:UILabel>
<androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
Text="Simple Native Color Picker"
TextSize="24"
View.HorizontalOptions="Center">
<androidWidget:TextView.Typeface>
<androidGraphics:Typeface x:FactoryMethod="Create">
<x:Arguments>
<x:String>cursive</x:String>
<androidGraphics:TypefaceStyle>Normal</androidGraphics:TypefaceStyle>
</x:Arguments>
</androidGraphics:Typeface>
</androidWidget:TextView.Typeface>
</androidWidget:TextView>
<winControls:TextBlock Text="Simple Native Color Picker"
FontSize="20"
FontStyle="{x:Static winText:FontStyle.Italic}"
View.HorizontalOptions="Center">
<winControls:TextBlock.FontFamily>
<winMedia:FontFamily>
<x:Arguments>
<x:String>Georgia</x:String>
</x:Arguments>
</winMedia:FontFamily>
</winControls:TextBlock.FontFamily>
</winControls:TextBlock>
...
</ContentPage>

El UIFont.FromName Factory Method se usa para establecer la UILabel.Font propiedad en un nuevo UIFont en iOS.
El UIFont nombre y el tamaño se especifican mediante los argumentos del método que son elementos
secundarios del x:Arguments atributo.
El Typeface.Create Factory Method se usa para establecer la TextView.Typeface propiedad en un nuevo Typeface
en Android. El Typeface nombre de familia y el estilo se especifican mediante los argumentos del método que son
elementos secundarios del x:Arguments atributo.
El FontFamily constructor se usa para establecer la TextBlock.FontFamily propiedad en una nueva FontFamily en
el plataforma universal de Windows (UWP). El FontFamily nombre se especifica mediante el argumento de
método que es un elemento secundario del x:Arguments atributo.

NOTE
Los argumentos deben coincidir con los tipos requeridos por el constructor o Factory Method.

Las siguientes capturas de pantallas muestran el resultado de la especificación de Factory Method y argumentos de
constructor para establecer la fuente en diferentes vistas nativas:

Para obtener más información sobre cómo pasar argumentos en XAML, vea pasar argumentos en XAML.

Referencia a vistas nativas desde el código


Aunque no es posible asignar un nombre a una vista nativa con el x:Name atributo, es posible recuperar una
instancia de vista nativa declarada en un archivo XAML de su archivo de código subyacente en un proyecto de
acceso compartido, siempre que la vista nativa sea un elemento secundario de un ContentView que especifique un
x:Name valor de atributo. Después, dentro de las directivas de compilación condicional en el archivo de código
subyacente, debe:
1. Recupere el ContentView.Content valor de propiedad y conviértalo a un tipo específico de la plataforma
NativeViewWrapper .
2. Recupere la NativeViewWrapper.NativeElement propiedad y conviértala en el tipo de vista nativa.

A continuación, se puede invocar la API nativa en la vista nativa para realizar las operaciones deseadas. Este
enfoque también ofrece la ventaja de que varias vistas nativas de XAML para distintas plataformas pueden ser
elementos secundarios del mismo ContentView . En el ejemplo de código siguiente se muestra esta técnica:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:local="clr-namespace:NativeViewInsideContentView"
x:Class="NativeViewInsideContentView.NativeViewInsideContentViewPage">
<StackLayout Margin="20">
<ContentView x:Name="contentViewTextParent" HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<ios:UILabel Text="Text in a UILabel" TextColor="{x:Static ios:UIColor.Red}" />
<androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
Text="Text in a TextView" />
<winControls:TextBlock Text="Text in a TextBlock" />
</ContentView>
<ContentView x:Name="contentViewButtonParent" HorizontalOptions="Center"
VerticalOptions="EndAndExpand">
<ios:UIButton TouchUpInside="OnButtonTap" View.HorizontalOptions="Center"
View.VerticalOptions="Center" />
<androidWidget:Button x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
Text="Scale and Rotate Text"
Click="OnButtonTap" />
<winControls:Button Content="Scale and Rotate Text" />
</ContentView>
</StackLayout>
</ContentPage>

En el ejemplo anterior, las vistas nativas de cada plataforma son elementos secundarios de ContentView los
controles, con el x:Name valor de atributo que se usa para recuperar ContentView en el código subyacente:
public partial class NativeViewInsideContentViewPage : ContentPage
{
public NativeViewInsideContentViewPage()
{
InitializeComponent();

#if __IOS__
var wrapper = (:::no-
loc(Xamarin.Forms):::.Platform.iOS.NativeViewWrapper)contentViewButtonParent.Content;
var button = (UIKit.UIButton)wrapper.NativeView;
button.SetTitle("Scale and Rotate Text", UIKit.UIControlState.Normal);
button.SetTitleColor(UIKit.UIColor.Black, UIKit.UIControlState.Normal);
#endif
#if __ANDROID__
var wrapper = (:::no-
loc(Xamarin.Forms):::.Platform.Android.NativeViewWrapper)contentViewTextParent.Content;
var textView = (Android.Widget.TextView)wrapper.NativeView;
textView.SetTextColor(Android.Graphics.Color.Red);
#endif
#if WINDOWS_UWP
var textWrapper = (:::no-
loc(Xamarin.Forms):::.Platform.UWP.NativeViewWrapper)contentViewTextParent.Content;
var textBlock = (Windows.UI.Xaml.Controls.TextBlock)textWrapper.NativeElement;
textBlock.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.Red);
var buttonWrapper = (:::no-
loc(Xamarin.Forms):::.Platform.UWP.NativeViewWrapper)contentViewButtonParent.Content;
var button = (Windows.UI.Xaml.Controls.Button)buttonWrapper.NativeElement;
button.Click += (sender, args) => OnButtonTap(sender, EventArgs.Empty);
#endif
}

async void OnButtonTap(object sender, EventArgs e)


{
contentViewButtonParent.Content.IsEnabled = false;
contentViewTextParent.Content.ScaleTo(2, 2000);
await contentViewTextParent.Content.RotateTo(360, 2000);
contentViewTextParent.Content.ScaleTo(1, 2000);
await contentViewTextParent.Content.RelRotateTo(360, 2000);
contentViewButtonParent.Content.IsEnabled = true;
}
}

ContentView.Content Se tiene acceso a la propiedad para recuperar la vista nativa ajustada como una instancia
específica de la plataforma NativeViewWrapper . A NativeViewWrapper.NativeElement continuación, se tiene acceso a
la propiedad para recuperar la vista nativa como su tipo nativo. A continuación, se invoca la API de la vista nativa
para realizar las operaciones deseadas.
Los botones nativos de iOS y Android comparten el mismo OnButtonTap controlador de eventos, porque cada
botón nativo consume un EventHandler delegado en respuesta a un evento Touch. Sin embargo, el Plataforma
universal de Windows (UWP) usa un independiente RoutedEventHandler que, a su vez, utiliza el OnButtonTap
controlador de eventos en este ejemplo. Por consiguiente, cuando se hace clic en un botón nativo, el OnButtonTap
controlador de eventos ejecuta, que escala y gira el control nativo que se encuentra dentro del ContentView
denominado contentViewTextParent . Las capturas de pantallas siguientes demuestran que esto sucede en cada
plataforma:
Vistas nativas de subclase
Muchas vistas nativas de iOS y Android no son adecuadas para crear instancias en XAML porque usan métodos, en
lugar de propiedades, para configurar el control. La solución a este problema es la subclase de vistas nativas en
contenedores que definen una API más compatible con XAML que usa las propiedades para configurar el control y
que usa eventos independientes de la plataforma. Las vistas nativas ajustadas se pueden colocar en un proyecto de
recursos compartidos (SAP) y encerrarse con directivas de compilación condicional, o bien colocarse en proyectos
específicos de la plataforma y se puede hacer referencia a ella desde XAML en un proyecto de biblioteca de .NET
Standard.
En el ejemplo de código siguiente se muestra una :::no-loc(Xamarin.Forms)::: página que utiliza vistas nativas de
subclases:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:iosLocal="clr-
namespace:SubclassedNativeControls.iOS;assembly=SubclassedNativeControls.iOS;targetPlatform=iOS"
xmlns:android="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
xmlns:androidLocal="clr-
namespace:SubclassedNativeControls.Droid;assembly=SubclassedNativeControls.Droid;targetPlatform=Android"
xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
xmlns:local="clr-namespace:SubclassedNativeControls"
x:Class="SubclassedNativeControls.SubclassedNativeControlsPage">
<StackLayout Margin="20">
<Label Text="Subclassed Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
<Label Text="You have chosen:" />
<Label Text="{Binding SelectedFruit}" />
</StackLayout>
<iosLocal:MyUIPickerView ItemsSource="{Binding Fruits}"
SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectedItemChanged}" />
<androidLocal:MySpinner x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
ItemsSource="{Binding Fruits}"
SelectedObject="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=ItemSelected}" />
<winControls:ComboBox ItemsSource="{Binding Fruits}"
SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectionChanged}" />
</StackLayout>
</ContentPage>

La página contiene un Label que muestra los frutos elegidos por el usuario desde un control nativo. Label Enlaza
a la SubclassedNativeControlsPageViewModel.SelectedFruit propiedad. La BindingContext de la página se establece
en una nueva instancia de la SubclassedNativeControlsPageViewModel clase en el archivo de código subyacente, con
la clase ViewModel que implementa la INotifyPropertyChanged interfaz.
La página también contiene una vista de selector nativa para cada plataforma. Cada vista nativa muestra la
colección de Fruits enlazando su ItemSource propiedad a la SubclassedNativeControlsPageViewModel.Fruits
colección. Esto permite al usuario seleccionar una fruta, tal como se muestra en las siguientes capturas de
pantallas:
En iOS y Android, los selectores nativos usan métodos para configurar los controles. Por lo tanto, estos selectores
deben ser subclases para exponer propiedades con el fin de que sean compatibles con XAML. En el Plataforma
universal de Windows (UWP), ComboBox ya es compatible con XAML y, por tanto, no requiere la subclase.
iOS
La implementación de iOS subclase la UIPickerView vista y expone propiedades y un evento que se puede
consumir fácilmente desde XAML:
public class MyUIPickerView : UIPickerView
{
public event EventHandler<EventArgs> SelectedItemChanged;

public MyUIPickerView()
{
var model = new PickerModel();
model.ItemChanged += (sender, e) =>
{
if (SelectedItemChanged != null)
{
SelectedItemChanged.Invoke(this, e);
}
};
Model = model;
}

public IList<string> ItemsSource


{
get
{
var pickerModel = Model as PickerModel;
return (pickerModel != null) ? pickerModel.Items : null;
}
set
{
var model = Model as PickerModel;
if (model != null)
{
model.Items = value;
}
}
}

public string SelectedItem


{
get { return (Model as PickerModel).SelectedItem; }
set { }
}
}

La MyUIPickerView clase expone ItemsSource SelectedItem las propiedades y, y un SelectedItemChanged evento.


Un UIPickerView requiere un UIPickerViewModel modelo de datos subyacente, al que se tiene acceso mediante las
MyUIPickerView propiedades y el evento. La UIPickerViewModel clase proporciona el modelo de datos PickerModel
:
class PickerModel : UIPickerViewModel
{
int selectedIndex = 0;
public event EventHandler<EventArgs> ItemChanged;
public IList<string> Items { get; set; }

public string SelectedItem


{
get
{
return Items != null && selectedIndex >= 0 && selectedIndex < Items.Count ? Items[selectedIndex] :
null;
}
}

public override nint GetRowsInComponent(UIPickerView pickerView, nint component)


{
return Items != null ? Items.Count : 0;
}

public override string GetTitle(UIPickerView pickerView, nint row, nint component)


{
return Items != null && Items.Count > row ? Items[(int)row] : null;
}

public override nint GetComponentCount(UIPickerView pickerView)


{
return 1;
}

public override void Selected(UIPickerView pickerView, nint row, nint component)


{
selectedIndex = (int)row;
if (ItemChanged != null)
{
ItemChanged.Invoke(this, new EventArgs());
}
}
}

La PickerModelclase proporciona el almacenamiento subyacente para la MyUIPickerView clase, a través de la


Items propiedad. Cada vez que cambia el elemento seleccionado en el MyUIPickerView , Selected se ejecuta el
método, que actualiza el índice seleccionado y activa el ItemChanged evento. Esto garantiza que la SelectedItem
propiedad siempre devolverá el último elemento seleccionado por el usuario. Además, la PickerModel clase
invalida los métodos que se usan para configurar la instancia de MyUIPickerView .
Android
La implementación de Android subclase la Spinner vista y expone propiedades y un evento que se puede
consumir fácilmente desde XAML:
class MySpinner : Spinner
{
ArrayAdapter adapter;
IList<string> items;

public IList<string> ItemsSource


{
get { return items; }
set
{
if (items != value)
{
items = value;
adapter.Clear();

foreach (string str in items)


{
adapter.Add(str);
}
}
}
}

public string SelectedObject


{
get { return (string)GetItemAtPosition(SelectedItemPosition); }
set
{
if (items != null)
{
int index = items.IndexOf(value);
if (index != -1)
{
SetSelection(index);
}
}
}
}

public MySpinner(Context context) : base(context)


{
ItemSelected += OnBindableSpinnerItemSelected;

adapter = new ArrayAdapter(context, Android.Resource.Layout.SimpleSpinnerItem);


adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
Adapter = adapter;
}

void OnBindableSpinnerItemSelected(object sender, ItemSelectedEventArgs args)


{
SelectedObject = (string)GetItemAtPosition(args.Position);
}
}

La MySpinner clase expone ItemsSource SelectedObject las propiedades y, y un ItemSelected evento. Los
elementos que muestra la MySpinner clase los proporciona el Adapter asociado a la vista, y los elementos se
rellenan en Adapter cuando ItemsSource se establece la propiedad por primera vez. Cada vez que el elemento
seleccionado en la MySpinner clase cambia, el OnBindableSpinnerItemSelected controlador de eventos actualiza la
SelectedObject propiedad.

Vínculos relacionados
NativeSwitch (ejemplo)
Forms2Native (ejemplo)
NativeViewInsideContentView (ejemplo)
SubclassedNativeControls (ejemplo)
Formularios nativos
Paso de argumentos en XAML
Vistas nativas en C#
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
Se puede hacer referencia directamente a las vistas nativas de iOS, Android y UWP desde :::no-loc(Xamarin.Forms):::
las páginas creadas con C#. En este artículo se muestra cómo agregar vistas nativas a un :::no-loc(Xamarin.Forms):::
diseño creado con C# y cómo invalidar el diseño de vistas personalizadas para corregir el uso de la API de
medición.

Información general
Cualquier :::no-loc(Xamarin.Forms)::: control que permita Content establecer o que tenga una Children colección
puede agregar vistas específicas de la plataforma. Por ejemplo, un iOS UILabel se puede agregar directamente a la
ContentView.Content propiedad o a la StackLayout.Children colección. Sin embargo, tenga en cuenta que esta
funcionalidad requiere el uso de #if definiciones en :::no-loc(Xamarin.Forms)::: soluciones de proyecto
compartidas y no está disponible en las :::no-loc(Xamarin.Forms)::: soluciones de biblioteca de .net Standard.
En las siguientes capturas de pantallas se muestran vistas específicas de la plataforma que se han agregado a un
:::no-loc(Xamarin.Forms)::: StackLayout :

La capacidad de agregar vistas específicas de la plataforma a un :::no-loc(Xamarin.Forms)::: diseño está habilitada


por dos métodos de extensión en cada plataforma:
Add : agrega una vista específica de la plataforma a la Children colección de un diseño.
ToView: toma una vista específica de la plataforma y la ajusta como una :::no-loc(Xamarin.Forms)::: View que
se puede establecer como la Content propiedad de un control.
El uso de estos métodos en un :::no-loc(Xamarin.Forms)::: proyecto compartido requiere la importación del espacio
de nombres específico de la plataforma adecuado :::no-loc(Xamarin.Forms)::: :
iOS : :::no-loc(Xamarin.Forms):::.Platform.iOS
Android : :::no-loc(Xamarin.Forms):::.Platform.Android
Plataforma universal de Windows (UWP) : :::no-loc(Xamarin.Forms):::.Platform.UWP

Adición de Platform-Specific vistas en cada plataforma


En las secciones siguientes se muestra cómo agregar vistas específicas de la plataforma a un :::no-
loc(Xamarin.Forms)::: diseño en cada plataforma.
iOS
En el ejemplo de código siguiente se muestra cómo agregar un UILabel a un StackLayout y un ContentView :

var uiLabel = new UILabel {


MinimumFontSize = 14f,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();

En el ejemplo se supone que las stackLayout instancias de y contentView se han creado previamente en XAML o
C#.
Android
En el ejemplo de código siguiente se muestra cómo agregar un TextView a un StackLayout y un ContentView :

var textView = new TextView (MainActivity.Instance) { Text = originalText, TextSize = 14 };


stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();

En el ejemplo se supone que las stackLayout instancias de y contentView se han creado previamente en XAML o
C#.
Plataforma universal de Windows
En el ejemplo de código siguiente se muestra cómo agregar un TextBlock a un StackLayout y un ContentView :

var textBlock = new TextBlock


{
Text = originalText,
FontSize = 14,
FontFamily = new FontFamily("HelveticaNeue"),
TextWrapping = TextWrapping.Wrap
};
stackLayout.Children.Add(textBlock);
contentView.Content = textBlock.ToView();

En el ejemplo se supone que las stackLayout instancias de y contentView se han creado previamente en XAML o
C#.
Invalidar medidas de plataforma para vistas personalizadas
Las vistas personalizadas en cada plataforma a menudo solo implementan correctamente la medida para el
escenario de diseño para el que se diseñaron. Por ejemplo, una vista personalizada puede haberse diseñado para
ocupar solo la mitad del ancho disponible del dispositivo. Sin embargo, después de compartirse con otros
usuarios, la vista personalizada puede ser necesaria para ocupar el ancho total disponible del dispositivo. Por lo
tanto, puede ser necesario invalidar una implementación de medida de vistas personalizadas cuando se reutiliza en
un :::no-loc(Xamarin.Forms)::: diseño. Por ese motivo, los Add ToView métodos de extensión y proporcionan
invalidaciones que permiten especificar los delegados de medida, lo que puede invalidar el diseño de la vista
personalizada cuando se agrega a un :::no-loc(Xamarin.Forms)::: diseño.
En las secciones siguientes se muestra cómo invalidar el diseño de vistas personalizadas para corregir el uso de la
API de medición.
iOS
En el ejemplo de código siguiente se muestra la CustomControl clase, que se hereda de UILabel :

public class CustomControl : UILabel


{
public override string Text {
get { return base.Text; }
set { base.Text = value.ToUpper (); }
}

public override CGSize SizeThatFits (CGSize size)


{
return new CGSize (size.Width, 150);
}
}

Una instancia de esta vista se agrega a un StackLayout , como se muestra en el ejemplo de código siguiente:

var customControl = new CustomControl {


MinimumFontSize = 14,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = "This control has incorrect sizing - there's empty space above and below it."
};
stackLayout.Children.Add (customControl);

Sin embargo, dado que la CustomControl.SizeThatFits invalidación siempre devuelve un alto de 150, la vista se
mostrará con un espacio vacío por encima y por debajo, tal como se muestra en la siguiente captura de pantalla:

Una solución a este problema es proporcionar una GetDesiredSizeDelegate implementación, como se muestra en
el ejemplo de código siguiente:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, double width, double height)


{
var uiView = renderer.Control;

if (uiView == null) {
return null;
}

var constraint = new CGSize (width, height);

// Let the CustomControl determine its size (which will be wrong)


var badRect = uiView.SizeThatFits (constraint);

// Use the width and substitute the height


return new SizeRequest (new Size (badRect.Width, 70));
}

Este método usa el ancho proporcionado por el CustomControl.SizeThatFits método, pero sustituye el alto de 150
por un alto de 70. Cuando CustomControl se agrega la instancia a StackLayout , se FixSize puede especificar el
método como GetDesiredSizeDelegate para corregir la medida incorrecta proporcionada por la CustomControl
clase:

stackLayout.Children.Add (customControl, FixSize);

Esto hace que la vista personalizada se muestre correctamente, sin espacio vacío por encima y por debajo, tal como
se muestra en la siguiente captura de pantalla:

Android
En el ejemplo de código siguiente se muestra la CustomControl clase, que se hereda de TextView :
public class CustomControl : TextView
{
public CustomControl (Context context) : base (context)
{
}

protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)


{
int width = MeasureSpec.GetSize (widthMeasureSpec);

// Force the width to half of what's been requested.


// This is deliberately wrong to demonstrate providing an override to fix it with.
int widthSpec = MeasureSpec.MakeMeasureSpec (width / 2, MeasureSpec.GetMode (widthMeasureSpec));

base.OnMeasure (widthSpec, heightMeasureSpec);


}
}

Una instancia de esta vista se agrega a un StackLayout , como se muestra en el ejemplo de código siguiente:

var customControl = new CustomControl (MainActivity.Instance) {


Text = "This control has incorrect sizing - it doesn't occupy the available width of the device.",
TextSize = 14
};
stackLayout.Children.Add (customControl);

Sin embargo, dado que la CustomControl.OnMeasure invalidación siempre devuelve la mitad del ancho solicitado, se
mostrará la vista ocupando solo la mitad del ancho disponible del dispositivo, tal como se muestra en la siguiente
captura de pantalla:

Una solución a este problema es proporcionar una GetDesiredSizeDelegate implementación, como se muestra en
el ejemplo de código siguiente:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, int widthConstraint, int heightConstraint)


{
var nativeView = renderer.Control;

if ((widthConstraint == 0 && heightConstraint == 0) || nativeView == null) {


return null;
}

int width = Android.Views.View.MeasureSpec.GetSize (widthConstraint);


int widthSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec (
width * 2, Android.Views.View.MeasureSpec.GetMode (widthConstraint));
nativeView.Measure (widthSpec, heightConstraint);
return new SizeRequest (new Size (nativeView.MeasuredWidth, nativeView.MeasuredHeight));
}

Este método usa el ancho proporcionado por el CustomControl.OnMeasure método, pero lo multiplica por dos.
Cuando CustomControl se agrega la instancia a StackLayout , se FixSize puede especificar el método como
GetDesiredSizeDelegate para corregir la medida incorrecta proporcionada por la CustomControl clase:
stackLayout.Children.Add (customControl, FixSize);

Esto hace que la vista personalizada se muestre correctamente, ocupando el ancho del dispositivo, tal como se
muestra en la siguiente captura de pantalla:

Plataforma universal de Windows


En el ejemplo de código siguiente se muestra la CustomControl clase, que se hereda de Panel :
public class CustomControl : Panel
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text", typeof(string), typeof(CustomControl), new PropertyMetadata(default(string),
OnTextPropertyChanged));

public string Text


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

readonly TextBlock textBlock;

public CustomControl()
{
textBlock = new TextBlock
{
MinHeight = 0,
MaxHeight = double.PositiveInfinity,
MinWidth = 0,
MaxWidth = double.PositiveInfinity,
FontSize = 14,
TextWrapping = TextWrapping.Wrap,
VerticalAlignment = VerticalAlignment.Center
};

Children.Add(textBlock);
}

static void OnTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs


args)
{
((CustomControl)dependencyObject).textBlock.Text = (string)args.NewValue;
}

protected override Size ArrangeOverride(Size finalSize)


{
// This is deliberately wrong to demonstrate providing an override to fix it with.
textBlock.Arrange(new Rect(0, 0, finalSize.Width/2, finalSize.Height));
return finalSize;
}

protected override Size MeasureOverride(Size availableSize)


{
textBlock.Measure(availableSize);
return new Size(textBlock.DesiredSize.Width, textBlock.DesiredSize.Height);
}
}

Una instancia de esta vista se agrega a un StackLayout , como se muestra en el ejemplo de código siguiente:

var brokenControl = new CustomControl {


Text = "This control has incorrect sizing - it doesn't occupy the available width of the device."
};
stackLayout.Children.Add(brokenControl);

Sin embargo, dado que la CustomControl.ArrangeOverride invalidación siempre devuelve la mitad del ancho
solicitado, la vista se recortará hasta la mitad del ancho disponible del dispositivo, tal como se muestra en la
siguiente captura de pantalla:
Una solución a este problema es proporcionar una ArrangeOverrideDelegate implementación, al agregar la vista a
StackLayout , tal y como se muestra en el ejemplo de código siguiente:

stackLayout.Children.Add(fixedControl, arrangeOverrideDelegate: (renderer, finalSize) =>


{
if (finalSize.Width <= 0 || double.IsInfinity(finalSize.Width))
{
return null;
}
var frameworkElement = renderer.Control;
frameworkElement.Arrange(new Rect(0, 0, finalSize.Width * 2, finalSize.Height));
return finalSize;
});

Este método usa el ancho proporcionado por el CustomControl.ArrangeOverride método, pero lo multiplica por dos.
Esto hace que la vista personalizada se muestre correctamente, ocupando el ancho del dispositivo, tal como se
muestra en la siguiente captura de pantalla:

Resumen
En este artículo se ha explicado cómo agregar vistas nativas a un :::no-loc(Xamarin.Forms)::: diseño creado con C# y
cómo invalidar el diseño de vistas personalizadas para corregir el uso de la API de medición.

Vínculos relacionados
NativeEmbedding (ejemplo)
Formularios nativos
Iniciar sesión con Apple enXamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Iniciar sesión con Apple presenta un nuevo servicio que proporciona protección de identidad para los usuarios. A
partir de iOS 13 Apple requiere que las aplicaciones que usan proveedores de autenticación de terceros también
ofrezcan inicio de sesión con Apple. Para obtener instrucciones sobre el uso de esta característica con Xamarin. iOS,
Consulte más información aquí.
Al admitir el inicio de sesión con Apple en una Xamarin.Forms solución, existen consideraciones adicionales que se
deben tener en cuenta para Android y UWP. Para esas plataformas, Apple proporciona un flujo de trabajo diferente.

Configuración de Xamarin. iOS


En esta guía se describe la configuración necesaria para habilitar el inicio de sesión con Apple para las aplicaciones
de Xamarin. iOS.

Configuración para otras plataformas


En esta guía se describe la configuración necesaria para habilitar el inicio de sesión con Apple para otras
plataformas, como Xamarin.Forms Android y UWP.

Usar el inicio de sesión con Apple enXamarin.Forms


Con unos pocos servicios, puede admitir el inicio de sesión con Apple en Xamarin.Forms las aplicaciones
multiplataforma. En esta guía se describen los pasos necesarios.
Inicio de sesión con Apple en Xamarin. iOS
18/12/2020 • 6 minutes to read • Edit Online

Descargar el ejemplo
Iniciar sesión con Apple es un nuevo servicio que proporciona protección de identidad para los usuarios de
servicios de autenticación de terceros. A partir de iOS 13, Apple requiere que cualquier aplicación nueva que use
servicios de autenticación de terceros también debe proporcionar el inicio de sesión con Apple. Las aplicaciones
existentes que se están actualizando no necesitan agregar el inicio de sesión con Apple hasta el 2020 de abril.
En este documento se explica cómo puede Agregar inicio de sesión con Apple a aplicaciones de iOS 13.

Configuración de Apple Developer


Antes de compilar y ejecutar una aplicación con el inicio de sesión con Apple, debe completar estos pasos. En
certificados de desarrollador de Apple, identificadores & portal de perfiles :
1. Cree un nuevo identificador de ID. de aplicación .
2. Establezca una descripción en el campo Descripción .
3. Elija un identificador de agrupación explícito y establezca com.xamarin.AddingTheSignInWithAppleFlowToYourApp
en el campo.
4. Habilite el Inicio de sesión con la funcionalidad de Apple y registre la nueva identidad.
5. Cree un nuevo perfil de aprovisionamiento con la nueva identidad.
6. Descárguelo e instálelo en el dispositivo.
7. En Visual Studio, habilite el archivo de Inicio de sesión con la funcionalidad de Apple en derechos. plist .

Comprobar el estado de inicio de sesión


Cuando se inicie la aplicación, o cuando necesite comprobar por primera vez el estado de autenticación de un
usuario, cree una instancia de ASAuthorizationAppleIdProvider y compruebe el estado actual:
var appleIdProvider = new ASAuthorizationAppleIdProvider ();
appleIdProvider.GetCredentialState (KeychainItem.CurrentUserIdentifier, (credentialState, error) => {
switch (credentialState) {
case ASAuthorizationAppleIdProviderCredentialState.Authorized:
// The Apple ID credential is valid.
break;
case ASAuthorizationAppleIdProviderCredentialState.Revoked:
// The Apple ID credential is revoked.
break;
case ASAuthorizationAppleIdProviderCredentialState.NotFound:
// No credential was found, so show the sign-in UI.
InvokeOnMainThread (() => {
var storyboard = UIStoryboard.FromName ("Main", null);

if (!(storyboard.InstantiateViewController (nameof (LoginViewController)) is LoginViewController


viewController))
return;

viewController.ModalPresentationStyle = UIModalPresentationStyle.FormSheet;
viewController.ModalInPresentation = true;
Window?.RootViewController?.PresentViewController (viewController, true, null);
});
break;
}
});

En este código, al que se llama durante FinishedLaunching en AppDelegate.cs , la aplicación se encargará de que
el estado sea NotFound y presente al LoginViewController usuario. Si el estado ha devuelto Authorized o
Revoked , se puede presentar una acción diferente al usuario.

Un LoginViewController para iniciar sesión con Apple


UIViewControllerQue implementa la lógica de inicio de sesión y proporciona inicio de sesión con Apple debe
implementar IASAuthorizationControllerDelegate y IASAuthorizationControllerPresentationContextProviding
como en el LoginViewController ejemplo siguiente.
public partial class LoginViewController : UIViewController, IASAuthorizationControllerDelegate,
IASAuthorizationControllerPresentationContextProviding {
public LoginViewController (IntPtr handle) : base (handle)
{
}

public override void ViewDidLoad ()


{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.

SetupProviderLoginView ();
}

public override void ViewDidAppear (bool animated)


{
base.ViewDidAppear (animated);

PerformExistingAccountSetupFlows ();
}

void SetupProviderLoginView ()
{
var authorizationButton = new ASAuthorizationAppleIdButton (ASAuthorizationAppleIdButtonType.Default,
ASAuthorizationAppleIdButtonStyle.White);
authorizationButton.TouchUpInside += HandleAuthorizationAppleIDButtonPress;
loginProviderStackView.AddArrangedSubview (authorizationButton);
}

// Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
void PerformExistingAccountSetupFlows ()
{
// Prepare requests for both Apple ID and password providers.
ASAuthorizationRequest [] requests = {
new ASAuthorizationAppleIdProvider ().CreateRequest (),
new ASAuthorizationPasswordProvider ().CreateRequest ()
};

// Create an authorization controller with the given requests.


var authorizationController = new ASAuthorizationController (requests);
authorizationController.Delegate = this;
authorizationController.PresentationContextProvider = this;
authorizationController.PerformRequests ();
}

private void HandleAuthorizationAppleIDButtonPress (object sender, EventArgs e)


{
var appleIdProvider = new ASAuthorizationAppleIdProvider ();
var request = appleIdProvider.CreateRequest ();
request.RequestedScopes = new [] { ASAuthorizationScope.Email, ASAuthorizationScope.FullName };

var authorizationController = new ASAuthorizationController (new [] { request });


authorizationController.Delegate = this;
authorizationController.PresentationContextProvider = this;
authorizationController.PerformRequests ();
}
}
En este código de ejemplo se comprueba el estado de inicio de sesión actual en PerformExistingAccountSetupFlows
y se conecta a la vista actual como delegado. Si se encuentra una credencial existente de la cadena de claves de
iCloud o una credencial de identificador de Apple, se le pedirá al usuario que la use.
Apple proporciona ASAuthorizationAppleIdButton un botón específicamente para este propósito. Cuando se toca,
el botón desencadenará el flujo de trabajo controlado en el método HandleAuthorizationAppleIDButtonPress .

Controlar la autorización
En, IASAuthorizationController implemente cualquier lógica personalizada para almacenar la cuenta de usuario.
En el ejemplo siguiente se almacena la cuenta del usuario en la cadena de claves, el propio servicio de
almacenamiento de Apple.
#region IASAuthorizationController Delegate

[Export ("authorizationController:didCompleteWithAuthorization:")]
public void DidComplete (ASAuthorizationController controller, ASAuthorization authorization)
{
if (authorization.GetCredential<ASAuthorizationAppleIdCredential> () is ASAuthorizationAppleIdCredential
appleIdCredential) {
var userIdentifier = appleIdCredential.User;
var fullName = appleIdCredential.FullName;
var email = appleIdCredential.Email;

// Create an account in your system.


// For the purpose of this demo app, store the userIdentifier in the keychain.
try {
new KeychainItem ("com.example.apple-samplecode.juice", "userIdentifier").SaveItem
(userIdentifier);
} catch (Exception) {
Console.WriteLine ("Unable to save userIdentifier to keychain.");
}

// For the purpose of this demo app, show the Apple ID credential information in the
ResultViewController.
if (!(PresentingViewController is ResultViewController viewController))
return;

InvokeOnMainThread (() => {


viewController.UserIdentifierText = userIdentifier;
viewController.GivenNameText = fullName?.GivenName ?? "";
viewController.FamilyNameText = fullName?.FamilyName ?? "";
viewController.EmailText = email ?? "";

DismissViewController (true, null);


});
} else if (authorization.GetCredential<ASPasswordCredential> () is ASPasswordCredential
passwordCredential) {
// Sign in using an existing iCloud Keychain credential.
var username = passwordCredential.User;
var password = passwordCredential.Password;

// For the purpose of this demo app, show the password credential as an alert.
InvokeOnMainThread (() => {
var message = $"The app has received your selected credential from the keychain. \n\n Username:
{username}\n Password: {password}";
var alertController = UIAlertController.Create ("Keychain Credential Received", message,
UIAlertControllerStyle.Alert);
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));

PresentViewController (alertController, true, null);


});
}
}

[Export ("authorizationController:didCompleteWithError:")]
public void DidComplete (ASAuthorizationController controller, NSError error)
{
Console.WriteLine (error);
}

#endregion

Controlador de autorización
La parte final de esta implementación es la ASAuthorizationController que administra las solicitudes de
autorización para el proveedor.
#region IASAuthorizationControllerPresentation Context Providing

public UIWindow GetPresentationAnchor (ASAuthorizationController controller) => View.Window;

#endregion

Vínculos relacionados
Inicio de sesión con las directrices de Apple
Inicie sesión con el derecho de Apple.
Sesión de WWDC 2019 706: Introducción al inicio de sesión con Apple.
Configuración iniciar sesión con Apple para Xamarin. Forms
Configurar inicio de sesión con Apple
paraXamarin.Forms
18/12/2020 • 6 minutes to read • Edit Online

En esta guía se describe la serie de pasos necesarios para configurar las aplicaciones multiplataforma con el fin de
seguir el inicio de sesión con Apple. Aunque el programa de instalación de Apple es sencillo en el portal para
desarrolladores de Apple, es necesario realizar pasos adicionales para crear una relación segura entre Android y
Apple.

Configuración de Apple Developer


Antes de poder usar el inicio de sesión con Apple en sus aplicaciones, debe abordar algunos pasos de
configuración de la sección certificados, identificadores & perfiles del portal para desarrolladores de Apple.
Dominio de inicio de sesión de Apple
Registre el nombre de dominio y compruébelo con Apple en la sección más de la sección certificados,
identificadores & perfiles .

Agregue el dominio y haga clic en registrar .

NOTE
Si ve un error que le informará de que el dominio no es compatible con SPF, deberá agregar un registro TXT de DNS SPF a su
dominio y esperar a que se propague antes de continuar: el TXT SPF puede tener un aspecto similar al siguiente:
v=spf1 a a:myapp.com -all

A continuación, tendrá que comprobar la propiedad del dominio haciendo clic en Descargar para recuperar el
apple-developer-domain-association.txt archivo y cargarlo en la .well-known carpeta del sitio web del dominio.
Una vez .well-known/apple-developer-domain-association.txt cargado el archivo y accesible, puede hacer clic en
comprobar para que Apple Compruebe la propiedad del dominio.

NOTE
Apple comprobará la propiedad con https:// . Asegúrese de que tiene la configuración de SSL y de que el archivo es
accesible a través de una dirección URL segura.

Este proceso se completó correctamente antes de continuar.

Configuración del identificador de la aplicación


En la sección identificadores , cree un nuevo identificador y elija ID. de aplicación . Si ya tiene un ID. de aplicación,
elija editarlo en su lugar.

Habilite el Inicio de sesión con Apple . Lo más probable es que quiera usar la opción Habilitar como ID. de
aplicación principal .

Guarde los cambios en el identificador de la aplicación.

Crear un identificador de servicio


En la sección identificadores , cree un nuevo identificador y elija ID. de ser vicio .

Asigne a su ID. de servicios una descripción y un identificador. Este identificador será su ServerId . Asegúrese de
habilitar el Inicio de sesión con Apple .
Antes de continuar, haga clic en configurar junto a la opción iniciar sesión con Apple habilitada.
En el panel Configuración, asegúrese de que está seleccionado el identificador de aplicación principal
correcto.
A continuación, elija el dominio web que configuró anteriormente.
Por último, agregue una o más direcciones URL de retorno . Cualquier redirect_uri usuario que use más
adelante debe registrarse aquí exactamente como lo usa. Asegúrese http:// de incluir o https:// en la dirección
URL cuando la escriba.

NOTE
Con fines de prueba, no puede usar 127.0.0.1 ni localhost , pero puede usar otros dominios como local.test . Si
decide hacerlo, puede editar el archivo del equipo hosts para resolver este dominio ficticio en la dirección IP local.

Guarde los cambios cuando termine.

Creación de una clave para el identificador de servicios


En la sección claves , cree una nueva clave .
Asigne un nombre a la clave y habilite el Inicio de sesión con Apple .
Haga clic en configurar después iniciar sesión con Apple.
Asegúrese de que el identificador de aplicación principal correcto está seleccionado y haga clic en Guardar .
Haga clic en continuar y, a continuación, Regístrese para crear la nueva clave.
A continuación, solo tendrá una oportunidad de descargar la clave que acaba de generar. Haga clic en Descargar .

Además, tome nota del identificador de clave en este paso. Se usará para su KeyId posterior.
Habrá descargado un .p8 archivo de clave. Puede abrir este archivo en el Bloc de notas o VSCode para ver el
contenido del texto. Deben tener un aspecto similar al siguiente:

-----BEGIN PRIVATE KEY-----


MIGTAgEAMBMGBasGSM49AgGFCCqGSM49AwEHBHkwdwIBAQQg3MX8n6VnQ2WzgEy0
Skoz9uOvatLMKTUIPyPCAejzzUCgCgYIKoZIzj0DAQehRANCAARZ0DoM6QPqpJxP
JKSlWz0AohFhYre10EXPkjrih4jTm+b0AeG2BGuoIWd18i8FimGDgK6IzHHPsEqj
DHF5Svq0
-----END PRIVATE KEY-----

Asigne un nombre P8FileContents a esta clave y guárdela en un lugar seguro. Lo usará al integrar este servicio en
su aplicación móvil.

Resumen
En este artículo se describen los pasos necesarios para configurar el inicio de sesión con Apple para su uso en las
Xamarin.Forms aplicaciones.
Vínculos relacionados
Inicio de sesión con las directrices de Apple
Usar el inicio de sesión con Apple en :::no-
loc(Xamarin.Forms):::
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
Iniciar sesión con Apple es para todas las aplicaciones nuevas de iOS 13 que usan servicios de autenticación de
terceros. Los detalles de implementación entre iOS y Android son bastante diferentes. En esta guía se explica cómo
puede hacerlo hoy mismo en :::no-loc(Xamarin.Forms)::: .
En esta guía y muestra, se usan servicios de plataforma específicos para controlar el inicio de sesión con Apple:
Android con un servicio Web genérico que se comunica con Azure Functions con OpenID/OpenAuth
iOS usa la API nativa para la autenticación en iOS 13 y recurre a un servicio Web genérico para iOS 12 y
versiones anteriores

Un flujo de inicio de sesión de Apple de ejemplo


Este ejemplo ofrece una implementación de bien fundamentadas para que Apple Sign in funcione en su :::no-
loc(Xamarin.Forms)::: aplicación.
Usamos dos Azure Functions para ayudar con el flujo de autenticación:
1. applesignin_auth : Genera la dirección URL de autorización de inicio de sesión de Apple y la redirige a ella.
Haremos esto en el lado del servidor, en lugar de en la aplicación móvil, para que podamos almacenar en caché
el state y validarlo cuando los servidores de Apple envíen una devolución de llamada.
2. applesignin_callback : Controla la devolución de llamada POST de Apple y intercambia de forma segura el
código de autorización para un token de acceso y un token de identificador. Por último, redirige de nuevo al
esquema de URI de la aplicación, devolviendo los tokens en un fragmento de dirección URL.
La aplicación móvil se registra para controlar el esquema de URI personalizado que hemos seleccionado (en este
caso xamarinformsapplesignin:// ), por lo que la applesignin_callback función puede retransmitir los tokens de
vuelta a él.
Cuando el usuario inicia la autenticación, se producen los pasos siguientes:
1. La aplicación móvil genera un nonce state valor y y los pasa a la applesignin_auth función de Azure.
2. La applesignin_auth función de Azure genera una dirección URL de autorización de inicio de sesión de Apple
(con el proporcionado state y el nonce ) y redirige al explorador de aplicaciones móviles.
3. El usuario escribe sus credenciales de forma segura en la página de autorización de inicio de sesión de Apple
hospedada en los servidores de Apple.
4. Una vez que el flujo de inicio de sesión de Apple finaliza en los servidores de Apple, Apple redirige a la
redirect_uri que será la applesignin_callback función de Azure.
5. La solicitud de Apple enviada a la applesignin_callback función se valida para asegurarse de state que se
devuelve el correcto y que las notificaciones del token de identificador son válidas.
6. La applesignin_callback función de Azure intercambia el code publicado por Apple para un token de acceso ,
un token de actualización y un token de identificador (que contiene notificaciones sobre el ID. de usuario, el
nombre y el correo electrónico).
7. applesignin_callback Finalmente, la función de Azure redirige de nuevo al esquema de URI de la aplicación (
xamarinformsapplesignin:// ) anexando un fragmento de URI con los tokens (por ejemplo,
xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=... ).
8. La aplicación móvil analiza el fragmento de URI en AppleAccount y valida que la nonce demanda recibida
coincida con la nonce generada al inicio del flujo.
9. La aplicación móvil ya está autenticada.

Azure Functions
Este ejemplo utiliza Azure Functions. Como alternativa, un controlador de ASP.NET Core o una solución de servidor
Web similar podría ofrecer la misma funcionalidad.
Configuración
Deben configurarse varias opciones de configuración de la aplicación al usar Azure Functions:
APPLE_SIGNIN_KEY_ID : Este es el KeyId anterior.
APPLE_SIGNIN_TEAM_ID : Suele ser el identificador de equipo que se encuentra en el Perfil de pertenencia .
APPLE_SIGNIN_SERVER_ID : Este es el ServerId anterior. No es el identificador de la agrupación de aplicaciones,
sino el identificador del identificador de servicios que creó.
APPLE_SIGNIN_APP_CALLBACK_URI : Este es el esquema de URI personalizado que quiere redirigir de nuevo a la
aplicación con. En este ejemplo xamarinformsapplesignin:// se utiliza.
APPLE_SIGNIN_REDIRECT_URI -La dirección URL de redireccionamiento que se configura al crear el identificador
de servicios en la sección de configuración de inicio de sesión de Apple . Para probarlo, podría ser similar a lo
siguiente: http://local.test:7071/api/applesignin_callback
APPLE_SIGNIN_P8_KEY : El contenido de texto del .p8 archivo, con todas las \n nuevas líneas quitadas, por lo
que es una cadena larga.
Consideraciones sobre la seguridad
Nunca almacene la clave de P8 dentro del código de la aplicación. El código de aplicación es fácil de descargar y
desensamblar.
También se considera una práctica inadecuada usar un WebView para hospedar el flujo de autenticación y para
interceptar eventos de navegación por la dirección URL para obtener el código de autorización. En este momento,
actualmente no hay ninguna manera totalmente segura de controlar el inicio de sesión con Apple en dispositivos
que no son iOS13 y sin hospedar código en un servidor para controlar el intercambio de tokens. Se recomienda
hospedar el código de generación de la dirección URL de autorización en un servidor para que pueda almacenar
en caché el estado y validarlo cuando Apple emita una devolución de llamada POST al servidor.

Un servicio de inicio de sesión multiplataforma


Con :::no-loc(Xamarin.Forms)::: DependencyService, puede crear servicios de autenticación independientes que
usan los servicios de la plataforma en iOS y un servicio Web genérico para Android y otras plataformas que no
son iOS basados en una interfaz compartida.

public interface IAppleSignInService


{
bool Callback(string url);

Task<AppleAccount> SignInAsync();
}

En iOS, se usan las API nativas:

public class AppleSignInServiceiOS : IAppleSignInService


{
#if __IOS__13
AuthManager authManager;
#endif

bool Is13 => UIDevice.CurrentDevice.CheckSystemVersion(13, 0);


WebAppleSignInService webSignInService;

public AppleSignInServiceiOS()
{
if (!Is13)
webSignInService = new WebAppleSignInService();
}

public async Task<AppleAccount> SignInAsync()


{
// Fallback to web for older iOS versions
if (!Is13)
return await webSignInService.SignInAsync();

AppleAccount appleAccount = default;

#if __IOS__13
var provider = new ASAuthorizationAppleIdProvider();
var req = provider.CreateRequest();

authManager = new AuthManager(UIApplication.SharedApplication.KeyWindow);

req.RequestedScopes = new[] { ASAuthorizationScope.FullName, ASAuthorizationScope.Email };


var controller = new ASAuthorizationController(new[] { req });

controller.Delegate = authManager;
controller.PresentationContextProvider = authManager;

controller.PerformRequests();

var creds = await authManager.Credentials;

if (creds == null)
return null;

appleAccount = new AppleAccount();


appleAccount.IdToken = JwtToken.Decode(new NSString(creds.IdentityToken,
NSStringEncoding.UTF8).ToString());
appleAccount.Email = creds.Email;
appleAccount.UserId = creds.User;
appleAccount.Name = NSPersonNameComponentsFormatter.GetLocalizedString(creds.FullName,
NSPersonNameComponentsFormatterStyle.Default, NSPersonNameComponentsFormatterOptions.Phonetic);
appleAccount.RealUserStatus = creds.RealUserStatus.ToString();
#endif

return appleAccount;
}

public bool Callback(string url) => true;


}

#if __IOS__13
class AuthManager : NSObject, IASAuthorizationControllerDelegate,
IASAuthorizationControllerPresentationContextProviding
{
public Task<ASAuthorizationAppleIdCredential> Credentials
=> tcsCredential?.Task;

TaskCompletionSource<ASAuthorizationAppleIdCredential> tcsCredential;

UIWindow presentingAnchor;

public AuthManager(UIWindow presentingWindow)


{
tcsCredential = new TaskCompletionSource<ASAuthorizationAppleIdCredential>();
tcsCredential = new TaskCompletionSource<ASAuthorizationAppleIdCredential>();
presentingAnchor = presentingWindow;
}

public UIWindow GetPresentationAnchor(ASAuthorizationController controller)


=> presentingAnchor;

[Export("authorizationController:didCompleteWithAuthorization:")]
public void DidComplete(ASAuthorizationController controller, ASAuthorization authorization)
{
var creds = authorization.GetCredential<ASAuthorizationAppleIdCredential>();
tcsCredential?.TrySetResult(creds);
}

[Export("authorizationController:didCompleteWithError:")]
public void DidComplete(ASAuthorizationController controller, NSError error)
=> tcsCredential?.TrySetException(new Exception(error.LocalizedDescription));
}
#endif

La marca de compilación __IOS__13 se usa para proporcionar compatibilidad con iOS 13 así como versiones
heredadas que se reservan al servicio Web genérico.
En Android, se usa el servicio Web genérico con Azure Functions:
public class WebAppleSignInService : IAppleSignInService
{
// IMPORTANT: This is what you register each native platform's url handler to be
public const string CallbackUriScheme = "xamarinformsapplesignin";
public const string InitialAuthUrl = "http://local.test:7071/api/applesignin_auth";

string currentState;
string currentNonce;

TaskCompletionSource<AppleAccount> tcsAccount = null;

public bool Callback(string url)


{
// Only handle the url with our callback uri scheme
if (!url.StartsWith(CallbackUriScheme + "://"))
return false;

// Ensure we have a task waiting


if (tcsAccount != null && !tcsAccount.Task.IsCompleted)
{
try
{
// Parse the account from the url the app opened with
var account = AppleAccount.FromUrl(url);

// IMPORTANT: Validate the nonce returned is the same as our originating request!!
if (!account.IdToken.Nonce.Equals(currentNonce))
tcsAccount.TrySetException(new InvalidOperationException("Invalid or non-matching nonce
returned"));

// Set our account result


tcsAccount.TrySetResult(account);
}
catch (Exception ex)
{
tcsAccount.TrySetException(ex);
}
}

tcsAccount.TrySetResult(null);
return false;
}

public async Task<AppleAccount> SignInAsync()


{
tcsAccount = new TaskCompletionSource<AppleAccount>();

// Generate state and nonce which the server will use to initial the auth
// with Apple. The nonce should flow all the way back to us when our function
// redirects to our app
currentState = Util.GenerateState();
currentNonce = Util.GenerateNonce();

// Start the auth request on our function (which will redirect to apple)
// inside a browser (either SFSafariViewController, Chrome Custom Tabs, or native browser)
await :::no-loc(Xamarin.Essentials):::.Browser.OpenAsync($"{InitialAuthUrl}?&state=
{currentState}&nonce={currentNonce}",
:::no-loc(Xamarin.Essentials):::.BrowserLaunchMode.SystemPreferred);

return await tcsAccount.Task;


}
}

Resumen
En este artículo se describen los pasos necesarios para configurar el inicio de sesión con Apple para su uso en las
:::no-loc(Xamarin.Forms)::: aplicaciones.

Vínculos relacionados
XamarinFormsAppleSignIn (ejemplo)
Inicio de sesión con las directrices de Apple
Xamarin.FormsOtras plataformas
18/12/2020 • 2 minutes to read • Edit Online

Xamarin.Formsadmite plataformas adicionales más allá de iOS, Android y Windows.

IMPORTANT
Para obtener más información acerca de las Xamarin.Forms plataformas compatibles, consulte Xamarin.Forms compatibilidad
con plataformas.

GTK
Xamarin.Formsahora tiene compatibilidad con la versión preliminar de las aplicaciones GTK #.

Mac
Xamarin.Formsahora tiene compatibilidad con la versión preliminar para aplicaciones macOS.

Tizen
Tizen .NET le permite compilar aplicaciones .NET con Xamarin.Forms y el .NET Framework de Tizen.

WPF
Xamarin.Formsahora tiene compatibilidad con vista previa para aplicaciones de Windows Presentation Foundation
(WPF).
Configuración de la plataforma GTK #
18/12/2020 • 10 minutes to read • Edit Online

Xamarin.Forms ahora tiene compatibilidad con la versión preliminar de las aplicaciones GTK #. GTK # es un kit de
herramientas de interfaz gráfica de usuario que vincula los GTK + Toolkit y varias bibliotecas de GNOME, lo que
permite el desarrollo de aplicaciones de gráficos de GNOME totalmente nativas con mono y .NET. En este artículo
se muestra cómo agregar un proyecto de GTK # a una Xamarin.Forms solución.

IMPORTANT
Xamarin.Forms la comunidad proporciona compatibilidad con GTK #. Para obtener más información, consulte Xamarin.Forms
compatibilidad con plataformas.

Antes de empezar, cree una nueva Xamarin.Forms solución o use una solución existente Xamarin.Forms , por
ejemplo, GameOfLife .

NOTE
Aunque este artículo se centra en agregar una aplicación de GTK # a una Xamarin.Forms solución en VS2017 y Visual Studio
para Mac, también se puede realizar en MonoDevelop para Linux.

Agregar una aplicación GTK #


GTK # para macOS y Linux se instala como parte de mono. GTK # para .NET se puede instalar en Windows con el
instalador GTK #.
Visual Studio
Visual Studio para Mac
Siga estas instrucciones para agregar una aplicación GTK # que se ejecutará en el escritorio de Windows:
1. En Visual Studio 2019, haga clic con el botón derecho en el nombre de la solución en Explorador de
soluciones y elija Agregar > nuevo proyecto....
2. En la ventana nuevo proyecto , en la parte izquierda, seleccione Visual C# y escritorio clásico de
Windows . En la lista de tipos de proyecto, elija biblioteca de clases (.NET Framework) y asegúrese de
que la lista desplegable marco esté establecida en un mínimo de .NET Framework 4,7.
3. Escriba un nombre para el proyecto con una extensión GTK , por ejemplo GameOfLife. GTK . Haga clic en
el botón examinar , seleccione la carpeta que contiene los otros proyectos de la plataforma y presione
Seleccionar carpeta . Esto colocará el proyecto GTK en el mismo directorio que los demás proyectos de la
solución.
Presione el botón Aceptar para crear el proyecto.
4. En el Explorador de soluciones , haga clic con el botón derecho en el nuevo proyecto GTK y seleccione
administrar paquetes NuGet . Seleccione la pestaña examinar y busque Xamarin.Forms 3,0 o superior.

Seleccione el paquete y haga clic en el botón instalar .


5. Ahora busque ** Xamarin.Forms . Paquete Platform. GTK** 3,0 o superior.

Seleccione el paquete y haga clic en el botón instalar .


6. En el Explorador de soluciones , haga clic con el botón derecho en el nombre de la solución y seleccione
administrar paquetes NuGet para la solución . Seleccione la pestaña Actualizar y el Xamarin.Forms
paquete. Seleccione todos los proyectos y actualícelos a la misma Xamarin.Forms versión que usa el
proyecto GTK.
7. En el Explorador de soluciones , haga clic con el botón derecho en referencias en el proyecto GTK. En el
cuadro de diálogo Administrador de referencias , seleccione proyectos a la izquierda y active la casilla
adyacente al .net Standard o proyecto compartido:
8. En el cuadro de diálogo Administrador de referencias , presione el botón examinar y vaya a la carpeta
c:\Archivos de programa (x86) \GtkSharp\2.12\lib y seleccione los archivos atk-sharp.dll , gdk-
sharp.dll , glade-sharp.dll , glib-sharp.dll , gtk-dotnet.dll y gtk-sharp.dll .

Presione el botón Aceptar para agregar las referencias.


9. En el proyecto GTK, cambie el nombre de Class1.CS a Program.CS .
10. En el proyecto GTK, edite el archivo Program.CS para que sea similar al código siguiente:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.GTK;

namespace GameOfLife.GTK
{
class MainClass
{
[STAThread]
public static void Main(string[] args)
{
Gtk.Application.Init();
Forms.Init();

var app = new App();


var window = new FormsWindow();
window.LoadApplication(app);
window.SetApplicationTitle("Game of Life");
window.Show();

Gtk.Application.Run();
}
}
}

Este código inicializa GTK # y Xamarin.Forms , crea una ventana de la aplicación y ejecuta la aplicación.
11. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto GTK y seleccione
propiedades .
12. En la ventana propiedades , seleccione la pestaña aplicación y cambie la lista desplegable tipo de salida
a aplicación Windows .

13. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto GTK y seleccione
establecer como proyecto de inicio . Presione F5 para ejecutar el programa con el depurador de Visual
Studio en el escritorio de Windows:
Pasos siguientes
Funcionalidad específica de plataforma
Puede determinar en qué plataforma Xamarin.Forms se está ejecutando la aplicación desde XAML o desde código.
Esto le permite cambiar las características del programa cuando se ejecuta en GTK #. En el código, compare el valor
de Device.RuntimePlatform con la Device.GTK constante (que es igual a la cadena "GTK"). Si hay una coincidencia,
la aplicación se ejecuta en GTK #.
En XAML, puede usar la OnPlatform etiqueta para seleccionar un valor de propiedad específico de la plataforma:

<Button.TextColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White" />
<On Platform="macOS" Value="White" />
<On Platform="Android" Value="Black" />
<On Platform="GTK" Value="Blue" />
</OnPlatform>
</Button.TextColor>

Icono de aplicación
Puede establecer el icono de la aplicación en el inicio:

window.SetApplicationIcon("icon.png");

Temas
Hay una amplia variedad de temas disponibles para GTK # y se pueden usar desde una Xamarin.Forms aplicación:
GtkThemes.Init ();
GtkThemes.LoadCustomTheme ("Themes/gtkrc");

Formularios nativos
Los formularios nativos permiten Xamarin.Forms ContentPage el consumo de páginas derivadas de proyectos
nativos, incluidos los proyectos de GTK #. Esto se puede lograr creando una instancia de la ContentPage Página
derivada de y convirtiéndola en el tipo de GTK # nativo mediante el CreateContainer método de extensión:

var settingsView = new SettingsView().CreateContainer();


vbox.PackEnd(settingsView, true, true, 0);

Para obtener más información sobre los formularios nativos, vea formularios nativos.

Issues
Se trata de una versión preliminar, por lo que debe esperar que no todo esté listo para la producción. Para ver el
estado actual de la implementación, vea el Estadoy los problemas conocidos actuales, consulte Pending & known
issues.
Configuración de la plataforma Mac
18/12/2020 • 5 minutes to read • Edit Online

Antes de empezar, cree un proyecto (o use uno existente) Xamarin.Forms . Solo puede agregar aplicaciones Mac
mediante Visual Studio para Mac.

Agregar un proyecto de macOS a Xamarin.Forms vídeo

Agregar una aplicación Mac


Siga estas instrucciones para agregar una aplicación Mac que se ejecutará en macOS Sierra y macOS el Capitan:
1. En Visual Studio para Mac, haga clic con el botón derecho en la Xamarin.Forms solución existente y elija
Agregar > agregar nuevo proyecto...
2. En la ventana nuevo proyecto , elija Mac > App > aplicación de coco y presione siguiente .
3. Escriba un nombre de aplicación (y, opcionalmente, elija otro nombre para el elemento de acoplamiento)
y, a continuación, presione siguiente .
4. Revise la configuración y presione crear . Estos pasos se muestran a continuación:

5. En el proyecto de Mac, haga clic con el botón derecho en paquetes > agregar paquetes... para agregar
Xamarin.Forms NuGet. También debe actualizar los otros proyectos para que usen la misma versión del
Xamarin.Forms paquete NuGet.
6. En el proyecto de Mac, haga clic con el botón derecho en referencias y agregue una referencia al proyecto
Xamarin.Forms (proyecto compartido o proyecto de biblioteca de .net Standard).

7. Actualice Main.CS para inicializar AppDelegate :

static class MainClass


{
static void Main(string[] args)
{
NSApplication.Init();
NSApplication.SharedApplication.Delegate = new AppDelegate(); // add this line
NSApplication.Main(args);
}
}

8. Actualice AppDelegate para inicializar Xamarin.Forms , crear una ventana de y cargar la Xamarin.Forms
aplicación (recordar para establecer un adecuado Title ). Si tiene otras dependencias que deben
inicializarse, hágalo aquí también.

using Xamarin.Forms;
using Xamarin.Forms.Platform.MacOS;
// also add a using for the Xamarin.Forms project, if the namespace is different to this file
...
[Register("AppDelegate")]
public class AppDelegate : FormsApplicationDelegate
{
NSWindow window;
public AppDelegate()
{
var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled;

var rect = new CoreGraphics.CGRect(200, 1000, 1024, 768);


window = new NSWindow(rect, style, NSBackingStore.Buffered, false);
window.Title = "Xamarin.Forms on Mac!"; // choose your own Title here
window.TitleVisibility = NSWindowTitleVisibility.Hidden;
}

public override NSWindow MainWindow


{
get { return window; }
}

public override void DidFinishLaunching(NSNotification notification)


{
Forms.Init();
LoadApplication(new App());
base.DidFinishLaunching(notification);
}
}

9. Haga doble clic en Main. Stor yboard para editarlo en Xcode. Seleccione la ventana y desactive la casilla
es el controlador inicial (esto se debe a que el código anterior crea una ventana):

Puede editar el sistema de menús en el guion gráfico para quitar elementos no deseados.
10. Por último, agregue los recursos locales (por ejemplo, archivos de imagen) de los proyectos de plataforma
existentes que son necesarios.
11. El proyecto de Mac ahora debe ejecutar el Xamarin.Forms código en MacOS.

Pasos siguientes
Aplicación de estilos
Con los cambios recientes realizados en OnPlatform , ahora puede tener como destino cualquier número de
plataformas. Esto incluye macOS.

<Button.TextColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White"/>
<On Platform="macOS" Value="White"/>
<On Platform="Android" Value="Black"/>
</OnPlatform>
</Button.TextColor>

Tenga en cuenta que también puede duplicar en plataformas como esta: <On Platform="iOS, macOS" ...> .
Tamaño y posición de la ventana
Puede ajustar el tamaño y la ubicación iniciales de la ventana en la AppDelegate :

var rect = new CoreGraphics.CGRect(200, 1000, 1024, 768); // x, y, width, height

Problemas conocidos
Se trata de una versión preliminar, por lo que debe esperar que no todo esté listo para la producción. A
continuación se muestran algunas cosas que puede encontrar al agregar macOS a los proyectos:
No todos los paquetes Nuget están listos para macOS
Es posible que algunas de las bibliotecas que usa todavía no admitan macOS. En este caso, deberá enviar una
solicitud al mantenedor del proyecto para agregarlo. Hasta que tengan soporte técnico, es posible que tenga que
buscar alternativas.
Características que faltan Xamarin.Forms
No todas Xamarin.Forms las características se han completado en esta versión preliminar. Para obtener más
información, consulte el estado de compatibilidad de la plataforma MacOS en el Xamarin.Forms repositorio de
github.

Vínculos relacionados
Xamarin.Mac
Tizen .NET
18/12/2020 • 2 minutes to read • Edit Online

Tizen .NET le permite desarrollar aplicaciones de Tizen para que se ejecuten en dispositivos de Samsung, como
televisores, ponibles, dispositivos móviles y otros dispositivos de IoT.
Tizen .NET le permite compilar aplicaciones .NET con Xamarin.Forms y Tizen .NET Framework. La plataforma .NET
Tizen es compatible con Samsung. Xamarin.Formspermite crear fácilmente interfaces de usuario, mientras que la
API de TizenFX proporciona interfaces para el hardware que se encuentra en dispositivos modernos de TV, móviles,
portátil y IoT. Para obtener más información sobre Tizen .NET, consulte Introduction to Tizen .net Application.

Introducción
Antes de empezar a desarrollar aplicaciones .NET de Tizen, primero debe configurar el entorno de desarrollo. Para
obtener más información, consulte instalación de Visual Studio Tools para Tizen.
Para obtener información sobre cómo agregar un proyecto de Tizen .NET a una Xamarin.Forms solución existente,
consulte creación de su primera aplicación de Tizen .net.

Documentación
Xamarin.Forms documentación sobre – Cómo crear aplicaciones multiplataforma con C# y Xamarin.Forms .
developer.tizen.org – documentación y vídeos de developer.Tizen.org para ayudarle a compilar e
implementar aplicaciones de Tizen.

Ejemplos
Samsung mantiene una bifurcación de los Xamarin.Forms ejemplos con proyectos de Tizen agregadosy hay un
repositorio independiente Tizen-CSharp-samples que contiene proyectos adicionales, incluidas las demostraciones
específicas de portátil y TV.
Configuración de la plataforma WPF
18/12/2020 • 6 minutes to read • Edit Online

Xamarin.Forms tiene compatibilidad con la versión preliminar de Windows Presentation Foundation (WPF), en
.NET Framework y en .NET Core 3. En este artículo se muestra cómo agregar un proyecto de WPF que tiene como
destino .NET Framework a una Xamarin.Forms solución.

IMPORTANT
Xamarin.Forms la comunidad proporciona compatibilidad con WPF. Para obtener más información, consulte Xamarin.Forms
compatibilidad con plataformas.

Antes de empezar, cree una nueva Xamarin.Forms solución en Visual Studio 2019 o use una solución existente
Xamarin.Forms , por ejemplo, BoxViewClock . Solo puede agregar aplicaciones de WPF a una Xamarin.Forms
solución en Windows.

Agregar una aplicación WPF


Siga estas instrucciones para agregar una aplicación de WPF que se ejecutará en los equipos de escritorio de
Windows 7, 8 y 10:
1. En Visual Studio 2019, haga clic con el botón derecho en el nombre de la solución en el Explorador de
soluciones y elija Agregar > nuevo proyecto....
2. En la ventana Agregar un nuevo proyecto , seleccione C# en la lista desplegable idiomas , seleccione
Windows en el menú desplegable plataformas y seleccione escritorio en la lista desplegable tipo de
proyecto . En la lista de tipos de proyecto, elija aplicación WPF (.NET Framework) :
Presione el botón siguiente .

NOTE
Xamarin.Forms 4,7 incluye compatibilidad con aplicaciones de WPF que se ejecutan en .NET Core 3.

3. En la ventana configurar el nuevo proyecto , escriba un nombre para el proyecto con una extensión de
WPF , por ejemplo, BoxViewClock . WPF . Haga clic en el botón examinar , seleccione la carpeta
BoxViewClock y presione Seleccionar carpeta para colocar el proyecto de WPF en el mismo directorio
que los demás proyectos de la solución:
Presione el botón crear para crear el proyecto.
4. En el Explorador de soluciones , haga clic con el botón derecho en el nuevo proyecto BoxViewClock .
WPF y seleccione administrar paquetes NuGet.... Seleccione la pestaña examinar y busque **
Xamarin.Forms . Plataforma. WPF**:

Seleccione el paquete y haga clic en el botón instalar .


5. Haga clic con el botón derecho en el nombre de la solución en el Explorador de soluciones y seleccione
administrar paquetes NuGet para la solución.... Seleccione la pestaña actualizaciones y, a
continuación, seleccione el Xamarin.Forms paquete. Seleccione todos los proyectos y actualícelos a la
misma Xamarin.Forms versión:

6. En el proyecto de WPF, haga clic con el botón derecho en referencias y seleccione Agregar referencia....
En el cuadro de diálogo Administrador de referencias , seleccione proyectos a la izquierda y active la
casilla situada junto al proyecto BoxViewClock :

Presione el botón Aceptar .


7. Edite el archivo MainWindow. Xaml del proyecto de WPF. En la Window etiqueta, agregue una declaración
de espacio de nombres XML para ** Xamarin.Forms . **Ensamblado y espacio de nombres de Platform.
WPF:

xmlns:wpf="clr-namespace:Xamarin.Forms.Platform.WPF;assembly=Xamarin.Forms.Platform.WPF"

Ahora cambie la Window etiqueta a wpf:FormsApplicationPage . Cambie la Title configuración al nombre


de la aplicación, por ejemplo, BoxViewClock . El archivo XAML completado debe tener el siguiente aspecto:

<wpf:FormsApplicationPage x:Class="BoxViewClock.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BoxViewClock.WPF"
xmlns:wpf="clr-namespace:Xamarin.Forms.Platform.WPF;assembly=Xamarin.Forms.Platform.WPF"
mc:Ignorable="d"
Title="BoxViewClock" Height="450" Width="800">
<Grid>

</Grid>
</wpf:FormsApplicationPage>

8. Edite el archivo MainWindow.Xaml.CS del proyecto de WPF. Agregue dos nuevas using directivas:

using Xamarin.Forms;
using Xamarin.Forms.Platform.WPF;

Cambie la clase base de MainWindow de Window a FormsApplicationPage . Después de la


InitializeComponent llamada, agregue las dos instrucciones siguientes:

Forms.Init();
LoadApplication(new BoxViewClock.App());

A excepción de los comentarios y using las directivas no utilizadas, el archivo MainWindows.Xaml.CS


completo debe tener el siguiente aspecto:
using Xamarin.Forms;
using Xamarin.Forms.Platform.WPF;

namespace BoxViewClock.WPF
{
public partial class MainWindow : FormsApplicationPage
{
public MainWindow()
{
InitializeComponent();

Forms.Init();
LoadApplication(new BoxViewClock.App());
}
}
}

9. Haga clic con el botón derecho en el proyecto WPF en el Explorador de soluciones y seleccione
establecer como proyecto de inicio . Presione F5 para ejecutar el programa con el depurador de Visual
Studio en el escritorio de Windows:

Características específicas de la plataforma


Puede determinar la plataforma en la Xamarin.Forms que se ejecuta la aplicación desde código o XAML. Esto le
permite cambiar las características del programa cuando se ejecuta en WPF. En el código, compare el valor de
Device.RuntimePlatform con la Device.WPF constante (que es igual a la cadena "WPF"). Si hay una coincidencia, la
aplicación se ejecuta en WPF.
En XAML, puede usar la OnPlatform etiqueta para seleccionar un valor de propiedad específico de la plataforma:

<Button.TextColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White" />
<On Platform="macOS" Value="White" />
<On Platform="Android" Value="Black" />
<On Platform="WPF" Value="Blue" />
</OnPlatform>
</Button.TextColor>
Tamaño de la ventana
Puede ajustar el tamaño inicial de la ventana en el archivo MainWindow. Xaml de WPF:

Title="BoxViewClock" Height="450" Width="800"

Issues
Se trata de una versión preliminar, por lo que debe esperar que no todo esté listo para la producción. No todos los
paquetes NuGet para Xamarin.Forms están preparados para WPF y es posible que algunas características no
funcionen por completo.

Vídeo relacionado
Xamarin.Forms vídeo de sopor te de WPF 3,0
Xamarin.Essentials
18/12/2020 • 5 minutes to read • Edit Online

Xamarin.Essentials brinda a los desarrolladores API multiplataformas para sus aplicaciones móviles.
Android, iOS y UWP ofrecen API de plataforma y sistema operativo únicas a las que los desarrolladores pueden
tener acceso desde C# mediante Xamarin. Xamarin.Essentials brinda una API multiplataforma única que funciona
con cualquier aplicación Xamarin.Forms, Android, iOS o UWP accesible desde código compartido,
independientemente de cómo se creara la interfaz de usuario.

Introducción a Xamarin.Essentials
Siga la guía de introducción para instalar el paquete NuGet de Xamarin.Essentials en los proyectos nuevos o
existentes de Xamarin.Forms, Android, iOS o UWP.

Guías de características
Siga las guías para integrar estas características de Xamarin.Essentials en las aplicaciones:
Accelerometer: recupere los datos de aceleración del dispositivo en un espacio tridimensional.
Acciones de aplicación: obtenga y establezca accesos directos para la aplicación.
App Information: conozca información sobre la aplicación.
Tema de la aplicación: detecte el tema actual solicitado para la aplicación.
Barometer: supervise los cambios de presión con el barómetro.
Battery: detecte fácilmente el nivel, origen y estado de la batería.
Clipboard: establezca o lea fácil y rápidamente texto en el Portapapeles.
Color Converters: métodos del asistente para System.Drawing.Color.
Compass: supervise los cambios en la brújula.
Connectivity: compruebe el estado de la conectividad y detecte cambios.
Contactos: recupere información sobre un contacto en el dispositivo.
Detect Shake: detecte movimientos de agitación en el dispositivo.
Device Display Information: obtenga la orientación y las métricas de la pantalla del dispositivo.
Device Information: conozca información sobre el dispositivo de manera sencilla.
Email: envíe fácilmente mensajes de correo electrónico.
Selector de archivos: permite al usuario seleccionar archivos del dispositivo.
File System Helpers: guarde fácilmente archivos en los datos de la aplicación.
Flashlight: una manera sencilla de encender y apagar la linterna.
Geocoding: coordenadas y direcciones de código geográfico y de código geográfico inverso.
Geolocation: recupere la ubicación de GPS del dispositivo.
Gyroscope: haga seguimiento de la rotación alrededor de tres ejes primarios del dispositivo.
Comentarios hápticos: haga clic en la tecla control y mantenga presionados los hápticos.
Launcher: permite que una aplicación abra un URI por el sistema.
Magnetometer: detecte la orientación del dispositivo respectivo del campo magnético de la Tierra.
MainThread: ejecute código en el subproceso principal de la aplicación.
Maps: abra la aplicación de mapas en una ubicación específica.
Selector de archivos multimedia: permite al usuario escoger o tomar fotos y vídeos.
Open Browser: abra rápidamente y sin problemas un explorador en un sitio web específico.
Orientation Sensor: recupere la orientación del dispositivo en un espacio tridimensional.
Permissions: compruebe y solicite permisos a los usuarios.
Phone Dialer: abra el marcador telefónico.
Platform Extensions: métodos del asistente para convertir Rect, Size y Point.
Preferences: agregue rápida y sencillamente las preferencias persistentes.
Captura de pantalla: tome una captura de la pantalla actual de la aplicación.
Secure Storage: almacene datos de manera segura.
Recurso compartido: envíe texto y vínculos de sitio web a otras aplicaciones.
SMS: cree un mensajes SMS para enviarlo.
Text-to-Speech: vocalice texto en el dispositivo.
Unit Converters: métodos del asistente para convertir unidades.
Version Tracking: haga seguimiento de las versiones de las aplicaciones y los números de compilación.
Vibrate: haga que el dispositivo vibre.
Autenticador web: inicie los flujos de autenticación web y escuche una devolución de llamada.

Solución de problemas
Busque ayuda si se encuentra con problemas.

Xamarin.Essentials en Preguntas y respuestas


Formule preguntas sobre el acceso a las características nativas con Xamarin.Essentials.

Notas de la versión
Consulte todas las notas de cada versión de Xamarin.Essentials.

Documentación de la API
Examine la documentación de la API para conocer cada característica de Xamarin.Essentials.
Introducción a Xamarin.Essentials
18/12/2020 • 5 minutes to read • Edit Online

Xamarin.Essentials brinda una API multiplataforma única que funciona con cualquier aplicación iOS,
Android o UWP accesible desde código compartido, independientemente de cómo se creara la interfaz
de usuario. Para obtener más información sobre los sistemas operativos compatibles, consulte la guía
de compatibilidad con plataformas y características.

Instalación
Xamarin.Essentials está disponible como paquete NuGet y se incluye en todos los proyectos nuevos de
Visual Studio. También se puede agregar a cualquier proyecto existente mediante Visual Studio con los
pasos siguientes.
1. Descargue e instale Visual Studio con Visual Studio Tools para Xamarin.
2. Abra un proyecto existente o cree uno nuevo con la plantilla de aplicación vacía en Visual
Studio C# (Android, iPhone e iPad o multiplataforma).

IMPORTANT
Si se agrega a un proyecto de UWP, asegúrese de que en las propiedades del proyecto esté establecida
la compilación 16299 u otra posterior.

3. Agregue el paquete NuGet Xamarin.Essentials a cada proyecto:


Visual Studio
Visual Studio para Mac
En el panel del Explorador de soluciones, haga clic con el botón derecho en el nombre de la
solución y seleccione Administrar paquetes NuGet . Busque Xamarin.Essentials e instale el
paquete en TODOS los proyectos, incluidos Android, iOS, UWP y las bibliotecas de .NET
Standard.
4. Agregue una referencia a Xamarin.Essentials en cualquier clase de C# para hacer referencia a las
API.

using Xamarin.Essentials;

5. Xamarin.Essentials requiere una configuración específica de plataforma:


Android
iOS
UWP
La versión mínima de Android compatible con Xamarin.Essentials es la 4.4, que corresponde a
un nivel de API 19, pero la versión de destino de Android para compilar debe ser la 9.0 o 10.0,
correspondiente al nivel de API 28 y 29. (En Visual Studio, estas dos versiones se establecen en el
cuadro de diálogo Propiedades del proyecto correspondiente al proyecto de Android en la
pestaña Manifiesto de Android). En Visual Studio para Mac, se establecen en el cuadro de
diálogo Opciones del proyecto correspondiente al proyecto de Android, en la pestaña Aplicación
de Android).
Al compilar en Android 9.0, Xamarin.Essentials instala la versión 28.0.0.3 de las bibliotecas de
Xamarin.Android.Support que necesita. Las demás bibliotecas de Xamarin.Android.Support que
requiere la aplicación también se deben actualizar a la versión 28.0.0.3 con el administrador de
paquetes NuGet. Todas las bibliotecas de Xamarin.Android.Support que la aplicación usa deben
ser iguales y la versión debe ser al menos 28.0.0.3. Consulte la página de solución de problemas
si no puede agregar el paquete NuGet de Xamarin.Essentials ni actualizar los paquetes NuGet de
la solución.
A partir de la versión 1.5.0, al compilar en Android 10.0, Xamarin.Essentials instala las bibliotecas
de compatibilidad de AndroidX que necesita. Lea la documentación de AndroidX si aún no ha
realizado la transición.
En el elemento MainLauncher del proyecto de Android o cualquier Activity que se inicie,
Xamarin.Essentials se debe inicializar en el método OnCreate :

protected override void OnCreate(Bundle savedInstanceState) {


//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your
code, it may also be called: bundle
//...

Para controlar los permisos en tiempo de ejecución de Android, Xamarin.Essentials debe recibir
cualquier OnRequestPermissionsResult . Agregue el código siguiente a todas las clases Activity :

public override void OnRequestPermissionsResult(int requestCode, string[] permissions,


Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions,
grantResults);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);


}

6. Siga las guías de Xamarin.Essentials para poder copiar y pegar fragmentos de código para cada
característica.

Xamarin.Essentials: API multiplataforma para aplicaciones


móviles (vídeo).

Otros recursos
Es recomendable que los desarrolladores que trabajan por primera vez con Xamarin visiten
Introducción al desarrollo de Xamarin.
Visite el repositorio GitHub de Xamarin.Essentials para ver el código fuente actual, descubrir qué viene
más adelante, ejecutar ejemplos y clonar el repositorio. Estaremos encantados de recibir cualquier
colaboración de la comunidad.
Examine la documentación de la API para conocer cada característica de Xamarin.Essentials.
Compatibilidad de la plataforma
18/12/2020 • 2 minutes to read • Edit Online

Xamarin.Essentials admite las siguientes plataformas y sistemas operativos:

P L ATA F O RM A VERSIÓ N

Android 4.4 (API 19) o versiones posteriores

iOS 10.0 o versiones posteriores

Tizen 4.0 o versiones posteriores

tvOS 10.0 o versiones posteriores

watchOS 4.0 o versiones posteriores

UWP 10.0.16299.0 o versiones posteriores

macOS 10.12.6 (Sierra) o versiones posteriores

NOTE
Tizen es compatible oficialmente con el equipo de desarrollo de Samsung.
tvOS & watchOS tienen una cobertura de API limitada; consulte la guía de características para obtener más información.
La compatibilidad con macOS está en versión preliminar.

Compatibilidad con características


Xamarin.Essentials siempre intenta incorporar características a cada plataforma, aunque a veces hay limitaciones
en función del dispositivo. A continuación se muestra una guía de las características admitidas en cada plataforma.
Guía de iconos:
: totalmente compatible
: compatibilidad limitada
: no compatible

C A RA C T ERÍ
ST IC A A N DRO ID IO S UW P WATC H O S T VO S T IZ EN MAC OS

Acelerómetr
o

Acciones de
aplicación

Información
de la
aplicación
C A RA C T ERÍ
ST IC A A N DRO ID IO S UW P WATC H O S T VO S T IZ EN MAC OS

Tema de la
aplicación

Barómetro

Batería

Portapapele
s

Convertidor
es de
colores

Brújula

Conectivida
d

Contactos

Detección
de
vibraciones

Información
de pantalla
del
dispositivo

Información
del
dispositivo

Correo
electrónico

Selector de
archivos

Asistentes
del sistema
de archivos

Linterna

Codificación
geográfica
C A RA C T ERÍ
ST IC A A N DRO ID IO S UW P WATC H O S T VO S T IZ EN MAC OS

Geolocalizac
ión

Giroscopio

Comentario
s hápticos

Selector

Magnetóm
etro

MainThread

Mapas

Selector de
archivos
multimedia

Apertura
del
explorador

Sensor de
orientación

Permisos

Marcador
telefónico

Extensiones
de
plataforma

Preferencias

Instantánea

Almacenami
ento seguro

Compartir

SMS

Texto a voz
C A RA C T ERÍ
ST IC A A N DRO ID IO S UW P WATC H O S T VO S T IZ EN MAC OS

Convertidor
es de
unidades

Seguimient
o de
versiones

Vibración

Autenticado
r web
Xamarin.Essentials: Acelerómetro
18/12/2020 • 4 minutes to read • Edit Online

La clase Accelerometer permite supervisar el sensor del acelerómetro del dispositivo, que indica la aceleración
del dispositivo en un espacio tridimensional.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Accelerometer
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad Accelerometer funciona mediante una llamada a los métodos Start y Stop para realizar
escuchas de los cambios realizados en la aceleración. Los cambios se enviarán a través del evento ReadingChanged
. A continuación le mostramos un ejemplo de uso:
public class AccelerometerTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public AccelerometerTest()
{
// Register for reading changes, be sure to unsubscribe when finished
Accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
}

void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)


{
var data = e.Reading;
Console.WriteLine($"Reading: X: {data.Acceleration.X}, Y: {data.Acceleration.Y}, Z:
{data.Acceleration.Z}");
// Process Acceleration X, Y, and Z
}

public void ToggleAccelerometer()


{
try
{
if (Accelerometer.IsMonitoring)
Accelerometer.Stop();
else
Accelerometer.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Las lecturas del acelerómetro se notifican en G. La G es una unidad de fuerza gravitacional igual a la ejercida por
el campo gravitatorio de la Tierra (9,81 m/s^2).
El sistema de coordenadas se define con respecto a la pantalla del teléfono en orientación predeterminada.
Cuando cambia la orientación de pantalla del dispositivo no se intercambian los ejes.
El eje X es horizontal y apunta a la derecha, el eje Y es vertical y apunta hacia arriba, y el eje Z apunta hacia el
exterior de la parte frontal de la pantalla. En este sistema, las coordenadas de detrás de la pantalla tienen valores
de Z negativo.
Ejemplos:
Cuando el dispositivo se encuentra plano sobre una mesa y se mueve desde el lado izquierdo hacia la
derecha, el valor de aceleración de X es positivo.
Cuando el dispositivo se encuentra plano sobre una mesa, el valor de aceleración es + 1,00 G o (+ 9,81
m/s^2), que corresponden a la aceleración del dispositivo (0 m/s^2) menos la fuerza de la gravedad (-
9,81 m/s^2) y normalizada en G.
Cuando el dispositivo se encuentra plano sobre una mesa y se mueve hacia el cielo con una aceleración de
A m/s^2, el valor de la aceleración es igual A + 9,81, que corresponde a la aceleración del dispositivo (+ A
m/s^2) menos la fuerza de la gravedad (- 9,81 m/s^2) y normalizada en G.
Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de
usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

API
Código fuente de Accelerometer
Documentación de API de Accelerometer

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Acciones de aplicación
18/12/2020 • 3 minutes to read • Edit Online

La clase AppActions permite crear y responder a accesos directos de aplicaciones desde el icono de la aplicación.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de AppActions , se requiere la configuración específica siguiente para la plataforma.
Android
iOS
UWP
Agregue el filtro de intención a la clase MainActivity :

[IntentFilter(
new[] { Xamarin.Essentials.Platform.Intent.ActionAppAction },
Categories = new[] { Intent.CategoryDefault })]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
...

Luego, agregue la lógica siguiente para controlar las acciones:

protected override void OnResume()


{
base.OnResume();

Xamarin.Essentials.Platform.OnResume(this);
}

protected override void OnNewIntent(Intent intent)


{
base.OnNewIntent(intent);

Xamarin.Essentials.Platform.OnNewIntent(intent);
}

Creación de acciones
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Las acciones de aplicación se pueden crear en cualquier momento, pero se crean a menudo cuando se inicia una
aplicación. Llame al método SetAsync para crear la lista de acciones de la aplicación.

try
{
await AppActions.SetAsync(
new AppAction("app_info", "App Info", icon: "app_info_action_icon"),
new AppAction("battery_info", "Battery Info"));
}
catch (FeatureNotSupportedException ex)
{
Debug.WriteLine("App Actions not supported");
}

Si las acciones de aplicación no se admiten en la versión específica del sistema operativo, se producirá un
elemento FeatureNotSupportedException .
Se pueden especificar las propiedades siguientes en un elemento AppAction :
Id: identificador único que se usa para responder a la acción pulsar.
Título: título visible que se va a mostrar.
Subtítulo: si se admite un subtítulo para mostrar bajo el título.
Icono: debe coincidir con los iconos del directorio de recursos correspondiente en cada plataforma.

Respuesta a acciones
Cuando la aplicación empieza el registro para el evento OnAppAction . Cuando se selecciona una acción de
aplicación, el evento se enviará con información relativa a qué acción se ha seleccionado.
public App()
{
//...
AppActions.OnAppAction += AppActions_OnAppAction;
}

void AppActions_OnAppAction(object sender, AppActionEventArgs e)


{
// Don't handle events fired for old application instances
// and cleanup the old instance's event handler
if (Application.Current != this && Application.Current is App app)
{
AppActions.OnAppAction -= app.AppActions_OnAppAction;
return;
}
Device.BeginInvokeOnMainThread(async () =>
{
var page = e.AppAction.Id switch
{
"battery_info" => new BatteryPage(),
"app_info" => new AppInfoPage(),
_ => default(Page)
};
if (page != null)
{
await Application.Current.MainPage.Navigation.PopToRootAsync();
await Application.Current.MainPage.Navigation.PushAsync(page);
}
});
}

GetActions
Se puede obtener la lista actual de las acciones de aplicación mediante una llamada a AppActions.GetAsync() .

API
Código fuente de AppActions
Documentación de AppActions API
Xamarin.Essentials: Información de la aplicación:
18/12/2020 • 2 minutes to read • Edit Online

La clase AppInfo proporciona información sobre la aplicación.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de AppInfo
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Obtención de información de la aplicación:


La información siguiente se expone a través de la API:

// Application Name
var appName = AppInfo.Name;

// Package Name/Application Identifier (com.microsoft.testapp)


var packageName = AppInfo.PackageName;

// Application Version (1.0.0)


var version = AppInfo.VersionString;

// Application Build Number (1)


var build = AppInfo.BuildString;

Representación de la configuración de la aplicación


La clase AppInfo también puede mostrar una página de configuración mantenida por el sistema operativo para la
aplicación:

// Display settings page


AppInfo.ShowSettingsUI();

Esta página de configuración permite al usuario cambiar los permisos de la aplicación y realizar otras tareas
específicas de la plataforma.

Detalles de implementación de la plataforma


Android
iOS
UWP
La información sobre la aplicación se extrae de AndroidManifest.xml para estos campos:
Build : android:versionCode en el nodo manifest
Name - android:label en el nodo application
PackageName : package en el nodo manifest
VersionString : android:versionName en el nodo application

API
Código fuente de AppInfo
Documentación de API para AppInfo

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Tema de la aplicación
18/12/2020 • 3 minutes to read • Edit Online

La API RequestedTheme forma parte de la clase AppInfo y proporciona información sobre el tema que el
sistema solicita para la aplicación en ejecución.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de RequestedTheme
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Obtener información sobre el tema


El tema de la aplicación solicitado puede detectarse con la API siguiente:

AppTheme appTheme = AppInfo.RequestedTheme;

Esto proporcionará el tema que el sistema ha solicitado actualmente para la aplicación. El valor devuelto será
cualquiera de los siguientes:
Sin especificar
Claro
Oscuro
Se devolverá Sin especificar cuando el sistema operativo no tenga un estilo de interfaz de usuario específico para
solicitarlo. Un ejemplo de esto se encuentran en los dispositivos que ejecutan versiones de iOS anteriores a la 13.0.

Detalles de implementación de la plataforma


Android
iOS
UWP
Android usa modos de configuración para especificar el tipo de tema que se va a solicitar al usuario. En función de
la versión de Android, el usuario puede modificarlo o se cambia cuando está habilitado el modo de ahorro de
batería.
Puede obtener más información en la documentación oficial de Android sobre el tema oscuro.

API
Código fuente de AppInfo
Documentación de API para AppInfo

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Barómetro
18/12/2020 • 3 minutes to read • Edit Online

La clase Barometer permite supervisar el sensor del barómetro del dispositivo, que mide la presión.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Barometer
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Barometer funciona mediante una llamada a los métodos Start y Stop para escuchar los cambios en la lectura
de presión del barómetro en hectopascales. Los cambios se enviarán a través del evento ReadingChanged . A
continuación le mostramos un ejemplo de uso:
public class BarometerTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public BarometerTest()
{
// Register for reading changes.
Barometer.ReadingChanged += Barometer_ReadingChanged;
}

void Barometer_ReadingChanged(object sender, BarometerChangedEventArgs e)


{
var data = e.Reading;
// Process Pressure
Console.WriteLine($"Reading: Pressure: {data.PressureInHectopascals} hectopascals");
}

public void ToggleBarometer()


{
try
{
if (Barometer.IsMonitoring)
Barometer.Stop();
else
Barometer.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

Detalles de implementación de la plataforma


Android
iOS
UWP
Sin detalles de implementación específicos para la plataforma.
API
Código fuente de Barometer
Documentación de API para Barometer
Xamarin.Essentials: Batería
18/12/2020 • 5 minutes to read • Edit Online

La clase Batter y le permite comprobar la información sobre la batería del dispositivo y supervisar los cambios.
Además, ofrece información sobre el estado de ahorro de energía del dispositivo, que indica si este se está
ejecutando en el modo de bajo consumo. Las aplicaciones deben evitar el procesamiento en segundo plano si el
estado de ahorro de energía del dispositivo está activado.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de Batter y , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso Battery es necesario y se debe configurar en el proyecto Android. Se puede agregar de las siguientes
maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.BatteryStats)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.BATTERY_STATS" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active el permiso Batter y (Batería). Esto actualizará
automáticamente el archivo AndroidManifest.xml .

Uso de Battery
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Compruebe la información actual de la batería:


var level = Battery.ChargeLevel; // returns 0.0 to 1.0 or 1.0 when on AC or no battery.

var state = Battery.State;

switch (state)
{
case BatteryState.Charging:
// Currently charging
break;
case BatteryState.Full:
// Battery is full
break;
case BatteryState.Discharging:
case BatteryState.NotCharging:
// Currently discharging battery or not being charged
break;
case BatteryState.NotPresent:
// Battery doesn't exist in device (desktop computer)
case BatteryState.Unknown:
// Unable to detect battery state
break;
}

var source = Battery.PowerSource;

switch (source)
{
case BatteryPowerSource.Battery:
// Being powered by the battery
break;
case BatteryPowerSource.AC:
// Being powered by A/C unit
break;
case BatteryPowerSource.Usb:
// Being powered by USB cable
break;
case BatteryPowerSource.Wireless:
// Powered via wireless charging
break;
case BatteryPowerSource.Unknown:
// Unable to detect power source
break;
}

Cada vez que se cambia cualquiera de las propiedades de la batería, se desencadena un evento:

public class BatteryTest


{
public BatteryTest()
{
// Register for battery changes, be sure to unsubscribe when needed
Battery.BatteryInfoChanged += Battery_BatteryInfoChanged;
}

void Battery_BatteryInfoChanged(object sender, BatteryInfoChangedEventArgs e)


{
var level = e.ChargeLevel;
var state = e.State;
var source = e.PowerSource;
Console.WriteLine($"Reading: Level: {level}, State: {state}, Source: {source}");
}
}

Es posible poner los dispositivos que usan baterías en el modo de ahorro en caso de baja energía. A veces, los
dispositivos cambian automáticamente a este modo, por ejemplo, cuando la batería cae por debajo del 20 % de su
capacidad. El sistema operativo responde al modo de ahorro de energía reduciendo las actividades que tienden a
agotar la batería. Para ayudar, las aplicaciones pueden evitar el procesamiento en segundo plano u otras
actividades de alta potencia cuando el modo de ahorro de energía está activado.
También puede conocer el estado de ahorro de energía actual del dispositivo con la propiedad estática
Battery.EnergySaverStatus :

// Get energy saver status


var status = Battery.EnergySaverStatus;

Esta propiedad devuelve un miembro de la enumeración EnergySaverStatus , que es On , Off o Unknown . Si la


propiedad devuelve On , la aplicación debe evitar el procesamiento en segundo plano o cualquier otra actividad
que pueda tener un consumo energético grande.
La aplicación también debe instalar un controlador de eventos. La clase Batter y expone un evento que se
desencadena cuando cambia el estado de ahorro de energía:

public class EnergySaverTest


{
public EnergySaverTest()
{
// Subscribe to changes of energy-saver status
Battery.EnergySaverStatusChanged += OnEnergySaverStatusChanged;
}

private void OnEnergySaverStatusChanged(EnergySaverStatusChangedEventArgs e)


{
// Process change
var status = e.EnergySaverStatus;
}
}

Si el estado de ahorro de energía cambia a On , la aplicación debe detener el procesamiento en segundo plano. Si
el estado cambia a Unknown o Off , la aplicación puede reanudar el procesamiento en segundo plano.

Diferencias entre plataformas


Android
iOS
UWP
No hay diferencias entre las plataformas.

API
Código fuente de Battery
Documentación de API para Battery

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Portapapeles
18/12/2020 • 2 minutes to read • Edit Online

La clase Clipboard permite copiar y pegar texto en el Portapapeles del sistema entre aplicaciones.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Clipboard
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Para comprobar si actualmente el Por tapapeles tiene texto listo para pegar:

var hasText = Clipboard.HasText;

Para establecer texto en el Por tapapeles :

await Clipboard.SetTextAsync("Hello World");

Para leer texto desde el Por tapapeles :

var text = await Clipboard.GetTextAsync();

Siempre que cambia el contenido del Portapapeles, se desencadena un evento:

public class ClipboardTest


{
public ClipboardTest()
{
// Register for clipboard changes, be sure to unsubscribe when needed
Clipboard.ClipboardContentChanged += OnClipboardContentChanged;
}

void OnClipboardContentChanged(object sender, EventArgs e)


{
Console.WriteLine($"Last clipboard change at {DateTime.UtcNow:T}";);
}
}

TIP
El acceso al Portapapeles debe realizarse en el subproceso de la interfaz de usuario principal. Consulte la API de MainThread
para ver cómo invocar métodos en el subproceso de la interfaz de usuario principal.
API
Código fuente de Clipboard
Documentación de API de Clipboard

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: convertidores de color
18/12/2020 • 2 minutes to read • Edit Online

La clase ColorConver ters de Xamarin.Essentials ofrece varios métodos auxiliares para System.Drawing.Color.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de los convertidores de color


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Al trabajar con System.Drawing.Color , puede usar los convertidores integrados de Xamarin.Forms para crear un
color a partir de Hsl, Hex o UInt.

var blueHex = ColorConverters.FromHex("#3498db");


var blueHsl = ColorConverters.FromHsl(204, 70, 53);
var blueUInt = ColorConverters.FromUInt(3447003);

Uso de extensiones de color


Los método de extensión de System.Drawing.Color le permiten aplicar propiedades diferentes:

var blue = ColorConverters.FromHex("#3498db");

// Multiplies the current alpha by 50%


var blueWithAlpha = blue.MultiplyAlpha(.5f);

Hay varios métodos de extensión, por ejemplo:


GetComplementary
MultiplyAlpha
ToUInt
WithAlpha
WithHue
WithLuminosity
WithSaturation

Uso de las extensiones de plataforma


Además, puede convertir System.Drawing.Color a la estructura de color específica de la plataforma. Estos métodos
solo se pueden llamar desde proyectos de UWP, iOS y Android.
var system = System.Drawing.Color.FromArgb(255, 52, 152, 219);

// Extension to convert to Android.Graphics.Color, UIKit.UIColor, or Windows.UI.Color


var platform = system.ToPlatformColor();

var platform = new Android.Graphics.Color(52, 152, 219, 255);

// Back to System.Drawing.Color
var system = platform.ToSystemColor();

El método ToSystemColor se aplica a Android.Graphics.Color, UIKit.UIColor y Windows.UI.Color.

API
Código fuente de los convertidores de color
Documentación sobre la API de los convertidores de color
Color fuente de las extensiones de color
Documentación sobre la API de las extensiones de color

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Brújula
18/12/2020 • 4 minutes to read • Edit Online

La clase Compass permite supervisar la dirección del norte magnético del dispositivo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Compass
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad Compass funciona mediante una llamada a los métodos Start y Stop para realizar escuchas de
los cambios realizados en la brújula. Los cambios se enviarán a través del evento ReadingChanged . A continuación
se muestra un ejemplo:
public class CompassTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public CompassTest()
{
// Register for reading changes, be sure to unsubscribe when finished
Compass.ReadingChanged += Compass_ReadingChanged;
}

void Compass_ReadingChanged(object sender, CompassChangedEventArgs e)


{
var data = e.Reading;
Console.WriteLine($"Reading: {data.HeadingMagneticNorth} degrees");
// Process Heading Magnetic North
}

public void ToggleCompass()


{
try
{
if (Compass.IsMonitoring)
Compass.Stop();
else
Compass.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Some other exception has occurred
}
}
}

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

Detalles de implementación de la plataforma


Android
Android no proporciona una API para recuperar la dirección de la brújula. El acelerómetro y el magnetómetro se
usan para calcular la dirección del norte magnético, lo que recomienda Google.
En casos poco habituales, es posible que vea resultados incoherentes porque sea necesario calibrar los sensores,
lo que implica mover el dispositivo describiendo un ocho. La mejor manera de hacerlo es abrir Google Maps,
pulsar en el punto de la ubicación y seleccionar Calibrar la brújula .
La ejecución simultánea de varios sensores desde la aplicación puede ajustar la velocidad del sensor.

Filtro de paso bajo


Debido a la forma de calcular y actualizar los valores de la brújula de Android, es posible que sea necesario
suavizarlos. Se puede aplicar un filtro de paso bajo que calcula el promedio de los valores de seno y coseno de los
ángulos y que se puede activar si se sobrecarga el método Start , que acepta el parámetro
bool applyLowPassFilter :

Compass.Start(SensorSpeed.UI, applyLowPassFilter: true);

Esto solo se aplica a la plataforma Android; el parámetro se ignora en iOS y UWP. Puede leer más información aquí.

API
Código fuente de Compass
Documentación de API para Compass

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Conectividad
18/12/2020 • 4 minutes to read • Edit Online

La clase Connectivity permite supervisar los cambios en las condiciones de red del dispositivo, revisar el acceso
actual a la red y cómo está conectado actualmente.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad Connectivity , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso AccessNetworkState es necesario y se debe configurar en el proyecto Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y compruebe el permiso Access Network State (Estado de
red de acceso). Esto actualizará automáticamente el archivo AndroidManifest.xml .

Uso de Connectivity
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Compruebe el acceso de red actual:

var current = Connectivity.NetworkAccess;

if (current == NetworkAccess.Internet)
{
// Connection to internet is available
}
Acceso a la red cae en estas categorías:
Internet : acceso a Internet y local.
ConstrainedInternet : acceso limitado a Internet. Indica la conectividad cautiva del portal, donde se
proporciona acceso local a un portal web, pero el acceso a Internet requiere que se proporcionen credenciales
específicas a través de un portal.
Local : solo acceso a la red local.
None : sin conectividad disponible.
Unknown : no se puede determinar la conectividad de Internet.
Puede comprobar qué tipo de perfil de conexión el dispositivo usa de manera activa:

var profiles = Connectivity.ConnectionProfiles;


if (profiles.Contains(ConnectionProfile.WiFi))
{
// Active Wi-Fi connection.
}

Cada vez que el perfil de conexión o el acceso a la red cambia, puede recibir un evento cuando se desencadena:

public class ConnectivityTest


{
public ConnectivityTest()
{
// Register for connectivity changes, be sure to unsubscribe when finished
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
}

void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)


{
var access = e.NetworkAccess;
var profiles = e.ConnectionProfiles;
}
}

Limitaciones
Es importante tener en cuenta que puede que NetworkAccess informe Internet , pero no hay disponible acceso
total a la Web. Debido a cómo funciona la conectividad en cada plataforma, solo puede garantizar que hay
disponible una conexión. Por ejemplo, es posible que el dispositivo esté conectado a una red Wi-Fi, pero el
enrutador no está conectado a Internet. En esta instancia, puede que se indique que hay Internet, pero no hay
disponible ninguna conexión activa.

API
Código fuente de Connectivity
Documentación de API de Connectivity

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Contactos
18/12/2020 • 3 minutes to read • Edit Online

La clase Contacts permite a un usuario seleccionar un contacto y recuperar información sobre él.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de Contacts , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso ReadContacts es necesario y se debe configurar en el proyecto Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.ReadContacts)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.READ_CONTACTS" /> />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active este permiso. Esto actualizará automáticamente el
archivo AndroidManifest.xml .

Seleccionar un contacto
Al llamar a Contacts.PickContactAsync() , aparecerá el cuadro de diálogo de contacto y permitirá al usuario recibir
información sobre el usuario.
try
{
var contact = await Contacts.PickContactAsync();

if(contact == null)
return;

var id = contact.Id;
var namePrefix = contact.NamePrefix;
var givenName = contact.GivenName;
var middleName = contact.MiddleName;
var familyName = contact.FamilyName;
var nameSuffix = contact.NameSuffix;
var displayName = contact.DisplayName;
var phones = contact.Phones; // List of phone numbers
var emails = contact.Emails; // List of email addresses
}
catch (Exception ex)
{
// Handle exception here.
}

Obtener todos los contactos


ObservableCollection<Contact> contactsCollect = new ObservableCollection<Contact>();

try
{
// cancellationToken parameter is optional
var cancellationToken = default(CancellationToken);
var contacts = await Contacts.GetAllAsync(cancellationToken);

if (contacts == null)
return;

foreach (var contact in contacts)


contactsCollect.Add(contact);
}
catch (Exception ex)
{
// Handle exception here.
}

Diferencias entre plataformas


Android
iOS
UWP
El parámetro cancellationToken del método GetAllAsync solo se usa en UWP.

API
Código fuente de Contacts
Documentación de Contacts API
Xamarin.Essentials: detección de agitaciones
18/12/2020 • 3 minutes to read • Edit Online

La clase Accelerometer permite supervisar el sensor del acelerómetro del dispositivo, que indica la aceleración
del dispositivo en un espacio tridimensional. Además, le permite registrar eventos que se realizarán cuando el
usuario agite el dispositivo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de la detección de agitaciones


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Para detectar la agitación del dispositivo, debe usar la funcionalidad Accelerometer mediante una llamada a los
métodos Start y Stop para realizar cambios en la aceleración y para detectar movimientos de agitación.
Siempre que se detecte agitación, se desencadenará un evento ShakeDetected . Se recomienda usar Game o faster
para SensorSpeed . A continuación le mostramos un ejemplo de uso:
public class DetectShakeTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.Game;

public DetectShakeTest()
{
// Register for reading changes, be sure to unsubscribe when finished
Accelerometer.ShakeDetected += Accelerometer_ShakeDetected ;
}

void Accelerometer_ShakeDetected (object sender, EventArgs e)


{
// Process shake event
}

public void ToggleAccelerometer()


{
try
{
if (Accelerometer.IsMonitoring)
Accelerometer.Stop();
else
Accelerometer.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

Detalles de implementación
La API para la detección de agitaciones usa lecturas sin procesar del acelerómetro para calcular la aceleración.
Utiliza un mecanismo de cola simple para detectar si tres cuartos de los eventos recientes del acelerómetro se han
producido durante el último medio segundo. La aceleración se calcula añadiendo el cuadrado de las lecturas X, Y y
Z del acelerómetro y comparándolo con un umbral determinado.

API
Código fuente de Accelerometer
Documentación de API de Accelerometer

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Información de pantalla del
dispositivo
18/12/2020 • 2 minutes to read • Edit Online

La clase DeviceDisplay proporciona información sobre las métricas de la pantalla del dispositivo que determinan
cómo se ejecuta la aplicación. También puede solicitar que la pantalla no se apague mientras la aplicación se esté
ejecutando.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de DeviceDisplay
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Información de la pantalla principal


Además de información básica del dispositivo, la clase DeviceDisplay contiene información sobre la pantalla y la
orientación del dispositivo.

// Get Metrics
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;

// Orientation (Landscape, Portrait, Square, Unknown)


var orientation = mainDisplayInfo.Orientation;

// Rotation (0, 90, 180, 270)


var rotation = mainDisplayInfo.Rotation;

// Width (in pixels)


var width = mainDisplayInfo.Width;

// Height (in pixels)


var height = mainDisplayInfo.Height;

// Screen density
var density = mainDisplayInfo.Density;

La clase DeviceDisplay también expone un evento al que se puede suscribir para que se desencadene siempre
que cambie cualquier métrica de pantalla:
public class DisplayInfoTest
{
public DisplayInfoTest()
{
// Subscribe to changes of screen metrics
DeviceDisplay.MainDisplayInfoChanged += OnMainDisplayInfoChanged;
}

void OnMainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)


{
// Process changes
var displayInfo = e.DisplayInfo;
}
}

La clase DeviceDisplay expone una propiedad bool con el nombre KeepScreenOn . Esta propiedad puede
establecerse para que intente impedir que la pantalla del dispositivo se apague o bloquee.

public class KeepScreenOnTest


{
public void ToggleScreenLock()
{
DeviceDisplay.KeepScreenOn = !DeviceDisplay.KeepScreenOn;
}
}

Diferencias entre plataformas


Android
iOS
UWP
No hay diferencias.

API
Código fuente de DeviceDisplay
Documentación de API de DeviceDisplay

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Información del dispositivo
18/12/2020 • 2 minutes to read • Edit Online

La clase DeviceInfo proporciona información sobre el dispositivo en el que se ejecuta la aplicación.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de DeviceInfo
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La información siguiente se expone a través de la API:

// Device Model (SMG-950U, iPhone10,6)


var device = DeviceInfo.Model;

// Manufacturer (Samsung)
var manufacturer = DeviceInfo.Manufacturer;

// Device Name (Motz's iPhone)


var deviceName = DeviceInfo.Name;

// Operating System Version Number (7.0)


var version = DeviceInfo.VersionString;

// Platform (Android)
var platform = DeviceInfo.Platform;

// Idiom (Phone)
var idiom = DeviceInfo.Idiom;

// Device Type (Physical)


var deviceType = DeviceInfo.DeviceType;

Plataformas
DeviceInfo.Platformse correlaciona con una cadena de constante que se asigna al sistema operativo. Los valores
se pueden comprobar con el struct DevicePlatform :
DevicePlatform.iOS : iOS
DevicePlatform.Android : Android
DevicePlatform.UWP : UWP
DevicePlatform.Unknown : desconocido

Expresiones
DeviceInfo.Idiom se correlaciona con una cadena de constante que se asigna al tipo de dispositivo en el que se
ejecuta la aplicación. Los valores se pueden comprobar con el struct DeviceIdiom :
DeviceIdiom.Phone : teléfono
DeviceIdiom.Tablet : tableta
DeviceIdiom.Desktop : escritorio
DeviceIdiom.TV : TV
DeviceIdiom.Watch : reloj
DeviceIdiom.Unknown : desconocido

Tipo de dispositivo
DeviceInfo.DeviceType pone en correlación una enumeración para determinar si la aplicación se ejecuta en un
dispositivo físico o virtual. Un dispositivo virtual es un simulador o emulador.

Detalles de implementación de la plataforma


iOS
iOS no expone una API para que los desarrolladores obtengan el modelo del dispositivo iOS específico. En su
lugar, se devuelve un identificador de hardware, como iPhone10,6 que hace referencia al iPhone X. Apple no
proporciona una asignación de estos identificadores, pero se puede encontrar en The iPhone Wiki y Get iOS Model
(fuentes no oficiales).

API
Código fuente de DeviceInfo
Documentación de API para DeviceInfo

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


:::no-loc(Xamarin.Essentials):::: Correo electrónico
18/12/2020 • 4 minutes to read • Edit Online

La clase Email permite que una aplicación abra la aplicación de correo electrónico predeterminada con
información especificada incluido el asunto, el cuerpo y los destinatarios (PARA, CC, CCO).
Para acceder a la función de Email , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
Si la versión de Android de destino del proyecto se establece en Android 11 (R API 30) , debe actualizar el
manifiesto de Android con las consultas que se usan con los nuevos requisitos de visibilidad de los paquetes.
Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest :

<queries>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

TIP
Para usar la API de correo electrónico en iOS, debe ejecutarla en un dispositivo físico, si no, se producirá una excepción.

Uso de Email
Agregue una referencia a :::no-loc(Xamarin.Essentials)::: en la clase:

using :::no-loc(Xamarin.Essentials):::;

Para usar la funcionalidad de Email se llama al método ComposeAsync con un valor EmailMessage que contiene
información sobre el correo electrónico:
public class EmailTest
{
public async Task SendEmail(string subject, string body, List<string> recipients)
{
try
{
var message = new EmailMessage
{
Subject = subject,
Body = body,
To = recipients,
//Cc = ccRecipients,
//Bcc = bccRecipients
};
await Email.ComposeAsync(message);
}
catch (FeatureNotSupportedException fbsEx)
{
// Email is not supported on this device
}
catch (Exception ex)
{
// Some other exception occurred
}
}
}

Datos adjuntos
Esta característica permite que una aplicación envíe archivos por correo electrónico a través de clientes de correo
electrónico en el dispositivo. :::no-loc(Xamarin.Essentials)::: detectará automáticamente el tipo de archivo (MIME) y
solicitará que el archivo se agregue como datos adjuntos. Cada cliente de correo electrónico es diferente y podría
admitir únicamente extensiones de archivo concretas o ninguna en absoluto.
A continuación se muestra un ejemplo en el que se escribe texto en el disco y se agrega como datos adjuntos a un
correo electrónico:

var message = new EmailMessage


{
Subject = "Hello",
Body = "World",
};

var fn = "Attachment.txt";
var file = Path.Combine(FileSystem.CacheDirectory, fn);
File.WriteAllText(file, "Hello World");

message.Attachments.Add(new EmailAttachment(file));

await Email.ComposeAsync(message);

Diferencias entre plataformas


Android
iOS
UWP
No todos los clientes de correo electrónico para Android admiten Html . Puesto que no hay ninguna manera de
detectar este problema, recomendamos usar PlainText para enviar correos electrónicos.
API
Código fuente de Email
Documentación de API para Email

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Selector de archivos
18/12/2020 • 4 minutes to read • Edit Online

La clase FilePicker permite a un usuario seleccionar uno o varios archivos del dispositivo.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de FilePicker , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso ReadExternalStorage es necesario y se debe configurar en el proyecto Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active este permiso. Esto actualizará automáticamente el
archivo AndroidManifest.xml .

TIP
Se debe llamar a todos los métodos del subproceso de interfaz de usuario porque Xamarin.Essentialscontrola
automáticamente las solicitudes y comprobaciones de permisos.

Selección de un archivo
El método FilePicker.PickAsync() permite al usuario seleccionar un archivo desde el dispositivo. Se pueden
seleccionar diferentes elementos PickOptions al llamar al método, lo que permite especificar el título que se va a
mostrar y los tipos de archivo que el usuario puede elegir. De forma predeterminada
async Task<FileResult> PickAndShow(PickOptions options)
{
try
{
var result = await FilePicker.PickAsync();
if (result != null)
{
Text = $"File Name: {result.FileName}";
if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
{
var stream = await result.OpenReadAsync();
Image = ImageSource.FromStream(() => stream);
}
}
}
catch (Exception ex)
{
// The user canceled or something went wrong
}
}

Los tipos de archivo predeterminados se proporcionan con FilePickerFileType.Images , FilePickerFileType.Png y


FilePickerFilerType.Videos . Puede especificar tipos de archivos personalizados al crear PickOptions y se pueden
personalizar por plataforma. Por ejemplo, aquí se muestra cómo especificaría determinados tipos de archivo de
cómic:

var customFileType =
new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
{
{ DevicePlatform.iOS, new[] { "public.my.comic.extension" } }, // or general UTType values
{ DevicePlatform.Android, new[] { "application/comics" } },
{ DevicePlatform.UWP, new[] { ".cbr", ".cbz" } },
{ DevicePlatform.Tizen, new[] { "*/*" } },
{ DevicePlatform.macOS, new[] { "cbr", "cbz" } }, // or general UTType values
});
var options = new PickOptions
{
PickerTitle = "Please select a comic file",
FileTypes = customFileType,
};

Selección de varios archivos


Si desea que el usuario elija varios archivos, puede llamar al método FilePicker.PickMultipleAsync() . También
toma PickOptions como parámetro para especificar información adicional. Los resultados son los mismos que
PickAsync , pero en lugar de un único elemento FileResult se devuelve un elemento IEnumerable<FileResult>
que se puede iterar.

Diferencias entre plataformas


Android
iOS
UWP
Es posible que el URI del archivo resultante no se pueda conservar entre los reinicios.
Xamarin.Essentials: Asistentes del sistema de archivos
18/12/2020 • 4 minutes to read • Edit Online

La clase FileSystem contiene una serie de aplicaciones auxiliares para buscar la caché y los directorios de datos
de la aplicación y abrir archivos dentro del paquete de aplicación.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.

Uso de aplicaciones auxiliares de sistema de archivos


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Para obtener el directorio de la aplicación para almacenar datos en caché . Los datos en caché se pueden usar
para cualquier dato que se tenga que conservar más tiempo que los temporales, pero no deben ser datos
necesarios para que el funcionamiento sea correcto, ya que el sistema operativo es el que determina el momento
en el que se borra el almacén.

var cacheDir = FileSystem.CacheDirectory;

Para obtener el directorio de nivel superior de la aplicación para todos los archivos que no son archivos de datos
de usuario. Se realiza una copia de seguridad de estos archivos con el marco de sincronización del sistema
operativo. Vea Detalles de implementación de la plataforma a continuación.

var mainDir = FileSystem.AppDataDirectory;

Para abrir un archivo que se incluye en el paquete de aplicación:

using (var stream = await FileSystem.OpenAppPackageFileAsync(templateFileName))


{
using (var reader = new StreamReader(stream))
{
var fileContents = await reader.ReadToEndAsync();
}
}

Detalles de implementación de la plataforma


Android
iOS
UWP
CacheDirector y : devuelve el CacheDir del contexto actual.
AppDataDirector y : devuelve el FilesDir del contexto actual y se realiza una copia de seguridad mediante
Copia de seguridad automática a partir de la API 23 y versiones posteriores.
Agregue un archivo a la carpeta Activos del proyecto de Android y marque la acción de compilación como
AndroidAsset para usarla con OpenAppPackageFileAsync .

API
Código fuente de las aplicaciones auxiliares de sistema de archivos
Documentación de API para FileSystem

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Linterna
18/12/2020 • 4 minutes to read • Edit Online

La clase Flashlight tiene la capacidad de activar o desactivar el flash de la cámara del dispositivo y convertirlo en
una linterna.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de Flashlight , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
Los permisos Flashlight y Camera son obligatorios y se deben configurar en el proyecto de Android. Se puede
agregar de las siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.Flashlight)]
[assembly: UsesPermission(Android.Manifest.Permission.Camera)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.FLASHLIGHT" />


<uses-permission android:name="android.permission.CAMERA" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos necesarios: y active los permisos FL ASHLIGHT (Linterna) y CAMERA
(Cámara). Esto actualizará automáticamente el archivo AndroidManifest.xml .
Mediante la adición de estos permisos Google Play filtrará automáticamente los dispositivos sin necesidad de
hardware específico. Para solucionarlo, agregue lo siguiente al archivo AssemblyInfo.cs del proyecto de Android:

[assembly: UsesFeature("android.hardware.camera", Required = false)]


[assembly: UsesFeature("android.hardware.camera.autofocus", Required = false)]

Esta API usa permisos en tiempo de ejecución en Android. Asegúrese de que Xamarin.Essentials se haya
inicializado por completo y de que el control de permisos esté configurado en la aplicación.
En MainLauncher del proyecto Android o en cualquier Activity que se inicia, Xamarin.Essentials se debe inicializar
en el método OnCreate :
protected override void OnCreate(Bundle savedInstanceState)
{
//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your code, it may also be
called: bundle
//...
}

Para controlar los permisos en tiempo de ejecución de Android, Xamarin.Essentials debe recibir cualquier
OnRequestPermissionsResult . Agregue el código siguiente a todas las clases Activity :

public override void OnRequestPermissionsResult(int requestCode, string[] permissions,


Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);


}

Uso de Flashlight
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La linterna se puede activar y desactivar a través de los métodos TurnOnAsync y TurnOffAsync :

try
{
// Turn On
await Flashlight.TurnOnAsync();

// Turn Off
await Flashlight.TurnOffAsync();
}
catch (FeatureNotSupportedException fnsEx)
{
// Handle not supported on device exception
}
catch (PermissionException pEx)
{
// Handle permission exception
}
catch (Exception ex)
{
// Unable to turn on/off flashlight
}

Detalles de implementación de la plataforma


Android
iOS
UWP
La clase Flashlight se ha optimizado en función del sistema operativo del dispositivo.
Nivel de API 23 y superior
En los niveles de API más recientes, se usa el Modo Linterna para activar o desactivar la unidad de flash del
dispositivo.
Nivel de API 22 e inferior
Se crea una textura de la superficie de cámara para activar o desactivar el FlashMode de la unidad de la cámara.

API
Código fuente de Flashlight
Documentación de API para Flashlight

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Codificación geográfica
18/12/2020 • 3 minutes to read • Edit Online

La clase Geocoding proporciona API para geocodificar una marca de posición en coordenadas de posición e
invertir las coordenadas de código geográfico a una marca de posición.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de Geocoding , se requiere la siguiente configuración específica para la
plataforma.
Android
iOS
UWP
No se requiere configuración adicional.

Uso de Geocoding
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Obtención de coordenadas de ubicación para una dirección:

try
{
var address = "Microsoft Building 25 Redmond WA USA";
var locations = await Geocoding.GetLocationsAsync(address);

var location = locations?.FirstOrDefault();


if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude:
{location.Altitude}");
}
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Handle exception that may have occurred in geocoding
}

La altitud no siempre está disponible. Si no lo está, es posible que la propiedad Altitude sea null o que el valor
sea cero. Si lo está, el valor se expresa en metros sobre el nivel del mar.

Uso la geocodificación inversa


La geocodificación inversa es el proceso de obtener marcas de posición para un conjunto de coordenadas
existente:

try
{
var lat = 47.673988;
var lon = -122.121513;

var placemarks = await Geocoding.GetPlacemarksAsync(lat, lon);

var placemark = placemarks?.FirstOrDefault();


if (placemark != null)
{
var geocodeAddress =
$"AdminArea: {placemark.AdminArea}\n" +
$"CountryCode: {placemark.CountryCode}\n" +
$"CountryName: {placemark.CountryName}\n" +
$"FeatureName: {placemark.FeatureName}\n" +
$"Locality: {placemark.Locality}\n" +
$"PostalCode: {placemark.PostalCode}\n" +
$"SubAdminArea: {placemark.SubAdminArea}\n" +
$"SubLocality: {placemark.SubLocality}\n" +
$"SubThoroughfare: {placemark.SubThoroughfare}\n" +
$"Thoroughfare: {placemark.Thoroughfare}\n";

Console.WriteLine(geocodeAddress);
}
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Handle exception that may have occurred in geocoding
}

Distancia entre dos ubicaciones


Las clases Location y LocationExtensions definen métodos para calcular la distancia entre dos ubicaciones.
Consulte el artículo Xamarin.Essentials: Geolocalización para ver un ejemplo.

API
Código fuente de Geocoding
Documentación de API para Geocoding

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Geolocalización
18/12/2020 • 9 minutes to read • Edit Online

La clase Geolocation proporciona las API para recuperar las coordenadas de geolocalización actuales del
dispositivo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de Geolocation , se requiere la siguiente configuración específica para la
plataforma:
Android
iOS
UWP
Los permisos Coarse y Fine Location son requeridos y se deben configurar en el proyecto de Android. Además, si
la aplicación tiene como destino Android 5.0 (nivel de API 21) o versiones posteriores, debe declarar que la
aplicación usa las características de hardware en el archivo de manifiesto. Se puede agregar de las siguientes
maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
[assembly: UsesFeature("android.hardware.location", Required = false)]
[assembly: UsesFeature("android.hardware.location.gps", Required = false)]
[assembly: UsesFeature("android.hardware.location.network", Required = false)]

O bien, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest :

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />


<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false" />

O bien, haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En
Manifiesto de Android , busque el área Permisos requeridos: y active los permisos
ACCESS_COARSE_LOCATION y ACCESS_FINE_LOCATION . Esto actualizará automáticamente el archivo
AndroidManifest.xml .
Esta API usa permisos en tiempo de ejecución en Android. Asegúrese de que Xamarin.Essentials se haya
inicializado por completo y de que el control de permisos esté configurado en la aplicación.
En MainLauncher del proyecto Android o en cualquier Activity que se inicia, Xamarin.Essentials se debe inicializar
en el método OnCreate :
protected override void OnCreate(Bundle savedInstanceState)
{
//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your code, it may also be
called: bundle
//...
}

Para controlar los permisos en tiempo de ejecución de Android, Xamarin.Essentials debe recibir cualquier
OnRequestPermissionsResult . Agregue el código siguiente a todas las clases Activity :

public override void OnRequestPermissionsResult(int requestCode, string[] permissions,


Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);


}

Uso de Geolocation
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La API Geolocation también le pedirá permisos al usuario cuando sea necesario.


Puede obtener la última ubicación conocida del dispositivo mediante una llamada al método
GetLastKnownLocationAsync . A menudo, esto es más rápido que hacer una consulta completa, pero puede ser
menos preciso y probablemente devuelva null si no existe ninguna ubicación en caché.

try
{
var location = await Geolocation.GetLastKnownLocationAsync();

if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude:
{location.Altitude}");
}
}
catch (FeatureNotSupportedException fnsEx)
{
// Handle not supported on device exception
}
catch (FeatureNotEnabledException fneEx)
{
// Handle not enabled on device exception
}
catch (PermissionException pEx)
{
// Handle permission exception
}
catch (Exception ex)
{
// Unable to get location
}
La altitud no siempre está disponible. Si no lo está, es posible que la propiedad Altitude sea null o que el valor
sea cero. Si lo está, el valor se expresa en metros sobre el nivel del mar.
Para consultar las coordenadas de ubicación del dispositivo actual, se puede usar GetLocationAsync . Es mejor
pasar un valor GeolocationRequest completo y CancellationToken , ya que se puede tardar algún tiempo en
obtener la ubicación del dispositivo.

CancellationTokenSource cts;

async Task GetCurrentLocation()


{
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);

if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude:
{location.Altitude}");
}
}
catch (FeatureNotSupportedException fnsEx)
{
// Handle not supported on device exception
}
catch (FeatureNotEnabledException fneEx)
{
// Handle not enabled on device exception
}
catch (PermissionException pEx)
{
// Handle permission exception
}
catch (Exception ex)
{
// Unable to get location
}
}

protected override void OnDisappearing()


{
if (cts != null && !cts.IsCancellationRequested)
cts.Cancel();
base.OnDisappearing();
}

Precisión de la ubicación geográfica


En la tabla siguiente se describe la precisión por plataforma:
Mínima
P L ATA F O RM A DISTA N C IA ( EN M ET RO S)

Android 500

iOS 3000

UWP 1000 - 5000


Bajo
P L ATA F O RM A DISTA N C IA ( EN M ET RO S)

Android 500

iOS 1000

UWP 300 - 3000

Media (valor predeterminado )


P L ATA F O RM A DISTA N C IA ( EN M ET RO S)

Android 100 - 500

iOS 100

UWP 30-500

Alto
P L ATA F O RM A DISTA N C IA ( EN M ET RO S)

Android 0 - 100

iOS 10

UWP <= 10

Óptima
P L ATA F O RM A DISTA N C IA ( EN M ET RO S)

Android 0 - 100

iOS ~0

UWP <= 10

Detección de ubicaciones ficticias


Algunos dispositivos pueden devolver una ubicación ficticia desde el proveedor o desde una aplicación que
proporciona ubicaciones ficticias. Puede detectarlo usando IsFromMockProvider en cualquier Location .

var request = new GeolocationRequest(GeolocationAccuracy.Medium);


var location = await Geolocation.GetLocationAsync(request);

if (location != null)
{
if(location.IsFromMockProvider)
{
// location is from a mock provider
}
}
Distancia entre dos ubicaciones
Las clases Location y LocationExtensions definen métodos CalculateDistance que permiten calcular la distancia
entre dos ubicaciones geográficas. Esta distancia calculada no tiene en cuenta las carreteras ni otros caminos, y
simplemente es la distancia más corta entre los dos puntos a lo largo de la superficie de la Tierra, lo que también
se conoce como distancia ortodrómica o coloquialmente, "distancia a vuelo de pájaro".
Por ejemplo:

Location boston = new Location(42.358056, -71.063611);


Location sanFrancisco = new Location(37.783333, -122.416667);
double miles = Location.CalculateDistance(boston, sanFrancisco, DistanceUnits.Miles);

El constructor Location tiene argumentos de latitud y longitud en ese orden. Los valores de latitud positivos están
al norte del Ecuador, y los valores de longitud positivos están al este del meridiano de Greenwich. Use el
argumento final CalculateDistance para especificar millas o kilómetros. La clase UnitConverters también define
los métodos KilometersToMiles y MilesToKilometers para la conversión entre las dos unidades.

Diferencias entre plataformas


La altitud se calcula de forma diferente en cada plataforma.
Android
iOS
UWP
En Android, la altitud, si está disponible, se devuelve en metros por encima del elipsoide de referencia WGS 84. Si
esta ubicación no tiene ninguna altitud, se devuelve 0.

API
Código fuente de Geolocation
Documentación de API para Geolocation

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Giroscopio
18/12/2020 • 2 minutes to read • Edit Online

La clase Gyroscope permite supervisar el sensor de giroscopio del dispositivo, que es la rotación sobre los tres
ejes principales del dispositivo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Gyroscope
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad Gyroscope funciona mediante una llamada a los métodos Start y Stop para realizar escuchas
de los cambios realizados en el giroscopio. Los cambios se enviarán a través del evento ReadingChanged en rad/s.
A continuación le mostramos un ejemplo de uso:
public class GyroscopeTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public GyroscopeTest()
{
// Register for reading changes.
Gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
}

void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)


{
var data = e.Reading;
// Process Angular Velocity X, Y, and Z reported in rad/s
Console.WriteLine($"Reading: X: {data.AngularVelocity.X}, Y: {data.AngularVelocity.Y}, Z:
{data.AngularVelocity.Z}");
}

public void ToggleGyroscope()


{
try
{
if (Gyroscope.IsMonitoring)
Gyroscope.Stop();
else
Gyroscope.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

API
Código fuente de Gyroscope
Documentación de API para Gyroscope
Xamarin.Essentials: Comentarios hápticos
18/12/2020 • 2 minutes to read • Edit Online

La clase HapticFeedback permite controlar los comentarios hápticos en el dispositivo.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de HapticFeedback , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso Vibrate (Vibrar) es necesario y se debe configurar en el proyecto de Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.VIBRATE" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active el permiso VIBRATE (Vibrar). Esto actualizará
automáticamente el archivo AndroidManifest.xml .

Uso de comentarios hápticos


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La función de comentarios hápticos se puede realizar con un tipo de comentarios Click o LongPress .
try
{
// Perform click feedback
HapticFeedback.Perform(HapticFeedbackType.Click);

// Or use long press


HapticFeedback.Perform(HapticFeedbackType.LongPress);
}
catch (FeatureNotSupportedException ex)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}

API
Código fuente de HapticFeedback
Documentación de HapticFeedback API
Xamarin.Essentials: Selector
18/12/2020 • 3 minutes to read • Edit Online

La clase Launcher permite que una aplicación abra un URI por el sistema. A menudo se usa al vincular en
profundidad en los esquemas de URI personalizados de otra aplicación. Si quiere abrir el explorador en un sitio
web, debe hacer referencia a la API Browser .

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Launcher
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Para usar la funcionalidad Launcher, llame al método OpenAsync y pase un string o Uri para abrirla. Si quiere, el
método CanOpenAsync se puede usar para comprobar si el esquema de URI se puede administrar desde una
aplicación del dispositivo.

public class LauncherTest


{
public async Task OpenRideShareAsync()
{
var supportsUri = await Launcher.CanOpenAsync("lyft://");
if (supportsUri)
await Launcher.OpenAsync("lyft://ridetype?id=lyft_line");
}
}

Esto se puede combinar en una sola llamada con TryOpenAsync , que comprueba si se puede abrir el parámetro y,
si es así, lo abre.

public class LauncherTest


{
public async Task<bool> OpenRideShareAsync()
{
return await Launcher.TryOpenAsync("lyft://ridetype?id=lyft_line");
}
}

Configuración de una plataforma adicional


Android
iOS
UWP
Sin configuración adicional.
Archivos
Esta característica permite a una aplicación solicitar a otras que abran y vean un archivo. Xamarin.Essentials
detectará automáticamente el tipo de archivo (MIME) y solicitará su apertura.
A continuación se muestra un ejemplo de cómo escribir texto y cómo solicitar la apertura:

var fn = "File.txt";
var file = Path.Combine(FileSystem.CacheDirectory, fn);
File.WriteAllText(file, "Hello World");

await Launcher.OpenAsync(new OpenFileRequest


{
File = new ReadOnlyFile(file)
});

Diferencias entre plataformas


Android
iOS
UWP
La tarea devuelta desde CanOpenAsync se completa de inmediato.

API
Código fuente de Launcher
Documentación de API para Launcher

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Magnetómetro
18/12/2020 • 2 minutes to read • Edit Online

La clase Magnetometer permite supervisar el sensor de magnetómetro del dispositivo, que indica la orientación
del dispositivo con respecto al campo magnético de la Tierra.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Magnetometer
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad Magnetometer funciona mediante una llamada a los métodos Start y Stop para realizar
escuchas de los cambios en el magnetómetro. Los cambios se enviarán a través del evento ReadingChanged . A
continuación le mostramos un ejemplo de uso:
public class MagnetometerTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public MagnetometerTest()
{
// Register for reading changes.
Magnetometer.ReadingChanged += Magnetometer_ReadingChanged;
}

void Magnetometer_ReadingChanged(object sender, MagnetometerChangedEventArgs e)


{
var data = e.Reading;
// Process MagneticField X, Y, and Z
Console.WriteLine($"Reading: X: {data.MagneticField.X}, Y: {data.MagneticField.Y}, Z:
{data.MagneticField.Z}");
}

public void ToggleMagnetometer()


{
try
{
if (Magnetometer.IsMonitoring)
Magnetometer.Stop();
else
Magnetometer.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Todos los datos se devuelven en µT (microteslas).

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

API
Código fuente de Magnetometer
Documentación de API para Magnetometer
Xamarin.Essentials: MainThread
18/12/2020 • 6 minutes to read • Edit Online

La clase MainThread permite que las aplicaciones ejecuten código en el subproceso de ejecución principal y
determinen si un bloque de código determinado se ejecuta actualmente en el subproceso principal.

Fondo
La mayoría de los sistemas operativos, incluidos iOS, Android y Plataforma universal de Windows, usan un
modelo de un único subproceso para el código que participa en la interfaz de usuario. Este modelo resulta
necesario para serializar de manera adecuada los eventos de la interfaz de usuario, incluidas pulsaciones de
teclas y entradas táctiles. Con frecuencia, este subproceso se denomina el subproceso principal, el subproceso
de interfaz de usuario o el subproceso de UI. La desventaja que presenta este modelo es que todo el código
que accede a los elementos de la interfaz de usuario se deben ejecutar en el subproceso principal de la
aplicación.
Algunas veces, las aplicaciones deben usar eventos que llamar al controlador de eventos en un subproceso de
ejecución secundario. (Es posible que las clases Accelerometer , Compass , Gyroscope , Magnetometer y
OrientationSensor de Xamarin.Essentials devuelvan información en un subproceso secundario cuando se usa
con velocidades más rápidas). Si el controlador de eventos debe acceder a los elementos de la interfaz de
usuario, debe ejecutar ese código en el subproceso principal. La clase MainThread permite que la aplicación
ejecute este código en el subproceso principal.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de
que la biblioteca está correctamente instalada y configurada en los proyectos.

Ejecución de código en el subproceso principal


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Para ejecutar código en el subproceso principal, llame al método MainThread.BeginInvokeOnMainThread estático.


El argumento es un objeto Action , que no es más que un método sin argumentos y sin valor devuelto:

MainThread.BeginInvokeOnMainThread(() =>
{
// Code to run on the main thread
});

También es posible definir un método independiente para el código que se debe ejecutar en el subproceso
principal:

void MyMainThreadCode()
{
// Code to run on the main thread
}
Luego, para ejecutar este método en el subproceso principal, haga referencia a él en el método
BeginInvokeOnMainThread :

MainThread.BeginInvokeOnMainThread(MyMainThreadCode);

NOTE
Xamarin.Forms tiene un método denominado Device.BeginInvokeOnMainThread(Action) que hace lo mismo que
MainThread.BeginInvokeOnMainThread(Action) . Si bien puede usar cualquier método en una aplicación de
Xamarin.Forms, considere si el código de llamada necesita una dependencia de Xamarin.Forms. Si no es así,
MainThread.BeginInvokeOnMainThread(Action) probablemente sea una mejor opción.

Determinación de si el código se ejecuta en el subproceso principal


La clase MainThread también permite que una aplicación determine si un bloque de código determinado se
ejecuta en el subproceso principal. La propiedad IsMainThread devuelve true si el código que llama a la
propiedad se ejecuta en el subproceso principal. Un programa puede usar esta propiedad para ejecutar
código diferente para el subproceso principal o secundario:

if (MainThread.IsMainThread)
{
// Code to run if this is the main thread
}
else
{
// Code to run if this is a secondary thread
}

Tal vez se pregunte si debe comprobar que el código se esté ejecutando en un subproceso secundario antes
de llamar a BeginInvokeOnMainThread , por ejemplo, de esta manera:

if (MainThread.IsMainThread)
{
MyMainThreadCode();
}
else
{
MainThread.BeginInvokeOnMainThread(MyMainThreadCode);
}

Quizás sospeche que esta comprobación puede mejorar el rendimiento si el bloque de código ya se ejecuta
en el subproceso principal.
Sin embargo, esta comprobación no es necesaria. Las implementaciones de plataforma de los
BeginInvokeOnMainThread mismos comprueban si la llamada se realiza en el subproceso principal. La
penalización de rendimiento si llama a BeginInvokeOnMainThread cuando no es realmente necesario es muy
pequeña.

Otros métodos
La clase MainThread incluye los siguientes métodos static adicionales, que se pueden usar para interactuar
con los elementos de la interfaz de usuario de los subprocesos de fondo:
M ÉTO DO A RGUM EN TO S VA LO RES DEVUELTO S P RO P Ó SITO

InvokeOnMainThreadAsync<T> Func<T> Task<T> Invoca un elemento


Func<T> en el subproceso
principal y espera a que se
complete.

InvokeOnMainThreadAsync Action Task Invoca un elemento


Action en el subproceso
principal y espera a que se
complete.

InvokeOnMainThreadAsync<T> Func<Task<T>> Task<T> Invoca un elemento


Func<Task<T>> en el
subproceso principal y
espera a que se complete.

InvokeOnMainThreadAsync Func<Task> Task Invoca un elemento


Func<Task> en el
subproceso principal y
espera a que se complete.

GetMainThreadSynchronizationContextAsync Task<SynchronizationContext>Devuelve el elemento


SynchronizationContext
para el subproceso
principal.

API
Código fuente de MainThread
Documentación de API de MainThread

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Asignación
18/12/2020 • 3 minutes to read • Edit Online

La clase Map permite que una aplicación abra la aplicación de mapas instalada en una ubicación o marca de
posición específica.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Map
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Map funciona mediante una llamada al método OpenAsync con Location o Placemark abierto con
MapLaunchOptions opcional.

public class MapTest


{
public async Task NavigateToBuilding25()
{
var location = new Location(47.645160, -122.1306032);
var options = new MapLaunchOptions { Name = "Microsoft Building 25" };

try
{
await Map.OpenAsync(location, options);
}
catch (Exception ex)
{
// No map application available to open
}
}
}

Cuando se abre con Placemark , se requiere la siguiente información:


CountryName
AdminArea
Thoroughfare
Locality
public class MapTest
{
public async Task NavigateToBuilding25()
{
var placemark = new Placemark
{
CountryName = "United States",
AdminArea = "WA",
Thoroughfare = "Microsoft Building 25",
Locality = "Redmond"
};
var options = new MapLaunchOptions { Name = "Microsoft Building 25" };

try
{
await Map.OpenAsync(placemark, options);
}
catch (Exception ex)
{
// No map application available to open or placemark can not be located
}
}
}

Métodos de extensión.
Si ya tiene una referencia a Location o a Placemark , puede usar el método de extensión integrado OpenMapAsync
con MapLaunchOptions opcional:

public class MapTest


{
public async Task OpenPlacemarkOnMap(Placemark placemark)
{
try
{
await placemark.OpenMapAsync();
}
catch (Exception ex)
{
// No map application available to open
}
}
}

Modo de instrucciones
Si llama a OpenMapAsync sin MapLaunchOptions , el mapa se iniciará en la ubicación especificada. Si quiere, puede
hacer que se calcule una ruta de navegación desde la posición actual del dispositivo. Para ello, establezca
NavigationMode en MapLaunchOptions :
public class MapTest
{
public async Task NavigateToBuilding25()
{
var location = new Location(47.645160, -122.1306032);
var options = new MapLaunchOptions { NavigationMode = NavigationMode.Driving };

await Map.OpenAsync(location, options);


}
}

Diferencias entre plataformas


Android
iOS
UWP
NavigationMode admite Bicycling (Bicicleta), Driving (Automóvil) y Walking (A pie).

Detalles de implementación de la plataforma


Android
iOS
UWP
Android usa el esquema geo: de URI para iniciar la aplicación de mapas en el dispositivo. Esto podría pedirle al
usuario que seleccione de una aplicación existente que admite este esquema de URI. Xamarin.Essentials se ha
probado con Google Maps, que admite este esquema.

API
Código fuente de Map
Documentación de API para Map

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
:::no-loc(Xamarin.Essentials):::: Selector de archivos
multimedia
18/12/2020 • 4 minutes to read • Edit Online

La clase MediaPicker permite al usuario escoger o tomar una foto o vídeo en el dispositivo.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de MediaPicker , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
Los permisos siguientes son requeridos y se deben configurar en el proyecto de Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

// Needed for Picking photo/video


[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]

// Needed for Taking photo/video


[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.Camera)]

// Add these properties if you would like to filter out devices that do not have cameras, or set to false to
make them optional
[assembly: UsesFeature("android.hardware.camera", Required = true)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active estos permisos. Esto actualizará automáticamente el
archivo AndroidManifest.xml .
Uso del Selector de archivos multimedia
La clase MediaPicker tiene los métodos siguientes que devuelven un elemento FileResult que se puede usar
para obtener la ubicación de los archivos o para leerlo como Stream .
PickPhotoAsync : abre el explorador de elementos multimedia para seleccionar una foto.
CapturePhotoAsync : abre la cámara para tomar una foto.
PickVideoAsync : abre el explorador de elementos multimedia para seleccionar un vídeo.
CaptureVideoAsync : abre la cámara para grabar un vídeo.

Cada método toma opcionalmente un parámetro MediaPickerOptions que permite establecer el elemento Title
en algunos sistemas operativos que se muestran a los usuarios.

TIP
Se debe llamar a todos los métodos del subproceso de interfaz de usuario porque :::no-loc(Xamarin.Essentials):::controla
automáticamente las solicitudes y comprobaciones de permisos.

Uso general
async Task TakePhotoAsync()
{
try
{
var photo = await MediaPicker.CapturePhotoAsync();
await LoadPhotoAsync(photo);
Console.WriteLine($"CapturePhotoAsync COMPLETED: {PhotoPath}");
}
catch (Exception ex)
{
Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
}
}

async Task LoadPhotoAsync(FileResult photo)


{
// canceled
if (photo == null)
{
PhotoPath = null;
return;
}
// save the file into local storage
var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
using (var stream = await photo.OpenReadAsync())
using (var newStream = File.OpenWrite(newFile))
await stream.CopyToAsync(newStream);

PhotoPath = newFile;
}

API
Código fuente de MediaPicker
Documentación de MediaPicker API
Xamarin.Essentials: Explorador
18/12/2020 • 3 minutes to read • Edit Online

La clase Browser permite que una aplicación abra un vínculo web en el explorador optimizado preferido del
sistema o en el explorador externo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de Browser , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
Si la versión de Android de destino del proyecto se establece en Android 11 (R API 30) , debe actualizar el
manifiesto de Android con las consultas que se usan con los nuevos requisitos de visibilidad de los paquetes.
Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest :

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent>
</queries>

Uso de Browser
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad Browser funciona mediante una llamada al método OpenAsync con Uri y BrowserLaunchMode .
public class BrowserTest
{
public async Task OpenBrowser(Uri uri)
{
try
{
await Browser.OpenAsync(uri, BrowserLaunchMode.SystemPreferred);
}
catch(Exception ex)
{
// An unexpected error occured. No browser may be installed on the device.
}
}
}

Este método devuelve un valor después de que el usuario inicie el explorador, a veces sin que tenga que llegar a
cerrarlo. El resultado bool indica si el inicio ha sido correcto o no.

Personalización
Al usar el explorador preferido por el sistema, tiene disponibles varias opciones de personalización para iOS y
Android, por ejemplo, TitleMode (solo en Android), preferencias para las opciones de color para la Toolbar (en
iOS y Android) y Controls que se muestran (solo en iOS).
Estas opciones se especifican usando BrowserLaunchOptions al llamar a OpenAsync .

await Browser.OpenAsync(uri, new BrowserLaunchOptions


{
LaunchMode = BrowserLaunchMode.SystemPreferred,
TitleMode = BrowserTitleMode.Show,
PreferredToolbarColor = Color.AliceBlue,
PreferredControlColor = Color.Violet
});

Detalles de implementación de la plataforma


Android
iOS
UWP
El modo de inicio determina cómo se inicia el explorador:

Preferencia del sistema


Se intenta usar Pestañas personalizadas para cargar el Uri y mantener el reconocimiento de la navegación.

Externo
Se usará Intent para solicitar que se abra el URI a través del explorador normal del sistema.
API
Código fuente de Browser
Documentación de API para Browser

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: OrientationSensor
18/12/2020 • 7 minutes to read • Edit Online

La clase OrientationSensor permite supervisar la orientación de un dispositivo en un espacio tridimensional.

NOTE
Esta clase se usa para determinar la orientación de un dispositivo en un espacio tridimensional. Si tiene que determinar si la
visualización de vídeo del dispositivo está en modo vertical u horizontal, use la propiedad Orientation del objeto
ScreenMetrics disponible en la clase DeviceDisplay .

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.

Uso de OrientationSensor
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

OrientationSensor se habilita mediante una llamada al método Start para supervisar los cambios en la
orientación del dispositivo y se deshabilita al llamar al método Stop . Los cambios se enviarán a través del evento
ReadingChanged . Este es un uso de ejemplo:
public class OrientationSensorTest
{
// Set speed delay for monitoring changes.
SensorSpeed speed = SensorSpeed.UI;

public OrientationSensorTest()
{
// Register for reading changes, be sure to unsubscribe when finished
OrientationSensor.ReadingChanged += OrientationSensor_ReadingChanged;
}

void OrientationSensor_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)


{
var data = e.Reading;
Console.WriteLine($"Reading: X: {data.Orientation.X}, Y: {data.Orientation.Y}, Z:
{data.Orientation.Z}, W: {data.Orientation.W}");
// Process Orientation quaternion (X, Y, Z, and W)
}

public void ToggleOrientationSensor()


{
try
{
if (OrientationSensor.IsMonitoring)
OrientationSensor.Stop();
else
OrientationSensor.Start(speed);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Las lecturas de OrientationSensor se informan como un Quaternion que describe la orientación del dispositivo
en función de dos sistemas de coordenadas tridimensionales:
El dispositivo (generalmente un teléfono o tableta) tiene un sistema de coordenadas tridimensional con los
siguientes ejes:
El eje X positivo apunta a la derecha de la visualización en modo vertical.
El eje Y positivo apunta a la parte de arriba del dispositivo en modo horizontal.
El eje Z positivo apunta fuera de la pantalla.
El sistema de coordenadas tridimensional de la Tierra tiene estos ejes:
El eje X positivo es tangente a la superficie de la Tierra y apunta al este.
El eje Y positivo también es tangente a la superficie de la Tierra y apunta al norte.
El eje Z positivo es perpendicular a la superficie de la Tierra y apunta hacia arriba.
Quaterniondescribe la rotación del sistema de coordenadas del dispositivo en relación con el sistema de
coordenadas de la Tierra.
Un valor Quaternion está estrechamente relacionado con la rotación alrededor de un eje. Si un eje de rotación es
el vector normalizado (ax, ay, az) y el ángulo de rotación es Θ , los componentes (X, Y, Z, W) del cuaternión son:
(ax·sin(Θ /2), ay·sin(Θ /2), az·sin(Θ /2), cos(Θ /2))
x y z

Estos son los sistemas de coordenadas de la derecha por lo que, con el pulgar de la mano derecha apuntando en
la dirección positiva del eje de rotación, la curva de los dedos indica la dirección de la rotación de los ángulos
positivos.
Ejemplos:
Cuando el dispositivo está pantalla arriba sobre una mesa, con la parte superior del mismo (en el modo
vertical) apuntando al norte, los dos sistemas de coordenadas están alineados. El valor Quaternion
representa el cuaternión de identidad (0, 0, 0, 1). Todas las rotaciones se pueden analizar en relación con
esta posición.
Cuando el dispositivo está pantalla arriba sobre una mesa y la parte superior del mismo (en el modo
vertical) apunta al oeste, el valor de Quaternion es (0, 0, 0.707, 0.707). El dispositivo se giró 90 grados
alrededor del eje Z de la Tierra.
Cuando el dispositivo se sostiene de manera vertical, con la parte superior (en el modo vertical) apuntando
al cielo y la parte posterior orientada al norte, es porque el dispositivo se giró 90 grados alrededor del eje
X. El valor de Quaternion es (0.707, 0, 0, 0.707).
Si el dispositivo se coloca de manera tal que el borde izquierdo esté sobre una mesa y la parte superior
apunte al norte, es porque el dispositivo se giró –90 grados alrededor del eje Y (o 90 grados alrededor del
eje Y negativo). El valor de Quaternion es (0, -0.707, 0, 0.707).

Velocidad de sensor
Más rápido : obtener los datos del sensor tan rápido como sea posible (no se garantiza la devolución en el
subproceso de interfaz de usuario).
Juego : velocidad adecuada para juegos (no se garantiza la devolución en el subproceso de interfaz de
usuario).
Predeterminado : velocidad predeterminada adecuada para los cambios de orientación de pantalla.
Interfaz de usuario : velocidad adecuada para la interfaz de usuario general.
Si no se garantiza la ejecución del controlador de eventos en el subproceso de interfaz de usuario y si el
controlador de eventos necesita tener acceso a elementos de la interfaz de usuario, use el método
MainThread.BeginInvokeOnMainThread para ejecutar ese código en el subproceso de interfaz de usuario.

API
Código fuente de OrientationSensor
Documentación de API de OrientationSensor
Xamarin.Essentials: Permisos
18/12/2020 • 11 minutes to read • Edit Online

La clase Permissions proporciona la capacidad de comprobar y solicitar permisos en tiempo de ejecución.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Esta API usa permisos en tiempo de ejecución en Android. Asegúrese de que Xamarin.Essentials se haya
inicializado por completo y de que el control de permisos esté configurado en la aplicación.
En MainLauncher del proyecto Android o en cualquier Activity que se inicia, Xamarin.Essentials se debe inicializar
en el método OnCreate :

protected override void OnCreate(Bundle savedInstanceState)


{
//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your code, it may also be
called: bundle
//...
}

Para controlar los permisos en tiempo de ejecución de Android, Xamarin.Essentials debe recibir cualquier
OnRequestPermissionsResult . Agregue el código siguiente a todas las clases Activity :

public override void OnRequestPermissionsResult(int requestCode, string[] permissions,


Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);


}

Uso de permisos
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Comprobación de permisos
Para comprobar el estado actual de un permiso, use el método CheckStatusAsync junto con el permiso específico
para el que obtener el estado.

var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();

Se produce una excepción PermissionException si no se declara el permiso necesario.


Es mejor comprobar el estado del permiso antes de solicitarlo. Cada sistema operativo devuelve un estado
predeterminado diferente si nunca se ha solicitado el usuario. iOS devuelve Unknown , mientras que otros
devuelven Denied . Si el estado es Granted , no es necesario realizar otras llamadas. En iOS, si el estado es Denied ,
debe pedir al usuario que cambie el permiso en la configuración, y en Android puede llamar a
ShouldShowRationale para detectar si el usuario ya denegó el permiso en el pasado.

Solicitar permisos
Para solicitar un permiso a los usuarios, use el método RequestAsync junto con el permiso específico que se va a
solicitar. Si el usuario ha concedido previamente el permiso y no lo ha revocado, este método devuelve Granted
inmediatamente y no muestra ningún cuadro de diálogo.

var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

Se produce una excepción PermissionException si no se declara el permiso necesario.


Tenga en cuenta que, en algunas plataformas, una solicitud de permiso solo se puede activar una vez. El
desarrollador debe controlar otros mensajes para comprobar si un permiso tiene el estado Denied y pedir al
usuario que lo active manualmente.

Estado del permiso


Al usar los elementos CheckStatusAsync o RequestAsync , se devuelve un estado PermissionStatus que se puede
usar para determinar los pasos siguientes:
Desconocido: el permiso tiene un estado desconocido.
Denegado: el usuario denegó la solicitud del permiso.
Deshabilitado: la característica está deshabilitada en el dispositivo.
Concedido: el usuario concedió el permiso o se ha concedido automáticamente.
Restringido: en un estado restringido.

Explicación de por qué se necesita el permiso

Es un procedimiento recomendado explicar el motivo por el que la aplicación necesita un permiso específico. En
iOS debe especificar una cadena que se muestre al usuario. Android no tiene esta capacidad y, además, el estado
de permiso tiene como valor predeterminado Deshabilitado. Esto limita la capacidad de saber si el usuario ha
denegado el permiso o si es la primera vez que se le solicita. El método ShouldShowRationale se puede usar para
determinar si se debe mostrar una interfaz de usuario educativa. Si el método devuelve true , esto se debe a que
el usuario ha denegado o deshabilitado el permiso en el pasado. Otras plataformas siempre devolverán false
cuando se llame a este método.

Permisos disponibles
Xamarin.Essentials intenta abstraer tantos permisos como sea posible. Pero cada sistema operativo tiene un
conjunto diferente de permisos en tiempo de ejecución. Además, hay diferencias a la hora de proporcionar una
única API para algunos permisos. Esta es una guía para los permisos disponibles actualmente:
Guía de iconos:
: compatible
: no compatible/requerido

P ERM ISO A N DRO ID IO S UW P WATC H O S T VO S T IZ EN

CalendarRead

CalendarWrite

Cámara

ContactsRead

ContactsWrite

Linterna

LocationWhen
InUse

LocationAlway
s

Multimedia

Micrófono

Teléfono

Fotos

Recordatorios

Sensores

SMS

Voz

StorageRead

StorageWrite

Si un permiso se marca con , siempre devolverá Granted cuando se compruebe o solicite.

Uso general
El código siguiente presenta el patrón de uso general para determinar si un permiso se ha concedido, o para
solicitarlo, en el caso de que todavía no se haya hecho. Este código usa características que están disponibles con la
versión 1.6.0 de Xamarin.Essentials y las posteriores.
public async Task<PermissionStatus> CheckAndRequestLocationPermission()
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();

if (status == PermissionStatus.Granted)
return status;

if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)


{
// Prompt the user to turn on in settings
// On iOS once a permission has been denied it may not be requested again from the application
return status;
}

if (Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>())
{
// Prompt the user with additional information as to why the permission is needed
}

status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

return status;
}

Cada tipo de permiso puede tener una instancia de este creada, para que se pueda llamar directamente a los
métodos.

public async Task GetLocationAsync()


{
var status = await CheckAndRequestPermissionAsync(new Permissions.LocationWhenInUse());
if (status != PermissionStatus.Granted)
{
// Notify user permission was denied
return;
}

var location = await Geolocation.GetLocationAsync();


}

public async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>(T permission)


where T : BasePermission
{
var status = await permission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await permission.RequestAsync();
}

return status;
}

Extensión de permisos
La API Permissions se creó para aportar flexibilidad y extensibilidad a las aplicaciones que requieren validación o
permisos adicionales que no están incluidos en Xamarin.Essentials. Cree una clase que herede de BasePermission
e implemente los métodos abstractos necesarios.
public class MyPermission : BasePermission
{
// This method checks if current status of the permission
public override Task<PermissionStatus> CheckStatusAsync()
{
throw new System.NotImplementedException();
}

// This method is optional and a PermissionException is often thrown if a permission is not declared
public override void EnsureDeclared()
{
throw new System.NotImplementedException();
}

// Requests the user to accept or deny a permission


public override Task<PermissionStatus> RequestAsync()
{
throw new System.NotImplementedException();
}
}

Al implementar un permiso en una plataforma específica, se puede heredar de la clase BasePlatformPermission .


Esto proporciona métodos auxiliares de la plataforma adicionales para comprobar automáticamente las
declaraciones. Esto puede ayudar a la hora de crear permisos personalizados para realizar agrupaciones. Por
ejemplo, puede solicitar acceso Leer y Escribir en el almacenamiento en Android con el siguiente permiso
personalizado.

public class ReadWriteStoragePermission : Xamarin.Essentials.Permissions.BasePlatformPermission


{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string
androidPermission, bool isRuntime)>
{
(Android.Manifest.Permission.ReadExternalStorage, true),
(Android.Manifest.Permission.WriteExternalStorage, true)
}.ToArray();
}

Después, puede llamar al nuevo permiso desde el proyecto de Android.

await Permissions.RequestAsync<ReadWriteStoragePermission>();

Si quiere llamar a esta API desde el código compartido, puede crear una interfaz y usar un servicio de dependencia
para efectuar el registro y obtener la implementación.

public interface IReadWritePermission


{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}

Después, implemente la interfaz en el proyecto de la plataforma:


public class ReadWriteStoragePermission : Xamarin.Essentials.Permissions.BasePlatformPermission,
IReadWritePermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string
androidPermission, bool isRuntime)>
{
(Android.Manifest.Permission.ReadExternalStorage, true),
(Android.Manifest.Permission.WriteExternalStorage, true)
}.ToArray();
}

Luego, puede registrar la implementación específica:

DependencyService.Register<IReadWritePermission, ReadWriteStoragePermission>();

A continuación, desde el proyecto compartido, puede resolverla y usarla:

var readWritePermission = DependencyService.Get<IReadWritePermission>();


var status = await readWritePermission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await readWritePermission.RequestAsync();
}

Detalles de implementación de la plataforma


Android
iOS
UWP
Los permisos deben tener los atributos coincidentes establecidos en el archivo de manifiesto de Android. El estado
de permiso tiene como valor predeterminado Denegado.
Obtenga más información en el documento Permisos en Xamarin.Android.

API
Código fuente de los permisos
Documentación de la API Permisos

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


:::no-loc(Xamarin.Essentials):::: Marcador telefónico
18/12/2020 • 2 minutes to read • Edit Online

La clase PhoneDialer permite que una aplicación abra un número de teléfono en el marcador telefónico.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Android
iOS
UWP
Si la versión de Android de destino del proyecto se establece en Android 11 (R API 30) , debe actualizar el
manifiesto de Android con las consultas que se usan con los nuevos requisitos de visibilidad de los paquetes.
Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest :

<queries>
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel"/>
</intent>
</queries>

Uso del marcador telefónico


Agregue una referencia a :::no-loc(Xamarin.Essentials)::: en la clase:

using :::no-loc(Xamarin.Essentials):::;

La funcionalidad de marcador telefónico funciona mediante una llamada al método Open con un número de
teléfono con el que abrir el marcador. Cuando se solicita Open , la API intentará automáticamente dar formato al
número en función del código de país, si se especifica.
public class PhoneDialerTest
{
public void PlacePhoneCall(string number)
{
try
{
PhoneDialer.Open(number);
}
catch (ArgumentNullException anEx)
{
// Number was null or white space
}
catch (FeatureNotSupportedException ex)
{
// Phone Dialer is not supported on this device.
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

API
Código fuente del marcador telefónico
Documentación de API para PhoneDialer

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: extensiones de plataforma
18/12/2020 • 3 minutes to read • Edit Online

Xamarin.Essentials ofrece varios métodos de extensión de plataforma cuando se trabaja con tipos de plataforma
como Rect, Size y Point. Esto significa que puede convertir la versión System de estos tipos a tipos específicos de
iOS, Android y UWP.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de las extensiones de plataforma


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Todas las extensiones de plataforma solo se pueden llamar desde proyectos UWP, iOS o Android.

Extensiones de Android
Solo se puede acceder a estas extensiones desde un proyecto de Android.
Actividad y contexto de la aplicación
Con las extensiones de plataforma de la clase Platform , puede acceder a los elementos Context o Activity para
la aplicación en ejecución.

var context = Platform.AppContext;

// Current Activity or null if not initialized or not started.


var activity = Platform.CurrentActivity;

Si hay una situación en la que se necesita el elemento Activity , pero la aplicación no se ha iniciado totalmente, se
debe usar el método WaitForActivityAsync .

var activity = await Platform.WaitForActivityAsync();

Ciclo de vida de la actividad


Además de obtener la actividad actual, también puede registrarse en los eventos de ciclo de vida.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);

Xamarin.Essentials.Platform.Init(this, bundle);

Xamarin.Essentials.Platform.ActivityStateChanged += Platform_ActivityStateChanged;
}

protected override void OnDestroy()


{
base.OnDestroy();
Xamarin.Essentials.Platform.ActivityStateChanged -= Platform_ActivityStateChanged;
}

void Platform_ActivityStateChanged(object sender, Xamarin.Essentials.ActivityStateChangedEventArgs e) =>


Toast.MakeText(this, e.State.ToString(), ToastLength.Short).Show();

Los estados de actividad son los siguientes:


Creado
Reanudado
En pausa
Destruido
SaveInstanceState
Comenzado
Detenido
Lea la documentación Ciclo de vida de la actividad para obtener más información.

Extensiones de iOS
Solo se puede acceder a estas extensiones desde un proyecto de iOS.
UIViewController actual
Acceder al elemento UIViewController actualmente visible:

var vc = Platform.GetCurrentUIViewController();

Este método devolverá null si no puede detectar un elemento UIViewController .

Extensiones multiplataforma
Estas extensiones existen en todas las plataformas.
Punto

var system = new System.Drawing.Point(x, y);

// Convert to CoreGraphics.CGPoint, Android.Graphics.Point, and Windows.Foundation.Point


var platform = system.ToPlatformPoint();

// Back to System.Drawing.Point
var system2 = platform.ToSystemPoint();

Tamaño
var system = new System.Drawing.Size(width, height);

// Convert to CoreGraphics.CGSize, Android.Util.Size, and Windows.Foundation.Size


var platform = system.ToPlatformSize();

// Back to System.Drawing.Size
var system2 = platform.ToSystemSize();

Rectángulo

var system = new System.Drawing.Rectangle(x, y, width, height);

// Convert to CoreGraphics.CGRect, Android.Graphics.Rect, and Windows.Foundation.Rect


var platform = system.ToPlatformRectangle();

// Back to System.Drawing.Rectangle
var system2 = platform.ToSystemRectangle();

API
Código fuente de los convertidores
Documentación sobre la API de los convertidores de puntos
Documentación sobre la API de los convertidores de rectángulos
Documentación sobre la API de los convertidores de tamaño
:::no-loc(Xamarin.Essentials):::: Preferencias
18/12/2020 • 5 minutes to read • Edit Online

La clase Preferences ayuda a almacenar las preferencias de la aplicación en un almacén de clave y valor.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que
la biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Preferences
Agregue una referencia a :::no-loc(Xamarin.Essentials)::: en la clase:

using :::no-loc(Xamarin.Essentials):::;

Para guardar un valor para una clave determinada en las preferencias:

Preferences.Set("my_key", "my_value");

Para recuperar un valor de las preferencias o un valor predeterminado si no se establece:

var myValue = Preferences.Get("my_key", "default_value");

Para comprobar si una clave determinada existe en las preferencias:

bool hasKey = Preferences.ContainsKey("my_key");

Para quitar la clave de las preferencias:

Preferences.Remove("my_key");

Para quitar todas las preferencias:

Preferences.Clear();

TIP
Los métodos anteriores toman un parámetro string opcional denominado sharedName . Este parámetro se usa para
crear contenedores adicionales para las preferencias que son útiles en algunos casos de uso. Un caso de uso es cuando la
aplicación necesita compartir preferencias entre extensiones o con una aplicación de inspección. Lea los detalles de
implementación de la plataforma a continuación.

Tipos de datos admitidos


En Preferences se admiten los tipos de datos siguientes:
bool
double
int
float
long
string
DateTime

Integración con la configuración del sistema


Las preferencias se almacenan de forma nativa, lo que permite integrar la configuración en la configuración
nativa del sistema. Siga la documentación y los ejemplos de la plataforma para integrarlos con ella:
Apple: Implementación de un lote de configuración de iOS
Ejemplo de preferencias de la aplicación de iOS
Configuración de watchOS
Android: Introducción a las pantallas de configuración

Detalles de implementación
Los valores de DateTime se almacenan en un formato binario de 64 bits (entero largo) mediante dos métodos
definidos por la clase DateTime : El método ToBinary se usa para codificar el valor DateTime , mientras que el
método FromBinary descodifica el valor. Vea la documentación de estos métodos para obtener los ajustes que se
podrían realizar en los valores descodificados cuando se almacena un valor DateTime que no es de hora universal
coordinada (UTC).

Detalles de implementación de la plataforma


Android
iOS
UWP
Todos los datos se almacenan en Preferencias compartidas. Si no se especifica ningún elemento sharedName , se
usan las preferencias compartidas predeterminadas; de lo contrario, el nombre se usa para obtener una
preferencia compartida privada con el nombre especificado.

Persistencia
La desinstalación de la aplicación hará que se quiten todas las preferencias , con la excepción de las aplicaciones
que tienen como destino y se ejecutan en Android 6.0 (nivel de API 23), o una versión posterior, que usan copia
de seguridad automática . Esta característica está activada de forma predeterminada y conserva los datos de
aplicación, incluidas las preferencias compar tidas , que son las que usa la API Preferences . Se puede
deshabilitar si se sigue la documentación de Google.

Limitaciones
Cuando se almacena una cadena, esta API está pensada para almacenar pequeñas cantidades de texto. El
rendimiento puede ser inferior si se intenta usar para almacenar grandes cantidades de texto.

API
Código fuente de Preferences
Documentación de API para Preferences

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Captura de pantalla
18/12/2020 • 2 minutes to read • Edit Online

La clase Screenshot permite realizar una captura de la pantalla de la aplicación que se muestra actualmente.

Introducción
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de la captura de pantalla


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Después, llame a CaptureAsync para realizar una captura de la pantalla actual de la aplicación en ejecución. Esto
devolverá un elemento ScreenshotResult que se puede usar para obtener Width , Height y un elemento Stream
de la captura de pantalla tomada.

async Task CaptureScreenshot()


{
var screenshot = await Screenshot.CaptureAsync();
var stream = await screenshot.OpenReadAsync();

Image = ImageSource.FromStream(() => stream);


}

API
Código fuente de la captura de pantalla
Documentación de Screenshot API
:::no-loc(Xamarin.Essentials):::: Almacenamiento
seguro
18/12/2020 • 9 minutes to read • Edit Online

La clase SecureStorage ayuda a almacenar pares de clave-valor sencillos de manera segura.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad SecureStorage , se requiere la siguiente configuración específica para la
plataforma:
Android
iOS
UWP

TIP
Copia de seguridad para aplicaciones es una característica de Android 6.0 (nivel de API 23) y versiones posteriores que crea
copias de seguridad de los datos de aplicación del usuario (preferencias compartidas, archivos en el almacenamiento interno
de la aplicación y otros archivos específicos). Los datos se restauran cuando se reinstala o instala una aplicación en un
dispositivo nuevo. Esto puede afectar a SecureStorage , que utiliza las preferencias compartidas de las que se creó una
copia de seguridad y que no se pueden descifrar cuando se realiza la restauración. :::no-loc(Xamarin.Essentials)::: controla
automáticamente este caso quitando la clave para que se pueda restablecer, pero puede dar un paso adicional
deshabilitando Copia de seguridad automática.

Habilitación o deshabilitación de copia de seguridad


Puede elegir deshabilitar Copia de seguridad automática para toda la aplicación al establecer el valor
android:allowBackup en false en el archivo AndroidManifest.xml . Este enfoque solo se recomienda si planea
restaurar los datos de otra manera.

<manifest ... >


...
<application android:allowBackup="false" ... >
...
</application>
</manifest>

Copia de seguridad selectiva


Es posible configurar Copia de seguridad automática para deshabilitar la copia de seguridad de contenido
específico. Puede crear un conjunto de reglas personalizadas para excluir los elementos SecureStore de la copia
de seguridad.
1. Establezca el atributo android:fullBackupContent en AndroidManifest.xml :
<application ...
android:fullBackupContent="@xml/auto_backup_rules">
</application>

2. Cree un archivo XML denominado auto_backup_rules.xml en el directorio Resources/xml con la acción


de compilación AndroidResource . Luego, establezca este contenido que incluye todas las preferencias
compartidas, excepto SecureStorage :

<?xml version="1.0" encoding="utf-8"?>


<full-backup-content>
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="${applicationId}.xamarinessentials.xml"/>
</full-backup-content>

Uso de Secure Storage


Agregue una referencia a :::no-loc(Xamarin.Essentials)::: en la clase:

using :::no-loc(Xamarin.Essentials):::;

Para guardar un valor para una clave determinada en el almacenamiento seguro:

try
{
await SecureStorage.SetAsync("oauth_token", "secret-oauth-token-value");
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}

Para recuperar un valor desde el almacenamiento seguro:

try
{
var oauthToken = await SecureStorage.GetAsync("oauth_token");
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}

NOTE
Si no hay ningún valor asociado con la clave solicitada, GetAsync devolverá null .

Para quitar una clave específica, llame a:

SecureStorage.Remove("oauth_token");

Para quitar todas las claves, llame a:


SecureStorage.RemoveAll();

TIP
Es posible que se produzca una excepción al llamar a GetAsync o SetAsync . Esto puede deberse a que un dispositivo no
admita el almacenamiento seguro, las claves de cifrado cambiantes o los daños en los datos. Es mejor controlar esto
mediante la eliminación y posterior reincorporación del valor, si es posible.

Detalles de implementación de la plataforma


Android
iOS
UWP
Android KeyStore se usa para almacenar la clave de cifrado con la que se cifra el valor antes de guardarlo en
Preferencias compartidas con un nombre de archivo [ID-PAQUETE-APLICACIÓN].xamarinessentials . La clave
(no una clave criptográfica, la clave para el valor ) usada en el archivo de preferencias compartido es un hash MD5
de la clave pasada a las API SecureStorage .
Nivel de API 23 y superior
En los niveles de API más nuevos, una clave AES se obtiene de Android KeyStore y se usa con una cifra
AES/GCM/NoPadding para cifrar el valor antes de que se almacene en el archivo de preferencias compartidas.
Nivel de API 22 e inferior
En los niveles de API anteriores, Android KeyStore solo admite el almacenamiento de claves RSA , que se usa con
una cifra RSA/ECB/PKCS1Padding para cifrar una clave AES (generada de manera aleatoria en tiempo de
ejecución) y se almacena en el archivo de preferencias compartidas en la clave SecureStorageKey , si todavía no se
ha generado una.
SecureStorage usa la API Preferences y sigue la misma persistencia de datos que se describe en la
documentación sobre Preferencias. Si se actualiza un dispositivo desde Nivel de API 22 o inferior a Nivel de API 23
y superior, se seguirá usando este tipo de cifrado a menos que la aplicación se desinstale o se llame a RemoveAll .

Limitaciones
Esta API está pensada para almacenar pequeñas cantidades de texto. El rendimiento puede ser lento si intenta
usarla para almacenar grandes cantidades de texto.

API
Código fuente de SecureStorage
Documentación de API de SecureStorage

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Compartir
18/12/2020 • 4 minutes to read • Edit Online

La clase Share permite que una aplicación comparta datos como texto y vínculos web con otras aplicaciones del
dispositivo.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Share
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Share funciona mediante una llamada al método RequestAsync con una carga de solicitud de datos que incluye
información para compartir con otras aplicaciones. Se pueden combinar texto y Uri, cada plataforma controlara el
filtrado basado en el contenido.

public class ShareTest


{
public async Task ShareText(string text)
{
await Share.RequestAsync(new ShareTextRequest
{
Text = text,
Title = "Share Text"
});
}

public async Task ShareUri(string uri)


{
await Share.RequestAsync(new ShareTextRequest
{
Uri = uri,
Title = "Share Web Link"
});
}
}

Interfaz de usuario para compartir con una aplicación externa que aparece cuando se realiza la solicitud:
Archivo
Esta característica permite que una aplicación comparta archivos con otras aplicaciones del dispositivo.
Xamarin.Essentials detectará automáticamente el tipo de archivo (MIME) y solicitará el uso compartido. Cada
plataforma podría admitir únicamente determinadas extensiones de archivo.
A continuación se muestra un ejemplo en el que se escribe texto en el disco y se comparte con otras aplicaciones:

var fn = "Attachment.txt";
var file = Path.Combine(FileSystem.CacheDirectory, fn);
File.WriteAllText(file, "Hello World");

await Share.RequestAsync(new ShareFileRequest


{
Title = Title,
File = new ShareFile(file)
});

Varios archivos

El uso compartido de varios archivos difiere con respecto al de un único archivo solo en la posibilidad de enviar
varios archivos a la vez:

var file1 = Path.Combine(FileSystem.CacheDirectory, "Attachment1.txt");


File.WriteAllText(file, "Content 1");
var file2 = Path.Combine(FileSystem.CacheDirectory, "Attachment2.txt");
File.WriteAllText(file, "Content 2");

await Share.RequestAsync(new ShareMultipleFilesRequest


{
Title = ShareFilesTitle,
Files = new ShareFile[] { new ShareFile(file1), new ShareFile(file2) }
});

Ubicación de la presentación
Al solicitar un recurso compartido en iPadOS, tiene la posibilidad de presentar un control emergente. Esto
especifica dónde aparecerá la ventana emergente y dónde apuntará directamente una flecha. Esta ubicación suele
ser el control que inició la acción. Puede especificar la ubicación con la propiedad PresentationSourceBounds :

await Share.RequestAsync(new ShareFileRequest


{
Title = Title,
File = new ShareFile(file),
PresentationSourceBounds = DeviceInfo.Platform== DevicePlatform.iOS && DeviceInfo.Idiom ==
DeviceIdiom.Tablet
? new System.Drawing.Rectangle(0, 20, 0, 0)
: System.Drawing.Rectangle.Empty
});

Si usa Xamarin.Forms, podrá pasar View y calcular los límites:

public static class ViewHelpers


{
public static Rectangle GetAbsoluteBounds(this Xamarin.Forms.View element)
{
Element looper = element;

var absoluteX = element.X + element.Margin.Top;


var absoluteY = element.Y + element.Margin.Left;

// Add logic to handle titles, headers, or other non-view bars

while (looper.Parent != null)


{
looper = looper.Parent;
if (looper is Xamarin.Forms.View v)
{
absoluteX += v.X + v.Margin.Top;
absoluteY += v.Y + v.Margin.Left;
}
}

return new Rectangle(absoluteX, absoluteY, element.Width, element.Height);


}

public static System.Drawing.Rectangle ToSystemRectangle(this Rectangle rect) =>


new System.Drawing.Rectangle((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
}

Después, se puede usar al llamar a RequstAsync :


public Command<Xamarin.Forms.View> ShareCommand { get; } = new Command<Xamarin.Forms.View>(Share);
async void Share(Xamarin.Forms.View element)
{
try
{
Analytics.TrackEvent("ShareWithFriends");
var bounds = element.GetAbsoluteBounds();

await Share.RequestAsync(new ShareTextRequest


{
PresentationSourceBounds = bounds.ToSystemRectangle(),
Title = "Title",
Text = "Text"
});
}
catch (Exception)
{
// Handle exception that share failed
}
}

Puede pasar el elemento que realiza la llamada cuando se desencadene Command :

<Button Text="Share"
Command="{Binding ShareWithFriendsCommand}"
CommandParameter="{Binding Source={RelativeSource Self}}"/>

Diferencias entre plataformas


Android
iOS
UWP
La propiedad Subject se usa para el asunto deseado de un mensaje.

API
Código fuente de Share
Documentación de API para Share

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: SMS
18/12/2020 • 2 minutes to read • Edit Online

La clase SMS permite que una aplicación abra la aplicación SMS predeterminada con un mensaje especificado
para enviar un destinatario.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la función de SMS , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
Si la versión de Android de destino del proyecto se establece en Android 11 (R API 30) , debe actualizar el
manifiesto de Android con las consultas que se usan con los nuevos requisitos de visibilidad de los paquetes.
Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest :

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="smsto"/>
</intent>
</queries>

Uso de SMS
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad SMS funciona mediante una llamada al método ComposeAsync , un SmsMessage que contiene el
destinatario del mensaje y el cuerpo del mismo, ambos opcionales.
public class SmsTest
{
public async Task SendSms(string messageText, string recipient)
{
try
{
var message = new SmsMessage(messageText, new []{ recipient });
await Sms.ComposeAsync(message);
}
catch (FeatureNotSupportedException ex)
{
// Sms is not supported on this device.
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

Si quiere, puede pasar varios destinatarios a un SmsMessage :

public class SmsTest


{
public async Task SendSms(string messageText, string[] recipients)
{
try
{
var message = new SmsMessage(messageText, recipients);
await Sms.ComposeAsync(message);
}
catch (FeatureNotSupportedException ex)
{
// Sms is not supported on this device.
}
catch (Exception ex)
{
// Other error has occurred.
}
}
}

API
Código fuente de SMS
Documentación de API para SMS

Vídeo relacionado
Encuentre más vídeos de Xamarin en Channel 9 y YouTube.
Xamarin.Essentials: Texto a voz
18/12/2020 • 3 minutes to read • Edit Online

La clase TextToSpeech permite que una aplicación utilice los motores de texto a voz para responder a texto del
dispositivo y también para consultar los idiomas disponibles que el motor puede admitir.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Text-to-Speech
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Text-to-Speech funciona mediante una llamada al método SpeakAsync con parámetros opcionales y de texto y se
devuelve una vez que finaliza la declaración.

public async Task SpeakNowDefaultSettings()


{
await TextToSpeech.SpeakAsync("Hello World");

// This method will block until utterance finishes.


}

public void SpeakNowDefaultSettings2()


{
TextToSpeech.SpeakAsync("Hello World").ContinueWith((t) =>
{
// Logic that will run after utterance finishes.

}, TaskScheduler.FromCurrentSynchronizationContext());
}

Este método toma un elemento CancellationToken opcional para detener la declaración una vez que se inicia.
CancellationTokenSource cts;
public async Task SpeakNowDefaultSettings()
{
cts = new CancellationTokenSource();
await TextToSpeech.SpeakAsync("Hello World", cancelToken: cts.Token);

// This method will block until utterance finishes.


}

// Cancel speech if a cancellation token exists & hasn't been already requested.
public void CancelSpeech()
{
if (cts?.IsCancellationRequested ?? true)
return;

cts.Cancel();
}

Text-to-Speech pondrá automáticamente en la cola las solicitudes de voz del mismo subproceso.

bool isBusy = false;


public void SpeakMultiple()
{
isBusy = true;
Task.Run(async () =>
{
await TextToSpeech.SpeakAsync("Hello World 1");
await TextToSpeech.SpeakAsync("Hello World 2");
await TextToSpeech.SpeakAsync("Hello World 3");
isBusy = false;
});

// or you can query multiple without a Task:


Task.WhenAll(
TextToSpeech.SpeakAsync("Hello World 1"),
TextToSpeech.SpeakAsync("Hello World 2"),
TextToSpeech.SpeakAsync("Hello World 3"))
.ContinueWith((t) => { isBusy = false; }, TaskScheduler.FromCurrentSynchronizationContext());
}

Configuración de voz
Para más información sobre cómo se responde el audio, SpeechOptions permite ajustar el volumen, el tono y la
configuración regional.

public async Task SpeakNow()


{
var settings = new SpeechOptions()
{
Volume = .75f,
Pitch = 1.0f
};

await TextToSpeech.SpeakAsync("Hello World", settings);


}

Los siguientes son los valores compatibles para estos parámetros:

PA RÁ M ET RO M ÍN IM A M Á XIM O

Tono 0 2.0
PA RÁ M ET RO M ÍN IM A M Á XIM O

Volumen 0 1.0

Configuraciones regionales de voz


Cada plataforma admite distintas configuraciones regionales para responder texto en distintos idiomas y acentos.
Las plataformas tienen distintos códigos y formas de especificar la configuración regional, que es la razón por la
que Xamarin.Essentials proporciona una clase Locale multiplataforma y una manera de hacer consultas con
GetLocalesAsync .

public async Task SpeakNow()


{
var locales = await TextToSpeech.GetLocalesAsync();

// Grab the first locale


var locale = locales.FirstOrDefault();

var settings = new SpeechOptions()


{
Volume = .75f,
Pitch = 1.0f,
Locale = locale
};

await TextToSpeech.SpeakAsync("Hello World", settings);


}

Limitaciones
No se garantiza la cola de declaraciones si se llama a través de varios subprocesos.
La reproducción de audio en segundo plano no se admite de manera oficial.

API
Código fuente de TextToSpeech
Documentación de TextToSpeech API

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: convertidores de unidades
18/12/2020 • 2 minutes to read • Edit Online

La clase UnitConver ters proporciona varios convertidores de unidades para ayudar a los desarrolladores que
usan Xamarin.Essentials.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de convertidores de unidades


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

Todos los convertidores de unidades están disponibles mediante el uso de la clase estática UnitConverters de
Xamarin.Essentials. Por ejemplo, puede convertir fácilmente grados Fahrenheit a Celsius.

var celsius = UnitConverters.FahrenheitToCelsius(32.0);

Esta es una lista de las conversiones disponibles:


FahrenheitToCelsius
CelsiusToFahrenheit
CelsiusToKelvin
KelvinToCelsius
MilesToMeters
MilesToMeters
KilometersToMiles
MetersToInternationalFeet
InternationalFeetToMeters
DegreesToRadians
RadiansToDegrees
DegreesPerSecondToRadiansPerSecond
RadiansPerSecondToDegreesPerSecond
DegreesPerSecondToHertz
DegreesPerSecondToHertz
HertzToDegreesPerSecond
HertzToDegreesPerSecond
KilopascalsToHectopascals
HectopascalsToKilopascals
KilopascalsToPascals
HectopascalsToKilopascals
AtmospheresToPascals
PascalsToAtmospheres
CoordinatesToMiles
CoordinatesToKilometers
KilogramsToPounds
PoundsToKilograms
StonesToPounds
PoundsToStones

API
Código fuente de los convertidores de unidades
Documentación sobre la API de los convertidores de unidades

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Seguimiento de versiones
18/12/2020 • 2 minutes to read • Edit Online

La clase VersionTracking permite comprobar los números de versión y compilación de las aplicaciones además
de ver información adicional, por ejemplo si es la primera vez que se ha iniciado la aplicación o para la versión
actual, obtener información de la compilación anterior y mucho más.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.

Uso de Seguimiento de versiones


Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La primera vez que use la clase VersionTracking , se iniciará el seguimiento de la versión actual. Solo se debe
llamar a Track al principio de la aplicación cada vez que se cargue para asegurarse de que se realiza el
seguimiento de la información de versión actual:

VersionTracking.Track();

Después de la llamada inicial a Track , se puede leer la información de la versión:


// First time ever launched application
var firstLaunch = VersionTracking.IsFirstLaunchEver;

// First time launching current version


var firstLaunchCurrent = VersionTracking.IsFirstLaunchForCurrentVersion;

// First time launching current build


var firstLaunchBuild = VersionTracking.IsFirstLaunchForCurrentBuild;

// Current app version (2.0.0)


var currentVersion = VersionTracking.CurrentVersion;

// Current build (2)


var currentBuild = VersionTracking.CurrentBuild;

// Previous app version (1.0.0)


var previousVersion = VersionTracking.PreviousVersion;

// Previous app build (1)


var previousBuild = VersionTracking.PreviousBuild;

// First version of app installed (1.0.0)


var firstVersion = VersionTracking.FirstInstalledVersion;

// First build of app installed (1)


var firstBuild = VersionTracking.FirstInstalledBuild;

// List of versions installed (1.0.0, 2.0.0)


var versionHistory = VersionTracking.VersionHistory;

// List of builds installed (1, 2)


var buildHistory = VersionTracking.BuildHistory;

Detalles de implementación de la plataforma


Toda la información de la versión se almacena mediante la API Preferences de Xamarin.Essentials y se almacena
con un nombre de archivo [ID-DEL-PAQUETE-DE-L A-APLICACIÓN].xamarinessentials.versiontracking , y
sigue la misma persistencia de datos que se describe en la documentación de Preferences.

API
Código fuente de VersionTracking
Documentación de API para VersionTracking

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Vibración
18/12/2020 • 2 minutes to read • Edit Online

La clase Vibration permite iniciar y detener la funcionalidad de vibración durante un período de tiempo
determinado.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de Vibration , se requiere la siguiente configuración específica para la plataforma.
Android
iOS
UWP
El permiso Vibrate (Vibrar) es necesario y se debe configurar en el proyecto de Android. Se puede agregar de las
siguientes maneras:
Abra el archivo AssemblyInfo.cs de la carpeta Propiedades y agregue lo siguiente:

[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]

O BIEN, actualice el manifiesto de Android:


Abra el archivo AndroidManifest.xml de la carpeta Propiedades y agregue lo siguiente dentro del nodo
manifest .

<uses-permission android:name="android.permission.VIBRATE" />

O haga clic con el botón derecho en el proyecto de Android y abra las propiedades del proyecto. En Manifiesto
de Android , busque el área Permisos requeridos: y active el permiso VIBRATE (Vibrar). Esto actualizará
automáticamente el archivo AndroidManifest.xml .

Uso de Vibration
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La funcionalidad de Vibration se puede solicitar para un período de tiempo concreto o el valor predeterminado de
500 milisegundos.
try
{
// Use default vibration length
Vibration.Vibrate();

// Or use specified time


var duration = TimeSpan.FromSeconds(1);
Vibration.Vibrate(duration);
}
catch (FeatureNotSupportedException ex)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}

Se puede solicitar la cancelación de la vibración del dispositivo con el método Cancel :

try
{
Vibration.Cancel();
}
catch (FeatureNotSupportedException ex)
{
// Feature not supported on device
}
catch (Exception ex)
{
// Other error has occurred.
}

Diferencias entre plataformas


Android
iOS
UWP
No hay diferencias entre las plataformas.

API
Código fuente de Vibration
Documentación de API para Vibration

Vídeo relacionado

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.


Xamarin.Essentials: Autenticador web
18/12/2020 • 13 minutes to read • Edit Online

La clase WebAuthenticator permite iniciar flujos basados en explorador que escuchan una devolución de
llamada a una dirección URL específica registrada en la aplicación.

Información general
Muchas aplicaciones requieren la adición de la autenticación de usuario y esto suele significar permitir que los
usuarios inicien sesión en sus cuentas de inicio de sesión existentes de Microsoft, Facebook, Google y ahora Apple.
La biblioteca de autenticación de Microsoft (MSAL) proporciona una excelente solución llave en mano para
agregar la autenticación a la aplicación. Hay incluso compatibilidad con las aplicaciones de Xamarin en su paquete
NuGet de cliente.
Si le interesa usar su propio servicio web para la autenticación, es posible usar WebAuthenticator para
implementar la funcionalidad del lado cliente.

¿Por qué usar un back-end de servidor?


Muchos proveedores de autenticación han pasado a ofrecer solo flujos de autenticación explícitos o de dos
segmentos para garantizar una mayor seguridad. Esto significa que necesitará un "secreto de cliente" del
proveedor para completar el flujo de autenticación. Lamentablemente, las aplicaciones móviles no son un lugar
ideal para almacenar secretos y todo lo que se almacena en el código o los archivos binarios de una aplicación
móvil o, en caso contrario, se considera que no son seguros a nivel general.
El procedimiento recomendado es usar un back-end web como una capa intermedia entre su aplicación móvil y el
proveedor de autenticación.

IMPORTANT
Se recomienda encarecidamente usar patrones y bibliotecas de autenticación anteriores destinados exclusivamente para
móviles y que no usen un back-end web en el flujo de autenticación, debido a la falta de seguridad inherente para almacenar
secretos de cliente.

Primeros pasos
Para empezar a usar esta API, lea la guía de introducción para Xamarin.Essentials con el fin de asegurarse de que la
biblioteca está correctamente instalada y configurada en los proyectos.
Para acceder a la funcionalidad de WebAuthenticator , se requiere la siguiente configuración específica para la
plataforma.
Android
iOS
UWP
Android requiere una configuración del filtro de intención para controlar el identificador URI de devolución de
llamada. Esto se realiza con facilidad mediante la creación de subclases de la clase
WebAuthenticatorCallbackActivity :
NOTE
Considere la posibilidad de implementar vínculos a aplicaciones Android para controlar el identificador URI de devolución de
llamada y asegurarse de que la aplicación es la única que se puede registrar para controlar el identificador URI de devolución
de llamada.

const string CALLBACK_SCHEME = "myapp";

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]


[IntentFilter(new[] { Android.Content.Intent.ActionView },
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
DataScheme = CALLBACK_SCHEME)]
public class WebAuthenticationCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity
{
}

También tendrá que volver a llamar a Essentials desde la invalidación OnResume en MainActivity :

protected override void OnResume()


{
base.OnResume();

Xamarin.Essentials.Platform.OnResume();
}

Uso de WebAuthenticator
Agregue una referencia a Xamarin.Essentials en la clase:

using Xamarin.Essentials;

La API se compone principalmente de un único método AuthenticateAsync que adopta dos parámetros: la
dirección URL que se debe usar para iniciar el flujo del explorador web y el identificador URI al que se espera que
el flujo devuelva la llamada en última instancia y en la que la aplicación debe estar registrada para poder realizar el
control.
El resultado es un elemento WebAuthenticatorResult que incluye todos los parámetros de consulta analizados
desde el identificador URI de devolución de llamada:

var authResult = await WebAuthenticator.AuthenticateAsync(


new Uri("https://mysite.com/mobileauth/Microsoft"),
new Uri("myapp://"));

var accessToken = authResult?.AccessToken;

La API WebAuthenticator se encarga de iniciar la dirección URL en el explorador y esperar a que se reciba la
devolución de llamada:
Si el usuario cancela el flujo en cualquier momento, se devuelve una excepción TaskCanceledException .

Diferencias entre plataformas


Android
iOS
UWP
Las pestañas personalizadas se usan siempre que estén disponibles; de lo contrario, se inicia una intención para la
dirección URL.

Inicio de sesión de Apple


Según las directrices de revisión de Apple, si su aplicación usa cualquier servicio de inicio de sesión social para
autenticarse, también debe ofrecer el inicio de sesión de Apple como una opción.
Para agregar el inicio de sesión de Apple a sus aplicaciones, primero necesitará configurar la aplicación para que
use el inicio de sesión de Apple.
Para iOS 13 y versiones posteriores, deberá llamar al método AppleSignInAuthenticator.AuthenticateAsync() . De
este modo, se usará la API de inicio de sesión de Apple nativa de forma subyacente para que los usuarios
obtengan la mejor experiencia posible en estos dispositivos. Puede escribir el código compartido para usar la API
correcta en tiempo de ejecución de la siguiente manera:
var scheme = "..."; // Apple, Microsoft, Google, Facebook, etc.
WebAuthenticatorResult r = null;

if (scheme.Equals("Apple")
&& DeviceInfo.Platform == DevicePlatform.iOS
&& DeviceInfo.Version.Major >= 13)
{
// Use Native Apple Sign In API's
r = await AppleSignInAuthenticator.AuthenticateAsync();
}
else
{
// Web Authentication flow
var authUrl = new Uri(authenticationUrl + scheme);
var callbackUrl = new Uri("xamarinessentials://");

r = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl);


}

var authToken = string.Empty;


if (r.Properties.TryGetValue("name", out var name) && !string.IsNullOrEmpty(name))
authToken += $"Name: {name}{Environment.NewLine}";
if (r.Properties.TryGetValue("email", out var email) && !string.IsNullOrEmpty(email))
authToken += $"Email: {email}{Environment.NewLine}";

// Note that Apple Sign In has an IdToken and not an AccessToken


authToken += r?.AccessToken ?? r?.IdToken;

TIP
En el caso de dispositivos que no son iOS 13, se iniciará el flujo de autenticación web, que también se puede usar para
habilitar el inicio de sesión de Apple en los dispositivos Android y UWP. Puede iniciar sesión en su cuenta de iCloud en el
simulador de iOS para probar el inicio de sesión de Apple.

Back-end de servidor de ASP.NET Core


Es posible usar la API WebAuthenticator con cualquier servicio back-end web. Para usarla con una aplicación de
ASP.NET Core, primero debe configurar la aplicación web con los pasos siguientes:
1. Configure los proveedores de autenticación social externos que desee en una aplicación web de ASP.NET Core.
2. Establezca el esquema de autenticación predeterminado en CookieAuthenticationDefaults.AuthenticationScheme
en la llamada .AddAuthentication() .
3. Use .AddCookie() en la llamada .AddAuthentication() de Startup.cs.
4. Todos los proveedores deben configurarse con .SaveTokens = true; .

services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddFacebook(fb =>
{
fb.AppId = Configuration["FacebookAppId"];
fb.AppSecret = Configuration["FacebookAppSecret"];
fb.SaveTokens = true;
});
TIP
Si desea incluir el inicio de sesión de Apple, puede usar el paquete NuGet AspNet.Security.OAuth.Apple . Puede ver el
ejemplo de Startup.cs completo en el repositorio de GitHub de Essentials.

Agregar un controlador de autenticación móvil personalizado


Con un flujo de autenticación móvil, suele ser conveniente iniciar el flujo directamente en un proveedor que el
usuario ha elegido (por ejemplo, haciendo clic en un botón de "Microsoft" en la pantalla de inicio de sesión de la
aplicación). También es importante poder devolver información relevante a la aplicación en un identificador URI de
devolución de llamada específico para finalizar el flujo de autenticación.
Para ello, use un controlador de API personalizado:

[Route("mobileauth")]
[ApiController]
public class AuthController : ControllerBase
{
const string callbackScheme = "myapp";

[HttpGet("{scheme}")] // eg: Microsoft, Facebook, Apple, etc


public async Task Get([FromRoute]string scheme)
{
// 1. Initiate authentication flow with the scheme (provider)
// 2. When the provider calls back to this URL
// a. Parse out the result
// b. Build the app callback URL
// c. Redirect back to the app
}
}

El propósito de este controlador es deducir el esquema (proveedor) que la aplicación está solicitando e iniciar el
flujo de autenticación con el proveedor social. Cuando el proveedor devuelve la llamada al back-end web, el
controlador analiza el resultado y redirige al identificador URI de devolución de llamada de la aplicación con
parámetros.
En ocasiones, puede que desee devolver datos como el elemento access_token del proveedor a la aplicación, algo
que se puede hacer con los parámetros de consulta del identificador URI de devolución de llamada. O bien, puede
que desee crear su propia identidad en el servidor y pasar su propio token a la aplicación. En qué y hasta qué
punto puede intervenir el usuario.
Consulte el ejemplo del controlador completo en el repositorio de Essentials.

NOTE
En el ejemplo anterior, se muestra cómo devolver el token de acceso desde el proveedor de autenticación de terceros (es
decir: OAuth). Para obtener un token que pueda usar para autorizar solicitudes web al propio back-end web, debe crear su
propio token en la aplicación web y devolverlo en su lugar. En Información general sobre la autenticación de ASP.NET Core se
ofrece más información sobre los escenarios de autenticación avanzada en ASP.NET Core.

API
Código fuente de WebAuthenticator
Documentación de la API de WebAuthenticator
Ejemplo de servidor de ASP.NET Core
Xamarin.Essentials: Solución de problemas
18/12/2020 • 2 minutes to read • Edit Online

Error: Se detectó un conflicto de versión para


Xamarin.Android.Support.Compat
El error siguiente se puede producir al actualizar paquetes NuGet (o agregar un paquete nuevo) con un proyecto
de Xamarin.Forms que usa Xamarin.Essentials:

NU1107: Version conflict detected for Xamarin.Android.Support.Compat. Reference the package directly from the
project to resolve this issue.
MyApp -> Xamarin.Essentials 1.3.1 -> Xamarin.Android.Support.CustomTabs 28.0.0.3 ->
Xamarin.Android.Support.Compat (= 28.0.0.3)
MyApp -> Xamarin.Forms 3.1.0.583944 -> Xamarin.Android.Support.v4 25.4.0.2 -> Xamarin.Android.Support.Compat
(= 25.4.0.2).

El problema es que no coinciden las dependencias de los dos paquetes NuGet. Esto se puede resolver agregando
de forma manual una versión específica de la dependencia (en este caso Xamarin.Android.Suppor t.Compat )
que puede admitir los dos.
Para ello, agregue de forma manual el paquete NuGet que es el origen del conflicto y use la lista Versión para
seleccionar una versión específica. Actualmente, la versión 28.0.0.3 del paquete NuGet
Xamarin.Android.Support.Compat & Xamarin.Android.Support.Core.Util resolverá este error.
Consulte esta entrada de blog para obtener más información y ver un vídeo sobre cómo resolver el problema.
Si surge algún problema o encuentra un error, notifíquelo en el repositorio de GitHub de Xamarin.Essentials.
Xamarin.Formsalmacenamiento de datos local
18/12/2020 • 2 minutes to read • Edit Online

Archivos
El control de archivos con Xamarin.Forms se puede lograr mediante el uso de código en una biblioteca de .net
Standard o mediante el uso de recursos incrustados. En este artículo se explica cómo realizar el control de archivos
desde código compartido en una Xamarin.Forms aplicación.

Bases de datos locales


Xamarin.Formsadmite aplicaciones controladas por la base de datos que usan el motor de base de datos SQLite, lo
que permite cargar y guardar objetos en código compartido. En este artículo se describe cómo Xamarin.Forms las
aplicaciones pueden leer y escribir datos en una base de datos SQLite local mediante SQLite.net.
Control de archivos en :::no-loc(Xamarin.Forms):::
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
El control de archivos con :::no-loc(Xamarin.Forms)::: se puede lograr mediante el uso de código en una biblioteca
de .net Standard o mediante el uso de recursos incrustados.

Información general
:::no-loc(Xamarin.Forms)::: el código se ejecuta en varias plataformas, cada una de las cuales tiene su propio
sistema de archivos. Anteriormente, esto significaba que las acciones de leer y escribir en archivos se realizaban
con mayor facilidad mediante las API de archivo nativas de cada plataforma. Como alternativa, los recursos
incrustados son una solución más sencilla para distribuir archivos de datos con una aplicación. Pero, con .NET
Standard 2.0, se puede compartir el código de acceso a archivos en bibliotecas de .NET Standard.
Para obtener información sobre cómo controlar archivos de imagen, vea la página Trabajar con imágenes.

Guardar y cargar archivos


Las clases System.IO pueden usarse para acceder al sistema de archivos en cada plataforma. La clase File le
permite crear, eliminar y leer archivos, mientras que la clase Directory le permite crear, eliminar o enumerar el
contenido de directorios. También puede usar la subclases Stream , que pueden proporcionar un mayor nivel de
control sobre las operaciones de archivo (como la compresión o la búsqueda de ubicaciones en un archivo).
Un archivo de texto se puede escribir con el método File.WriteAllText :

File.WriteAllText(fileName, text);

Un archivo de texto se puede leer con el método File.ReadAllText :

string text = File.ReadAllText(fileName);

Además, el método File.Exists determina si existe el archivo especificado:

bool doesExist = File.Exists(fileName);

La ruta de acceso del archivo en cada plataforma se puede determinar a partir de una biblioteca .NET Standard
mediante el uso de un valor de la Environment.SpecialFolder enumeración como primer argumento del
Environment.GetFolderPath método. Después, esto puede combinarse con un nombre de archivo con el método
Path.Combine :

string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),


"temp.txt");

Estas operaciones se demuestran en la aplicación de ejemplo, donde se incluye una página que guarda y carga
texto:
Cargar archivos insertados como recursos
Para insertar un archivo en un ensamblado de .NET Standard , cree o agregue un archivo y asegúrese de usar
esta acción de compilación: EmbeddedResource .
Visual Studio
Visual Studio para Mac

GetManifestResourceStream se usa para acceder al archivo incrustado mediante su identificador de recurso . De


forma predeterminada, el identificador de recurso es el nombre de archivo con el prefijo del espacio de nombres
predeterminado para el proyecto en el que está incrustado; en este caso, el ensamblado es WorkingWithFiles y
el nombre de archivo es LibTextResource.txt , por lo que el identificador de recurso es
WorkingWithFiles.LibTextResource.txt .

var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;


Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.LibTextResource.txt");
string text = "";
using (var reader = new System.IO.StreamReader (stream))
{
text = reader.ReadToEnd ();
}

Después, la variable text puede usarse para mostrar el texto o, en cualquier caso, usarlo en el código. En esta
captura de pantalla de la aplicación de ejemplo, se muestra el texto representado en un control Label .

Cargar y deserializar código XML es igual de sencillo. En el código siguiente, se muestra un archivo XML que se
carga y se deserializa desde un recurso y, después, se enlaza a ListView para mostrarlo. El archivo XML contiene
una matriz de objetos Monkey (la clase se define en el código de ejemplo).

var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;


Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.LibXmlResource.xml");
List<Monkey> monkeys;
using (var reader = new System.IO.StreamReader (stream)) {
var serializer = new XmlSerializer(typeof(List<Monkey>));
monkeys = (List<Monkey>)serializer.Deserialize(reader);
}
var listView = new ListView ();
listView.ItemsSource = monkeys;

Insertar en proyectos compartidos


Los proyectos compartidos también pueden contener archivos como recursos incrustados; pero, como el
contenido de un proyecto compartido se compila en los proyectos a los que se hace referencia, el prefijo usado
para los identificadores de recursos del archivo incrustado puede cambiar. Esto quiere decir que el identificador de
recurso para cada archivo incrustado puede ser distinto para cada plataforma.
Hay disponibles dos soluciones a este problema con los proyectos compartidos:
Sincronizar los proyectos : edite las propiedades del proyecto para cada plataforma con el fin de usar el
mismo nombre de ensamblado y el mismo espacio de nombres predeterminado. Después, este valor se puede
“codificar de forma rígida” como el prefijo para los identificadores de recursos incrustados en el proyecto
compartido.
Directivas de compilador #if : use directivas de compilador para establecer el prefijo de identificador de
recurso correcto y use ese valor para crear de forma dinámica el identificador de recurso correcto.
A continuación, se muestra un ejemplo de código de la segunda opción. Las directivas de compilador se usan para
seleccionar el prefijo del recurso codificado de forma rígida (que suele ser el mismo que el espacio de nombres
predeterminado del proyecto al que se hace referencia). Después, la variable resourcePrefix se usa para crear un
identificador de recurso válido al concatenarlo con el nombre de archivo del recurso incrustado.

#if __IOS__
var resourcePrefix = "WorkingWithFiles.iOS.";
#endif
#if __ANDROID__
var resourcePrefix = "WorkingWithFiles.Droid.";
#endif

Debug.WriteLine("Using this resource prefix: " + resourcePrefix);


// note that the prefix includes the trailing period '.' that is required
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(SharedPage)).Assembly;
Stream stream = assembly.GetManifestResourceStream
(resourcePrefix + "SharedTextResource.txt");

Organización de recursos
En los ejemplos anteriores, se da por supuesto que el archivo está insertado en el directorio raíz del proyecto de la
biblioteca de .NET Standard, por lo que el identificador de recurso tendría el formato
EspacioDeNombres.NombreDeArchivo.Extensión , como WorkingWithFiles.LibTextResource.txt y
WorkingWithFiles.iOS.SharedTextResource.txt .

Los recursos incrustados se pueden organizar en carpetas. Cuando un recurso incrustado se coloca en una
carpeta, el nombre de carpeta se convierte en parte del identificador de recurso (separado por puntos), de forma
que el formato del identificador de recurso se convierte en
EspacioDeNombres.Carpeta.NombreDeArchivo.Extensión . Al colocar los archivos usados en la aplicación de
ejemplo en una carpeta MyFolder , los identificadores de recurso correspondientes serían
WorkingWithFiles.MyFolder.LibTextResource.txt y WorkingWithFiles.iOS.MyFolder.SharedTextResource.txt .

Depurar recursos incrustados


Como a veces es difícil comprender por qué no se carga un recurso específico, puede agregarse de forma
temporal el siguiente código de depuración a una aplicación para ayudar a confirmar que los recursos se han
configurado correctamente. En el panel Errores , se mostrarán todos los recursos incrustados conocidos en el
ensamblado específico para ayudar a depurar los problemas de carga de recursos.

using System.Reflection;
// ...
// use for debugging, not in released app code!
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(SharedPage)).Assembly;
foreach (var res in assembly.GetManifestResourceNames()) {
System.Diagnostics.Debug.WriteLine("found resource: " + res);
}

Resumen
En este artículo, se muestran algunas operaciones de archivo sencillas para guardar y cargar texto en el dispositivo
y para cargar recursos incrustados. Con .NET Standard 2.0, se puede compartir el código de acceso a archivos en
bibliotecas de .NET Standard.

Vínculos relacionados
FilesSample
Ejemplos de :::no-loc(Xamarin.Forms):::
Trabajar con el sistema de archivos en Xamarin.iOS
:::no-loc(Xamarin.Forms)::: Bases de datos locales
18/12/2020 • 13 minutes to read • Edit Online

Descargar el ejemplo
El motor de base de :::no-loc(Xamarin.Forms)::: datos SQLite permite a las aplicaciones cargar y guardar objetos de
datos en código compartido. La aplicación de ejemplo usa una tabla de base de datos de SQLite para almacenar
los elementos de la lista de tareas. En este artículo se describe cómo usar SQLite.Net en código compartido para
almacenar y recuperar información en una base de datos local.

Integre SQLite.NET en Mobile Apps siguiendo estos pasos:


1. Instale el paquete NuGet.
2. Configure constantes.
3. Cree una clase de acceso a base de datos.
4. Acceder a los :::no-loc(Xamarin.Forms)::: datos en .
5. Configuración avanzada.

Instalar el paquete NuGet de SQLite


Use el administrador de paquetes NuGet para buscar SQLite-net-PCL y agregue la versión más reciente al
proyecto de código compartido.
Hay varios paquetes NuGet con nombres similares. El paquete correcto tiene estos atributos:
Id.: sqlite-net-pcl
Autores: SQLite-net
Propietarios: praeclarum
Vínculo de NuGet: sqlite-net-pcl

NOTE
Independientemente del nombre del paquete, utilice el paquete de NuGet sqlite-net-pcl incluso en los proyectos de .NET
Standard.

Configurar constantes de aplicación


El proyecto de ejemplo incluye un archivo constants.CS que proporciona datos de configuración comunes:
public static class Constants
{
public const string DatabaseFilename = "TodoSQLite.db3";

public const SQLite.SQLiteOpenFlags Flags =


// open the database in read/write mode
SQLite.SQLiteOpenFlags.ReadWrite |
// create the database if it doesn't exist
SQLite.SQLiteOpenFlags.Create |
// enable multi-threaded database access
SQLite.SQLiteOpenFlags.SharedCache;

public static string DatabasePath


{
get
{
var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return Path.Combine(basePath, DatabaseFilename);
}
}
}

El archivo de constantes especifica SQLiteOpenFlag los valores de enumeración predeterminados que se usan para
inicializar la conexión de base de datos. La SQLiteOpenFlag enumeración admite estos valores:
Create : La conexión creará automáticamente el archivo de base de datos si no existe.
FullMutex : La conexión se abre en el modo de subproceso serializado.
NoMutex : La conexión se abre en modo de subprocesamiento múltiple.
PrivateCache : La conexión no participará en la memoria caché compartida, aunque esté habilitada.
ReadWrite : La conexión puede leer y escribir datos.
SharedCache : La conexión participará en la memoria caché compartida, si está habilitada.
ProtectionComplete : El archivo está cifrado y no se puede obtener acceso a él mientras el dispositivo está
bloqueado.
ProtectionCompleteUnlessOpen : El archivo se cifra hasta que se abre, pero es accesible aunque el usuario
bloquee el dispositivo.
ProtectionCompleteUntilFirstUserAuthentication : El archivo se cifra hasta que el usuario haya arrancado y
desbloqueado el dispositivo.
ProtectionNone : El archivo de base de datos no está cifrado.

Es posible que tenga que especificar marcas diferentes en función de cómo se utilizará la base de datos. Para
obtener más información acerca de SQLiteOpenFlags , consulte abrir una nueva conexión de base de datos en
SQLite.org.

Crear una clase de acceso a base de datos


Una clase contenedora de base de datos abstrae la capa de acceso a datos desde el resto de la aplicación. Esta
clase centraliza la lógica de consulta y simplifica la administración de la inicialización de la base de datos, lo que
facilita refactorizar o expandir las operaciones de datos a medida que crece la aplicación. La aplicación todo define
una TodoItemDatabase clase para este propósito.
Inicialización diferida
TodoItemDatabase Usa la clase .net Lazy para retrasar la inicialización de la base de datos hasta que se obtiene
acceso por primera vez. El uso de la inicialización diferida evita que el proceso de carga de la base de datos retrase
el inicio de la aplicación. Para obtener más información, vea < > clase T diferida.
public class TodoItemDatabase
{
static readonly Lazy<SQLiteAsyncConnection> lazyInitializer = new Lazy<SQLiteAsyncConnection>(() =>
{
return new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
});

static SQLiteAsyncConnection Database => lazyInitializer.Value;


static bool initialized = false;

public TodoItemDatabase()
{
InitializeAsync().SafeFireAndForget(false);
}

async Task InitializeAsync()


{
if (!initialized)
{
if (!Database.TableMappings.Any(m => m.MappedType.Name == typeof(TodoItem).Name))
{
await Database.CreateTablesAsync(CreateFlags.None, typeof(TodoItem)).ConfigureAwait(false);
}
initialized = true;
}
}

//...
}

La conexión de base de datos es un campo estático que garantiza que se utiliza una conexión de base de datos
única para la vida de la aplicación. El uso de una conexión estática persistente ofrece un mejor rendimiento que
abrir y cerrar conexiones varias veces durante una sesión de aplicación única.
El InitializeAsync método es responsable de comprobar si ya existe una tabla para almacenar TodoItem objetos.
Este método crea automáticamente la tabla si no existe.
El método de extensión SafeFireAndForget
Cuando TodoItemDatabase se crea una instancia de la clase, se debe inicializar la conexión de base de datos, que es
un proceso asincrónico. Pero:
Los constructores de clase no pueden ser asincrónicos.
Un método asincrónico que no está en espera no producirá excepciones.
El uso del Wait método bloquea el subproceso y reabsorbe las excepciones.

Con el fin de iniciar la inicialización asincrónica, evitar el bloqueo de la ejecución y tener la oportunidad de
detectar excepciones, la aplicación de ejemplo usa un método de extensión denominado SafeFireAndForget . El
SafeFireAndForget método de extensión proporciona funcionalidad adicional a la Task clase:
public static class TaskExtensions
{
// NOTE: Async void is intentional here. This provides a way
// to call an async method from the constructor while
// communicating intent to fire and forget, and allow
// handling of exceptions
public static async void SafeFireAndForget(this Task task,
bool returnToCallingContext,
Action<Exception> onException = null)
{
try
{
await task.ConfigureAwait(returnToCallingContext);
}

// if the provided action is not null, catch and


// pass the thrown exception
catch (Exception ex) when (onException != null)
{
onException(ex);
}
}
}

El SafeFireAndForget método espera la ejecución asincrónica del Task objeto proporcionado y permite asociar
un Action que se llama si se produce una excepción.
Para obtener más información, vea patrón asincrónico basado en tareas (TAP).
Métodos de manipulación de datos
La TodoItemDatabase clase incluye métodos para los cuatro tipos de manipulación de datos: crear, leer, editar y
eliminar. La biblioteca SQLite.NET proporciona un mapa relacional de objetos simple (ORM) que permite
almacenar y recuperar objetos sin escribir instrucciones SQL.
public class TodoItemDatabase {

// ...

public Task<List<TodoItem>> GetItemsAsync()


{
return Database.Table<TodoItem>().ToListAsync();
}

public Task<List<TodoItem>> GetItemsNotDoneAsync()


{
// SQL queries are also possible
return Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
}

public Task<TodoItem> GetItemAsync(int id)


{
return Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
}

public Task<int> SaveItemAsync(TodoItem item)


{
if (item.ID != 0)
{
return Database.UpdateAsync(item);
}
else
{
return Database.InsertAsync(item);
}
}

public Task<int> DeleteItemAsync(TodoItem item)


{
return Database.DeleteAsync(item);
}
}

Acceder a los datos en :::no-loc(Xamarin.Forms):::


La :::no-loc(Xamarin.Forms)::: App clase expone una instancia de la TodoItemDatabase clase:

static TodoItemDatabase database;


public static TodoItemDatabase Database
{
get
{
if (database == null)
{
database = new TodoItemDatabase();
}
return database;
}
}

Esta propiedad permite :::no-loc(Xamarin.Forms)::: a los componentes llamar a métodos de recuperación y


manipulación de datos en la Database instancia en respuesta a la interacción del usuario. Por ejemplo:
var saveButton = new Button { Text = "Save" };
saveButton.Clicked += async (sender, e) =>
{
var todoItem = (TodoItem)BindingContext;
await App.Database.SaveItemAsync(todoItem);
await Navigation.PopAsync();
};

Configuración avanzada
SQLite proporciona una sólida API con más características de las que se describen en este artículo y en la
aplicación de ejemplo. En las secciones siguientes se tratan las características que son importantes para la
escalabilidad.
Para obtener más información, consulte la documentación de SQLite en SQLite.org.
Registro de escritura anticipada
De forma predeterminada, SQLite usa un diario de reversión tradicional. Una copia del contenido de la base de
datos sin modificar se escribe en un archivo de reversión independiente y, a continuación, los cambios se escriben
directamente en el archivo de base de datos. La confirmación se produce cuando se elimina el diario de reversión.
El registro de Write-Ahead (WAL) escribe los cambios en un archivo de WAL independiente en primer lugar. En el
modo WAL, una confirmación es un registro especial que se anexa al archivo WAL, lo que permite que se
produzcan varias transacciones en un único archivo de WAL. Un archivo WAL se vuelve a combinar en el archivo
de base de datos en una operación especial denominada punto de control.
WAL puede ser más rápido para las bases de datos locales, ya que los lectores y los escritores no se bloquean
entre sí, lo que permite que las operaciones de lectura y escritura estén simultáneas. Sin embargo, el modo WAL
no permite cambios en el tamaño de página , agrega asociaciones de archivo adicionales a la base de datos y
agrega la operación de punto de comprobación adicional.
Para habilitar WAL en SQLite.NET, llame al EnableWriteAheadLoggingAsync método en la SQLiteAsyncConnection
instancia de:

await Database.EnableWriteAheadLoggingAsync();

Para obtener más información, consulte SQLite Write-Ahead Logging on SQLite.org.


Copiar una base de datos
Hay varios casos en los que puede ser necesario copiar una base de datos de SQLite:
Una base de datos se distribuye con la aplicación, pero debe copiarse o moverse al almacenamiento grabable
en el dispositivo móvil.
Debe realizar una copia de seguridad o una copia de la base de datos.
Necesita la versión, el movimiento o el cambio de nombre del archivo de base de datos.
En general, mover, cambiar el nombre o copiar un archivo de base de datos es el mismo proceso que cualquier
otro tipo de archivo con algunas consideraciones adicionales:
Todas las conexiones de base de datos deben cerrarse antes de intentar trasladar el archivo de base de datos.
Si usa el registro de escritura previa, SQLite creará un archivo de acceso a memoria compartida (. SHM) y un
archivo (. Wal). Asegúrese de aplicar también los cambios a estos archivos.
Para obtener más información, vea control de :::no-loc(Xamarin.Forms)::: archivos en .
Vínculos relacionados
Aplicación de ejemplo todo
Paquete NuGet de SQLite.NET
Documentación de SQLite
Uso de SQLite con Android
Uso de SQLite con iOS
Modelo asincrónico basado en tareas (TAP)
Lazy < T ( > clase)
:::no-loc(Xamarin.Forms)::: y servicios de Azure
18/12/2020 • 2 minutes to read • Edit Online

Usar una base de datos de documentos de Azure Cosmos DB en :::no-


loc(Xamarin.Forms):::
Una base de datos de documentos Azure Cosmos DB es una base de datos NoSQL que proporciona acceso de baja
latencia a los documentos JSON, que ofrece un servicio de base de datos escalable, rápido y de alta disponibilidad
para aplicaciones que requieren escalado sin problemas y replicación global. En este artículo se explica cómo usar
la biblioteca de cliente de .NET Standard de Azure Cosmos DB para integrar una base de datos de documentos
Azure Cosmos DB en una :::no-loc(Xamarin.Forms)::: aplicación.

Envíe y reciba notificaciones de envío con Azure Notification Hubs y


:::no-loc(Xamarin.Forms):::
Azure Notification Hubs le permite centralizar las notificaciones entre plataformas para que la aplicación back-end
pueda comunicarse con un solo concentrador. Azure Notification Hubs se encargará de distribuir las notificaciones
de inserción a varios proveedores de plataformas. En este artículo se explica cómo integrar Azure Notification Hubs
en una :::no-loc(Xamarin.Forms)::: aplicación.

Almacenar y obtener acceso a los datos de Azure Storage desde :::no-


loc(Xamarin.Forms):::
Azure Storage es una solución de almacenamiento en la nube escalable que se puede usar para almacenar datos
estructurados y no estructurados. En este artículo se muestra cómo usar :::no-loc(Xamarin.Forms)::: para almacenar
datos binarios y de texto en Azure Storage y cómo obtener acceso a los datos.

Buscar datos con Azure Search y :::no-loc(Xamarin.Forms):::


Azure Search es un servicio en la nube que proporciona capacidades de indexación y consulta para los datos
cargados. Esto elimina los requisitos de infraestructura y las complejidades del algoritmo de búsqueda
tradicionalmente asociadas a la implementación de la funcionalidad de búsqueda en una aplicación. En este artículo
se muestra cómo usar la biblioteca de búsqueda de Microsoft Azure para integrar Azure Search en una :::no-
loc(Xamarin.Forms)::: aplicación.

Azure Functions con :::no-loc(Xamarin.Forms):::


En este artículo se muestra cómo crear la primera función de Azure con la que interactúa :::no-loc(Xamarin.Forms):::
.
Usar una base de datos de documentos de Azure
Cosmos DB en :::no-loc(Xamarin.Forms):::
18/12/2020 • 16 minutes to read • Edit Online

Descargar el ejemplo
Una base de datos de documentos Azure Cosmos DB es una base de datos NoSQL que proporciona acceso de baja
latencia a los documentos JSON, que ofrece un servicio de base de datos escalable, rápido y de alta disponibilidad
para aplicaciones que requieren escalado sin problemas y replicación global. En este artículo se explica cómo usar
la biblioteca de cliente de .NET Standard de Azure Cosmos DB para integrar una base de datos de documentos
Azure Cosmos DB en una :::no-loc(Xamarin.Forms)::: aplicación.

Vídeo Microsoft Azure Cosmos DB


Se puede aprovisionar una cuenta de base de datos de documentos Azure Cosmos DB mediante una suscripción
de Azure. Cada cuenta de base de datos puede tener cero o más bases de datos. Una base de datos de documentos
en Azure Cosmos DB es un contenedor lógico para las colecciones de documentos y los usuarios.
Una base de datos de documentos Azure Cosmos DB puede contener cero o más colecciones de documentos. Cada
colección de documentos puede tener un nivel de rendimiento diferente, lo que permite especificar más
rendimiento para colecciones de acceso frecuente y menos capacidad de proceso para las colecciones a las que se
accede con poca frecuencia.
Cada colección de documentos consta de cero o más documentos JSON. Los documentos de una colección no
están disponibles para el esquema y, por tanto, no es necesario que compartan la misma estructura o campos. A
medida que se agregan documentos a una colección de documentos, Cosmos DB los indexa automáticamente y
están disponibles para ser consultados.
Para fines de desarrollo, una base de datos de documentos también se puede consumir a través de un emulador.
Con el emulador, las aplicaciones se pueden desarrollar y probar de forma local, sin necesidad de crear una
suscripción de Azure ni incurrir en ningún costo. Para obtener más información sobre el emulador, consulte
desarrollo local con el emulador de Azure Cosmos dB.
En este artículo y en la aplicación de ejemplo que lo acompaña, se muestra una aplicación de lista de tareas
pendientes en la que las tareas se almacenan en una base de datos de documentos Azure Cosmos DB. Para obtener
más información acerca de la aplicación de ejemplo, vea Descripción del ejemplo.
Para obtener más información acerca de Azure Cosmos DB, consulte la documentación de Azure Cosmos dB.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Configuración
El proceso de integración de una base de datos de documentos de Azure Cosmos DB en una :::no-
loc(Xamarin.Forms)::: aplicación de es el siguiente:
1. Cree una cuenta de Cosmos DB. Para obtener más información, consulte crear una cuenta de Azure Cosmos dB.
2. Agregue el paquete NuGet de la biblioteca de cliente de Azure Cosmos DB .net Standard a los proyectos de la
plataforma de la :::no-loc(Xamarin.Forms)::: solución.
3. Agregue using directivas para los Microsoft.Azure.Documents Microsoft.Azure.Documents.Client espacios de
nombres, y Microsoft.Azure.Documents.Linq a las clases que tendrán acceso a la cuenta de Cosmos dB.
Después de realizar estos pasos, la biblioteca de cliente de Azure Cosmos DB .NET Standard se puede usar para
configurar y ejecutar solicitudes en la base de datos de documentos.

NOTE
La biblioteca de cliente de Azure Cosmos DB .NET Standard solo se puede instalar en proyectos de plataforma y no en un
proyecto de biblioteca de clases portable (PCL). Por lo tanto, la aplicación de ejemplo es un proyecto de acceso compartido
(SAP) para evitar la duplicación de código. Sin embargo, DependencyService se puede utilizar la clase en un proyecto de
PCL para invocar Azure Cosmos DB .net Standard código de biblioteca de cliente contenido en proyectos específicos de la
plataforma.

Consumo de la cuenta de Azure Cosmos DB


El DocumentClient tipo encapsula el extremo, las credenciales y la Directiva de conexión utilizada para obtener
acceso a la cuenta de Azure Cosmos dB, y se usa para configurar y ejecutar solicitudes en la cuenta. En el ejemplo
de código siguiente se muestra cómo crear una instancia de esta clase:

DocumentClient client = new DocumentClient(new Uri(Constants.EndpointUri), Constants.PrimaryKey);

El URI de Cosmos DB y la clave principal deben proporcionarse al DocumentClient constructor. Estos se pueden
obtener desde Azure portal. Para obtener más información, consulte conexión a una cuenta de Azure Cosmos dB.
Crear una base de datos
Una base de datos de documentos es un contenedor lógico para las colecciones de documentos y los usuarios, y se
puede crear en el portal de Azure o mediante programación con el DocumentClient.CreateDatabaseIfNotExistsAsync
método:

public async Task CreateDatabase(string databaseName)


{
...
await client.CreateDatabaseIfNotExistsAsync(new Database
{
Id = databaseName
});
...
}

El CreateDatabaseIfNotExistsAsync método especifica un Database objeto como argumento, con el Database


objeto que especifica el nombre de la base de datos como su Id propiedad. El CreateDatabaseIfNotExistsAsync
método crea la base de datos si no existe o devuelve la base de datos si ya existe. Sin embargo, la aplicación de
ejemplo omite los datos devueltos por el CreateDatabaseIfNotExistsAsync método.

NOTE
El CreateDatabaseIfNotExistsAsync método devuelve un Task<ResourceResponse<Database>> objeto y se puede
comprobar el código de estado de la respuesta para determinar si se creó una base de datos o si se devolvió una base de
datos existente.

Crear una colección de documentos


Una colección de documentos es un contenedor de documentos JSON y se puede crear en el portal de Azure o
mediante programación con el DocumentClient.CreateDocumentCollectionIfNotExistsAsync método:

public async Task CreateDocumentCollection(string databaseName, string collectionName)


{
...
// Create collection with 400 RU/s
await client.CreateDocumentCollectionIfNotExistsAsync(
UriFactory.CreateDatabaseUri(databaseName),
new DocumentCollection
{
Id = collectionName
},
new RequestOptions
{
OfferThroughput = 400
});
...
}

El CreateDocumentCollectionIfNotExistsAsync método requiere dos argumentos obligatorios: un nombre de base de


datos especificado como Uri y un DocumentCollection objeto. El DocumentCollection objeto representa una
colección de documentos cuyo nombre se especifica con la Id propiedad. El
CreateDocumentCollectionIfNotExistsAsync método crea la colección de documentos si no existe o devuelve la
colección de documentos si ya existe. Sin embargo, la aplicación de ejemplo omite los datos devueltos por el
CreateDocumentCollectionIfNotExistsAsync método.

NOTE
El CreateDocumentCollectionIfNotExistsAsync método devuelve un Task<ResourceResponse<DocumentCollection>>
objeto y se puede comprobar el código de estado de la respuesta para determinar si se ha creado una colección de
documentos o si se ha devuelto una colección de documentos existente.

Opcionalmente, el CreateDocumentCollectionIfNotExistsAsync método también puede especificar un


RequestOptions objeto, que encapsula las opciones que se pueden especificar para las solicitudes emitidas a la
cuenta de Cosmos dB. La RequestOptions.OfferThroughput propiedad se usa para definir el nivel de rendimiento de
la colección de documentos y, en la aplicación de ejemplo, se establece en 400 unidades de solicitud por segundo.
Este valor debe aumentar o disminuir en función de si se accede a la colección con frecuencia o con poca
frecuencia.

IMPORTANT
Tenga en cuenta que el CreateDocumentCollectionIfNotExistsAsync método creará una nueva colección con un
rendimiento reservado, que tiene implicaciones de precios.

Recuperación de documentos de colección de documentos


El contenido de una colección de documentos se puede recuperar creando y ejecutando una consulta de
documento. Una consulta de documento se crea con el DocumentClient.CreateDocumentQuery método:
public async Task<List<TodoItem>> GetTodoItemsAsync()
{
...
var query = client.CreateDocumentQuery<TodoItem>(collectionLink)
.AsDocumentQuery();
while (query.HasMoreResults)
{
Items.AddRange(await query.ExecuteNextAsync<TodoItem>());
}
...
}

Esta consulta recupera de forma asincrónica todos los documentos de la colección especificada y coloca los
documentos en una List<TodoItem> colección para su presentación.
El CreateDocumentQuery<T> método especifica un Uri argumento que representa la colección que se debe
consultar para los documentos. En este ejemplo, la collectionLink variable es un campo de nivel de clase que
especifica el Uri que representa la colección de documentos de la que se van a recuperar documentos:

Uri collectionLink = UriFactory.CreateDocumentCollectionUri(Constants.DatabaseName, Constants.CollectionName);

El CreateDocumentQuery<T> método crea una consulta que se ejecuta sincrónicamente y devuelve un IQueryable<T>
objeto. Sin embargo, el AsDocumentQuery método convierte el IQueryable<T> objeto en un IDocumentQuery<T>
objeto que se puede ejecutar de forma asincrónica. La consulta asincrónica se ejecuta con el
IDocumentQuery<T>.ExecuteNextAsync método, que recupera la siguiente página de resultados de la base de datos de
documentos, con la IDocumentQuery<T>.HasMoreResults propiedad que indica si se van a devolver resultados
adicionales de la consulta.
Los documentos se pueden filtrar en el lado servidor incluyendo una Where cláusula en la consulta, que aplica un
predicado de filtrado a la consulta en la colección de documentos:

var query = client.CreateDocumentQuery<TodoItem>(collectionLink)


.Where(f => f.Done != true)
.AsDocumentQuery();

Esta consulta recupera todos los documentos de la colección cuya Done propiedad es igual a false .
Insertar un documento en una colección de documentos
Los documentos son contenido JSON definido por el usuario y se pueden insertar en una colección de
documentos con el DocumentClient.CreateDocumentAsync método:

public async Task SaveTodoItemAsync(TodoItem item, bool isNewItem = false)


{
...
await client.CreateDocumentAsync(collectionLink, item);
...
}

El CreateDocumentAsync método especifica un Uri argumento que representa la colección en la que se debe
insertar el documento y un object argumento que representa el documento que se va a insertar.
Reemplazar un documento en una colección de documentos
Los documentos se pueden reemplazar en una colección de documentos con el
DocumentClient.ReplaceDocumentAsync método:
public async Task SaveTodoItemAsync(TodoItem item, bool isNewItem = false)
{
...
await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(Constants.DatabaseName,
Constants.CollectionName, item.Id), item);
...
}

El ReplaceDocumentAsync método especifica un Uri argumento que representa el documento de la colección que
se debe reemplazar y un object argumento que representa los datos de documento actualizados.
Eliminar un documento de una colección de documentos
Un documento se puede eliminar de una colección de documentos con el DocumentClient.DeleteDocumentAsync
método:

public async Task DeleteTodoItemAsync(string id)


{
...
await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Constants.DatabaseName,
Constants.CollectionName, id));
...
}

El DeleteDocumentAsync método especifica un Uri argumento que representa el documento de la colección que se
debe eliminar.
Eliminar una colección de documentos
Una colección de documentos se puede eliminar de una base de datos con el
DocumentClient.DeleteDocumentCollectionAsync método:

await client.DeleteDocumentCollectionAsync(collectionLink);

El DeleteDocumentCollectionAsync método especifica un Uri argumento que representa la colección de


documentos que se va a eliminar. Tenga en cuenta que al invocar este método también se eliminarán los
documentos almacenados en la colección.
Eliminar una base de datos
Una base de datos se puede eliminar de una cuenta de base de datos de Cosmos DB con el
DocumentClient.DeleteDatabaesAsync método:

await client.DeleteDatabaseAsync(UriFactory.CreateDatabaseUri(Constants.DatabaseName));

El DeleteDatabaseAsync método especifica un Uri argumento que representa la base de datos que se va a
eliminar. Tenga en cuenta que al invocar este método también se eliminarán las colecciones de documentos
almacenadas en la base de datos y los documentos almacenados en las colecciones de documentos.

Resumen
En este artículo se explica cómo usar la biblioteca de cliente de .NET Standard de Azure Cosmos DB para integrar
una base de datos de documentos Azure Cosmos DB en una :::no-loc(Xamarin.Forms)::: aplicación. Una base de
datos de documentos Azure Cosmos DB es una base de datos NoSQL que proporciona acceso de baja latencia a
los documentos JSON, que ofrece un servicio de base de datos escalable, rápido y de alta disponibilidad para
aplicaciones que requieren escalado sin problemas y replicación global.
Vínculos relacionados
Azure Cosmos DB todo (ejemplo)
Documentación sobre Azure Cosmos DB
Biblioteca de cliente de Azure Cosmos DB .NET Standard
API de Azure Cosmos DB
Envío y recepción de notificaciones push con Azure
Notification Hubs y Xamarin.Forms
18/12/2020 • 28 minutes to read • Edit Online

Descargar el ejemplo
Las notificaciones push entregan información de un sistema back-end a una aplicación móvil. Apple, Google y otras
plataformas tienen su propio servicio de notificaciones push (PNS). Azure Notification Hubs permite centralizar las
notificaciones multiplataforma para que la aplicación de back-end pueda comunicarse con un solo centro de
conectividad, que se encarga de distribuir las notificaciones al PNS específico de cada plataforma.
Integre Azure Notification Hubs en las aplicaciones móviles con estos pasos:
1. Configure los servicios de notificaciones push y el centro de notificaciones de Azure.
2. Entienda como se usan las plantillas y las etiquetas.
3. Cree una aplicación de Xamarin.Forms multiplataforma.
4. Configure el proyecto nativo de Android para notificaciones push.
5. Configure el proyecto nativo de iOS para notificaciones push.
6. Pruebe notificaciones mediante el centro de notificaciones de Azure.
7. Cree una aplicación de back-end para enviar notificaciones.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Configuración de los servicios de notificaciones push y el centro de


notificaciones de Azure
La integración de Azure Notification Hubs con una aplicación móvil de Xamarin.Forms es similar a la integración de
Azure Notification Hubs con una aplicación nativa de Xamarin. Configure una aplicación de Firebase Cloud
Messaging (FCM) siguiendo los pasos de la consola de Firebase que se detallan en Envío de notificaciones push a
Xamarin.Android mediante Azure Notification Hubs. Realice los pasos siguientes con el tutorial de
Xamarin.Android:
1. Defina un nombre de paquete de Android como com.xamarin.notifysample , que se usa en el ejemplo.
2. Descargue google-services.json de la consola de Firebase. En un paso posterior se agrega este archivo a la
aplicación Android.
3. Cree una instancia del centro de notificaciones de Azure y asígnele un nombre. En este artículo y este ejemplo
se usa xdocsnotificationhub como nombre del centro de conectividad.
4. Copie la Clave de ser vidor de FCM y guárdela como Clave de API en Google (GCM/FCM) en el centro de
notificaciones de Azure.
En la captura de pantalla siguiente se muestra la configuración de la plataforma Google en el centro de
notificaciones de Azure:
Necesita un equipo macOS para finalizar la configuración de dispositivos iOS. Configure Apple Push Notification
Service (APN) siguiendo los pasos iniciales que se detallan en Envío de notificaciones push a Xamarin.iOS mediante
Azure Notification Hubs. Realice los pasos siguientes con el tutorial de Xamarin.iOS:
1. Defina un identificador de agrupación de iOS. En este artículo y este ejemplo se usa com.xamarin.notifysample
como identificador de agrupación.
2. Cree un archivo de solicitud de firma de certificado (CSR) y úselo para generar un certificado de notificación
push.
3. Cargue el certificado de notificación push en Apple (APNS) en el centro de notificaciones de Azure.
En la captura de pantalla siguiente se muestra la configuración de la plataforma Apple en el centro de
notificaciones de Azure:

Registro de plantillas y etiquetas en el centro de notificaciones de


Azure
El centro de notificaciones de Azure necesita que las aplicaciones móviles se registren en el centro de conectividad,
definan plantillas y se suscriban a etiquetas. El registro vincula un identificador de PNS específico de la plataforma
a un identificador del centro de notificaciones de Azure. Para obtener más información sobre los registros, vea
Administración de registros.
Las plantillas permiten a los dispositivos especificar plantillas de mensajes con parámetros. Los mensajes entrantes
se pueden personalizar por dispositivo y por etiqueta. Para obtener más información sobre las plantillas, vea
Plantillas.
Las etiquetas se pueden usar para suscribirse a categorías de mensajes, como noticias, deportes y el tiempo. Para
simplificar, la aplicación de ejemplo define una plantilla predeterminada con un solo parámetro denominado
messageParam y una sola etiqueta denominada default . En sistemas más complejos, se pueden usar etiquetas
específicas del usuario para enviar mensajes a un usuario entre dispositivos con notificaciones personalizadas. Para
obtener más información sobre las etiquetas, vea Expresiones de etiqueta y enrutamiento.
Para recibir mensajes correctamente, cada aplicación nativa debe realizar estos pasos:
1. Obtener un identificador de PNS o un token del PNS de la plataforma.
2. Registrar el identificador de PNS en el centro de notificaciones de Azure.
3. Especificar una plantilla que contenga los mismos parámetros que los mensajes salientes.
4. Suscribirse a la etiqueta de destino de los mensajes salientes.
Estos pasos se explican más detalladamente para cada plataforma en las secciones Configuración de la aplicación
Android para recibir notificaciones y Configuración de iOS para recibir notificaciones.

Funcionalidad de la aplicación de Xamarin.Forms


La aplicación de ejemplo de Xamarin.Forms muestra una lista de mensajes de notificación push. Esto se logra con
el método AddMessage , que agrega el mensaje de notificación push especificado a la interfaz de usuario. Además,
este método evita que se agreguen mensajes duplicados a la interfaz de usuario y se ejecuta en el subproceso
principal para que se pueda llamar desde cualquier subproceso. En el código siguiente se muestra el método
AddMessage :

public void AddMessage(string message)


{
Device.BeginInvokeOnMainThread(() =>
{
if (messageDisplay.Children.OfType<Label>().Where(c => c.Text == message).Any())
{
// Do nothing, an identical message already exists
}
else
{
Label label = new Label()
{
Text = message,
HorizontalOptions = LayoutOptions.CenterAndExpand,
VerticalOptions = LayoutOptions.Start
};
messageDisplay.Children.Add(label);
}
});
}

La aplicación de ejemplo contiene un archivo AppConstants.cs , que define las propiedades que usan los proyectos
de la plataforma. Este archivo debe personalizarse con los valores del centro de notificaciones de Azure. En el
código siguiente, se muestra el archivo AppConstants.cs :
public static class AppConstants
{
public static string NotificationChannelName { get; set; } = "XamarinNotifyChannel";
public static string NotificationHubName { get; set; } = "< Insert your Azure Notification Hub name >";
public static string ListenConnectionString { get; set; } = "< Insert your
DefaultListenSharedAccessSignature >";
public static string DebugTag { get; set; } = "XamarinNotify";
public static string[] SubscriptionTags { get; set; } = { "default" };
public static string FCMTemplateBody { get; set; } = "{\"data\":{\"message\":\"$(messageParam)\"}}";
public static string APNTemplateBody { get; set; } = "{\"aps\":{\"alert\":\"$(messageParam)\"}}";
}

Personalice los valores siguientes de AppConstants para conectar la aplicación de ejemplo al centro de
notificaciones de Azure:
NotificationHubName : use el nombre del centro de notificaciones de Azure que ha creado en Azure Portal.
ListenConnectionString : este valor se encuentra en el centro de notificaciones de Azure, en Directivas de
acceso .
La captura de pantalla siguiente muestra dónde se encuentran estos valores en Azure Portal:

Configuración de la aplicación Android para recibir notificaciones


Realice los pasos siguientes para configurar la aplicación Android a fin de recibir y procesar notificaciones:
1. Configure el Nombre del paquete de Android para que coincida con el nombre del paquete de la consola de
Firebase.
2. Instale los siguientes paquetes NuGet para interactuar con Google Play, Firebase y Azure Notification Hubs:
a. Xamarin.GooglePlayServices.Base
b. Xamarin.Firebase.Messaging
c. Xamarin.Azure.NotificationHubs.Android
3. Copie el archivo google-services.json que ha descargado durante la instalación de FCM en el proyecto y
establezca la acción de compilación en GoogleServicesJson .
4. Configure AndroidManifest.xml para comunicarse con Firebase.
5. Invalide FirebaseMessagingService para controlar los mensajes.
6. Agregue notificaciones entrantes a la interfaz de usuario de Xamarin.Forms.
La acción de compilación GoogleServicesJson forma parte del paquete NuGet Xamarin.GooglePlayServices.Base .
Visual Studio 2019 establece las acciones de compilación disponibles durante el inicio. Si no ve GoogleServicesJson
como acción de compilación, reinicie Visual Studio 2019 después de instalar los paquetes NuGet.
IMPORTANT
La entrega de notificaciones de inserción mientras la aplicación está en suspensión requiere el uso de AndroidX. Para obtener
información sobre cómo migrar a AndroidX, vea Migración de AndroidX en Xamarin.Forms.

Configuración del manifiesto de Android


Los elementos receiver del elemento application permiten a la aplicación comunicarse con Firebase. Los
elementos uses-permission permiten a la aplicación controlar los mensajes y registrarse en el centro de
notificaciones de Azure. El archivo completo AndroidManifest.xml debe tener un aspecto similar al del ejemplo
siguiente:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"


android:versionName="1.0" package="YOUR_PACKAGE_NAME" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<application android:label="Notification Hub Sample">
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
</manifest>

Invalidación de FirebaseMessagingService para controlar los mensajes


Para registrarse en Firebase y controlar los mensajes, cree una subclase a partir de la clase
FirebaseMessagingService . La aplicación de ejemplo define una clase FirebaseService que crea una subclase a
partir de FirebaseMessagingService . Esta clase se etiqueta con un atributo IntentFilter , que incluye el filtro
com.google.firebase.MESSAGING_EVENT . Este filtro permite que Android pase los mensajes entrantes a esta clase para
controlar:

[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseService : FirebaseMessagingService
{
// ...
}

Cuando se inicia la aplicación, el SDK de Firebase solicita automáticamente un identificador de token único del
servidor de Firebase. Tras una solicitud correcta, se llama al método OnNewToken en la clase FirebaseService . El
proyecto de ejemplo invalida este método y registra el token en Azure Notification Hubs:
public override void OnNewToken(string token)
{
// NOTE: save token instance locally, or log if desired

SendRegistrationToServer(token);
}

void SendRegistrationToServer(string token)


{
try
{
NotificationHub hub = new NotificationHub(AppConstants.NotificationHubName,
AppConstants.ListenConnectionString, this);

// register device with Azure Notification Hub using the token from FCM
Registration registration = hub.Register(token, AppConstants.SubscriptionTags);

// subscribe to the SubscriptionTags list with a simple template.


string pnsHandle = registration.PNSHandle;
TemplateRegistration templateReg = hub.RegisterTemplate(pnsHandle, "defaultTemplate",
AppConstants.FCMTemplateBody, AppConstants.SubscriptionTags);
}
catch (Exception e)
{
Log.Error(AppConstants.DebugTag, $"Error registering device: {e.Message}");
}
}

El método SendRegistrationToServer registra el dispositivo en el centro de notificaciones de Azure y se suscribe a


etiquetas con una plantilla. La aplicación de ejemplo define una sola etiqueta denominada default y una plantilla
con un solo parámetro denominado messageParam en el archivo AppConstants.cs . Para obtener más información
sobre el registro, las etiquetas y las plantillas, vea Registro de plantillas y etiquetas en el centro de notificaciones de
Azure.
Cuando se recibe un mensaje, se llama al método OnMessageReceived en la clase FirebaseService :
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
string messageBody = string.Empty;

if (message.GetNotification() != null)
{
messageBody = message.GetNotification().Body;
}

// NOTE: test messages sent via the Azure portal will be received here
else
{
messageBody = message.Data.Values.First();
}

// convert the incoming message to a local notification


SendLocalNotification(messageBody);

// send the incoming message directly to the MainPage


SendMessageToMainPage(messageBody);
}

void SendLocalNotification(string body)


{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
intent.PutExtra("message", body);

//Unique request code to avoid PendingIntent collision.


var requestCode = new Random().Next();
var pendingIntent = PendingIntent.GetActivity(this, requestCode, intent, PendingIntentFlags.OneShot);

var notificationBuilder = new NotificationCompat.Builder(this, AppConstants.NotificationChannelName)


.SetContentTitle("XamarinNotify Message")
.SetSmallIcon(Resource.Drawable.ic_launcher)
.SetContentText(body)
.SetAutoCancel(true)
.SetShowWhen(false)
.SetContentIntent(pendingIntent);

if (Build.VERSION.SdkInt >= BuildVersionCodes.O)


{
notificationBuilder.SetChannelId(AppConstants.NotificationChannelName);
}

var notificationManager = NotificationManager.FromContext(this);


notificationManager.Notify(0, notificationBuilder.Build());
}

void SendMessageToMainPage(string body)


{
(App.Current.MainPage as MainPage)?.AddMessage(body);
}

Los mensajes entrantes se convierten en una notificación local con el método SendLocalNotification . Este método
crea un nuevo elemento Intent y coloca el contenido del mensaje en el Intent como una string Extra .
Cuando el usuario pulsa la notificación local, tanto si la aplicación está en primer como en segundo plano, se inicia
MainActivity y tiene acceso al contenido del mensaje a través del objeto Intent .

La notificación local y el ejemplo Intent requieren que el usuario realice la acción de pulsar en la notificación. Esto
es deseable si el usuario debe realizar una acción antes de que cambie el estado de la aplicación. Aunque en
algunos casos puede que quiera acceder a los datos del mensaje sin necesidad de una acción del usuario. En el
ejemplo anterior también se envía el mensaje directamente a la instancia actual de MainPage con el método
SendMessageToMainPage . En producción, si implementa ambos métodos para un solo tipo de mensaje, el objeto
MainPage obtiene mensajes duplicados si el usuario pulsa la notificación.
Adición de notificaciones entrantes a la interfaz de usuario de Xamarin.Forms
La clase MainActivity necesita obtener permiso para controlar las notificaciones y administrar los datos de los
mensajes entrantes. En el siguiente código se muestra la implementación completa de MainActivity :

[Activity(Label = "NotificationHubSample", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher =


true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, LaunchMode =
LaunchMode.SingleTop)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;

base.OnCreate(savedInstanceState);

global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());

if (!IsPlayServiceAvailable())
{
throw new Exception("This device does not have Google Play Services and cannot receive push
notifications.");
}

CreateNotificationChannel();
}

protected override void OnNewIntent(Intent intent)


{
if (intent.Extras != null)
{
var message = intent.GetStringExtra("message");
(App.Current.MainPage as MainPage)?.AddMessage(message);
}

base.OnNewIntent(intent);
}

bool IsPlayServiceAvailable()
{
int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.Success)
{
if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
Log.Debug(AppConstants.DebugTag, GoogleApiAvailability.Instance.GetErrorString(resultCode));
else
{
Log.Debug(AppConstants.DebugTag, "This device is not supported");
}
return false;
}
return true;
}

void CreateNotificationChannel()
{
// Notification channels are new as of "Oreo".
// There is no need to create a notification channel on older versions of Android.
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelName = AppConstants.NotificationChannelName;
var channelDescription = String.Empty;
var channel = new NotificationChannel(channelName, channelName, NotificationImportance.Default)
var channel = new NotificationChannel(channelName, channelName, NotificationImportance.Default)
{
Description = channelDescription
};

var notificationManager = (NotificationManager)GetSystemService(NotificationService);


notificationManager.CreateNotificationChannel(channel);
}
}
}

El atributo Activity establece el LaunchMode de la aplicación en SingleTop . Este modo de inicio indica al sistema
operativo Android que solo permita una instancia de esta actividad. Con este modo de inicio, los datos entrantes de
Intent se enrutan al método OnNewIntent , que extrae los datos del mensaje y los envía a la instancia de MainPage
a través del método AddMessage . Si la aplicación usa otro modo de inicio, debe controlar los datos de Intent de
forma diferente.
El método OnCreate usa un método auxiliar denominado IsPlayServiceAvailable para asegurarse de que el
dispositivo admite el servicio Google Play. Los emuladores o dispositivos que no admiten el servicio Google Play
no pueden recibir notificaciones push de Firebase.

Configuración de iOS para recibir notificaciones


El proceso de configuración de la aplicación iOS para recibir notificaciones es el siguiente:
1. Configure el identificador de agrupación del archivo Info.plist para que coincida con el valor usado en el
perfil de aprovisionamiento.
2. Agregue la opción Habilitar notificaciones push al archivo Entitlements.plist .
3. Agregue el paquete NuGet Xamarin.Azure.NotificationHubs.iOS al proyecto.
4. Regístrese para recibir notificaciones en APNS.
5. Registre la aplicación en el centro de notificaciones de Azure y suscríbase a las etiquetas.
6. Agregue notificaciones de APNS a la interfaz de usuario de Xamarin.Forms.
En la siguiente captura de pantalla, se muestra la opción Habilitar notificaciones push seleccionada en el
archivo Entitlements.plist en Visual Studio:

Registro para recibir notificaciones en APNS


El método FinishedLaunching del archivo AppDelegate.cs se debe invalidar para registrarse para recibir
notificaciones remotas. El registro difiere en función de la versión de iOS que se use en el dispositivo. El proyecto
de iOS de la aplicación de ejemplo invalida el método FinishedLaunching para llamar a
RegisterForRemoteNotifications , como se muestra en el ejemplo siguiente:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());

base.FinishedLaunching(app, options);

RegisterForRemoteNotifications();

return true;
}

void RegisterForRemoteNotifications()
{
// register for remote notifications based on system version
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert |
UNAuthorizationOptions.Badge |
UNAuthorizationOptions.Sound,
(granted, error) =>
{
if (granted)
InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications);
});
}
else if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
new NSSet());

UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert |
UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
}

Registro en el centro de notificaciones de Azure y suscripción a etiquetas


Una vez que el dispositivo se ha registrado correctamente para recibir notificaciones remotas durante el método
FinishedLaunching , iOS llama al método RegisteredForRemoteNotifications . Este método debe invalidarse para
realizar las siguientes acciones:
1. Crear una instancia de SBNotificationHub .
2. Anular el registro de los registros existentes.
3. Registrar el dispositivo en el centro de notificaciones.
4. Suscribirse a etiquetas específicas con una plantilla.
Para obtener más información sobre el registro del dispositivo, las plantillas y las etiquetas, vea Registro de
plantillas y etiquetas en el centro de notificaciones de Azure. En el código siguiente se muestra el registro del
dispositivo y las plantillas:
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
Hub = new SBNotificationHub(AppConstants.ListenConnectionString, AppConstants.NotificationHubName);

// update registration with Azure Notification Hub


Hub.UnregisterAll(deviceToken, (error) =>
{
if (error != null)
{
Debug.WriteLine($"Unable to call unregister {error}");
return;
}

var tags = new NSSet(AppConstants.SubscriptionTags.ToArray());


Hub.RegisterNative(deviceToken, tags, (errorCallback) =>
{
if (errorCallback != null)
{
Debug.WriteLine($"RegisterNativeAsync error: {errorCallback}");
}
});

var templateExpiration =
DateTime.Now.AddDays(120).ToString(System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));
Hub.RegisterTemplate(deviceToken, "defaultTemplate", AppConstants.APNTemplateBody, templateExpiration,
tags, (errorCallback) =>
{
if (errorCallback != null)
{
Debug.WriteLine($"RegisterTemplateAsync error: {errorCallback}");
}
});
});
}

NOTE
Se puede producir un error al registrarse para recibir notificaciones remotas en situaciones como la ausencia de conexión de
red. Puede optar por invalidar el método FailedToRegisterForRemoteNotifications para controlar el error de registro.

Adición de notificaciones de APNS a la interfaz de usuario de Xamarin.Forms


Cuando un dispositivo recibe una notificación remota, iOS llama al método ReceivedRemoteNotification . El JSON
del mensaje entrante se convierte en un objeto NSDictionary y el método ProcessNotification extrae valores del
diccionario y los envía a la instancia de MainPage de Xamarin.Forms. El método ReceivedRemoteNotifications se
invalida para llamar a ProcessNotification , como se muestra en el siguiente código:
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
ProcessNotification(userInfo, false);
}

void ProcessNotification(NSDictionary options, bool fromFinishedLaunching)


{
// make sure we have a payload
if (options != null && options.ContainsKey(new NSString("aps")))
{
// get the APS dictionary and extract message payload. Message JSON will be converted
// into a NSDictionary so more complex payloads may require more processing
NSDictionary aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;
string payload = string.Empty;
NSString payloadKey = new NSString("alert");
if (aps.ContainsKey(payloadKey))
{
payload = aps[payloadKey].ToString();
}

if (!string.IsNullOrWhiteSpace(payload))
{
(App.Current.MainPage as MainPage)?.AddMessage(payload);
}

}
else
{
Debug.WriteLine($"Received request to process notification but there was no payload.");
}
}

Prueba de notificaciones en Azure Portal


Azure Notification Hubs permite comprobar que la aplicación puede recibir mensajes de prueba. La sección Envío
de prueba del centro de notificaciones permite seleccionar la plataforma de destino y enviar un mensaje. Al
establecer Envío a la expresión de etiqueta en default , se envían mensajes a las aplicaciones que han
registrado una plantilla para la etiqueta default . Al hacer clic en el botón Enviar , se genera un informe que
incluye el número de dispositivos a los que llega el mensaje. En la captura de pantalla siguiente se muestra una
prueba de notificación de Android en Azure Portal:
Sugerencias para las pruebas
1. Al probar que una aplicación puede recibir notificaciones push, debe usar un dispositivo físico. Los dispositivos
virtuales iOS y Android no se podrían configurar correctamente para recibir notificaciones push.
2. La aplicación de ejemplo de Android registra el token y las plantillas una vez que se emite el token de Firebase.
Durante las pruebas, es posible que tenga que solicitar un nuevo token y volver a registrarlo en el centro de
notificaciones de Azure. La mejor manera de forzar esto es limpiar el proyecto, eliminar las carpetas bin y obj
, y desinstalar la aplicación del dispositivo antes de volver a compilar e implementar.
3. Muchas partes del flujo de notificaciones push se ejecutan de forma asincrónica. Esto puede dar lugar a que no
se alcancen los puntos de interrupción o a que se alcancen en un orden inesperado. Use el registro del
dispositivo o de depuración para realizar un seguimiento de la ejecución sin interrumpir el flujo de la aplicación.
Filtre el registro del dispositivo Android mediante el elemento DebugTag especificado en Constants .
4. Cuando se detiene la depuración en Visual Studio, se fuerza a la aplicación a cerrarse. Los receptores de
mensajes u otros servicios iniciados como parte del proceso de depuración se cierran y no responden a los
eventos de mensaje.

Creación de un distribuidor de notificaciones


Azure Notification Hubs permite que la aplicación de back-end envíe notificaciones a dispositivos entre
plataformas. En el ejemplo, se muestra el envío de notificaciones con una aplicación de consola. La aplicación
incluye el archivo DispatcherConstants.cs , que define las siguientes propiedades:
public static class DispatcherConstants
{
public static string[] SubscriptionTags { get; set; } = { "default" };
public static string NotificationHubName { get; set; } = "< Insert your Azure Notification Hub name >";
public static string FullAccessConnectionString { get; set; } = "< Insert your
DefaultFullSharedAccessSignature >";
}

Debe configurar el archivo DispatcherConstants.cs para que coincida con la configuración del centro de
notificaciones de Azure. El valor de la propiedad SubscriptionTags debe coincidir con los valores usados en las
aplicaciones cliente. La propiedad NotificationHubName es el nombre de la instancia del centro de notificaciones de
Azure. La propiedad FullAccessConnectionString es la clave de acceso que se encuentra en las Directivas de
acceso del centro de notificaciones. En la captura de pantalla siguiente se muestra la ubicación de las propiedades
NotificationHubName y FullAccessConnectionString en Azure Portal:

La aplicación de consola recorre en bucle cada valor SubscriptionTags y envía notificaciones a los suscriptores
mediante una instancia de la clase NotificationHubClient . En el código siguiente se muestra la clase Program de la
aplicación de consola:
class Program
{
static int messageCount = 0;

static void Main(string[] args)


{
Console.WriteLine($"Press the spacebar to send a message to each tag in {string.Join(", ",
DispatcherConstants.SubscriptionTags)}");
WriteSeparator();
while (Console.ReadKey().Key == ConsoleKey.Spacebar)
{
SendTemplateNotificationsAsync().GetAwaiter().GetResult();
}
}

private static async Task SendTemplateNotificationsAsync()


{
NotificationHubClient hub =
NotificationHubClient.CreateClientFromConnectionString(DispatcherConstants.FullAccessConnectionString,
DispatcherConstants.NotificationHubName);
Dictionary<string, string> templateParameters = new Dictionary<string, string>();

messageCount++;

// Send a template notification to each tag. This will go to any devices that
// have subscribed to this tag with a template that includes "messageParam"
// as a parameter
foreach (var tag in DispatcherConstants.SubscriptionTags)
{
templateParameters["messageParam"] = $"Notification #{messageCount} to {tag} category
subscribers!";
try
{
await hub.SendTemplateNotificationAsync(templateParameters, tag);
Console.WriteLine($"Sent message to {tag} subscribers.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send template notification: {ex.Message}");
}
}

Console.WriteLine($"Sent messages to {DispatcherConstants.SubscriptionTags.Length} tags.");


WriteSeparator();
}

private static void WriteSeparator()


{
Console.WriteLine("==========================================================================");
}
}

Al ejecutar la aplicación de consola de ejemplo, se puede presionar la barra espaciadora para enviar mensajes. Los
dispositivos que ejecutan las aplicaciones cliente deben recibir notificaciones numeradas, siempre que estén
configuradas correctamente.

Vínculos relacionados
Plantillas de notificaciones push.
Administración de registros de dispositivos.
Expresiones de etiqueta y enrutamiento.
Tutorial de Azure Notification Hubs de Xamarin.Android.
Tutorial de Azure Notification Hubs de Xamarin.iOS.
Almacenar y obtener acceso a los datos de Azure
Storage desde :::no-loc(Xamarin.Forms):::
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
Azure Storage es una solución de almacenamiento en la nube escalable que se puede usar para almacenar datos
estructurados y no estructurados. En este artículo se muestra cómo usar :::no-loc(Xamarin.Forms)::: para almacenar
datos binarios y de texto en Azure Storage y cómo obtener acceso a los datos.
Azure Storage proporciona cuatro servicios de almacenamiento:
Blob Storage. Un BLOB puede ser texto o datos binarios, como copias de seguridad, máquinas virtuales, archivos
multimedia o documentos.
Table Storage es un almacén de atributos de clave NoSQL.
Queue Storage es un servicio de mensajería para el procesamiento de flujos de trabajo y la comunicación entre
servicios en la nube.
File Storage proporciona almacenamiento compartido mediante el protocolo SMB.
Existen dos tipos de cuentas de almacenamiento:
Las cuentas de almacenamiento de uso general proporcionan acceso a Azure Storage Services desde una sola
cuenta.
Una cuenta de almacenamiento de blobs es una cuenta de almacenamiento especializada para almacenar
BLOBs. Este tipo de cuenta se recomienda cuando solo necesita almacenar datos de blobs.
En este artículo y en la aplicación de ejemplo que lo acompaña, se muestra la carga de archivos de imagen y texto
en el almacenamiento de blobs y su descarga. Además, también se muestra cómo recuperar una lista de archivos
del almacenamiento de blobs y cómo eliminar archivos.
Para obtener más información sobre Azure Storage, consulte Introducción al almacenamiento.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Introducción a Blob Storage


El almacenamiento de blobs consta de tres componentes, que se muestran en el diagrama siguiente:
Todo acceso a Azure Storage se realiza a través de una cuenta de almacenamiento. Una cuenta de almacenamiento
puede contener un número ilimitado de contenedores y un contenedor puede almacenar un número ilimitado de
blobs, hasta el límite de capacidad de la cuenta de almacenamiento.
Un BLOB es un archivo de cualquier tipo y tamaño. Azure Storage admite tres tipos de BLOB diferentes:
Los blobs en bloques están optimizados para la transmisión por secuencias y el almacenamiento de objetos en
la nube, y son una buena opción para almacenar copias de seguridad, archivos multimedia, documentos, etc.
Los blobs en bloques pueden tener un tamaño máximo de 195 GB.
Los blobs en anexos son similares a los blobs en bloques, pero están optimizados para operaciones de anexión,
como el registro. Los blobs en anexos pueden tener un tamaño máximo de 195 GB.
Los blobs en páginas están optimizados para operaciones frecuentes de lectura y escritura y se suelen usar para
almacenar máquinas virtuales y sus discos. Los blobs en páginas pueden tener un tamaño máximo de 1 TB.

NOTE
Tenga en cuenta que las cuentas de almacenamiento de blobs admiten blobs en bloques y anexos, pero no blobs en páginas.

Un BLOB se carga en Azure Storage y se descarga de Azure Storage, como un flujo de bytes. Por lo tanto, los
archivos se deben convertir en una secuencia de bytes antes de la carga y volver a convertirlos a su representación
original después de la descarga.
Cada objeto almacenado en Azure Storage tiene una dirección URL única. El nombre de la cuenta de
almacenamiento forma el subdominio de esa dirección y la combinación de subdominio y nombre de dominio
forma un punto de conexión para la cuenta de almacenamiento. Por ejemplo, si la cuenta de almacenamiento se
denomina mystorageaccount , el punto de conexión de BLOB predeterminado de la cuenta de almacenamiento es
https://mystorageaccount.blob.core.windows.net .

La dirección URL para el acceso a un objeto en una cuenta de almacenamiento se crea anexando la ubicación del
objeto en la cuenta de almacenamiento al extremo. Por ejemplo, una dirección de BLOB tendrá el formato
https://mystorageaccount.blob.core.windows.net/mycontainer/myblob .

Configuración
El proceso para integrar una cuenta de Azure Storage en una :::no-loc(Xamarin.Forms)::: aplicación de es el
siguiente:
1. Cree una cuenta de almacenamiento. Para obtener más información, consulte Creación de una cuenta de
almacenamiento.
2. Agregue la biblioteca de cliente de Azure Storage a la :::no-loc(Xamarin.Forms)::: aplicación.
3. Configure la cadena de conexión de almacenamiento. Para obtener más información, consulte conexión a Azure
Storage.
4. Agregue using directivas para los Microsoft.WindowsAzure.Storage Microsoft.WindowsAzure.Storage.Blob
espacios de nombres y a las clases que van a tener acceso a Azure Storage.

Conexión a Azure Storage


Se deben autenticar todas las solicitudes realizadas en los recursos de la cuenta de almacenamiento. Aunque los
blobs se pueden configurar para admitir la autenticación anónima, hay dos enfoques principales que una
aplicación puede usar para autenticarse con una cuenta de almacenamiento:
Clave compartida. Este enfoque usa el nombre de cuenta de Azure Storage y la clave de cuenta para tener
acceso a los servicios de almacenamiento. A una cuenta de almacenamiento se le asignan dos claves privadas
en creación que se pueden usar para la autenticación de clave compartida.
Firma de acceso compartido. Se trata de un token que se puede anexar a una dirección URL que permite el
acceso delegado a un recurso de almacenamiento, con los permisos que especifica, durante el período de
tiempo que sea válido.
Se pueden especificar cadenas de conexión que incluyan la información de autenticación necesaria para obtener
acceso a Azure Storage recursos de una aplicación. Además, se puede configurar una cadena de conexión para
conectarse al emulador de almacenamiento de Azure desde Visual Studio.

NOTE
Azure Storage admite HTTP y HTTPS en una cadena de conexión. Sin embargo, se recomienda el uso de HTTPS.

Conexión al emulador de Azure Storage


El emulador de Azure Storage proporciona un entorno local que emula los servicios de blobs, colas y tablas de
Azure para fines de desarrollo.
La siguiente cadena de conexión debe usarse para conectarse al emulador de almacenamiento de Azure:

UseDevelopmentStorage=true

Para obtener más información sobre el emulador de Azure Storage, consulte uso del emulador de Azure Storage
para desarrollo y pruebas.
Conexión a Azure Storage mediante una clave compartida
El siguiente formato de cadena de conexión se debe usar para conectarse a Azure Storage con una clave
compartida:

DefaultEndpointsProtocol=[http|https];AccountName=myAccountName;AccountKey=myAccountKey

myAccountName debe reemplazarse por el nombre de la cuenta de almacenamiento y myAccountKey debe


reemplazarse por una de las dos claves de acceso de cuenta.

NOTE
Al usar la autenticación de clave compartida, el nombre de cuenta y la clave de cuenta se distribuirán a cada persona que use
la aplicación, lo que proporcionará acceso completo de lectura y escritura a la cuenta de almacenamiento. Por lo tanto, use la
autenticación de clave compartida solo con fines de prueba y no distribuya nunca claves a otros usuarios.
Conexión a Azure Storage mediante una firma de acceso compartido
El siguiente formato de cadena de conexión se debe usar para conectarse a Azure Storage con una SAS:
BlobEndpoint=myBlobEndpoint;SharedAccessSignature=mySharedAccessSignature

myBlobEndpoint debe reemplazarse por la dirección URL del punto de conexión del BLOB y
mySharedAccessSignature debe reemplazarse por la SAS. La SAS proporciona el protocolo, el punto de conexión de
servicio y las credenciales para tener acceso al recurso.

NOTE
La autenticación de SAS se recomienda para las aplicaciones de producción. Sin embargo, en una aplicación de producción, la
SAS se debe recuperar de un servicio back-end a petición, en lugar de agruparse con la aplicación.

Para obtener más información sobre las firmas de acceso compartido, consulte uso de firmas de acceso
compartido (SAS).

Creación de un contenedor
El GetContainer método se usa para recuperar una referencia a un contenedor con nombre, que se puede usar
para recuperar los blobs del contenedor o para agregar blobs al contenedor. El siguiente ejemplo de código
muestra el método GetContainer :

static CloudBlobContainer GetContainer(ContainerType containerType)


{
var account = CloudStorageAccount.Parse(Constants.StorageConnection);
var client = account.CreateCloudBlobClient();
return client.GetContainerReference(containerType.ToString().ToLower());
}

El CloudStorageAccount.Parse método analiza una cadena de conexión y devuelve una CloudStorageAccount


instancia de que representa la cuenta de almacenamiento. CloudBlobClient Después, el método crea una instancia
de, que se usa para recuperar contenedores y blobs CreateCloudBlobClient . El GetContainerReference método
recupera el contenedor especificado como una CloudBlobContainer instancia, antes de que se devuelva al método
de llamada. En este ejemplo, el nombre del contenedor es el ContainerType valor de enumeración, convertido en
una cadena en minúsculas.

NOTE
Los nombres de contenedor deben estar en minúsculas y deben empezar por una letra o un número. Además, solo pueden
contener letras, números y el carácter de guión, y deben tener entre 3 y 63 caracteres de longitud.

El GetContainer método se invoca de la siguiente manera:

var container = GetContainer(containerType);

La CloudBlobContainer instancia se puede usar para crear un contenedor si aún no existe:

await container.CreateIfNotExistsAsync();

De forma predeterminada, un contenedor recién creado es privado. Esto significa que se debe especificar una clave
de acceso de almacenamiento para recuperar los blobs del contenedor. Para obtener información acerca de cómo
crear blobs dentro de un contenedor, consulte crear un contenedor.

Cargar datos en un contenedor


El UploadFileAsync método se usa para cargar un flujo de datos de bytes en el almacenamiento de blobs y se
muestra en el ejemplo de código siguiente:

public static async Task<string> UploadFileAsync(ContainerType containerType, Stream stream)


{
var container = GetContainer(containerType);
await container.CreateIfNotExistsAsync();

var name = Guid.NewGuid().ToString();


var fileBlob = container.GetBlockBlobReference(name);
await fileBlob.UploadFromStreamAsync(stream);

return name;
}

Después de recuperar una referencia de contenedor, el método crea el contenedor si aún no existe. Guid A
continuación, se crea un nuevo para que actúe como un nombre de BLOB único y se recupera una referencia de
bloque de BLOB como una CloudBlockBlob instancia de. A continuación, la secuencia de datos se carga en el BLOB
mediante el UploadFromStreamAsync método, que crea el BLOB si todavía no existe o lo sobrescribe si existe.
Antes de que un archivo se pueda cargar en el almacenamiento de blobs mediante este método, primero se debe
convertir en una secuencia de bytes. Esto se muestra en el ejemplo de código siguiente:

var byteData = Encoding.UTF8.GetBytes(text);


uploadedFilename = await AzureStorage.UploadFileAsync(ContainerType.Text, new MemoryStream(byteData));

Los text datos se convierten en una matriz de bytes, que se ajusta a continuación como una secuencia que se
pasa al UploadFileAsync método.

Descargar datos de un contenedor


El GetFileAsync método se usa para descargar datos de BLOB de Azure Storage y se muestra en el ejemplo de
código siguiente:

public static async Task<byte[]> GetFileAsync(ContainerType containerType, string name)


{
var container = GetContainer(containerType);

var blob = container.GetBlobReference(name);


if (await blob.ExistsAsync())
{
await blob.FetchAttributesAsync();
byte[] blobBytes = new byte[blob.Properties.Length];

await blob.DownloadToByteArrayAsync(blobBytes, 0);


return blobBytes;
}
return null;
}

Después de recuperar una referencia de contenedor, el método recupera una referencia de BLOB para los datos
almacenados. Si existe el BLOB, el método recupera sus propiedades FetchAttributesAsync . Se crea una matriz de
bytes con el tamaño correcto y el BLOB se descarga como una matriz de bytes que se devuelve al método de
llamada.
Después de descargar los datos de bytes BLOB, se debe convertir a su representación original. Esto se muestra en
el ejemplo de código siguiente:

var byteData = await AzureStorage.GetFileAsync(ContainerType.Text, uploadedFilename);


string text = Encoding.UTF8.GetString(byteData);

La matriz de bytes se recupera de Azure Storage por el GetFileAsync método, antes de que se vuelva a convertir
en una cadena codificada UTF8.

Enumerar los datos de un contenedor


El GetFilesListAsync método se usa para recuperar una lista de los blobs almacenados en un contenedor y se
muestra en el ejemplo de código siguiente:

public static async Task<IList<string>> GetFilesListAsync(ContainerType containerType)


{
var container = GetContainer(containerType);

var allBlobsList = new List<string>();


BlobContinuationToken token = null;

do
{
var result = await container.ListBlobsSegmentedAsync(token);
if (result.Results.Count() > 0)
{
var blobs = result.Results.Cast<CloudBlockBlob>().Select(b => b.Name);
allBlobsList.AddRange(blobs);
}
token = result.ContinuationToken;
} while (token != null);

return allBlobsList;
}

Después de recuperar una referencia de contenedor, el método usa el método del contenedor
ListBlobsSegmentedAsync para recuperar las referencias a los blobs dentro del contenedor. Los resultados devueltos
por el ListBlobsSegmentedAsync método se enumeran mientras la BlobContinuationToken instancia no es null .
Cada BLOB se convierte desde el objeto devuelto IListBlobItem a un objeto para CloudBlockBlob tener acceso a la
Name propiedad del BLOB, antes de que se agregue el valor a la allBlobsList colección. Una vez que la
BlobContinuationToken instancia es null , se devuelve el último nombre del BLOB y la ejecución sale del bucle.

Eliminar datos de un contenedor


El DeleteFileAsync método se usa para eliminar un BLOB de un contenedor y se muestra en el ejemplo de código
siguiente:

public static async Task<bool> DeleteFileAsync(ContainerType containerType, string name)


{
var container = GetContainer(containerType);
var blob = container.GetBlobReference(name);
return await blob.DeleteIfExistsAsync();
}

Después de recuperar una referencia de contenedor, el método recupera una referencia de BLOB para el BLOB
especificado. Después, el BLOB se elimina con el DeleteIfExistsAsync método.

Vínculos relacionados
Azure Storage (ejemplo)
Introducción al almacenamiento
Uso de Blob Storage desde Xamarin
Uso de firmas de acceso compartido (SAS)
Azure Storage de Windows (NuGet)
Buscar datos con Azure Search y :::no-
loc(Xamarin.Forms):::
18/12/2020 • 21 minutes to read • Edit Online

Descargar el ejemplo
Azure Search es un servicio en la nube que proporciona capacidades de indexación y consulta para los datos
cargados. Esto elimina los requisitos de infraestructura y las complejidades del algoritmo de búsqueda
tradicionalmente asociadas a la implementación de la funcionalidad de búsqueda en una aplicación. En este
artículo se muestra cómo usar la biblioteca de búsqueda de Microsoft Azure para integrar Azure Search en una
:::no-loc(Xamarin.Forms)::: aplicación.

Información general
Los datos se almacenan en Azure Search como índices y documentos. Un Índice es un almacén de datos que el
servicio de Azure Search puede buscar y es conceptualmente similar a una tabla de base de datos. Un documento
es una unidad única de datos en los que se pueden realizar búsquedas en un índice y es conceptualmente similar a
una fila de base de datos. Al cargar documentos y enviar consultas de búsqueda a Azure Search, se realizan
solicitudes a un índice específico en el servicio de búsqueda.
Cada solicitud realizada a Azure Search debe incluir el nombre del servicio y una clave de API. Hay dos tipos de
clave de API:
Las claves de administración conceden derechos completos a todas las operaciones. Esto incluye administrar el
servicio, crear y eliminar índices y orígenes de datos.
Las claves de consulta conceden acceso de solo lectura a los índices y documentos y deben ser usadas por las
aplicaciones que emiten solicitudes de búsqueda.
La solicitud más común para Azure Search es ejecutar una consulta. Hay dos tipos de consultas que se pueden
enviar:
Una consulta de búsqueda busca uno o más elementos en todos los campos de búsqueda de un índice. Las
consultas de búsqueda se crean mediante la sintaxis simplificada o la sintaxis de consulta de Lucene. Para
obtener más información, vea Sintaxis de consulta simple en Azure Searchy Sintaxis de consulta de Lucene en
Azure Search.
Una consulta de filtro evalúa una expresión booleana sobre todos los campos filtrables de un índice. Las
consultas de filtro se crean mediante un subconjunto del lenguaje de filtro de OData. Para obtener más
información, vea Sintaxis de expresiones de oData para Azure Search.
Las consultas de búsqueda y las consultas de filtro se pueden usar por separado o juntas. Cuando se usan
conjuntamente, la consulta de filtro se aplica primero a todo el índice y, a continuación, se realiza la consulta de
búsqueda en los resultados de la consulta de filtro.
Azure Search también admite la recuperación de sugerencias basadas en la entrada de búsqueda. Para obtener
más información, vea consultas de sugerencias.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.
Configuración
El proceso para integrar Azure Search en una :::no-loc(Xamarin.Forms)::: aplicación es el siguiente:
1. Cree un servicio de Azure Search. Para obtener más información, consulte creación de un servicio de Azure
Search mediante Azure portal.
2. Quite Silverlight como plataforma de destino de la :::no-loc(Xamarin.Forms)::: biblioteca de clases portable (PCL)
de la solución. Esto se puede lograr cambiando el perfil de PCL a cualquier perfil que admita el desarrollo
multiplataforma, pero no es compatible con Silverlight, como el perfil 151 o el perfil 92.
3. Agregue el paquete NuGet de la biblioteca de búsqueda Microsoft Azure al proyecto PCL de la :::no-
loc(Xamarin.Forms)::: solución.
Después de seguir estos pasos, la API de la biblioteca de Microsoft Search se puede usar para administrar índices
de búsqueda y orígenes de datos, cargar y administrar documentos y ejecutar consultas.

Creación de un índice de Azure Search


Se debe definir un esquema de índice que se asigne a la estructura de los datos que se van a buscar. Esto puede
realizarse en Azure portal o mediante programación con la SearchServiceClient clase. Esta clase administra las
conexiones a Azure Search y se puede usar para crear un índice. En el ejemplo de código siguiente se muestra
cómo crear una instancia de esta clase:

var searchClient =
new SearchServiceClient(Constants.SearchServiceName, new SearchCredentials(Constants.AdminApiKey));

La SearchServiceClient sobrecarga del constructor toma un nombre de servicio de búsqueda y un


SearchCredentials objeto como argumentos, con el objeto que contiene SearchCredentials la clave de
administración para el servicio Azure Search. La clave de administración es necesaria para crear un índice.

NOTE
SearchServiceClient Se debe usar una sola instancia en una aplicación para evitar que se abran demasiadas conexiones a
Azure Search.

El objeto define un índice Index , como se muestra en el ejemplo de código siguiente:


static void CreateSearchIndex()
{
var index = new Index()
{
Name = Constants.Index,
Fields = new[]
{
new Field("id", DataType.String) { IsKey = true, IsRetrievable = true },
new Field("name", DataType.String) { IsRetrievable = true, IsFilterable = true, IsSortable = true,
IsSearchable = true },
new Field("location", DataType.String) { IsRetrievable = true, IsFilterable = true, IsSortable = true,
IsSearchable = true },
new Field("details", DataType.String) { IsRetrievable = true, IsFilterable = true, IsSearchable = true
},
new Field("imageUrl", DataType.String) { IsRetrievable = true }
},
Suggesters = new[]
{
new Suggester("nameSuggester", SuggesterSearchMode.AnalyzingInfixMatching, new[] { "name" })
}
};

searchClient.Indexes.Create(index);
}

La Index.Name propiedad se debe establecer en el nombre del índice y la Index.Fields propiedad debe
establecerse en una matriz de Field objetos. Cada Field instancia especifica un nombre, un tipo y cualquier
propiedad, que especifica cómo se usa el campo. Estas propiedades incluyen:
IsKey : indica si el campo es la clave del índice. Solo un campo del índice, de tipo DataType.String , debe
designarse como el campo de clave.
IsFacetable : indica si es posible realizar una navegación por facetas en este campo. El valor predeterminado
es false .
IsFilterable : indica si el campo se puede usar en consultas de filtro. El valor predeterminado es false .
IsRetrievable : indica si el campo se puede recuperar en los resultados de la búsqueda. El valor
predeterminado es true .
IsSearchable : indica si el campo está incluido en las búsquedas de texto completo. El valor predeterminado es
false .
IsSortable : indica si el campo se puede usar en OrderBy expresiones. El valor predeterminado es false .

NOTE
Cambiar un índice después de su implementación implica volver a generar y volver a cargar los datos.

Un Index objeto puede especificar opcionalmente una Suggesters propiedad, que define los campos en el índice
que se van a utilizar para admitir las consultas de autocompletar o buscar sugerencias. La Suggesters propiedad
se debe establecer en una matriz de Suggester objetos que definan los campos que se usan para generar los
resultados de la sugerencia de búsqueda.
Después de crear el Index objeto, el índice se crea llamando a Indexes.Create en la SearchServiceClient
instancia de.

NOTE
Al crear un índice a partir de una aplicación que debe mantener la capacidad de respuesta, use el Indexes.CreateAsync
método.
Para obtener más información, vea creación de un índice de Azure Search mediante el SDK de .net.

Eliminar el índice de Azure Search


Un índice se puede eliminar llamando a Indexes.Delete en la SearchServiceClient instancia de:

searchClient.Indexes.Delete(Constants.Index);

Cargar datos en el índice de Azure Search


Después de definir el índice, los datos se pueden cargar en él mediante uno de estos dos modelos:
Modelo de extracción : los datos se ingestan periódicamente de Azure Cosmos DB, Azure SQL Database,
Azure Blob Storage o SQL Server hospedan en una máquina virtual de Azure.
Modelo de envío : los datos se envían mediante programación al índice. Este es el modelo adoptado en este
artículo.
SearchIndexClient Se debe crear una instancia de para importar los datos en el índice. Esto puede realizarse
llamando al SearchServiceClient.Indexes.GetClient método, como se muestra en el ejemplo de código siguiente:

static void UploadDataToSearchIndex()


{
var indexClient = searchClient.Indexes.GetClient(Constants.Index);

var monkeyList = MonkeyData.Monkeys.Select(m => new


{
id = Guid.NewGuid().ToString(),
name = m.Name,
location = m.Location,
details = m.Details,
imageUrl = m.ImageUrl
});

var batch = IndexBatch.New(monkeyList.Select(IndexAction.Upload));


try
{
indexClient.Documents.Index(batch);
}
catch (IndexBatchException ex)
{
// Sometimes when the Search service is under load, indexing will fail for some
// documents in the batch. Compensating actions like delaying and retrying should be taken.
// Here, the failed document keys are logged.
Console.WriteLine("Failed to index some documents: {0}",
string.Join(", ", ex.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key)));
}
}

Los datos que se van a importar en el índice se empaquetan como un IndexBatch objeto, que encapsula una
colección de IndexAction objetos. Cada IndexAction instancia contiene un documento y una propiedad que indica
Azure Search qué acción se debe realizar en el documento. En el ejemplo de código anterior, IndexAction.Upload se
especifica la acción, lo que hace que el documento se inserte en el índice si es nuevo o se reemplaza si ya existe.
IndexBatch A continuación, el objeto se envía al índice llamando al Documents.Index método en el
SearchIndexClient objeto. Para obtener información sobre otras acciones de indexación, consulte decidir qué
acción de indexación se va a usar.
NOTE
Solo se pueden incluir 1000 documentos en una única solicitud de indexación.

Tenga en cuenta que en el ejemplo de código anterior, la monkeyList colección se crea como un objeto anónimo a
partir de una colección de Monkey objetos. Esto crea datos para el id campo y resuelve la asignación de nombres
de propiedades de mayúsculas y minúsculas Pascal Monkey a nombres de campos de índice de búsqueda de
mayúsculas y minúsculas. También se puede realizar esta asignación agregando el
[SerializePropertyNamesAsCamelCase] atributo a la Monkey clase.

Para obtener más información, consulte carga de datos en Azure Search mediante el SDK de .net.

Consultar el índice de Azure Search


SearchIndexClient Se debe crear una instancia de para consultar un índice. Cuando una aplicación ejecuta
consultas, es aconsejable seguir el principio de privilegios mínimos y crear un SearchIndexClient directamente,
pasando la clave de consulta como argumento. Esto garantiza que los usuarios tengan acceso de solo lectura a los
índices y documentos. Este enfoque se muestra en el ejemplo de código siguiente:

SearchIndexClient indexClient =
new SearchIndexClient(Constants.SearchServiceName, Constants.Index, new
SearchCredentials(Constants.QueryApiKey));

La SearchIndexClient sobrecarga del constructor toma como argumentos un nombre de servicio de búsqueda, un
nombre de índice y un SearchCredentials objeto, con el objeto que contiene la SearchCredentials clave de
consulta para el servicio de Azure Search.
Consultas de búsqueda
El índice se puede consultar llamando al Documents.SearchAsync método en la SearchIndexClient instancia, como
se muestra en el ejemplo de código siguiente:

async Task AzureSearch(string text)


{
Monkeys.Clear();

var searchResults = await indexClient.Documents.SearchAsync<Monkey>(text);


foreach (SearchResult<Monkey> result in searchResults.Results)
{
Monkeys.Add(new Monkey
{
Name = result.Document.Name,
Location = result.Document.Location,
Details = result.Document.Details,
ImageUrl = result.Document.ImageUrl
});
}
}

El SearchAsync método toma un argumento de texto de búsqueda y un SearchParameters objeto opcional que se
puede usar para refinar aún más la consulta. Una consulta de búsqueda se especifica como el argumento de texto
de búsqueda, mientras que se puede especificar una consulta de filtro estableciendo la Filter propiedad del
SearchParameters argumento. En el ejemplo de código siguiente se muestran ambos tipos de consulta:
var parameters = new SearchParameters
{
Filter = "location ne 'China' and location ne 'Vietnam'"
};
var searchResults = await indexClient.Documents.SearchAsync<Monkey>(text, parameters);

Esta consulta de filtro se aplica a todo el índice y quita los documentos de los resultados en los que el location
campo no es igual a China y no es igual a Vietnam. Después del filtrado, la consulta de búsqueda se realiza en los
resultados de la consulta de filtro.

NOTE
Para filtrar sin buscar, pase * como el argumento de texto de búsqueda.

El SearchAsync método devuelve un DocumentSearchResult objeto que contiene los resultados de la consulta. Este
objeto se enumera, con cada Document objeto que se crea como un Monkey objeto y se agrega a Monkeys
ObservableCollection para su presentación. Las capturas de pantallas siguientes muestran los resultados de las
consultas de búsqueda devueltos desde Azure Search:

Para obtener más información sobre la búsqueda y el filtrado, consulte consultar el índice de Azure Search
mediante el SDK de .net.
Consultas de sugerencias
Azure Search permite solicitar sugerencias en función de una consulta de búsqueda, llamando al
Documents.SuggestAsync método en la SearchIndexClient instancia. Esto se muestra en el ejemplo de código
siguiente:
async Task AzureSuggestions(string text)
{
Suggestions.Clear();

var parameters = new SuggestParameters()


{
UseFuzzyMatching = true,
HighlightPreTag = "[",
HighlightPostTag = "]",
MinimumCoverage = 100,
Top = 10
};

var suggestionResults =
await indexClient.Documents.SuggestAsync<Monkey>(text, "nameSuggester", parameters);

foreach (var result in suggestionResults.Results)


{
Suggestions.Add(new Monkey
{
Name = result.Text,
Location = result.Document.Location,
Details = result.Document.Details,
ImageUrl = result.Document.ImageUrl
});
}
}

El SuggestAsync método toma un argumento de texto de búsqueda, el nombre del que se va a usar (que se define
en el índice) y un SuggestParameters objeto opcional que se puede usar para refinar aún más la consulta. La
SuggestParameters instancia de establece las siguientes propiedades:

UseFuzzyMatching : cuando se establece en true , Azure Search encontrará sugerencias incluso si hay un
carácter sustituido o que falta en el texto de búsqueda.
HighlightPreTag : la etiqueta que se antepone a los aciertos de sugerencias.
HighlightPostTag : la etiqueta que se anexa a los aciertos de sugerencias.
MinimumCoverage : representa el porcentaje del índice que debe estar cubierta por una consulta de sugerencia
para que se notifique un éxito en la consulta. El valor predeterminado es 80.
Top : número de sugerencias que se van a recuperar. Debe ser un entero comprendido entre 1 y 100, con un
valor predeterminado de 5.
El efecto general es que los 10 resultados principales del índice se devolverán con el resaltado de referencias, y los
resultados incluirán documentos que incluyan términos de búsqueda escritos de forma similar.
El SuggestAsync método devuelve un DocumentSuggestResult objeto que contiene los resultados de la consulta.
Este objeto se enumera, con cada Document objeto que se crea como un Monkey objeto y se agrega a Monkeys
ObservableCollection para su presentación. Las capturas de pantallas siguientes muestran los resultados de
sugerencias devueltos por Azure Search:
Tenga en cuenta que en la aplicación de ejemplo, el SuggestAsync método solo se invoca cuando el usuario finaliza
la entrada de un término de búsqueda. Sin embargo, también se puede usar para admitir consultas de búsqueda
Autocompletar mediante la ejecución de en cada KeyPress.

Resumen
En este artículo se muestra cómo usar la biblioteca de búsqueda de Microsoft Azure para integrar Azure Search en
una :::no-loc(Xamarin.Forms)::: aplicación. Azure Search es un servicio en la nube que proporciona capacidades de
indexación y consulta para los datos cargados. Esto elimina los requisitos de infraestructura y las complejidades del
algoritmo de búsqueda tradicionalmente asociadas a la implementación de la funcionalidad de búsqueda en una
aplicación.

Vínculos relacionados
Azure Search (ejemplo)
Azure Search documentación
Microsoft Azure biblioteca de búsqueda
Introducción a Azure Functions
18/12/2020 • 2 minutes to read • Edit Online

Descargar el ejemplo
Comience a crear su primera función de Azure que interactúe con Xamarin.Forms .
Visual Studio 2019
Visual Studio 2017
Visual Studio para Mac

Instrucciones paso a paso


Además del vídeo, puede seguir estas instrucciones para compilar la primera función mediante Visual Studio.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Vínculos relacionados
Azure Functions docs
Implementación de una función de Azure simple con un Xamarin.Forms cliente (ejemplo)
Xamarin.Formsy Azure Cognitive Services
18/12/2020 • 2 minutes to read • Edit Online

Introducción
Microsoft Cognitive Services son un conjunto de API, SDK y servicios disponibles para que los desarrolladores
puedan hacer que sus aplicaciones sean más inteligentes mediante la adición de características como el
reconocimiento facial, el reconocimiento de voz y la comprensión de lenguajes. En este artículo se proporciona una
introducción a la aplicación de ejemplo que muestra cómo invocar algunas de las API de servicio cognitiva de
Microsoft desde Xamarin.Forms aplicaciones.

Reconocimiento de voz
El servicio de voz de Azure es una API basada en la nube que proporciona algoritmos para procesar el lenguaje
hablado. En este artículo se explica cómo usar el servicio de voz de Azure para transcribir voz a texto en una
Xamarin.Forms aplicación.

Corrector ortográfico
Bing Spell Check realiza la revisión ortográfica contextual del texto, proporcionando sugerencias en línea para
palabras mal escritas. En este artículo se explica cómo usar la API de REST de Bing Spell Check para corregir errores
ortográficos en una Xamarin.Forms aplicación.

Traducción del texto


La API de traductor de Microsoft se puede usar para traducir la voz y el texto a través de una API de REST. En este
artículo se explica cómo usar Microsoft Translator Text API para traducir texto de un idioma a otro en una
Xamarin.Forms aplicación.

Reconocimiento de emociones percibidas


El Face API toma una expresión facial en una imagen como entrada y devuelve datos que incluyen niveles de
confianza en un conjunto de emociones para cada una de las caras de la imagen. En este artículo se explica cómo
usar el Face API para reconocer emociones, para clasificar una Xamarin.Forms aplicación.
:::no-loc(Xamarin.Forms)::: y Azure Cognitive Services
introducción
18/12/2020 • 11 minutes to read • Edit Online

Descargar el ejemplo
Microsoft Cognitive Services son un conjunto de API, SDK y servicios disponibles para que los desarrolladores
puedan hacer que sus aplicaciones sean más inteligentes mediante la adición de características como el
reconocimiento facial, el reconocimiento de voz y la comprensión de lenguajes. En este artículo se proporciona una
introducción a la aplicación de ejemplo que muestra cómo invocar algunas de las API de los servicios cognitivos de
Microsoft.

Información general
El ejemplo adjunto es una aplicación de lista de tareas que proporciona funcionalidad para:
Ver una lista de tareas.
Agregue y edite tareas a través del teclado en pantalla o mediante el reconocimiento de voz con Microsoft
Speech API.
Revisar la ortografía de las tareas mediante el Bing Spell Check API. Para obtener más información, vea revisión
ortográfica mediante el Bing spell check API.
Traduzca tareas de inglés a alemán mediante la API de traductor. Para obtener más información, consulte
traducción de texto mediante la API de traductor.
Eliminar tareas.
Establezca el estado de una tarea en "listo".
Califique la aplicación con el reconocimiento de emociones mediante el Face API. Para obtener más información,
consulte el reconocimiento de emociones mediante el Face API.

WARNING
El Bing Speech API ha dejado de usarse en favor del servicio de voz de Azure. Para obtener un ejemplo dedicado a Azure
Speech Service, consulte reconocimiento de voz con la API del servicio de voz.

Las tareas se almacenan en una base de datos SQLite local. Para obtener más información sobre cómo usar una
base de datos SQLite local, consulte trabajar con una base de datos local.
TodoListPageSe muestra cuando se inicia la aplicación. En esta página se muestra una lista de las tareas
almacenadas en la base de datos local y se permite al usuario crear una nueva tarea o evaluar la aplicación:
Se pueden crear nuevos elementos haciendo clic en el + botón, que navega a TodoItemPage . También se puede
navegar por esta página seleccionando una tarea:
TodoItemPage Permite que las tareas se creen, se editen, se comprueben ortográficamente, se traduzcan, se guarden
y se eliminen. El reconocimiento de voz se puede usar para crear o editar una tarea. Para ello, presione el botón
micrófono para iniciar la grabación y presione el mismo botón una segunda vez para detener la grabación, que
envía la grabación a la API de reconocimiento de Bing Speech.
Al hacer clic en el botón de emoticonos de TodoListPage , navega hasta el RateAppPage , que se usa para realizar el
reconocimiento de emociones en una imagen de una expresión facial:

RateAppPage Permite al usuario tomar una fotografía de su superficie, que se envía al Face API con la emoción
devuelta que se muestra.

Descripción de la anatomía de la aplicación


El proyecto de código compartido para la aplicación de ejemplo consta de cinco carpetas principales:

C A RP ETA P RO P Ó SITO

Modelos Contiene las clases de modelo de datos para la aplicación. Esto


incluye la TodoItem clase, que modela un único elemento de
datos utilizado por la aplicación. La carpeta también incluye las
clases que se usan para modelar las respuestas JSON
devueltas de diferentes API del servicio cognitivo de Microsoft.

Repositorios Contiene la interfaz y la


ITodoItemRepository
TodoItemRepository clase que se utilizan para realizar
operaciones de base de datos.
C A RP ETA P RO P Ó SITO

Servicios Contiene las interfaces y clases que se usan para tener acceso
a diferentes API del servicio cognitivo de Microsoft, junto con
las interfaces que utiliza la DependencyService clase para
buscar las clases que implementan las interfaces en los
proyectos de la plataforma.

Utils Contiene la clase, que usa la


Timer
AuthenticationService clase para renovar un token de
acceso de JWT cada 9 minutos.

Vistas Contiene las páginas de la aplicación.

El proyecto de código compartido también contiene algunos archivos importantes:

A RC H IVO P RO P Ó SITO

Constants.cs La Constants clase, que especifica las claves de API y los


puntos de conexión de las API de servicio cognitiva de
Microsoft que se invocan. Las constantes de clave de API
requieren actualización para tener acceso a las diferentes API
de servicio cognitiva.

App.xaml.cs La App clase es responsable de crear instancias de la primera


página que mostrará la aplicación en cada plataforma y de la
TodoManager clase que se utiliza para invocar las operaciones
de base de datos.

Paquetes NuGet
La aplicación de ejemplo usa los siguientes paquetes NuGet:
Newtonsoft.Json : proporciona un marco JSON para .NET.
PCLStorage : proporciona un conjunto de API de e/s de archivos locales entre plataformas.
sqlite-net-pcl : proporciona almacenamiento de base de datos de SQLite.
Xam.Plugin.Media : proporciona las API de selección y toma de fotografías multiplataforma.

Además, estos paquetes NuGet también instalan sus propias dependencias.


Modelado de los datos
La aplicación de ejemplo utiliza la TodoItem clase para modelar los datos que se muestran y se almacenan en la
base de datos SQLite local. En el ejemplo de código siguiente se muestra la clase TodoItem :

public class TodoItem


{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public bool Done { get; set; }
}

La ID propiedad se usa para identificar de forma única cada TodoItem instancia y está decorada con atributos de
SQLite que hacen que la propiedad sea una clave principal de incremento automático en la base de datos.
Invocar operaciones de base de datos
La TodoItemRepository clase implementa las operaciones de base de datos y se puede tener acceso a una instancia
de la clase a través de la App.TodoManager propiedad. La TodoItemRepository clase proporciona los siguientes
métodos para invocar operaciones de base de datos:
GetAllItemsAsync : recupera todos los elementos de la base de datos SQLite local.
GetItemAsync : recupera un elemento especificado de la base de datos SQLite local.
SaveItemAsync : crea o actualiza un elemento en la base de datos SQLite local.
DeleteItemAsync : elimina el elemento especificado de la base de datos SQLite local.
Implementaciones del proyecto de plataforma
La Services carpeta del proyecto de código compartido contiene las IFileHelper IAudioRecorderService
interfaces y que usa la DependencyService clase para buscar las clases que implementan las interfaces en los
proyectos de la plataforma.
La IFileHelper clase implementa la interfaz FileHelper en cada proyecto de plataforma. Esta clase consta de un
único método, GetLocalFilePath , que devuelve una ruta de acceso de archivo local para almacenar la base de
datos de SQLite.
La IAudioRecorderService clase implementa la interfaz AudioRecorderService en cada proyecto de plataforma. Esta
clase consta de StartRecording StopRecording los métodos de compatibilidad, y, que usan las API de la plataforma
para grabar audio desde el micrófono del dispositivo y almacenarlo como un archivo WAV. En iOS,
AudioRecorderService usa la AVFoundation API para grabar audio. En Android, AudioRecordService usa la
AudioRecord API para grabar audio. En el Plataforma universal de Windows (UWP), AudioRecorderService usa la
AudioGraph API para grabar audio.

Invocar servicios cognitivos


La aplicación de ejemplo invoca el siguiente Microsoft Cognitive Services:
Microsoft Speech API. Para obtener más información, consulte reconocimiento de voz mediante Microsoft
Speech API.
Bing Spell Check API. Para obtener más información, vea revisión ortográfica mediante el Bing spell check API.
Traduzca la API. Para obtener más información, consulte traducción de texto mediante la API de traductor.
Face API. Para obtener más información, consulte el reconocimiento de emociones mediante el Face API.

Vínculos relacionados
Reconocimiento de voz con la API del servicio Speech
Microsoft Cognitive Services documentación
Cognitive Services todo (ejemplo)
Reconocimiento de voz con el servicio de voz de
Azure
18/12/2020 • 14 minutes to read • Edit Online

Descargar el ejemplo
El servicio de voz de Azure es una API basada en la nube que ofrece la funcionalidad siguiente:
La conversión de voz en texto transcripción archivos de audio o secuencias en texto.
La conversión de texto a voz convier te el texto de entrada en voz sintetizada de tipo humano.
La traducción de voz permite la traducción en tiempo real en varios idiomas tanto en voz a texto como en
voz a voz.
Los asistentes de voz pueden crear interfaces de conversación de tipo humano para aplicaciones.
En este artículo se explica cómo se implementa Speech-to-Text en la aplicación de ejemplo :::no-
loc(Xamarin.Forms)::: con el servicio de voz de Azure. En las siguientes capturas de pantallas se muestra la
aplicación de ejemplo en iOS y Android:

Creación de un recurso de Azure Speech Services


El servicio de voz de Azure forma parte de Azure Cognitive Services, que proporciona API basadas en la nube
para tareas como el reconocimiento de imágenes, el reconocimiento de voz y la traducción, y la búsqueda de
Bing. Para obtener más información, consulte ¿Qué son Azure Cognitive Services?.
El proyecto de ejemplo requiere la creación de un recurso de Cognitive Services de Azure en el Azure Portal. Se
puede crear un recurso de Cognitive Services para un servicio único, como Speech Service, o como un recurso de
varios servicios. Los pasos para crear un recurso de Speech Services son los siguientes:
1. Inicie sesión en el Azure portal.
2. Cree un recurso de varios servicios o de un solo servicio.
3. Obtenga la clave de API y la información de la región para el recurso.
4. Actualice el archivo constants.CS de ejemplo.
Para obtener una guía paso a paso sobre la creación de un recurso, vea creación de un recurso de Cognitive
Services.
NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar. Una vez que tenga una cuenta, se puede
crear un recurso de servicio único en el nivel gratis para probar el servicio.

Configuración de la aplicación con el servicio de voz


Después de crear un recurso de Cognitive Services, el archivo constants.CS se puede actualizar con la región y la
clave de API del recurso de Azure:

public static class Constants


{
public static string CognitiveServicesApiKey = "YOUR_KEY_GOES_HERE";
public static string CognitiveServicesRegion = "westus";
}

Instalar el paquete NuGet Speech Service


La aplicación de ejemplo usa el paquete NuGet Microsoft. cognitiveser vices account. Speech para
conectarse al servicio de voz de Azure. Instale este paquete de NuGet en el proyecto compartido y en cada
proyecto de plataforma.

Creación de una interfaz IMicrophoneService


Cada plataforma requiere permiso para acceder al micrófono. El proyecto de ejemplo proporciona una
IMicrophoneService interfaz en el proyecto compartido y utiliza :::no-loc(Xamarin.Forms)::: DependencyService
para obtener implementaciones de la plataforma de la interfaz.

public interface IMicrophoneService


{
Task<bool> GetPermissionAsync();
void OnRequestPermissionResult(bool isGranted);
}

Crear el diseño de página


El proyecto de ejemplo define un diseño de página básico en el archivo mainpage. Xaml . Los elementos de
diseño de clave son un Button que inicia el proceso de transcripción, un que Label contiene el texto
transformado y un ActivityIndicator para mostrar cuándo está en curso la transcripción:
<ContentPage ...>
<StackLayout>
<Frame ...>
<ScrollView x:Name="scroll"
...>
<Label x:Name="transcribedText"
... />
</ScrollView>
</Frame>

<ActivityIndicator x:Name="transcribingIndicator"
IsRunning="False" />
<Button x:Name="transcribeButton"
...
Clicked="TranscribeClicked"/>
</StackLayout>
</ContentPage>

Implementar el servicio de voz


El archivo de código subyacente mainpage.Xaml.CS contiene toda la lógica para enviar audio y recibir texto
transasignado desde el servicio de voz de Azure.
El MainPage constructor obtiene una instancia de la IMicrophoneService interfaz de DependencyService :

public partial class MainPage : ContentPage


{
SpeechRecognizer recognizer;
IMicrophoneService micService;
bool isTranscribing = false;

public MainPage()
{
InitializeComponent();

micService = DependencyService.Resolve<IMicrophoneService>();
}

// ...
}

TranscribeClicked Se llama al método cuando transcribeButton se puntea la instancia:


async void TranscribeClicked(object sender, EventArgs e)
{
bool isMicEnabled = await micService.GetPermissionAsync();

// EARLY OUT: make sure mic is accessible


if (!isMicEnabled)
{
UpdateTranscription("Please grant access to the microphone!");
return;
}

// initialize speech recognizer


if (recognizer == null)
{
var config = SpeechConfig.FromSubscription(Constants.CognitiveServicesApiKey,
Constants.CognitiveServicesRegion);
recognizer = new SpeechRecognizer(config);
recognizer.Recognized += (obj, args) =>
{
UpdateTranscription(args.Result.Text);
};
}

// if already transcribing, stop speech recognizer


if (isTranscribing)
{
try
{
await recognizer.StopContinuousRecognitionAsync();
}
catch(Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = false;
}

// if not transcribing, start speech recognizer


else
{
Device.BeginInvokeOnMainThread(() =>
{
InsertDateTimeRecord();
});
try
{
await recognizer.StartContinuousRecognitionAsync();
}
catch(Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = true;
}
UpdateDisplayState();
}

El método TranscribeClicked hace lo siguiente:


1. Comprueba si la aplicación tiene acceso al micrófono y sale pronto si no lo hace.
2. Crea una instancia de la SpeechRecognizer clase si aún no existe.
3. Detiene la transcripción continua si está en curso.
4. Inserta una marca de tiempo e inicia la transcripción continua si no está en curso.
5. Notifica a la aplicación que actualice su apariencia en función del nuevo estado de la aplicación.
El resto de los MainPage métodos de clase son aplicaciones auxiliares para mostrar el estado de la aplicación:

void UpdateTranscription(string newText)


{
Device.BeginInvokeOnMainThread(() =>
{
if (!string.IsNullOrWhiteSpace(newText))
{
transcribedText.Text += $"{newText}\n";
}
});
}

void InsertDateTimeRecord()
{
var msg = $"=================\n{DateTime.Now.ToString()}\n=================";
UpdateTranscription(msg);
}

void UpdateDisplayState()
{
Device.BeginInvokeOnMainThread(() =>
{
if (isTranscribing)
{
transcribeButton.Text = "Stop";
transcribeButton.BackgroundColor = Color.Red;
transcribingIndicator.IsRunning = true;
}
else
{
transcribeButton.Text = "Transcribe";
transcribeButton.BackgroundColor = Color.Green;
transcribingIndicator.IsRunning = false;
}
});
}

El UpdateTranscriptionmétodo escribe el proporcionado newText string en el Label elemento denominado


transcribedText . Obliga a que esta actualización se produzca en el subproceso de la interfaz de usuario, por lo
que se puede llamar desde cualquier contexto sin producir excepciones. InsertDateTimeRecord Escribe la fecha y
hora actuales en la transcribedText instancia de para marcar el inicio de una nueva transcripción. Por último, el
UpdateDisplayState método actualiza Button los ActivityIndicator elementos y para reflejar si la transcripción
está en curso o no.

Creación de servicios de micrófono de plataforma


La aplicación debe tener acceso al micrófono para recopilar datos de voz. La IMicrophoneService interfaz debe
implementarse y registrarse con DependencyService en cada plataforma para que la aplicación funcione.
Android
El proyecto de ejemplo define una IMicrophoneService implementación de para Android denominada
AndroidMicrophoneService :
[assembly: Dependency(typeof(AndroidMicrophoneService))]
namespace CognitiveSpeechService.Droid.Services
{
public class AndroidMicrophoneService : IMicrophoneService
{
public const int RecordAudioPermissionCode = 1;
private TaskCompletionSource<bool> tcsPermissions;
string[] permissions = new string[] { Manifest.Permission.RecordAudio };

public Task<bool> GetPermissionAsync()


{
tcsPermissions = new TaskCompletionSource<bool>();

if ((int)Build.VERSION.SdkInt < 23)


{
tcsPermissions.TrySetResult(true);
}
else
{
var currentActivity = MainActivity.Instance;
if (ActivityCompat.CheckSelfPermission(currentActivity, Manifest.Permission.RecordAudio) !=
(int)Permission.Granted)
{
RequestMicPermissions();
}
else
{
tcsPermissions.TrySetResult(true);
}

return tcsPermissions.Task;
}

public void OnRequestPermissionResult(bool isGranted)


{
tcsPermissions.TrySetResult(isGranted);
}

void RequestMicPermissions()
{
if (ActivityCompat.ShouldShowRequestPermissionRationale(MainActivity.Instance,
Manifest.Permission.RecordAudio))
{
Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content),
"Microphone permissions are required for speech transcription!",
Snackbar.LengthIndefinite)
.SetAction("Ok", v =>
{
((Activity)MainActivity.Instance).RequestPermissions(permissions,
RecordAudioPermissionCode);
})
.Show();
}
else
{
ActivityCompat.RequestPermissions((Activity)MainActivity.Instance, permissions,
RecordAudioPermissionCode);
}
}
}
}

AndroidMicrophoneService Tiene las siguientes características:


1. El Dependency atributo registra la clase con DependencyService .
2. El GetPermissionAsync método comprueba si se requieren permisos en función de la versión de Android SDK y
llama a RequestMicPermissions si aún no se ha concedido el permiso.
3. El RequestMicPermissions método utiliza la Snackbar clase para solicitar permisos al usuario si se requiere un
razonamiento; de lo contrario, solicita directamente los permisos de grabación de audio.
4. OnRequestPermissionResult Se llama al método con un bool resultado una vez que el usuario ha respondido a
la solicitud de permisos.
La MainActivity clase se personaliza para actualizar la AndroidMicrophoneService instancia cuando se completan
las solicitudes de permisos:

public class MainActivity : global:::::no-loc(Xamarin.Forms):::.Platform.Android.FormsAppCompatActivity


{
IMicrophoneService micService;
internal static MainActivity Instance { get; private set; }

protected override void OnCreate(Bundle savedInstanceState)


{
Instance = this;
// ...
micService = DependencyService.Resolve<IMicrophoneService>();
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum]
Android.Content.PM.Permission[] grantResults)
{
// ...
switch(requestCode)
{
case AndroidMicrophoneService.RecordAudioPermissionCode:
if (grantResults[0] == Permission.Granted)
{
micService.OnRequestPermissionResult(true);
}
else
{
micService.OnRequestPermissionResult(false);
}
break;
}
}
}

La MainActivity clase define una referencia estática denominada Instance , que es necesaria para el
AndroidMicrophoneService objeto al solicitar permisos. Invalida el OnRequestPermissionsResult método para
actualizar el AndroidMicrophoneService objeto cuando el usuario aprueba o deniega la solicitud de permisos.
Por último, la aplicación de Android debe incluir el permiso para grabar audio en el archivo de
AndroidManifest.xml :

<manifest ...>
...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

iOS
El proyecto de ejemplo define una IMicrophoneService implementación de para iOS llamada
iOSMicrophoneService :
[assembly: Dependency(typeof(iOSMicrophoneService))]
namespace CognitiveSpeechService.iOS.Services
{
public class iOSMicrophoneService : IMicrophoneService
{
TaskCompletionSource<bool> tcsPermissions;

public Task<bool> GetPermissionAsync()


{
tcsPermissions = new TaskCompletionSource<bool>();
RequestMicPermission();
return tcsPermissions.Task;
}

public void OnRequestPermissionResult(bool isGranted)


{
tcsPermissions.TrySetResult(isGranted);
}

void RequestMicPermission()
{
var session = AVAudioSession.SharedInstance();
session.RequestRecordPermission((granted) =>
{
tcsPermissions.TrySetResult(granted);
});
}
}
}

iOSMicrophoneService Tiene las siguientes características:


1. El Dependency atributo registra la clase con DependencyService .
2. El GetPermissionAsync método llama RequestMicPermissions a para solicitar permisos al usuario del
dispositivo.
3. El RequestMicPermissions método utiliza la AVAudioSession instancia compartida para solicitar permisos de
grabación.
4. El OnRequestPermissionResult método actualiza la TaskCompletionSource instancia con el bool valor
proporcionado.
Por último, el archivo info. plist de la aplicación iOS debe incluir un mensaje que indique al usuario el motivo
por el que la aplicación solicita el acceso al micrófono. Edite el archivo info. plist para incluir las siguientes
etiquetas en el <dict> elemento:

<plist>
<dict>
...
<key>NSMicrophoneUsageDescription</key>
<string>Voice transcription requires microphone access</string>
</dict>
</plist>

UWP
El proyecto de ejemplo define una IMicrophoneService implementación de para UWP denominada
UWPMicrophoneService :
[assembly: Dependency(typeof(UWPMicrophoneService))]
namespace CognitiveSpeechService.UWP.Services
{
public class UWPMicrophoneService : IMicrophoneService
{
public async Task<bool> GetPermissionAsync()
{
bool isMicAvailable = true;
try
{
var mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings();
settings.StreamingCaptureMode = StreamingCaptureMode.Audio;
await mediaCapture.InitializeAsync(settings);
}
catch(Exception ex)
{
isMicAvailable = false;
}

if(!isMicAvailable)
{
await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-microphone"));
}

return isMicAvailable;
}

public void OnRequestPermissionResult(bool isGranted)


{
// intentionally does nothing
}
}
}

UWPMicrophoneService Tiene las siguientes características:


1. El Dependency atributo registra la clase con DependencyService .
2. El GetPermissionAsync método intenta inicializar una MediaCapture instancia de. Si se produce un error, se
inicia una solicitud de usuario para habilitar el micrófono.
3. El OnRequestPermissionResult método existe para satisfacer la interfaz, pero no es necesario para la
implementación de UWP.
Por último, el paquete de UWP. appxmanifest debe especificar que la aplicación usa el micrófono. Haga doble
clic en el archivo package. appxmanifest y seleccione la opción micrófono en la pestaña capacidades de Visual
Studio 2019:

Prueba de la aplicación
Ejecute la aplicación y haga clic en el botón transcribir . La aplicación debe solicitar acceso al micrófono e iniciar
el proceso de transcripción. ActivityIndicator Se animará, mostrando que la transcripción está activa. A medida
que hable, la aplicación transmitirá los datos de audio al recurso de Azure Speech Services, que responderá con el
texto transformado. El texto Label transformado aparecerá en el elemento a medida que se reciba.
NOTE
Los emuladores de Android no pueden cargar e inicializar las bibliotecas del servicio de voz. Se recomienda realizar pruebas
en un dispositivo físico para la plataforma Android.

Vínculos relacionados
Ejemplo de servicio de voz de Azure
Información general de Azure Speech Service
Cree un recurso de Cognitive Services
Inicio rápido: Reconocimiento de voz a través de un micrófono
Revisión ortográfica mediante el Bing Spell Check
API
18/12/2020 • 9 minutes to read • Edit Online

Descargar el ejemplo
Bing Spell Check realiza la revisión ortográfica contextual del texto, proporcionando sugerencias en línea para
palabras mal escritas. En este artículo se explica cómo usar la API de REST de Bing Spell Check para corregir
errores ortográficos en una :::no-loc(Xamarin.Forms)::: aplicación.

Información general
La API de REST de Bing Spell Check tiene dos modos operativos y se debe especificar un modo al realizar una
solicitud a la API:
Spell corrige texto corto (hasta 9 palabras) sin cambios de mayúsculas y minúsculas.
Proof corrige texto largo, proporciona correcciones de mayúsculas y minúsculas y puntuación básica y
suprime las correcciones agresivas.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Se debe obtener una clave de API para usar el Bing Spell Check API. Esto puede obtenerse en el intento Cognitive
Services
Para obtener una lista de los idiomas admitidos por el Bing Spell Check API, consulte idiomas admitidos. Para
obtener más información sobre el Bing Spell Check API, consulte la documentación de Bing spell check.

Authentication
Cada solicitud realizada al Bing Spell Check API requiere una clave de API que debe especificarse como el valor del
Ocp-Apim-Subscription-Key encabezado. En el ejemplo de código siguiente se muestra cómo agregar la clave de
API al Ocp-Apim-Subscription-Key encabezado de una solicitud:

public BingSpellCheckService()
{
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", Constants.BingSpellCheckApiKey);
}

Si no se pasa una clave de API válida a la Bing Spell Check API, se producirá un error de respuesta 401.

Realización de revisión ortográfica


La revisión ortográfica se puede lograr realizando una solicitud GET o POST a la SpellCheck API en
https://api.cognitive.microsoft.com/bing/v7.0/SpellCheck . Al realizar una solicitud GET, el texto que se va a
revisar se envía como un parámetro de consulta. Al realizar una solicitud POST, el texto que se va a revisar se
envía en el cuerpo de la solicitud. Las solicitudes GET se limitan a la revisión ortográfica de 1500 caracteres
debidos a la limitación de longitud de cadena de parámetro de consulta. Por lo tanto, las solicitudes POST deben
realizarse normalmente a menos que se Compruebe la ortografía de las cadenas cortas.
En la aplicación de ejemplo, el SpellCheckTextAsync método invoca el proceso de revisión ortográfica:

public async Task<SpellCheckResult> SpellCheckTextAsync(string text)


{
string requestUri = GenerateRequestUri(Constants.BingSpellCheckEndpoint, text, SpellCheckMode.Spell);
var response = await SendRequestAsync(requestUri);
var spellCheckResults = JsonConvert.DeserializeObject<SpellCheckResult>(response);
return spellCheckResults;
}

El SpellCheckTextAsync método genera un URI de solicitud y, a continuación, envía la solicitud a la SpellCheck


API, que devuelve una respuesta JSON que contiene el resultado. La respuesta JSON se deserializa, con el
resultado devuelto al método de llamada para su presentación.
Configurar la revisión ortográfica
El proceso de revisión ortográfica se puede configurar especificando parámetros de consulta HTTP:

string GenerateRequestUri(string spellCheckEndpoint, string text, SpellCheckMode mode)


{
string requestUri = spellCheckEndpoint;
requestUri += string.Format("?text={0}", text); // text to spell check
requestUri += string.Format("&mode={0}", mode.ToString().ToLower()); // spellcheck mode - proof or spell
return requestUri;
}

Este método establece el texto que se va a comprobar ortográfica y el modo de revisión ortográfica.
Para obtener más información acerca de la API de REST de Bing Spell Check, consulte referencia de spell check API
V7.
Envío de la solicitud
El SendRequestAsync método realiza la solicitud get a la API de REST de Bing spell check y devuelve la respuesta:

async Task<string> SendRequestAsync(string url)


{
var response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}

Este método envía la solicitud GET a la SpellCheck API, con la dirección URL de la solicitud que especifica el texto
que se va a traducir y el modo de revisión ortográfica. A continuación, la respuesta se lee y se devuelve al método
de llamada.
La SpellCheck API enviará el código de Estado HTTP 200 (OK) en la respuesta, siempre que la solicitud sea válida,
lo que indica que la solicitud se realizó correctamente y que la información solicitada se encuentra en la respuesta.
Para obtener una lista de objetos de respuesta, vea objetos de respuesta.
Procesar la respuesta
La respuesta de la API se devuelve en formato JSON. Los siguientes datos JSON muestran el mensaje de
respuesta para el texto mal escrito Go shappin tommorow :
{
"_type":"SpellCheck",
"flaggedTokens":[
{
"offset":3,
"token":"shappin",
"type":"UnknownToken",
"suggestions":[
{
"suggestion":"shopping",
"score":1
}
]
},
{
"offset":11,
"token":"tommorow",
"type":"UnknownToken",
"suggestions":[
{
"suggestion":"tomorrow",
"score":1
}
]
}
],
"correctionType":"High"
}

La flaggedTokens matriz contiene una matriz de palabras en el texto que se han marcado como no escritas
correctamente o que son gramaticalmente incorrectas. La matriz estará vacía si no se encuentran errores
ortográficos o gramaticales. Las etiquetas dentro de la matriz son:
offset : un desplazamiento de base cero desde el principio de la cadena de texto hasta la palabra que se
marcó.
token : la palabra de la cadena de texto que no está escrita correctamente o es gramaticalmente incorrecta.
type : el tipo de error que provocó la marca de la palabra. Hay dos valores posibles: RepeatedToken y
UnknownToken .
suggestions : una matriz de palabras en las que se corregirá el error ortográfico o gramatical. La matriz se
compone de un suggestion y un score , que indica el nivel de confianza que la corrección sugerida es
correcta.
En la aplicación de ejemplo, la respuesta JSON se deserializa en una SpellCheckResult instancia de, con el
resultado devuelto al método de llamada para su presentación. En el ejemplo de código siguiente se muestra
cómo SpellCheckResult se procesa la instancia para su presentación:

var spellCheckResult = await bingSpellCheckService.SpellCheckTextAsync(TodoItem.Name);


foreach (var flaggedToken in spellCheckResult.FlaggedTokens)
{
TodoItem.Name = TodoItem.Name.Replace(flaggedToken.Token,
flaggedToken.Suggestions.FirstOrDefault().Suggestion);
}

Este código recorre en iteración la FlaggedTokens colección y reemplaza las palabras mal escritas o
gramaticalmente incorrectas en el texto de origen con la primera sugerencia. Las capturas de pantallas siguientes
se muestran antes y después de la revisión ortográfica:
NOTE
En el ejemplo anterior Replace se usa para simplificar, pero en una gran cantidad de texto podría reemplazar el token
equivocado. La API proporciona el offset valor que debe usarse en las aplicaciones de producción para identificar la
ubicación correcta en el texto de origen para realizar una actualización.

Resumen
En este artículo se explica cómo usar la API de REST de Bing Spell Check para corregir errores ortográficos en una
:::no-loc(Xamarin.Forms)::: aplicación. Bing Spell Check realiza la revisión ortográfica contextual del texto,
proporcionando sugerencias en línea para palabras mal escritas.

Vínculos relacionados
Bing Spell Check documentación
Consumo de un servicio web RESTful
Cognitive Services todo (ejemplo)
Referencia de la API Bing Spell Check v7
Traducción de texto mediante la API de traductor
18/12/2020 • 8 minutes to read • Edit Online

Descargar el ejemplo
La API de traductor de Microsoft se puede usar para traducir la voz y el texto a través de una API de REST. En este
artículo se explica cómo usar Microsoft Translator Text API para traducir texto de un idioma a otro en una :::no-
loc(Xamarin.Forms)::: aplicación.

Información general
La API de traductor tiene dos componentes:
Una API de REST de traducción de texto para traducir texto de un idioma a texto de otro idioma. La API detecta
automáticamente el idioma del texto que se envió antes de traducirlo.
Una API de REST de traducción de voz para transcribir la voz de un idioma en texto de otro idioma. La API
también integra las funcionalidades de texto a voz para volver a hablar el texto traducido.
Este artículo se centra en traducir texto de un idioma a otro mediante el Translator Text API.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Se debe obtener una clave de API para usar el Translator Text API. Esto puede obtenerse en Cómo suscribirse a
Microsoft Translator Text API.
Para obtener más información acerca de Microsoft Translator Text API, consulte la documentación de Translator
Text API.

Authentication
Cada solicitud realizada al Translator Text API requiere un token de acceso JSON Web Token (JWT), que se puede
obtener del servicio de token de servicios cognitivos en https://api.cognitive.microsoft.com/sts/v1.0/issueToken
. Un token se puede obtener realizando una solicitud POST al servicio de token, especificando un
Ocp-Apim-Subscription-Key encabezado que contiene la clave de API como su valor.

En el ejemplo de código siguiente se muestra cómo solicitar un token de acceso del servicio de token:
public AuthenticationService(string apiKey)
{
subscriptionKey = apiKey;
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
}
...
async Task<string> FetchTokenAsync(string fetchUri)
{
UriBuilder uriBuilder = new UriBuilder(fetchUri);
uriBuilder.Path += "/issueToken";
var result = await httpClient.PostAsync(uriBuilder.Uri.AbsoluteUri, null);
return await result.Content.ReadAsStringAsync();
}

El token de acceso devuelto, que es texto Base64, tiene una hora de expiración de 10 minutos. Por lo tanto, la
aplicación de ejemplo renueva el token de acceso cada 9 minutos.
El token de acceso debe especificarse en cada Translator Text API llamada como un Authorization encabezado con
el prefijo de la cadena Bearer , como se muestra en el ejemplo de código siguiente:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);

Para obtener más información sobre el servicio de token de servicios cognitivos, vea autenticación.

Realizar la traducción de texto


La traducción de texto se puede lograr mediante la realización de una solicitud GET a la translate API en
https://api.microsofttranslator.com/v2/http.svc/translate . En la aplicación de ejemplo, el TranslateTextAsync
método invoca el proceso de traducción de texto:

public async Task<string> TranslateTextAsync(string text)


{
...
string requestUri = GenerateRequestUri(Constants.TextTranslatorEndpoint, text, "en", "de");
string accessToken = authenticationService.GetAccessToken();
var response = await SendRequestAsync(requestUri, accessToken);
var xml = XDocument.Parse(response);
return xml.Root.Value;
}

El TranslateTextAsync método genera un URI de solicitud y recupera un token de acceso del servicio de token. La
solicitud de traducción de texto se envía a la translate API, que devuelve una respuesta XML que contiene el
resultado. Se analiza la respuesta XML y el resultado de la conversión se devuelve al método de llamada para su
presentación.
Para obtener más información sobre las API de REST de traducción de texto, consulte Translator Text API.
Configurar la traducción de texto
El proceso de traducción de texto se puede configurar especificando parámetros de consulta HTTP:
string GenerateRequestUri(string endpoint, string text, string to)
{
string requestUri = endpoint;
requestUri += string.Format("?text={0}", Uri.EscapeUriString(text));
requestUri += string.Format("&to={0}", to);
return requestUri;
}

Este método establece el texto que se va a traducir y el idioma al que se va a traducir el texto. Para obtener una
lista de los idiomas admitidos por Microsoft Translator, consulte idiomas admitidos en microsoft Translator Text
API.

NOTE
Si una aplicación necesita saber en qué idioma se encuentra el texto, se Detect puede llamar a la API para detectar el
idioma de la cadena de texto.

Envío de la solicitud
El SendRequestAsync método realiza la solicitud get a la API de REST de traducción de texto y devuelve la
respuesta:

async Task<string> SendRequestAsync(string url, string bearerToken)


{
if (httpClient == null)
{
httpClient = new HttpClient();
}
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);

var response = await httpClient.GetAsync(url);


return await response.Content.ReadAsStringAsync();
}

Este método compila la solicitud GET agregando el token de acceso al Authorization encabezado, con el prefijo
de la cadena Bearer . La solicitud GET se envía a la translate API, con la dirección URL de la solicitud que
especifica el texto que se va a traducir y el idioma al que se va a traducir el texto. A continuación, la respuesta se
lee y se devuelve al método de llamada.
La translate API enviará el código de Estado HTTP 200 (OK) en la respuesta, siempre que la solicitud sea válida,
lo que indica que la solicitud se realizó correctamente y que la información solicitada se encuentra en la respuesta.
Para obtener una lista de las posibles respuestas de error, consulte mensajes de respuesta en obtener traducción.
Procesar la respuesta
La respuesta de la API se devuelve en formato XML. Los siguientes datos XML muestran un mensaje de respuesta
correcta típico:

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Morgen kaufen gehen ein</string>

En la aplicación de ejemplo, la respuesta XML se analiza en una XDocument instancia de, con el valor raíz de XML
que se devuelve al método de llamada para su presentación, tal y como se muestra en las siguientes capturas de
pantalla:
Resumen
En este artículo se explica cómo usar Microsoft Translator Text API para traducir texto de un idioma a texto de otro
idioma en una :::no-loc(Xamarin.Forms)::: aplicación. Además de traducir texto, la API de Microsoft Translator
también puede transcribir la voz de un idioma en el texto de otro idioma.

Vínculos relacionados
Documentación sobre Translator Text API
Consumo de un servicio web RESTful
Cognitive Services todo (ejemplo)
Translator Text API
Reconocimiento de emociones percibido mediante el
Face API
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
El Face API puede realizar la detección de emociones para detectar enojo, desprecio, asco, miedo, felicidad,
neutralidad, tristeza y sorpresa, en una expresión facial en función de las anotaciones percibidas por los
codificadores humanos. Sin embargo, es importante tener en cuenta que las expresiones faciales solas no
representan necesariamente los Estados internos de las personas.
Además de devolver un resultado de emoción para una expresión facial, el Face API también puede devolver un
cuadro de límite para los rostros detectados.
El reconocimiento de emociones puede realizarse a través de una biblioteca de cliente y a través de una API de
REST. Este artículo se centra en realizar el reconocimiento de emociones a través de la API de REST. Para más
información sobre la API de REST, consulte la API de REST de caras.
El Face API también puede usarse para reconocer las expresiones faciales de las personas en vídeo y puede
devolver un resumen de sus emociones. Para obtener más información, consulte análisis de vídeos en tiempo real.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Se debe obtener una clave de API para usar el Face API. Esto puede obtenerse en el intento Cognitive Services.
Para obtener más información sobre el Face API, vea face API.

Authentication
Cada solicitud realizada al Face API requiere una clave de API que debe especificarse como el valor del
Ocp-Apim-Subscription-Key encabezado. En el ejemplo de código siguiente se muestra cómo agregar la clave de
API al Ocp-Apim-Subscription-Key encabezado de una solicitud:

public FaceRecognitionService()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("ocp-apim-subscription-key", Constants.FaceApiKey);
}

Si no se pasa una clave de API válida a la Face API, se producirá un error de respuesta 401.

Realizar el reconocimiento de emociones


El reconocimiento de emociones se realiza mediante una solicitud POST que contiene una imagen a la detect API
en https://[location].api.cognitive.microsoft.com/face/v1.0 , donde [location]] es la región que se usó para
obtener la clave de API. Los parámetros de solicitud opcionales son:
returnFaceId : indica si se van a devolver faceIds de los rostros detectados. El valor predeterminado es true .
returnFaceLandmarks : indica si se van a devolver puntos de referencia faciales de los rostros detectados. El
valor predeterminado es false .
returnFaceAttributes : si se analizan y devuelven uno o varios atributos de la esfera especificados. Los
atributos de caras admitidos son age , gender , headPose , smile , facialHair , glasses , emotion , hair ,
makeup , occlusion , accessories ,, blur exposure y noise . Tenga en cuenta que el análisis de atributos
faciales tiene un costo adicional de cálculo y tiempo.
El contenido de la imagen debe colocarse en el cuerpo de la solicitud POST como una dirección URL o datos
binarios.

NOTE
Los formatos de archivo de imagen admitidos son JPEG, PNG, GIF y BMP, y el tamaño de archivo permitido es de 1 KB a 4
MB.

En la aplicación de ejemplo, el proceso de reconocimiento de emociones se invoca llamando al DetectAsync


método:

Face[] faces = await _faceRecognitionService.DetectAsync(photoStream, true, false, new FaceAttributeType[] {


FaceAttributeType.Emotion });

Esta llamada al método especifica la secuencia que contiene los datos de la imagen, que se debe devolver faceIds,
que no se deben devolver puntos de referencia de caras y que se debe analizar la emoción de la imagen. También
especifica que los resultados se devolverán como una matriz de Face objetos. A su vez, el DetectAsync método
invoca la detect API de REST que realiza el reconocimiento de emociones:

public async Task<Face[]> DetectAsync(Stream imageStream, bool returnFaceId, bool returnFaceLandmarks,


IEnumerable<FaceAttributeType> returnFaceAttributes)
{
var requestUrl =
$"{Constants.FaceEndpoint}/detect?returnFaceId={returnFaceId}" +
"&returnFaceLandmarks={returnFaceLandmarks}" +
"&returnFaceAttributes={GetAttributeString(returnFaceAttributes)}";
return await SendRequestAsync<Stream, Face[]>(HttpMethod.Post, requestUrl, imageStream);
}

Este método genera un URI de solicitud y, a continuación, envía la solicitud a la detect API a través del
SendRequestAsync método.

NOTE
Debe usar la misma región en las llamadas a Face API que usó para obtener las claves de suscripción. Por ejemplo, si ha
obtenido las claves de suscripción de la westus región, el punto de conexión de detección de caras será
https://westus.api.cognitive.microsoft.com/face/v1.0/detect .

Envío de la solicitud
El SendRequestAsync método realiza la solicitud post al Face API y devuelve el resultado como una Face matriz:
async Task<TResponse> SendRequestAsync<TRequest, TResponse>(HttpMethod httpMethod, string requestUrl,
TRequest requestBody)
{
var request = new HttpRequestMessage(httpMethod, Constants.FaceEndpoint);
request.RequestUri = new Uri(requestUrl);
if (requestBody != null)
{
if (requestBody is Stream)
{
request.Content = new StreamContent(requestBody as Stream);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
}
else
{
// If the image is supplied via a URL
request.Content = new StringContent(JsonConvert.SerializeObject(requestBody, s_settings),
Encoding.UTF8, "application/json");
}
}

HttpResponseMessage responseMessage = await _client.SendAsync(request);


if (responseMessage.IsSuccessStatusCode)
{
string responseContent = null;
if (responseMessage.Content != null)
{
responseContent = await responseMessage.Content.ReadAsStringAsync();
}
if (!string.IsNullOrWhiteSpace(responseContent))
{
return JsonConvert.DeserializeObject<TResponse>(responseContent, s_settings);
}
return default(TResponse);
}
else
{
...
}
return default(TResponse);
}

Si la imagen se proporciona a través de una secuencia, el método compila la solicitud POST encapsulando el flujo
de imagen en una StreamContent instancia, que proporciona contenido http basado en una secuencia. Como
alternativa, si la imagen se proporciona a través de una dirección URL, el método genera la solicitud POST
encapsulando la dirección URL en una StringContent instancia de, que proporciona contenido http basado en una
cadena.
La solicitud POST se envía a la detect API. La respuesta se lee, se deserializa y se devuelve al método de llamada.
La detect API enviará el código de Estado HTTP 200 (OK) en la respuesta, siempre que la solicitud sea válida, lo
que indica que la solicitud se realizó correctamente y que la información solicitada se encuentra en la respuesta.
Para obtener una lista de las posibles respuestas de error, consulte la API de REST de caras.
Procesamiento de la respuesta
La respuesta de la API se devuelve en formato JSON. Los siguientes datos JSON muestran un mensaje de
respuesta correcta típico que proporciona los datos solicitados por la aplicación de ejemplo:
[
{
"faceId":"8a1a80fe-1027-48cf-a7f0-e61c0f005051",
"faceRectangle":{
"top":192,
"left":164,
"width":339,
"height":339
},
"faceAttributes":{
"emotion":{
"anger":0.0,
"contempt":0.0,
"disgust":0.0,
"fear":0.0,
"happiness":1.0,
"neutral":0.0,
"sadness":0.0,
"surprise":0.0
}
}
}
]

Un mensaje de respuesta correcta consta de una matriz de entradas de caras clasificada por tamaño de rectángulo
facial en orden descendente, mientras que una respuesta vacía indica que no se han detectado rostros. Cada una
de las caras reconocidas incluye una serie de atributos de caras opcionales, que se especifican mediante el
returnFaceAttributes argumento para el DetectAsync método.

En la aplicación de ejemplo, la respuesta JSON se deserializa en una matriz de Face objetos. Al interpretar los
resultados de la Face API, la emoción detectada debe interpretarse como la emoción con la puntuación más alta,
ya que las puntuaciones se normalizan para sumar a una. Por lo tanto, la aplicación de ejemplo muestra la
emoción reconocida con la puntuación más alta para la mayor superficie detectada en la imagen. Esto se consigue
con el código siguiente:

emotionResultLabel.Text = faces.FirstOrDefault().FaceAttributes.Emotion.ToRankedList().FirstOrDefault().Key;

En la captura de pantalla siguiente se muestra el resultado del proceso de reconocimiento de emociones en la


aplicación de ejemplo:
Vínculos relacionados
Face API.
Cognitive Services todo (ejemplo)
API de REST de caras
Xamarin.Formsy servicios Web
18/12/2020 • 2 minutes to read • Edit Online

Introducción
En este artículo se proporciona un tutorial de la Xamarin.Forms aplicación de ejemplo que muestra cómo
comunicarse con distintos servicios Web. Entre los temas tratados se incluyen la anatomía de la aplicación, las
páginas, el modelo de datos y la invocación de operaciones del servicio Web.

Consumo de un servicio web ASP.NET (ASMX)


Los servicios Web de ASP.NET (ASMX) proporcionan la capacidad de crear servicios web que envíen mensajes a
través de HTTP mediante el Protocolo simple de acceso a objetos (SOAP). SOAP es un protocolo independiente de
la plataforma e independiente del lenguaje para crear y obtener acceso a servicios Web. No es necesario que los
consumidores de un servicio ASMX sepan nada sobre la plataforma, el modelo de objetos o el lenguaje de
programación usado para implementar el servicio. Solo necesitan saber cómo enviar y recibir mensajes SOAP. En
este artículo se muestra cómo consumir un servicio Web ASMX desde una Xamarin.Forms aplicación de.

Consumir un servicio Web de Windows Communication Foundation


(WCF)
WCF es el marco unificado de Microsoft para la creación de aplicaciones orientadas a servicios. Permite a los
desarrolladores compilar aplicaciones distribuidas seguras, con transacciones y de confianza, e interoperables. Hay
diferencias entre los servicios Web de ASP.NET (ASMX) y WCF, pero es importante comprender que WCF es
compatible con las mismas funcionalidades que ASMX proporciona: mensajes SOAP a través de HTTP. En este
artículo se muestra cómo consumir un servicio SOAP de WCF desde una Xamarin.Forms aplicación.

Consumo de un servicio web RESTful


La transferencia de estado representacional (REST) es un estilo arquitectónico para la creación de servicios Web.
Las solicitudes REST se realizan a través de HTTP usando los mismos verbos HTTP que los exploradores web
utilizan para recuperar páginas web y enviar datos a los servidores. En este artículo se muestra cómo consumir un
servicio web RESTful desde una Xamarin.Forms aplicación.
:::no-loc(Xamarin.Forms)::: Introducción a servicios
Web
18/12/2020 • 7 minutes to read • Edit Online

Descargar el ejemplo
En este tema se proporciona un tutorial de la :::no-loc(Xamarin.Forms)::: aplicación de ejemplo que muestra cómo
comunicarse con distintos servicios Web. Aunque cada servicio Web utiliza una aplicación de ejemplo
independiente, son funcionalmente similares y comparten clases comunes.
La aplicación de lista de tareas pendientes de ejemplo que se describe a continuación se usa para mostrar cómo
acceder a los distintos tipos de back-ends de servicio Web con :::no-loc(Xamarin.Forms)::: . Proporciona
funcionalidad para:
Ver una lista de tareas.
Agregar, editar y eliminar tareas.
Establezca el estado de una tarea en "listo".
Diga el nombre de la tarea y los campos de notas.
En todos los casos, las tareas se almacenan en un back-end al que se tiene acceso a través de un servicio Web.
Cuando se inicia la aplicación, se muestra una página que muestra las tareas recuperadas del servicio Web y
permite al usuario crear una nueva tarea. Al hacer clic en una tarea, la aplicación se desplaza a una segunda
página en la que se puede editar, guardar, eliminar e decir la tarea. A continuación se muestra la aplicación final:
En cada tema de esta guía se proporciona un vínculo de descarga a una versión diferente de la aplicación que
muestra un tipo específico de back-end de servicios Web. Descargue el código de ejemplo correspondiente en la
página relacionada con cada estilo de servicio Web.

Descripción de la anatomía de la aplicación


El proyecto de código compartido para cada aplicación de ejemplo consta de tres carpetas principales:

C A RP ETA P RO P Ó SITO

data Contiene las clases e interfaces utilizadas para administrar los


elementos de datos y comunicarse con el servicio Web. Como
mínimo, incluye la TodoItemManager clase, que se expone a
través de una propiedad en la App clase para invocar las
operaciones del servicio Web.

Modelos Contiene las clases de modelo de datos para la aplicación.


Como mínimo, incluye la TodoItem clase, que modela un
único elemento de datos utilizado por la aplicación. La carpeta
también puede incluir cualquier clase adicional que se use
para modelar los datos de usuario.

Vistas Contiene las páginas de la aplicación. Normalmente, se


compone de TodoListPage las TodoItemPage clases y y
cualquier clase adicional que se use para la autenticación.

El proyecto de código compartido de cada aplicación también consta de varios archivos importantes:

A RC H IVO P RO P Ó SITO

Constants.cs La Constants clase, que especifica las constantes utilizadas


por la aplicación para comunicarse con el servicio Web. Estas
constantes requieren actualización para tener acceso al
servicio de back-end personal creado en un proveedor.

ITextToSpeech.cs La ITextToSpeech interfaz, que especifica que el Speak


método debe proporcionarse mediante cualquier clase de
implementación.
A RC H IVO P RO P Ó SITO

Todo.cs La App clase que es responsable de crear instancias de la


primera página que mostrará la aplicación en cada plataforma
y de la TodoItemManager clase que se utiliza para invocar las
operaciones del servicio Web.

Ver páginas
La mayoría de las aplicaciones de ejemplo contienen al menos dos páginas:
TodoListPage : esta página muestra una lista de TodoItem instancias y un icono de marca si la TodoItem.Done
propiedad es true . Al hacer clic en un elemento, navega hasta el TodoItemPage . Además, se pueden crear
nuevos elementos haciendo clic en el + símbolo.
TodoItemPage : en esta página se muestran los detalles de la seleccionada TodoItem y se permite editar,
guardar, eliminar y hablar.
Además, algunas aplicaciones de ejemplo contienen páginas adicionales que se usan para administrar el proceso
de autenticación del usuario.
Modelado de los datos
Cada aplicación de ejemplo utiliza la TodoItem clase para modelar los datos que se muestran y se envían al
servicio web para su almacenamiento. En el ejemplo de código siguiente se muestra la clase TodoItem :

public class TodoItem


{
public string ID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public bool Done { get; set; }
}

La ID propiedad se usa para identificar de forma única cada TodoItem instancia y la usa cada servicio web para
identificar los datos que se van a actualizar o eliminar.
Invocar operaciones de servicio Web
Se tiene acceso a las operaciones del servicio Web a través de la TodoItemManager clase y se puede tener acceso a
una instancia de la clase a través de la App.TodoManager propiedad. La TodoItemManager clase proporciona los
siguientes métodos para invocar las operaciones del servicio Web:
GetTasksAsync : este método se usa para rellenar el ListView control en TodoListPage con las TodoItem
instancias recuperadas del servicio Web.
SaveTaskAsync : este método se usa para crear o actualizar una TodoItem instancia en el servicio Web.
DeleteTaskAsync : este método se usa para eliminar una TodoItem instancia en el servicio Web.
Además, algunas aplicaciones de ejemplo contienen métodos adicionales en la TodoItemManager clase, que se
usan para administrar el proceso de autenticación del usuario.
En lugar de invocar directamente las operaciones de servicio Web, los TodoItemManager métodos invocan
métodos en una clase dependiente que se inserta en el TodoItemManager constructor. Por ejemplo, una aplicación
de ejemplo inyecta la RestService clase en el TodoItemManager constructor para proporcionar la implementación
que usa las API de REST para tener acceso a los datos.

Vínculos relacionados
ASMX (ejemplo)
WCF (ejemplo)
REST (ejemplo)
Consumo de un servicio web ASP.NET (ASMX)
18/12/2020 • 12 minutes to read • Edit Online

Descargar el ejemplo
ASMX proporciona la capacidad de compilar servicios web que envían mensajes mediante el Protocolo simple de
acceso a objetos (SOAP). SOAP es un protocolo independiente de la plataforma e independiente del lenguaje para
crear y obtener acceso a servicios Web. No es necesario que los consumidores de un servicio ASMX sepan nada
sobre la plataforma, el modelo de objetos o el lenguaje de programación usado para implementar el servicio. Solo
necesitan saber cómo enviar y recibir mensajes SOAP. En este artículo se muestra cómo consumir un servicio
SOAP de ASMX desde una :::no-loc(Xamarin.Forms)::: aplicación.
Un mensaje SOAP es un documento XML que contiene los elementos siguientes:
Un elemento raíz denominado Envelope que identifica el documento XML como un mensaje SOAP.
Un elemento de encabezado opcional que contiene información específica de la aplicación, como datos de
autenticación. Si el elemento de encabezado está presente, debe ser el primer elemento secundario del
elemento de sobre .
Un elemento Body necesario que contiene el mensaje SOAP destinado al destinatario.
Un elemento Fault opcional que se usa para indicar mensajes de error. Si el elemento Fault está presente, debe
ser un elemento secundario del elemento Body .
SOAP puede operar a través de varios protocolos de transporte, incluidos HTTP, SMTP, TCP y UDP. Sin embargo, un
servicio ASMX solo puede funcionar a través de HTTP. La plataforma Xamarin admite implementaciones estándar
de SOAP 1,1 a través de HTTP y esto incluye compatibilidad con muchas de las configuraciones estándar del
servicio ASMX.
En este ejemplo se incluyen las aplicaciones móviles que se ejecutan en dispositivos físicos o emulados, y un
servicio de ASMX que proporciona métodos para obtener, agregar, editar y eliminar datos. Cuando se ejecutan las
aplicaciones móviles, se conectan al servicio ASMX hospedado localmente, tal como se muestra en la siguiente
captura de pantalla:
NOTE
En iOS 9 y versiones posteriores, la seguridad de transporte de aplicaciones (ATS) exige conexiones seguras entre recursos de
Internet (como el servidor back-end de la aplicación) y la aplicación, lo que evita la divulgación accidental de información
confidencial. Dado que ATS está habilitado de forma predeterminada en las aplicaciones compiladas para iOS 9, todas las
conexiones estarán sujetas a los requisitos de seguridad de ATS. Si las conexiones no cumplen estos requisitos, se producirá
un error con una excepción. ATS puede no participar en si no es posible usar el HTTPS Protocolo y proteger la comunicación
de los recursos de Internet. Esto se puede lograr actualizando el archivo info. plist de la aplicación. Para obtener más
información, vea seguridad de transporte de aplicaciones.

Consumo del servicio web


El servicio ASMX proporciona las siguientes operaciones:

O P ERA C IÓ N DESC RIP C IÓ N PA RÁ M ET RO S

GetTodoItems Obtención de una lista de tareas


pendientes

CreateTodoItem Crear un nuevo elemento de tareas Un TodoItem serializado en XML


pendientes

EditTodoItem Actualizar una tarea pendiente Un TodoItem serializado en XML

DeleteTodoItem Eliminar una tarea pendiente Un TodoItem serializado en XML

Para obtener más información sobre el modelo de datos que se usa en la aplicación, vea modelar los datos.

Crear el proxy de TodoService


Una clase de proxy, denominada TodoService , extiende SoapHttpClientProtocol y proporciona métodos para
comunicarse con el servicio asmx a través de http. El proxy se genera agregando una referencia Web a cada
proyecto específico de la plataforma en Visual Studio 2019 o Visual Studio 2017. La referencia Web genera
métodos y eventos para cada acción definida en el documento del lenguaje de descripción de servicios web
(WSDL) del servicio.
Por ejemplo, la GetTodoItems acción de servicio produce un GetTodoItemsAsync método y un
GetTodoItemsCompleted evento en el proxy. El método generado tiene un tipo de valor devuelto void e invoca la
GetTodoItems acción en la SoapHttpClientProtocol clase primaria. Cuando el método invocado recibe una
respuesta del servicio, activa el GetTodoItemsCompleted evento y proporciona los datos de respuesta dentro de la
propiedad del evento Result .

Crear la implementación de ISoapService


Para permitir que el proyecto compartido y multiplataforma funcione con el servicio, el ejemplo define la
ISoapService interfaz, que sigue el modelo de programación asincrónica de tareas en C#. Cada plataforma
implementa ISoapService para exponer el proxy específico de la plataforma. En el ejemplo TaskCompletionSource
se usan objetos para exponer el proxy como una interfaz asincrónica de tarea. Los detalles sobre
TaskCompletionSource el uso de se encuentran en las implementaciones de cada tipo de acción en las secciones
siguientes.
El ejemplo SoapService :
1. Crea TodoService una instancia de como una instancia de nivel de clase.
2. Crea una colección denominada Items para almacenar TodoItem objetos.
3. Especifica un extremo personalizado para la Url propiedad opcional en el TodoService

public class SoapService : ISoapService


{
ASMXService.TodoService todoService;
public List<TodoItem> Items { get; private set; } = new List<TodoItem>();

public SoapService ()
{
todoService = new ASMXService.TodoService ();
todoService.Url = Constants.SoapUrl;
...
}
}

Crear objetos de transferencia de datos


La aplicación de ejemplo utiliza la TodoItem clase para modelar los datos. Para almacenar un TodoItem elemento
en el servicio Web, primero debe convertirse en el tipo generado por el proxy TodoItem . Esto se logra mediante el
ToASMXServiceTodoItem método, tal y como se muestra en el ejemplo de código siguiente:

ASMXService.TodoItem ToASMXServiceTodoItem (TodoItem item)


{
return new ASMXService.TodoItem {
ID = item.ID,
Name = item.Name,
Notes = item.Notes,
Done = item.Done
};
}

Este método crea una nueva ASMService.TodoItem instancia de y establece cada propiedad en la propiedad idéntica
de la TodoItem instancia de.
Del mismo modo, cuando se recuperan datos del servicio Web, se deben convertir del tipo generado por el proxy
TodoItem a una TodoItem instancia de. Esto se logra con el FromASMXServiceTodoItem método, como se muestra en
el ejemplo de código siguiente:

static TodoItem FromASMXServiceTodoItem (ASMXService.TodoItem item)


{
return new TodoItem {
ID = item.ID,
Name = item.Name,
Notes = item.Notes,
Done = item.Done
};
}

Este método recupera los datos del tipo generado por el proxy TodoItem y lo establece en la instancia que se
acaba de crear TodoItem .
Recuperación de datos
La ISoapService interfaz espera que el RefreshDataAsync método devuelva un Task con la colección de
elementos. Sin embargo, el TodoService.GetTodoItemsAsync método devuelve void. Para satisfacer el patrón de
interfaz, debe llamar a GetTodoItemsAsync , esperar a GetTodoItemsCompleted que se desencadene el evento y
rellenar la colección. Esto le permite devolver una colección válida a la interfaz de usuario.
En el ejemplo siguiente se crea un nuevo TaskCompletionSource , se inicia la llamada asincrónica en el
RefreshDataAsync método y se espera el Task proporcionado por TaskCompletionSource . Cuando
TodoService_GetTodoItemsCompleted se invoca el controlador de eventos, rellena la Items colección y actualiza
TaskCompletionSource :
public class SoapService : ISoapService
{
TaskCompletionSource<bool> getRequestComplete = null;
...

public SoapService()
{
...
todoService.GetTodoItemsCompleted += TodoService_GetTodoItemsCompleted;
}

public async Task<List<TodoItem>> RefreshDataAsync()


{
getRequestComplete = new TaskCompletionSource<bool>();
todoService.GetTodoItemsAsync();
await getRequestComplete.Task;
return Items;
}

private void TodoService_GetTodoItemsCompleted(object sender, ASMXService.GetTodoItemsCompletedEventArgs


e)
{
try
{
getRequestComplete = getRequestComplete ?? new TaskCompletionSource<bool>();

Items = new List<TodoItem>();


foreach (var item in e.Result)
{
Items.Add(FromASMXServiceTodoItem(item));
}
getRequestComplete?.TrySetResult(true);
}
catch (Exception ex)
{
Debug.WriteLine(@"\t\tERROR {0}", ex.Message);
}
}

...
}

Para obtener más información, vea modelo de programación asincrónica y TPL y la programación asincrónica .NET
Framework tradicional.
Crear o editar datos
Al crear o editar datos, debe implementar el ISoapService.SaveTodoItemAsync método. Este método detecta si
TodoItem es un elemento nuevo o actualizado y llama al método adecuado en el todoService objeto. Los
CreateTodoItemCompleted EditTodoItemCompleted controladores de eventos y también deben implementarse para
que sepa cuándo todoService ha recibido una respuesta del servicio asmx (estos se pueden combinar en un único
controlador porque realizan la misma operación). En el ejemplo siguiente se muestran las implementaciones de
interfaces y controladores de eventos, así como el TaskCompletionSource objeto que se usa para operar de forma
asincrónica:
public class SoapService : ISoapService
{
TaskCompletionSource<bool> saveRequestComplete = null;
...

public SoapService()
{
...
todoService.CreateTodoItemCompleted += TodoService_SaveTodoItemCompleted;
todoService.EditTodoItemCompleted += TodoService_SaveTodoItemCompleted;
}

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)


{
try
{
var todoItem = ToASMXServiceTodoItem(item);
saveRequestComplete = new TaskCompletionSource<bool>();
if (isNewItem)
{
todoService.CreateTodoItemAsync(todoItem);
}
else
{
todoService.EditTodoItemAsync(todoItem);
}
await saveRequestComplete.Task;
}
catch (SoapException se)
{
Debug.WriteLine("\t\t{0}", se.Message);
}
catch (Exception ex)
{
Debug.WriteLine("\t\tERROR {0}", ex.Message);
}
}

private void TodoService_SaveTodoItemCompleted(object sender,


System.ComponentModel.AsyncCompletedEventArgs e)
{
saveRequestComplete?.TrySetResult(true);
}

...
}

Eliminación de datos
La eliminación de datos requiere una implementación similar. Defina un TaskCompletionSource , implemente un
controlador de eventos y el ISoapService.DeleteTodoItemAsync método:
public class SoapService : ISoapService
{
TaskCompletionSource<bool> deleteRequestComplete = null;
...

public SoapService()
{
...
todoService.DeleteTodoItemCompleted += TodoService_DeleteTodoItemCompleted;
}

public async Task DeleteTodoItemAsync (string id)


{
try
{
deleteRequestComplete = new TaskCompletionSource<bool>();
todoService.DeleteTodoItemAsync(id);
await deleteRequestComplete.Task;
}
catch (SoapException se)
{
Debug.WriteLine("\t\t{0}", se.Message);
}
catch (Exception ex)
{
Debug.WriteLine("\t\tERROR {0}", ex.Message);
}
}

private void TodoService_DeleteTodoItemCompleted(object sender,


System.ComponentModel.AsyncCompletedEventArgs e)
{
deleteRequestComplete?.TrySetResult(true);
}

...
}

Prueba del servicio web


La prueba de dispositivos físicos o emulados con un servicio hospedado localmente requiere la configuración
personalizada de IIS, las direcciones de extremo y las reglas de Firewall. Para obtener más información sobre cómo
configurar el entorno para las pruebas, consulte configurar el acceso remoto a IIS Express. La única diferencia entre
probar WCF y ASMX es el número de puerto de TodoService.

Vínculos relacionados
TodoASMX (ejemplo)
IAsyncResult
Consumir un servicio Web de Windows
Communication Foundation (WCF)
18/12/2020 • 25 minutes to read • Edit Online

Descargar el ejemplo
WCF es el marco unificado de Microsoft para la creación de aplicaciones orientadas a servicios. Permite a los
desarrolladores compilar aplicaciones distribuidas seguras, con transacciones y de confianza, e interoperables. En
este artículo se muestra cómo consumir un servicio de Protocolo simple de acceso a objetos (SOAP) de WCF desde
una :::no-loc(Xamarin.Forms)::: aplicación de.
WCF describe un servicio con varios contratos diferentes, entre los que se incluyen:
Contratos de datos : defina las estructuras de datos que forman la base del contenido de un mensaje.
Contratos de mensajes : redacte mensajes a partir de contratos de datos existentes.
Contratos de error : permite especificar los errores de SOAP personalizados.
Contratos de ser vicio : especifique las operaciones que admiten los servicios y los mensajes necesarios para
interactuar con cada operación. También especifican cualquier comportamiento de error personalizado que se
puede asociar a las operaciones en cada servicio.
Hay diferencias entre los servicios Web de ASP.NET (ASMX) y WCF, pero WCF admite las mismas funcionalidades
que ASMX proporciona: mensajes SOAP a través de HTTP. Para obtener más información sobre cómo consumir un
servicio de ASMX, consulte consumo de servicios Web de ASP.net (asmx).

IMPORTANT
La compatibilidad con la plataforma de Xamarin para WCF se limita a los mensajes SOAP con codificación de texto a través de
HTTP/HTTPS mediante la BasicHttpBinding clase.
La compatibilidad con WCF requiere el uso de herramientas que solo están disponibles en un entorno de Windows para
generar el proxy y hospedar el TodoWCFService. Para compilar y probar la aplicación de iOS, será necesario implementar el
TodoWCFService en un equipo Windows o como un servicio Web de Azure.
Normalmente, las aplicaciones nativas de Xamarin Forms comparten código con una biblioteca de clases .NET Standard. Sin
embargo, .NET Core no es compatible actualmente con WCF, por lo que el proyecto compartido debe ser una biblioteca de
clases portable heredada. Para obtener información sobre la compatibilidad de WCF en .NET Core, vea elegir entre .net Core y
.NET Framework para aplicaciones de servidor.

La solución de aplicación de ejemplo incluye un servicio WCF que se puede ejecutar localmente y se muestra en la
siguiente captura de pantalla:
NOTE
En iOS 9 y versiones posteriores, la seguridad de transporte de aplicaciones (ATS) exige conexiones seguras entre recursos de
Internet (como el servidor back-end de la aplicación) y la aplicación, lo que evita la divulgación accidental de información
confidencial. Dado que ATS está habilitado de forma predeterminada en las aplicaciones compiladas para iOS 9, todas las
conexiones estarán sujetas a los requisitos de seguridad de ATS. Si las conexiones no cumplen estos requisitos, se producirá
un error con una excepción.
ATS puede no participar en si no es posible usar el HTTPS Protocolo y proteger la comunicación de los recursos de Internet.
Esto se puede lograr actualizando el archivo info. plist de la aplicación. Para obtener más información, vea seguridad de
transporte de aplicaciones.

Consumo del servicio web


El servicio WCF proporciona las siguientes operaciones:

O P ERA C IÓ N DESC RIP C IÓ N PA RÁ M ET RO S

GetTodoItems Obtención de una lista de tareas


pendientes

CreateTodoItem Crear un nuevo elemento de tareas Un TodoItem serializado en XML


pendientes

EditTodoItem Actualizar una tarea pendiente Un TodoItem serializado en XML

DeleteTodoItem Eliminar una tarea pendiente Un TodoItem serializado en XML


Para obtener más información sobre el modelo de datos que se usa en la aplicación, vea modelar los datos.
Se debe generar un proxy para consumir un servicio WCF, lo que permite que la aplicación se conecte al servicio. El
proxy se construye mediante el consumo de metadatos de servicio que definen los métodos y la configuración de
servicio asociada. Estos metadatos se exponen en forma de un documento de lenguaje de descripción de servicios
web (WSDL) generado por el servicio Web. El proxy se puede compilar mediante el Microsoft WCF Web Service
Reference Provider en Visual Studio 2017 para agregar una referencia de servicio para el servicio Web a una
biblioteca de .NET Standard. Una alternativa a la creación del proxy mediante el Microsoft WCF Web Service
Reference Provider en Visual Studio 2017 es usar la herramienta de utilidad de metadatos de ServiceModel
(svcutil.exe). Para obtener más información, vea herramienta de utilidad de metadatos de ServiceModel
(Svcutil.exe).
Las clases de proxy generadas proporcionan métodos para consumir los servicios web que usan el modelo de
diseño del modelo de programación asincrónica (APM). En este patrón, una operación asincrónica se implementa
como dos métodos denominados BeginOperationName y EndOperationName , que comienzan y finalizan la
operación asincrónica.
El método BeginOperationName comienza la operación asincrónica y devuelve un objeto que implementa la
IAsyncResult interfaz. Después de llamar a BeginOperationName , una aplicación puede seguir ejecutando
instrucciones en el subproceso que realiza la llamada, mientras que la operación asincrónica tiene lugar en un
subproceso del grupo de subprocesos.
Para cada llamada a BeginOperationName , la aplicación también debe llamar a EndOperationName para obtener
los resultados de la operación. El valor devuelto de EndOperationName es el mismo tipo devuelto por el método
de servicio Web sincrónico. Por ejemplo, el EndGetTodoItems método devuelve una colección de TodoItem
instancias de. El método EndOperationName también incluye un IAsyncResult parámetro que debe establecerse
en la instancia devuelta por la llamada correspondiente al método BeginOperationName .
La biblioteca TPL (Task Parallel Library) puede simplificar el proceso de consumo de un par de métodos Begin/end
de APM encapsulando las operaciones asincrónicas en el mismo Task objeto. Esta encapsulación la proporcionan
varias sobrecargas del TaskFactory.FromAsync método.
Para obtener más información acerca de APM, vea modelo de programación asincrónica y TPL y la programación
asincrónica .NET Framework tradicional en MSDN.
Crear el objeto TodoServiceClient
La clase de proxy generada proporciona la TodoServiceClient clase, que se usa para comunicarse con el servicio
WCF a través de http. Proporciona funcionalidad para invocar métodos de servicio web como operaciones
asincrónicas de una instancia de servicio identificada por un identificador URI. Para obtener más información
acerca de las operaciones asincrónicas, vea información general sobre la compatibilidad con Async.
La TodoServiceClient instancia de se declara en el nivel de clase para que el objeto se encuentre siempre y cuando
la aplicación deba consumir el servicio WCF, como se muestra en el ejemplo de código siguiente:

public class SoapService : ISoapService


{
ITodoService todoService;
...

public SoapService ()
{
todoService = new TodoServiceClient (
new BasicHttpBinding (),
new EndpointAddress (Constants.SoapUrl));
}
...
}
La TodoServiceClient instancia se configura con información de enlace y una dirección de extremo. Un enlace se
utiliza para especificar los detalles de transporte, codificación y protocolo necesarios para que las aplicaciones y los
servicios se comuniquen entre sí. BasicHttpBinding Especifica que los mensajes SOAP codificados por texto se
enviarán a través del Protocolo de transporte http. La especificación de una dirección de extremo permite que la
aplicación se conecte a diferentes instancias del servicio WCF, siempre que haya varias instancias publicadas.
Para obtener más información sobre la configuración de la referencia de servicio, consulte configuración de la
referencia de servicio.
Crear objetos de transferencia de datos
La aplicación de ejemplo utiliza la TodoItem clase para modelar los datos. Para almacenar un TodoItem elemento
en el servicio Web, primero debe convertirse en el tipo generado por el proxy TodoItem . Esto se logra mediante el
ToWCFServiceTodoItem método, tal y como se muestra en el ejemplo de código siguiente:

TodoWCFService.TodoItem ToWCFServiceTodoItem (TodoItem item)


{
return new TodoWCFService.TodoItem
{
ID = item.ID,
Name = item.Name,
Notes = item.Notes,
Done = item.Done
};
}

Este método simplemente crea una nueva TodoWCFService.TodoItem instancia de y establece cada propiedad en la
propiedad idéntica de la TodoItem instancia de.
Del mismo modo, cuando se recuperan datos del servicio Web, se deben convertir del tipo generado por el proxy
TodoItem a una TodoItem instancia de. Esto se logra con el FromWCFServiceTodoItem método, como se muestra en
el ejemplo de código siguiente:

static TodoItem FromWCFServiceTodoItem (TodoWCFService.TodoItem item)


{
return new TodoItem
{
ID = item.ID,
Name = item.Name,
Notes = item.Notes,
Done = item.Done
};
}

Este método simplemente recupera los datos del tipo generado por el proxy TodoItem y los establece en la
instancia recién creada TodoItem .
Recuperación de datos
Los TodoServiceClient.BeginGetTodoItems TodoServiceClient.EndGetTodoItems métodos y se usan para llamar a la
GetTodoItems operación proporcionada por el servicio Web. Estos métodos asincrónicos se encapsulan en un
Task objeto, tal y como se muestra en el ejemplo de código siguiente:
public async Task<List<TodoItem>> RefreshDataAsync ()
{
...
var todoItems = await Task.Factory.FromAsync <ObservableCollection<TodoWCFService.TodoItem>> (
todoService.BeginGetTodoItems,
todoService.EndGetTodoItems,
null,
TaskCreationOptions.None);

foreach (var item in todoItems)


{
Items.Add (FromWCFServiceTodoItem (item));
}
...
}

El Task.Factory.FromAsync método crea un Task que ejecuta el TodoServiceClient.EndGetTodoItems método una


vez que TodoServiceClient.BeginGetTodoItems se completa el método, con el null parámetro que indica que no se
pasa ningún dato al BeginGetTodoItems delegado. Por último, el valor de la TaskCreationOptions enumeración
especifica que se debe usar el comportamiento predeterminado para la creación y ejecución de tareas.
El TodoServiceClient.EndGetTodoItems método devuelve una ObservableCollection TodoWCFService.TodoItem
instancia de, que se convierte a continuación en List una TodoItem instancia de para su presentación.
Crear datos
Los TodoServiceClient.BeginCreateTodoItem TodoServiceClient.EndCreateTodoItem métodos y se usan para llamar a
la CreateTodoItem operación proporcionada por el servicio Web. Estos métodos asincrónicos se encapsulan en un
Task objeto, tal y como se muestra en el ejemplo de código siguiente:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)


{
...
var todoItem = ToWCFServiceTodoItem (item);
...
await Task.Factory.FromAsync (
todoService.BeginCreateTodoItem,
todoService.EndCreateTodoItem,
todoItem,
TaskCreationOptions.None);
...
}

El Task.Factory.FromAsync método crea un Task que ejecuta el TodoServiceClient.EndCreateTodoItem método una


vez que se TodoServiceClient.BeginCreateTodoItem completa el método, con el todoItem parámetro en los datos
que se pasan al BeginCreateTodoItem delegado para especificar el TodoItem que va a crear el servicio Web. Por
último, el valor de la TaskCreationOptions enumeración especifica que se debe usar el comportamiento
predeterminado para la creación y ejecución de tareas.
El servicio Web inicia una excepción FaultException si no puede crear el TodoItem control, que es controlado por
la aplicación.
Actualización de datos
Los TodoServiceClient.BeginEditTodoItem TodoServiceClient.EndEditTodoItem métodos y se usan para llamar a la
EditTodoItem operación proporcionada por el servicio Web. Estos métodos asincrónicos se encapsulan en un
Task objeto, tal y como se muestra en el ejemplo de código siguiente:
public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
...
var todoItem = ToWCFServiceTodoItem (item);
...
await Task.Factory.FromAsync (
todoService.BeginEditTodoItem,
todoService.EndEditTodoItem,
todoItem,
TaskCreationOptions.None);
...
}

El Task.Factory.FromAsync método crea un Task que ejecuta el TodoServiceClient.EndEditTodoItem método una


vez que TodoServiceClient.BeginCreateTodoItem se completa el método, con el todoItem parámetro en los datos
que se pasan al BeginEditTodoItem delegado para especificar el TodoItem que va a ser actualizado por el servicio
Web. Por último, el valor de la TaskCreationOptions enumeración especifica que se debe usar el comportamiento
predeterminado para la creación y ejecución de tareas.
El servicio Web inicia una excepción FaultException si no puede localizar o actualizar el TodoItem control, que es
controlado por la aplicación.
Eliminación de datos
Los TodoServiceClient.BeginDeleteTodoItem TodoServiceClient.EndDeleteTodoItem métodos y se usan para llamar a
la DeleteTodoItem operación proporcionada por el servicio Web. Estos métodos asincrónicos se encapsulan en un
Task objeto, tal y como se muestra en el ejemplo de código siguiente:

public async Task DeleteTodoItemAsync (string id)


{
...
await Task.Factory.FromAsync (
todoService.BeginDeleteTodoItem,
todoService.EndDeleteTodoItem,
id,
TaskCreationOptions.None);
...
}

El Task.Factory.FromAsync método crea un Task que ejecuta el TodoServiceClient.EndDeleteTodoItem método una


vez que se TodoServiceClient.BeginDeleteTodoItem completa el método, con el id parámetro en los datos que se
pasan al BeginDeleteTodoItem delegado para especificar el TodoItem que va a eliminar el servicio Web. Por último,
el valor de la TaskCreationOptions enumeración especifica que se debe usar el comportamiento predeterminado
para la creación y ejecución de tareas.
El servicio Web inicia una excepción FaultException si no puede localizar o eliminar TodoItem , que es controlada
por la aplicación.

Configurar el acceso remoto a IIS Express


En Visual Studio 2017 o Visual Studio 2019, debería poder probar la aplicación de UWP en un equipo sin ninguna
configuración adicional. La prueba de clientes iOS y Android puede requerir los pasos adicionales de esta sección.
Consulte conexión a servicios web locales desde simuladores de iOS y emuladores de Android para obtener más
información.
De forma predeterminada, IIS Express solo responderá a las solicitudes a localhost . Los dispositivos remotos
(como un dispositivo Android, un iPhone o incluso un simulador) no tendrán acceso a su servicio WCF local. Debe
conocer la dirección IP de la estación de trabajo de Windows 10 en la red local. En este ejemplo, supongamos que
la estación de trabajo tiene la dirección IP 192.168.1.143 . En los pasos siguientes se explica cómo configurar
Windows 10 y IIS Express para aceptar conexiones remotas y conectarse al servicio desde un dispositivo físico o
virtual:
1. Agregue una excepción al firewall de Windows . Debe abrir un puerto a través del firewall de Windows
que las aplicaciones de la subred pueden utilizar para comunicarse con el servicio WCF. Cree una regla de
entrada que abra el puerto 49393 en el firewall. En un símbolo del sistema administrativo, ejecute este
comando:

netsh advfirewall firewall add rule name="TodoWCFService" dir=in protocol=tcp localport=49393


profile=private remoteip=localsubnet action=allow

2. Configure IIS Express para aceptar conexiones remotas . Puede configurar IIS Express editando el
archivo de configuración de IIS Express en [directorio de la solución]
.vs\config\applicationhost.config . Busque el site elemento con el nombre TodoWCFService . Debe ser
similar al siguiente XML:

<site name="TodoWCFService" id="2">


<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:49393:localhost" />
</bindings>
</site>

Tendrá que agregar dos binding elementos para abrir el puerto 49393 para el tráfico externo y el emulador
de Android. El enlace usa un [IP address]:[port]:[hostname] formato que especifica el modo en que IIS
Express responderá a las solicitudes. Las solicitudes externas tendrán nombres de host que se deben
especificar como binding . Agregue el siguiente código XML al bindings elemento, reemplazando la
dirección IP por su propia dirección IP:

<binding protocol="http" bindingInformation="*:49393:192.168.1.143" />


<binding protocol="http" bindingInformation="*:49393:127.0.0.1" />

Después de los cambios, el bindings elemento debe tener un aspecto similar al siguiente:

<site name="TodoWCFService" id="2">


<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:49393:localhost" />
<binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
<binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
</bindings>
</site>

IMPORTANT
De forma predeterminada, IIS Express no aceptará conexiones de orígenes externos por motivos de seguridad. Para
habilitar las conexiones desde dispositivos remotos, debe ejecutar IIS Express con permisos administrativos. La forma
más fácil de hacerlo es ejecutar Visual Studio 2017 con permisos administrativos. Esto iniciará IIS Express con
permisos administrativos al ejecutar TodoWCFService.
Una vez completados estos pasos, debería poder ejecutar el TodoWCFService y conectarse desde otros
dispositivos de la subred. Para probar esto, ejecute la aplicación y visite
http://localhost:49393/TodoService.svc . Si recibe un error de Solicitud incorrecta al visitar la dirección
URL, bindings puede que la configuración de IIS Express sea incorrecta (la solicitud está llegando IIS
Express pero se está rechazando). Si obtiene un error diferente, puede que la aplicación no se esté
ejecutando o que el Firewall esté configurado incorrectamente.
Para permitir que IIS Express siga ejecutando y atendiendo el servicio, desactive la opción Editar y
continuar en las propiedades del proyecto > depuradores de > web .
3. Personalice los dispositivos de extremo que se usan para tener acceso al ser vicio . Este paso
implica la configuración de la aplicación cliente, que se ejecuta en un dispositivo físico o emulado, para tener
acceso al servicio WCF.
El emulador de Android emplea un proxy interno que impide que el emulador acceda directamente a la
dirección del equipo host localhost . En su lugar, la dirección 10.0.2.2 en el emulador se enruta a
localhost en el equipo host a través de un proxy interno. Estas solicitudes de proxy tendrán 127.0.0.1
como nombre de host en el encabezado de solicitud, por lo que ha creado el enlace de IIS Express para este
nombre de host en los pasos anteriores.
El simulador de iOS se ejecuta en un host de compilación de Mac, incluso si usa el simulador de iOS remoto
para Windows. Las solicitudes de red del simulador tendrán la dirección IP de la estación de trabajo en la red
local como nombre de host (en este ejemplo 192.168.1.143 , es, pero es probable que la dirección IP real
sea diferente). Este es el motivo por el que creó el enlace de IIS Express para este nombre de host en los
pasos anteriores.
Asegúrese de SoapUrl que la propiedad del archivo constants.CS del proyecto TodoWCF (portable) tenga
valores que sean correctos para la red:

public static string SoapUrl


{
get
{
var defaultUrl = "http://localhost:49393/TodoService.svc";

if (Device.RuntimePlatform == Device.Android)
{
defaultUrl = "http://10.0.2.2:49393/TodoService.svc";
}
else if (Device.RuntimePlatform == Device.iOS)
{
defaultUrl = "http://192.168.1.143:49393/TodoService.svc";
}

return defaultUrl;
}
}

Una vez que haya configurado el constants.CS con los puntos de conexión adecuados, podrá conectarse a
la TodoWCFService que se ejecuta en la estación de trabajo de Windows 10 desde dispositivos físicos o
virtuales.

Vínculos relacionados
TodoWCF (ejemplo)
Creación de un cliente de Windows Communication Foundation
Herramienta de utilidad de metadatos de ServiceModel (svcutil.exe)
Consumo de un servicio web RESTful
18/12/2020 • 15 minutes to read • Edit Online

Descargar el ejemplo
La integración de un servicio Web en una aplicación es un escenario común. En este artículo se muestra cómo
consumir un servicio web RESTful desde una :::no-loc(Xamarin.Forms)::: aplicación.
La transferencia de estado representacional (REST) es un estilo arquitectónico para la creación de servicios Web.
Las solicitudes REST se realizan a través de HTTP usando los mismos verbos HTTP que los exploradores web
utilizan para recuperar páginas web y enviar datos a los servidores. Los verbos son:
Get : esta operación se usa para recuperar datos del servicio Web.
Post : esta operación se usa para crear un nuevo elemento de datos en el servicio Web.
Put : esta operación se usa para actualizar un elemento de datos en el servicio Web.
Patch : esta operación se usa para actualizar un elemento de datos en el servicio Web mediante la descripción
de un conjunto de instrucciones sobre cómo se debe modificar el elemento. Este verbo no se utiliza en la
aplicación de ejemplo.
Delete : esta operación se usa para eliminar un elemento de datos en el servicio Web.
Las API de servicios web que se adhieren a REST se denominan API de RESTful y se definen mediante:
Identificador URI base.
Métodos HTTP, como GET, POST, PUT, PATCH o DELETE.
Un tipo de medio para los datos, como notación de objetos JavaScript (JSON).
Los servicios web RESTful normalmente usan mensajes JSON para devolver datos al cliente. JSON es un formato
de intercambio de datos basado en texto que genera cargas compactos, lo que reduce los requisitos de ancho de
banda cuando se envían datos. La aplicación de ejemplo usa la biblioteca de NewtonSoft JSON.net de código
abierto para serializar y deserializar mensajes.
La simplicidad de REST ha ayudado a convertirlo en el método principal para tener acceso a los servicios web en
aplicaciones móviles.
Cuando se ejecute la aplicación de ejemplo, se conectará a un servicio REST hospedado localmente, como se
muestra en la siguiente captura de pantalla:
NOTE
En iOS 9 y versiones posteriores, la seguridad de transporte de aplicaciones (ATS) exige conexiones seguras entre recursos de
Internet (como el servidor back-end de la aplicación) y la aplicación, lo que evita la divulgación accidental de información
confidencial. Dado que ATS está habilitado de forma predeterminada en las aplicaciones compiladas para iOS 9, todas las
conexiones estarán sujetas a los requisitos de seguridad de ATS. Si las conexiones no cumplen estos requisitos, se producirá
un error con una excepción.
ATS puede no participar en si no es posible usar el protocolo https y proteger la comunicación para los recursos de Internet.
Esto se puede lograr actualizando el archivo info. plist de la aplicación. Para obtener más información, vea seguridad de
transporte de aplicaciones.

Consumo del servicio Web


El servicio REST se escribe utilizando ASP.NET Core y proporciona las operaciones siguientes:

O P ERA C IÓ N H T T P M ET H O D URI REL AT IVO PA RÁ M ET RO S

Obtención de una lista de GET /api/todoitems/


tareas pendientes

Crear un nuevo elemento de POST /api/todoitems/ Un TodoItem con formato


tareas pendientes JSON

Actualizar una tarea PUT /api/todoitems/ Un TodoItem con formato


pendiente JSON

Eliminar una tarea pendiente Delete /api/todoitems/{id}

La mayoría de los URI incluyen el TodoItem identificador de la ruta de acceso. Por ejemplo, para eliminar el
TodoItem cuyo identificador es 6bb8a868-dba1-4f1a-93b7-24ebce87e243 , el cliente envía una solicitud DELETE a
http://hostname/api/todoitems/6bb8a868-dba1-4f1a-93b7-24ebce87e243 . Para obtener más información sobre el
modelo de datos utilizado en la aplicación de ejemplo, vea modelar los datos.
Cuando el marco de la API Web recibe una solicitud, enruta la solicitud a una acción. Estas acciones son
simplemente métodos públicos en la TodoItemsController clase. El marco de trabajo utiliza una tabla de
enrutamiento para determinar qué acción se debe invocar en respuesta a una solicitud, que se muestra en el
ejemplo de código siguiente:

config.Routes.MapHttpRoute(
name: "TodoItemsApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller="todoitems", id = RouteParameter.Optional }
);

La tabla de enrutamiento contiene una plantilla de ruta y, cuando el marco de la API Web recibe una solicitud HTTP,
intenta hacer coincidir el URI con la plantilla de ruta de la tabla de enrutamiento. Si no se encuentra una ruta
coincidente, el cliente recibe un error 404 (no encontrado). Si se encuentra una ruta coincidente, la API Web
selecciona el controlador y la acción como se indica a continuación:
Para encontrar el controlador, la API Web agrega "Controller" al valor de la variable {Controller} .
Para encontrar la acción, la API Web examina el método HTTP y examina las acciones del controlador que se
representan con el mismo método HTTP que un atributo.
La variable de marcador de posición {ID} está asignada a un parámetro de acción.
El servicio REST utiliza la autenticación básica. Para obtener más información, consulte autenticación de un servicio
web RESTful. Para obtener más información sobre el enrutamiento de ASP.NET Web API, consulte enrutamiento en
ASP.net web API en el sitio web de ASP.net. Para obtener más información sobre cómo compilar el servicio REST
mediante ASP.NET Core, vea crear servicios back-end para aplicaciones móviles nativas.
La HttpClient clase se utiliza para enviar y recibir solicitudes a través de http. Proporciona funcionalidad para
enviar solicitudes HTTP y recibir respuestas HTTP de un recurso identificado por un URI. Cada solicitud se envía
como una operación asincrónica. Para obtener más información acerca de las operaciones asincrónicas, vea
información general sobre la compatibilidad con Async.
La HttpResponseMessage clase representa un mensaje de respuesta http recibido del servicio Web después de que
se haya realizado una solicitud HTTP. Contiene información sobre la respuesta, incluido el código de estado, los
encabezados y cualquier cuerpo. La HttpContent clase representa el cuerpo http y los encabezados de contenido,
como Content-Type y Content-Encoding . El contenido se puede leer mediante cualquiera de los ReadAs métodos,
como ReadAsStringAsync y ReadAsByteArrayAsync , dependiendo del formato de los datos.
Creación del objeto HTTPClient
La HttpClient instancia de se declara en el nivel de clase para que el objeto se encuentre siempre que la
aplicación necesite hacer solicitudes HTTP, como se muestra en el ejemplo de código siguiente:

public class RestService : IRestService


{
HttpClient client;
...

public RestService ()
{
client = new HttpClient ();
}
...
}

Recuperación de datos
El HttpClient.GetAsync método se usa para enviar la solicitud GET al servicio Web especificado por el URI y, a
continuación, recibir la respuesta del servicio Web, como se muestra en el ejemplo de código siguiente:
public async Task<List<TodoItem>> RefreshDataAsync ()
{
...
Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, string.Empty));
...
HttpResponseMessage response = await client.GetAsync (uri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync ();
Items = JsonConvert.DeserializeObject <List<TodoItem>> (content);
}
...
}

El servicio REST envía un código de Estado HTTP en la HttpResponseMessage.IsSuccessStatusCode propiedad para


indicar si la solicitud HTTP se ha realizado correctamente o no. Para esta operación, el servicio REST envía el
código de Estado HTTP 200 (OK) en la respuesta, que indica que la solicitud se realizó correctamente y que la
información solicitada se encuentra en la respuesta.
Si la operación HTTP se realizó correctamente, se lee el contenido de la respuesta para su presentación. La
HttpResponseMessage.Content propiedad representa el contenido de la respuesta HTTP y el
HttpContent.ReadAsStringAsync método escribe de forma asincrónica el contenido http en una cadena. A
continuación, este contenido se convierte de JSON a un List de TodoItem instancias de.

WARNING
El uso del ReadAsStringAsync método para recuperar una respuesta grande puede tener un impacto negativo en el
rendimiento. En tales circunstancias, la respuesta se debe deserializar directamente para evitar tener que almacenarla
completamente en búfer.

Crear datos
El HttpClient.PostAsync método se usa para enviar la solicitud post al servicio Web especificado por el URI y, a
continuación, para recibir la respuesta del servicio Web, como se muestra en el ejemplo de código siguiente:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)


{
Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, string.Empty));

...
string json = JsonConvert.SerializeObject (item);
StringContent content = new StringContent (json, Encoding.UTF8, "application/json");

HttpResponseMessage response = null;


if (isNewItem)
{
response = await client.PostAsync (uri, content);
}
...

if (response.IsSuccessStatusCode)
{
Debug.WriteLine (@"\tTodoItem successfully saved.");
}
...
}

La TodoItem instancia se convierte en una carga JSON para enviarla al servicio Web. Esta carga se inserta
entonces en el cuerpo del contenido HTTP que se enviará al servicio Web antes de que se realice la solicitud con el
PostAsync método.
El servicio REST envía un código de Estado HTTP en la HttpResponseMessage.IsSuccessStatusCode propiedad para
indicar si la solicitud HTTP se ha realizado correctamente o no. Las respuestas comunes para esta operación son:
201 (creado) : la solicitud dio lugar a la creación de un nuevo recurso antes de que se enviara la respuesta.
400 (solicitud incorrecta) : el servidor no entiende la solicitud.
409 (conflicto) : no se pudo realizar la solicitud debido a un conflicto en el servidor.
Actualización de datos
El HttpClient.PutAsync método se usa para enviar la solicitud Put al servicio Web especificado por el URI y, a
continuación, recibir la respuesta del servicio Web, como se muestra en el ejemplo de código siguiente:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)


{
...
response = await client.PutAsync (uri, content);
...
}

La operación del PutAsync método es idéntica al PostAsync método que se usa para crear los datos en el servicio
Web. Sin embargo, las posibles respuestas enviadas desde el servicio Web difieren.
El servicio REST envía un código de Estado HTTP en la HttpResponseMessage.IsSuccessStatusCode propiedad para
indicar si la solicitud HTTP se ha realizado correctamente o no. Las respuestas comunes para esta operación son:
204 (sin contenido) : la solicitud se ha procesado correctamente y la respuesta está en blanco
intencionadamente.
400 (solicitud incorrecta) : el servidor no entiende la solicitud.
404 (no encontrado) : el recurso solicitado no existe en el servidor.
Eliminar datos
El HttpClient.DeleteAsync método se usa para enviar la solicitud de eliminación al servicio Web especificado por
el URI y, a continuación, recibir la respuesta del servicio Web, como se muestra en el ejemplo de código siguiente:

public async Task DeleteTodoItemAsync (string id)


{
Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, id));
...
HttpResponseMessage response = await client.DeleteAsync (uri);
if (response.IsSuccessStatusCode)
{
Debug.WriteLine (@"\tTodoItem successfully deleted.");
}
...
}

El servicio REST envía un código de Estado HTTP en la HttpResponseMessage.IsSuccessStatusCode propiedad para


indicar si la solicitud HTTP se ha realizado correctamente o no. Las respuestas comunes para esta operación son:
204 (sin contenido) : la solicitud se ha procesado correctamente y la respuesta está en blanco
intencionadamente.
400 (solicitud incorrecta) : el servidor no entiende la solicitud.
404 (no encontrado) : el recurso solicitado no existe en el servidor.

Vínculos relacionados
Creación de servicios back-end para aplicaciones móviles nativas
TodoREST (ejemplo)
HttpClient
Xamarin.FormsAutenticación de servicio Web
18/12/2020 • 2 minutes to read • Edit Online

Autenticación de un servicio web RESTful


HTTP admite el uso de varios mecanismos de autenticación para controlar el acceso a los recursos. La autenticación
básica solo proporciona acceso a los recursos a los clientes que tienen las credenciales correctas. En este artículo se
explica cómo usar la autenticación básica para proteger el acceso a los recursos del servicio web RESTful.

Autenticación de usuarios con Azure Active Directory B2C


Azure Active Directory B2C es una solución de administración de identidades en la nube, destinada a aplicaciones
móviles y web orientadas al consumidor. En este artículo se explica cómo usar la biblioteca de autenticación de
Microsoft (MSAL) y Azure Active Directory B2C para integrar la administración de identidades de consumidor en
una Xamarin.Forms aplicación.

Autentique a los usuarios con una base de datos de documentos Azure


Cosmos DB yXamarin.Forms
Azure Cosmos DB bases de datos de documentos admiten colecciones con particiones, que pueden abarcar varios
servidores y particiones, a la vez que admiten un almacenamiento y un rendimiento ilimitados. En este artículo se
explica cómo combinar el control de acceso con colecciones con particiones, de modo que un usuario solo pueda
tener acceso a sus propios documentos en una Xamarin.Forms aplicación.
Autenticación de un servicio web RESTful
18/12/2020 • 6 minutes to read • Edit Online

HTTP admite el uso de varios mecanismos de autenticación para controlar el acceso a los recursos. La
autenticación básica solo proporciona acceso a los recursos a los clientes que tienen las credenciales correctas. En
este artículo se muestra cómo usar la autenticación básica para proteger el acceso a los recursos del servicio web
RESTful.

NOTE
En iOS 9 y versiones posteriores, la seguridad de transporte de aplicaciones (ATS) exige conexiones seguras entre recursos de
Internet (como el servidor back-end de la aplicación) y la aplicación, lo que evita la divulgación accidental de información
confidencial. Dado que ATS está habilitado de forma predeterminada en las aplicaciones compiladas para iOS 9, todas las
conexiones estarán sujetas a los requisitos de seguridad de ATS. Si las conexiones no cumplen estos requisitos, se producirá
un error con una excepción. ATS puede no participar en si no es posible usar el HTTPS Protocolo y proteger la
comunicación de los recursos de Internet. Esto se puede lograr actualizando el archivo info. plist de la aplicación. Para
obtener más información, vea seguridad de transporte de aplicaciones.

Autenticación de usuarios a través de HTTP


La autenticación básica es el mecanismo de autenticación más sencillo admitido por HTTP y implica que el cliente
envíe el nombre de usuario y la contraseña como texto codificado en Base64 sin cifrar. Funciona como se indica a
continuación:
Si un servicio web recibe una solicitud de un recurso protegido, rechaza la solicitud con un código de Estado
HTTP 401 (acceso denegado) y establece el encabezado de respuesta WWW-Authenticate, tal como se muestra
en el diagrama siguiente:

Si un servicio web recibe una solicitud de un recurso protegido, con el Authorization encabezado establecido
correctamente, el servicio web responde con un código de Estado HTTP 200, lo que indica que la solicitud se ha
realizado correctamente y que la información solicitada se encuentra en la respuesta. Este escenario se muestra
en el diagrama siguiente:
NOTE
La autenticación básica solo se debe usar a través de una conexión HTTPS. Cuando se usa a través de una conexión HTTP, el
Authorization encabezado se puede descodificar fácilmente si un atacante captura el tráfico http.

Especificación de la autenticación básica en una solicitud Web


El uso de la autenticación básica se especifica de la siguiente manera:
1. La cadena "Basic" se agrega al Authorization encabezado de la solicitud.
2. El nombre de usuario y la contraseña se combinan en una cadena con el formato "nombre de usuario:
contraseña", que se codifica en Base64 y se agrega al Authorization encabezado de la solicitud.
Por lo tanto, con un nombre de usuario de "XamarinUser" y una contraseña de "XamarinPassword", el encabezado
se convierte en:

Authorization: Basic WGFtYXJpblVzZXI6WGFtYXJpblBhc3N3b3Jk

La HttpClient clase puede establecer el valor del encabezado en la


Authorization
HttpClient.DefaultRequestHeaders.Authorization propiedad. Dado que la HttpClient instancia existe en varias
solicitudes, el Authorization encabezado solo tiene que establecerse una vez, en lugar de hacerlo al realizar cada
solicitud, como se muestra en el ejemplo de código siguiente:

public class RestService : IRestService


{
HttpClient _client;
...

public RestService ()
{
var authData = string.Format ("{0}:{1}", Constants.Username, Constants.Password);
var authHeaderValue = Convert.ToBase64String (Encoding.UTF8.GetBytes (authData));

_client = new HttpClient ();


_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Basic", authHeaderValue);
}
...
}

Después, cuando se realiza una solicitud a una operación de servicio Web, la solicitud se firma con el
Authorization encabezado, que indica si el usuario tiene permiso para invocar la operación.

IMPORTANT
Aunque este código almacena las credenciales como constantes, no deben almacenarse en un formato no seguro en una
aplicación publicada.

Procesar el lado servidor del encabezado de autorización


El servicio REST debe decorar cada acción con el [BasicAuthentication] atributo. Este atributo se usa para analizar
el Authorization encabezado y determinar si las credenciales codificadas en Base64 son válidas comparándolos
con los valores almacenados en Web.config. Aunque este enfoque es adecuado para un servicio de ejemplo, debe
extenderse para un servicio Web de acceso público.
En el módulo de autenticación básica que usa IIS, los usuarios se autentican con sus credenciales de Windows. Por
lo tanto, los usuarios deben tener cuentas en el dominio del servidor. Sin embargo, el modelo de autenticación
básica puede configurarse para permitir la autenticación personalizada, donde las cuentas de usuario se autentican
en un origen externo, como una base de datos de. Para obtener más información, vea autenticación básica en
ASP.net web API en el sitio web de ASP.net.

NOTE
La autenticación básica no se diseñó para administrar el registro. Por lo tanto, el método de autenticación básica estándar
para cerrar sesión es finalizar la sesión.

Vínculos relacionados
Consumo de un servicio web RESTful
HttpClient
Autenticación de usuarios con Azure Active Directory
B2C
18/12/2020 • 18 minutes to read • Edit Online

Descargar el ejemplo
Azure Active Directory B2C proporciona administración de identidades en la nube para aplicaciones web y móviles
orientadas al consumidor. En este artículo se muestra cómo usar Azure Active Directory B2C para integrar la
administración de identidades en una aplicación móvil con la biblioteca de autenticación de Microsoft.

Información general
Azure Active Directory B2C (ADB2C) es un servicio de administración de identidades para aplicaciones orientadas
al consumidor. Permite a los usuarios iniciar sesión en su aplicación mediante sus cuentas de redes sociales
existentes o credenciales personalizadas, como el correo electrónico o el nombre de usuario, y la contraseña. Las
cuentas de credenciales personalizadas se conocen como cuentas locales .
El proceso para integrar el servicio de administración de identidades de Azure Active Directory B2C en una
aplicación móvil es el siguiente:
1. Cree un inquilino de Azure Active Directory B2C.
2. Registre la aplicación móvil con el inquilino de Azure Active Directory B2C.
3. Cree directivas para el registro y el inicio de sesión y olvidó los flujos de usuario de contraseñas.
4. Use la biblioteca de autenticación de Microsoft (MSAL) para iniciar un flujo de trabajo de autenticación con su
inquilino de Azure Active Directory B2C.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Azure Active Directory B2C admite varios proveedores de identidades, como Microsoft, GitHub, Facebook, Twitter,
etc. Para obtener más información sobre las funcionalidades de Azure Active Directory B2C, consulte la
documentación de Azure Active Directory B2C.
La biblioteca de autenticación de Microsoft admite varias arquitecturas de aplicaciones y plataformas. Para obtener
información sobre las funcionalidades de MSAL, consulte biblioteca de autenticación de Microsoft en github.

Configuración de un inquilino de Azure Active Directory B2C


Para ejecutar el proyecto de ejemplo, debe crear un inquilino de Azure Active Directory B2C. Para obtener más
información, consulte crear un inquilino de Azure Active Directory B2C en el Azure portal.
Una vez que cree un inquilino, necesitará el nombre de inquilino y el identificador de inquilino para
configurar la aplicación móvil. El identificador de inquilino y el nombre se definen mediante el dominio que se
genera cuando se crea la dirección URL del inquilino. Si la dirección URL del inquilino generada es
https://contoso20190410tenant.onmicrosoft.com/ el identificador del inquilino es
contoso20190410tenant.onmicrosoft.com y el nombre del inquilino es contoso20190410tenant . Busque el dominio
del inquilino en el Azure Portal; para ello, haga clic en el filtro directorio y suscripción en el menú superior. En
la captura de pantalla siguiente se muestra el botón de filtro de suscripción y el directorio de Azure y el dominio
del inquilino:

En el proyecto de ejemplo, edite el archivo constants.CS para establecer los tenantName tenantId campos y. En el
código siguiente se muestra cómo se deben establecer estos valores si el dominio del inquilino es
https://contoso20190410tenant.onmicrosoft.com/ , reemplace estos valores con los valores de su portal:

public static class Constants


{
static readonly string tenantName = "contoso20190410tenant";
static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
...
}

Registre su aplicación móvil con Azure Active Directory B2C


Una aplicación móvil debe estar registrada con el inquilino para poder conectarse y autenticar a los usuarios. El
proceso de registro asigna un identificador de aplicación único a la aplicación y una dirección URL de
redireccionamiento que dirige las respuestas de vuelta a la aplicación después de la autenticación. Para obtener
más información, vea Azure Active Directory B2C: registrar la aplicación. Deberá conocer el ID. de aplicación
asignado a la aplicación, que aparece después del nombre de la aplicación en la vista Propiedades. En la captura de
pantalla siguiente se muestra dónde encontrar el identificador de la aplicación:

La biblioteca de autenticación de Microsoft espera que la dirección URL de redireccionamiento de la aplicación


sea el identificador de la aplicación con el prefijo "msal" de texto y seguido por un punto de conexión
denominado "auth". Si el identificador de la aplicación es "1234abcd", la dirección URL completa debe ser
msal1234abcd://auth . Asegúrese de que la aplicación ha habilitado la configuración de Native Client y cree un
URI de redireccionamiento personalizado mediante el identificador de la aplicación, tal como se muestra en la
siguiente captura de pantalla:
La dirección URL se usará más adelante tanto en el ApplicationManifest.xml Android como en el archivo info.
plist de iOS.
En el proyecto de ejemplo, edite el archivo constants.CS para establecer el clientId campo en el identificador de
la aplicación . En el código siguiente se muestra cómo se debe establecer este valor si el identificador de la
aplicación es 1234abcd :

public static class Constants


{
static readonly string tenantName = "contoso20190410tenant";
static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
static readonly string clientId = "1234abcd";
...
}

Crear directivas de registro e inicio de sesión y olvidar las directivas de


contraseñas
Una directiva es una experiencia que recorren los usuarios para completar una tarea, como crear una cuenta o
restablecer una contraseña. Una directiva también especifica el contenido de los tokens que recibe la aplicación
cuando el usuario vuelve de la experiencia. Debe configurar las directivas para el registro y el inicio de sesión de la
cuenta y para restablecer la contraseña. Azure tiene directivas integradas que simplifican la creación de directivas
comunes. Para obtener más información, vea Azure Active Directory B2C: directivas integradas.
Cuando haya completado la configuración de la Directiva, debe tener dos directivas en la vista flujos de usuario
(directivas) en el Azure portal. En la captura de pantalla siguiente se muestran dos directivas configuradas en el
Azure Portal:
En el proyecto de ejemplo, edite el archivo constants.CS para establecer los policySignin policyPassword
campos y para que reflejen los nombres elegidos durante la configuración de la Directiva:

public static class Constants


{
static readonly string tenantName = "contoso20190410tenant";
static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
static readonly string clientId = "1234abcd";
static readonly string policySignin = "B2C_1_signupsignin1";
static readonly string policyPassword = "B2C_1_passwordreset";
...
}

Usar la biblioteca de autenticación de Microsoft (MSAL) para la


autenticación
El paquete NuGet de la biblioteca de autenticación de Microsoft (MSAL) debe agregarse al proyecto compartido,
.NET Standard y los proyectos de plataforma de una :::no-loc(Xamarin.Forms)::: solución. MSAL incluye una
PublicClientApplicationBuilder clase que construye un objeto que se adhiere a la IPublicClientApplication
interfaz. MSAL emplea With cláusulas para proporcionar parámetros adicionales al constructor y a los métodos de
autenticación.
En el proyecto de ejemplo, el código subyacente de app. Xaml define las propiedades estáticas denominadas
AuthenticationClient y UIParent , y crea instancias del AuthenticationClient objeto en el constructor. La
WithIosKeychainSecurityGroup cláusula proporciona un nombre de grupo de seguridad para las aplicaciones de
iOS. La WithB2CAuthority cláusula proporciona la autoridad o directiva predeterminada que se utilizará para
autenticar a los usuarios. La WithRedirectUri cláusula indica a la instancia de Notification hubs de Azure el URI de
redireccionamiento que se usará si se especifican varios URI. En el ejemplo siguiente se muestra cómo crear una
instancia de PublicClientApplication :
public partial class App : Application
{
public static IPublicClientApplication AuthenticationClient { get; private set; }

public static object UIParent { get; set; } = null;

public App()
{
InitializeComponent();

AuthenticationClient = PublicClientApplicationBuilder.Create(Constants.ClientId)
.WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
.WithB2CAuthority(Constants.AuthoritySignin)
.WithRedirectUri($"msal{Constants.ClientId}://auth")
.Build();

MainPage = new NavigationPage(new LoginPage());


}

...

NOTE
Si la instancia de Azure Notification Hubs solo tiene definido un URI de redirección, la AuthenticationClient instancia
puede funcionar sin especificar el URI de redirección con la WithRedirectUri cláusula. Sin embargo, siempre debe
especificar este valor en caso de que la configuración de Azure se expanda para admitir otros clientes o métodos de
autenticación.

El controlador de eventos en el código de LoginPage.Xaml.CS subyacente llama


OnAppearing
AcquireTokenSilentAsync a para actualizar el token de autenticación para los usuarios que iniciaron sesión antes. El
proceso de autenticación redirige a LogoutPage si es correcto y no realiza ninguna acción en caso de error. En el
ejemplo siguiente se muestra el proceso de reautenticación silenciosa en OnAppearing :

public partial class LoginPage : ContentPage


{
...

protected override async void OnAppearing()


{
try
{
// Look for existing account
IEnumerable<IAccount> accounts = await App.AuthenticationClient.GetAccountsAsync();

AuthenticationResult result = await App.AuthenticationClient


.AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault())
.ExecuteAsync();

await Navigation.PushAsync(new LogoutPage(result));


}
catch
{
// Do nothing - the user isn't logged in
}
base.OnAppearing();
}

...
}

El OnLoginButtonClicked controlador de eventos (que se desencadena cuando se hace clic en el botón de inicio de
sesión) llama a AcquireTokenAsync . La biblioteca MSAL abre automáticamente el explorador de dispositivos
móviles y navega a la página de inicio de sesión. La dirección URL de inicio de sesión, denominada entidad de
cer tificación , es una combinación del nombre de inquilino y las directivas definidas en el archivo constants.CS .
Si el usuario elige la opción Olvidó la contraseña, se devuelve a la aplicación con una excepción, lo que inicia la
experiencia de olvido de contraseña. En el ejemplo siguiente se muestra el proceso de autenticación:

public partial class LoginPage : ContentPage


{
...

async void OnLoginButtonClicked(object sender, EventArgs e)


{
AuthenticationResult result;
try
{
result = await App.AuthenticationClient
.AcquireTokenInteractive(Constants.Scopes)
.WithPrompt(Prompt.SelectAccount)
.WithParentActivityOrWindow(App.UIParent)
.ExecuteAsync();

await Navigation.PushAsync(new LogoutPage(result));


}
catch (MsalException ex)
{
if (ex.Message != null && ex.Message.Contains("AADB2C90118"))
{
result = await OnForgotPassword();
await Navigation.PushAsync(new LogoutPage(result));
}
else if (ex.ErrorCode != "authentication_canceled")
{
await DisplayAlert("An error has occurred", "Exception message: " + ex.Message, "Dismiss");
}
}
}

...
}

El OnForgotPassword método es similar al proceso de inicio de sesión, pero implementa una directiva
personalizada. OnForgotPassword utiliza una sobrecarga diferente de AcquireTokenAsync , que permite proporcionar
una autoridad específica. En el ejemplo siguiente se muestra cómo proporcionar una entidad de cer tificación
personalizada al adquirir un token:
public partial class LoginPage : ContentPage
{
...
async Task<AuthenticationResult> OnForgotPassword()
{
try
{
return await App.AuthenticationClient
.AcquireTokenInteractive(Constants.Scopes)
.WithPrompt(Prompt.SelectAccount)
.WithParentActivityOrWindow(App.UIParent)
.WithB2CAuthority(Constants.AuthorityPasswordReset)
.ExecuteAsync();
}
catch (MsalException)
{
// Do nothing - ErrorCode will be displayed in OnLoginButtonClicked
return null;
}
}
}

La parte final de la autenticación es el proceso de cierre de sesión. OnLogoutButtonClicked Se llama al método


cuando el usuario presiona el botón Cerrar sesión. Recorre en bucle todas las cuentas y garantiza que sus tokens se
han invalidado. En el ejemplo siguiente se muestra la implementación de cierre de sesión:

public partial class LogoutPage : ContentPage


{
...
async void OnLogoutButtonClicked(object sender, EventArgs e)
{
IEnumerable<IAccount> accounts = await App.AuthenticationClient.GetAccountsAsync();

while (accounts.Any())
{
await App.AuthenticationClient.RemoveAsync(accounts.First());
accounts = await App.AuthenticationClient.GetAccountsAsync();
}

await Navigation.PopAsync();
}
}

iOS
En iOS, el esquema de la dirección URL personalizada que se registró con Azure Active Directory B2C debe estar
registrado en info. plist . MSAL espera que el esquema de la dirección URL se adhiere a un patrón específico,
descrito anteriormente en registrar la aplicación móvil con Azure Active Directory B2C. En la captura de pantalla
siguiente se muestra el esquema de la dirección URL personalizada en info. plist .

MSAL también requiere derechos de cadena de claves en iOS, registrados en Entitilements. plist , tal como se
muestra en la siguiente captura de pantalla:
Cuando Azure Active Directory B2C completa la solicitud de autorización, se redirige a la dirección URL de
redireccionamiento registrada. El esquema de la dirección URL personalizada hace que iOS inicie la aplicación
móvil y pase la dirección URL como un parámetro de inicio, donde se procesa mediante la OpenUrl invalidación de
la clase de la aplicación AppDelegate , y devuelve el control de la experiencia a MSAL. La OpenUrl implementación
se muestra en el ejemplo de código siguiente:

using Microsoft.Identity.Client;

namespace TodoAzure.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global:::::no-
loc(Xamarin.Forms):::.Platform.iOS.FormsApplicationDelegate
{
...
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
return base.OpenUrl(app, url, options);
}
}
}

Android
En Android, el esquema de la dirección URL personalizada que se registró con Azure Active Directory B2C debe
estar registrado en el AndroidManifest.xml . MSAL espera que el esquema de la dirección URL se adhiere a un
patrón específico, descrito anteriormente en registrar la aplicación móvil con Azure Active Directory B2C. En el
ejemplo siguiente se muestra el esquema de la dirección URL personalizada en el AndroidManifest.xml .
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"
android:versionName="1.0" package="com.xamarin.adb2cauthorization">
<uses-sdk android:minSdkVersion="15" />
<application android:label="ADB2CAuthorization">
<activity android:name="microsoft.identity.client.BrowserTabActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- example -->
<!-- <data android:scheme="msalaaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" android:host="auth" /> -->
<data android:scheme="INSERT_URI_SCHEME_HERE" android:host="auth" />
</intent-filter>
</activity>"
</application>
</manifest>

La MainActivity clase debe modificarse para proporcionar el UIParent objeto a la aplicación durante la OnCreate
llamada. Cuando Azure Active Directory B2C completa la solicitud de autorización, redirige al esquema de URL
registrado desde el AndroidManifest.xml . El esquema de URI registrado hace que Android llame al
OnActivityResult método con la dirección URL como parámetro de inicio, donde lo procesa el
SetAuthenticationContinuationEventArgs método.

public class MainActivity : FormsAppCompatActivity


{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;

base.OnCreate(bundle);

Forms.Init(this, bundle);
LoadApplication(new App());
App.UIParent = this;
}

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)


{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode,
data);
}
}

Plataforma universal de Windows


No es necesario realizar ninguna configuración adicional para usar MSAL en el Plataforma universal de Windows

Ejecución del proyecto


Ejecute la aplicación en un dispositivo físico o virtual. Al pulsar el botón Inicio de sesión , debe abrir el explorador
y navegar a una página donde puede iniciar sesión o crear una cuenta. Después de completar el proceso de inicio
de sesión, debe volver a la página de cierre de sesión de la aplicación. En la captura de pantalla siguiente se
muestra la pantalla de inicio de sesión de usuario que se ejecuta en Android e iOS:
Vínculos relacionados
AzureADB2CAuth (ejemplo)
Azure Active Directory B2C
Biblioteca de autenticación de Microsoft
Documentación de la biblioteca de autenticación de Microsoft
Autentique a los usuarios con una base de datos de
documentos Azure Cosmos DB y :::no-
loc(Xamarin.Forms):::
18/12/2020 • 23 minutes to read • Edit Online

Descargar el ejemplo
Azure Cosmos DB bases de datos de documentos admiten colecciones con particiones, que pueden abarcar varios
servidores y particiones, a la vez que admiten un almacenamiento y un rendimiento ilimitados. En este artículo se
explica cómo combinar el control de acceso con colecciones con particiones, de modo que un usuario solo pueda
tener acceso a sus propios documentos en una :::no-loc(Xamarin.Forms)::: aplicación.

Información general
Se debe especificar una clave de partición al crear una colección con particiones, y los documentos con la misma
clave de partición se almacenarán en la misma partición. Por lo tanto, si se especifica la identidad del usuario como
una clave de partición, se generará una colección con particiones que solo almacenará documentos para ese
usuario. Esto también garantiza que el Azure Cosmos DB base de datos de documentos se escalará a medida que
aumente el número de usuarios y elementos.
Se debe conceder acceso a cualquier recopilación y el modelo de control de acceso de la API de SQL define dos
tipos de construcciones de acceso:
Las claves maestras habilitan el acceso administrativo completo a todos los recursos de una cuenta de
Cosmos dB y se crean cuando se crea una cuenta de Cosmos dB.
Los tokens de recursos capturan la relación entre el usuario de una base de datos y el permiso que tiene el
usuario para un recurso de Cosmos dB específico, como una colección o un documento.
Al exponer una clave maestra, se abre una Cosmos DB cuenta a la posibilidad de que se use el uso
malintencionado o por negligencia. Sin embargo, Azure Cosmos DB los tokens de recursos proporcionan un
mecanismo seguro para permitir que los clientes Lean, escriban y eliminen recursos específicos en una cuenta de
Azure Cosmos DB de acuerdo con los permisos concedidos.
Un enfoque típico para solicitar, generar y entregar tokens de recursos a una aplicación móvil es usar un agente de
token de recurso. En el diagrama siguiente se muestra información general de alto nivel sobre cómo la aplicación
de ejemplo usa un agente de token de recursos para administrar el acceso a los datos de la base de datos de
documentos:
El agente de token de recursos es un servicio de API Web de nivel intermedio, hospedado en Azure App Service,
que posee la clave maestra de la cuenta de Cosmos DB. La aplicación de ejemplo usa el agente de tokens de
recursos para administrar el acceso a los datos de la base de datos de documentos como se indica a continuación:
1. En el inicio de sesión, la :::no-loc(Xamarin.Forms)::: aplicación se pone en contacto Azure App Service para iniciar
un flujo de autenticación.
2. Azure App Service realiza un flujo de autenticación de OAuth con Facebook. Una vez completado el flujo de
autenticación, la :::no-loc(Xamarin.Forms)::: aplicación recibe un token de acceso.
3. La :::no-loc(Xamarin.Forms)::: aplicación usa el token de acceso para solicitar un token de recurso del agente de
token de recursos.
4. El agente de token de recursos usa el token de acceso para solicitar la identidad del usuario de Facebook. La
identidad del usuario se usa para solicitar un token de recurso de Cosmos DB, que se usa para conceder acceso
de lectura y escritura a la colección con particiones del usuario autenticado.
5. La :::no-loc(Xamarin.Forms)::: aplicación utiliza el token de recurso para acceder directamente a Cosmos dB
recursos con los permisos definidos por el token de recurso.

NOTE
Cuando el token de recurso expira, las solicitudes de base de datos de documentos posteriores recibirán una excepción de
401 no autorizado. En este momento, :::no-loc(Xamarin.Forms)::: las aplicaciones deben volver a establecer la identidad y
solicitar un nuevo token de recurso.

Para obtener más información acerca de la creación de particiones Cosmos DB, consulte partición y escalado en
Azure Cosmos dB. Para obtener más información sobre el control de acceso de Cosmos DB, consulte proteger el
acceso a los datos de Cosmos dB y el control de acceso en la API de SQL.

Configuración
El proceso para integrar el agente de token de recursos en una :::no-loc(Xamarin.Forms)::: aplicación es el siguiente:
1. Cree una cuenta de Cosmos DB que vaya a usar el control de acceso. Para obtener más información, consulte
configuración de Azure Cosmos dB.
2. Cree un Azure App Service para hospedar el agente de token de recursos. Para obtener más información,
consulte configuración de Azure App Service.
3. Cree una aplicación de Facebook para realizar la autenticación. Para obtener más información, consulte
configuración de aplicaciones de Facebook.
4. Configure el Azure App Service para facilitar la autenticación con Facebook. Para obtener más información,
consulte Configuración de la autenticación de Azure App Service.
5. Configure la :::no-loc(Xamarin.Forms)::: aplicación de ejemplo para que se comunique con Azure App Service y
cosmos dB. Para obtener más información, consulte Configuración de la :::no-loc(Xamarin.Forms)::: aplicación.

NOTE
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Configuración de Azure Cosmos DB


El proceso para crear una cuenta de Cosmos DB que usará el control de acceso es el siguiente:
1. Cree una cuenta de Cosmos DB. Para obtener más información, consulte crear una cuenta de Azure Cosmos dB.
2. En la cuenta de Cosmos DB, cree una nueva colección denominada UserItems y especifique una clave de
partición de /userid .
Configuración de Azure App Service
El proceso para hospedar el agente de token de recursos en Azure App Service es el siguiente:
1. En el Azure Portal, cree una nueva aplicación Web de App Service. Para obtener más información, consulte
crear una aplicación web en un APP Service Environment.
2. En el Azure Portal, abra la hoja configuración de la aplicación de la aplicación web y agregue la siguiente
configuración:
accountUrl : el valor debe ser la dirección URL de la cuenta de Cosmos DB de la hoja claves de la cuenta
de Cosmos DB.
accountKey : el valor debe ser la clave maestra Cosmos DB (principal o secundaria) de la hoja claves de
la cuenta de Cosmos DB.
databaseId : el valor debe ser el nombre de la base de datos Cosmos DB.
collectionId : el valor debe ser el nombre de la colección de Cosmos DB (en este caso, UserItems ).
hostUrl : el valor debe ser la dirección URL de la aplicación web en la hoja de información general de la
cuenta de App Service.
En la captura de pantalla siguiente se muestra esta configuración:

3. Publique la solución de agente de tokens de recursos en la aplicación Web de Azure App Service.
Configuración de la aplicación Facebook
El proceso para crear una aplicación de Facebook para realizar la autenticación es el siguiente:
1. Cree una aplicación de Facebook. Para obtener más información, consulte registrar y configurar una aplicación
en el centro para desarrolladores de Facebook.
2. Agregue el producto de inicio de sesión de Facebook a la aplicación. Para obtener más información, consulte
adición de un inicio de sesión de Facebook a su aplicación o sitio web en el centro para desarrolladores de
Facebook.
3. Configure el inicio de sesión de Facebook de la siguiente manera:
Habilite el inicio de sesión de OAuth de cliente.
Habilite el inicio de sesión de OAuth Web.
Establezca el URI de redirección de OAuth válido en el URI de la aplicación Web de App Service, con
/.auth/login/facebook/callback anexado.

En la captura de pantalla siguiente se muestra esta configuración:

Para obtener más información, consulte registrar la aplicación con Facebook.


Configuración de la autenticación de Azure App Service
El proceso para configurar la autenticación de App Service Easy es el siguiente:
1. En Azure portal, vaya a la aplicación Web de App Service.
2. En Azure portal, abra la hoja autenticación/autorización y realice la siguiente configuración:
App Service la autenticación debe estar activada.
La acción que debe llevarse a cabo cuando una solicitud no está autenticada debe establecerse para
iniciar sesión en Facebook .
En la captura de pantalla siguiente se muestra esta configuración:
También se debe configurar la aplicación Web de App Service para comunicarse con la aplicación de Facebook
para habilitar el flujo de autenticación. Para ello, seleccione el proveedor de identidades de Facebook y escriba los
valores de ID. de aplicación y secreto de la aplicación en la configuración de la aplicación de Facebook en el
centro para desarrolladores de Facebook. Para obtener más información, consulte Agregar información de
Facebook a la aplicación.
:::no -loc(Xamarin.Forms)::: Configuración de la aplicación
El proceso para configurar la :::no-loc(Xamarin.Forms)::: aplicación de ejemplo es el siguiente:
1. Abra la :::no-loc(Xamarin.Forms)::: solución.
2. Abra Constants.cs y actualice los valores de las constantes siguientes:
EndpointUri : el valor debe ser la dirección URL de la cuenta de Cosmos DB de la hoja claves de la
cuenta de Cosmos DB.
DatabaseName : el valor debe ser el nombre de la base de datos de documentos.
CollectionName : el valor debe ser el nombre de la colección de bases de datos de documentos (en este
caso, UserItems ).
ResourceTokenBrokerUrl : el valor debe ser la dirección URL de la aplicación web del agente de token de
recursos en la hoja de información general de la cuenta de App Service.

Iniciando inicio de sesión


La aplicación de ejemplo inicia el proceso de inicio de sesión redirigiendo un explorador a una dirección URL del
proveedor de identidades, como se muestra en el código de ejemplo siguiente:

var auth = new Xamarin.Auth.WebRedirectAuthenticator(


new Uri(Constants.ResourceTokenBrokerUrl + "/.auth/login/facebook"),
new Uri(Constants.ResourceTokenBrokerUrl + "/.auth/login/done"));

Esto hace que se inicie un flujo de autenticación de OAuth entre Azure App Service y Facebook, que muestra la
página de inicio de sesión de Facebook:
Para cancelar el inicio de sesión, presione el botón Cancelar en iOS o presione el botón atrás en Android, en cuyo
caso el usuario permanece sin autenticar y la interfaz de usuario del proveedor de identidades se quita de la
pantalla.

Obtención de un token de recurso


Después de la autenticación correcta, se WebRedirectAuthenticator.Completed activa el evento. En el ejemplo de
código siguiente se muestra cómo controlar este evento:
auth.Completed += async (sender, e) =>
{
if (e.IsAuthenticated && e.Account.Properties.ContainsKey("token"))
{
var easyAuthResponseJson = JsonConvert.DeserializeObject<JObject>(e.Account.Properties["token"]);
var easyAuthToken = easyAuthResponseJson.GetValue("authenticationToken").ToString();

// Call the ResourceBroker to get the resource token


using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("x-zumo-auth", easyAuthToken);
var response = await httpClient.GetAsync(Constants.ResourceTokenBrokerUrl + "/api/resourcetoken/");
var jsonString = await response.Content.ReadAsStringAsync();
var tokenJson = JsonConvert.DeserializeObject<JObject>(jsonString);
resourceToken = tokenJson.GetValue("token").ToString();
UserId = tokenJson.GetValue("userid").ToString();

if (!string.IsNullOrWhiteSpace(resourceToken))
{
client = new DocumentClient(new Uri(Constants.EndpointUri), resourceToken);
...
}
...
}
}
};

El resultado de una autenticación correcta es un token de acceso, que es la


AuthenticatorCompletedEventArgs.Account propiedad disponible. El token de acceso se extrae y se usa en una
solicitud GET a la API del agente de token de recurso resourcetoken .
La resourcetoken API usa el token de acceso para solicitar la identidad del usuario de Facebook, que a su vez se
usa para solicitar un token de recurso de Cosmos dB. Si ya existe un documento de permiso válido para el usuario
en la base de datos de documentos, se recupera y se devuelve a la aplicación un documento JSON que contiene el
token de recurso :::no-loc(Xamarin.Forms)::: . Si no existe un documento de permiso válido para el usuario, se crea
un usuario y un permiso en la base de datos de documentos, y el token de recurso se extrae del documento de
permisos y se devuelve a la :::no-loc(Xamarin.Forms)::: aplicación en un documento JSON.

NOTE
Un usuario de la base de datos de documentos es un recurso asociado a una base de datos de documentos y cada base de
datos puede contener cero o más usuarios. Un permiso de base de datos de documentos es un recurso asociado a un
usuario de base de datos de documentos y cada usuario puede contener cero o más permisos. Un recurso de permiso
proporciona acceso a un token de seguridad que el usuario necesita al intentar tener acceso a un recurso, como un
documento.

Si la resourcetoken API se completa correctamente, enviará el código de Estado HTTP 200 (correcto) en la
respuesta, junto con un documento JSON que contiene el token de recurso. Los siguientes datos JSON muestran
un mensaje de respuesta correcta típico:
{
"id": "John Smithpermission",
"token":
"type=resource&ver=1&sig=zx6k2zzxqktzvuzuku4b7y==;a74aukk99qtwk8v5rxfrfz7ay7zzqfkbfkremrwtaapvavw2mrvia4umbi/7
iiwkrrq+buqqrzkaq4pp15y6bki1u//zf7p9x/aefbvqvq3tjjqiffurfx+vexa1xarxkkv9rbua9ypfzr47xpp5vmxuvzbekkwq6txme0xxxb
jhzaxbkvzaji+iru3xqjp05amvq1r1q2k+qrarurhmjzah/ha0evixazkve2xk1zu9u/jpyf1xrwbkxqpzebvqwma+hyyaazemr6qx9uz9be==
;",
"expires": 4035948,
"userid": "John Smith"
}

El WebRedirectAuthenticator.Completed controlador de eventos lee la respuesta de la resourcetoken API y extrae el


token de recurso y el ID. de usuario. A continuación, el token de recurso se pasa como argumento al
DocumentClient constructor, que encapsula el extremo, las credenciales y la Directiva de conexión utilizada para
obtener acceso a Cosmos dB, y se usa para configurar y ejecutar solicitudes en cosmos dB. El token de recurso se
envía con cada solicitud para acceder directamente a un recurso e indica que se concede acceso de lectura y
escritura a la colección con particiones de los usuarios autenticados.

Recuperación de documentos
La recuperación de documentos que solo pertenecen al usuario autenticado se puede lograr creando una consulta
de documento que incluya el identificador del usuario como clave de partición y se muestra en el ejemplo de
código siguiente:

var query = client.CreateDocumentQuery<TodoItem>(collectionLink,


new FeedOptions
{
MaxItemCount = -1,
PartitionKey = new PartitionKey(UserId)
})
.Where(item => !item.Id.Contains("permission"))
.AsDocumentQuery();
while (query.HasMoreResults)
{
Items.AddRange(await query.ExecuteNextAsync<TodoItem>());
}

La consulta recupera de forma asincrónica todos los documentos que pertenecen al usuario autenticado, de la
colección especificada y los coloca en una List<TodoItem> colección para su presentación.
El CreateDocumentQuery<T> método especifica un Uri argumento que representa la colección que se debe
consultar para los documentos y un FeedOptions objeto. El FeedOptions objeto especifica que la consulta puede
devolver un número ilimitado de elementos, y el identificador del usuario como una clave de partición. Esto
garantiza que solo se devuelvan en el resultado los documentos de la colección con particiones del usuario.

NOTE
Tenga en cuenta que los documentos de permisos, creados por el agente de token de recursos, se almacenan en la misma
colección de documentos que los documentos creados por la :::no-loc(Xamarin.Forms)::: aplicación. Por lo tanto, la consulta
de documento contiene una Where cláusula que aplica un predicado de filtrado a la consulta en la colección de
documentos. Esta cláusula garantiza que los documentos de permiso no se devuelven desde la colección de documentos.

Para obtener más información sobre cómo recuperar documentos de una colección de documentos, vea recuperar
documentos de colecciónde documentos.
Insertar documentos
Antes de insertar un documento en una colección de documentos, la TodoItem.UserId propiedad debe actualizarse
con el valor que se usa como clave de partición, tal y como se muestra en el ejemplo de código siguiente:

item.UserId = UserId;
await client.CreateDocumentAsync(collectionLink, item);

Esto garantiza que el documento se insertará en la colección con particiones del usuario.
Para obtener más información sobre cómo insertar un documento en una colección de documentos, vea Insertar
un documento en una colección de documentos.

Eliminar documentos
El valor de la clave de partición se debe especificar al eliminar un documento de una colección con particiones,
como se muestra en el ejemplo de código siguiente:

await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Constants.DatabaseName,
Constants.CollectionName, id),
new RequestOptions
{
PartitionKey = new PartitionKey(UserId)
});

Esto garantiza que Cosmos DB sepa en qué colección con particiones se va a eliminar el documento.
Para obtener más información sobre cómo eliminar un documento de una colección de documentos, vea eliminar
un documento de una colección de documentos.

Resumen
En este artículo se explica cómo combinar el control de acceso con colecciones con particiones, de modo que un
usuario solo pueda tener acceso a sus documentos de base de datos de documentos en una :::no-
loc(Xamarin.Forms)::: aplicación. La especificación de la identidad del usuario como clave de partición garantiza
que una colección con particiones solo puede almacenar documentos para ese usuario.

Vínculos relacionados
Azure Cosmos DB autenticación de todo (ejemplo)
Consumo de una base de datos de documentos de Cosmos Azure DB
Protección del acceso a los datos de Azure Cosmos DB
Control de acceso en la API de SQL.
Cómo particionar y escalar en Azure Cosmos DB
Biblioteca de cliente de Azure Cosmos DB
API de Azure Cosmos DB
Mejora del rendimiento de las aplicaciones de
Xamarin.Forms
18/12/2020 • 31 minutes to read • Edit Online

Evolve 2016: Optimizar el rendimiento de las aplicaciones con Xamarin.Forms


El mal rendimiento de una aplicación se manifiesta de muchas formas. Puede hacer que parezca que una
aplicación deja de responder, puede ocasionar un desplazamiento lento y puede reducir la duración de la batería
del dispositivo. La optimización del rendimiento conlleva mucho más que la mera implementación de código
eficaz. También debe tenerse en cuenta la experiencia de rendimiento de la aplicación del usuario. Por ejemplo,
asegurarse de que las operaciones se ejecuten sin evitar que el usuario realice otras actividades puede ayudar a
mejorar su experiencia.
Existen varias técnicas para aumentar el rendimiento (y la percepción del rendimiento) de las aplicaciones
Xamarin.Forms. En conjunto, estas técnicas pueden reducir considerablemente la cantidad de trabajo que está
realizando una CPU y la cantidad de memoria consumida por una aplicación.

NOTE
Antes de leer este artículo, debería leer Rendimiento multiplataforma, donde se explican técnicas no específicas de una
plataforma para mejorar el uso de memoria y el rendimiento de las aplicaciones compiladas con la plataforma Xamarin.

Habilitación del compilador de XAML


XAML se puede compilar de forma opcional directamente en lenguaje intermedio (IL) con el compilador XAML
(XAMLC). XAMLC ofrece una serie de ventajas:
Realiza la comprobación en tiempo de compilación de XAML, notificando al usuario de los errores.
Reduce parte del tiempo de carga y creación de instancias para los elementos XAML.
Facilita reducir el tamaño de archivo del ensamblado final al dejar de incluir archivos .xaml.
XAMLC está habilitado de forma predeterminada en las nuevas soluciones de Xamarin.Forms. Sin embargo, es
posible que deba habilitarse en soluciones anteriores. Para más información, vea Compilación de XAML.

Uso de enlaces compilados


Los enlaces compilados mejoran el rendimiento del enlace de datos en las aplicaciones de Xamarin.Forms
mediante la resolución de expresiones de enlace en tiempo de compilación, en lugar de en tiempo de ejecución
con reflexión. La compilación de una expresión de enlace genera código compilado que normalmente resuelve un
enlace entre 8 y 20 veces más rápido que usando un enlace clásico. Para obtener más información, vea Enlaces
compilados.

Reducción de enlaces innecesarios


No use enlaces para el contenido que se puede establecer fácilmente de forma estática. No hay ninguna ventaja
en enlazar datos que no necesitan ser enlazados, ya que los enlaces no son rentables. Por ejemplo, establecer
Button.Text = "Accept" tiene una menor sobrecarga que enlazar Button.Text a una propiedad string del
modelo de vista con el valor "Accept".
Uso de representadores rápidos
Los representadores rápidos reducen la inflación y los costos de representación de controles de Xamarin.Forms
en Android mediante la reducción de la jerarquía de control nativo resultante. Además, esto mejora el
rendimiento porque crea menos objetos, lo cual a su vez genera un árbol visual menos complejo y menos uso de
memoria.
A partir de Xamarin.Forms 4.0, todas las aplicaciones con destino FormsAppCompatActivity usan representadores
rápidos de forma predeterminada. Para obtener más información, consulte Fast Renderers (Representadores
rápidos).

Habilitación del seguimiento de inicio en Android


La compilación Ahead of Time (AOT) en Android minimiza la sobrecarga de inicio de la aplicación Just-In-Time
(JIT) y el uso de memoria, a costa de crear un archivo APK mucho más grande. Una alternativa es usar el
seguimiento de inicio, que proporciona un intercambio entre el tamaño del archivo APK de Android y el tiempo
de inicio, en comparación con una compilación AOT convencional.
En vez de compilar la mayor cantidad de la aplicación en código no administrado, el seguimiento de inicio
compila únicamente el conjunto de métodos administrados que representan las partes más costosas del inicio de
la aplicación en una aplicación Xamarin.Forms en blanco. El uso de este enfoque conlleva la producción de un
archivo APK más pequeño en comparación con la compilación AOT convencional, así como una proporción
similar de mejoras de inicio.

Habilitación de la compresión del diseño


La compresión de diseño quita diseños especificados del árbol visual, en un intento de mejorar el rendimiento de
la representación de la página. La ventaja de rendimiento que esto ofrece varía según la complejidad de una
página, la versión del sistema operativo que se va a usar y el dispositivo en el que se ejecuta la aplicación. Sin
embargo, las mejoras de rendimiento más importantes se apreciarán en los dispositivos más antiguos. Para
obtener más información, vea Layout Compression (Compresión de diseño).

Elección del diseño adecuado


Un diseño capaz de mostrar varios elementos secundarios, pero que solo tiene un elemento secundario, es poco
rentable. Por ejemplo, el siguiente ejemplo de código muestra un StackLayout con un único elemento secundario:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DisplayImage.HomePage">
<StackLayout>
<Image Source="waterfront.jpg" />
</StackLayout>
</ContentPage>

Es un desperdicio y el elemento StackLayout debería eliminarse, como se muestra en el ejemplo de código


siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DisplayImage.HomePage">
<Image Source="waterfront.jpg" />
</ContentPage>

Además, no intente reproducir el aspecto de un diseño específico mediante combinaciones de otros diseños, dado
que como resultado se realizarían cálculos de diseño innecesarios. Por ejemplo, no intente reproducir un diseño
Grid mediante una combinación de instancias StackLayout . El ejemplo de código siguiente muestra un ejemplo
de esta práctica incorrecta:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Details.HomePage"
Padding="0,20,0,0">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</StackLayout>
</StackLayout>
</ContentPage>

Es una pérdida de tiempo porque se realizan cálculos de diseño innecesarios. En su lugar, el diseño deseado
puede lograrse mejor mediante un Grid , como se muestra en el ejemplo de código siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Details.HomePage"
Padding="0,20,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label Text="Name:" />
<Entry Grid.Column="1" Placeholder="Enter your name" />
<Label Grid.Row="1" Text="Age:" />
<Entry Grid.Row="1" Grid.Column="1" Placeholder="Enter your age" />
<Label Grid.Row="2" Text="Occupation:" />
<Entry Grid.Row="2" Grid.Column="1" Placeholder="Enter your occupation" />
<Label Grid.Row="3" Text="Address:" />
<Entry Grid.Row="3" Grid.Column="1" Placeholder="Enter your address" />
</Grid>
</ContentPage>

Optimización del rendimiento del diseño


Para obtener el mejor rendimiento posible, siga estas instrucciones:
Reduzca la profundidad de las jerarquías de diseño especificando valores de propiedad Margin , lo que
permite la creación de diseños con menos vistas ajustadas. Para más información, vea Márgenes y relleno.
Cuando se usa un Grid , intente asegurarse de establecer en tamaño Auto el menor número posible de filas y
columnas. Cada fila o columna de tamaño automático hará que el motor de diseño tenga que realizar cálculos
de diseño adicionales. En su lugar, use filas y columnas de tamaño fijo si es posible. Como alternativa,
establezca las filas y columnas para ocupar una cantidad proporcional de espacio con el valor de enumeración
GridUnitType.Star , siempre que el árbol primario siga estas directrices de diseño.
No establezca las propiedades VerticalOptions y HorizontalOptions de un diseño a menos que sea necesario.
Los valores predeterminados de LayoutOptions.Fill y LayoutOptions.FillAndExpand permiten la optimización
de diseño óptima. Cambiar estas propiedades tiene un costo y consume memoria, incluso cuando se
establecen en los valores predeterminados.
Evite el uso de un RelativeLayout siempre que sea posible. Como resultado, la CPU tendrá que realizar mucho
más trabajo.
Cuando se usa un AbsoluteLayout , evite el uso de la propiedad AbsoluteLayout.AutoSize siempre que sea
posible.
Cuando se usa un StackLayout , asegúrese de que solo un elemento secundario se establece en
LayoutOptions.Expands . Esta propiedad garantiza que el elemento secundario especificado ocupa el mayor
espacio que el StackLayout puede asignarle y es poco rentable realizar estos cálculos más de una vez.
Evite llamar a los métodos de la clase Layout , dado que provocan que se realicen cálculos de diseño costosos.
En su lugar, es probable que se pueda obtener el comportamiento de diseño deseado estableciendo las
propiedades TranslationX y TranslationY . Como alternativa, cree una subclase de la clase Layout<View> para
lograr el comportamiento de diseño deseado.
No actualice ninguna instancia Label con más frecuencia de la necesaria, dado que el cambio de tamaño de la
etiqueta puede producir que se vuelva a calcular la pantalla completa.
No establezca la propiedad Label.VerticalTextAlignment a menos sea necesario.
Establezca el LineBreakMode de las instancias Label en NoWrap siempre que sea posible.

Uso de la programación asincrónica


Mediante la programación asincrónica se puede mejorar la capacidad de respuesta general de la aplicación y,
normalmente, evitar los cuellos de botella de rendimiento. En .NET, el Modelo asincrónico basado en tareas (TAP)
es el modelo de diseño recomendado para las operaciones asincrónicas. Pero el uso incorrecto de TAP puede dar
lugar a aplicaciones no ejecutables. Por tanto, al usar TAP, se deben seguir las instrucciones siguientes.
Aspectos básicos
Entender el ciclo de vida de la tarea, que se representa mediante la enumeración TaskStatus . Para más
información, vea El significado de TaskStatus y Estado de la tarea.
Use el método Task.WhenAll para esperar de forma asincrónica a que finalicen varias operaciones
asincrónicas, en lugar de utilizar await de manera individual en una serie de operaciones asincrónicas.
Para más información, vea Task.WhenAll.
Use el método Task.WhenAny para esperar de forma asincrónica a que finalice una de varias operaciones
asincrónicas. Para más información, vea Task.WhenAny.
Use el método Task.Delay para crear un objeto Task que finaliza tras el tiempo especificado. Esto resulta
útil para escenarios como los de sondeo de datos y para retrasar el control de la entrada del usuario
durante un tiempo predeterminado. Para más información, vea Task.Delay.
Ejecute operaciones de CPU sincrónicas intensivas en el grupo de subprocesos con el método Task.Run .
Este método es un acceso directo para el método TaskFactory.StartNew , con los argumentos óptimos
establecidos. Para más información, vea Task.Run.
Evite intentar crear constructores asincrónicos. En su lugar, use eventos de ciclo de vida o lógica de
inicialización independiente para ejecutar await de forma correcta en cualquier inicialización. Para más
información, vea Constructores asincrónicos en blog.stephencleary.com.
Use el modelo de tareas en diferido para evitar esperar a que se completen las operaciones asincrónicas
durante el inicio de la aplicación. Para más información, vea AsyncLazy.
Cree un contenedor de tareas para las operaciones asincrónicas existentes, que no usan TAP, mediante la
creación de objetos TaskCompletionSource<T> . Estos objetos obtienen las ventajas de la programación de
Task y permiten controlar la duración y la finalización del objeto Task asociado. Para más información,
vea La naturaleza de TaskCompletionSource.
Devuelva un objeto Task , en lugar de devolver un objeto Task en espera, cuando no sea necesario
procesar el resultado de una operación asincrónica. Esto es más eficaz debido a que se realizan menos
cambios de contexto.
Use la biblioteca Flujo de datos de la biblioteca de procesamiento paralelo basado en tareas (TPL) en
escenarios como el procesamiento de datos a medida que estén disponibles, o bien cuando tenga varias
operaciones que se deban comunicar entre sí de forma asincrónica. Para más información, vea Flujo de
datos (biblioteca TPL).
IU
Llame a una versión asincrónica de una API, si está disponible. Esto mantendrá desbloqueado al
subproceso de IU, lo que le ayudará a mejorar la experiencia del usuario con la aplicación.
Actualice los elementos de la interfaz de usuario con los datos de las operaciones asincrónicas en el
subproceso de la interfaz de usuario, para evitar que se inicien excepciones. Pero las actualizaciones de la
propiedad ListView.ItemsSource se calcularán de forma automática en el subproceso de la interfaz de
usuario. Para obtener información sobre cómo determinar si el código se ejecuta en el subproceso de la
interfaz de usuario, vea Xamarin.Essentials: MainThread.

IMPORTANT
Todas las propiedades de control que se actualicen a través del enlace de datos se serializarán de forma automática
en el subproceso de la interfaz de usuario.

Control de errores
Obtenga información sobre el control de excepciones asincrónico. Las excepciones no controladas que se
inician desde código que se ejecuta de forma asincrónica se propagan de vuelta al subproceso que realiza la
llamada, excepto en escenarios concretos. Para más información, vea Control de excepciones (biblioteca TPL).
Evite crear métodos async void y, en su lugar, cree métodos async Task . Estos facilitan el control de errores,
la redacción y la capacidad de prueba. La excepción a esta instrucción son los controladores de eventos
asincrónicos, que deben devolver void . Para más información, vea Evite async void.
No mezcle código de bloqueo y asincrónico mediante una llamada a los métodos Task.Wait , Task.Result o
GetAwaiter().GetResult , ya que pueden dar lugar a un interbloqueo. Pero si se debe incumplir esta instrucción,
el enfoque preferido consiste en llamar al método GetAwaiter().GetResult porque conserva las excepciones
de la tarea. Para más información, vea Async hasta el infinito y Control de excepciones de tareas en .NET 4.5.
Use el método ConfigureAwait siempre que sea posible para crear código sin contexto. El código sin contexto
tiene un rendimiento mejor en las aplicaciones para dispositivos móviles y es una técnica útil para evitar los
interbloqueos cuando se trabaja con una base de código parcialmente asincrónica. Para más información, vea
Configuración del contexto.
Use tareas de continuación para obtener funcionalidad como el control de excepciones iniciadas por la
operación asincrónica anterior y la cancelación de una continuación antes de iniciarse o mientras se ejecuta.
Para más información, vea Encadenamiento de tareas mediante tareas de continuación.
Use una implementación de ICommand asincrónica cuando se invoquen operaciones asincrónicas desde
ICommand . Esto garantiza que se puedan controlar las excepciones de la lógica de comandos asincrónica. Para
más información, vea Programación asincrónica: Modelos para aplicaciones de MVVM asincrónicas:
Comandos.

Elección cuidadosa de un contenedor de inyección de dependencia


Los contenedores de inyección de dependencia proporcionan restricciones de rendimiento adicionales a las
aplicaciones móviles. El registro y la resolución de tipos con un contenedor supone un costo de rendimiento, que
los contenedores usan reflexión para crear todos los tipos, especialmente si se reconstruyen las dependencias
para cada navegación de página en la aplicación. Si hay muchas dependencias, o estas son muy amplias, el costo
de la creación puede aumentar significativamente. Además, el registro de tipos, que normalmente se produce
durante el inicio de la aplicación, puede afectar notablemente al tiempo de inicio, según el contenedor que se esté
usando.
Como alternativa, la inyección de dependencia puede optimizarse implementándola manualmente mediante
factorías.

Creación de aplicaciones Shell


Las aplicaciones de Xamarin.Forms Shell proporcionan una experiencia de navegación bien fundamentada basada
en controles flotantes y pestañas. Si la experiencia de usuario de la aplicación se puede implementar con Shell, es
recomendable hacerlo. Las aplicaciones Shell ayudan a evitar una experiencia de inicio deficiente, ya que las
páginas se crean bajo demanda en respuesta a la navegación, en lugar de durante el inicio de la aplicación. Esto
sucede con las aplicaciones que usan "TabbedPage". Para obtener más información, consulte Xamarin.Forms Shell.

Uso de CollectionView en lugar de ListView


CollectionView es una vista para presentar listas de datos con diferentes especificaciones de diseño. Proporciona
una alternativa a ListView más flexible y con mejor rendimiento. Para obtener más información, vea
CollectionView de Xamarin.Forms.

Optimización del rendimiento de ListView


Al usar ListView hay una serie de experiencias de usuario que deben optimizarse:
Inicialización : el intervalo de tiempo que comienza cuando se crea el control y que termina cuando los
elementos se muestran en pantalla.
Desplazamiento : la capacidad de desplazarse por la lista y garantizar que la interfaz de usuario no se retrasa
con los gestos de toque.
Interacción para agregar, eliminar y seleccionar elementos.
El control ListView requiere una aplicación para proporcionar datos y plantillas de celda. La forma de
conseguirlo tendrá un gran impacto en el rendimiento del control. Para más información, vea Rendimiento de
ListView.

Optimizar los recursos de imagen


Mostrar recursos de imagen puede aumentar considerablemente la superficie de memoria de la aplicación. Por
tanto, solo se deberían crear cuando fuera necesario y deberían liberarse en cuanto la aplicación no los necesitara.
Por ejemplo, si una aplicación muestra una imagen mediante la lectura de sus datos desde una secuencia,
asegúrese de que esa secuencia se crea solo cuando sea necesario y que se libera cuando ya no es necesaria. Esto
se consigue mediante la creación de la secuencia cuando se crea la página, o cuando se desencadena el evento
Page.Appearing y, después, mediante la eliminación de la secuencia cuando se desencadena el evento
Page.Disappearing .
Al descargar una imagen para mostrar con el método ImageSource.FromUri , guarde en la memoria caché la
imagen descargada asegurándose de que la propiedad UriImageSource.CachingEnabled está establecida en true .
Para más información, vea Trabajar con imágenes.
Para más información, vea Optimizar los recursos de imagen.

Reducción del tamaño del árbol visual


Reducir el número de elementos en una página hará que la página se procese más rápido. Hay dos técnicas
principales para conseguir esto. La primera es ocultar los elementos que no están visibles. La propiedad
IsVisible de cada elemento determina si el elemento debe ser parte del árbol visual o no. Por tanto, si un
elemento no es visible porque está oculto detrás de otros elementos, quite el elemento o establezca su propiedad
IsVisible en false .

La segunda técnica es quitar los elementos innecesarios. Por ejemplo, en el ejemplo de código siguiente se
muestra un diseño de página que contiene varios objetos Label :

<StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</StackLayout>
</StackLayout>

Se puede mantener el mismo diseño de página con un número reducido de elementos, como se muestra en el
ejemplo de código siguiente:

<StackLayout Padding="20,35,20,20" Spacing="25">


<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</StackLayout>

Reducción del tamaño del diccionario de recursos de aplicación


Todos los recursos que se usan en la aplicación se deben almacenar en el diccionario de recursos de la aplicación
para evitar la duplicación. Esto facilita reducir la cantidad de código XAML que tiene que analizarse en la
aplicación. El siguiente ejemplo de código muestra el recurso HeadingLabelStyle , que se usa en toda la aplicación,
por lo que se define en el diccionario de recursos de la aplicación:
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Resources.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="HeadingLabelStyle" TargetType="Label">
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

Pero el código de XAML que es específico de una página no debería incluirse en el diccionario de recursos de la
aplicación, dado que los recursos se analizarán en el inicio de la aplicación en lugar de cuando los solicite una
página. Si una página que no es la página de inicio usa un recurso, se debe colocar en el diccionario de recursos
para esa página, para así reducir el código de XAML que se analiza cuando se inicia la aplicación. El siguiente
ejemplo de código muestra el recurso HeadingLabelStyle , que solo se usa en una página, por lo que se define en
el diccionario de recursos de la página:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.HomePage"
Padding="0,20,0,0">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="HeadingLabelStyle" TargetType="Label">
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>

Para obtener más información sobre los dominios de aplicación, vea Estilos de XAML.

Uso del patrón de presentador personalizado


La mayoría de las clases de representador de Xamarin.Forms exponen el método OnElementChanged , al que se
llama cuando se crea un control personalizado de Xamarin.Forms para representar el control nativo
correspondiente. Las clases de representador personalizadas, en cada proyecto de la plataforma, invalidan
después este método para crear instancias y personalizar el control nativo. El método SetNativeControl se usa
para crear una instancia del control nativo y este método también asignará la referencia de control a la propiedad
Control .

Aun así, en algunos casos se puede llamar al método OnElementChanged varias veces. Por lo tanto, para evitar
pérdidas de memoria que pueden afectar al rendimiento, debe tener cuidado al crear una instancia de un nuevo
control nativo. El enfoque que usar al crear instancias de un nuevo control nativo en un presentador
personalizado se muestra en el ejemplo de código siguiente:
protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e)
{
base.OnElementChanged (e);

if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}

if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control with the SetNativeControl method
}
// Configure the control and subscribe to event handlers
}
}

Solo se debe crear una instancia de un nuevo control nativo una vez, cuando la propiedad Control es null .
Además, solo se debe crear el control, configurarlo y suscribir los controladores de eventos cuando se adjunta el
presentador personalizado a un nuevo elemento de Xamarin.Forms. De forma similar, solo se debe cancelar la
suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que está asociado
el presentador. Adoptar este enfoque facilita crear un presentador personalizado de rendimiento eficaz que no
sufra pérdidas de memoria.

IMPORTANT
El método SetNativeControl solo se debe invocar si la propiedad e.NewElement no es null y la propiedad Control
es null .

Para más información sobre presentadores personalizados, vea Personalización de controles en cada plataforma.

Vínculos relacionados
Rendimiento multiplataforma
Compilación de XAML
Enlaces compilados
Representadores rápidos
Compresión de diseño
Xamarin.Forms Shell
CollectionView de Xamarin.Forms
Rendimiento de ListView
Optimizar los recursos de imagen
Estilos XAML
Personalización de controles en cada plataforma
Reinicio rápido de Xamarin (versión preliminar)
18/12/2020 • 7 minutes to read • Edit Online

El reinicio rápido de Xamarin permite probar rápidamente los cambios en la aplicación durante el desarrollo,
incluidas las ediciones de código de varios archivos, los recursos y las referencias. Inserta los cambios nuevos en el
grupo de aplicaciones existente en el destino de depuración, lo que genera un ciclo de compilación e
implementación mucho más rápido.

IMPORTANT
En este momento, el reinicio rápido de Xamarin está disponible en la versión estable de Visual Studio 2019 16.5 y es
compatible con aplicaciones iOS en las que se usa :::no-loc(Xamarin.Forms):::. La compatibilidad con Visual Studio para Mac y
aplicaciones que no son de :::no-loc(Xamarin.Forms)::: está en la hoja de ruta.

Requisitos
Visual Studio 2019, versión 16.5
iTunes (64 bits)
Cuenta de desarrollador de Apple e inscripción pagada al Programa para Desarrolladores de Apple

Instalación inicial
NOTE
El reinicio rápido de Xamarin está deshabilitado de forma predeterminada en la versión preliminar. Puede habilitarlo en
Herramientas > Opciones > Entorno > Características de versión preliminar > Habilitar el reinicio rápido de
Xamarin .

1. Asegúrese de que el proyecto de iOS está establecido como proyecto de inicio y la configuración de
compilación en Depuración|iPhone .
a. Si se trata de un proyecto existente, vaya a Compilar > Configuration Manager... y asegúrese de que
Implementación está habilitado para el proyecto de iOS.
2. Seleccione y haga clic en Dispositivo local en la barra de herramientas para iniciar el Asistente para la
instalación:

3. Si iTunes no está instalado, haga clic en Descargar iTunes para descargar el instalador. Haga clic en
Siguiente cuando se complete la instalación de iTunes.
4. Conecte un dispositivo iOS al equipo. Si un dispositivo ya estaba conectado, desconéctelo y vuelva a
conectarlo. El nombre del dispositivo aparecerá en el asistente una vez que se haya detectado. Haga clic en
Siguiente .
5. Escriba las credenciales de la cuenta de desarrollador de Apple y haga clic en Siguiente .
6. Seleccione un equipo de desarrollo mediante el menú desplegable para habilitar el aprovisionamiento
automático en el proyecto. Haga clic en Finalizar .

NOTE
Se recomienda el uso del aprovisionamiento automático para que se puedan configurar fácilmente dispositivos iOS
adicionales para la implementación. Pero puede deshabilitarlo y seguir usando el aprovisionamiento manual si los perfiles de
aprovisionamiento correctos están presentes.

Uso del reinicio rápido de Xamarin


Después de la instalación inicial, el dispositivo conectado aparecerá en el menú desplegable de destino de
depuración. Para depurar la aplicación, seleccione el dispositivo en la lista desplegable y haga clic en el botón
Ejecutar . Es posible que vea un mensaje de Visual Studio en el que se le pide que inicie manualmente la aplicación
en el dispositivo para poder iniciar la sesión de depuración.
Puede realizar modificaciones en los archivos de código durante la depuración y después presionar el botón
Reiniciar de la barra de herramientas de depuración, o bien Ctrl+Mayús+F5 , para reiniciar la sesión de
depuración con los nuevos cambios aplicados:

También puede usar el símbolo de preprocesador HOTRESTART para impedir que se ejecute cierto código al depurar
con el reinicio rápido de Xamarin.

Limitaciones
Actualmente solo se admiten aplicaciones iOS compiladas con :::no-loc(Xamarin.Forms)::: y dispositivos iOS.
Solo se admiten dispositivos iOS de 64 bits. A partir de iOS 11, Apple ya no permite ejecutar aplicaciones iOS en
la arquitectura de 32 bits (dispositivos anteriores a iPhone 5s).
Los archivos de Storyboard y XIB no se admiten y es posible que la aplicación se bloquee si intenta cargarlos en
tiempo de ejecución. Use el símbolo de preprocesador HOTRESTART para evitar que se ejecute el código.
No se admiten marcos ni bibliotecas de iOS estáticos y es posible que vea errores en tiempo de ejecución o
bloqueos si la aplicación intenta cargarlos. Use el símbolo de preprocesador HOTRESTART para evitar que se
ejecute el código. Las bibliotecas de iOS dinámicas se admiten.
No se puede usar el reinicio rápido de Xamarin para crear paquetes de aplicaciones para la publicación. Seguirá
necesitando un equipo Mac a fin de realizar una compilación, firma e implementación completas para la
aplicación en producción.
Los catálogos de recursos actualmente no se admiten. Al usar el reinicio rápido, la aplicación mostrará el icono y
la pantalla de inicio predeterminados para las aplicaciones de Xamarin. Cuando se emparejan con un equipo
Mac, o se desarrollan en un equipo Mac, los catálogos de recursos funcionarán.

Solucionar problemas
Hay un problema conocido en el que tener habilitadas compilaciones específicas del dispositivo impide que la
aplicación entre en el modo de depuración. La solución consiste en deshabilitarlo en Propiedades >
Compilación de iOS y reintentar la depuración. Este problema se corregirá en futuras versiones.
Si la aplicación ya está en el dispositivo, es posible que se produzca un error AMDeviceStartHouseArrestService al
intentar realizar la implementación con el reinicio rápido. La solución alternativa consiste en desinstalar la
aplicación en el dispositivo y volver a implementarla.
Si escribe un identificador de Apple que no forme parte del Programa para Desarrolladores de Apple, podría
mostrarse el siguiente error:
Authentication Error. Xcode 7.3 or later is required to continue developing with your Apple ID . Debe tener
una cuenta de desarrollador de Apple válida para usar el reinicio rápido de Xamarin en dispositivos iOS.
Para notificar problemas adicionales, use la herramienta de comentarios en Ayuda > Enviar comentarios > Notificar
un problema.
Introducción a la distribución de aplicaciones
Xamarin.iOS
15/04/2020 • 5 minutes to read • Edit Online

Este documento contiene información general sobre las técnicas de distribución que están disponibles para las
aplicaciones Xamarin.iOS y actúa como un punto de partida a documentos más detallados sobre el tema.
Una vez que se ha desarrollado una aplicación de Xamarin.iOS, el siguiente paso del ciclo de vida de desarrollo de
software es distribuirla a los usuarios, como se muestra en la sección destacada del siguiente diagrama:

Apple proporciona los siguientes métodos para distribuir una aplicación de iOS:
App Store
Interna (Enterprise)
Ad Hoc
Aplicaciones personalizadas para empresas
Todos estos escenarios requieren que las aplicaciones se aprovisionen mediante el correspondiente perfil de
aprovisionamiento. Los perfiles de aprovisionamiento son archivos que contienen información de firma de código,
así como la identidad de la aplicación y el mecanismo de distribución previsto. También contienen información
sobre en qué dispositivos se puede implementar la aplicación para la distribución que no se realice a través del App
Store.

Distribución a través del App Store


IMPORTANT
Apple ha comunicado que, a partir de marzo de 2019, las aplicaciones y actualizaciones que se envíen al App Store deberán
haberse compilado con el SDK de iOS 12.1 o posterior, incluido en Xcode 10.1 y versiones posteriores. Las aplicaciones
también deberán admitir los tamaños de pantalla del iPhone XS y el iPad Pro de 12.9".

Se trata de la forma principal mediante la que se distribuyen las aplicaciones de iOS a los consumidores en
dispositivos iOS. Todas las aplicaciones que se envían a la App Store requieren la aprobación de Apple.
Las aplicaciones se envían a la App Store a través de un portal llamado iTunes Connect. La guía Configurar la
aplicación en iTunes Connect proporciona más información sobre cómo configurar y usar este portal para preparar
una aplicación de Xamarin.iOS para su publicación en el App Store.
Es importante tener en cuenta que solo los desarrolladores que pertenecen al Programa para desarrolladores
de Apple tienen acceso a iTunes Connect. Los miembros del Programa para desarrolladores empresariales
de Apple no tienen acceso.
Para obtener más información, visite la guía Distribución a través del App Store.

Distribución interna
A veces denominada Distribución empresarial, la distribución interna permite a los miembros del Programa para
desarrolladores empresariales de Apple distribuir aplicaciones internamente a otros miembros de la misma
organización. La distribución interna tiene las ventajas de no requerir una revisión de la App Store y no tener
ningún límite en el número de dispositivos en los que se puede instalar una aplicación. Sin embargo, es importante
tener en cuenta que los miembros del Programa para desarrolladores empresariales de Apple no tienen
acceso a iTunes Connect y, por lo tanto, el licenciatario es responsable de distribuir la aplicación.
Para obtener más información sobre cómo configurar y cómo distribuir una aplicación de forma interna, consulte la
Guía de distribución interna.

Distribución ad hoc
Los usuarios pueden probar las aplicaciones de Xamarin.iOS a través de la distribución ad hoc, que está disponible
tanto en el Programa para desarrolladores de Apple y el Programa para desarrolladores empresariales
de Apple , y permite realizar la prueba en un máximo de 100 dispositivos iOS. El mejor caso de uso para la
distribución ad hoc es la distribución dentro de una empresa cuando iTunes Connect no es una opción.
Para obtener más información sobre cómo configurar y cómo distribuir una aplicación de forma interna, consulte la
Guía de distribución ad hoc.

Aplicaciones personalizadas para empresas


Apple permite la distribución personalizada de aplicaciones a empresas e instituciones educativas. Revise la Guía de
usuario de Apple Business Manager para obtener información.

Vínculos relacionados
Distribución a través del App Store
Configuración de una aplicación en iTunes Connect
Publicación en App Store
Distribución interna
Distribución ad hoc
Archivo iTunesMetadata.plist
Compatibilidad con IPA
Solución de problemas
Publicar una aplicación
18/12/2020 • 6 minutes to read • Edit Online

Después de crear una gran aplicación, la gente querrá usarla. En este artículo se describen los pasos implicados en
la distribución pública de una aplicación creada con Xamarin.Android a través de canales como el correo
electrónico, un servidor web privado, Google Play o la Tienda Apps de Amazon para Android.

Información general
El paso final en el desarrollo de una aplicación de Xamarin.Android es publicar la aplicación. La publicación es el
proceso de compilación de una aplicación de Xamarin.Android para que esté lista para que los usuarios la instalen
en sus dispositivos, e implica dos tareas esenciales:
Preparación para la publicación –: se crea una versión de lanzamiento de la aplicación que se puede
implementar en dispositivos Android (consulte Preparar una aplicación para su lanzamiento para obtener
más información sobre la preparación de lanzamiento).
**** Distribución–: la versión de lanzamiento de una aplicación estará disponible a través de uno o varios
canales de distribución.
El siguiente diagrama ilustra los pasos de publicación de una aplicación de Xamarin.Android:

Como puede observarse en el diagrama anterior, la preparación es la misma independientemente del método de
distribución que se utilice. Hay varias formas de poner una aplicación de Android a disposición de los usuarios:
**** A través de un sitio web–: una aplicación Xamarin.Android puede estar disponible para su descarga en un
sitio web, desde el que los usuarios pueden instalarla haciendo clic en un vínculo.
**** Por correo electrónico–: los usuarios pueden instalar una aplicación Xamarin.Android desde su correo
electrónico. La aplicación se instalará cuando se abra el archivo adjunto con un dispositivo Android.
A través de una tienda : existen varias tiendas de aplicaciones para la distribución, como Google Play o la
Tienda Apps de Amazon para Android.
Utilizar una tienda establecida es la manera más común para publicar una aplicación, ya que proporciona la mayor
cobertura de mercado y el máximo control sobre la distribución. Sin embargo, publicar una aplicación a través de
una tienda requiere un esfuerzo adicional.
Varios canales pueden distribuir una aplicación de Xamarin.Android simultáneamente. Por ejemplo, una aplicación
puede publicarse en Google Play, la Tienda Apps de Amazon para Android y también se puede descargar desde un
servidor web.
Los otros dos métodos de distribución (descarga o correo electrónico) son más útiles para un subconjunto de
usuarios, como un entorno empresarial o una aplicación que solo está destinada a un conjunto de usuarios
pequeño o controlado. La distribución a través de un servidor y de correo electrónico también son modelos de
publicación más sencillos, que requieren menos preparación para publicar una aplicación.
El programa de distribución de aplicaciones de Amazon Mobile permite a los desarrolladores de aplicaciones
móviles distribuir y vender sus aplicaciones en Amazon. Los usuarios pueden descubrir y comprar aplicaciones en
sus dispositivos Android mediante la aplicación Tienda Apps de Amazon. A continuación aparece una captura de
pantalla de la Tienda Apps de Amazon en un dispositivo Android:
Google Play es, posiblemente, la tienda más completa y popular para aplicaciones Android. Google Play permite a
los usuarios detectar, descargar, evaluar y pagar por aplicaciones haciendo clic en un solo icono en su dispositivo o
su equipo. Google Play también proporciona herramientas para ayudar en el análisis de ventas y las tendencias del
mercado y controlar qué dispositivos y usuarios pueden descargar una aplicación. A continuación aparece una
captura de pantalla de Google Play en un dispositivo Android:
En esta sección se muestra cómo cargar la aplicación en una tienda, como Google Play, junto con el material
promocional adecuado. Se describen los archivos de expansión del APK y se ofrece información general conceptual
sobre qué son y cómo funcionan. También se describen los servicios de Licencias de Google. Por último, se
presentan medios alternativos de distribución, incluido el uso de un servidor web HTTP, la distribución por correo
electrónico simple y Amazon Appstore para Android.

Vínculos relacionados
HelloWorldPublishing (ejemplo)
Proceso de compilación
Vinculación
Obtener una clave de API de Google Maps
Implementación mediante Visual Studio App Center
Firma de aplicaciones
Publicación en Google Play
Licencias de aplicaciones de Google
Android.Play.ExpansionLibrary
Portal de distribución de aplicaciones móviles
Preguntas más frecuentes sobre la distribución de aplicaciones móviles de Amazon
Publicación de aplicaciones Xamarin.Mac en el Mac
App Store
15/04/2020 • 3 minutes to read • Edit Online

Información general
Las aplicaciones de Xamarin.Mac pueden distribuirse de dos maneras diferentes:
Developer ID : las aplicaciones firmadas con un Developer ID se distribuyen fuera del App Store, pero
Gatekeeper las reconoce y les da permiso para que se instalen.
Mac App Store : las aplicaciones deben tener un paquete de instalación, y la aplicación y el programa de
instalación deben estar firmados para enviarse al Mac App Store.
En este documento se explica cómo utilizar Visual Studio para Mac y Xcode para configurar una cuenta de
desarrollador de Apple y configurar un proyecto de Xamarin.Mac para cada tipo de implementación.

Programa para desarrolladores de Mac


Al unirse al Programa para desarrolladores de Mac, el desarrollador tendrá la opción de hacerlo como una persona
o una empresa, como se muestra en la captura de pantalla siguiente:
Elija el tipo de inscripción correcto para su situación.

NOTE
Las selecciones realizadas aquí afectarán al modo en que algunas pantallas aparecen al configurar una cuenta de
desarrollador. Las descripciones y las capturas de pantalla de este documento se realizan desde la perspectiva de una cuenta
de desarrollador personal. En una empresa , algunas opciones solo estarán disponibles para los usuarios del
Administrador del equipo .

Certificados e identificadores
En esta guía se describe el proceso de creación de los certificados y los identificadores necesarios para publicar una
aplicación Xamarin.Mac.
Creación de un perfil de aprovisionamiento
En esta guía se describe el proceso de creación de los perfiles de aprovisionamiento necesarios para publicar una
aplicación Xamarin.Mac.
Configuración de aplicaciones de Mac
En esta guía se describe el proceso de configuración de una aplicación Xamarin.Mac para su publicación.
Firmar con Developer ID
En esta guía se explican los pasos para firmar una aplicación Xamarin.Mac con Developer ID para su publicación.
Paquete para el Mac App Store
En esta guía se describe el proceso de creación de una aplicación Xamarin.Mac para su publicación en el Mac App
Store.
Cargar en el Mac App Store
En esta guía se describe el proceso de carga de una aplicación Xamarin.Mac para su publicación en el Mac App
Store.

Vínculos relacionados
Instalación
Ejemplo de Hello, Mac
Identificador del desarrollador y equipo selector
Xamarin.Formsconceptos avanzados & Internals
18/12/2020 • 2 minutes to read • Edit Online

Obtenga información sobre los conceptos avanzados y los elementos internos de Xamarin.Forms .

Jerarquía de clases de controles


Obtenga información sobre la jerarquía de tipos que se usa para crear la interfaz de usuario de una Xamarin.Forms
aplicación.

Resolución de dependencias
Obtenga información sobre cómo insertar un método de resolución de dependencias en Xamarin.Forms , para que
una aplicación tenga control sobre la creación y la duración de los representadores, efectos e DependencyService
implementaciones personalizados.

Marcas experimentales
Xamarin.Formslas marcas experimentales permiten al equipo de ingeniería enviar nuevas características a los
usuarios con más rapidez, al mismo tiempo que se pueden cambiar las API de características antes de que pasen a
una versión estable.

Representadores rápidos
Obtenga información acerca de los representadores rápidos, lo que reduce la inflación y los costos de
representación de un Xamarin.Forms control en Android mediante el acoplamiento de la jerarquía de control nativa
resultante.

Vínculo de origen
Obtenga información sobre cómo depurar la aplicación en el Xamarin.Forms código fuente.
Xamarin.FormsJerarquía de clases de controles
18/12/2020 • 2 minutes to read • Edit Online

Xamarin.Formsse compone de cientos de tipos, en varios espacios de nombres. Los desarrolladores deben estar
familiarizados con la jerarquía de tipos que se usan para crear la interfaz de usuario de una Xamarin.Forms
aplicación, que residen en el Xamarin.Forms espacio de nombres.
Estos tipos se pueden dividir en páginas, diseños, vistas y celdas. Una Xamarin.Forms Página suele ocupar toda la
pantalla y todos los tipos de página derivan de la Page clase. Las páginas normalmente contienen un diseño y
todos los tipos de diseño derivan de la Layout clase. Normalmente, un diseño contiene vistas y posiblemente
otros diseños, y todos los tipos de vista se derivan en última instancia de la View clase. Por último, las celdas son
controles especializados que se usan en Mostrar datos en los TableView ListView controles y. En última instancia,
las páginas, los diseños, las vistas y las celdas se derivan de la Element clase.
En el diagrama de clases siguiente se muestra la jerarquía de tipos que se suelen usar para compilar una interfaz
de usuario en Xamarin.Forms :

NOTE
Aquíse puede descargar una versión de alta resolución del diagrama de clases. Sin embargo, tenga en cuenta que el
diagrama solo muestra un tipo de Shell.

Vínculos relacionados
Xamarin.FormsReferencia de controles
Resolución de dependencias :::no-
loc(Xamarin.Forms):::
18/12/2020 • 19 minutes to read • Edit Online

Descargar el ejemplo
En este artículo se explica cómo insertar un método de resolución de dependencias en :::no-loc(Xamarin.Forms):::
para que el contenedor de inserción de dependencias de una aplicación controle la creación y la duración de los
representadores personalizados, los efectos y las implementaciones de DependencyService. Los ejemplos de
código de este artículo se han tomado del ejemplo de resolución de dependencias mediante contenedores .
En el contexto de una :::no-loc(Xamarin.Forms)::: aplicación que usa el patrón Model-View-ViewModel (MVVM), se
puede usar un contenedor de inserción de dependencias para registrar y resolver modelos de vista, así como para
registrar servicios e insertarlos en modelos de vista. Durante la creación del modelo de vista, el contenedor
inserta las dependencias necesarias. Si no se han creado estas dependencias, el contenedor crea y resuelve
primero las dependencias. Para obtener más información sobre la inserción de dependencias, incluidos ejemplos
de inserción de dependencias en los modelos de vista, consulte inserción de dependencias.
Normalmente, el control sobre la creación y la duración de los tipos de los proyectos de plataforma se realiza
mediante :::no-loc(Xamarin.Forms)::: , que utiliza el Activator.CreateInstance método para crear instancias de
representadores, efectos e DependencyService implementaciones personalizados. Desafortunadamente, esto
limita el control del desarrollador sobre la creación y la duración de estos tipos, y la capacidad de insertar
dependencias en ellos. Este comportamiento se puede cambiar insertando un método de resolución de
dependencia en :::no-loc(Xamarin.Forms)::: que controla cómo se crearán los tipos, ya sea por el contenedor de
inserción de dependencias de la aplicación o por :::no-loc(Xamarin.Forms)::: . Sin embargo, tenga en cuenta que no
hay ningún requisito para insertar un método de resolución de dependencias en :::no-loc(Xamarin.Forms)::: . :::no-
loc(Xamarin.Forms)::: continuará creando y administrando la duración de los tipos en los proyectos de plataforma
si no se inserta un método de resolución de dependencias.

NOTE
Aunque este artículo se centra en la inserción de un método de resolución de dependencias en :::no-loc(Xamarin.Forms):::
que resuelve los tipos registrados mediante un contenedor de inserción de dependencias, también es posible insertar un
método de resolución de dependencias que use métodos de generador para resolver los tipos registrados. Para obtener
más información, vea el ejemplo de resolución de dependencias mediante métodos de generador .

Insertar un método de resolución de dependencias


La DependencyResolver clase proporciona la capacidad de insertar un método de resolución de dependencias en
:::no-loc(Xamarin.Forms)::: , mediante el ResolveUsing método. A continuación, cuando :::no-loc(Xamarin.Forms):::
necesita una instancia de un tipo determinado, el método de resolución de dependencias tiene la oportunidad de
proporcionar la instancia. Si el método de resolución de dependencias devuelve null para un tipo solicitado,
:::no-loc(Xamarin.Forms)::: vuelve a intentar crear la instancia de tipo en sí mediante el Activator.CreateInstance
método.
En el ejemplo siguiente se muestra cómo establecer el método de resolución de dependencias con el
ResolveUsing método:
using Autofac;
using :::no-loc(Xamarin.Forms):::.Internals;
...

public partial class App : Application


{
// IContainer and ContainerBuilder are provided by Autofac
static IContainer container;
static readonly ContainerBuilder builder = new ContainerBuilder();

public App()
{
...
DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) :
null);
...
}
...
}

En este ejemplo, el método de resolución de dependencias se establece en una expresión lambda que usa el
contenedor de inserción de dependencias Autofac para resolver los tipos que se han registrado con el contenedor.
De lo contrario, se null devolverá, lo que provocará que se :::no-loc(Xamarin.Forms)::: intente resolver el tipo.

NOTE
La API usada por un contenedor de inserción de dependencias es específica del contenedor. En los ejemplos de código de
este artículo se usa Autofac como contenedor de inserción de dependencias, que proporciona los IContainer
ContainerBuilder tipos y. También se podrían usar contenedores de inyección de dependencia alternativos, pero usarían
API diferentes de las que se presentan aquí.

Tenga en cuenta que no hay ningún requisito para establecer el método de resolución de dependencias durante el
inicio de la aplicación. Se puede establecer en cualquier momento. La única restricción es que :::no-
loc(Xamarin.Forms)::: necesita conocer el método de resolución de dependencias en el momento en que la
aplicación intenta consumir tipos almacenados en el contenedor de inserción de dependencias. Por lo tanto, si hay
servicios en el contenedor de inserción de dependencias que la aplicación necesitará durante el inicio, el método
de resolución de dependencias tendrá que establecerse en una fase temprana del ciclo de vida de la aplicación.
Del mismo modo, si el contenedor de inserción de dependencias administra la creación y la duración de un
determinado Effect , :::no-loc(Xamarin.Forms)::: deberá conocer el método de resolución de dependencias antes
de intentar crear una vista que lo use Effect .

WARNING
El registro y la resolución de tipos con un contenedor de inserción de dependencias tiene un costo de rendimiento debido al
uso del contenedor de la reflexión para crear cada tipo, especialmente si se reconstruyen las dependencias para cada
navegación de página en la aplicación. Si hay muchas dependencias, o estas son muy amplias, el costo de la creación puede
aumentar significativamente.

Registrar tipos
Los tipos se deben registrar con el contenedor de inserción de dependencias para poder resolverlos a través del
método de resolución de dependencias. En el ejemplo de código siguiente se muestran los métodos de registro
que la aplicación de ejemplo expone en la App clase para el contenedor Autofac:
using Autofac;
using Autofac.Core;
...

public partial class App : Application


{
static IContainer container;
static readonly ContainerBuilder builder = new ContainerBuilder();
...

public static void RegisterType<T>() where T : class


{
builder.RegisterType<T>();
}

public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
{
builder.RegisterType<T>().As<TInterface>();
}

public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type,


string param2Name) where T : class
{
builder.RegisterType<T>()
.WithParameters(new List<Parameter>()
{
new TypedParameter(param1Type, param1Value),
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
(pi, ctx) => ctx.Resolve(param2Type))
});
}

public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type


param2Type, string param2Name) where TInterface : class where T : class, TInterface
{
builder.RegisterType<T>()
.WithParameters(new List<Parameter>()
{
new TypedParameter(param1Type, param1Value),
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
(pi, ctx) => ctx.Resolve(param2Type))
}).As<TInterface>();
}

public static void BuildContainer()


{
container = builder.Build();
}
...
}

Cuando una aplicación usa un método de resolución de dependencias para resolver tipos de un contenedor, los
registros de tipo se suelen realizar desde proyectos de plataforma. Esto permite a los proyectos de plataforma
registrar los tipos de representadores, efectos e DependencyService implementaciones personalizados.
Después del registro de tipos de un proyecto de plataforma, el IContainer objeto se debe compilar, lo que se
logra mediante una llamada al BuildContainer método. Este método invoca Build el método de Autofac en la
ContainerBuilder instancia de, que crea un nuevo contenedor de inserción de dependencias que contiene los
registros que se han realizado.
En las secciones siguientes, Logger se inserta una clase que implementa la ILogger interfaz en constructores de
clase. La Logger clase implementa la funcionalidad de registro simple mediante el Debug.WriteLine método y se
usa para mostrar cómo se pueden insertar los servicios en representadores, efectos e DependencyService
implementaciones personalizados.
Registrar representadores personalizados
La aplicación de ejemplo incluye una página que reproduce vídeos Web, cuyo origen XAML se muestra en el
ejemplo siguiente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
...>
<video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

VideoPlayerUna clase implementa la vista en cada plataforma VideoPlayerRenderer , que proporciona la


funcionalidad para reproducir el vídeo. Para obtener más información sobre estas clases de representadores
personalizados, vea implementar un reproductor de vídeo.
En iOS y en el Plataforma universal de Windows (UWP), las VideoPlayerRenderer clases tienen el siguiente
constructor, que requiere un ILogger argumento:

public VideoPlayerRenderer(ILogger logger)


{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

En todas las plataformas, escriba el registro con el contenedor de inserción de dependencias se realiza mediante
el RegisterTypes método, que se invoca antes de la plataforma que carga la aplicación con el
LoadApplication(new App()) método. En el ejemplo siguiente se muestra el RegisterTypes método en la
plataforma iOS:

void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
App.BuildContainer();
}

En este ejemplo, el tipo concreto se registra mediante una asignación en su tipo de interfaz y el
Logger
VideoPlayerRenderer tipo se registra directamente sin una asignación de interfaz. Cuando el usuario navega a la
página que contiene la VideoPlayer vista, se invocará el método de resolución de dependencias para resolver el
VideoPlayerRenderer tipo del contenedor de inserción de dependencias, que también resolverá e insertará el
Logger tipo en el VideoPlayerRenderer constructor.

El constructor de la plataforma Android es ligeramente más complicado, ya que requiere un


VideoPlayerRenderer
Context argumento además del ILogger argumento:

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)


{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

En el ejemplo siguiente se muestra el RegisterTypes método en la plataforma Android:


void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>
(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}

En este ejemplo, el App.RegisterTypeWithParameters método registra VideoPlayerRenderer con el contenedor de


inserción de dependencias. El método de registro garantiza que la MainActivity instancia se insertará como
Context argumento y que el tipo se Logger insertará como el ILogger argumento.

Registrar efectos
La aplicación de ejemplo incluye una página que utiliza un efecto de seguimiento táctil para arrastrar BoxView
instancias alrededor de la página. El Effect se agrega a BoxView mediante el código siguiente:

var boxView = new BoxView { ... };


var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

La TouchEffect clase es un RoutingEffect que se implementa en cada plataforma mediante una TouchEffect
clase que es PlatformEffect . La TouchEffect clase Platform proporciona la funcionalidad para arrastrar el
BoxView alrededor de la página. Para obtener más información sobre estas clases de efectos, vea invocación de
eventos a partir de efectos.
En todas las plataformas, la TouchEffect clase tiene el siguiente constructor, que requiere un ILogger
argumento:

public TouchEffect(ILogger logger)


{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

En todas las plataformas, escriba el registro con el contenedor de inserción de dependencias se realiza mediante
el RegisterTypes método, que se invoca antes de la plataforma que carga la aplicación con el
LoadApplication(new App()) método. En el ejemplo siguiente se muestra el RegisterTypes método en la
plataforma Android:

void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<TouchTracking.Droid.TouchEffect>();
App.BuildContainer();
}

En este ejemplo, el Logger tipo concreto se registra mediante una asignación en su tipo de interfaz y el
TouchEffect tipo se registra directamente sin una asignación de interfaz. Cuando el usuario navega a la página
que contiene una BoxView instancia de que tiene TouchEffect adjunta el objeto, se invocará el método de
resolución de dependencias para resolver el TouchEffect tipo de plataforma del contenedor de inserción de
dependencias, que también resolverá e insertará el Logger tipo en el TouchEffect constructor.
Registrar implementaciones de DependencyService
La aplicación de ejemplo incluye una página que usa DependencyService implementaciones en cada plataforma
para permitir que el usuario elija una foto de la biblioteca de imágenes del dispositivo. La IPhotoPicker interfaz
define la funcionalidad que implementan las DependencyService implementaciones y se muestra en el ejemplo
siguiente:

public interface IPhotoPicker


{
Task<Stream> GetImageStreamAsync();
}

En cada proyecto de la plataforma, la PhotoPicker clase implementa la IPhotoPicker interfaz mediante las API de
la plataforma. Para obtener más información acerca de estos servicios de dependencia, consulte seleccionar una
foto de la biblioteca de imágenes.
En iOS y UWP, las PhotoPicker clases tienen el siguiente constructor, que requiere un ILogger argumento:

public PhotoPicker(ILogger logger)


{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

En todas las plataformas, escriba el registro con el contenedor de inserción de dependencias se realiza mediante
el RegisterTypes método, que se invoca antes de la plataforma que carga la aplicación con el
LoadApplication(new App()) método. En el ejemplo siguiente se muestra el RegisterTypes método en UWP:

void RegisterTypes()
{
DIContainerDemo.App.RegisterType<ILogger, Logger>();
DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
DIContainerDemo.App.BuildContainer();
}

En este ejemplo, el Logger tipo concreto se registra mediante una asignación en su tipo de interfaz y el
PhotoPicker tipo también se registra a través de una asignación de interfaz.

El PhotoPickerconstructor de la plataforma Android es ligeramente más complicado, ya que requiere un


Context argumento además del ILogger argumento:

public PhotoPicker(Context context, ILogger logger)


{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

En el ejemplo siguiente se muestra el RegisterTypes método en la plataforma Android:

void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context),
this, typeof(ILogger), "logger");
App.BuildContainer();
}

En este ejemplo, el App.RegisterTypeWithParameters método registra PhotoPicker con el contenedor de inserción


de dependencias. El método de registro garantiza que la MainActivity instancia se insertará como Context
argumento y que el tipo se Logger insertará como el ILogger argumento.
Cuando el usuario navega a la página de selección de fotografías y elige seleccionar una foto,
OnSelectPhotoButtonClicked se ejecuta el controlador:

async void OnSelectPhotoButtonClicked(object sender, EventArgs e)


{
...
var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
var stream = await photoPickerService.GetImageStreamAsync();
if (stream != null)
{
image.Source = ImageSource.FromStream(() => stream);
}
...
}

Cuando DependencyService.Resolve<T> se invoca el método, se invocará el método de resolución de dependencias


para resolver el PhotoPicker tipo del contenedor de inserción de dependencias, que también resolverá e
insertará el Logger tipo en el PhotoPicker constructor.

NOTE
El Resolve<T> método se debe usar al resolver un tipo desde el contenedor de inserción de dependencias de la aplicación
a través de DependencyService .

Vínculos relacionados
Resolución de dependencias mediante contenedores (ejemplo)
Inserción de dependencia
Implementación de un reproductor de vídeo
Invocar eventos a partir de efectos
Seleccionar una foto de la biblioteca de imágenes
Xamarin.Formsmarcas experimentales
18/12/2020 • 3 minutes to read • Edit Online

Cuando Xamarin.Forms se implementa una nueva característica, a veces se coloca detrás de una marca
experimental. Esto permite al equipo de ingeniería proporcionar nuevas características con mayor rapidez, al
tiempo que sigue pudiendo cambiar las API de características antes de que pasen a una versión estable.
Después, se quita la marca experimental una vez que la característica se mueve a una versión estable.
Xamarin.Formsincluye las siguientes marcas experimentales:
Brush_Experimental
CarouselView_Experimental
DragAndDrop_Experimental
Expander_Experimental
Markup_Experimental
MediaElement_Experimental
RadioButton_Experimental
Shapes_Experimental
Shell_UWP_Experimental
SwipeView_Experimental

El uso de la funcionalidad que está detrás de una marca experimental requiere habilitar la marca, o marcas, en la
aplicación. Hay dos métodos para habilitar las marcas experimentales:
Habilite la marca experimental, o marcas, en los proyectos de la plataforma.
Habilite la marca experimental, o marcas, en la App clase.

WARNING
Si se utiliza la funcionalidad que está detrás de una marca experimental, sin habilitar la marca, la aplicación producirá una
excepción que indica qué marca debe estar habilitada.

Habilitar marcas en proyectos de plataforma


El Xamarin.Forms.Forms.SetFlags método se puede usar para habilitar una marca experimental en los proyectos
de la plataforma:

Xamarin.Forms.Forms.SetFlags("CarouselView_Experimental");

El SetFlags método debe invocarse en la AppDelegate clase en iOS, en la MainActivity clase en Android y en la
App clase en UWP.

IMPORTANT
La habilitación de una marca experimental en los proyectos de la plataforma debe realizarse antes de que Forms.Init se
invoque el método.
El Xamarin.Forms.Forms.SetFlags método acepta un string argumento de matriz, lo que hace posible habilitar
varias marcas experimentales en una única llamada al método:

Xamarin.Forms.Forms.SetFlags(new string[] { "CarouselView_Experimental", "MediaElement_Experimental",


"SwipeView_Experimental" });

WARNING
Nunca llame al SetFlags método más de una vez, ya que las llamadas subsiguientes sobrescribirán el resultado de las
llamadas anteriores.

Habilitar marcas en la clase de la aplicación


El Device.SetFlags método se puede usar para habilitar una marca experimental en la App clase en el proyecto
de código compartido:

Device.SetFlags(new string[]{ "MediaElement_Experimental" });

El Device.SetFlags método acepta un IReadOnlyList<string> argumento, lo que permite habilitar varias marcas
experimentales en una única llamada al método:

Device.SetFlags(new string[]{ "CarouselView_Experimental", "MediaElement_Experimental",


"SwipeView_Experimental" });

WARNING
Nunca llame al SetFlags método más de una vez, ya que las llamadas subsiguientes sobrescribirán el resultado de las
llamadas anteriores.

Marcas experimentales anteriores


En la tabla siguiente se enumeran las marcas experimentales de las características que ahora están disponibles
con carácter general y la Xamarin.Forms versión en la que se quitó la marca experimental:

M A RC A XA M A RIN . F O RM SEM ISIÓ N

AppTheme_Experimental 4.8

CollectionView_Experimental 4.3

FastRenderers_Experimental 4.0

IndicatorView_Experimental 4,7

Shell_Experimental 4.0

StateTriggers_Experimental 4,7

Visual_Experimental 3.6
Xamarin.FormsRepresentadores rápidos
18/12/2020 • 3 minutes to read • Edit Online

Tradicionalmente, la mayoría de los representadores de control originales en Android se componen de dos


vistas:
Control nativo, como Button o TextView .
Contenedor ViewGroup que controla algunos de los trabajos de diseño, el control de gestos y otras tareas.
Sin embargo, este enfoque tiene una implicación de rendimiento en que se crean dos vistas para cada control
lógico, lo que da como resultado un árbol visual más complejo que requiere más memoria y más
procesamiento para representarse en la pantalla.
Los representadores rápidos reducen los costos de inflación y representación de un Xamarin.Forms control en
una sola vista. Por lo tanto, en lugar de crear dos vistas y agregarlas al árbol de vista, solo se crea una. Esto
mejora el rendimiento al crear menos objetos, lo que, a su vez, significa un árbol de vista menos complejo y
menos uso de memoria (lo que también da lugar a menos pausas de recolección de elementos no utilizados).
Los representadores rápidos están disponibles para los siguientes controles en Xamarin.Forms en Android:
Button
Frame
Image
Label
MediaElement

Funcionalmente, estos representadores rápidos no son diferentes a los representadores heredados. A partir de
Xamarin.Forms 4,0 y versiones posteriores, todas las aplicaciones que tienen como destino
FormsAppCompatActivity usarán estos representadores rápidos de forma predeterminada. Los representadores
de todos los controles nuevos, incluidos ImageButton y CollectionView , usan el enfoque de representador
rápido.
Las mejoras de rendimiento cuando se usan representadores rápidos variarán para cada aplicación, en función
de la complejidad del diseño. Por ejemplo, las mejoras de rendimiento de x2 son posibles al desplazarse a través
de un ListView que contiene miles de filas de datos, donde las celdas de cada fila se componen de controles
que usan representadores rápidos, lo que da como resultado un desplazamiento más suave visible.

NOTE
Se pueden crear representadores personalizados para los representadores rápidos mediante el mismo enfoque que se usa
para los representadores heredados. Para obtener más información, consulte Custom Renderers (Representadores
personalizados).

Compatibilidad con versiones anteriores


Los representadores rápidos se pueden invalidar con los siguientes enfoques:
1. Habilitar los representadores heredados agregando la siguiente línea de código a la MainActivity clase
antes de llamar a Forms.Init :
Forms.SetFlags("UseLegacyRenderers");

2. Usar representadores personalizados que tienen como destino los representadores heredados. Los
representadores personalizados existentes seguirán funcionando con los representadores heredados.
3. Especificar un distinto View.Visual , como Material , que usa representadores diferentes. Para obtener
más información sobre el material visual, vea el Xamarin.Forms material visual.

Vínculos relacionados
Representadores personalizados
Vínculo de origen conXamarin.Forms
18/12/2020 • 4 minutes to read • Edit Online

Xamarin.FormsLos paquetes NuGet incluyen asignaciones de vínculo de origen. El vínculo de origen asigna las
bibliotecas compiladas, contenidas en un paquete NuGet, a un repositorio de código fuente. Visual Studio
descargará los archivos de código fuente durante la depuración y permitirá a los desarrolladores recorrer el código
y habilitar la depuración de paquetes sin compilar desde el origen.
Para obtener más información sobre el uso del vínculo de origen, consulte la documentación de vínculo de origen.

WARNING
Visual Studio 2019 admite el vínculo de origen para el depurador de .net , pero actualmente no admite el vínculo de
origen para el depurador de mono . Por lo tanto, puede usar el vínculo de origen para depurar aplicaciones UWP, pero no
aplicaciones Android o iOS. Al depurar aplicaciones para UWP, debe asegurarse de que los archivos PDB de las bibliotecas que
desea depurar se copian en la carpeta appx en el directorio bin en el que se compila la aplicación.

Habilitación del vínculo de origen


El uso de un vínculo de origen requiere la habilitación de la depuración para el código externo; de lo contrario, el
depurador devolverá las llamadas pasadas al código no contenido en la solución actual. En Visual Studio 2019, se
puede encontrar en el menú Opciones de la sección depuración :

Asegúrese de que Habilitar solo mi código está deshabilitado y que Habilitar la compatibilidad con
vínculos de origen está habilitada.

Habilitación del vínculo de origen


El uso de un vínculo de origen requiere la habilitación de la depuración para el código externo; de lo contrario, el
depurador devolverá las llamadas pasadas al código no contenido en la solución actual. Esta opción puede
encontrarse en la ventana preferencias de la sección depurador :
Asegúrese de que la opción ir a código externo está habilitada.

Depurar Xamarin.Forms mediante vínculo de origen


Si está habilitada la depuración de paquetes externos, Visual Studio usará las asignaciones de vínculo de origen
contenidas en el paquete de NuGet para descargar y recorrer el código fuente externo. Esto se puede probar
estableciendo un punto de interrupción en una llamada a un método proporcionado por Xamarin.Forms :

En función de la configuración especificada en las opciones del depurador , Visual Studio le advertirá de que está
descargando los archivos de origen:

Una vez que permita que Visual Studio Descargue los archivos, el depurador entrará en el código externo.

Almacenamiento en caché de vínculos de origen


El vínculo de origen usa el almacenamiento en caché para el rendimiento. El directorio de almacenamiento en
caché para el vínculo de origen se define en el menú Opciones , en depuración , en la sección símbolos :

Este menú permite especificar el directorio de almacenamiento en caché para todos los símbolos de depuración,
así como borrar la memoria caché si se producen problemas con los símbolos almacenados en caché.

Almacenamiento en caché de vínculos de origen


El vínculo de origen usa el almacenamiento en caché para el rendimiento. El directorio de almacenamiento en
caché para el vínculo de origen en MacOS es /Users/<username>/Library/Caches/VisualStudio/8.0/Symbols . Esta
carpeta contiene subcarpetas que almacenan el repositorio que se usa para descargar los archivos de origen. Si el
repositorio de respaldo de un paquete NuGet ha cambiado, puede que tenga que eliminar manualmente estas
carpetas para actualizar la memoria caché.

Vínculos relacionados
Documentación de vínculo de origen
Vínculo de origen en GitHub
Solución:::no-loc(Xamarin.Forms):::
18/12/2020 • 3 minutes to read • Edit Online

Condiciones de error comunes y cómo resolverlas

Error: "no se puede encontrar una versión de :::no-loc(Xamarin.Forms):::


compatible con..."
Los errores siguientes pueden aparecer en la ventana de la consola del paquete al actualizar todos los paquetes
de NuGet en una :::no-loc(Xamarin.Forms)::: solución o en un :::no-loc(Xamarin.Forms)::: proyecto de aplicación de
Android:

Attempting to resolve dependency 'Xamarin.Android.Support.v7.AppCompat (= 23.3.0.0)'.


Attempting to resolve dependency 'Xamarin.Android.Support.v4 (= 23.3.0.0)'.
Looking for updates for 'Xamarin.Android.Support.v7.MediaRouter'...
Updating 'Xamarin.Android.Support.v7.MediaRouter' from version '23.3.0.0' to '23.3.1.0' in project
'Todo.Droid'.
Updating 'Xamarin.Android.Support.v7.MediaRouter 23.3.0.0' to 'Xamarin.Android.Support.v7.MediaRouter 23.3.1.0'
failed.
Unable to find a version of ':::no-loc(Xamarin.Forms):::' that is compatible with
'Xamarin.Android.Support.v7.MediaRouter 23.3.0.0'.

¿Qué causa este error?


Visual Studio para Mac (o Visual Studio) pueden indicar que las actualizaciones están disponibles para el :::no-
loc(Xamarin.Forms)::: paquete de NuGet y todas sus dependencias. En Xamarin Studio, el nodo paquetes de la
solución podría ser similar al siguiente (los números de versión pueden ser diferentes):

Este error puede producirse si intenta actualizar todos los paquetes.


Esto se debe a que con los proyectos de Android establecidos en una versión de destino o compilación de Android
6,0 (API 23) o inferior, :::no-loc(Xamarin.Forms)::: tiene una dependencia fuerte en versiones específicas de los
paquetes de soporte de Android. Aunque las versiones actualizadas de esos paquetes pueden estar disponibles,
:::no-loc(Xamarin.Forms)::: no es necesariamente compatible con ellas.
En este caso, solo debe actualizar el :::no-loc(Xamarin.Forms)::: paquete, ya que se asegurará de que las
dependencias permanecen en versiones compatibles. Otros paquetes que ha agregado al proyecto también se
pueden actualizar de forma individual siempre que no hagan que los paquetes de soporte de Android se actualicen.
NOTE
Si usa :::no-loc(Xamarin.Forms)::: 2.3.4 o posterior y la versión de destino o compilación del proyecto de Android está
establecida en Android 7,0 (API 24) o superior, las dependencias fuertes mencionadas anteriormente ya no se aplican y puede
actualizar los paquetes de soporte independientemente del :::no-loc(Xamarin.Forms)::: paquete.

Corregir: quitar todos los paquetes y volver a agregar:::no -loc(Xamarin.Forms):::


Si los paquetes de Xamarin. Android. suppor t se han actualizado a versiones incompatibles, la solución más
sencilla consiste en:
1. Elimine manualmente todos los paquetes NuGet en el proyecto de Android y, a continuación,
2. Vuelva a agregar el :::no-loc(Xamarin.Forms)::: paquete.
Se descargarán automáticamente las versiones correctas de los demás paquetes.
Para ver un vídeo de este proceso, consulte esta publicación de foros.
Xamarin.FormsPreguntas más frecuentes
18/12/2020 • 2 minutes to read • Edit Online

¿Puedo actualizar la Xamarin.Forms plantilla predeterminada a un


paquete de NuGet más reciente?
En esta guía se usa la Xamarin.Forms plantilla de biblioteca de .net Standard como ejemplo, pero el mismo método
general también funcionará para la Xamarin.Forms plantilla de proyecto compartido.

¿Por qué no funciona el diseñador XAML de Visual Studio para


Xamarin.Forms los archivos XAML?
Xamarin.Formsno admite actualmente los diseñadores visuales para los archivos XAML.

Error de compilación de Android: error inesperado en la tarea


"LinkAssemblies"
Es posible que vea un mensaje de error The "LinkAssemblies" task failed unexpectedly al compilar un proyecto de
Xamarin. Android que use formularios. Esto sucede cuando el enlazador está activo (normalmente en una
compilación de versión para reducir el tamaño del paquete de la aplicación). y se produce porque los destinos de
Android no se actualizan al marco de trabajo más reciente.

"¿Por qué es mi Xamarin.Forms . Se produce un ERROR en el proyecto


de Android de Maps con COMPILETODALVIK: ERROR de nivel superior
inesperado? "
Este error puede aparecer en el panel de errores de Visual Studio para Mac o en la ventana de salida de la
compilación de Visual Studio; en los proyectos de Android con Xamarin.Forms . Maps. Normalmente se resuelve
aumentando el tamaño del montón de Java para el proyecto de Xamarin. Android.
¿Puedo actualizar la Xamarin.Forms plantilla
predeterminada a un paquete de NuGet más
reciente?
18/12/2020 • 2 minutes to read • Edit Online

En esta guía se usa la Xamarin.Forms plantilla de biblioteca de .net Standard como ejemplo, pero el mismo método
general también funcionará para la Xamarin.Forms plantilla de proyecto compartido. Esta guía se ha escrito con el
ejemplo de actualización de Xamarin.Forms 1.5.1.6471 a 2.1.0.6529, pero se pueden realizar los mismos pasos
para establecer otras versiones como valor predeterminado.
1. Copie la plantilla original .zip de:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Xamarin\Xamarin\[Xamarin
Version]\T\PT\Cross-Platform\Xamarin.Forms.PCL.zip

2. Descomprima el .zip en una ubicación temporal.


3. Cambie todas las apariciones de la versión anterior del Xamarin.Forms paquete a la nueva versión que le
gustaría usar.
FormsTemplate\FormsTemplate.vstemplate
FormsTemplate.Android\FormsTemplate.Android.vstemplate
FormsTemplate.iOS\FormsTemplate.iOS.vstemplate

Ejemplo: <package id="Xamarin.Forms" version="1.5.1.6471" /> ->


<package id="Xamarin.Forms" version="2.1.0.6529" />

4. Cambie el elemento "Name" del archivo de plantilla de varios proyectos principal (


Xamarin.Forms.PCL.vstemplate ) para que sea único. Por ejemplo:

<Name>Blank App (Xamarin.Forms Portable) - 2.1.0.6529</Name>

5. Vuelva a comprimir toda la carpeta de plantillas. Asegúrese de que coincide con la estructura de archivo
original del .zip archivo. El Xamarin.Forms.PCL.vstemplate archivo debe estar en la parte superior del
.zip archivo, no en ninguna carpeta.

6. Cree un subdirectorio "Mobile Apps" en la carpeta de plantillas de Visual Studio por usuario:

%USERPROFILE%\Documents\Visual Studio 2013\Templates\ProjectTemplates\Visual C#\Mobile Apps

7. Copie la nueva carpeta de plantillas comprimidas en el nuevo directorio "Mobile Apps".


8. Descargue el paquete de NuGet que coincida con la versión del paso 3. Por ejemplo,
https://nuget.org/api/v2/package/ Xamarin.Forms /2.1.0.6529 (vea también
https://stackoverflow.com/questions/8597375/how-to-get-the-url-of-a-nupkg-file ) y cópielo en la
subcarpeta correspondiente de la carpeta de extensiones de Visual Studio de Xamarin:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Xamarin\Xamarin\[Xamarin
Version]\Packages
¿Por qué no funciona el diseñador XAML de Visual
Studio para Xamarin.Forms los archivos XAML?
18/12/2020 • 2 minutes to read • Edit Online

Xamarin.Formsno admite actualmente los diseñadores visuales para los archivos XAML. Por este motivo, al intentar
abrir un archivo XAML de formularios en el diseñador de la interfaz de usuario XAML de Visual Studio o en el
diseñador de la interfaz de usuario XAML con codificación, se produce el siguiente mensaje de error:

"El archivo no se puede abrir con el editor seleccionado. Elija otro editor ".

Esta limitación se describe en la guía de Xamarin.Forms conceptos básicos de XAML :

"Todavía no hay un diseñador visual para generar XAML en Xamarin.Forms aplicaciones, por lo que todos los
XAML deben estar escritos a mano".

Sin embargo, el Xamarin.Forms previsor de XAML se puede mostrar si se selecciona la opción de menú **Ver >
otra ventana Xamarin.Forms ** de vista previa de Windows >.
Error de compilación de Android: error inesperado en
la tarea LinkAssemblies
18/12/2020 • 3 minutes to read • Edit Online

Es posible que vea un mensaje de error The "LinkAssemblies" task failed unexpectedly al compilar un proyecto de
Xamarin. Android que use formularios. Esto sucede cuando el enlazador está activo (normalmente en una
compilación de versión para reducir el tamaño del paquete de la aplicación). y se produce porque los destinos de
Android no se actualizan al marco de trabajo más reciente. (Más información: Xamarin.Forms plataformas
admitidas)
La solución a este problema es asegurarse de que tiene las últimas versiones de Android SDK compatibles y
establecer la versión de .NET Framework de destino en la plataforma instalada más reciente. También se
recomienda establecer la versión de Android de destino en la plataforma instalada más reciente y la versión de
Android mínima en la API 19 o superior. Esto se considera la configuración admitida.

Establecer en Visual Studio para Mac


1. Haga clic con el botón derecho en el proyecto de Android y seleccione Opciones en el menú.
2. En el cuadro de diálogo Opciones del proyecto , vaya a compilar > general .
3. Establezca compilar con la versión de Android: (plataforma de destino) en la plataforma instalada más
reciente.
4. En el cuadro de diálogo Opciones del proyecto , vaya a compilar > aplicación de Android .
5. Establezca la versión mínima de Android en el nivel de API 19 o superior y la versión de Android de
destino en la plataforma instalada más reciente que eligió en (3).

Configuración en Visual Studio


1. Haga clic con el botón derecho en el proyecto de Android y seleccione propiedades en el menú.
2. En las propiedades del proyecto, vaya a aplicación .
3. Establezca compilar con la versión de Android: (plataforma de destino) en la plataforma instalada más
reciente.
4. En las propiedades del proyecto, vaya a manifiesto de Android .
5. Establezca la versión mínima de Android en el nivel de API 19 o superior y la versión de Android de
destino en la plataforma instalada más reciente que eligió en (3).
Una vez que haya actualizado la configuración, limpie y recompile el proyecto para asegurarse de que los cambios
se recogen.
¿Por qué mi Xamarin.Forms . Se produce un ERROR
en el proyecto de Android de Maps con
COMPILETODALVIK de nivel superior inesperado?
18/12/2020 • 2 minutes to read • Edit Online

Este error puede aparecer en el panel de errores de Visual Studio para Mac o en la ventana de salida de la
compilación de Visual Studio; en los proyectos de Android con Xamarin.Forms . Maps.
Esto se resuelve normalmente aumentando el tamaño del montón de Java para el proyecto de Xamarin. Android.
Siga estos pasos para aumentar el tamaño del montón:

Visual Studio
1. Haga clic con el botón derecho en el proyecto de Android & abrir las opciones del proyecto.
2. Vaya a Opciones de Android-> avanzado
3. En el cuadro de texto tamaño del montón de Java escriba 1G.
4. Recompile el proyecto.

Visual Studio para Mac


1. Haga clic con el botón derecho en el proyecto de Android & abrir las opciones del proyecto.
2. Vaya a compilación-> Android Build-> avanzado
3. En el cuadro de texto tamaño del montón de Java escriba 1G.
4. Recompile el proyecto.
Ejemplos de Xamarin.Forms
18/12/2020 • 2 minutes to read • Edit Online

Las aplicaciones de ejemplo y las demostraciones de código de Xamarin.Forms le ayudan a empezar a trabajar y
comprender los conceptos de Xamarin.Forms.
Todas las muestras de Xamarin.Forms

Lista de tareas pendientes


En este ejemplo se muestra una aplicación de la lista de tareas pendientes donde los datos se almacenan y se
accede a ellos en una base de datos local de SQLite.

BugSweeper
Se trata de un juego familiar con un nuevo toque. Diez errores están ocultos en una cuadrícula de 9 x 9 iconos.
Para ganar, debe buscar y marcar los diez errores.
Calculadora RPN
Una calculadora RPN (notación polaca inversa) permite que se introduzcan operaciones y números sin paréntesis
o una clave igual.

SpinPaint
El programa simula un disco giratorio que se puede pintar al tocar y mover los dedos. SpinPaint responde al tacto
cuando se pinta una línea con dedo, pero también duplica esa línea en tres imágenes espejo en los otros tres
cuadrantes del disco.
Ejemplos de XAML
El lenguaje XAML permite a los desarrolladores definir interfaces de usuario en Xamarin.Forms mediante el
marcado, en lugar de código.

Xuzzle
Este juego es una variación del rompecabezas clásico de 14 o 15 piezas que puede resolver deslizando las piezas
en el orden correcto.

Todas las muestras


Para obtener el conjunto completo de aplicaciones de ejemplo y demostraciones de código de Xamarin.Forms, vea
Todos los ejemplos de Xamarin.Forms.
Creación de aplicaciones móviles con el libro de
Xamarin.Forms
18/12/2020 • 10 minutes to read • Edit Online

Descargar el ejemplo
El libro Creación de aplicaciones móviles con Xamarin.Forms de Charles Petzold es una guía
para aprender a escribir aplicaciones de Xamarin.Forms. El único requisito previo es el
conocimiento del lenguaje de programación de C#. El libro proporciona una amplia
exploración en la interfaz de usuario de Xamarin.Forms y también abarca animación, MVVM,
desencadenadores, comportamientos, diseños personalizados, representadores
personalizados y mucho más.
El 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.

Descarga del libro electrónico gratis


Descargue su formato de libro electrónico preferido de Microsoft Virtual Academy:
PDF (56Mb)
ePub (151Mb)
Kindle Edition (325Mb)
También puede descargar capítulos individuales como archivos PDF.

Muestras
Los ejemplos están disponibles en github e incluyen proyectos para iOS, Android y Plataforma universal de
Windows (UWP). (Xamarin.Forms ya no admite Windows 10 Mobile, pero las aplicaciones de Xamarin.Forms se
ejecutan en Windows 10 Desktop).

Resúmenes de los capítulos


Los resúmenes de capítulos están disponibles en la tabla de capítulos que figura a continuación. Estos
resúmenes describen el contenido de cada capítulo e incluyen varios tipos de vínculos:
Vínculos a los propios capítulos del libro (en la parte inferior de la página) y a los artículos relacionados
Vínculos a todos los ejemplos del repositorio de GitHub Xamarin-Forms-Book-samples
Vínculos a la documentación de la API para obtener descripciones más detalladas de las clases,
estructuras, propiedades y enumeraciones (entre otros) de Xamarin.Forms.
Estos resúmenes también indican que el material del capítulo podría estar un poco anticuado.

Descargar capítulos y resúmenes


C A P ÍT ULO T EXTO C O M P L ETO RESUM EN

Capítulo 1. ¿Cómo encaja Descargar PDF Resumen


Xamarin.Forms?

Capítulo 2. Anatomía de una aplicación Descargar PDF Resumen

Capítulo 3. Profundizar en el texto Descargar PDF Resumen

Capítulo 4. Desplazamiento de la pila Descargar PDF Resumen

Capítulo 5. La cuestión de los tamaños Descargar PDF Resumen

Capítulo 6. Clics de botón Descargar PDF Resumen

Capítulo 7. Código XAML o Código Descargar PDF Resumen

Capítulo 8. Código y XAML en armonía Descargar PDF Resumen

Capítulo 9. Llamadas de API específicas Descargar PDF Resumen


de la plataforma

Capítulo 10. Extensiones de marcado Descargar PDF Resumen


XAML

Capítulo 11. La infraestructura Descargar PDF Resumen


enlazable

Capítulo 12. Estilos Descargar PDF Resumen

Capítulo 13. Mapas de bits Descargar PDF Resumen

Capítulo 14. Diseño absoluto Descargar PDF Resumen

Capítulo 15. La interfaz interactiva Descargar PDF Resumen

Capítulo 16. Enlace de datos Descargar PDF Resumen

Capítulo 17. Dominio de la cuadrícula Descargar PDF Resumen

Capítulo 18. MVVM Descargar PDF Resumen

Capítulo 19. Vistas de colección Descargar PDF Resumen

Capítulo 20. Asincronía y E/S de Descargar PDF Resumen


archivos

Capítulo 21. Transformaciones Descargar PDF Resumen

Capítulo 22. Animación Descargar PDF Resumen

Capítulo 23. Desencadenadores y Descargar PDF Resumen


comportamientos
C A P ÍT ULO T EXTO C O M P L ETO RESUM EN

Capítulo 24. Navegación de páginas Descargar PDF Resumen

Capítulo 25. Variedades de páginas Descargar PDF Resumen

Capítulo 26. Diseños personalizados Descargar PDF Resumen

Capítulo 27. Representadores Descargar PDF Resumen


personalizados

Capítulo 28. Ubicación y mapas Descargar PDF Resumen

Por qué el libro está obsoleto


Desde la publicación de Creación de aplicaciones móviles con Xamarin.Forms , se han agregado varias
características nuevas a Xamarin.Forms. Estas nuevas características se describen en los artículos individuales de
la documentación de Xamarin.Forms.
Otros cambios han provocado que parte del contenido del libro no esté actualizado:
las bibliotecas .NET Standard 2.0 han remplazado a las bibliotecas de clases portables.
Una aplicación de Xamarin.Forms generalmente usa una biblioteca para compartir el código entre las distintas
plataformas. Originalmente, se trataba de una biblioteca de clases portable (PCL). Hay muchas referencias a PCL
en el libro y los resúmenes del capítulo.
La biblioteca de clases portable se ha reemplazado por una biblioteca .NET Standard 2.0, como se describe en el
artículo Compatibilidad con .NET Standard 2.0 en Xamarin.Forms. Todo el código de ejemplo del libro se ha
actualizado para usar las bibliotecas .NET Standard 2.0.
La mayor parte de la información del libro relativa al rol de la biblioteca de clases portable sigue siendo la
misma para una biblioteca .NET Standard 2.0. Una diferencia es que solo una PCL tiene un "perfil" numérico.
Además, hay algunas ventajas en las bibliotecas .NET Standard 2.0. Por ejemplo, en el capítulo 20, Asincronía y
E/S de archivos, se describe cómo usar las plataformas subyacentes para realizar la E/S de archivos. Ya no es
necesario. La biblioteca .NET Standard 2.0 admite las clases conocidas de System.IO para todas las plataformas
de Xamarin.Forms.
La biblioteca .NET Standard 2.0 también permite a las aplicaciones de Xamarin.Forms usar HttpClient para
tener acceso a los archivos a través de Internet en lugar de WebRequest u otras clases.
El rol de XAML se ha elevado.
Creación de aplicaciones móviles con Xamarin.Forms empieza por describir la escritura de aplicaciones de
Xamarin.Forms mediante C#. El lenguaje de marcado de aplicaciones extensible (XAML) no se introduce hasta el
Capítulo 7. XAML o código.
XAML ahora tiene un rol mucho más importante en Xamarin.Forms. Las plantillas de solución de Xamarin.Forms
distribuidas con Visual Studio crean archivos de paginación basados en XAML. Un desarrollador que use
Xamarin.Forms debe familiarizarse con XAML lo antes posible. La sección Lenguaje XAML (eXtensible
Application Markup Language) de la documentación de Xamarin.Forms contiene varios artículos sobre XAML
para comenzar.
Plataformas compatibles
Xamarin.Forms ya no admite Windows 8.1 ni Windows Phone 8.1.
A veces, el libro hace referencia a Windows Runtime. Se trata de un término que engloba la API de Windows que
se usa en varias versiones de Windows y Windows Phone. Las versiones más recientes de Xamarin.Forms se
limitan a admitir la Plataforma universal de Windows, que es la API para Windows 10 y Windows 10 Mobile.
Una biblioteca .NET Standard 2.0 no es compatible con ninguna versión de Windows 10 Mobile. Por lo tanto, una
aplicación de Xamarin.Forms que use una biblioteca de .NET Standard no se ejecutará en un dispositivo
Windows 10 Mobile. Las aplicaciones de Xamarin.Forms siguen ejecutándose en el escritorio de Windows 10,
versiones 10.0.16299.0 y posteriores.
Xamarin.Forms es también compatible con la versión preliminar de las plataformas Mac, WPF, GTK# y Tizen.
Resúmenes de los capítulos
Los resúmenes de los capítulos incluyen información sobre los cambios de Xamarin.Forms desde que se escribió
el libro. A menudo se presentan en forma de notas:

NOTE
En las notas de cada página se indican las áreas en las que Xamarin.Forms difiere del material presentado en el libro.

Muestras
En el repositorio de GitHub Xamarin-Forms-Book-samples , la rama original-code-from-book contiene
ejemplos de programas coherentes con el libro. La rama maestra contiene proyectos que se han actualizado
para quitar API en desuso y reflejar las API mejoradas. Además, los proyectos de Android de la rama maestra se
han actualizado para el diseño de materiales a través de AppCompat de Android y, por lo general, mostrarán
texto en negro sobre un fondo blanco.

Vínculos relacionados
Blog de MS Press
Código de ejemplo del libro
Patrones de aplicación empresarial con el libro
electrónico de :::no-loc(Xamarin.Forms):::
18/12/2020 • 10 minutes to read • Edit Online

Instrucciones arquitectónicas para desarrollar aplicaciones empresariales adaptables, mantenibles y comprobables


:::no-loc(Xamarin.Forms):::

NOTE
Este libro electrónico se publicó en el muelle de 2017 y no se ha actualizado desde entonces. Hay mucho en el libro que
sigue siendo útil, pero parte del material es obsoleto.

En este libro electrónico se proporcionan instrucciones sobre cómo implementar el patrón Model-View-
ViewModel (MVVM), la inserción de dependencias, la navegación, la validación y la administración de
configuración, a la vez que se mantiene el acoplamiento flexible. Además, también hay instrucciones sobre cómo
realizar la autenticación y la autorización con IdentityServer, el acceso a los datos de microservicios en contenedor
y las pruebas unitarias.

Prefacio
En este capítulo se explica el propósito y el ámbito de la guía y a quién se destina.

Introducción
Los desarrolladores de aplicaciones empresariales se enfrentan a varios desafíos que pueden modificar la
arquitectura de la aplicación durante el desarrollo. Por lo tanto, es importante compilar una aplicación para que se
pueda modificar o ampliar con el tiempo. El diseño para esta adaptación puede ser difícil, pero normalmente
implica la creación de particiones de una aplicación en componentes discretos de acoplamiento flexible que se
pueden integrar fácilmente en una aplicación.

MVVM
El patrón Model-View-ViewModel (MVVM) ayuda a separar sin problemas la lógica empresarial y de presentación
de una aplicación desde su interfaz de usuario (UI). Mantener una separación limpia entre la lógica de la aplicación
y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y puede hacer que una aplicación sea
más fácil de probar, mantener y desarrollar. También puede mejorar considerablemente las oportunidades de
reutilización del código y permite a los desarrolladores y diseñadores de la interfaz de usuario colaborar con
mayor facilidad al desarrollar sus partes respectivas de una aplicación.

Inserción de dependencias
La inserción de dependencias permite desacoplar tipos concretos del código que depende de estos tipos.
Normalmente usa un contenedor que contiene una lista de registros y asignaciones entre las interfaces y los tipos
abstractos, y los tipos concretos que implementan o amplían estos tipos.
Los contenedores de inserción de dependencias reducen el acoplamiento entre objetos proporcionando una
utilidad para crear instancias de la clase y administrar su duración en función de la configuración del contenedor.
Durante la creación de objetos, el contenedor inserta las dependencias que el objeto requiere en él. Si estas
dependencias no se han creado todavía, el contenedor crea y resuelve primero sus dependencias.

Comunicación entre componentes débilmente acoplados


La clase MessagingCenter de :::no-loc(Xamarin.Forms)::: implementa el patrón de publicación y suscripción, lo que
permite la comunicación basada en mensajes entre los componentes que no se recomienda vincular mediante
referencias de tipo y objeto. Este mecanismo permite a los publicadores y suscriptores comunicarse sin tener una
referencia mutua, lo que ayuda a reducir las dependencias entre los componentes, a la vez que permite que los
componentes se desarrollen y prueben de forma independiente.

Navegación
:::no-loc(Xamarin.Forms)::: incluye compatibilidad para la navegación de páginas, que suele ser el resultado de la
interacción del usuario con la interfaz de usuario, o de la propia aplicación, como resultado de los cambios internos
en el estado controlado por lógica. Sin embargo, la navegación puede ser compleja de implementar en
aplicaciones que usan el patrón MVVM.
En este capítulo se presenta una NavigationService clase, que se usa para realizar la navegación del modelo de
vista primero desde los modelos de vista. Colocar la lógica de navegación en las clases del modelo de vista
significa que la lógica se puede ejecutar a través de pruebas automatizadas. Además, el modelo de vista puede
implementar la lógica para controlar la navegación para asegurarse de que se aplican ciertas reglas de negocios.

Validación
Cualquier aplicación que acepte la entrada de los usuarios debe asegurarse de que la entrada es válida. Sin
validación, un usuario puede proporcionar datos que provocan un error en la aplicación. La validación aplica las
reglas de negocios y evita que un atacante Inserte datos malintencionados.
En el contexto del patrón Model-View-ViewModel (MVVM), a menudo se necesitará un modelo de vista o modelo
para realizar la validación de datos y señalar los errores de validación en la vista para que el usuario pueda
corregirlos.

Administración de la configuración
La configuración permite la separación de los datos que configura el comportamiento de una aplicación desde el
código, lo que permite cambiar el comportamiento sin volver a compilar la aplicación. La configuración de la
aplicación son los datos que una aplicación crea y administra, y la configuración de usuario es la configuración
personalizable de una aplicación que afecta al comportamiento de la aplicación y no requiere un reajuste
frecuente.

Microservicios en contenedores
Los microservicios ofrecen un enfoque para el desarrollo y la implementación de aplicaciones que se adapta a los
requisitos de agilidad, escala y confiabilidad de las aplicaciones en la nube modernas. Una de las principales
ventajas de los microservicios es que se pueden escalar horizontalmente de forma independiente, lo que significa
que se puede escalar un área funcional específica que requiere más capacidad de procesamiento o ancho de banda
de red para admitir la demanda, sin necesidad de escalar de forma innecesaria las áreas de la aplicación que no
experimentan una mayor demanda.

Autenticación y autorización
Hay muchos enfoques para integrar la autenticación y la autorización en una :::no-loc(Xamarin.Forms)::: aplicación
que se comunica con una aplicación web MVC de ASP.net. Aquí, la autenticación y la autorización se realizan con un
microservicio de identidad en contenedor que usa IdentityServer 4. IdentityServer es un marco de OpenID
Connect y OAuth 2,0 de código abierto para ASP.NET Core que se integra con ASP.NET Core identidad para realizar
la autenticación de token de portador.

Acceso a datos remotos


Muchas soluciones modernas basadas en Web hacen uso de servicios Web, hospedados por servidores Web, para
proporcionar funcionalidad a las aplicaciones cliente remotas. Las operaciones que expone un servicio web
constituyen una API Web y las aplicaciones cliente deben ser capaces de usar la API Web sin saber cómo se
implementan los datos o las operaciones que expone la API.

Pruebas unitarias
Probar modelos y ver modelos de aplicaciones MVVM es idéntico a probar cualquier otra clase y se pueden usar
las mismas herramientas y técnicas. Sin embargo, hay algunos patrones que son típicos para modelar y ver las
clases de modelo, que pueden beneficiarse de las técnicas de prueba unitaria específicas.

Sitio de la comunidad
Este proyecto tiene un sitio de la comunidad, en el que puede publicar preguntas y proporcionar comentarios. El
sitio de la comunidad se encuentra en GitHub. También se puede enviar un correo electrónico a los comentarios
sobre el libro electrónico [email protected] .

Vínculos relacionados
Descargar libro electrónico (2 MB PDF)
eShopOnContainers (GitHub) (ejemplo)
Gráficos de SkiaSharp :::no-loc(Xamarin.Forms):::
18/12/2020 • 5 minutes to read • Edit Online

Descargar el ejemplo
Uso de SkiaSharp para gráficos 2D en las :::no-loc(Xamarin.Forms)::: aplicaciones
SkiaSharp es un sistema de gráficos 2D para .NET y C# basado en el motor de gráficos Skia de código abierto que
se usa en gran medida en productos de Google. Puede usar SkiaSharp en las :::no-loc(Xamarin.Forms):::
aplicaciones para dibujar gráficos vectoriales 2D, mapas de bits y texto.
En esta guía se da por supuesto que está familiarizado con la :::no-loc(Xamarin.Forms)::: programación.

Seminario Web: SkiaSharp para :::no-loc(Xamarin.Forms):::

SkiaSharp Windows°PE
SkiaSharp para :::no-loc(Xamarin.Forms)::: se empaqueta como un paquete NuGet. Después de crear una :::no-
loc(Xamarin.Forms)::: solución en Visual Studio o Visual Studio para Mac, puede usar el administrador de
paquetes NuGet para buscar el paquete SkiaSharp. views. Forms y agregarlo a la solución. Si comprueba la
sección referencias de cada proyecto después de agregar SkiaSharp, puede ver que se han agregado varias
bibliotecas SkiaSharp a cada uno de los proyectos de la solución.
Si su :::no-loc(Xamarin.Forms)::: aplicación tiene como destino iOS, edite su archivo info. plist para cambiar el
destino de implementación mínimo a iOS 8,0.
En cualquier página de C# que use SkiaSharp, querrá incluir una using Directiva para el SkiaSharp espacio de
nombres, que engloba todas las clases, estructuras y enumeraciones SkiaSharp que usará en la programación de
gráficos. También querrá una using Directiva para el SkiaSharp.Views.Forms espacio de nombres para las clases
específicas de :::no-loc(Xamarin.Forms)::: . Se trata de un espacio de nombres mucho más pequeño, con la clase
más importante SKCanvasView . Esta clase se deriva de la :::no-loc(Xamarin.Forms)::: View clase y hospeda la
salida de los gráficos de SkiaSharp.

IMPORTANT
El SkiaSharp.Views.Forms espacio de nombres también contiene una SKGLView clase que se deriva de View pero
utiliza OpenGL para representar gráficos. Por motivos de simplicidad, esta guía se restringe a SKCanvasView , pero usar
SKGLView en su lugar es bastante similar.

Conceptos básicos de dibujo de SkiaSharp


Algunas de las cifras de gráficos más sencillas que se pueden dibujar con SkiaSharp son círculos, óvalos y
rectángulos. Al mostrar estas figuras, obtendrá información sobre las coordenadas, los tamaños y los colores de
SkiaSharp. La presentación de texto y mapas de bits es más compleja, pero estos artículos también presentan
esas técnicas.

Trazados y líneas de SkiaSharp


Una ruta de acceso de gráficos es una serie de líneas rectas y curvas conectadas. Las rutas de acceso se pueden
trazar, rellenar o ambos. En este artículo se incluyen muchos aspectos del dibujo de líneas, incluidos los extremos
del trazo y las combinaciones, y las líneas discontinuas y con puntos, pero se detienen pocas geometrías de curva.

Transformaciones de SkiaSharp
Las transformaciones permiten que los objetos de gráficos se traduzcan, escalen, giren o sesgadon
uniformemente. En este artículo también se muestra cómo puede usar una matriz de transformación estándar de
3 por 3 para crear transformaciones no afines y aplicar transformaciones a trazados.

Trazados y curvas de SkiaSharp


La exploración de las rutas de acceso continúa con la adición de curvas a los objetos de trazado y la explotación
de otras características de ruta de acceso eficaces. Verá cómo puede especificar una ruta de acceso completa en
una cadena de texto concisa, cómo usar los efectos de la ruta de acceso y cómo profundizar en la ruta de acceso
interna.

Mapas de bits de SkiaSharp


Los mapas de bits son matrices rectangulares de bits que corresponden a los píxeles de un dispositivo de
pantalla. En esta serie de artículos se muestra cómo cargar, guardar, mostrar, crear, dibujar, animar y obtener
acceso a los bits de los mapas de bits de SkiaSharp.

Efectos SkiaSharp
Los efectos son propiedades que modifican la presentación normal de gráficos, incluidos los degradados lineales
y circulares, el mosaico de mapas de bits, los modos de mezcla, el desenfoque y otros.

Vínculos relacionados
API de SkiaSharp
SkiaSharpFormsDemos (ejemplo)
SkiaSharp con :::no-loc(Xamarin.Forms)::: seminario web (vídeo)

También podría gustarte