我有一个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);
}
}
}
这很好用,并为我提供了以下外观
现在,对于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";
}
}
但这给了我
的渲染输出我的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>
问:为什么圆形容器无法正确渲染?
感谢您的宝贵时间。
答案 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。