Wiederverwendbarer RoundEffect in Xamarin.Forms

Beispiel herunterladen Das Beispiel herunterladen

Wichtig

Es ist nicht mehr erforderlich, einen RoundEffect zu verwenden, um ein Steuerelement als Kreis zu rendern. Die derzeit empfohlene Vorgehensweise besteht darin, das Steuerelement mithilfe einer EllipseGeometry zu beschneiden. Weitere Informationen finden Sie unter Beschneiden mit einer Geometrie.

RoundEffect vereinfacht das Rendering von Steuerelementen, die als Kreis von VisualElement abgeleitet werden. Dieser Effekt kann verwendet werden, um kreisförmige Bilder, Schaltflächen oder andere Steuerelemente zu erstellen:

RoundEffect-Screenshots unter iOS und Android

Erstellen einer freigegebenen RoutingEffect-Klasse

Im freigegebenen Projekt muss eine Effektklasse angelegt werden, um einen plattformübergreifenden Effekt zu erzeugen. Die Beispielanwendung erstellt eine leere RoundEffect-Klasse, die sich von der Klasse RoutingEffect ableitet:

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

Diese Klasse ermöglicht es dem freigegebenen Projekt, die Referenzen auf den Effekt im Code oder XAML aufzulösen, bietet aber keine Funktionalität. Der Effekt muss für jede Plattform Implementierungen enthalten.

Implementieren des Android-Effekts

Das Android-Plattformprojekt definiert eine Klasse RoundEffect, die sich von PlatformEffect ableitet. Diese Klasse ist mit assembly-Attributen versehen, die es Xamarin.Forms ermöglichen, die Effektklasse aufzulösen:

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

Die Android-Plattform verwendet das Konzept eines OutlineProvider, um die Edges eines Steuerelements zu definieren. Das Beispielprojekt beinhaltet eine CornerRadiusProvider-Klasse, die sich von der ViewOutlineProvider-Klasse ableitet:

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);
    }
}

Diese Klasse verwendet die Width Eigenschaften und Height der Xamarin.FormsElement instance, um einen Radius zu berechnen, der der Hälfte der kürzesten Dimension entspricht.

Sobald eine OutlineProvider-Eigenschaft definiert ist, kann die Klasse RoundEffect ihn nutzen, um den Effekt zu implementieren:

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;
        }
    }
}

Die OnAttached-Methode wird aufgerufen, wenn der Effekt an ein Element angehängt ist. Das vorhandene OutlineProvider-Objekt wird gespeichert, sodass es wiederhergestellt werden kann, wenn der Effekt entfernt wird. Eine neue Instanz von CornerRadiusOutlineProvider wird verwendet, wenn OutlineProvider und ClipToOutline auf „true“ festgelegt sind, um überfließende Elemente an den Umrandungen auszuschneiden.

Die OnDetatched-Methode wird aufgerufen, wenn der Effekt aus einem Element entfernt wird und den ursprünglichen OutlineProvider-Wert wiederherstellt.

Hinweis

Abhängig vom Elementtyp kann die Eigenschaft Control NULL oder nicht NULL sein. Wenn die Eigenschaft Control nicht NULL ist, können die abgerundeten Ecken direkt auf das Steuerelement angewendet werden. Wenn sie jedoch NULL ist, müssen die abgerundeten Ecken auf das Objekt Container angewendet werden. Das Feld effectTarget ermöglicht es, den Effekt auf das entsprechende Objekt anzuwenden.

Implementieren des iOS-Effekts

Das iOS-Plattformprojekt definiert eine Klasse RoundEffect, die sich von PlatformEffect ableitet. Diese Klasse ist mit assembly-Attributen versehen, die es Xamarin.Forms ermöglichen, die Effektklasse aufzulösen:

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

Unter iOS haben Steuerelemente eine Layer-Eigenschaft, die eine CornerRadius-Eigenschaft enthält. Die Klassenimplementierung von RoundEffect unter iOS berechnet den entsprechenden Eckenradius und aktualisiert die Eigenschaft CornerRadius der Ebene:

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;
    }
}

Die CalculateRadius -Methode berechnet einen Radius basierend auf der Minimaldimension von Xamarin.FormsElement. Die OnAttached-Methode wird aufgerufen, wenn der Effekt an ein Steuerelement angehängt ist, und aktualisiert die CornerRadius-Eigenschaft der Ebene. Sie legt die ClipToBounds-Eigenschaft auf true fest, sodass überfließende Elemente an den Grenzen des Steuerelements abgeschnitten werden. Die OnDetatched-Methode wird aufgerufen, wenn der Effekt aus einem Steuerelement entfernt wird und diese Änderungen rückgängig macht, und den ursprünglichen Eckenradius wiederherstellt.

Hinweis

Abhängig vom Elementtyp kann die Eigenschaft Control NULL oder nicht NULL sein. Wenn die Eigenschaft Control nicht NULL ist, können die abgerundeten Ecken direkt auf das Steuerelement angewendet werden. Wenn sie jedoch NULL ist, müssen die abgerundeten Ecken auf das Objekt Container angewendet werden. Das Feld effectTarget ermöglicht es, den Effekt auf das entsprechende Objekt anzuwenden.

Nutzung des Effekts

Sobald der Effekt plattformübergreifend implementiert ist, kann er von Xamarin.Forms-Steuerelementen genutzt werden. Eine gängige Anwendung von RoundEffect besteht darin, ein Image-Objekt kreisförmig zu machen. Die folgende XAML zeigt den Effekt, der auf eine Image-Instanz angewendet wird:

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

Der Effekt kann auch in Code angewendet werden:

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

Die Klasse RoundEffect kann auf jedes Steuerelement angewendet werden, das sich von VisualElement ableitet.

Hinweis

Damit der Effekt den richtigen Radius berechnen kann, muss das Steuerelement, auf das er angewendet wird, eine explizite Größenanpassung aufweisen. Daher sollten die Eigenschaften HeightRequest und WidthRequest definiert werden. Wenn das betroffene Steuerelement in einem StackLayout erscheint, sollte seine HorizontalOptions-Eigenschaft keinen der Expand-Werte (Erweitern) wie LayoutOptions.CenterAndExpand verwenden, sonst hat es keine genauen Dimensionen.