Android用カメラライブラリのCameraKitを使ってみた

![](https://github.com/CameraKit/camerakit-android/raw/master/.repo/gh-readme-header.svg?sanitize=true "")

# はじめに
AndroidのcameraApiって分かりづらい、とっつきにくいと思う方は多いのではないでしょうか?
CameraとCamera2があったり、SurfaceViewが必要だったり・・・
普通に実装しようとしても僕のようなへっぽこエンジニアには正直つらいです
ある日見つけたCameraKitというライブラリが良い感じだったので紹介します


# いいところ
- viewに配置したCameraKitView中心に実装できる

# 実装サンプル
- 撮影して、保存するところまでやってみます

**注:beta3.9の情報のため、仕様は変更になっている可能性があります**


## gradle

```
implementation 'com.camerakit:camerakit:1.0.0-beta3.9'
implementation 'com.camerakit:jpegkit:0.1.0'
```

## 権限取得
- [PermissionsDispatcher](https://github.com/permissions-dispatcher/PermissionsDispatche)にお世話になりました

```kotlin

@RuntimePermissions
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (savedInstanceState == null) {
            saveWithPermissionCheck()
        }
    }

    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
    fun save() {
        supportFragmentManager.beginTransaction().add(R.id.container, CameraFragment()).commit()
    }

    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
    fun showDeniedForWriteExternalStorage() {
        // 権限を許可されなかったとき
        when {
            PermissionUtils.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) -> {
                Toast.makeText(this, "カメラを使用する権限を取得できませんでした", Toast.LENGTH_SHORT).show()
            }
            PermissionUtils.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) -> {
                Toast.makeText(this, "撮影データを保存する権限を取得できませんでした", Toast.LENGTH_SHORT).show()
            }
        }
    }

    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
    fun showRationaleForWriteExternalStorage(request: PermissionRequest) {
        // 再度権限を要求した
        MaterialDialog(this)
            .message(text = "このアプリのご利用には、カメラの使用とデータ保存のための権限を取得する必要があります")
            .show {
                positiveButton(text = "許可") { dialog ->
                    request.proceed()
                }
                negativeButton(text = "今はしない")
            }
    }

    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
    fun showNeverAskForWriteExternalStorage() {
        // 今後表示しないを選択した
        MaterialDialog(this)
            .title(text = "カメラを利用できません")
            .message(text = "カメラを使用するためには、設定画面 > 許可からご自身で権限を許可して頂く必要があります。")
            .show {
                positiveButton(text = "設定画面へ") {
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    intent.data = Uri.fromParts("package", packageName, null)
                    startActivity(intent)
                }
                negativeButton(text = "今はしない")
            }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        onRequestPermissionsResult(requestCode, grantResults)
    }
}
```

- activity_main

```xml
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
    />

</RelativeLayout>
```

## CameraViewの設定
- 権限を取得できたら、CameraViewを保持するFragmentを生成します

```kotlin

class CameraFragment : Fragment() {
    private val df = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault())
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_camera, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        captureButton.setOnClickListener {
            cameraView.captureImage { _, bytes ->
                val currentTimeMillis = System.currentTimeMillis()
                val datetime = df.format(Date(currentTimeMillis))
                val savedPhoto = File(Environment.getExternalStorageDirectory(), "$datetime-$currentTimeMillis.jpg")
                try {
                    val outputStream = FileOutputStream(savedPhoto.path)
                    outputStream.write(bytes)
                    outputStream.close()
                } catch (e: java.io.IOException) {
                    e.printStackTrace()
                }
            }
        }
    }

    override fun onStart() {
        super.onStart()
        cameraView.onStart()
    }

    override fun onResume() {
        super.onResume()
        cameraView.onResume()
    }

    override fun onPause() {
        cameraView.onPause()
        super.onPause()
    }

    override fun onStop() {
        cameraView.onStop()
        super.onStop()
    }
}
```

- fragment_camera.xml

```xml
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
>

    <com.camerakit.CameraKitView
            android:id="@+id/cameraView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:keepScreenOn="true"
            app:camera_flash="off"
            app:camera_focus="auto"
            app:camera_imageJpegQuality="100"
    />

    <Button
            android:text="撮影ボタン"
            android:id="@+id/captureButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
    />
</RelativeLayout>
```

# 簡単に解説
- CameraKitViewを配置しておけばカメラの準備ができていて、プレビューはそこに表示され、任意のタイミングでそのイメージを取得できる
  - 直感的!!!
- カメラの権限のみであればpermissionDispacherを使わずに実装できるのですが、storageに関してうまくできなかったのでActivity側で事前に取得してしまう設計にしています
- 他にもいろいろプロパティがあり、ぱっと見で理解できそうなドキュメントになっているので困ったら[こちら](https://camerakit.io/docs)にどうぞ
- 知見ある方の情報お待ちしています m_ _m

# 関連リンク

- https://camerakit.io/
- https://github.com/CameraKit/camerakit-android