El soporte de WPF en Xamarin.Forms ahora está disponible en la Nightly (paquetes previos) de Xamarin.Forms.En este artículo, vamos a ver cómo agregar este nuevo proyecto a una solución existente de Xamarin.Forms, los obstáculos que puedes encontrar así como el estado actual y el futuro de las características de esta nueva plataforma.
Backend WPF
WPF es la abreviación de Windows Presentation Foundation. Hablamos de un conjunto de APIs destinadas a crear interfaces de usuario enriquecidas para Windows.
Cabe destacar que se soporta Windows Vista hasta Windows 10.
Crear nuevo proyecto
Tras crear un nuevo proyecto Xamarin.Forms con una librería NET Standard.
A continuación, vamos a añadir un proyecto WPF. Dentro de la categoría escritorio clássico de Windows, elegimos la opción Aplicación de WPF:
A continuación, vamos a actualizar el paquete de Xamarin.Forms para utilizar la última Nightly (donde ya se incluye la Preview de WPF).
Y actualizamos Xamarin.Forms además de instalar el paquete en todos los proyectos:
A continuación, vamos a instalar el paquete Xamarin.Forms.Platform.WPF en el proyecto WPF. Este paquete es el que cuenta con las implementaciones necesarias de Xamarin.Forms en WPF.
Todo listo a nivel de dependencias. Es hora de cambiar levemente el código para inicializar Forms.
En el archivo App.xaml vamos a añadir algunos recursos utilizados por el backend WPF:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/WPFLightToolkit;component/Assets/Default.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Default Global Color -->
<SolidColorBrush x:Key="WindowBackgroundColor" Color="White" />
<SolidColorBrush x:Key="AccentColor" Color="#3498db" />
<!-- Default Command Bar Color -->
<SolidColorBrush x:Key="CommandBarBackgroundColor" Color="#3498db" />
<SolidColorBrush x:Key="CommandBarTextColor" Color="White" />
<!-- Default Title Bar Color -->
<SolidColorBrush x:Key="DefaultTitleBarBackgroundColor" Color="#3498db" />
<SolidColorBrush x:Key="DefaultTitleBarTextColor" Color="White" />
<!-- Default Tabbed Bar Color -->
<SolidColorBrush x:Key="DefaultTabbedBarBackgroundColor" Color="#3498db" />
<SolidColorBrush x:Key="DefaultTabbedBarTextColor" Color="White" />
</ResourceDictionary>
</Application.Resources>
Actualizamos la ventana principal, MainWindow.xaml para utilizar FormsApplicationPage:
También actualizaremos el tipo del code behind en MainWindow.xaml.cs:
using Xamarin.Forms.Platform.WPF;
namespace WPFSample.WPF
{
public partial class MainWindow : FormsApplicationPage
{
public MainWindow()
{
InitializeComponent();
}
}
}
Todo listo, ejecutamos!
Más ejemplos!
Un nuevo backend ofrece la posibilidad de llegar a nuevas plataformas, es normal querer probar todas las posibilidades. ¡No te preocupes, vamos a ver más ejemplos!
WeatherApp es otro ejemplo de aplicación Xamarin.Forms con soporte a WPF. Es una aplicación para ver la información meteorológica de forma muy visual.
Disponible en GitHub:
Native Embedding
La posibilidad de tomar cualquier ContentPage y usarlo en aplicaciones nativas, ¡también está disponible en el backend de WPF!
var settingsView = new SettingsView().CreateContainer();
vbox.PackEnd(settingsView, true, true, 0);
El resultado:
Tienes un ejemplo disponible en GitHub:
¿Algo más?
Ahora que tienes una aplicación, es hora de ponerse la gorra de diseñador de UX y comenzar a explorar los cambios necesarios para adaptar la aplicación a esta nueva plataforma.
Estilos
Échale un vistazo al estilo de la interfaz de usuario para Linux. Es posible que el mismo estilo utilizado en los dispositivos móviles no se vea bien.¡Esto lo cambios fácilmente! Con los cambios recientes realizados en OnPlatform, ahora puede seleccionar cada plataforma.Eso incluye a WPF.
if (Device.RuntimePlatform == Device.WPF)
return "Images/image.jpg";
else if (Device.RuntimePlatform == Device.UWP)
return "Assets/image.jpg";
else
return "image.jpg";
Recuerda, es una Preview…
Esta Preview tiene un soporte muy alto de las características y opciones de Xamarin.Forms. Sin embargo, hay algunas características aun no disponibles:
En toda aplicación móvil la apariencia visual es vital. Cada vez es mayor el esfuerzo depositado a la hora de crear aplicaciones atractivas a la par que intuitivas y en muchos casos conseguir una imagen única que diferencia a la Aplicación del resto es prioritario. Por este motivo, debemos de contar con opciones sencillas de poder personalizar los distintos elementos que componen la interfaz.
Los estilos permitir definir múltiples propiedades visuales de elementos de la interfaz de forma reutilizable.
Hasta ahora, podíamos utilizar C# o XAML para definir estilos. También tenemos la posibilidad de utilizar diferentes diccionarios de recursos entre otras opciones. Ahora, con la llegada de la última versión nightly de Xamarin.Forms, se incluye soporte a definir los estilos utilizando CSS.
NOTA: Puedes ver el conjunto de opciones soportadas en esta Pull Request de Stephane Delcroix.
En este artículo, vamos a conocer las opciones disponibles utilizando CSS para definir estilos.
Estilos utilizando CSS
Vamos a crear un nuevo proyecto Xamarin.Forms utilizando una librería NET Standard. Tras crear el proyecto, actualizamos Xamarin.Forms a su última versión nightly disponible (2.6.0.52014-nightly al momento de escribir este artículo).
Los estilos los aplicaremos utilizando CSS, para ello debemos crear archivo/s CSS. Puedes crear estos archivos en la librería NET Standard en cualquier localización aunque la recomendación sería mantener todos los estilos manteniendo una estructura de carpetas que ayude a mantener el proyecto con facilidad. En nuestro ejemplo, creamos una carpeta llamada Styles y dentro un archivo MainView.css donde aplicaremos los estilos a utilizar en la vista principal de la aplicación.
Es importante recordar que se debe establecer la propiedad Build Action del archivo CSS a EmbeddedResource.
De esta forma, podemos acceder a los estilos CSS desde la ContentPage.
Opciones soportadas (selectores y propiedades)
Los selectores en CSS son imprescindibles para poder aplicar los estilos. El selector es quien determina a que elemento hay que aplicar el estilo.
Los mismos selectores utilizados en CSS se utilizan en este caso en Xamarin.Forms salvo una excepción, ^base , que solo aplica en Xamarin.Forms.
Selector
Ejemplo
Descripción
.class
.header
Selecciona todos los elementos con la propiedad StyleClass que contiene ‘header’.
#id
#email
Selecciona todos los elementos con StyleId establecido a email.
*
*
Secciona todos los elementos.
element
label
Selecciona todos los elementos de tipo Label.
^base
^contentpage
Selecciona todos los elementos con ContentPage como clase base, esto incluye a la propia ContentPage. Este selector no esta presente en la especificación CSS y solo aplica a Xamarin.Forms.
element,element
label,button
Selecciona todos los Buttons y todos los Labels.
element element
stacklayout label
Selecciona todos los Labels dentro de un StackLayout.
element>element
stacklayout>label
Selecciona todos los Labels con un StackLayout como padre directo.
element+element
label+entry
Selecciona todos los Entries que están directamente tras un Label.
element~element
label~entry
Selecciona todos los Entries precedidos por un Label.
Se pueden realizar combinaciones de diferentes selectores lo que nos otorga grandes posibilidades de forma muy simple. Ejemplo:
StackLayout > ContentView > label.email
El resto de selectores que no aparecen en la tabla no estan soportados por ahora (Ejemplo: @media o @supports).
Pasamos a ver el listado de propiedades soportadas:
Propiedad
Aplica a
background-color
VisualElement
background-image
Page
border-color
Button, Frame
border-width
Button
color
Button, DatePicker, Editor, Entry, Label, Picker,
SearchBar, TimePicker
direction
VisualElement
font-family
Button, DatePicker, Editor, Entry, Label, Picker,
SearchBar, TimePicker, Span
font-size
Button, DatePicker, Editor, Entry, Label, Picker,
SearchBar, TimePicker, Span
font-style
Button, DatePicker, Editor, Entry, Label, Picker,
SearchBar, TimePicker, Span
height
VisualElement
margin
View
margin-left
View
margin-top
View
margin-right
View
margin-bottom
View
min-height
VisualElement
min-width
VisualElement
opacity
VisualElement
padding
Layout, Page
padding-left
Layout, Page
padding-top
Layout, Page
padding-right
Layout, Page
padding-bottom
Layout, Page
text-align
Entry, EntryCell, Label, SearchBar
visibility
VisualElement
width
VisualElement
Se soportan diferentes formatos de colores (RGB, RGBA, Hex, HSL, etc) y propiedades de Thickness con el mismo formato que en XAML.
Aplicando estilos en XAML
Tras ver el conjunto de selectores y propiedades soportadas, podemos aplicar estilos.
Con StyleClass buscamos el estilo definido por un selector de tipo .class.
Ejemplo disponible en GitHub:
Es importante recordar que, la posibilidad de aplicar estilos utilizando CSS es un añadido. No llega para reemplazar la posibilidad de estilos con XAML (en estos momentos, aplicar estilos con XAML o C# cuenta con más características).
El aplicar estilos con CSS cuenta con ciertos beneficios como la flexibilidad aportada por los selectores además de reducir la verbosidad de XAML. Además, hoy día, la mayoría de herramientas de diseño (como Sketch) cuentan con la posibilidad de facilitar los estilos en CSS.
¿Qué te parece esta nueva característica?. Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.
El trabajo con datos en dispositivos móviles se ha convertido ya en algo común y habitual en el desarrollo de aplicaciones. Existe una gran variedad de tipos de datos y formas de almacenamiento:
Archivos de texto. Texto plano o html cacheado en el espacio de almacenamiento aislado de la aplicación.
Imágenes. En el espacio de almacenamiento aislado de la aplicación o almacenadas en directorios conocidos del sistema.
Archivos serializados. Archivos XML o Json con objetos serializados.
Bases de datos. Cuando se requieren datos estructurados, obtener información más compleja con consultas avanzadas entre otro tipo de necesidades, la posibilidad de las bases de datos es la elección idónea.
Las ventajas de utilizar una base de datos son múltiples:
Almacenamiento estructurado con eficacia alta.
Posibilidad de utilizar consultas y aplicar filtros.
Posibilidad de reutilizar conocimientos de base de datos en la gestión de datos en nuestras aplicaciones móviles.
Introducción a SQLite
SQLite es un motor de base de datos Open Source utilizado en todas las plataformas móviles y adoptado tanto por Apple como Google como Microsoft. El uso de SQLite en aplicaciones móviles es una gran opción ya que:
La base de datos es pequeña y fácil de portar.
La base de datos se concentra en un pequeño archivo.
Implementa la mayor parte del estándar SQL92.
Arrancamos el proyecto
Comenzamos creando una aplicación Xamarin.Forms utilizando una librería NET Standard:
Tras crear la aplicación, añadimos las carpetas básicas para aplicar el patrón MVVM además del paquete NuGet de Autofac para la gestión del contenedor de dependencias.
Con el proyecto y estructura base creada, vamos a añadir SQLite al proyecto. Para ello, vamos a usar NuGet. Vamos a añadir en cada proyecto de la solución la última versión disponible del paquete utilizando NuGet. El paquete a utilizar es sql-net-pcl, implementación Open Source con soporte a .NET.
Tras añadir la referencia vamos a crear una interfaz que defina como obtener la conexión con la base de datos y abstraer la funcionalidad específica de cada plataforma. Trabajando con SQLite, el único trabajo específico a implementar en cada plataforma es determinar la ruta a la base de datos.
public interface IPathService
{
string GetDatabasePath();
}
En Android, la implementación de IPathService nos permite obtener la ruta a la base de datos.
[assembly: Dependency(typeof(PathService))]
namespace TodoSqlite.Droid.Services.Sqlite
{
public class PathService : IPathService
{
public string GetDatabasePath()
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
return Path.Combine(path, AppSettings.DatabaseName);
}
}
}
NOTA: Utilizamos el atributo assembly:Dependency para poder realizar la resolución de la implementación con DependencyService.
En iOS:
[assembly: Dependency(typeof(PathService))]
namespace TodoSqlite.iOS.Services.Sqlite
{
public class PathService : IPathService
{
public string GetDatabasePath()
{
string docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string libFolder = Path.Combine(docFolder, "..", "Library", "Databases");
if (!Directory.Exists(libFolder))
{
Directory.CreateDirectory(libFolder);
}
return Path.Combine(libFolder, AppSettings.DatabaseName);
}
}
}
El archivo de la base de datos lo situamos dentro de la carpeta Library dentro del espacio de almacenamiento de la aplicación.
Y en UWP:
[assembly: Dependency(typeof(PathService))]
namespace TodoSqlite.UWP.Services.Sqlite
{
public class PathService : IPathService
{
public string GetDatabasePath()
{
return Path.Combine(ApplicationData.Current.LocalFolder.Path, AppSettings.DatabaseName);
}
}
}
Todo listo para comenzar!
La definición de modelos
En nuestra aplicación, trabajaremos con elementos del listado ToDo, una única entidad sencilla.
public class TodoItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public bool Done { get; set; }
}
La gestión de campos especiales o relacionados las gestionamos mediante el uso de etiquetas. En nuestro ejemplo establecemos el campo Id como clave primaria gracias a la etiqueta PrimaryKey y además que autoincremente con el uso de AutoIncrement.
Trabajando con SQLite
Para trabajar con la base de datos utilizaremos DependencyService para obtener la implementación de IPathService y obtener la ruta a la base de datos en cada plataforma.
var databasePath = DependencyService.Get<IPathService>().GetDatabasePath();
Con la ruta de la base de datos, creamos una conexión:
var sqlCon = new SQLiteAsyncConnection(databasePath);
Comenzamos creando la tabla necesaria en la base de datos.
Continuamos con las operaciones básicas de CRUD. Para obtener la información almacenada en una tabla podemos acceder a la tabla y obtener el listado utilizando el método ToListAsync.
public async Task<IList<TodoItem>> GetAll()
{
var items = new List<TodoItem>();
using (await Mutex.LockAsync().ConfigureAwait(false))
{
items = await _sqlCon.Table<TodoItem>().ToListAsync().ConfigureAwait(false);
}
return items;
}
NOTA: Podemos realizar consultar SQL utilizando el método QueryAync.
A la hora de insertar, verificamos si estamos ante un registro existente o no, para realizar el registro de un nuevo elemento o actualizar uno existente con los métodos InsertAsync o UpdateAsync respectivamente.
public async Task Insert(TodoItem item)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
var existingTodoItem = await _sqlCon.Table<TodoItem>()
.Where(x => x.Id == item.Id)
.FirstOrDefaultAsync();
if (existingTodoItem == null)
{
await _sqlCon.InsertAsync(item).ConfigureAwait(false);
}
else
{
item.Id = existingTodoItem.Id;
await _sqlCon.UpdateAsync(item).ConfigureAwait(false);
}
}
}
Eliminar es una acción sencilla realizada con el método DeleteAsync.
await _sqlCon.DeleteAsync(item);
El resultado del ejemplo:
Tenéis el código fuente disponible e GitHub:
Múltiples tablas
Con lo visto hasta aquí, tenemos suficiente para gestionar una base de datos local en Android, iOS y Windows. Sin embargo, ¿cómo hacemos relaciones entre diferentes tablas?, ¿y operaciones en cascada que afecten a múltiples tablas a la vez?.
Con sqlite-net-pcl, podemos trabajar con diferentes tablas y realizar relaciones. Veamos un ejemplo:
public class Stock
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Symbol { get; set; }
}
public class Valuation
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[Indexed]
public int StockId { get; set; }
public DateTime Time { get; set; }
public decimal Price { get; set; }
}
Es importante resaltar el uso de atributo Indexed. Estamos asociando las tablas Valuation y Stock.
Creamos ambas tablas utilizando el método CreateTable por cada tabla:
Y obtenemos la información relacionada con ambas tablas gracias al uso de queries:
return db.Query<Valuation> ("select * from Valuation where StockId = ?", stock.Id);
De esta forma obtendríamos los registros de la tabla Valuation dado un SotckId específico.
Esta es una forma simple de trabajar. Sin embargo, existe un wrapper de SQLite.NET llamado SQLite-Net Extensions que amplía funcionalidades como permitir gestionar relaciones (uno a uno, uno a varios, varios a uno y varios a varios), operaciones en cascada además de otras opciones interesantes de forma muy sencilla.
Vamos a regresar a nuestra aplicación de tareas. Para aprender como gestionar más de una tabla, vamos a añadir una segunda tabla de pasos en las tareas. De modo que, una tarea tendrá N pasos.
Veamos la creación de las tablas. Creamos una segunda tabla (recuerda, una clase) llamada Step:
[Table("Steps")]
public class Step
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey(typeof(TodoItem))]
public int WorkerId { get; set; }
}
Al crear directamente la definición, vamos a especificar relaciones entre tablas. En este caso, destacamos una propiedad que será la ForeignKey de la tabla TodoItem.
[Table("TodoItems")]
public class TodoItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public bool Done { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.CascadeInsert)]
public List<Step> Steps { get; set; }
}
Gracias a las extensiones aportadas, podemos definir directamente la relación entre las tablas TodoItems y Steps. En este caso será un OneToMany. Por defecto, las operaciones no son recursivas. Podemos modificar este compartamiento estableciendo operaciones en cascada. En nuestro caso, usaremos CascadeOperations para establecer el insertar registros en cascada (al registrar un nuevo TodoItem, registraremos también sus Steps).
Para crear ambas tablas, al igual que con sqlite-net-pcl, utilizamos el método CreateTableAsync:
A la hora de desarrollar aplicaciones multiplataforma con Xamarin una de las arquitecturas más utilizadas sin duda alguna es MVVM. Para realizar la implementación contamos con diferentes opciones y algunos frameworks como MvvmCross, una de las opciones más utilizadas con Xamarin Classic.
Tras recibir diferente feedback, muchos tienen la duda…¿y con Xamarin.Forms?.
En este artículo vamos a ver como utilizar MvvmCross con Xamarin.Forms tanto para acciones básicas como asociar vistas con viewmodels así como realizar la navegación entre páginas o el uso de plugins.
¿Lo vemos?
MVVM
Model-View-ViewModel (MVVM) es un patrón de diseño de aplicaciones que permite desacoplar el código de interfaz de usuario del código que no sea de interfaz de usuario.
El patrón MVVM consta de 3 partes:
La vista (View) contiene la interfaz de usuario y su lógica.
El vista-modelo (ViewModel) contiene la lógica de presentación.
El modelo (Model) contiene la lógica de negocio junto a los datos.
La vista interactúa con el vista-modelo mediante enlace a datos (Data Binding) y mediante comandos:
Las ventajas conseguidas utilizando el patrón son significativas:
Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
El mantenimiento es más sencillo.
Permite realizar Test a nuestro código.
Permite una más fácil reutilización de código.
MvvmCross
MvvmCross es un framework que permite aplicar el patrón MVVM, totalmente centrado en Xamarin y el ecosistema móvil. Cuenta con una serie de opciones destinadas a resolver algunas de las necesidades más comunes en el desarrollo móvil además de una gran comunidad que aporta mejoras tanto al propio framework como una diversidad amplia de plugins.
MvvmCross soporta una gran variedad de plataformas. En el caso de Xamarin.Forms se soporta:
PCL
Android
iOS
UWP
En el futuro, al pasar releases estables, se añadirán otras plataformas como Linux (Gtk) o Tizen.
Utilizar MvvmCross en Xamarin.Forms
MvvmCross cuenta con una serie de paquetes NuGet que nos facilitan muchísimo la integración con Xamarin.Forms. Hablamos de los paquetes StarterPack que se encargará de añadir tanto las librerías básicas necesarias como los archivos que nos permitirán utilizar MvvmCross en cada plataforma y de MvvmCross.Forms.
NOTA: A la hora de escribir este artículo, utilizamos la versión 5.4.2 de MvvmCross.
Tras añadir ambos paquetes a todos los proyectos de la solución, comenzamos a editar cada proyecto.
PCL
Comenzamos modificando la aplicación Xamarin.Forms. Para ello, cambiamos:
En esta misma clase, vamos a eliminarla línea encargada de estableder la MainPage. Vamos a indicar cual es la vista inicial de la aplicación, a continuación. Al añadir el paquete StarterPack en la PCL se añadió una clase llamada App que vamos a renombrar a MvxApp (puedes renombrarla a CoreApp, u otro nombre):
public class mvxApp : MvvmCross.Core.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterNavigationServiceAppStart<MvxMainViewModel>();
}
}
Utilizamos el método RegisterNavigationServiceAppStart para indicar la viewmodel asociada a una vista que se mostrará como vista de inicio.
Todo listo a nivel de librería portable. Es hora de pasar a proyectos de cada plataforma.
Android
En cada plataforma vamos a contar con una clase llamada Setup encargada de hacer de bootstrapper ed MvvmCross (inicializa dependencias, componentes, etc). La mayoría de las acciones realizadas son virtuales, por lo que permiten personalización.
public class Setup : MvxFormsAndroidSetup
{
public Setup(Context applicationContext) : base(applicationContext)
{
}
protected override MvxFormsApplication CreateFormsApplication()
{
return new Core.App();
}
protected override IMvxApplication CreateApp()
{
return new Core.MvxApp();
}
protected override IMvxTrace CreateDebugTrace()
{
return new DebugTrace();
}
}
Establecemos MvxFormsApplication y la implementación de IMvxApplication de nuestra PCL.
NOTA: Fíjate que el tipo base de la clase setup en Android es MvxFormsAndroidSetup. En la clase Setup añadida por StarterPack, el tipo es MvxAndroidSetup.
Por otro lado, modificamos la actividad principal para cambiar el tipo a MvxFormsAppCompatActivity:
Llegamos a iOS donde tendremos una serie de pasos a realizar, muy similares a Android. Comenzamos por la clase Setup:
public class Setup : MvxFormsIosSetup
{
public Setup(IMvxApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
protected override MvxFormsApplication CreateFormsApplication()
{
return new Core.App();
}
protected override IMvxApplication CreateApp()
{
return new Core.MvxApp();
}
protected override IMvxTrace CreateDebugTrace()
{
return new DebugTrace();
}
}
También necesitamos modificar la clase AppDelegate:
[Register("AppDelegate")]
public partial class AppDelegate : MvxFormsApplicationDelegate
{
public override UIWindow Window { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var setup = new Setup(this, Window);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
LoadApplication(setup.FormsApplication);
Window.MakeKeyAndVisible();
return true;
}
}
Todo listo!
UWP
Al igual que en plataformas anteriores, comenzamos preparando la clase Setup:
public class Setup : MvxFormsWindowsSetup
{
private readonly LaunchActivatedEventArgs launchActivatedEventArgs;
public Setup(Windows.UI.Xaml.Controls.Frame rootFrame, LaunchActivatedEventArgs e) : base(rootFrame, e)
{
launchActivatedEventArgs = e;
}
protected override IMvxApplication CreateApp()
{
return new Core.MvxApp();
}
protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame)
{
Forms.Init(launchActivatedEventArgs);
var presenter = new MvxFormsUwpViewPresenter(rootFrame, new MvxFormsApplication());
Mvx.RegisterSingleton<IMvxViewPresenter>(presenter);
return presenter;
}
protected override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.None;
}
Modificamos la clase App.xaml.cs donde utilizaremos la clase Setup en el método OnLaunched tras inicializar Xamarin.Forms:
var setup = new Setup(rootFrame, e);
setup.Initialize();
Por último, en la página principal nativa del proyecto UWP:
public MainPage()
{
InitializeComponent();
var start = Mvx.Resolve<IMvxAppStart>();
start.Start();
var presenter = Mvx.Resolve<IMvxViewPresenter>() as MvxFormsUwpViewPresenter;
LoadApplication(presenter.FormsApplication);
}
Llega la hora de trabajar con vistas y viewmodels
Las vistas, es decir, páginas en Xamarin.Forms deben extender de MvxContentPage. Por lo que debemos cambiar:
public partial class MvxMainView : ContentPage
Por:
public partial class MvxMainView : MvxContentPage<MvxMainViewModel>
De igual forma, en el XAML de la página, se debe utilizar el namespace:
Por defecto, MvvmCross asociada vistas con ViewModels utilizando convención de nombres. Es decir, si tienes una vista llamada SignInView, la ViewModel debe llamarse SignInViewModel.
Cada ViewModel debe ser una clase que herede de MvxViewModel.
Tienes un ejemplo de implementación básico disponible en GitHub:
Navegación
MvvmCross utiliza las ViewModels para realizar la navegación. Es decir, la navegación se realiza de ViewModel a Viewmodel en lugar de vista a vista. Tenemos disponible un servicio completo de navegación llamado IMvxNavigationService.
En el ejemplo anterior, navegamos a una vista llamada SecondView (asociada a la ViewModel llamada SecondViewModel) pasando un parámetro de tipo cadena.
También contamos con diferentes eventos que podemos utilizar en cada ViewModel para la gestión del ciclo de la navegación (navegar, recibir parámetros, etc.):
public override void Prepare(string parameter)
{
Parameter = parameter;
}
Tienes un ejemplo de implementación de navegación con MvvmCross disponible en GitHub:
NOTA: Para obtener más información acerca de la navegación en MvvmCross, consulta este enlace.
MvvmCross utiliza Presenter para navegar a vistas en cada plataforma nativa. Actúa como «pegamento» entre vistas y ViewModels. Como podemos deducir de su nombre, un Presenter toma una solicitud de presentación de una ViewModel y decide como presentar la UI. De esa forma, se logra una clara separación entre vistas y ViewModels. Esto también es clave para poder mostrar vistas nativas, Forms o una mezcla de ellas.
NOTA: Se puede cambiar tanto el comportamiento como del servicio de navegación como de Presenter.
Plugins
Haciendo una sencilla búsqueda de «plugin MvvmCross» en NuGet tendremos acceso a una gran variedad de paquetes. Tras añadir el paquete, podemos utilizar el plugin haciendo uso de inyección de dependencias o vía resolución de interfaz. Veamos un ejemplo haciendo uso de Acr.MvvmCross.Plugins.UserDialogs.
Tienes un ejemplo de uso de plugins MvvmCross disponible en GitHub:
MvvmCross añade más funcionalidad en Xamarin.Forms como su propio sistema de enlace a datos (se puede utilizar perfectamente el de Xamarin.Forms) que añade opciones interesantes como Mutiple bindings o converters muy comunes por ejemplo. Sin embargo, en este artículo hemos visto los conceptos básicos necesarios en cualquier aplicación. En futuros artículos, continuaremos profundizando en otros conceptos así como ver otros frameworks y opciones!.
La evolución de Xamarin.Forms continua centrada en varios puntos donde destaca con fuerza la búsqueda de rendimiento. Con Xamarin.Forms 2.4.0 se introducía (en Android) los Fast Renderers. El concepto de los Fast Renderers es sencillo, simplificar al máximo los costes de renderizado de cada elemento visual.
Ahora, con la Preview de la versión 2.5.0 recibimos también Layout Compression.
¿Qué es Layout Compression?, ¿cómo afecta en nuestras aplicaciones?.
Layout Compression
Para conocer el concepto de Layout Compression junto con su aporte vamos a utilizar una ejemplo lo más «real» posible. Recientemente preparamos un ejemplo donde realizábamos la interfaz de Netflix con Xamarin.Forms. Este será nuestro ejemplo.
Al realizar un diseño cuidado de una aplicación móvil utilizamos una serie de elementos visuales. Estos elementos visuales, a su vez, en cada platataforma en determinadas ocasiones serán envueltos en paneles y otros elementos visuales para facilitar su uso y gestión. A la hora de renderizar cada vista, se realizan una serie de cálculos de tamaño y posicionamiento de cada elemento. Al contar con mayor complejidad, contaremos con mayor anidación de elementos, mayor número de iteraciones serán necesarias para calcular todo.
Layout Compression permite indicar elementos con anidamiento innecesario y optar a no crear layout. Con esta opción habilitada los layouts se optimizarán en tiempo de compilación permitiendo obtener un mejor rendimiento en tiempo de ejecución.
Con Xamarin Inspector podemos tener una listado completo de la jerarquía que compone la interfaz de usuario. Vamos a aplicar Fast Renderers junto con Layout Compression para hacer una comparativa directa cada vista de la aplicación Xamarin.Forms replicando la interfaz de Netflix.
Vamos a comenzar activando Fast Renderers. Para ello, en el proyecto Android, en su actividad principal:
Posteriormente es el momento de analizar la aplicación, identificar Layouts (Grid, StackLayout, AbsoluteLayout, RelativeLayout) para comprimir:
<Grid
CompressedLayout.IsHeadless="true">
</Grid>
¿Resultados?
Vista de elección de perfil
Partimos en la vista de selección de perfil. Con Xamarin Inspector realizamos un recuento del número total de Renderers necesarios en esta vista llegando a un total de 71 Renderers.
Tras activar Fast Renderers y utilizar Layout Compression reducimos el número hasta los 51.
Default: 71 Renderers.
Layout Compression: 51 Renderers.
Vista principal
Continuamos realizando el mismo proceso con la vista principal de la aplicación. Estamos ante una vista de mayor complejidad. El número total de Renderers sube hasta los 223.
Y utilizando Xamarin.Forms 2.5.0 Pre-Release bajamos a las 169.
Default: 223 Renderers.
Layout Compression: 169 Renderers.
Vista de detalles
Llegamos a la vista más compleja con 296 Renderers.
Que reducimos hasta 229 utilizando Layout Compression.
Default: 296 Renderers.
Layout Compression: 229 Renderers.
A tener en cuenta
La compresión de un Layout hace que se no cree su Renderer por lo tanto, no funcionarían aspectos relacionados con el Layout como:
Color de fondo.
Gestos.
Transformaciones.
Etc.
Revisa con cuidado si haces uso de TapGestureRecognizer u otra característica para decidir si puedes comprimir o no el Layout.
Conclusiones
En este ejemplos concreto (hemos intentado utilizar un ejemplo con cierta complejidad a nivel visual) la media de reducción de Renderers es de un 24%. Se exactamente que te estas preguntando, ¿como impactará en mi aplicación?, ¿será más rápida?. La respuesta es si. Antes que nada ten en cuenta que afecta a Android e iOS. En nuestro ejemplo, la aplicación si se nota más fluida aunque todo dependerá de diversos factores como el dispositivo, versión del sistema operativo, etc. En un Oneplus 3 (Snapdragon 820, 6GB de RAM) apenas se notan diferencias mientras que con un Nexus 5 (Snapdragon 800, 2 GB de RAM) si se nota mejora al cargar las páginas.
¿Y a ti que te parece?, ¿has realizado pruebas?. Te animo a compartir tus resultados en los comentarios de la entrada!
En todas las plataformas, las aplicaciones móviles incluyen animaciones que otorgan movimiento, fluidez y focalizan la atención del usuario en las zonas deseadas. Actualmente no son un extra o añadido en las aplicaciones, sino una parte importante en la experiencia y usabilidad de las mismas.
Como desarrolladores, debemos no solo cuidar por supuesto el correcto funcionamiento de la aplicación, sino que debemos preocuparnos también por la usabilidad y la experiencia otorgada, donde entran en juego las animaciones.
Entre el conjunto de posibilidades a la hora de animar elementos, las transiciones entre páginas son un punto destacado para conseguir trasmitir fluidez y sensación de continuidad.
¿Cómo aplicamos transiciones entre páginas en Xamarin.Forms?.
En este artículo, vamos a ver como aplicar diferentes transiciones entre páginas en aplicaciones Xamarin.Forms.
Navegar entre páginas y animaciones
Entre los diferentes patrones habituales utilizados en el desarrollo móvil, el más utilizado es la navegación en pila. En Xamarin.Forms la clase NavigationPage ofrece una experiencia de navegación jerárquica donde el usuario puede navegar a través de las páginas tanto hacia delante como hacia atrás.
Para navegar de una página a otra, la aplicación añadirá (push) una nueva página en el navigation stack o pila de navegación.
await Navigation.PushAsync (new Page());
Para navegar atrás, a la página anterior, la aplicación eliminará (pop) la página actual de la pila de navegación, y a partir de ese momento la última página disponible en la pila pasará a ser la página activa.
await Navigation.PopAsync ();
Por defecto, tanto al navegar hacia delante como hacia atrás, se aplica una transición entre páginas. Podemos desactivar la animación tanto al navegar hacia delante:
await Navigation.PushAsync (new Page(), false);
Como hacia atrás:
await Navigation.PopAsync (false);
Transiciones personalizadas
Pero…¿y si necesitamos/queremos aplicar una transición personalizada en nuestra aplicación?. Las transiciones entre páginas son un aspecto bien cubierto en cada plataforma. Accediendo a cada una de ellas, con código específico por plataforma, podremos crear experiencias personalizadas. Si, lo habrás imaginado, vamos a utilizar un Custom Renderer.
En nuestra librería portable o net standard, comenzamos creando un nuevo control que hereda de NavigationPage:
public class TransitionNavigationPage : NavigationPage
{
public TransitionNavigationPage() : base()
{
}
public TransitionNavigationPage(Page root) : base(root)
{
}
}
Necesitamos determinar que animación entre un conjunto deseamos aplicar. Para conseguir este objetivo, primero creamos una enumeración con todos los tipos diferentes de transiciones que podremos utilizar:
En Android, desde Lollipop, se dedicó un gran esfuerzo en mejorar el sistema de transiciones disponibles. Con la llegada de Material Design, llegó una nueva oleada de opciones. Tenemos la posibilidad de aplicar transiciones entre Activities y entre Fragments.
Utilizaremos la clase FragmentTransaction que nos permite realizar diferentes operaciones entre las que se encuentra, establecer la animación a aplicar en la transición utilizando el método SetCustomAnimations.
Transiciones en iOS
En el caso de iOS, aunque dependerá del tipo de animación a aplicar, utilizaremos principalmente la clase CATransition. Esta clase permite trabajar con la funcionalidad de animaciones del Core pudiendo aplicar una animación a un Layer completo.
Código específico de plataforma
En cada plataforma, vamos a crear una nueva clase TransitionNavigationPageRenderer donde realizaremos la implementación de nuestro Custom Renderer.
[assembly: ExportRenderer(typeof(TransitionNavigationPage.Controls.TransitionNavigationPage), typeof(TransitionNavigationPageRenderer))]
namespace TransitionNavigationPage.iOS.Renderers
{
public class TransitionNavigationPageRenderer : NavigationRenderer
{
}
}
En Android, utilizaremos el método SetupPageTransition para modificar la animación a utilizar.
protected override void SetupPageTransition(FragmentTransaction transaction, bool isPush)
{
switch (_transitionType)
{
case TransitionType.Fade:
break;
case TransitionType.Flip:
break;
case TransitionType.Scale:
break;
case TransitionType.SlideFromLeft:
break;
case TransitionType.SlideFromRight:
break;
case TransitionType.SlideFromTop:
break;
case TransitionType.SlideFromBottom:
break;
default:
break;
}
}
En el caso de iOS, utilizaremos los métodos PushViewController y PopViewController para detectar cuando navegamos hacia delante y hacia atrás para aplicar animaciones personalizadas.
Fade
Comenzamos aplicando una de las animaciones más sencillas y posiblemente utilizadas en muchas ocasiones, Fade.
En Android, creamos un archivo XML con la definición de la animación en la carpeta anim dentro de Resources.
NOTA: Utilizando ObjectAnimator podemos aplciar transformaciones mucho más efectivas para realizar esta animación en Android. Fue introducido con API Level 11 (Android 3.0).
Puedes descargar el ejemplo realizado desde GitHub:
¿Más opciones?
Realizando una combinación de las opciones realizadas podemos abordar muchas otras transiciones habituales. Existen otro tipo de transiciones que han ido ganando peso en Material Design, etc.
Por ejemplo, en Android hablamos de opciones como CircularReveal.
Podemos conseguir este efecto utilizando el método CreateCircularReveal disponible en ViewAnimationUtils.
En próximos artículos, podemos abordar de nuevo este punto viendo otras opciones como la anterior. ¿Qué transiciones sueles utilizar?, ¿cuál te gustaría ver?. Recuerda, cualquier duda o comentario es bienvenida en los comentarios de la entrada.
Xamarin.Forms cuenta con diferentes páginas que aportan diferentes experiencias de navegación.
Entre las opciones, una de las más utilizadas es la experiencia ofrecida por NavigationPage. Esta experiencia de navegación consiste en una navegación jerárquica e la que el usuario puede navegar por las páginas hacia delante y hacia atrás. Se implementa la navegacion como una pila de páginas (LIFO).
El layout de la NavigationPage
El layout de la NavigationPage es dependiente de cada plataforma:
En iOS, se utiliza una barra de navegación en la parte superior de la página mostrando título, y si es necesario, el botón de navegar atrás.
En Android, se muestra también una barra de navegación en la parte superior con título, icono y botón de navegar atrás.
En Windows, se muestra el título en una CommandBar situada en la parte superior. El botón de navegación atrás aparecerá en la barra de título de la ventana en escritorio y el botón virtual atrás en la parte inferior en móvil.
¿Cómo podemos personalizar la barra de navegación?
En este artículo vamos a aprender como:
Ocultar la barra de navegación.
Personalizar colores de la barra, del título, etc.
Añadir un logo personalizado en la barra de navegación.
Personalizar la fuente del título.
Personalizar el icono del botón atrás.
Ocultar la barra de navegación
En determinadas páginass puede interesarnos no mostrar la barra de navegación. Es bastante sencillo controlar este comportamiento gracias al método SetHasNavigationBar.
En C#:
NavigationPage.SetHasNavigationBar(this, false);
En XAML:
NavigationPage.HasNavigationBar="False"
Personalizando colores
Continuamos con otra necesidad bastante común en muchas aplicaciones, personalizar el color de fondo y del título de la aplicación.
Con la propiedad BarBackgroundColor podemos definir el color de fondo de la barra de navegación mientras que con la propiedad BarTextColor definimos el color del título.
Puedes descargar el código del ejemplo desde GitHub:
Fuente personalizada en el título
El uso de fuentes personalizadas suele ayudar a potenciar la imagen de marca en muchas aplicaciones móviles. Existen multitud de casos donde el título de la barra de navegación cuenta con una fuente personalizada aplicada.
¿Cómo logramos esto?.
Vamos a necesitar código específico en cada plataforma. Entran en juego los Custom Renderers.
Android
En Android comenzamos añadiendo la fuente a utilizar en la carpeta de Assets.
Accedemos a la Toolbar (tras ser añadida) y posteriormente accedemos a sus elementos hijos, para acceder al título (TextView) y modificar la fuente.
Este mismo proceso será muy similar en otras plataformas.
iOS
También vamos a necesitar un Custom Render:
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.iOS.Renderers
{
public class CustomNavigationPageRenderer : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var textAttributes = new UITextAttributes();
textAttributes.Font = UIFont.FromName("Trashtalk", 20);
UINavigationBar.Appearance.SetTitleTextAttributes(textAttributes);
}
}
}
}
Bastante sencillo. Accedemos a la NavigationBar en iOS y aprovechamos el método SetTitleTextAttributes para personalizar la fuente.
UWP
Y llegamos a Windows.
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.UWP.Renderers
{
public class CustomNavigationPageRenderer : NavigationPageRenderer
{
private CommandBar _commandBar;
public CustomNavigationPageRenderer()
{
ElementChanged += OnElementChanged;
}
private void OnElementChanged(object sender, VisualElementChangedEventArgs e)
{
ElementChanged -= OnElementChanged;
ContainerElement.Loaded += OnContainerElementLoaded;
}
private void OnContainerElementLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
ContainerElement.Loaded -= OnContainerElementLoaded;
_commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
var commandBarContent = ((Border)_commandBar.Content);
var textBlock = commandBarContent.Child as TextBlock;
if(textBlock != null)
{
textBlock.FontFamily = new FontFamily("/Assets/Trashtalk.ttf#Trashtalk");
}
}
}
}
Muy similar a lo realizado anteriormente en otras plataformas. Accedemos a la CommandBar utilizada como barra de navegación. A continuación, buscamos el TextBlock utilizado como título y utilizando la propiedad FontFamily, modificamos la fuente.
NOTA: Se puede definir el Element o parte compartida del Custom Renderer donde definir propiedades como el nombre de la fuente a utilizar o propiedades de tipo bool para aplicar la fuente personalizada o no en base a necesidades de cada página y hacer todo más personalizable.
El resultado en Android:
En iOS:
Y en Windows:
Puedes descargar el código del ejemplo desde GitHub:
Añadir un logo
Hemos hablado previamente del uso de fuentes personalizadas para conseguir trasmitir mejor la imagen de marca. Otro uso habitual es el de incluir una imagen (logo) en la barra de navegación.
¿Cómo lo conseguimos?.
Android
En Android podemos modificar el contenido de la Toolbar utilizando un layout AXML. Tras añadir la imagen a utilizar como logo en los recursos del proyecto, definimos un nuevo Layout:
La evolución de Xamarin.Forms es meritoria. En los últimos tiempos se han recibido novedades interesantes como efectos, vistas nativas, Forms Embedding, etc. Sin embargo, en muchos casos se sigue asociado a desarrollos muy simples o formularios básicos.
Realmente, en el estado actual de Xamarin.Forms se pueden conseguir aplicaciones nativas de gran escala, con interfaces cuidadas y con alta integración con la plataforma. Hay que tener en cuenta el conjunto de Custom Renderers (código específico en cada plataforma) necesario para lograrlo.
NOTA: La elección entre Xamarin Classic o Xamarin.Forms es importante. Es necesario evaluar la aplicación a desarrollar, el conjunto de características específicas de cada plataforma (que pueden requerir un Custom Renderer), etc.
En este artículo, vamos a tomar como referencia una aplicación bastante conocida, disponible en las listas de destacados de las diferentes tiendas, Netflix. Vamos a desarrollar la interfaz de la aplicación móvil de Netflix con Xamarin.Forms paso a paso.
¿Cuántos Custom Renderers crees que serán necesarios?, ¿qué cantidad de código podremos compartir?, ¿tendremos buen resultado final?.
La pantalla de selección de perfil
Vamos a comenzar partiendo de una pantalla bastante representativa y común en Netflix, la selección de usuario o perfil.
Vamos a analizar características de la pantalla y a determinar que vamos a necesitar:
Se puede ver una barra de navegación. Algo que podemos conseguir de forma sencilla en Xamarin.Forms gracias al uso de NavigationPage.
En la barra de navegación se muestra una imagen, el logo de la aplicación. Para poder ajustar la imagen, tamaño y posición, vamos a necesitar un Custom Renderer en el caso de iOS. Para Android, podemos utilizar layout AXML con facilidad.
En la barra de navegación también tenemos un botón de edición. Nada complejo que podemos abordar utilizando ToolbarItem.
Y llegamos al listado de opciones de perfil para elegir. La mayor característica que diferencia al listado es ele uso de columnas. Utilizaremos FlowListView.
Añadir el logo en la barra de navegación
En Android tenemos la definición de la Toolbar en Resources > layout >Toolbar.axml. Para añadir una imagen como logo y poder configurarla a nuestro antojo:
¿Cómo podemos gestionar esta necesidad?. Tenemos muchas opciones:
Crear un control personalizado de Layout.
Crear un control más simple utilizando un ScrollView y un Grid.
Utilizar algo existente que nos otorge esta solución.
La comunidad Xamarin y el conjunto de plugins, controles y librerías disponibles es elevada. Vamos a utilizar FlowListView.
FlowListView permite crear listados con columnas además de permitir:
Carga infinita.
Cualquier contenido como celda.
DataTemplateSelector.
Grupos.
Etc.
Añadimos el paquete NuGet a todos los proyectos de la solución. Debemos realizar la inicialización de FlowListView en nuestra librería compartida, App.xaml.cs:
FlowListView.Init();
A continuación, y tras declarar el espacio de nombres en la página XAML de selección de perfil:
Definimos dos columnas con la propiedad FlowColumnCount.
Vamos a ver el resultado final de lo que llevamos, en Android:
Y en iOS:
Vamos bastante bien, ¿continuamos?.
Pantalla principal
Continuamos con la pantalla principal y al igual que hicimos previamente, vamos a realizar un análisis previo de que vamos a necesitar:
Tenemos un menú lateral deslizante. Un patrón de navegación muy conocido y utilizado en una gran variedad de aplicaciones. En Xamarin.Forms podemos utilizar este patrón de navegación utilizando una MasterDetailPage.
Se continua mostrando el logo en la barra superior. Continuaremos utilizando lo previamente realizado.
Contamos con un botón de búsqueda en la parte superior. De nuevo, volveremos a hacer uso de ToolbarItem.
El contenido se caracteriza por contar con diferentes grupos o bloques de elementos a los que accedemos realizando scroll horizontal. Para conseguir este objetivo, vamos a realizar un control personal.
Menú lateral deslizante
Al navegar al apartado principal, necesitaremos utilizar una MasterDetailPage.
MasterDetailPage cuenta con dos propiedades fundamentales:
Master: Contenido del menú lateral deslizante.
Detail: Vista que visualizamos.
En Master vamos a definir el menú principal de Netflix un poco más adelante. En Detail, vamos a definir en una ContentPage la vista principal.
Listado horizontal
Necesitamos utilizar un listado horizontal. Ante un volumen elevado de elementos debemos de pensar en aspectos como el rendimiento (caché, reutilización del celdas, etc.). En este caso, cada listado horizontal cuenta con un volumen bajo de elementos.
Vamos a realizar un control personal con código totalmente compartido y utilizando elementos de Xamarin.Forms. Vamos a utilizar un ScrollView junto a un StackLayout apilando elementos horizontalmente. Veamos el constructor del control con la definición básica del mismo:
public HorizontalList()
{
_scrollView = new ScrollView();
_itemsStackLayout = new StackLayout
{
Padding = Padding,
Spacing = Spacing,
HorizontalOptions = LayoutOptions.FillAndExpand
};
_scrollView.Content = _itemsStackLayout;
Children.Add(_scrollView);
}
Necesitamos definir la fuente de información, como se verá cada elemento, etc. Para ello, definimos una serie de BindableProperties:
Dentro de un StackLayout (apila elementos por defecto verticalmente) repetimos la misma estructura para mostrar diferentes grupos (últimas novedades, películas, series, etc.). El resultado en Android:
Y en iOS:
El menú principal
¿Recuerdas que anteriormente hablamos ligeramente del menú principal al hablar de la MasterDetailPage?. Es hora de retomarlo. Características:
Tenemos una cabecera donde podemos ver el perfil utilizado en la aplicación.
A continuación, tenemos un listado con las diferentes secciones de la aplicación.
Hay apartados que destacan al contar con separadores entre otros elementos además de icono que refuerza su contenido.
Listado de opciones
Todos los puntos anteriores los podemos conseguir directamente utilizando un ListView junto a una ViewCell personalizada y el Header.
El separador solo aparece con algunos de los elementos. Por este motivo, y aunque el ListView cuenta con la propiedad SeparatorVisibility, creamos el separador en la plantilla que define a cada elemento donde vamos a controlar la visibilidad en base a una propiedad que define cada elemento del menú.
Llegamos a la vista más compleja de todas las que llevamos.
¿Por qué?.
Analicemos las necesidades:
La barra de navegación, donde encontraremos el botón de navegación atrás entre otras opciones, es transparente!. Vamos a necesitar un Custom Renderer para conseguir este objetivo tanto en Android como en iOS.
El logo… desaparece. Tenemos que gestionar este cambio. En Android usaremos código específico para acceder a la imagen de logo y jugar con su visibilidad. En iOS, también vamos a utilizar código específico pero en forma de Custom Renderer.
La imagen destacada del elemento (película, documental o serie) seleccionada hace efecto Parallax. Gracias a las opciones de transformación de Xamarin.Forms podremos hacer translaciones, jugar con la opacidad o escala. Es decir, podemos conseguir este efecto gestionando el scroll realizado y con código totalmente compartido.
Hay un listado de elementos similares mostrado en tres columnas. Aquí volveremos a hacer uso de FlowListView.
Barra transparente
Una de las características principales de la página de detalles es la barra de navegación transparente.
¿Cómo lo conseguimos?.
En Android, vamos a crear un Custom Renderer de NavigationPage.
public class CustomNavigationBarRenderer : NavigationPageRenderer
{
}
Junto al uso de mensajería utilizando FormsToolkit haremos que por defecto el color de la barra de navegación sea de color, menos el la página de detalles, que será transparente.
En el renderer personalizado de la NavigationPage, vamos a verificar si el color es transparente para modificar el tamaño y posición del Layout para conseguir el efecto.
En el caso de iOS, necesitamos de nuevo un Custom Renderer.
public class TransparentNavigationBarPageRenderer : PageRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (NavigationController != null)
{
NavigationController.NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationController.NavigationBar.ShadowImage = new UIImage();
NavigationController.NavigationBar.BarTintColor = UIColor.Clear;
NavigationController.NavigationBar.TintColor = UIColor.White;
}
}
}
Modificamos el color de la barra, suprimimos la imagen de sombra para evitar cualquier tipo de separación visual entre la cabecera y el contenido para lograr la sensación de continuidad buscado.
Es momento de quitar el logo
De nuevo, hacemos uso de mensajería utilizando FormsToolkit para conseguir este objetivo en Android. Desde la vista de detalles (PCL):
Sin duda, lo habrás experimentado ya sea en web o e aplicaciones móviles. Haces scroll y el fondo (o una imagen) se mueve a una velocidad distinta que el contenido, creando un ligero efecto de profundidad.
¿Cómo conseguimos esto?.
Hemos comentado que el efecto se aplica al hacer scroll. Por lo tanto, comenzamos capturando información cada vez que se realiza scroll gracias al evento Scrolled del ScrollView.
Dependiendo de la dirección del scroll, se captura la cantidad de scroll realizado con e.ScrollY, para finalmente aplicar una transformación de transladación de la cabecera.
El resultado:
NOTA: Se puede modificar este efecto para conseguir potenciarlo aún más. Habitualmente también se juega con la escala y con la opacidad. Ambas opciones al igual que la transladación son posibles desde código compartido.
Listado con tres columnas
Nada «diferente» a lo ya visto previamente. Hacemos uso de FlowListView:
Aplicando 3 columnas con la propiedad FlowColumnCount.
El resultado en Android:
Y en iOS:
Puedes descargar el código del ejemplo desde GitHub:
¿Qué plugins o componentes se han utilizado?
Se ha utilizado:
FFImageLoading – Con el objetivo principal de cachear imágenes. Dado el tipo de aplicación y la importancia de las imágenes, es importante. Aunque recuerda, en este ejemplo todas las imágenes son locales.
Xamarin Forms Toolkit – Toolkit para Xamarin.Forms con helpers, converters, etc. Se hace uso principalmente del sistema de mensajería.
FlowListView – ListView con soporte a columnas (entre otras características).
Conclusiones
Creo que las capturas son una buena conclusión. Logramos replicar la interfaz de usuario de una aplicación sumamente utilizada y destacada en las diferentes tiendas utilizando exclusivamente lo que proporciona Xamarin.Forms junto a plugins o componentes gratuitos por parte de la comunidad.
Tras pasar varias analíticas podemos ver que en este ejemplo, en Android se llega a compartir un 85,87% de código y un 89.53% en iOS. Podríamos seguir compartiendo gran cantidad de lógica como peticiones HTTP, gestión de errores, etc. Sin embargo, probablemente y de cara a ciertas necesidades de la aplicación, también se requerirían más Custom Renders y/o efectos. La cantidad de código compartida seguiría siendo elevada.
Y llegamos a la parte final del artículo. Es un concepto de artículo que tenía en mente y he decidido a lanzarme a ello tras ver una idea similar por varios compañeros en la comunidad. ¿Qué te parece este tipo de artículos?. Es sumamente divertido preparar una aplicación conocida e intetar «desgranar» cada pasos a realizar. Sin duda, espero repetir esta idea con otras aplicaciones aunque me gustaría saber tu opinión. ¿prefieres artículo habitual, videoblog, otro formato?.
Cuando desarrollamos nuestras aplicaciones, un factor importante a tener en cuenta es el público objetivo de la misma. Analizando este punto debemos determinar si distribuimos nuestra aplicación a nivel nacional, en multiples países o a nivel mundial. Si decidimos la última opción debemos de tener en cuenta que aunque las probabilidades de llegar a una mayor cantidad de público aumentan considerablemente, no todo el mundo habla español por lo que seguimos restringidos.
¿Qué podemos hacer?
En este artículo vamos a realizar los pasos necesarios para localizar una aplicación Xamarin.Forms , ¿te apuntas?.
Globalización en Apps Xamarin.Forms
El proceso de globalización consiste en preparar nuestra aplicación para «todo el mundo». La aplicación debe estar preparada para mostrar la información en diferentes idiomas. Los textos y recursos de nuestra aplicación deben ser diferentes en base al idioma configurado por el dispositivo.
Archivos RESX
Para la gestión de textos en cada idioma utilizaremos archivos RESX. Tendremos un archivo de recursos diferente por cada idioma soportado. Cada archivo se basa en clave-valor. Las claves se repetirán en cada archivo mientras que el valor será la cadena localizada al idioma correspondiente.
Creando los archivos RESX
En la librería portable, comenzamos creando una carpeta Resources con el objetivo de mantener todos los archivos organizados. A continuación, crearemos un archivo de recursos por cada idioma soportado. Para crear el archivo de recursos, hacemos clic derecho sobre la recien creada carpeta Resources, Añadir > Nuevo archivo > General > Resource File.
Nombra al archivo como Resources o AppResources (nombres habituales) para el idioma por defecto, inglés. Por cada idioma soportado, se debe repetir el proceso llamando al archivo AppResources.{nombre cultura}.resx.
NOTA: Puedes ver un listado completo de países, idiomas y su cultura en este enlace.
Añadiendo soporte al idioma español:
Tras ambos archivos, si abrimos un archivo de recurso veremos algo como lo siguiente:
Añadiremos las claves en la comuna Nombre y el valor localizado a cada idioma en la columna Valor. Posteriormente, accederemos a cada valor localizado utilizando la clave. En base al idioma, se accederá a un archivo de recursos u otro. De esta forma, podremos mantener de forma sencilla los textos localizados y ofecer una aplicación localizada a diferentes idiomas.
Utilizando los archivos desde C#
Tras añadir un valor:
Podemos acceder al valor desde código C# de forma sumamente sencilla:
Label label = new Label()
{
Text = AppResources.LanguageText
};
Si se lanza la aplicación en español y en inglés el valor estará localizado en cada idioma.
Y ahora desde XAML
Utilizar valores localizados utillizando archivos de recursos es rápido y directo desde código C#. Sin embargo, desde XAML necesitamos «algo más». Por defecto, no tenemos ninguna extensión de marcado que nos permita indicar el valor de la clave que buscamos en el archivo de recursos para permitir localizar la aplicación. Sin embargo, si podemos crear extensiones de marcado personalizadas utilizando la interfaz IMarkupExtension:
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
const string ResourceId = "XamarinFormsLocalization.Resources.AppResources";
public string Text { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return null;
ResourceManager resourceManager = new ResourceManager(ResourceId, typeof(TranslateExtension).GetTypeInfo().Assembly);
return resourceManager.GetString(Text, CultureInfo.CurrentCulture);
}
}
Utilizamos la clase ResourceManager para acceder al archivo de recurso correspondiente. A continuación, se utiliza el método GetString para obtener el valor asociado a la clave pasada como parámetro en la propiedad Text.
¿Y cómo lo utilizamos?.
Sencillo, primero declaramos el espacio de nombres en XAML necesario:
Utillizamos la extensión pasando como parámetro la clave deseada en la propiedad Text. También podemos utilizar la extensión de marcado de forma menos verbosa:
Una aplicación móvil no se compone exclusivamente de textos. Existen otra serie completa de recursos muy utilizados. Entre los recursos más utilizados, imágenes.
Normalmente se cuentan con imágenes que se pueden ver exactamente igual en diferentes idiomas, pero no así con todas.
¿Cómo gestionamos la localización de imágenes por idioma?
Es una tarea que debemos realizar en cada plataforma de forma específica.
Android
Utilizamos la carpeta drawable dentro de Resources para la gestión de imágenes. Para contar con imágenes diferentes por idioma, podemos crear carpetas con el nombre drawable-{código del idioma}. Android se basa en esta estructura de carpetas con sufijos para localizar imágenes.
NOTA: Para soportar diferentes imágenes para resoluciones diferentes y además basándonos en el idioma, seguimos utilizando la misma estructura de sufijos en carpetas. Por ejemplo: drawable-es-xdpi.
iOS
En este caso, iOS, utiliza Localization Projects o .lproj para contener imágenes por idioma junto con recursos de cadenas. Dentro de la carpeta Resources debemos crear {códido de idioma}.lproj por cada idioma soportado.
Cada carpeta correspondiente a un idioma contará con sus versiones de imágenes. En caso de que una carpeta de un idioma no cuenta con imágenes, se utilizarán las correspondientes del idioma por defecto.
NOTA: Para pantallas retina se pueden añadir imágenes @2x y @3x.
UWP
En el caso de aplicaciones universales, organizaremos dentro de la carpeta Assets los recursos por idioma en carpetas con el nombre de la cultura.
No tenemos un mecanismo que automáticamente seleccione la imagen correspondiente a cada idioma. Por este motivo, vamos a crear un sencillo Custom Renderer.
xmlns:extensions="clr-namespace:XamarinFormsLocalization.MarkupExtensions"
[assembly: ExportRenderer(typeof(Image), typeof(XamarinFormsLocalization.UWP.Renderers.LocalizedImageRenderer))]
namespace XamarinFormsLocalization.UWP.Renderers
{
public class LocalizedImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var fileImageSource = e.NewElement.Source as FileImageSource;
if (fileImageSource != null)
{
var fileName = fileImageSource.File;
string currentUICulture = CultureInfo.CurrentUICulture.ToString();
e.NewElement.Source = Path.Combine("Assets/" + currentUICulture + "/" + fileName);
}
}
}
}
}
Creamos un Custom Render en UWP del control Image. Se obtiene la ruta de la imágen a la que se añade la ruta correcta utilizando CurrentUICulture. De esta forma, automáticamente, en base al idioma se accederá a una ruta u otra.
El resultado de la aplicación en inglés:
Y en español:
Puedes descargar el código del ejemplo desde GitHub:
Existen otra serie de puntos interesantes relacionados con la localización de aplicaciones como por ejemplo la localización del nombre de la aplicación que veremos en otro artículo.
Recuerda, cualquier duda o comentario es bienvenido!
La evolución de Xamarin.Forms en los últimos años ha sido más que sorprendente. De igual forma que el interés y crecimiento de uso. Partía de una base sin duda atractiva, una capa de abstracción a nivel de UI que permite crear con el mismo código, C# o XAML, la interfaz de usuario en iOS, Android y Windows. Versión tras versión ha logrado llegar a permitir más posibilidades mejorando el soporte de cada plataforma, las posibilidades en XAML o el rendimiento (cabe destacar las mejoras en este punto). Hoy día se pueden hacer aplicaciones con resultados bastante sorprendentes.
Con la futura versión 3.0 en camino, entre las futuras mejoras encontramos la llegada de nuevos backends o lo que es lo mismo nuevas plataformas como WPF (Windows) o Linux (GTK#). Sin embargo, hoy día ya tenemos disponible como Preview una nueva plataforma, macOS.
En este artículo vamos a añadir un proyecto macOS y Xamarin.Forms al mismo. Veremos que con pocos sencillos pasos podemos lograr tener una aplicación para una nueva plataforma.
Configurar el proyecto macOS
Hasta tener plantillas de proyectos Xamarin.Forms con aplicación Cocoa, comenzaremos creando Mac > App > Cocoa App en Visual Studio para macOS.
NOTA: Al igual que el desarrollo con Xamarin.Mac, el desarrollo para macOS requiere de un Mac.
Tras crear el proyecto, debemos añadir una librería portable (PCL) donde añadiremos el código común compartido con otros posibles backends Xamarin.Forms. Tras añadir el proyecto de la PCL, debemos añadir la referencia de la misma en el proyecto macOS.
A continuación, añadimos en ambos proyectos el paquete NuGet de Xamarin.Forms:
La aplicación Cocoa viene con un Storryboard para configurar la interfaz de usuario y ciertos parámetros que no vamos a utilizar. Debemos editar el archivo Info.plist para eliminar la entrada NSMainStoryboardFile:
Llega el momento de realizar pequeños cambios de código para cargar la aplicación Xamarin.Forms. Comenzamos editando Main.cs:
Tenéis el código fuente del ejemplo utilizado disponible en GitHub:
Personalización por platafoma
La clase Device sin duda alguna es una de las claves a la hora de personalizar la interfaz de usuario por plataforma. Esta clase contiene diversas propiedades y métodos para personalizar la UI y la funcionalidad en base a la plataforma o el factor de forma.
Contamos con la propiedad RuntimePlatform que cuenta con un valor de una enumeración con cada una de las plataformas soportadas. Se ha incluido macOS y ahora tenemos también la posibilidad de crear código condicional para macOS en Xamarin.Forms: