手順
いや、これでもシンプルな方なんですよ。たぶん。すげえ大変そうだけど。
1、アプリからデバイスのカメラを立ち上げる
2、撮影した画像をデバイスのストレージに一時保存する
3、一時保存したファイルを読み込んで使う
という流れです。
なぜ画像を一時保存するかというと、カメラからそのまま画像を受け取ると低画質になるからです。
カメラとファイル保存の設定
manifests/AndroidManifests.xmlにカメラとストレージの仕様設定を追加。
manifest内のapplicationと同階層に以下を追加。
これはカメラのハードウェアと、カメラ権限、デバイスストレージの使用権限を設定しています。
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
application内のactivityと同階層に以下を追加。
アプリはデバイスのファイルに直接アクセスできず、ファイルプロバイダーを経由する必要があるので、ファイルプロバイダーを設定します。
authoritiesはなんでもいいと思いますが、後で使います。アプリのパッケージ名(MainActivity.ktsのトップに記載されている).fileproviderが推奨です。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.YourAppName.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
AndroidManifests.xml全体は以下のようになります。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.YourAppName"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.YourAppName">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.YourAppName.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
画像ファイルを一時保存するパスを設定する。
res/xml に file_paths.xml というファイルを作ります。
デバイスのPicturesディレクトリを指定します。
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="images"
path="Pictures"/>
</paths>
画像表示用のモジュールをインストール。
アプリのbuild.gradle.ktsに以下を追加します。
implementation("io.coil-kt:coil-compose:2.1.0")
カメラを立ち上げる
必要なモジュールをインポート。
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.example.cautionplaterecognition.ui.theme.YourAppNameTheme
import java.io.File
import coil.compose.AsyncImage
import coil.request.ImageRequest
ActivityResultContract.TakePicture()というのを登録してlaunchすることでカメラを立ち上げます。successした場合、渡したuriに画像が保存されます。
Pictureディレクトリにファイルのパスを作り、ファイルプロバイダーでアクセスしてuriを取得します。
ファイルプロバイダーの名前はマニフェストファイルで設定したものと一致する必要があります。
private lateinit var photoFile: File
private var imageUri by mutableStateOf<Uri?>(null)
private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
imageUri = Uri.fromFile(photoFile)
}
}
private fun createImageFile(): File {
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${System.currentTimeMillis()}_",
".jpg",
storageDir
)
}
private fun takePhoto() {
photoFile = createImageFile()
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.example.cautionplaterecognition.fileprovider",
photoFile
)
takePictureLauncher.launch(photoURI)
}
UIに反映する
imageUriが変更されたら反映してくれるようにImageDisplayというComposableオブジェクトを作ります。
@Composable
fun ImageDisplay(imageUri: Uri) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = "Captured Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourAppNameTheme {
Box(modifier = Modifier.fillMaxSize()) {
imageUri?.let {
ImageDisplay(it)
}
Column(
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
if (checkAndRequestPermissions()) {
takePhoto()
}
}) {
Text("Take Photo")
}
}
}
}
}
}
ユーザーにカメラパーミッションを許可してもらう
カメラを使用するには、ユーザーにアプリ設定の権限を与えてもらう必要があります。
権限をチェックし、ない場合はポップアップでユーザーに依頼します(ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION))。
権限リクエストの結果が許可であれば、カメラを立ち上げ、許可されない場合はポップアップで設定を依頼します。
private fun checkAndRequestPermissions(): Boolean {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
// パーミッションが必要であることの説明を表示
Toast.makeText(this, "カメラのパーミッションが必要です。設定から許可してください。", Toast.LENGTH_LONG).show()
}
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
return false
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// パーミッションが許可されたらカメラを起動
takePhoto()
} else {
// パーミッションが拒否された場合
Toast.makeText(
this,
"カメラのパーミッションが必要です。設定から許可してください。",
Toast.LENGTH_SHORT
).show()
}
}
}
コード全体
package com.example.yourappname
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.example.cautionplaterecognition.ui.theme.YourAppNameTheme
import java.io.File
import coil.compose.AsyncImage
import coil.request.ImageRequest
class MainActivity : ComponentActivity() {
private lateinit var photoFile: File
private var imageUri by mutableStateOf<Uri?>(null)
companion object {
const val REQUEST_CAMERA_PERMISSION = 101
}
private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
imageUri = Uri.fromFile(photoFile)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourAppNameTheme {
Box(modifier = Modifier.fillMaxSize()) {
imageUri?.let {
ImageDisplay(it)
}
Column(
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
if (checkAndRequestPermissions()) {
takePhoto()
}
}) {
Text("Take Photo")
}
}
}
}
}
}
private fun takePhoto() {
photoFile = createImageFile()
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.example.yourappname.fileprovider",
photoFile
)
takePictureLauncher.launch(photoURI)
}
private fun createImageFile(): File {
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${System.currentTimeMillis()}_",
".jpg",
storageDir
)
}
private fun checkAndRequestPermissions(): Boolean {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
// パーミッションが必要であることの説明を表示
Toast.makeText(this, "カメラのパーミッションが必要です。設定から許可してください。", Toast.LENGTH_LONG).show()
}
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
return false
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// パーミッションが許可されたらカメラを起動
takePhoto()
} else {
// パーミッションが拒否された場合
Toast.makeText(
this,
"カメラのパーミッションが必要です。設定から許可してください。",
Toast.LENGTH_SHORT
).show()
}
}
}
}
@Composable
fun ImageDisplay(imageUri: Uri) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = "Captured Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Inside
)
}
🐣
フリーランスエンジニアです。
AIについて色々記事を書いていますのでよかったらプロフィールを見てみてください。
もし以下のようなご要望をお持ちでしたらお気軽にご相談ください。
AIサービスを開発したい、ビジネスにAIを組み込んで効率化したい、AIを使ったスマホアプリを開発したい、
ARを使ったアプリケーションを作りたい、スマホアプリを作りたいけどどこに相談したらいいかわからない…
いずれも中間コストを省いたリーズナブルな価格でお請けできます。
お仕事のご相談はこちらまで
rockyshikoku@gmail.com
機械学習やAR技術を使ったアプリケーションを作っています。
機械学習/AR関連の情報を発信しています。