Поделиться через


Многоразовый RoundEffect в Xamarin.Forms

Внимание

Больше не нужно использовать RoundEffect для отрисовки элемента управления в виде окружности. Последний рекомендуемый подход заключается в обрезке элемента управления с помощью EllipseGeometry. Дополнительные сведения см. в разделе Обрезка с помощью класса Geometry.

RoundEffect упрощает отрисовку любого элемента управления, производного от VisualElement, в виде круга. Этот эффект можно использовать для создания круглых изображений, кнопок или других элементов управления:

Снимки экрана RoundEffect в iOS и Android

Создание общего RoutingEffect

Для создания кроссплатформенного эффекта нужно создать класс эффектов в общем проекте. Пример приложения создает пустой класс RoundEffect, производный от класса RoutingEffect:

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

Этот класс позволяет общему проекту разрешать ссылки на данный эффект в коде или XAML, но не предоставляет никаких функциональных возможностей. Эффект должен иметь реализации для каждой платформы.

Реализация эффекта для Android

Проект платформы Android определяет класс RoundEffect, производный от PlatformEffect. Этот класс помечен атрибутами assembly, которые позволяют Xamarin.Forms разрешать класс эффектов:

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

Платформа Android использует концепцию OutlineProvider для определения границ элемента управления. Пример проекта включает класс CornerRadiusProvider, производный от класса ViewOutlineProvider:

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

Этот класс использует Width и Height свойства экземпляра для вычисления радиуса Xamarin.FormsElement , который составляет половину самого короткого измерения.

После того как поставщик структуры определен, класс RoundEffect может использовать его для реализации этого эффекта:

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

Метод OnAttached вызывается, когда эффект присоединяется к элементу. Существующий объект OutlineProvider сохраняется, чтобы его можно было восстановить при отсоединении этого эффекта. Новый экземпляр CornerRadiusOutlineProvider используется в качестве OutlineProvider, а для ClipToOutline устанавливается значение true для обрезки выступающих элементов по границе структуры.

Метод OnDetatched вызывается, когда эффект удаляется из элемента и восстанавливает исходное значение OutlineProvider.

Примечание.

В зависимости от типа элемента свойство Control может быть или не быть равно null. Если свойство Control не равно null, скругленные углы можно применить непосредственно к элементу управления. Однако при значении null скругленные углы должны применяться к объекту Container. Поле effectTarget позволяет применять эффект к соответствующему объекту.

Реализация эффекта для iOS

Проект платформы iOS определяет класс RoundEffect, производный от PlatformEffect. Этот класс помечен атрибутами assembly, которые позволяют Xamarin.Forms разрешать класс эффектов:

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

В iOS элементы управления имеют свойство Layer, имеющее свойство CornerRadius. Реализация класса RoundEffect в iOS вычисляет соответствующий радиус угла и обновляет свойство CornerRadius слоя:

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

Метод CalculateRadius вычисляет радиус на основе минимального Xamarin.FormsElementизмерения. Метод OnAttached вызывается, когда эффект присоединяется к элементу управления и обновляет свойство CornerRadius слоя. Он задает для свойства ClipToBounds значение true, поэтому выступающие элементы обрезаются по границам элемента управления. Метод OnDetatched вызывается, когда эффект удаляется из элемента и резервирует эти изменения, восстанавливая первоначальный радиус угла.

Примечание.

В зависимости от типа элемента свойство Control может быть или не быть равно null. Если свойство Control не равно null, скругленные углы можно применить непосредственно к элементу управления. Однако при значении null скругленные углы должны применяться к объекту Container. Поле effectTarget позволяет применять эффект к соответствующему объекту.

Использование эффекта

После реализации этого эффекта на разных платформах он может использоваться элементами управления Xamarin.Forms. Обычное приложение RoundEffect делает объект Image круглым. В следующем коде XAML показано применение эффекта к экземпляру Image:

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

Этот эффект можно также применить в коде:

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

Класс RoundEffect можно применить к любому элементу управления, производному от VisualElement.

Примечание.

Чтобы эффект вычислил правильный радиус, элемент управления, к которому он применяется, должен иметь явно заданный размер. То есть должны быть определены свойства HeightRequest и WidthRequest. Если затронутый элемент управления отображается в StackLayout, его свойство HorizontalOptions не должно использовать одно из значений Expand, например LayoutOptions.CenterAndExpand, или измерения будут неточными.