采用 C# 的本机视图
可以从使用 C# 创建的页面直接引用 Xamarin.Forms iOS、Android 和 UWP 中的本机视图。 本文演示如何将本机视图添加到 Xamarin.Forms 使用 C# 创建的布局,以及如何替代自定义视图的布局以更正其度量 API 使用情况。
概述
允许Content
设置或具有Children
集合的任何Xamarin.Forms控件都可以添加特定于平台的视图。 例如,可以将 iOS UILabel
直接添加到 ContentView.Content
属性或 集合中 StackLayout.Children
。 但请注意,此功能需要在共享项目解决方案中使用#if
Xamarin.Forms定义,并且不能从 Xamarin.Forms .NET Standard 库解决方案中使用。
以下屏幕截图演示了已添加到 的特定于平台的 Xamarin.FormsStackLayout
视图:
通过每个平台上的两种 Xamarin.Forms 扩展方法,可以向布局添加特定于平台的视图:
Add
– 将特定于平台的视图添加到Children
布局的集合中。ToView
– 采用特定于平台的视图,并将其包装为 Xamarin.FormsView
可设置为Content
控件的 属性的 。
在 Xamarin.Forms 共享项目中使用这些方法需要导入适当的特定于 Xamarin.Forms 平台的命名空间:
- iOS – Xamarin.Forms.Platform.iOS
- Android – Xamarin.Forms.Platform.Android
- 通用 Windows 平台 (UWP) – Xamarin.Forms.Platform.UWP
在每个平台上添加Platform-Specific视图
以下部分演示如何将特定于平台的视图添加到每个平台上的 Xamarin.Forms 布局。
iOS
下面的代码示例演示如何将 添加到 UILabel
StackLayout
和 ContentView
:
var uiLabel = new UILabel {
MinimumFontSize = 14f,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();
该示例假定 stackLayout
和 contentView
实例之前已在 XAML 或 C# 中创建。
Android
下面的代码示例演示如何将 添加到 TextView
StackLayout
和 ContentView
:
var textView = new TextView (MainActivity.Instance) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();
该示例假定 stackLayout
和 contentView
实例之前已在 XAML 或 C# 中创建。
通用 Windows 平台
下面的代码示例演示如何将 添加到 TextBlock
StackLayout
和 ContentView
:
var textBlock = new TextBlock
{
Text = originalText,
FontSize = 14,
FontFamily = new FontFamily("HelveticaNeue"),
TextWrapping = TextWrapping.Wrap
};
stackLayout.Children.Add(textBlock);
contentView.Content = textBlock.ToView();
该示例假定 stackLayout
和 contentView
实例之前已在 XAML 或 C# 中创建。
替代自定义视图的平台度量
每个平台上的自定义视图通常只针对设计它们的布局方案正确实现度量。 例如,自定义视图可能设计为仅占用设备可用宽度的一半。 但是,与其他用户共享后,可能需要自定义视图来占用设备的完整可用宽度。 因此,在布局中 Xamarin.Forms 重复使用时,可能需要重写自定义视图度量实现。 因此, Add
和 ToView
扩展方法提供了允许指定度量委托的替代,这些替代可以在将自定义视图布局添加到 Xamarin.Forms 布局时替代该布局。
以下部分演示如何重写自定义视图的布局,以更正其度量 API 使用情况。
iOS
下面的代码示例演示 CustomControl
继承自 UILabel
的 类:
public class CustomControl : UILabel
{
public override string Text {
get { return base.Text; }
set { base.Text = value.ToUpper (); }
}
public override CGSize SizeThatFits (CGSize size)
{
return new CGSize (size.Width, 150);
}
}
此视图的实例将添加到 StackLayout
,如以下代码示例所示:
var customControl = new CustomControl {
MinimumFontSize = 14,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = "This control has incorrect sizing - there's empty space above and below it."
};
stackLayout.Children.Add (customControl);
但是,由于 CustomControl.SizeThatFits
替代始终返回高度 150,因此视图上方和下方将显示空白区域,如以下屏幕截图所示:
此问题的解决方案是提供实现 GetDesiredSizeDelegate
,如以下代码示例所示:
SizeRequest? FixSize (NativeViewWrapperRenderer renderer, double width, double height)
{
var uiView = renderer.Control;
if (uiView == null) {
return null;
}
var constraint = new CGSize (width, height);
// Let the CustomControl determine its size (which will be wrong)
var badRect = uiView.SizeThatFits (constraint);
// Use the width and substitute the height
return new SizeRequest (new Size (badRect.Width, 70));
}
此方法使用 方法提供的 CustomControl.SizeThatFits
宽度,但将高度 150 替换为高度 70。 将 CustomControl
实例添加到 StackLayout
时, FixSize
可以将 方法指定为 GetDesiredSizeDelegate
,以修复 类提供的 CustomControl
错误度量:
stackLayout.Children.Add (customControl, FixSize);
这会导致自定义视图正确显示,其上方和下方没有空格,如以下屏幕截图所示:
Android
下面的代码示例演示 CustomControl
继承自 TextView
的 类:
public class CustomControl : TextView
{
public CustomControl (Context context) : base (context)
{
}
protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.GetSize (widthMeasureSpec);
// Force the width to half of what's been requested.
// This is deliberately wrong to demonstrate providing an override to fix it with.
int widthSpec = MeasureSpec.MakeMeasureSpec (width / 2, MeasureSpec.GetMode (widthMeasureSpec));
base.OnMeasure (widthSpec, heightMeasureSpec);
}
}
此视图的实例将添加到 StackLayout
,如以下代码示例所示:
var customControl = new CustomControl (MainActivity.Instance) {
Text = "This control has incorrect sizing - it doesn't occupy the available width of the device.",
TextSize = 14
};
stackLayout.Children.Add (customControl);
但是,由于 CustomControl.OnMeasure
替代始终返回所请求宽度的一半,因此视图将仅占用设备可用宽度的一半,如以下屏幕截图所示:
此问题的解决方案是提供实现 GetDesiredSizeDelegate
,如以下代码示例所示:
SizeRequest? FixSize (NativeViewWrapperRenderer renderer, int widthConstraint, int heightConstraint)
{
var nativeView = renderer.Control;
if ((widthConstraint == 0 && heightConstraint == 0) || nativeView == null) {
return null;
}
int width = Android.Views.View.MeasureSpec.GetSize (widthConstraint);
int widthSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec (
width * 2, Android.Views.View.MeasureSpec.GetMode (widthConstraint));
nativeView.Measure (widthSpec, heightConstraint);
return new SizeRequest (new Size (nativeView.MeasuredWidth, nativeView.MeasuredHeight));
}
此方法使用 方法提供的 CustomControl.OnMeasure
宽度,但将其乘以 2。 将 CustomControl
实例添加到 StackLayout
时, FixSize
可以将 方法指定为 GetDesiredSizeDelegate
,以修复 类提供的 CustomControl
错误度量:
stackLayout.Children.Add (customControl, FixSize);
这会导致自定义视图正确显示,占用设备的宽度,如以下屏幕截图所示:
通用 Windows 平台
下面的代码示例演示 CustomControl
继承自 Panel
的 类:
public class CustomControl : Panel
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text", typeof(string), typeof(CustomControl), new PropertyMetadata(default(string), OnTextPropertyChanged));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value.ToUpper()); }
}
readonly TextBlock textBlock;
public CustomControl()
{
textBlock = new TextBlock
{
MinHeight = 0,
MaxHeight = double.PositiveInfinity,
MinWidth = 0,
MaxWidth = double.PositiveInfinity,
FontSize = 14,
TextWrapping = TextWrapping.Wrap,
VerticalAlignment = VerticalAlignment.Center
};
Children.Add(textBlock);
}
static void OnTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((CustomControl)dependencyObject).textBlock.Text = (string)args.NewValue;
}
protected override Size ArrangeOverride(Size finalSize)
{
// This is deliberately wrong to demonstrate providing an override to fix it with.
textBlock.Arrange(new Rect(0, 0, finalSize.Width/2, finalSize.Height));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
textBlock.Measure(availableSize);
return new Size(textBlock.DesiredSize.Width, textBlock.DesiredSize.Height);
}
}
此视图的实例将添加到 StackLayout
,如以下代码示例所示:
var brokenControl = new CustomControl {
Text = "This control has incorrect sizing - it doesn't occupy the available width of the device."
};
stackLayout.Children.Add(brokenControl);
但是,由于 CustomControl.ArrangeOverride
替代始终返回所请求宽度的一半,因此视图将被剪切到设备的可用宽度的一半,如以下屏幕截图所示:
此问题的解决方案是在将视图添加到 StackLayout
时提供ArrangeOverrideDelegate
实现,如以下代码示例所示:
stackLayout.Children.Add(fixedControl, arrangeOverrideDelegate: (renderer, finalSize) =>
{
if (finalSize.Width <= 0 || double.IsInfinity(finalSize.Width))
{
return null;
}
var frameworkElement = renderer.Control;
frameworkElement.Arrange(new Rect(0, 0, finalSize.Width * 2, finalSize.Height));
return finalSize;
});
此方法使用 方法提供的 CustomControl.ArrangeOverride
宽度,但将其乘以 2。 这会导致自定义视图正确显示,占用设备的宽度,如以下屏幕截图所示:
总结
本文介绍了如何将本机视图添加到 Xamarin.Forms 使用 C# 创建的布局,以及如何替代自定义视图的布局以更正其度量 API 使用情况。