次の方法で共有


C# のネイティブ ビュー

iOS、Android、UWP のネイティブ ビューは、C# を使って作成された Xamarin.Forms ページから直接参照できます。 この記事では、C# を使って作成された Xamarin.Forms レイアウトにネイティブ ビューを追加する方法と、測定 API の使用を修正するためにカスタム ビューのレイアウトをオーバーライドする方法を見ていきます。

概要

Content を設定できる、または Children コレクションを持っている Xamarin.Forms コントロールでは、プラットフォーム固有のビューを追加できます。 たとえば、iOS の UILabel を、ContentView.Content プロパティまたは StackLayout.Children コレクションに直接追加できます。 ただし、この機能では、Xamarin.Forms 共有プロジェクト ソリューションで #if 定義を使う必要があり、これは Xamarin.Forms .NET Standard ライブラリ ソリューションからは使用できないことに注意してください。

次のスクリーンショットは、Xamarin.FormsStackLayout に追加されたプラットフォーム固有のビューを示したものです。

プラットフォーム固有設定のビューを含む StackLayout

プラットフォーム固有のビューを Xamarin.Forms レイアウトに追加する機能は、いずれのプラットフォームでも 2 つの拡張メソッドによって有効になります。

  • Add: プラットフォーム固有のビューをレイアウトの Children コレクションに追加します。
  • ToView: プラットフォーム固有のビューを取得し、コントロールの Contentプロパティとして設定できる Xamarin.FormsView としてそれをラップします。

Xamarin.Forms 共有プロジェクトでこれらのメソッドを使うには、適切なプラットフォーム固有の Xamarin.Forms 名前空間をインポートする必要があります。

  • iOS – Xamarin.Forms.Platform.iOS
  • Android – Xamarin.Forms.Platform.Android
  • ユニバーサル Windows プラットフォーム (UWP) – Xamarin.Forms.Platform.UWP

各プラットフォームでのプラットフォーム固有のビューの追加

以下のセクションでは、プラットフォーム固有のビューを Xamarin.Forms レイアウトに追加する方法をプラットフォームごとに見ていきます。

iOS

次のコード例は、UILabelStackLayoutContentView に追加する方法を示したものです。

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

この例では、stackLayoutcontentView のインスタンスが XAML または C# で前に作成されていることを前提としています。

Android

次のコード例は、TextViewStackLayoutContentView に追加する方法を示したものです。

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

この例では、stackLayoutcontentView のインスタンスが XAML または C# で前に作成されていることを前提としています。

ユニバーサル Windows プラットフォーム

次のコード例は、TextBlockStackLayoutContentView に追加する方法を示したものです。

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# で前に作成されていることを前提としています。

カスタム ビューのためのプラットフォームの測定のオーバーライド

多くの場合、各プラットフォームのカスタム ビューでの測定は、それらが設計されたレイアウト シナリオについてのみ正しく実装されます。 たとえば、カスタム ビューは、デバイスで利用できる幅の半分のみを占めるように設計されている場合があります。 ただし、他のユーザーと共有された後、デバイスで利用できる幅全体をカスタム ビューが占めることが必要になる場合があります。 したがって、カスタム ビューでは、Xamarin.Forms レイアウトで再利用されるときに、測定の実装をオーバーライドすることが必要な場合があります。 そのため、AddToView 拡張メソッドには、測定のデリゲートを指定できるオーバーライドが用意されており、Xamarin.Forms レイアウトに追加されるときにカスタム ビューのレイアウトをオーバーライドできます。

以下のセクションでは、カスタム ビューのレイアウトをオーバーライドして、測定 API の使用を修正する方法を見ていきます。

iOS

次に示すのは、UILabel を継承する CustomControl クラスのコードの例です。

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 を返すので、次のスクリーンショットで示すように、ビューの上部と下部には空の領域が表示されます。

SizeThatFits の実装が不適切な iOS の CustomControl

この問題を解決するには、次のコード例で示すように、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);

これにより、次のスクリーンショットで示すように、上部と下部に空の領域がない正しいカスタム ビューが表示されます。

GetDesiredSize のオーバーライドを使用した iOS の CustomControl

Android

次に示すのは、TextView を継承する CustomControl クラスのコードの例です。

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 のオーバーライドは要求された幅の半分を常に返すため、次のスクリーンショットで示すように、デバイスの使用可能な幅の半分のみを占めるビューが表示されます。

OnMeasure の実装が不適切な Android の CustomControl

この問題を解決するには、次のコード例で示すように、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);

これにより、次のスクリーンショットで示すように、デバイスの幅を占めるカスタム ビューが正しく表示されます。

カスタム GetDesiredSize のデリゲートを使用した Android の CustomControl

ユニバーサル Windows プラットフォーム

次に示すのは、Panel を継承する CustomControl クラスのコードの例です。

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 のオーバーライドは要求された幅の半分を常に返すため、次のスクリーンショットで示すように、ビューはデバイスの使用可能な幅の半分にクリップされます。

ArrangeOverride の実装が不適切な UWP の CustomControl

この問題を解決するには、次のコード例で示すように、ビューを 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 を掛けています。 これにより、次のスクリーンショットで示すように、デバイスの幅を占めるカスタム ビューが正しく表示されます。

ArrangeOverride のデリゲートを使用した UWP の CustomControl

まとめ

この記事では、C# を使って作成された Xamarin.Forms レイアウトにネイティブ ビューを追加する方法と、測定 API の使用を修正するためにカスタム ビューのレイアウトをオーバーライドする方法を説明しました。