C 中的本机视图#Native Views in C#

下载示例 下载示例Download Sample Download the sample

从 iOS、 Android 和 UWP 的本机视图,可以从使用 C# 创建的 Xamarin.Forms 页面直接引用。本文演示如何将本机视图添加到使用 C# 中,创建的 Xamarin.Forms 布局以及如何重写自定义视图来更正其度量 API 使用情况的布局。Native views from iOS, Android, and UWP can be directly referenced from Xamarin.Forms pages created using C#. This article demonstrates how to add native views to a Xamarin.Forms layout created using C#, and how to override the layout of custom views to correct their measurement API usage.

概述Overview

允许任何 Xamarin.Forms 控件Content设置,或具有Children集合中,可以添加特定于平台的视图。Any Xamarin.Forms control that allows Content to be set, or that has a Children collection, can add platform-specific views. 例如,iOSUILabel可以直接添加到 ContentView.Content 属性,或设置为 StackLayout.Children 集合。For example, an iOS UILabel can be directly added to the ContentView.Content property, or to the StackLayout.Children collection. 但请注意,此功能需要使用#ifXamarin.Forms 共享项目解决方案中定义,不提供从 Xamarin.Forms.NET Standard 库解决方案。However, note that this functionality requires the use of #if defines in Xamarin.Forms Shared Project solutions, and isn't available from Xamarin.Forms .NET Standard library solutions.

下面的屏幕截图演示了特定于平台的视图具有已添加到 Xamarin.Forms StackLayout :The following screenshots demonstrate platform-specific views having been added to a Xamarin.Forms StackLayout:

能够将特定于平台的视图添加到 Xamarin.Forms 布局被启用的每个平台上的两个扩展方法:The ability to add platform-specific views to a Xamarin.Forms layout is enabled by two extension methods on each platform:

  • Add – 将添加到的特定于平台的视图 Children 布局的集合。Add – adds a platform-specific view to the Children collection of a layout.
  • ToView – 采用特定于平台的视图,并将其包装为 Xamarin.Forms View 可以将设置为Content控件的属性。ToView – takes a platform-specific view and wraps it as a Xamarin.Forms View that can be set as the Content property of a control.

在 Xamarin.Forms 共享项目中使用这些方法需要导入相应的特定于平台的 Xamarin.Forms 命名空间:Using these methods in a Xamarin.Forms shared project requires importing the appropriate platform-specific Xamarin.Forms namespace:

  • iOS – Xamarin.Forms.Platform.iOSiOS – Xamarin.Forms.Platform.iOS
  • Android – Xamarin.Forms.Platform.AndroidAndroid – Xamarin.Forms.Platform.Android
  • 通用 Windows 平台 (UWP) – Xamarin.Forms.Platform.UWPUniversal Windows Platform (UWP) – Xamarin.Forms.Platform.UWP

添加每个平台上的特定于平台的视图Adding Platform-Specific Views on Each Platform

以下各节演示如何将特定于平台的视图添加到每个平台上的 Xamarin.Forms 布局。The following sections demonstrate how to add platform-specific views to a Xamarin.Forms layout on each platform.

iOSiOS

下面的代码示例演示如何添加UILabel StackLayout 和一个 ContentView :The following code example demonstrates how to add a UILabel to a StackLayout and a ContentView:

var uiLabel = new UILabel {
  MinimumFontSize = 14f,
  Lines = 0,
  LineBreakMode = UILineBreakMode.WordWrap,
  Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();

该示例假定stackLayoutcontentView具有以前在 XAML 或 C# 中创建实例。The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

AndroidAndroid

下面的代码示例演示如何添加TextView StackLayout 和一个 ContentView :The following code example demonstrates how to add a TextView to a StackLayout and a ContentView:

var textView = new TextView (MainActivity.Instance) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();

该示例假定stackLayoutcontentView具有以前在 XAML 或 C# 中创建实例。The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

通用 Windows 平台Universal Windows Platform

下面的代码示例演示如何添加TextBlock StackLayout 和一个 ContentView :The following code example demonstrates how to add a TextBlock to a StackLayout and 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();

该示例假定stackLayoutcontentView具有以前在 XAML 或 C# 中创建实例。The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

重写自定义视图的平台度量Overriding Platform Measurements for Custom Views

每个平台上的自定义视图通常只正确地实现设计它们的布局方案用于度量值。Custom views on each platform often only correctly implement measurement for the layout scenario for which they were designed. 例如,自定义视图可能旨在仅占用设备的可用宽度的一半。For example, a custom view may have been designed to only occupy half of the available width of the device. 但是,与其他用户共享之后, 自定义视图可能需要占用的设备的完整可用宽度。However, after being shared with other users, the custom view may be required to occupy the full available width of the device. 因此,可能需要时重新使用 Xamarin.Forms 布局中重写自定义视图度量实现。Therefore, it can be necessary to override a custom views measurement implementation when being reused in a Xamarin.Forms layout. 因此,AddToView扩展方法提供允许度量委托来指定,它时将其添加到 Xamarin.Forms 布局可以重写自定义视图布局的替代。For that reason, the Add and ToView extension methods provide overrides that allow measurement delegates to be specified, which can override the custom view layout when it's added to a Xamarin.Forms layout.

以下各节演示如何重写自定义视图,以更正其度量 API 使用情况的布局。The following sections demonstrate how to override the layout of custom views, to correct their measurement API usage.

iOSiOS

下面的代码示例演示CustomControl类,该类继承自UILabel:The following code example shows the CustomControl class, which inherits from 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 ,如以下代码示例所示:An instance of this view is added to a StackLayout, as demonstrated in the following code example:

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,视图将显示为具有空白区域的上方和下方,如以下屏幕截图中所示:However, because the CustomControl.SizeThatFits override always returns a height of 150, the view will be displayed with empty space above and below it, as shown in the following screenshot:

此问题的解决方案是提供GetDesiredSizeDelegate实现,如下面的代码示例中所示:A solution to this problem is to provide a GetDesiredSizeDelegate implementation, as demonstrated in the following code example:

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方法,但将替换为 70%高度为 150 的高度。This method uses the width provided by the CustomControl.SizeThatFits method, but substitutes the height of 150 for a height of 70. CustomControl实例添加到 StackLayout ,则FixSize方法可以指定为GetDesiredSizeDelegate若要修复错误所提供的度量CustomControl类:When the CustomControl instance is added to the StackLayout, the FixSize method can be specified as the GetDesiredSizeDelegate to fix the bad measurement provided by the CustomControl class:

stackLayout.Children.Add (customControl, FixSize);

这会导致自定义视图显示正确,而无需空白区域的上方和下方,如以下屏幕截图中所示:This results in the custom view being displayed correctly, without empty space above and below it, as shown in the following screenshot:

AndroidAndroid

下面的代码示例演示CustomControl类,该类继承自TextView:The following code example shows the CustomControl class, which inherits from 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 ,如以下代码示例所示:An instance of this view is added to a StackLayout, as demonstrated in the following code example:

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重写将始终返回请求的宽度的一半,则视图将显示占用只完成了一半的可用宽度的设备,如以下屏幕截图中所示:However, because the CustomControl.OnMeasure override always returns half of the requested width, the view will be displayed occupying only half the available width of the device, as shown in the following screenshot:

此问题的解决方案是提供GetDesiredSizeDelegate实现,如下面的代码示例中所示:A solution to this problem is to provide a GetDesiredSizeDelegate implementation, as demonstrated in the following code example:

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。This method uses the width provided by the CustomControl.OnMeasure method, but multiplies it by two. CustomControl实例添加到 StackLayout ,则FixSize方法可以指定为GetDesiredSizeDelegate若要修复错误所提供的度量CustomControl类:When the CustomControl instance is added to the StackLayout, the FixSize method can be specified as the GetDesiredSizeDelegate to fix the bad measurement provided by the CustomControl class:

stackLayout.Children.Add (customControl, FixSize);

这会导致自定义视图所显示正确,占用的宽度的设备,如以下屏幕截图中所示:This results in the custom view being displayed correctly, occupying the width of the device, as shown in the following screenshot:

通用 Windows 平台Universal Windows Platform

下面的代码示例演示CustomControl类,该类继承自Panel:The following code example shows the CustomControl class, which inherits from 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 ,如以下代码示例所示:An instance of this view is added to a StackLayout, as demonstrated in the following code example:

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重写将始终返回请求的宽度的一半,将为可用宽度的一半的设备,剪辑视图,如以下屏幕截图中所示:However, because the CustomControl.ArrangeOverride override always returns half of the requested width, the view will be clipped to half the available width of the device, as shown in the following screenshot:

此问题的解决方案是提供ArrangeOverrideDelegate实现中,添加到视图时 StackLayout ,如以下代码示例所示:A solution to this problem is to provide an ArrangeOverrideDelegate implementation, when adding the view to the StackLayout, as demonstrated in the following code example:

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。This method uses the width provided by the CustomControl.ArrangeOverride method, but multiplies it by two. 这会导致自定义视图所显示正确,占用的宽度的设备,如以下屏幕截图中所示:This results in the custom view being displayed correctly, occupying the width of the device, as shown in the following screenshot:

总结Summary

本文介绍如何将本机视图添加到使用 C# 中,创建的 Xamarin.Forms 布局以及如何重写自定义视图来更正其度量 API 使用情况的布局。This article explained how to add native views to a Xamarin.Forms layout created using C#, and how to override the layout of custom views to correct their measurement API usage.