🦁

【Camera2】画面全幅カスタムカメラアプリの実装

に公開

CameraActivity - Android Camera2 API 実装

概要

本ファイルは、Camera2 API・SurfaceView を用いた Android カメラ画面の実装例です。
以下の要件を満たしています:

  • プレビューを16:9のアスペクト比で表示(縦幅に合わせ、横ははみ出し)
  • 撮影ボタンを画面下に配置
  • 白い横長の枠線(クレジットカード比率)をオーバーレイで表示
  • 正方形ボタンを画面上部に配置

CameraActivity.kt

package com.example.cameraapp

import android.app.Activity
import android.hardware.camera2.*
import android.os.Bundle
import android.util.Size
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity

class CameraActivity : AppCompatActivity() {

    private lateinit var surfaceView: SurfaceView
    private lateinit var cameraManager: CameraManager
    private var cameraDevice: CameraDevice? = null
    private var previewSession: CameraCaptureSession? = null
    private lateinit var captureRequestBuilder: CaptureRequest.Builder
    private lateinit var previewSize: Size

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)

        surfaceView = findViewById(R.id.surfaceView)
        cameraManager = getSystemService(CAMERA_SERVICE) as CameraManager

        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                openCamera()
            }

            override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                cameraDevice?.close()
            }
        })
    }

    private fun openCamera() {
        val cameraId = cameraManager.cameraIdList.first()
        val stateCallback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cameraDevice = camera
                startPreview()
            }

            override fun onDisconnected(camera: CameraDevice) {
                camera.close()
            }

            override fun onError(camera: CameraDevice, error: Int) {
                camera.close()
            }
        }
        cameraManager.openCamera(cameraId, stateCallback, null)
    }

    private fun startPreview() {
        val surface = surfaceView.holder.surface
        cameraDevice?.let { camera ->
            captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureRequestBuilder.addTarget(surface)

            camera.createCaptureSession(listOf(surface), object : CameraCaptureSession.StateCallback() {
                override fun onConfigured(session: CameraCaptureSession) {
                    previewSession = session
                    captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
                    session.setRepeatingRequest(captureRequestBuilder.build(), null, null)
                }

                override fun onConfigureFailed(session: CameraCaptureSession) {}
            }, null)
        }
    }
}

res/layout/activity_camera.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />

    <View
        android:id="@+id/cardOverlay"
        android:layout_width="300dp"
        android:layout_height="189dp"
        android:layout_gravity="center"
        android:background="@drawable/overlay_border" />

    <Button
        android:id="@+id/shutterButton"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="32dp"
        android:background="@drawable/shutter_button_background" />

    <Button
        android:id="@+id/topButton"
        android:layout_width="56dp"
        android:layout_height="56dp"
        android:layout_gravity="top|end"
        android:layout_margin="16dp"
        android:text="" />
</FrameLayout>

res/drawable/overlay_border.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@android:color/transparent" />
    <stroke
        android:width="2dp"
        android:color="@android:color/white" />
</shape>

res/drawable/shutter_button_background.xml

<item>
    <shape android:shape="oval">
        <solid android:color="#FFFFFF" />
        <stroke
            android:width="3dp"
            android:color="#CCCCCC" />
    </shape>
</item>

備考

  • SurfaceView のアスペクト比調整には LayoutParams または ConstraintLayout が有効です。
  • 撮影処理や保存処理は CameraCaptureSession に追加してください。

Discussion