Xamarin.Forms RoundEffect reutilizável

Baixar exemplo Baixar o exemplo

Importante

Não é mais necessário usar um RoundEffect para renderizar um controle como um círculo. A abordagem recomendada mais recente é cortar o controle usando um EllipseGeometry. Para obter mais informações, consulte Clip with a Geometry.

O RoundEffect simplifica a renderização de qualquer controle derivado de VisualElement como um círculo. Esse efeito pode ser usado para criar imagens circulares, botões e outros controles:

Capturas de tela roundEffect no iOS e no Android

Criar um RoutingEffect compartilhado

Uma classe de efeito deve ser criada no projeto compartilhado para criar um efeito multiplataforma. O aplicativo de exemplo cria uma classe vazia RoundEffect derivada da RoutingEffect classe :

public class RoundEffect : RoutingEffect
{
    public RoundEffect() : base($"Xamarin.{nameof(RoundEffect)}")
    {
    }
}

Essa classe permite que o projeto compartilhado resolve as referências ao efeito no código ou XAML, mas não fornece nenhuma funcionalidade. O efeito deve ter implementações para cada plataforma.

Implementar o efeito Android

O projeto de plataforma Android define uma RoundEffect classe derivada de PlatformEffect. Essa classe é marcada com assembly atributos que permitem Xamarin.Forms resolve a classe de efeito:

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(RoundEffectDemo.Droid.RoundEffect), nameof(RoundEffectDemo.Droid.RoundEffect))]
namespace RoundEffectDemo.Droid
{
    public class RoundEffect : PlatformEffect
    {
        // ...
    }
}

A plataforma Android usa o conceito de um OutlineProvider para definir as bordas de um controle. O projeto de exemplo inclui uma CornerRadiusProvider classe derivada da ViewOutlineProvider classe :

class CornerRadiusOutlineProvider : ViewOutlineProvider
{
    Element element;

    public CornerRadiusOutlineProvider(Element formsElement)
    {
        element = formsElement;
    }

    public override void GetOutline(Android.Views.View view, Outline outline)
    {
        float scale = view.Resources.DisplayMetrics.Density;
        double width = (double)element.GetValue(VisualElement.WidthProperty) * scale;
        double height = (double)element.GetValue(VisualElement.HeightProperty) * scale;
        float minDimension = (float)Math.Min(height, width);
        float radius = minDimension / 2f;
        Rect rect = new Rect(0, 0, (int)width, (int)height);
        outline.SetRoundRect(rect, radius);
    }
}

Essa classe usa as Width propriedades e Height da Xamarin.FormsElement instância para calcular um raio que é metade da dimensão mais curta.

Depois que um provedor de estrutura de tópicos é definido, a RoundEffect classe pode consumi-la para implementar o efeito:

public class RoundEffect : PlatformEffect
{
    ViewOutlineProvider originalProvider;
    Android.Views.View effectTarget;

    protected override void OnAttached()
    {
        try
        {
            effectTarget = Control ?? Container;
            originalProvider = effectTarget.OutlineProvider;
            effectTarget.OutlineProvider = new CornerRadiusOutlineProvider(Element);
            effectTarget.ClipToOutline = true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Failed to set corner radius: {ex.Message}");
        }
    }

    protected override void OnDetached()
    {
        if(effectTarget != null)
        {
            effectTarget.OutlineProvider = originalProvider;
            effectTarget.ClipToOutline = false;
        }
    }
}

O OnAttached método é chamado quando o efeito é anexado a um elemento . O objeto existente OutlineProvider é salvo para que possa ser restaurado quando o efeito for desanexado. Uma nova instância do CornerRadiusOutlineProvider é usada como e OutlineProviderClipToOutline é definida como true para recortar elementos excedentes para as bordas da estrutura de tópicos.

O OnDetatched método é chamado quando o efeito é removido de um elemento e restaura o valor original OutlineProvider .

Observação

Dependendo do tipo de elemento, a Control propriedade pode ou não ser nula. Se a Control propriedade não for nula, os cantos arredondados poderão ser aplicados diretamente ao controle. No entanto, se for nulo, os cantos arredondados deverão ser aplicados ao Container objeto . O effectTarget campo permite que o efeito seja aplicado ao objeto apropriado.

Implementar o efeito iOS

O projeto da plataforma iOS define uma RoundEffect classe derivada de PlatformEffect. Essa classe é marcada com assembly atributos que permitem Xamarin.Forms resolve a classe de efeito:

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(RoundEffectDemo.iOS.RoundEffect), nameof(RoundEffectDemo.iOS.RoundEffect))]
namespace RoundEffectDemo.iOS
{
    public class RoundEffect : PlatformEffect
    {
        // ...
    }

No iOS, os controles têm uma Layer propriedade , que tem uma CornerRadius propriedade . A RoundEffect implementação da classe no iOS calcula o raio de canto apropriado e atualiza a propriedade da CornerRadius camada:

public class RoundEffect : PlatformEffect
{
    nfloat originalRadius;
    UIKit.UIView effectTarget;

    protected override void OnAttached()
    {
        try
        {
            effectTarget = Control ?? Container;
            originalRadius = effectTarget.Layer.CornerRadius;
            effectTarget.ClipsToBounds = true;
            effectTarget.Layer.CornerRadius = CalculateRadius();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Failed to set corner radius: {ex.Message}");
        }
    }

    protected override void OnDetached()
    {
        if (effectTarget != null)
        {
            effectTarget.ClipsToBounds = false;
            if (effectTarget.Layer != null)
            {
                effectTarget.Layer.CornerRadius = originalRadius;
            }
        }
    }

    float CalculateRadius()
    {
        double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
        double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
        float minDimension = (float)Math.Min(height, width);
        float radius = minDimension / 2f;

        return radius;
    }
}

O CalculateRadius método calcula um raio com base na dimensão mínima do Xamarin.FormsElement. O OnAttached método é chamado quando o efeito é anexado a um controle e atualiza a propriedade da CornerRadius camada. Ele define a ClipToBounds propriedade como para true que os elementos excedentes sejam recortados às bordas do controle. O OnDetatched método é chamado quando o efeito é removido de um controle e reverte essas alterações, restaurando o raio de canto original.

Observação

Dependendo do tipo de elemento, a Control propriedade pode ou não ser nula. Se a Control propriedade não for nula, os cantos arredondados poderão ser aplicados diretamente ao controle. No entanto, se for nulo, os cantos arredondados deverão ser aplicados ao Container objeto . O effectTarget campo permite que o efeito seja aplicado ao objeto apropriado.

Consumir o efeito

Depois que o efeito é implementado entre plataformas, ele pode ser consumido por Xamarin.Forms controles. Uma aplicação comum do RoundEffect está tornando um Image objeto circular. O XAML a seguir mostra o efeito que está sendo aplicado a uma Image instância:

<Image Source=outdoors"
       HeightRequest="100"
       WidthRequest="100">
    <Image.Effects>
        <local:RoundEffect />
    </Image.Effects>
</Image>

O efeito também pode ser aplicado no código:

var image = new Image
{
    Source = ImageSource.FromFile("outdoors"),
    HeightRequest = 100,
    WidthRequest = 100
};
image.Effects.Add(new RoundEffect());

A RoundEffect classe pode ser aplicada a qualquer controle derivado de VisualElement.

Observação

Para que o efeito calcule o raio correto, o controle ao qual ele é aplicado deve ter dimensionamento explícito. Portanto, as HeightRequest propriedades e WidthRequest devem ser definidas. Se o controle afetado aparecer em um StackLayout, sua HorizontalOptions propriedade não deverá usar um dos valores Expand , como LayoutOptions.CenterAndExpand ou não terá dimensões precisas.