Xamarin Forms – usando Xamarin Effects

Este artigo tem como objetivo mostrar como usar efeitos no desenvolvimento de aplicações móveis usando a framework Xamarin Forms.

Introdução

A framework Xamarin Forms permite abstrair a camada de user interface entre as diversas plataformas, ie, através desta framework podemos escrever um único código que define toda a aplicação e que irá ter a sua representação gráfica respeitando é claro o controlos gráficos de cada plataforma. Refiro-me é claro às plataformas iOS, Android e Windows, cujo UX difere em alguns aspetos.

Elementos gráficos relativos a iOS e Android  – diferenças ao nível controlos e icons
Figura 1: Elementos gráficos relativos a iOS e Android – diferenças ao nível controlos e icons

Para muitos a Figura 2 é reconhecida quando nos referimos à percentagem de código que podemos reutilizar entre usando o “tradicional desenvolvimento” – Xamarin.Android ou Xamarin.iOS e quando usamos a framework Xamarin Forms.

Xamarin vs Xamarin Forms
Figura 2: Xamarin vs Xamarin Forms

O uso da framework (Xamarin Forms) pode não se aplicar a todos os tipos de aplicações, pois os diversos requisitos e as especificidades técnicas da aplicação podem ser determinísticas na escolha – dou o exemplo de aplicações com muitas animações ou aplicações que a user interface usa controlos que são específicos da plataforma, e claro está dificulta no uso desta framework. Conceptualmente esta framework tem um interesse muito grande pela convergência que se tem vindo a sentir na reutilização de código e na redução do tempo de desenvolvimento.  E claro está, que para o bom uso da mesma é necessário compreender toda a abstração inerente e também a forma como os diversos garbage collections são geridos pois isto é relevante na gestão de memória da aplicação e deteção de memory leaks (criados pelo próprio programadores) – muitas das vezes estes aspetos não são tido em conta durante o desenvolvimento.

Uma das funcionalidades fornecidas pela Xamarin ao nível desta framework, logo desde o início, foi o chamado “Rendersque permite alterar o controlo de base, ie, é possível através do render obter o controlo nativo da plataforma e claro está aplicar as alterações pretendidas. Sem dúvidas que os renders são muito interessantes, mas quando se começa a criar muitos renderes podemos concluir que o uso do tradicional desenvolvimento (Xamarin.iOS ou Xamarin.Android) poderá ser o melhor (depende muito de caso para caso).

Mais recentemente, a Xamarin desenvolveu uma nova funcionalidade chamada “Effects” que tem como finalidade facilitar a customização dos controlos nativos, e desta forma a criação de “renders” deixa de fazer sentido em muitos casos, e todo o desenvolvimento fica então mais simplificado.

Descrição

Para exemplificar o uso de “Effects”, usemos o controlo Button como exemplo – vejam a seguinte figura:

Xamarin: Classe Button e as diversas representações em cada plataforma
Figura 3: Classe Button e as diversas representações em cada plataforma

Do lado esquerdo temos a classe Button da framework Xamarin Forms e do lado direito temos o controlo gráfico e o namespace a que pertence o respetivo controlo em cada plataforma – Button em Android e Windows e IUButton em iOS.

Xamarin: A classe ButtonRenderer, definida no projeto target, será a classe que permite fazer a tradução entre o Button de Xamarin Form para o controlo nativo
Figura 4: A classe ButtronRenderer, definida no projeto target, será a classe que permite fazer a tradução entre o Button de Xamarin Form para o controlo nativo.

Antes de existirem os “Effects” os renders eram estendidos, ie, a class ButtonRenderer era estendida em cada um dos targets, para se poder alterar o controlo nativo. Em alguns casos, a extensão dos “Renders” obriga a criar um novo controlo do lado de Xamarin Forms, ie, MyButton que herda da classe Button, para que desta forma o “Render se aplique apenas ao controlo especifico. Para diferenciar entre as duas funcionalidades, um bom exemplo do uso de “Effects” é aplicação e ajuste do theme da aplicação em que o controlo nativo se mantém e apenas se pretende ajustar algumas propriedades nativas – podendo ser definido de várias formas:

  • usando o nome do “Effects”
  • usando RoutingEffect

Por usa vez, podem ser afetos ao controlo de Xamarin Forms através da respetiva propriedade para o efeito ou através de attached properties.

Criação de um “Effect”

Suponhamos que temos um controlo que apresenta texto, uma Label, de seguida vamos ver como podemos definir o tipo de letra para cada target.

Começamos então por definir o effect em cada plataforma.

iOS

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(MyCustomFontEffect), "CustomFontEffect")]
namespace CustomFont.iOS {
  class MyCustomFontEffect : PlatformEffect {
    UIFont oldFont;
    protected override void OnAttached() {
      if (Element is Label == false) return;
      var label = Control as UILabel;
      oldFont = label.Font;
      label.Font = UIFont.FromName("Pacifico", label.Font.PointSize);
    }

    protected override void OnDetached() {
      if(oldFont != null) {
        var label = Control as UILabel;
        label.Font = oldFont;
    }
  }
}

 Android

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(MyCustomFontEffect), "CustomFontEffect")]
namespace CustomFont.Droid {
  class MyCustomFontEffect : PlatformEffect {
    Typeface oldFont;
    protected override void OnAttached() {
      if (Element is Label == false) return;
      var label = Control as TextView;
      oldFont = label.Typeface;
      var font = Typeface.CreateFromAsset(Android.App.Application.Context.Assets, "Pacifico.ttf");
      label.Typeface = font;
    }

    protected override void OnDetached() {
      if (oldFont != null) {
        var label = Control as TextView;
        label.Typeface = oldFont;
      }
    }
  }
}

Windows 

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(MyCustomFontEffect), "CustomFontEffect")]
namespace CustomFont.UWP {
  class MyCustomFontEffect : PlatformEffect {
    Windows.UI.Xaml.Media.FontFamily oldFontFamily;
    protected override void OnAttached() {
      if (Element is Label == false) return;
      TextBlock tb = Control as TextBlock;
      oldFontFamily = tb.FontFamily;
      tb.FontFamily = new Windows.UI.Xaml.Media.FontFamily(@"/Assets/Pacifico.ttf#Pacifico");
    }

    protected override void OnDetached() {
      if (oldFontFamily != null) {
        TextBlock tb = Control as TextBlock;
        tb.FontFamily = oldFontFamily;
      }
    }
  }
}

De salientar, que o método OnAttached é chamado quando o effect é atribuído a um controlo de Xamarin Forms, e o método OnDetached é chamando quando o effect deixa de estar associado ao controlo de Xamarin Forms. O método OnElementPropertyChanged é chamado quando ocorre alterações numa propriedade do controlo de Xamarin Forms que pode requerer alterações no controlo nativo associado.

De seguida vamos definir uma página em que vamos ter um Label à qual iremos aplicar o effect definido.

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:ef="clr-namespace:ControlExplorer"
    x:Class="ControlExplorer.MainPage">
  <StackLayout x:Name="stackLayout" Padding="20" Spacing="10">
    <Label x:Name="labelWelcome" Text="Welcome to Effects!" />
    <Switch x:Name="switchEffects"
      Toggled="OnSwitchToggled" IsToggled="True" />
    <Label Text="Effects are a great way to apply platform-specific UI features to a Xamarin.Forms defined UI.">
      <Label.Effects>
        <ef:LabelFontEffect />
      </Label.Effects>
    </Label>
    <Button x:Name="buttonClick" Text="Click Count = 0" Clicked="OnButtonClicked" TextColor="White" BackgroundColor="Blue" />
    <Slider Maximum="255" ValueChanged="OnSliderColorValueChanged" />
  </StackLayout>
</ContentPage>

C#

Para adicionar o effect ao controlo de Xamarin Forms:

private Effect _fontEffect;
public MainPage() {
  InitializeComponent();
  _fontEffect = Effect.Resolve("Xamarin.CustomFontEffect");
  labelWelcome.Effects.Add(_fontEffect);
}

Para remover (em qualquer momento):

labelWelcome.Effects.Remove(_fontEffect);

Em cada target é definido em cada effect o ExportEffect que conhece o tipo do effect criado e que tem um nome associado (CustomFontEffect), de salientar que esse nome deve ser comum nos diversos targets para que o effect quando aplicado em Xamarin Forms seja reconhecido.

[assembly: ExportEffect(typeof(MyCustomFontEffect), "CustomFontEffect")]

Criação de um RoutingEffect

A criação de RoutingEffects acaba por ser muito interessante, pelo fato de deixamos de usar string com o nome do effect e passamos a usar um objecto que define o effect. Portanto o RoutingEffect não é mais do que uma classe que fica definida no projeto partilhado e que permite usar um objecto para definir o effect em vez de usar o método  Resolve e a string do nome. A sua implementação de exemplo é a seguinte:

class LabelFontEffect : RoutingEffect {
  public LabelFontEffect() : base("Xamarin.CustomFontEffect") { }
}

Nota: De salientar, que no RoutingEffects é usado o nome do render CustomFontEffect, mas também o seu nome do ResolutionGroupName definido em cada effect.

[assembly: ResolutionGroupName("Xamarin")]

Desta forma, o efeito pode ser definido no controlo de Xamarin Forms da seguinte forma:

   <Label Text="Effects are a great way to apply platform-specific UI features to a Xamarin.Forms defined UI.">
     <Label.Effects>
       <ef:LabelFontEffect />
     </Label.Effects>
   </Label>

Um Effect pode ser associado a qualquer controlo que suporte effects, no entanto, este effect deve ser usado tendo em conta se está associado (a um Button ou a uma Label), pois não devemos atribuir effects cujo controlo nativo o não lhe corresponda. Quando é necessário criar um effect que possa ser usado por vários controlos porque as propriedades são comuns, ai podemos criar um effect ao nível da classe pai, ie, da classe Element (por exemplo). 

Conclusão

Em conclusão, os “effects” vem facilitar toda a customização exigida pelos designers e desta forma, o desenvolvimento de aplicações móveis usando Xamarin Forms fica mais rápido, simplificado o desenvolvimento, e para além disso a qualquer momento podemos alterar os “effects” de cada controlo de Xamarin Forms, permitindo assim que em runtime seja aplicado o design que se pretende. Os effects para além de poderem ser atribuídos aos controlos podem ser definidos através de attached properties ou de styles o que é uma mais valia para a qualidade de código a desenvolver.

Algumas referências