6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Androidバージョンの違いをCustomViewで吸収する

Last updated at Posted at 2015-06-01

新し目のバージョンのAndroidにしか無いViewを使いたいが、xmlをバージョンで分けたりしたくない
そこで、そういったViewをラップするCustomViewを作る

今回、ICS以上でTextureView、それ以下でSurfaceViewを使ったカメラを実装した

リポジトリ

https://github.com/petitviolet/MultiCamera
記事中のコードはこのリポジトリの一部抜粋となっています

ライブラリとして使用

Githubにaarをアップロードしたので、以下のように記述するとライブラリとして使用できます

build.gradle
repositories {
    maven {
        url "https://raw.githubusercontent.com/petitviolet/MultiCamera/master/repository/"
    }
}

dependencies {
    compile "net.petitviolet:multicamera:0.1.0"
}

実装方法

Androidバージョンに関係なくlayoutファイルでCamera領域を指定できるように親となるCustomViewを作る

layoutでは普通のViewと同様に指定する

<net.petitviolet.viewsample.view.CameraView
    android:id="@+id/camera"
    android:layout_width="match_parent"
    android:layout_height="500dp"/>

CustomViewの実装

CameraViewは以下の様な感じ

CameraView.java

public class CameraView extends FrameLayout {
    private static final String TAG = CameraView.class.getSimpleName();
    protected Context mContext;
    protected View mView;
    protected Camera mCamera;

    public CameraView(Context context) {
        super(context);
        mContext = context;
    }

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public CameraView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
    }


    protected void setView() {
        // Must be Override
    }

    public void show() {
        Log.d(TAG, "show");
        // Must be Override
    }
    
    public CameraView initView() {
        ViewGroup parentView = (ViewGroup) getParent();
        int position = parentView.indexOfChild(this);
        CameraView newView;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            newView = new CameraSurfaceView(mContext);
        } else {
            newView = new CameraTextureView(mContext);
        } 
        newView.setLayoutParams(getLayoutParams());
        newView.setId(getId());
        newView.setView();

        parentView.removeViewAt(position);
        parentView.addView(newView, position);
        newView.setView();
        return newView;
    }
}

このViewの肝はinitViewで、Androidバージョンに合わせたView、この場合はCameraSurfaceViewCameraTextureViewのインスタンスを新しく作成し、layoutの中で自分が置かれた場所から自分自身と入れ替えるようにしている
setViewおよびshowは、CameraViewでは何も実装がないが、abstract classではlayoutに配置できないため、空のメソッドを置いて継承したクラスで実装することとなる

継承したCameraSurfaceViewは以下

CameraSurfaceView
public class CameraSurfaceView extends CameraView {
    private static final String TAG = CameraSurfaceView.class.getSimpleName();
    private SurfaceView mSurfaceView;

    public CameraSurfaceView(Context context) {
        super(context);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void setView() {
        mView = LayoutInflater.from(mContext).inflate(R.layout.camera_surface, this, false);
        addView(mView);
        mSurfaceView = (SurfaceView) mView.findViewById(R.id.camera_surface);
    }

    @Override
    public void show() {
        // カメラの準備
        SurfaceHolder holder = mSurfaceView.getHolder();
        holder.addCallback(callback);
        Log.d(TAG, "show");
    }
    
    public void takePicture(TakePictureCallback callback) {
        // ...
    }
    
    public interface TakePictureCallback {
        void onSuccess(byte[] data);
        void onFail();
    }
}

setViewでlayoutをinflateして作ったViewをaddViewして自分自身のViewを構築している
inflateするxmlはSurfaceViewを置いてあるだけの簡単なものにしている

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <SurfaceView
        android:id="@+id/camera_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

また、CameraTexureViewにおけるsetViewshowTextureViewを初期化している

CameraTextureView
    @Override
    protected void setView() {
        mView = LayoutInflater.from(mContext).inflate(R.layout.camera_texture, this, false);
        addView(mView);
        mTextureView = (TextureView) mView.findViewById(R.id.camera_texture);
    }

    @Override
    public void show() {
        Log.d(TAG, "show");
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }

使う側の実装

findViewByIdで取得したCameraViewinitViewをcallして、CamereSurfaceViewCameraTextureViewの適切な方を生成する
この場合はその後にCameraの起動などを行うshowメソッドを呼ぶことでカメラとして使用できるようになる

MainActivity
public class MainActivity extends AppCompatActivity {
    CameraView cameraView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        cameraView = ((CameraView) findViewById(R.id.camera)).initView();

        findViewById(R.id.take_picture).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cameraView.takePicture(new CameraView.TakePictureCallback() {
                    @Override
                    public void onSuccess(byte[] data) {
                        Log.d("MainActivity", "onSuccess");        
                    }

                    @Override
                    public void onFail() {
                        Log.d("MainActivity", "onFail");
                    }
                });
            }
        });
        cameraView.show();
    }    
}    

所感

  • バージョンによる分岐処理をViewの中に隠蔽できるため、使う側で考えなくて良いというのが個人的には気に入っている
    • ライブラリとしてCustomViewを提供する場合に便利だと思う
  • layoutに使用するViewとしてabstract classを使えるようになると、空メソッドを置かずにabstractメソッドとして用意できるためもう少し綺麗に書けそうだが、実現はしない気がする
  • Lollipop以上でCamera2を使った実装をしたかったが、Camera2が複雑で面倒だったので途中で断念してしまった
6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?