iOS 自定义渲染不应用角半径
iOS custom render not apply corner radius
我根据一些谷歌搜索创建了一个自定义渲染,这将允许 Frame
完全控制其所有角半径(左、上、右、下)。但是,即使它设置为 60, 60, 0, 0
,我也不认为它符合这些标准。在 Android 上,它按预期工作。
iOS 屏幕:
Android:
在调试时我可以清楚地看到自定义渲染器正确地获取了数据,即角半径被相应地传递下来。这是自定义渲染器代码:
public override void LayoutSubviews()
{
base.LayoutSubviews();
UpdateCornerRadius();
UpdateShadow();
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
UpdateShadow();
UpdateCornerRadius();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(MultiCornerFrame.CornerRadius) || e.PropertyName == nameof(MultiCornerFrame))
{
UpdateCornerRadius();
}
if(e.PropertyName == nameof(MultiCornerFrame.Elevation))
{
UpdateShadow();
}
}
private void UpdateShadow()
{
var materialFrame = (MultiCornerFrame) Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = materialFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
// A very basic way of retrieving same one value for all of the corners
private double RetrieveCommonCornerRadius(CornerRadius cornerRadius)
{
var commonCornerRadius = cornerRadius.TopLeft;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.TopRight;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.BottomLeft;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.BottomRight;
}
}
}
return commonCornerRadius;
}
private UIRectCorner RetrieveRoundedCorners(CornerRadius cornerRadius)
{
var roundedCorners = default(UIRectCorner);
if (cornerRadius.TopLeft > 0)
{
roundedCorners |= UIRectCorner.TopLeft;
}
if (cornerRadius.TopRight > 0)
{
roundedCorners |= UIRectCorner.TopRight;
}
if (cornerRadius.BottomLeft > 0)
{
roundedCorners |= UIRectCorner.BottomLeft;
}
if (cornerRadius.BottomRight > 0)
{
roundedCorners |= UIRectCorner.BottomRight;
}
return roundedCorners;
}
private void UpdateCornerRadius()
{
var cornerRadius = (Element as MultiCornerFrame)?.CornerRadius;
if (!cornerRadius.HasValue)
{
return;
}
var roundedCornerRadius = RetrieveCommonCornerRadius(cornerRadius.Value);
if (roundedCornerRadius <= 0)
{
return;
}
var roundedCorners = RetrieveRoundedCorners(cornerRadius.Value);
var path = UIBezierPath.FromRoundedRect(Bounds, roundedCorners, new CGSize(roundedCornerRadius, roundedCornerRadius));
var mask = new CAShapeLayer { Path = path.CGPath};
NativeView.Layer.Mask = mask;
}
调用它的XAML如下所示:
<customcontrols:MultiCornerFrame Elevation="48" CornerRadius="60, 60, 0, 0" Grid.Row="1" BackgroundColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
如果我需要 post 更多代码,请告诉我
检查后,未在 OnElementChanged
方法中更新 CornerRadius
。我认为此更改将在创建 Frame Renderer 之前调用,然后这将无效。您可以尝试在 LayoutSublayersOfLayer
方法中执行此操作。
我通过创建一个空的 CustomFrame 进行了测试:
public class CustomFrame : Frame{}
在Xaml中:
<StackLayout Padding="20">
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<appframerenderer:CustomFrame BackgroundColor="Accent"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</StackLayout>
最后,CustomFrameRenderer编码如下:
[assembly: ExportRenderer(typeof(CustomFrame), typeof(CustomFrameRenderer))]
namespace AppFrameRenderer.iOS
{
public class CustomFrameRenderer : FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
}
public override void LayoutSublayersOfLayer(CALayer layer)
{
base.LayoutSublayersOfLayer(layer);
var path = UIBezierPath.FromRoundedRect(Bounds, UIRectCorner.TopLeft | UIRectCorner.TopRight, new CGSize(50, 50));
var maskLayer = new CAShapeLayer { Frame = Bounds, Path = path.CGPath };
layer.Mask = maskLayer;
}
}
}
效果符合预期:
所以,这里可以把UpdateShadow(); UpdateCornerRadius();
移到LayoutSublayersOfLayer
的方法试试。
所以经过一些调整后,我认为这与我们设置遮罩的时间有关 and/or 我们究竟在遮罩什么。我决定创建一个名为 UpdateCorners 的方法,如下所示:
private void UpdateCorners()
{
var path = UIBezierPath.FromRoundedRect(this.Bounds, UIRectCorner.TopLeft | UIRectCorner.TopRight, new CGSize(60, 60));
//var mask = new CAShapeLayer();
//mask.Path = path.CGPath;
//this.Layer.Mask = mask;
//right side
this.ClipsToBounds = true;
this.Layer.CornerRadius = 60;
this.Layer.MaskedCorners = CACornerMask.MinXMinYCorner | CACornerMask.MaxXMinYCorner;
var materialFrame = (MultiCornerFrame) Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = materialFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
注意这个方法是如何更新阴影的,因为某些原因当它在它自己的外部方法中时它没有这样做。现在我以前调用 UpdateCornerRadius 的地方我只是用新方法替换它。
我根据一些谷歌搜索创建了一个自定义渲染,这将允许 Frame
完全控制其所有角半径(左、上、右、下)。但是,即使它设置为 60, 60, 0, 0
,我也不认为它符合这些标准。在 Android 上,它按预期工作。
iOS 屏幕:
Android:
在调试时我可以清楚地看到自定义渲染器正确地获取了数据,即角半径被相应地传递下来。这是自定义渲染器代码:
public override void LayoutSubviews()
{
base.LayoutSubviews();
UpdateCornerRadius();
UpdateShadow();
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
UpdateShadow();
UpdateCornerRadius();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(MultiCornerFrame.CornerRadius) || e.PropertyName == nameof(MultiCornerFrame))
{
UpdateCornerRadius();
}
if(e.PropertyName == nameof(MultiCornerFrame.Elevation))
{
UpdateShadow();
}
}
private void UpdateShadow()
{
var materialFrame = (MultiCornerFrame) Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = materialFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
// A very basic way of retrieving same one value for all of the corners
private double RetrieveCommonCornerRadius(CornerRadius cornerRadius)
{
var commonCornerRadius = cornerRadius.TopLeft;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.TopRight;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.BottomLeft;
if (commonCornerRadius <= 0)
{
commonCornerRadius = cornerRadius.BottomRight;
}
}
}
return commonCornerRadius;
}
private UIRectCorner RetrieveRoundedCorners(CornerRadius cornerRadius)
{
var roundedCorners = default(UIRectCorner);
if (cornerRadius.TopLeft > 0)
{
roundedCorners |= UIRectCorner.TopLeft;
}
if (cornerRadius.TopRight > 0)
{
roundedCorners |= UIRectCorner.TopRight;
}
if (cornerRadius.BottomLeft > 0)
{
roundedCorners |= UIRectCorner.BottomLeft;
}
if (cornerRadius.BottomRight > 0)
{
roundedCorners |= UIRectCorner.BottomRight;
}
return roundedCorners;
}
private void UpdateCornerRadius()
{
var cornerRadius = (Element as MultiCornerFrame)?.CornerRadius;
if (!cornerRadius.HasValue)
{
return;
}
var roundedCornerRadius = RetrieveCommonCornerRadius(cornerRadius.Value);
if (roundedCornerRadius <= 0)
{
return;
}
var roundedCorners = RetrieveRoundedCorners(cornerRadius.Value);
var path = UIBezierPath.FromRoundedRect(Bounds, roundedCorners, new CGSize(roundedCornerRadius, roundedCornerRadius));
var mask = new CAShapeLayer { Path = path.CGPath};
NativeView.Layer.Mask = mask;
}
调用它的XAML如下所示:
<customcontrols:MultiCornerFrame Elevation="48" CornerRadius="60, 60, 0, 0" Grid.Row="1" BackgroundColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
如果我需要 post 更多代码,请告诉我
检查后,未在 OnElementChanged
方法中更新 CornerRadius
。我认为此更改将在创建 Frame Renderer 之前调用,然后这将无效。您可以尝试在 LayoutSublayersOfLayer
方法中执行此操作。
我通过创建一个空的 CustomFrame 进行了测试:
public class CustomFrame : Frame{}
在Xaml中:
<StackLayout Padding="20">
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<appframerenderer:CustomFrame BackgroundColor="Accent"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</StackLayout>
最后,CustomFrameRenderer编码如下:
[assembly: ExportRenderer(typeof(CustomFrame), typeof(CustomFrameRenderer))]
namespace AppFrameRenderer.iOS
{
public class CustomFrameRenderer : FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
}
public override void LayoutSublayersOfLayer(CALayer layer)
{
base.LayoutSublayersOfLayer(layer);
var path = UIBezierPath.FromRoundedRect(Bounds, UIRectCorner.TopLeft | UIRectCorner.TopRight, new CGSize(50, 50));
var maskLayer = new CAShapeLayer { Frame = Bounds, Path = path.CGPath };
layer.Mask = maskLayer;
}
}
}
效果符合预期:
所以,这里可以把UpdateShadow(); UpdateCornerRadius();
移到LayoutSublayersOfLayer
的方法试试。
所以经过一些调整后,我认为这与我们设置遮罩的时间有关 and/or 我们究竟在遮罩什么。我决定创建一个名为 UpdateCorners 的方法,如下所示:
private void UpdateCorners()
{
var path = UIBezierPath.FromRoundedRect(this.Bounds, UIRectCorner.TopLeft | UIRectCorner.TopRight, new CGSize(60, 60));
//var mask = new CAShapeLayer();
//mask.Path = path.CGPath;
//this.Layer.Mask = mask;
//right side
this.ClipsToBounds = true;
this.Layer.CornerRadius = 60;
this.Layer.MaskedCorners = CACornerMask.MinXMinYCorner | CACornerMask.MaxXMinYCorner;
var materialFrame = (MultiCornerFrame) Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = materialFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
注意这个方法是如何更新阴影的,因为某些原因当它在它自己的外部方法中时它没有这样做。现在我以前调用 UpdateCornerRadius 的地方我只是用新方法替换它。