Android のカメラのサンプル (5)

@IT から提供されている_もはやケータイに必須のカメラをAndroidで制御しよう_という記事。カメラ機能なコンテンツアプリのサンプルとして、結構上質なので読んでみますの続きです。多分これで最終回のはずなんですが、どうなるやら。

今回のテーマ

Camera.Parameters な例です。とりあえず Camera.Parameters | Android Developers によれば

  • Camera な service の設定
  • Camera#setParameter() 呼ばないと反映されない
    • 例えば setWhiteBalance しても setParameter 呼ばないと反映されないとか
  • デバイスによってカメラができる事は異なるので、設定する前にカメラができる事を確認しておく必要がある
    • 例えば setColorEffect を呼ぶ前に getSupportedColorEffects を呼ぶべき
    • そのカメラが ColorEffect をサポートしてない場合、getSupportedColorEffects は null を戻します

ええと、CameraPreview クラスで例えば以下のようなメソドが定義されております。

    protected void setPictureFormat(int format) {
        Camera.Parameters params = camera.getParameters();
        List supported = params.getSupportedPictureFormats();
        if (supported != null) {
            for (int f : supported) {
                if (f == format) {
		    params.setPreviewFormat(format);
		    camera.setParameters(params);
		    break;
                }
            }
        }
    }

getSupported なんたらは List を戻すのか。supported が null ならサポート対象外。上記の例では念の為に設定しようとしているソレがサポート対象範囲内か、も確認してます。
ちなみに CameraPreview クラスでは

  • PictureFormat
  • PreviewSize
  • AntiBanding
  • ColorEffect
  • FlashMode
  • FocusMode
  • SceneMode
  • WhiteBalance

の設定なメソドが用意されております。それぞれサポート対象範疇内かどうか、な確認付き。

Parameters

Activity については onCreate で

  • フルスクリーン
  • タイトルバーは略
  • setContentView に ParametersPreview なオブジェクトを生成して渡す
    • ParametersView は View を継承

といういつものパターン。ちなみにこの Activity では CameraPreferences という Activity を kick しますのでそこからの戻りを云々します。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (data != null) {
    	    view.setCameraParameters(data.getExtras());
        }
    }

基本的に CameraPreferences な Activity では Preferences なソレが変わるだけらしく、呼び出し元に戻ってきた時点で ParameterPreview#setCameraParameters が呼びだされるんですが、ここでは bundle な属性に渡された引数を設定して isUsingCamera を false にしてるのか。
このあたりも状態の設定に何かカギがありそうな気がしますが、とりあえず保管な処理の謎について。

  • CameraPreferences が表示された時点で surfaceDestroyed が呼ばれた
  • Parameter な Activity が再度表示された時点で surfaceCreated と surfaceChanged が呼ばれた

成程、最前面でなくなった時点で surfaceView は Destroyed されてしまう模様。あと、基本的に

  • onActivityResult
  • surfaceCreated

という順で呼び出される事を前提に作ってるように読めます。

  • onActivityResult から呼び出される ParameterPreview#setCameraParameters において bundle 属性に引数な bundle が設定されて isUsingCamera 属性が false に設定されている
  • surfaceCreated で bundle から値を取り出して Camera.Parameter なソレに set した後に Camera#setParameters している

あと、ParametersPreview#surfaceDestroyed でも正に微妙 (絶妙?) な処理がありますね。

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (isUsingCamera) {
            if (camera != null) {
                camera.stopPreview();
            }
        } else {
            super.surfaceDestroyed(holder);
        }
    }

isUsingCamera は画面左上のタップ (設定画面表示) 時に true になります (元に戻る時に false になります)。ので、上記の条件分岐は設定画面表示に伴なう surfaceDestroyed な呼び出しについては、camera.release をしない、というニガシな処理である事が分かります。

ParametersPreview

知らない内にこっちの範疇だったりしたのですが、とりあえず順に。
このクラスは AutoFocusPreview を継承してます。

public class ParametersPreview extends AutoFocusPreview {
    private Activity activity;
    private Bundle bundle;
    private boolean isUsingCamera;

    ParametersPreview(Context context) {
        super(context);
	activity = (Activity)context;
    }

activity という属性については

  • SharedPreferences 取得とか
  • Intent 作ったりとか
  • startActivityForResult 呼び出したりとか

という諸々の手続き呼び出しに必要。bundle という属性は上でがっつり出てきた通り、サブ画面との情報のやりとりで大活躍です。
では以降で順にインスタンスメソド単位で中身を確認してみます。

surfaceCreated

ええと、以下に順に列挙します。

  • super.surfaceCreated の呼び出し (CameraPreview クラス)
  • Camera.Paremeters なオブジェクトの取得 (Camera#getParameters メソド)
  • SharedPreferences なオブジェクトの取得
  • bundle の準備
    • bundle が null であればオブジェクト生成して SharedPreferences から値を取得
    • 値を取得というか設定されていれば値を取得、ですね
    • 値を取得して bundle に設定しております
  • Camera.Parameters 方面への設定
    • 値が設定されていれば set* なメソドを呼び出して値を設定して
      Camera#setParameters を呼び出す

よく考えたらスデに Preferences に設定エントリがあったら、それをセットしとかなきゃいけないんですよね。なかなかに素晴しい。

surfaceDestroyed

これはスデにコード全体を引用してました。機能としては以下ですね。

  • CameraPreferences 表示時の呼び出しであれば Camera#stopPreview するのみ
  • 上記以外であれば super.surfaceDestroyed を呼び出す (これも CameraPreview クラスで実装されてるソレになるのか)

onTouchEvent

この手続きの中では画面を 4 分割して左上部分をタッチしたかどうかで処理が異なります。

  • 左上の場合、CameraPreferences な Activity を kick してます
    • Intent に bundle を put してます
    • isUsingCamera を true にしてます (surfaceDestroyed 呼び出し時のナニ)
    • startActivityForResult 呼び出し (戻りな処理が必要)
  • 左上以外の場合、autoFocus メソド呼び出して撮影

setCameraParameters メソドについては略。

CameraPreferences

この Activity は PreferencesActivity を継承してます。onCreate 一発です。で、res/xml/parameters.xml が以下なカンジ。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
		xmlns:android="http://schemas.android.com/apk/res/android">
	<ListPreference
			android:key="antibanding"
			android:title="@string/antibanding"
			android:summary="@string/unknown"
			android:entries="@array/antibanding_array"
			android:entryValues="@array/antibanding_array"
			android:dialogTitle="@string/antibanding" />
	<ListPreference
			android:key="effect"
			android:title="@string/effect"
			android:summary="@string/unknown"
			android:entries="@array/effect_array"
			android:entryValues="@array/effect_array"
			android:dialogTitle="@string/effect" />
	<ListPreference
			android:key="flash_mode"
			android:title="@string/flash_mode"
			android:summary="@string/unknown"
			android:entries="@array/flash_mode_array"
			android:entryValues="@array/flash_mode_array"
			android:dialogTitle="@string/flash_mode" />
	<ListPreference
			android:key="focus_mode"
			android:title="@string/focus_mode"
			android:summary="@string/unknown"
			android:entries="@array/focus_mode_array"
			android:entryValues="@array/focus_mode_array"
			android:dialogTitle="@string/focus_mode" />
	<ListPreference
			android:key="scene_mode"
			android:title="@string/scene_mode"
			android:summary="@string/unknown"
			android:entries="@array/scene_mode_array"
			android:entryValues="@array/scene_mode_array"
			android:dialogTitle="@string/scene_mode" />
	<ListPreference
			android:key="white_balance"
			android:title="@string/white_balance"
			android:summary="@string/unknown"
			android:entries="@array/white_balance_array"
			android:entryValues="@array/white_balance_array"
			android:dialogTitle="@string/white_balance" />
</PreferenceScreen>

で、onCreate メソドでしてるのは以下か。

  • super.onCreate 呼び出し
  • getIntent メソドにより Intent なオブジェクト (前画面で設定) 取得
  • addPreferencesFromResource メソドにより、上記な xml を読み込み
  • ListPreference なオブジェクトを findPreference メソドで取得
    • 引数に渡すのは xml で key な属性の文字列で OK
  • OnPreferenceChangeListener なオブジェクトを生成して listener 属性にセット
    • 無名オブジェクトを作って onPreferenceChange メソドを実装
    • 中身としてはどの設定かを判断して intent.putExtra して preference 方面にも setSummary した後に setResult メソドを呼び出しています
  • それぞれの ListPreference なオブジェクトに対して 上記な listener オブジェクトを setOnPreferenceChangeListener メソドを呼び出し
  • intent にそれぞれの設定が格納されていたら listener.onPreferenceChange メソドが呼び出されるような処理を handler.post で UI thread に投入

setResult って何回呼び出しても良いんですね。初めて知りました。OnPreferenceChangeListener はこーゆーケイスで上手に使うと面白いですね。

終わりに

細かい部分を相当スルーしてるのですが、参考になれば幸いです。