Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

新し目のバージョンの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が複雑で面倒だったので途中で断念してしまった
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした