ビューの実装
Xamarin.Forms のカスタム ユーザー インターフェイス コントロールは、View クラスから派生させる必要があります。これは画面上にレイアウトとコントロールを配置するために使われます。 この記事では、デバイスのカメラからビデオ ストリームのプレビューを表示するために使う、Xamarin.Forms のカスタム コントロール用のカスタム レンダラーを作成する方法を示します。
すべての Xamarin.Forms ビューには、ネイティブ コントロールのインスタンスを作成する各プラットフォーム用のレンダラーが付属しています。 Xamarin.Forms アプリケーションによって View
がレンダリングされると、iOS では ViewRenderer
クラスがインスタンス化され、それによってネイティブの UIView
コントロールもインスタンス化されます。 Android プラットフォーム上では、ViewRenderer
クラスによってネイティブの View
コントロールがインスタンス化されます。 ユニバーサル Windows プラットフォーム (UWP) 上では、ViewRenderer
クラスによってネイティブの FrameworkElement
コントロールがインスタンス化されます。 Xamarin.Forms コントロールによってマップされるレンダラーとネイティブ コントロール クラスの詳細については、「Renderer Base Classes and Native Controls」(レンダラーの基底クラスおよびネイティブ コントロール) を参照してください。
Note
Android のコントロールの中には、ViewRenderer
クラスを使用しない高速レンダラーを使用するものがあります。 高速レンダラーの詳細については、「Xamarin.Forms 高速レンダラー」を参照してください。
次の図に、View
と、それを実装する、対応するネイティブ コントロールの関係を示します。
レンダリング プロセスを使用して、各プラットフォーム上で View
用のカスタム レンダラーを作成することで、プラットフォーム固有のカスタマイズを実装することができます。 これを行うための実行プロセスは次のとおりです。
- Xamarin.Forms カスタム コントロールを作成します。
- Xamarin.Forms からカスタム コントロールを使用します。
- 各プラットフォーム上でコントロールのカスタム レンダラーを作成します。
デバイスのカメラからのプレビュー ビデオ ストリームを表示する CameraPreview
レンダラーを実装する各項目について順番に説明します。 ビデオ ストリームをタップすると、停止/開始されます。
カスタム コントロールの作成
カスタム コントロールは、次のコード例のように、View
クラスをサブクラス化することで作成できます。
public class CameraPreview : View
{
public static readonly BindableProperty CameraProperty = BindableProperty.Create (
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraPreview),
defaultValue: CameraOptions.Rear);
public CameraOptions Camera
{
get { return (CameraOptions)GetValue (CameraProperty); }
set { SetValue (CameraProperty, value); }
}
}
CameraPreview
カスタム コントロールは、.NET 標準ライブラリ プロジェクトで作成され、このコントロールの API を定義します。 カスタム コントロールは、デバイスの前面または背面のカメラからビデオ ストリームを表示するかどうかを制御するために使用される Camera
プロパティを公開しています。 コントロールの作成時に Camera
プロパティの値が指定されていない場合は、既定で背面のカメラが指定されます。
カスタム コントロールの使用
CameraPreview
カスタム コントロールは、その場所の名前空間を宣言し、カスタム コントロール要素上で名前空間プレフィックスを使用することで .NET Standard ライブラリ プロジェクトの XAML で参照することができます。 次のコード例は、XAML ページがどのように CameraPreview
カスタム コントロールを使用できるかを示しています。
<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
<StackLayout>
<Label Text="Camera Preview:" />
<local:CameraPreview Camera="Rear"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
</StackLayout>
</ContentPage>
local
名前空間プレフィックスには任意の名前を付けることができます。 ただし、clr-namespace
と assembly
の値は、カスタム コントロールの詳細と一致させる必要があります。 名前空間が宣言されると、プレフィックスを使用してカスタム コントロールが参照されます。
次のコード例は、C# ページがどのように CameraPreview
カスタム コントロールを使用できるかを示しています。
public class MainPageCS : ContentPage
{
public MainPageCS ()
{
...
Content = new StackLayout
{
Children =
{
new Label { Text = "Camera Preview:" },
new CameraPreview
{
Camera = CameraOptions.Rear,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
}
}
};
}
}
CameraPreview
カスタム コントロールのインスタンスは、デバイスのカメラからプレビュー ビデオ ストリームを表示するために使用されます。 必要に応じて Camera
プロパティの値を指定する以外に、コントロールのカスタマイズはカスタム レンダラーで実行されます。
これで、カスタム レンダラーを各アプリケーション プロジェクトに追加して、プラットフォーム固有のカメラ プレビュー コントロールを作成できるようになりました。
各プラットフォームでのカスタム レンダラーの作成
iOS と UWP でカスタム レンダラー クラスを作成するプロセスは次のとおりです。
- カスタム コントロールをレンダリングする
ViewRenderer<T1,T2>
クラスのサブクラスを作成します。 最初の型引数は、レンダラーが使用するカスタム コントロール (この場合はCameraPreview
) にする必要があります。 2 つ目の型引数は、カスタム コントロールを実装するネイティブ コントロールにする必要があります。 - カスタム コントロールをレンダリングする
OnElementChanged
メソッドをオーバーライドして、ロジックを書き込んでカスタマイズします。 対応する Xamarin.Forms コントロールが作成されると、このメソッドが呼び出されます。 ExportRenderer
属性をカスタム レンダラー クラスに追加し、それを使用して Xamarin.Forms のカスタム コントロールがレンダリングされることを指定します。 この属性は、Xamarin.Forms にカスタム レンダラーを登録するために使用されます。
Android で高速レンダラーとしてカスタム レンダラー クラスを作成するプロセスは次のとおりです。
- カスタム コントロールをレンダリングする Android コントロールのサブクラスを作成します。 さらに、サブクラスで
IVisualElementRenderer
およびIViewRenderer
インターフェイスが実装されるように指定します。 - 高速レンダラー クラスで
IVisualElementRenderer
およびIViewRenderer
インターフェイスを実装します。 ExportRenderer
属性をカスタム レンダラー クラスに追加し、それを使用して Xamarin.Forms のカスタム コントロールがレンダリングされることを指定します。 この属性は、Xamarin.Forms にカスタム レンダラーを登録するために使用されます。
Note
ほとんどの Xamarin.Forms 要素では、プラットフォーム プロジェクトごとにカスタム レンダラーを指定するかどうかは任意です。 カスタム レンダラーが登録されていない場合は、コントロールの基底クラスの既定のレンダラーが使用されます。 ただし、View 要素をレンダリングするときは、各プラットフォーム プロジェクトにカスタム レンダラーが必要です。
次の図に、サンプル アプリケーション内の各プロジェクトの役割と、それらの関係を示します。
CameraPreview
カスタム コントロールはプラットフォーム固有のレンダラー クラスによってレンダリングされます。それは、iOS と UWP では ViewRenderer
クラスから、Android では FrameLayout
クラスから派生します。 この結果、次のスクリーンショットに示すように、プラットフォーム固有のコントロールを使用してそれぞれの CameraPreview
カスタム コントロールがレンダリングされます。
ViewRenderer
クラスは OnElementChanged
メソッドを公開します。このメソッドは、該当するネイティブ コントロールをレンダリングするために、Xamarin.Forms カスタム コントロールの作成時に呼び出されます。 このメソッドは、OldElement
および NewElement
プロパティを含む ElementChangedEventArgs
パラメーターを取得します。 これらのプロパティは、レンダラーがアタッチされていた Xamarin.Forms 要素と、レンダラーが現在アタッチされている Xamarin.Forms 要素をそれぞれ表しています。 サンプル アプリケーションでは、OldElement
プロパティが null
になり、NewElement
プロパティに CameraPreview
インスタンスへの参照が含まれます。
各プラットフォーム固有のレンダラー クラス内の OnElementChanged
メソッドのオーバーライドされたバージョンは、ネイティブ コントロールのインスタンス化とカスタマイズを実行する場所です。 SetNativeControl
メソッドはネイティブ コントロールのインスタンス化に使用されます。このメソッドはまた、コントロール参照を Control
プロパティに割り当てます。 さらに、レンダリングされている Xamarin.Forms コントロールへの参照は、Element
プロパティを使用して取得することができます。
状況によっては、OnElementChanged
メソッドが複数回呼び出されることがあります。 したがって、メモリ リークを防ぐため、新しいネイティブ コントロールをインスタンス化するときには慎重に行う必要があります。 カスタム レンダラーで新しいネイティブ コントロールをインスタンス化するときに使用する手法を次のコード例に示します。
protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
}
// Configure the control and subscribe to event handlers
}
}
新しいネイティブ コントロールは、Control
プロパティが null
のとき、1 回だけインスタンス化します。 さらに、カスタム レンダラーが新しい Xamarin.Forms 要素に関連付けられるときにのみ、コントロールを作成および構成し、イベント ハンドラーを登録する必要があります。 同様に、レンダラーが関連付けられている要素が変わるときにのみ、サブスクライブしていたイベント ハンドラーを登録解除します。 この手法を採用すると、メモリ リークが発生しない効率的なカスタム レンダラーを作成できます。
重要
SetNativeControl
メソッドは、e.NewElement
が null
ではない場合にのみ、呼び出す必要があります。
各カスタム レンダラー クラスは、レンダラーを Xamarin.Forms に登録する ExportRenderer
属性で修飾されます。 この属性は、レンダリングされている Xamarin.Forms カスタム コントロールの型名と、カスタム レンダラーの型名という 2 つのパラメーターを受け取ります。 属性の assembly
プレフィックスにより、属性がアセンブリ全体に適用されることが指定されます。
次のセクションで、各プラットフォーム固有の カスタム レンダラー クラスの実装について説明します。
iOS 上でのカスタム レンダラーの作成
次のコード例は、iOS プラットフォーム用のカスタム レンダラーを示します。
[assembly: ExportRenderer (typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.iOS
{
public class CameraPreviewRenderer : ViewRenderer<CameraPreview, UICameraPreview>
{
UICameraPreview uiCameraPreview;
protected override void OnElementChanged (ElementChangedEventArgs<CameraPreview> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
// Unsubscribe
uiCameraPreview.Tapped -= OnCameraPreviewTapped;
}
if (e.NewElement != null) {
if (Control == null) {
uiCameraPreview = new UICameraPreview (e.NewElement.Camera);
SetNativeControl (uiCameraPreview);
}
// Subscribe
uiCameraPreview.Tapped += OnCameraPreviewTapped;
}
}
void OnCameraPreviewTapped (object sender, EventArgs e)
{
if (uiCameraPreview.IsPreviewing) {
uiCameraPreview.CaptureSession.StopRunning ();
uiCameraPreview.IsPreviewing = false;
} else {
uiCameraPreview.CaptureSession.StartRunning ();
uiCameraPreview.IsPreviewing = true;
}
}
...
}
}
Control
プロパティが null
の場合、SetNativeControl
メソッドは、新しいネイティブの UICameraPreview
コントロールをインスタンス化し、それに対する参照を Control
プロパティに割り当てるために呼び出されます。 UICameraPreview
コントロールは、AVCapture
API を使用してカメラからプレビュー ストリームを提供するプラットフォーム固有のカスタム コントロールです。 タップ時にビデオ プレビューを停止/開始するために、OnCameraPreviewTapped
メソッドで処理される Tapped
イベントが公開されています。 Tapped
イベントは、カスタム レンダラーが新しい Xamarin.Forms 要素にアタッチされているときにサブスクライブされ、レンダラーがアタッチされている要素が変わったときにのみサブスクライブが解除されます。
Android 上でのカスタム レンダラーの作成
次のコード例では、Android プラットフォーム用の高速レンダラーを示します。
[assembly: ExportRenderer(typeof(CustomRenderer.CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.Droid
{
public class CameraPreviewRenderer : FrameLayout, IVisualElementRenderer, IViewRenderer
{
// ...
CameraPreview element;
VisualElementTracker visualElementTracker;
VisualElementRenderer visualElementRenderer;
FragmentManager fragmentManager;
CameraFragment cameraFragment;
FragmentManager FragmentManager => fragmentManager ??= Context.GetFragmentManager();
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
CameraPreview Element
{
get => element;
set
{
if (element == value)
{
return;
}
var oldElement = element;
element = value;
OnElementChanged(new ElementChangedEventArgs<CameraPreview>(oldElement, element));
}
}
public CameraPreviewRenderer(Context context) : base(context)
{
visualElementRenderer = new VisualElementRenderer(this);
}
void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
{
CameraFragment newFragment = null;
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
cameraFragment.Dispose();
}
if (e.NewElement != null)
{
this.EnsureId();
e.NewElement.PropertyChanged += OnElementPropertyChanged;
ElevationHelper.SetElevation(this, e.NewElement);
newFragment = new CameraFragment { Element = element };
}
FragmentManager.BeginTransaction()
.Replace(Id, cameraFragment = newFragment, "camera")
.Commit();
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
}
async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
ElementPropertyChanged?.Invoke(this, e);
switch (e.PropertyName)
{
case "Width":
await cameraFragment.RetrieveCameraDevice();
break;
}
}
// ...
}
}
この例では、カスタム レンダラーが新しい Xamarin.Forms 要素にアタッチされている場合、OnElementChanged
メソッドによって CameraFragment
オブジェクトが作成されます。 CameraFragment
型は、Camera2
API を使用してカメラからのプレビュー ストリームを提供するカスタム クラスです。 レンダラーがアタッチされている Xamarin.Forms 要素が変更されると、CameraFragment
オブジェクトは破棄されます。
UWP 上でのカスタム レンダラーの作成
次のコード例は、UWP 用のカスタム レンダラーを示します。
[assembly: ExportRenderer(typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.UWP
{
public class CameraPreviewRenderer : ViewRenderer<CameraPreview, Windows.UI.Xaml.Controls.CaptureElement>
{
...
CaptureElement _captureElement;
bool _isPreviewing;
protected override void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
Tapped -= OnCameraPreviewTapped;
...
}
if (e.NewElement != null)
{
if (Control == null)
{
...
_captureElement = new CaptureElement();
_captureElement.Stretch = Stretch.UniformToFill;
SetupCamera();
SetNativeControl(_captureElement);
}
// Subscribe
Tapped += OnCameraPreviewTapped;
}
}
async void OnCameraPreviewTapped(object sender, TappedRoutedEventArgs e)
{
if (_isPreviewing)
{
await StopPreviewAsync();
}
else
{
await StartPreviewAsync();
}
}
...
}
}
Control
プロパティが null
の場合、新しい CaptureElement
がインスタンス化され、SetupCamera
メソッドが呼び出されます。ここで、カメラからプレビュー ストリームを提供するために MediaCapture
API が使用されます。 CaptureElement
の参照を Control
プロパティに割り当てるために SetNativeControl
メソッドが呼び出されます。 CaptureElement
コントロールでは、タップ時にビデオ プレビューを停止/開始するために、OnCameraPreviewTapped
メソッドで処理される Tapped
イベントが公開されています。 Tapped
イベントは、カスタム レンダラーが新しい Xamarin.Forms 要素にアタッチされているときにサブスクライブされ、レンダラーがアタッチされている要素が変わったときにのみサブスクライブが解除されます。
Note
UWP アプリケーションでは、カメラにアクセスできるオブジェクトを停止して破棄することが重要です。 そうしないと、デバイスのカメラにアクセスしようとする他のアプリケーションの妨げになる可能性があります。 詳細については、「Display the camera preview」(カメラ プレビューの表示) を参照してください。
まとめ
この記事では、デバイスのカメラからビデオ ストリームのプレビューを表示するために使う、Xamarin.Forms のカスタム コントロール用のカスタム レンダラーを作成する方法について説明しました。 Xamarin.Forms のカスタム ユーザー インターフェイス コントロールは、View
クラスから派生させる必要があります。これは画面上にレイアウトとコントロールを配置するために使われます。