Passando parâmetros de efeito como propriedades anexadas
Propriedades anexadas podem ser usadas para definir parâmetros de efeito que respondem a alterações de propriedade de runtime. Este artigo demonstra o uso de propriedades anexadas para passar parâmetros para um efeito e a alteração de um parâmetro em runtime.
O processo para criar parâmetros de efeito que respondem a alterações de propriedade de runtime é o seguinte:
- Crie uma classe
static
que contém uma propriedade anexada para cada parâmetro a ser passado para o efeito. - Adicione outra propriedade anexada à classe que será usada para controlar a adição ou remoção do efeito para o controle a que a classe será anexada. Certifique-se de que a propriedade anexada registre um delegado
propertyChanged
que será executado quando o valor da propriedade for alterado. - Crie getters e setters
static
para cada propriedade anexada. - Implemente a lógica no delegado
propertyChanged
para adicionar e remover o efeito. - Implemente uma classe aninhada dentro da classe
static
, com o mesmo nome do efeito, que cria uma subclasse da classeRoutingEffect
. Para o construtor, chame o construtor da classe base passando uma concatenação do nome do grupo de resolução e a ID exclusiva que foi especificada em cada classe de efeito específica da plataforma.
Em seguida, é possível passar parâmetros para o efeito adicionando as propriedades anexadas e valores de propriedade ao controle apropriado. Além disso, os parâmetros podem ser alterados no runtime especificando um novo valor da propriedade anexada.
Observação
Uma propriedade anexada é um tipo especial de propriedade associável, definida em uma classe, mas anexada a outros objetos e reconhecível no XAML como atributos que contêm uma classe e um nome de propriedade separados por um ponto. Para obter mais informações, confira Propriedades anexadas.
O aplicativo de exemplo demonstra um ShadowEffect
que adiciona uma sombra ao texto exibido por um controle Label
. Além disso, a cor da sombra pode ser alterada em runtime. O seguinte diagrama ilustra as responsabilidades de cada projeto no aplicativo de exemplo, bem como as relações entre elas:
Um controle Label
no HomePage
é personalizado pelo LabelShadowEffect
em cada projeto específico da plataforma. Os parâmetros são passados para cada LabelShadowEffect
por meio das propriedades anexadas na classe ShadowEffect
. Cada classe LabelShadowEffect
é derivada da classe PlatformEffect
de cada plataforma. Isso faz com que uma sombra seja adicionada ao texto exibido pelo controle Label
, conforme mostrado nas capturas de tela seguir:
Criando parâmetros de efeito
Uma classe static
deve ser criada para representar os parâmetros em vigor, conforme demonstrado no exemplo de código a seguir:
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")
{
}
}
}
O ShadowEffect
contém cinco propriedades anexadas, com getters e setters static
para cada propriedade anexada. Quatro dessas propriedades representam parâmetros a serem passados para cada LabelShadowEffect
específico da plataforma. A classe ShadowEffect
também define uma propriedade anexada HasShadow
que é usada para controlar a adição ou remoção do efeito para o controle a que a classe ShadowEffect
é anexada. Essa propriedade anexada registra o método OnHasShadowChanged
que será executado quando o valor da propriedade for alterado. Esse método adiciona ou remove o efeito com base no valor da propriedade anexada HasShadow
.
A classe LabelShadowEffect
aninhada, que cria uma subclasse da classe RoutingEffect
, dá suporte à adição e à remoção de efeitos. A classe RoutingEffect
representa um efeito independente de plataforma que encapsula um efeito interno, que é geralmente é específico da plataforma. Isso simplifica o processo de remoção do efeito, porque não há nenhum acesso de tempo de compilação às informações de tipo para um efeito específico da plataforma. O construtor LabelShadowEffect
chama o construtor da classe base, passando um parâmetro composto por uma concatenação do nome do grupo de resolução e pela ID exclusiva que foi especificada em cada classe de efeito específica da plataforma. Isso habilita a adição e a remoção do efeito no método OnHasShadowChanged
, da seguinte maneira:
- Adição de efeito – uma nova instância do
LabelShadowEffect
é adicionada à coleçãoEffects
do controle. Isso substitui o uso do métodoEffect.Resolve
para adicionar o efeito. - Remoção de efeito – a primeira instância da coleção do
Effects
controle é recuperadaLabelShadowEffect
e removida.
Consumindo o efeito
Cada LabelShadowEffect
específico da plataforma pode ser consumido adicionando as propriedades anexadas a um controle Label
, conforme demonstrado no exemplo de código XAML a seguir:
<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>
O Label
equivalente em C# é mostrado no exemplo de código a seguir:
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));
Definir a propriedade anexada ShadowEffect.HasShadow
como true
executa o método ShadowEffect.OnHasShadowChanged
que adiciona ou remove o LabelShadowEffect
ao controle Label
. Nos dois exemplos de código, a propriedade anexada ShadowEffect.Color
fornece valores de cor específicos da plataforma. Para obter mais informações, confira Classe do dispositivo.
Além disso, um Button
permite que a cor da sombra seja alterada em runtime. Quando o usuário clica no Button
, a código a seguir altera a cor da sombra definindo a propriedade anexada ShadowEffect.Color
:
ShadowEffect.SetColor (label, Color.Teal);
Consumindo o efeito com um estilo
Os efeitos que podem ser consumidos adicionando propriedades anexadas a um controle também podem ser consumidos por um estilo. O exemplo de código XAML abaixo mostra um estilo explícito para o efeito de sombra, que pode ser aplicado 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>
O Style
pode ser aplicado a um Label
definindo sua propriedade Style
para a instância de Style
usando a extensão de marcação StaticResource
, conforme demonstrado no exemplo de código a seguir:
<Label Text="Label Shadow Effect" ... Style="{StaticResource ShadowEffectStyle}" />
Para mais informações sobre estilos, confira Estilos.
Criando o efeito em cada plataforma
As seções a seguir abordam a implementação da classe LabelShadowEffect
específica da plataforma.
Projeto do iOS
O exemplo de código a seguir mostra a implementação de LabelShadowEffect
para o projeto do 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));
}
}
O método OnAttached
chama métodos que recuperam os valores de propriedade anexada usando os getters ShadowEffect
e que definem as propriedades de Control.Layer
com os valores da propriedade para criar a sombra. Essa funcionalidade é encapsulada em um bloco try
/catch
caso o controle a que o efeito está anexado não tenha as propriedades de Control.Layer
. Nenhuma implementação é fornecida pelo método OnDetached
porque nenhuma limpeza é necessária.
Respondendo a alterações de propriedade
Se qualquer um dos valores da propriedade anexada ShadowEffect
for alterado em runtime, o efeito precisará responder exibindo as alterações. Uma versão de substituição do método OnElementPropertyChanged
, na classe de efeito específica da plataforma, é o lugar para responder a alterações de propriedade vinculáveis, conforme demonstrado no código de exemplo a seguir:
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 ();
}
}
...
}
O método OnElementPropertyChanged
atualiza o raio, a cor ou o deslocamento da sombra, desde que o valor da propriedade anexada ShadowEffect
tenha sido alterado. Uma verificação da propriedade alterada sempre deve ser feita, pois essa substituição pode ser chamada várias vezes.
Projeto do Android
O exemplo de código a seguir mostra a implementação de LabelShadowEffect
para o projeto do 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);
}
}
O método OnAttached
chama métodos que recuperam os valores de propriedade anexada usando os getters ShadowEffect
e chama um método que chama o método TextView.SetShadowLayer
para criar uma sombra usando os valores da propriedade. Essa funcionalidade é encapsulada em um bloco try
/catch
caso o controle a que o efeito está anexado não tenha as propriedades de Control.Layer
. Nenhuma implementação é fornecida pelo método OnDetached
porque nenhuma limpeza é necessária.
Respondendo a alterações de propriedade
Se qualquer um dos valores da propriedade anexada ShadowEffect
for alterado em runtime, o efeito precisará responder exibindo as alterações. Uma versão de substituição do método OnElementPropertyChanged
, na classe de efeito específica da plataforma, é o lugar para responder a alterações de propriedade vinculáveis, conforme demonstrado no código de exemplo a seguir:
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 ();
}
}
...
}
O método OnElementPropertyChanged
atualiza o raio, a cor ou o deslocamento da sombra, desde que o valor da propriedade anexada ShadowEffect
tenha sido alterado. Uma verificação da propriedade alterada sempre deve ser feita, pois essa substituição pode ser chamada várias vezes.
Projeto da Plataforma Universal do Windows
O exemplo de código a seguir mostra a implementação de LabelShadowEffect
para o projeto da UWP (Plataforma Universal do Windows):
[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);
}
}
}
A Plataforma Universal do Windows não fornece um efeito de sombra, de forma que a implementação de LabelShadowEffect
nas duas plataformas simula o efeito adicionando um segundo deslocamento Label
atrás do Label
principal. O método OnAttached
cria o novo Label
e define algumas propriedades de layout no Label
. Em seguida, ele chama métodos que recuperam os valores da propriedade anexada usando os getters ShadowEffect
e cria a sombra definindo as propriedades TextColor
, TranslationX
e TranslationY
para controlar a cor e a localização do Label
. Em seguida, o shadowLabel
é inserido deslocado atrás do Label
principal. Essa funcionalidade é encapsulada em um bloco try
/catch
caso o controle a que o efeito está anexado não tenha as propriedades de Control.Layer
. Nenhuma implementação é fornecida pelo método OnDetached
porque nenhuma limpeza é necessária.
Respondendo a alterações de propriedade
Se qualquer um dos valores da propriedade anexada ShadowEffect
for alterado em runtime, o efeito precisará responder exibindo as alterações. Uma versão de substituição do método OnElementPropertyChanged
, na classe de efeito específica da plataforma, é o lugar para responder a alterações de propriedade vinculáveis, conforme demonstrado no código de exemplo a seguir:
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 ();
}
}
...
}
O método OnElementPropertyChanged
atualiza a cor ou o deslocamento da sombra, desde que o valor da propriedade anexada ShadowEffect
tenha sido alterado. Uma verificação da propriedade alterada sempre deve ser feita, pois essa substituição pode ser chamada várias vezes.
Resumo
Este artigo demonstrou o uso de propriedades anexadas para passar parâmetros para um efeito e a alteração de um parâmetro em runtime. Propriedades anexadas podem ser usadas para definir parâmetros de efeito que respondem a alterações de propriedade de runtime.