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
に追加されたプラットフォーム固有のビューを示したものです。
プラットフォーム固有のビューを 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
次のコード例は、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
次に示すのは、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 を返すので、次のスクリーンショットで示すように、ビューの上部と下部には空の領域が表示されます。
この問題を解決するには、次のコード例で示すように、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
次に示すのは、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
のオーバーライドは要求された幅の半分を常に返すため、次のスクリーンショットで示すように、デバイスの使用可能な幅の半分のみを占めるビューが表示されます。
この問題を解決するには、次のコード例で示すように、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 プラットフォーム
次に示すのは、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
のオーバーライドは要求された幅の半分を常に返すため、次のスクリーンショットで示すように、ビューはデバイスの使用可能な幅の半分にクリップされます。
この問題を解決するには、次のコード例で示すように、ビューを 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 を掛けています。 これにより、次のスクリーンショットで示すように、デバイスの幅を占めるカスタム ビューが正しく表示されます。
まとめ
この記事では、C# を使って作成された Xamarin.Forms レイアウトにネイティブ ビューを追加する方法と、測定 API の使用を修正するためにカスタム ビューのレイアウトをオーバーライドする方法を説明しました。