自定义渲染器未按预期在iOS上运行

时间:2019-04-24 20:01:51

标签: xamarin xamarin.forms xamarin.ios renderer

我有一个Xamarin应用程序。我想以圆圈而不是正方形显示Image的页面之一。为此,我根据一些在线指导为每个平台创建了一个自定义呈现。这些类在下面;我拥有(便携式)项目中的第一个

public class CircleImage : Image
{
    public static readonly BindableProperty BorderThicknessProperty =
      BindableProperty.Create(propertyName: nameof(BorderThickness),
          returnType: typeof(float),
          declaringType: typeof(CircleImage),
          defaultValue: 0F);

    public float BorderThickness
    {
        get { return (float)GetValue(BorderThicknessProperty); }
        set { SetValue(BorderThicknessProperty, value); }
    }

    public static readonly BindableProperty BorderColorProperty =
        BindableProperty.Create(propertyName: nameof(BorderColor),
          returnType: typeof(Color),
          declaringType: typeof(CircleImage),
          defaultValue: Color.White);

    public Color BorderColor
    {
        get { return (Color)GetValue(BorderColorProperty); }
        set { SetValue(BorderColorProperty, value); }
    }

    public static readonly BindableProperty FillColorProperty =
        BindableProperty.Create(propertyName: nameof(FillColor),
          returnType: typeof(Color),
          declaringType: typeof(CircleImage),
          defaultValue: Color.Transparent);

    public Color FillColor
    {
        get { return (Color)GetValue(FillColorProperty); }
        set { SetValue(FillColorProperty, value); }
    }
}

然后对于Android,我有渲染器

[assembly: ExportRenderer(typeof(CircleImage), typeof(CircleImageRenderer))]
namespace GL.Droid.Renderer
{
    [Preserve(AllMembers = true)]
    public class CircleImageRenderer : ImageRenderer
    {
#pragma warning disable CS0618 // Type or member is obsolete.
        public CircleImageRenderer() : base()
#pragma warning restore CS0618 // Type or member is obsolete.
        {
        }

        public CircleImageRenderer(Context context) : base(context) { }
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously.

        public async static void Init()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            var temp = DateTime.Now;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement == null)
            {
                // Only enable hardware accelleration on lollipop.
                if ((int)Build.VERSION.SdkInt < 21)
                {
                    SetLayerType(LayerType.Software, null);
                }
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == CircleImage.BorderColorProperty.PropertyName ||
                e.PropertyName == CircleImage.BorderThicknessProperty.PropertyName ||
                e.PropertyName == CircleImage.FillColorProperty.PropertyName)
            {
                Invalidate();
            }
        }

        protected override bool DrawChild(Canvas canvas, Android.Views.View child, long drawingTime)
        {
            try
            {
                var radius = (float)Math.Min(Width, Height) / 2f;
                var borderThickness = ((CircleImage)Element).BorderThickness;
                var strokeWidth = 0f;

                if (borderThickness > 0)
                {
                    var logicalDensity = Android.App.Application.Context.Resources.DisplayMetrics.Density;
                    strokeWidth = (float)Math.Ceiling(borderThickness * logicalDensity + .5f);
                }
                radius -= strokeWidth / 2f;

                var path = new Path();
                path.AddCircle(Width / 2.0f, Height / 2.0f, radius, Path.Direction.Ccw);
                canvas.Save();
                canvas.ClipPath(path);

                var paint = new Paint
                {
                    AntiAlias = true
                };
                paint.SetStyle(Paint.Style.Fill);
                paint.Color = ((CircleImage)Element).FillColor.ToAndroid();

                canvas.DrawPath(path, paint);
                paint.Dispose();

                var result = base.DrawChild(canvas, child, drawingTime);
                path.Dispose();
                canvas.Restore();

                path = new Path();
                path.AddCircle(Width / 2f, Height / 2f, radius, Path.Direction.Ccw);
                if (strokeWidth > 0.0f)
                {
                    paint = new Paint
                    {
                        AntiAlias = true,
                        StrokeWidth = strokeWidth
                    };
                    paint.SetStyle(Paint.Style.Stroke);
                    paint.Color = ((CircleImage)Element).BorderColor.ToAndroid();
                    canvas.DrawPath(path, paint);
                    paint.Dispose();
                }
                path.Dispose();
                return result;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Unable to create circle image: " + ex);
            }
            return base.DrawChild(canvas, child, drawingTime);
        }
    }
}

这很好用,并为我提供了以下外观

Droid Works Fine

现在,对于iOS以及问题所在,我们有以下内容(据我所知)与

下的Android实现匹配
[assembly: ExportRenderer(typeof(CircleImage), typeof(CircleImageRenderer))]
namespace GL.iOS.Renderer
{
    [Preserve(AllMembers = true)]
    public class CircleImageRenderer : ImageRenderer
    {
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async static void Init()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
#pragma warning restore CS0108 // Member hides inherited member; missing new keyword
        {
            var temp = DateTime.Now;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
        {
            base.OnElementChanged(e);
            if (Element == null)
                return;

            CreateCircle();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == VisualElement.HeightProperty.PropertyName ||
                e.PropertyName == VisualElement.WidthProperty.PropertyName ||
                e.PropertyName == CircleImage.BorderColorProperty.PropertyName ||
                e.PropertyName == CircleImage.BorderThicknessProperty.PropertyName ||
                e.PropertyName == CircleImage.FillColorProperty.PropertyName)
            {
                CreateCircle();
            }
        }

        private void CreateCircle()
        {
            try
            {
                var min = Math.Min(Element.Width, Element.Height);
                Control.Layer.CornerRadius = (nfloat)(min / 2.0);

                Control.Layer.MasksToBounds = false;
                Control.BackgroundColor = ((CircleImage)Element).FillColor.ToUIColor();

                Control.ClipsToBounds = true;
                var borderThickness = ((CircleImage)Element).BorderThickness;

                // Remove previously added layers.
                var tempLayer = Control.Layer.Sublayers?
                    .Where(p => p.Name == borderName)
                    .FirstOrDefault();
                tempLayer?.RemoveFromSuperLayer();

                var externalBorder = new CALayer();
                externalBorder.Name = borderName;

                externalBorder.CornerRadius = Control.Layer.CornerRadius;
                externalBorder.Frame = new CGRect(-.5, -.5, min + 1, min + 1);

                externalBorder.BorderColor = ((CircleImage)Element).BorderColor.ToCGColor();
                externalBorder.BorderWidth = ((CircleImage)Element).BorderThickness;

                Control.Layer.AddSublayer(externalBorder);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to create circle image: " + ex);
            }
        }
        const string borderName = "borderLayerName";
    }
}

但这给了我

的渲染输出

Ios

我的XAML是

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage x:Class="GL.ProfilePage" 
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             Title="Generation London"
             xmlns:local="clr-namespace:GL;assembly=GL" 
             xmlns:Controls="clr-namespace:GL.Controls"
             xmlns:Converters="clr-namespace:GL.Converters"
             BackgroundColor="White">

    <ContentPage.Resources>
        <ResourceDictionary>
            <Converters:ResizingImageConverter x:Key="ResizingImageConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.Content>
        <ScrollView>
            <Grid ColumnSpacing="0" RowSpacing="0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <Image Aspect="AspectFill" 
                       Source="login_background.jpg" />
                <Image Aspect="Fill" 
                       Margin="0,-1,0,-1" 
                       Source="curved_mask.png" 
                       VerticalOptions="End" />

                <Controls:CircleImage BorderThickness="2"
                                      BorderColor="{x:Static local:Settings.LightPurple}" 
                                      WidthRequest="100" 
                                      HeightRequest="100"
                                      TranslationY="50" 
                                      HorizontalOptions="FillAndExpand"
                                      VerticalOptions="End"
                                      Source="{Binding ProfilePicture, Converter={StaticResource ResizingImageConverter}}">
                    <!--<Image.Source>
                        <UriImageSource Uri="{Binding ProfilePicture}" CacheValidity="90"/>
                    </Image.Source>-->
                </Controls:CircleImage>

                <StackLayout Grid.Row="1" Padding="0,50,0,00" HorizontalOptions="Center">
                    <Label x:Name="fullName" Style="{StaticResource MainLabel}"/>
                    <Label Margin="0,-5" Style="{StaticResource SubLabel}" Text="{Binding Occupation}" />
                </StackLayout>

                <Grid Grid.Row="2" Margin="0,30" ColumnSpacing="0" RowSpacing="0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <StackLayout>
                        <Label Style="{StaticResource ValueLabel}" Text="{Binding DateOfBirth, StringFormat='{0:dd/MM/yyyy}'}"/>
                        <Label Style="{StaticResource CaptionLabel}" Text="DOB"/>
                    </StackLayout>

                    <StackLayout Grid.Column="1">
                        <Label x:Name="workTubeStation" Style="{StaticResource ValueLabel}"/>
                        <Label Style="{StaticResource CaptionLabel}" Text="Nearest Tube"/>
                    </StackLayout>

                    <StackLayout Grid.Column="2">
                        <Label x:Name="gender" Style="{StaticResource ValueLabel}"/>
                        <Label Style="{StaticResource CaptionLabel}" Text="Gender"/>
                    </StackLayout>
                </Grid>

                <Grid Grid.Row="3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Label Grid.Column="1" 
                           Margin="0,-5" 
                           Text="Interests"
                           Style="{StaticResource MainLabel}"/>
                </Grid>

                <ContentView Grid.Row="4" Padding="5">
                    <ListView x:Name="userInterests" 
                              RowHeight="35" 
                              ItemsSource="{Binding Interests}" 
                              ItemTapped="NoOpInterestSelected"
                              HorizontalOptions="Center"
                              SeparatorVisibility="None">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <ViewCell.View>
                                        <Label Text="{Binding .}" 
                                               Style="{StaticResource ValueLabel}" 
                                               HorizontalTextAlignment="Center"
                                               YAlign="Center" />
                                    </ViewCell.View>
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </ContentView>

                <Button Grid.Row="5" 
                        Margin="20" 
                        Style="{StaticResource EditButton}" 
                        Clicked="OnEditProfile"
                        Text="Edit"/>

            </Grid>
        </ScrollView>
    </ContentPage.Content>
</ContentPage>
问:为什么圆形容器无法正确渲染?

感谢您的宝贵时间。

2 个答案:

答案 0 :(得分:1)

您尚未显示XAML,但根据渲染器和输出,似乎Image不仅覆盖了照片部分,还覆盖了整个屏幕宽度,这使您的代码可以正常工作(拐角半径并绘制椭圆)以显示在意外的部分上,并最终得到显示的结果。渲染器代码期望Image控件没有透明部分(例如,它使用AspectFill

答案 1 :(得分:0)

如果要为控件设置圆角,请参考以下代码

...
Control.Layer.MasksToBounds = true;
Control.Layer.CornerRadius = (nfloat)(min / 2.0);
Control.Layer.BorderColor = ((CircleImage)Element).BorderColor.ToCGColor();
Control.Layer.BorderWidth = ((CircleImage)Element).BorderThickness;;
...

您不需要在层上添加新的子层。如果要这样做,请参考此similar issue