Androidのアプリ開発関連のブログ

また1ヶ月空いてしまったが・・・

結局また1ヶ月何も作らなかったが、再開する。
定番だが、QRリーダー作ってみようかと思ってたわけで、先月調べたところSurfaceViewというアニメーション等の更新が必要なものを表示するViewがあり、カメラのプレビューにはそれ使う。
というわけで、まずはSurfaceViewにカメラの表示。


AndroidManifest.xml
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.CAMERA"/>
この2行を、<manifest>の下(<application>の前に書いた)に書いとく。
<uses-permission>でアプリがカメラを使えるようにして、<uses-feature>を書いておくとアプリ配信時にカメラ無し端末はGooglePlayに表示されなくなる。


APIリファレンスだとカメラはandroid.hardware.Cameraが非推薦でandroid.hardware.camera2を使うように書かれているが、camera2はAPIレベル21。
API 21はAndroid 5.0かな?流石に要件厳しすぎるんでandroid.hardware.Cameraの方使うことに。


MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.hardware.Camera;

public class MainActivity extends Activity{
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final Camera camera;
        if(Camera.getNumberOfCameras()==1){
            camera=Camera.open();
        }else{
            Camera.CameraInfo ci=new Camera.CameraInfo();
            Camera.getCameraInfo(0,ci);
            if(ci.facing==ci.CAMERA_FACING_BACK){
                camera=Camera.open(0);
            }else{
                camera=Camera.open(1);
            }
        }
        SurfaceView sv=(SurfaceView)findViewById(R.id.sv);
        sv.getHolder().addCallback(new SurfaceHolder.Callback(){
            public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){
           
            }
            public void surfaceCreated(SurfaceHolder holder){
                try{
                    camera.setPreviewDisplay(holder);
                    camera.startPreview();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            public void surfaceDestroyed(SurfaceHolder holder){
           
            }
        });
    }
}

40行ほどになってしまったが、半分は背面カメラの判別コード。
最初のお試しにはカメラの判別はしなくていいと思うんで、もっと簡単。

layoutファイルでSurfaceViewを定義。
コード中でidでSurfaceViewを取得して、getHolder()でSurfaceHolderを取得。
addCallbackでコールバックを設定。
addCallbackの引数はSurfaceHolder.Callback一つで、SurfaceHolder.Callbackはイベントリスナ的なやつかな?surfaceChanged、surfaceCreated、surfaceDestroyedの3つを定義する。
今回はお試しなんで、surfaceCreatedだけ定義した。これがSurfaceView作成時に1回だけ呼びだされて、内容変更時にsurfaceChanged、破棄時にsurfaceDestroyedが呼び出される感じかな。

Camera.open()で1番目のカメラが起動、カメラ番号を引数に渡すこともできCamera.open(1)だと2番目になるのかな?
Camera.getNumberOfCameras()で端末のカメラ数を取得できる。

SurfaceView作成時に呼び出されるsurfaceCreatedでカメラのプレビューを開始してSurfaceViewに描画する。
起動中のカメラのsetPreviewDisplay(holder)でSurfaceHolderを設定。
startPreview()するだけ。
tryで例外処理する必要があるけど、簡単だね。


今回はカメラが複数ある場合は背面カメラを使い、一つの場合はそれを起動。としたかったわけだが、
Callback内(サブクラス内)でカメラの取得が必要そうなんで、final修飾子つけた。
finalの時はその行で初期化しないとダメなもんだと思ってたが、確実に定義されるなら次以降のif文等で定義することもできるんだね。
対象カメラの判別をしてから定義する必要があるんで、final変数をif文内で初期化にした。
カメラが複数の場合はforで全カメラを判別すべきな気もするが、for内でfinal変数の初期化はできない?やりかたわからん。
端末にカメラが3個以上付いているというのはありえないよね?2個あるなら1番か2番のカメラのどちらかが確実に背面だと思うんで、forじゃなくてif文にした。

カメラの前背面の判別は、Camera.CameraInfo()で判別できるんだが、なんかわかりにくい。
static void Camera.getCameraInfo(int cameraId, Camera.CameraInfo cameraInfo)
↑これ
Cameraのstaticメソッドを使うんだが、直接CameraInfoを返してくれる仕様ならわかりやすいが、voidなんだよね。
空のCameraInfoを自分で作成してgetCameraInfoの引数として渡す。そうすると渡したCameraInfoで情報を得ることができるって仕様。
背面なら、取得したCameraInfoのfacingが、Camera.CameraInfo.CAMERA_FACING_BACKになる。
前面なら、Camera.CameraInfo.CAMERA_FACING_FRONT。



そんなわけで、カメラを扱うのはできた。
QRの解析にはZxingって定番ぽいライブラリ使うつもりだが、プレビュー中のカメラから自動で定期的にビットマップを取得してZxingで処理する感じだね。
やる気出せばとりあえずのものは1日でできると思うが、やる気が出るか・・・