本地嵌入
将特定于平台的控件添加到Xamarin.Forms布局
平台特定的控件可以直接添加到Xamarin.Forms布局中。本文演示如何向Xamarin.Forms布局添加特定于平台的控件,以及如何覆盖自定义控件的布局,以更正其测量API使用情况。
概观
允许Content
设置或具有Children
集合的任何Xamarin.Forms控件都可以添加平台特定的控件。例如,UILabel
可以将iOS 直接添加到该ContentView.Content
属性或StackLayout.Children
集合中。但是,请注意,此功能需要#if
在Xamarin.Forms共享项目解决方案中使用定义,并且不能从Xamarin.Forms便携式类库(PCL)解决方案获得。
以下屏幕截图显示了已经添加到Xamarin.Forms中的特定于平台的控件StackLayout
:
通过在每个平台上的两种扩展方法启用将特定于平台的控件添加到Xamarin.Forms布局的功能:
在Xamarin.Forms共享项目中使用这些方法需要导入相应的平台特定的Xamarin.Forms命名空间:
- iOS - Xamarin.Forms.Platform.iOS
- Android - Xamarin.Forms.Platform.Android
- Windows运行时 - Xamarin.Forms.Platform.WinRT
- 通用Windows平台(UWP) - Xamarin.Forms.Platform.UWP
在每个平台上添加平台特定的控件
以下部分演示如何将平台特定的控件添加到每个平台上的Xamarin.Forms布局。
iOS版
以下代码示例演示如何添加UILabel
到a StackLayout
和a ContentView
:
var uiLabel = new UILabel {
MinimumFontSize = 14f,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();
该示例假定先前已在XAML或C#中创建stackLayout
和contentView
实例。
Android的
以下代码示例演示如何添加TextView
到a StackLayout
和a ContentView
:
var textView = new TextView (Forms.Context) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();
该示例假定先前已在XAML或C#中创建stackLayout
和contentView
实例。
Windows运行时和通用Windows平台
以下代码示例演示如何添加TextBlock
到a StackLayout
和a ContentView
:
var textBlock = new TextBlock
{
Text = originalText,
FontSize = 14,
FontFamily = new FontFamily("HelveticaNeue"),
TextWrapping = TextWrapping.Wrap
};
stackLayout.Children.Add(textBlock);
contentView.Content = textBlock.ToView();
该示例假定先前已在XAML或C#中创建stackLayout
和contentView
实例。
自定义控件的覆盖平台测量
每个平台上的自定义控件通常只能正确地实现他们设计的布局场景的测量。例如,自定义控件可能被设计为仅占据设备的可用宽度的一半。但是,在与其他用户共享之后,可能需要自定义控件来占用设备的全部可用宽度。因此,当在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 in order 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 (Forms.Context) {
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运行时和通用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 in order 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
覆盖总是返回所请求宽度的一半,所以控件将被剪切到设备的可用宽度的一半,如以下屏幕截图所示:
解决这个问题的方法是提供一个ArrangeOverrideDelegate
实现,当添加控件时StackLayout
,如下面的代码示例所示:
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布局添加平台特定的控件,以及如何覆盖自定义控件的布局,以便更正其测量API使用情况。