Xamarin.Formsでカメラを使う – Android編

| | プログラミング | , ,

Xamarin.Forms上でのカメラのプレビュー表示をちょっと調べたので方法のメモです。

今回は Android でプレビューを表示する方法を、次回はWindows Phone 8上でプレビューを表示する方法を解説します。
※iOSの実装については触れません。もってないので!

サンプルプログラムをGitHubで公開しています
本記事のコードはこれを簡略化したものですので、詳しい実装はそちらをご覧下さい。

Custom Renderer

Xamarin.Forms にはハードウェアの抽象化APIがありません。
カメラプレビューを表示するには

  • Android なら SurfaceView か何かに Android.Hardware.Camera のプレビューを表示させる
  • WindowsPhone なら、Canvas の Background に VideoBrush を設定して、Camera のプレビューを流しこむ

といった方法が定石ですが、Xamarin.Form のPCLプロジェクトからはそもそもカメラオブジェクトに触れることすらできません。
「カメラのプレビューを表示するコントロール」が用意されていればいいのにと思うのですが、もちろんありません。

幸い、Xamarin.Forms にはプラットフォーム固有の機能を用いるための機構が備わっており、それを用いて独自の View のレンダラ(Custom Renderer)を作成することができます。
今回はこの Custom Renderer を用いてカメラプレビューの表示を実現します。

※この記事では Custom Renderer の基本的な使い方は扱いません。
  Custom Renderer について詳しくは以下の Xamarin 公式文書をご覧下さい。
  Customizing Controls for Each Platform
 

PCLプロジェクトで作るもの

PCLプロジェクトでは、カスタムレンダラを作成する元となる View を定義します。
今回はXamarin.Forms.Viewクラスを直接継承した CameraPreview クラスを作ります。

public class CameraPreview : View
{
	/// 
	/// 写真を撮影する。
	/// PCLから使う。
	/// 
	public void TakePicture()
	{
		if (PictureRequired != null) {
			PictureRequired(this, new EventArgs());
		}
	}
	
	/// 
	/// 写真が撮れたことを通知する。
	/// カスタムレンダラから使う
	/// 
	/// 撮影された画像
	public void OnPictureTaken(Models.IImage image)
	{
		if (image == null) { throw new ArgumentNullException("image"); }

		if (PictureTaken != null) {
			PictureTaken(this, new PictureTakenEventArgs(image));
		}
	}

	/// 
	/// TakePicture()が呼ばれた時に発生するイベント
	/// 
	public event EventHandler PictureRequired;

	/// 
	/// 写真が撮られた時に発生するイベント
	/// 
	public event EventHandler PictureTaken;
}

public sealed class PictureTakenEventArgs : EventArgs
{
	public PictureTakenEventArgs(Models.IImage image)
	{
		Image = image;
	}

	public Models.IImage Image { get; private set; }
}

IImageインターフェイスはプラットフォーム毎に異なるビットマップの取扱いを抽象化する為のクラスで、ここでは以下のように定義しています。
(今回のサンプルではプラットフォーム固有のビットマップを利用していないので、直接ImageSourceを扱っても問題ありません。)

public interface IImage
{
	ImageSource AsImageSource();
}

まず PCLプロジェクトで準備するものは、これで全てです。
このクラスは

  • PCL側から写真撮影
  • プラットフォーム毎のプロジェクトから撮影した写真の受け渡し

の双方に使うため、TakePicture() と OnPictureTaken() の双方を public として定義しています。

Android 用プロジェクトでの実装

Android では ViewRenderer クラスを継承したクラスを作る事により、View の派生クラスに対するカスタムレンダラを実装できます。

Xamarin.Forms.dllの更新

ViewRenderer にはGeneric版と非Generic版があり、Generic版を使うにはプロジェクトで参照している Xamarin.Forms.dll を Version 1.2.2.* 以降にアップデートする必要があります。
NuGet パッケージマネージャから簡単にできますので、ぱっとやってしまいましょう。
Android NuGet パッケージマネージャ

カスタムレンダラの実装

次にカスタムレンダラを実装します。
継承する Platform.Android.ViewRenderer<TView, TNativeView> の TView にはPCL内で定義したクラス、TNativeView にはカスタムレンダラで実際に表示させるプラットフォーム固有の View を指定します。
今回は Xamarin.Forms の View に CameraPreview クラス、実際に表示させるのは Android.Views.SurfaceView なので、ViewRenderer<CameraPreview, SurfaceView> を継承した CameraPreviewRenderer クラスを作成します。(全コード

public class CameraPreviewRenderer : ViewRenderer, ISurfaceHolderCallback
{
	Camera camera_ = null;

	protected override void OnElementChanged(ElementChangedEventArgs e)
	{
		base.OnElementChanged(e);

		if (e.OldElement == null) {
			// 最初に生成された時だけここを通る。
			var preview = e.NewElement;
			preview.PictureRequired += preview_PictureRequired;

			var surfaceView = new SurfaceView(Context);
			surfaceView.Holder.AddCallback(this);
			SetNativeControl(surfaceView);
		}
	}

	void preview_PictureRequired(object sender, EventArgs e)
	{
		CameraPreview preview = sender as CameraPreview;
		if (camera_ != null && preview != null) {
			camera_.TakePicture(null, null, new DelegatePictureCallback {
				PictureTaken = (data, camera) => {
					// jpegデータをMemoryStreamに書き込む
					MemoryStream ms = null;
					try {
						ms = new MemoryStream(data.Length);
						ms.Write(data, 0, data.Length);
						ms.Flush();
						ms.Seek(0, SeekOrigin.Begin);

						// MemoryStreamからImageSourceを生成
						preview.OnPictureTaken(new Models.AndroidImage {
							ImageSource = ImageSource.FromStream(() => ms)
						});
					}
					catch {
						if (ms != null) {
							ms.Dispose();
						}
						throw;
					}
				}
			});
		}
	}
	
	// ------- 中略 -------
}

サンプルではSurfaceViewを作成し、そこにカメラのプレビューを表示します。

まずOnElementChanged() 関数をオーバーライドし、e.OldElement == null のとき、つまり最初にCameraPreviewクラスのオブジェクトがこのレンダラに設定される時に、ビューの生成処理を行います。
プレビューを表示する SurfaceView を作成し SetNativeControl() メソッドで、生成した SurfaceView をカスタムレンダラのルート要素に設定します。
なお、ここではカメラを初期化せず SurfaceView の SurfaceCreated/SurfaceDestroyed イベントでカメラの初期化・破棄を行います。
※ここでは省いていますので、ソースコードを直接確認してください。

このとき OnElementChanged() の e.NewElement には CameraPreview クラスのインスタンスが入っていますので、これを取得して写真要求イベントのハンドラも設定します。
写真の要求があったら Camera.TakePicture() メソッドを呼び、そのコールバックで受け取ったjpegデータを一旦 MemoryStream に保存し、そこから ImageSource を作成し、撮影完了通知を行います。

カスタムレンダラのExport

カスタムレンダラを実装したら、次Xamarin.Forms.ExportRendererAttribute 属性を用いて作成したカスタムレンダラを Export します。
方法は簡単で、以下の一行をコードの戦闘部分などに記入するだけです。
アセンブリにこの属性を持たせることで、この(Androidの)プロジェクトにおいて、CameraPreview クラスのレンダラには CameraPreviewRenderer クラスが利用されるようになります。

[assembly: ExportRenderer(typeof(CameraPreview), typeof(XamarinFormsCameraPreview.Droid.Renderers.CameraPreviewRenderer))]

これで、カスタムレンダラの実装は完了です。

カスタムレンダラを利用する – PCLでの実装再び

カスタムレンダラが完成したので、次はそれを表示させましょう。
ここからはまたPCLプロジェクトをいじっていきます。

まずはカメラのプレビューを表示させるページを以下のように作ります。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
					   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
					   x:Class="XamarinFormsCameraPreview.Views.CapturePage">
	<AbsoluteLayout
		BackgroundColor="Gray">

		<Grid x:Name="gridCameraPreview_"
			AbsoluteLayout.LayoutBounds="0, 0, 1.0, 1.0"
			AbsoluteLayout.LayoutFlags="All"/>

		<Button
			Text="TakePicture"
			Clicked="OnCaptureButtonClicked"
			HorizontalOptions="Center"
			VerticalOptions="Center"
			AbsoluteLayout.LayoutBounds="0, 1.0, 1.0, 0.1"
			AbsoluteLayout.LayoutFlags="All"
			/>
	</AbsoluteLayout>
</ContentPage>

単純にGridを全体に表示させ、画面下部分に撮影ボタンを設置しています。
実はこのGridが肝で、カスタムレンダラを利用するビューをXAML上で直接配置すると、Xamarin.Forms はエラーを吐いてしまいます
これを防ぐため、CameraPreview クラスのインスタンス作成はコードビハインドで行います。

CapturePageのコードビハインドは以下のようになっています。 (全コード

public partial class CapturePage
{
	CameraPreview preview_ = null;

	public CapturePage()
	{
		InitializeComponent();

		preview_ = new CameraPreview();
		preview_.PictureTaken += preview__PictureTaken;
		gridCameraPreview_.Children.Add(preview_);
	}

	void preview__PictureTaken(object sender, PictureTakenEventArgs e)
	{
		// ------ 中略 ------
		// e.AsImageSource()で取得した写真画像をViewImagePageで表示する。
	}

	void OnCaptureButtonClicked(object sender, EventArgs e)
	{
		preview_.TakePicture();
	}
}

InitializeComponent() の後に CameraPreview クラスのインスタンスを作成し、GridのChildrenに追加しています。
これでプラットフォームに応じて作成された Custom Renderer を利用するビューが生成され、カメラのプレビューが表示されます。

また、撮影(Take Picture)ボタンが押された時には OnCaptureButtonClicked() メソッドが呼ばれ、CameraPreview に対して写真撮影の要求をします。

動作確認

実際にアプリケーションを動かして動作を確認してみましょう。

Android上でアプリケーションを起動すると、以下のようにカメラのプレビューが表示されます。
動作

プレビュー画面のアスペクト比や解像度、フォーカス、回転角度など本来はCameraPreviewRendererの中で設定しなければならないのですが、今回は主眼でないため省いています。
ここでTakePictureボタンをタップするとその場で写真が撮影され、以下の撮影写真のプレビュー画面へ遷移します。
Android 撮影後

正常に写真が撮影され、画像として取得できていることが確認できました。
Action Bar の Home ボタンか、端末の Back ボタンをタップすると、またカメラのプレビュー画面へ戻ります。


今回は、Xamarin.Formsでカメラのプレビューを表示する方法、特にAndroidでの実装について解説しました。

次回は Windows Phone 8 での実装を紹介したいと思います。
Windows Phone 8 での実装も Android のものとほとんど同じで、ここまでできれば簡単に実装ができます。

8 thoughts on “Xamarin.Formsでカメラを使う – Android編

  1. ピンバック: Xamarin.Formsでカメラを使う – Windows Phone 8編 | スタジオ大破の自転車大破日記

  2. ニューバランス M1400 DK ブラウン/タン/オフホワイト 『MADE IN USAモデル』 NEWBALANCE M1400 DK BROWN/TAN/OFF WHIT 返信

    &#12290;&#12354;&#12394;&#12383;&#12398;&#12506;&#12540;&#12472;&#12364;&#32654;&#12375;&#12356;&#12391;&#12377;&#12289;&#12354;&#12394;&#12383;&#12398;&#12464;&#12521;&#12501;&#12451;&#12483;&#12463;&#12364;&#20778;&#12428;&#12390;&#12356;&#12427;&#12289;&#12392;&#12356;&#12383;&#12384;&#12365;&#12414;&#12375;&#12383;&#65281;&#12424;&#12426;&#12289;&#12354;&#12394;&#12383;&#12364;&#20309;&#12395;&#12388;&#12356;&#12390;&#35441;&#12375;&#12390;&#12356;&#12427;&#12398;&#12363;&#12395;&#38306;&#36899;&#12375;&#12390;&#12356;&#12427;&#12477;&#12540;&#12473;&#12434;&#20351;&#29992;&#12375;&#12390;&#12356;&#12414;&#12377;&#12290; Youre&#12398;&#12399;&#30906;&#12363;&#12395;1&#19975;&#20154;&#12391;&#12289;&#33391;&#12356;&#20181;&#20107;&#12434;&#32173;&#25345;&#12375;&#12414;&#12377;&#65281;
    &#12491;&#12517;&#12540;&#12496;&#12521;&#12531;&#12473; M1400 DK &#12502;&#12521;&#12454;&#12531;/&#12479;&#12531;/&#12458;&#12501;&#12507;&#12527;&#12452;&#12488; &#12302;MADE IN USA&#12514;&#12487;&#12523;&#12303; NEWBALANCE M1400 DK BROWN/TAN/OFF WHITE &#12513;&#12531;&#12474; &#12473;&#12491;&#12540;&#12459;&#12540; &#21040;&#30528;&#24460;&#12524;&#12499;&#12517;&#12540;&#12398;&#12362;&#32004;&#26463;&#12391;&#12302;&#36865;&#26009;&#28961;&#26009;&#12303;&#65306;Foot Time <a href="http://pamelacao.com.ar/gohotnb/137.html" rel="nofollow ugc">http://pamelacao.com.ar/gohotnb/137.html</a>

  3. 【送料無料】【選べる2色!2個セット】ペリカン ミニ 15Lスタックストー 返信

    &#12354;&#12394;&#12383;&#12364;&#12363;&#12418;&#12375;&#12428;&#12394;&#12356;&#12371;&#12371;&#12454;&#12455;&#12502;&#12525;&#12464; &#12434;&#25345;&#12387;&#12390;&#12356;&#12414;&#12377;&#65281;&#12354;&#12394;&#12383;&#12399;&#12356;&#12367;&#12388;&#12363;&#12399;&#12289;&#31169;&#12398;&#12502;&#12525;&#12464;&#12398;&#35352;&#20107;&#12434;&#25307;&#24453;&#12377;&#12427;&#12395;&#12375;&#12383;&#12356;&#12391;&#12375;&#12423;&#12358;&#12363;&#65311;&#12363;&#12394;&#12426;
    [url=http://balticshuttle.com/dearsbaby/18.html]&#12304;&#36865;&#26009;&#28961;&#26009;&#12305;&#12304;&#36984;&#12409;&#12427;2&#33394;&#65281;2&#20491;&#12475;&#12483;&#12488;&#12305;&#12506;&#12522;&#12459;&#12531; &#12511;&#12491; 15&#65324;&#12473;&#12479;&#12483;&#12463;&#12473;&#12488;&#12540; stacksto,&#12288;pelican mini&#21454;&#32013; &#21454;&#32013;&#12508;&#12483;&#12463;&#12473; &#21454;&#32013;&#12465;&#12540;&#12473; &#12362;&#12418;&#12385;&#12419;&#31665;&#8251;&#21271;&#28023;&#36947;?&#27798;&#32260;?&#38626;&#23798;&#12399;&#36865;&#26009;&#28961;&#26009;&#23550;&#35937;&#22806;&#12304;&#12452;&#12531;&#12486;&#12522;&#12450;?[/url]

  4. ホットテックス 布団 敷きカバー セミダブル 肌布団 掛布団 タオルケット 返信

    &#12450;&#12452;&#12502;&#27663;&#12399;&#12289;&#12473;&#12479;&#12452;&#12523;&#12392;&#19968;&#32210;&#12395;&#12289;&#31169;&#12398;&#20491;&#20154;&#30340;&#12394;&#12502;&#12525;&#12464;&#12399;&#12289;&#12467;&#12540;&#12489;&#21270;&#12373;&#12428;&#24471;&#12427;&#12383;&#12417;&#12395;&#12289;&#36884;&#20013;&#12391;&#12356;&#12367;&#12388;&#12363;&#12398;&#12450;&#12452;&#12487;&#12450;&#12434;&#24471;&#12427;&#12371;&#12392;&#12434;&#26395;&#12435;&#12493;&#12483;&#12488;&#19978;&#12391;&#12354;&#12394;&#12383;&#12398;&#29694;&#22312;&#12398;&#12473;&#12479;&#12452;&#12523;&#12434;&#27169;&#32034;&#12375;&#12390;&#32032;&#26228;&#12425;&#12375;&#12356;&#12391;&#12377;&#12290;&#12354;&#12394;&#12383;&#12399;&#33258;&#20998;&#12391;&#12381;&#12428;&#12434;&#12467;&#12540;&#12487;&#12451;&#12531;&#12464;&#12375;&#12414;&#12375;&#12383;&#12363;&#12289;&#12354;&#12394;&#12383;&#12398;&#20195;&#12431;&#12426;&#12395;&#12381;&#12428;&#12434;&#34892;&#12358;&#12383;&#12417;&#12398;&#12467;&#12540;&#12480;&#12540;&#12434;&#21215;&#38598;&#12375;&#12414;&#12375;&#12383;&#12363;&#65311;
    &#12507;&#12483;&#12488;&#12486;&#12483;&#12463;&#12473; &#24067;&#22243; &#25975;&#12365;&#12459;&#12496;&#12540; &#12475;&#12511;&#12480;&#12502;&#12523; &#32908;&#24067;&#22243; &#25499;&#24067;&#22243; &#12479;&#12458;&#12523;&#12465;&#12483;&#12488; &#23517;&#20855; &#24067;&#22243; &#12405;&#12392;&#12435; &#24555;&#36969; &#24555;&#30496; &#26032;&#30528; &#36865;&#26009;&#36796;&#12415; &#12304;&#36865;&#26009;&#28961;&#26009;&#12305;&#65306;&#23478;&#20855;&#36890;&#36009;&#12398;&#12525;&#12454;&#12516; <a href="http://www.pierpaoli.com/elepshe/505.html" rel="nofollow ugc">http://www.pierpaoli.com/elepshe/505.html</a>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です