英文:
ActivityResultContracts TakePicture it is always returning false as a result
问题
使用Jetpack Compose时,当我调用使用相机拍照的方法时,ActivityResultContracts.TakePicture
的结果总是false。
示例代码:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val photoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) {
println("success")
println("photo uri: $photoUri")
} else println("result failed")
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) cameraLauncher.launch(photoUri)
else print("camera permission is denied")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "使用相机拍照")
}
}
}
我使用了accompanist-permissions
库来简化操作,打开相机应用并拍照的部分似乎正常工作,但cameraLauncher
的结果始终为false...
有人可以指导我解决这个问题吗?
英文:
I'm using Jetpack Compose, and when I call the method to take a picture with the camera, the result of ActivityResultContracts.TakePicture
is always false.
Sample code:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val photoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) {
println("success")
println("photo uri: $photoUri")
} else println("result failed")
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) cameraLauncher.launch(photoUri)
else print("camera permission is denied")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "Take a photo with Camera")
}
}
}
I used the accompanist-permissions
library to make it easier, the part of opening the camera app and taking the photo is apparently working normally, but the result from cameraLauncher
is always false...
Can anyone guide me to solve this problem?
答案1
得分: 6
你的代码问题在于你向 launch
传递一个空的 Uri,而相机应用程序无法将图像保存在该 Uri 上。如果你打开 TakePicture
类或将鼠标光标悬停在其上,你将看到以下信息:
> 一个 ActivityResultContract 用于拍摄照片并保存到提供的内容 Uri 中。如果图像保存到给定的 Uri 中,将返回 true。
换句话说,TakePicture
类不会自动为你创建一个 File
,你需要自己创建一个 File
并提供 Uri
。
我假设你想在应用程序内部的某些临时任务中拍照,为了实现这个目标,你需要了解你的代码中缺少的一些步骤:
- 由于你需要创建一个
File
并将其提供给相机应用程序,你需要使用 File Provider 创建规则并在Manifest
文件中声明它。 - 创建一个
File
并使用FileProvider
暴露其Uri
的函数。
让我们从 file_paths.xml
开始(在 res/xml 目录下):
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache_pictures"
path="/" />
</paths>
我在这里使用 cache-path
,遵循将文件暂时保存的思路。
在 Manifest
文件中:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<application ...>
<activity .../>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
创建一个 File
并返回一个 Uri
的扩展函数:
fun Context.createTempPictureUri(
provider: String = "${BuildConfig.APPLICATION_ID}.provider",
fileName: String = "picture_${System.currentTimeMillis()}",
fileExtension: String = ".png"
): Uri {
val tempFile = File.createTempFile(
fileName, fileExtension, cacheDir
).apply {
createNewFile()
}
return FileProvider.getUriForFile(applicationContext, provider, tempFile)
}
在这个示例中,使用了缓存文件夹 cacheDir
。如果在这里更改为 filesDir
,请确保在 file_paths.xml
中将 cache-path
更改为 files-path
。
现在在 Composable Screen
中,你可以有类似以下的内容:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val context = LocalContext.current
var currentPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) currentPhotoUri = tempPhotoUri
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) {
tempPhotoUri = context.createTempPictureUri()
cameraLauncher.launch(tempPhotoUri)
} else print("相机权限被拒绝")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(visible = currentPhotoUri.toString().isNotEmpty()) {
// 使用 Coil 库
AsyncImage(
modifier = Modifier.size(size = 240.dp),
model = currentPhotoUri,
contentDescription = null
)
}
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "使用相机拍照")
}
}
}
英文:
The problem with your code is that you are passing an empty Uri
to launch
, and the Camera app can't save the image in that Uri
. If you open the TakePicture
class or place the mouse cursor over it, you will see the following information:
> An ActivityResultContract to take a picture saving it into the
> provided content-Uri. Returns true if the image was saved into the
> given Uri.
In other words, the TakePicture
class will not automatically create a File
for you, you will have to create a File
yourself and provide the Uri
.
I'll assume a simple scenario where you want to take a photo for some temporary task within the app. To achieve this goal, you need to understand some steps that are missing in your code:
- As you need to create a
File
and expose it to the Camera app, you need to create rules with the File Provider and declare it in theManifest
file. - The function that creates a
File
and exposes itsUri
with theFileProvider
.
Lets start with file_paths.xml
(inside res/xml):
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache_pictures"
path="/" />
</paths>
I'm using cache-path
here following the idea of keeping the files temporarily.
In the Manifest
file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<application ...>
<activity .../>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
Extension to create a File
and return a Uri
:
fun Context.createTempPictureUri(
provider: String = "${BuildConfig.APPLICATION_ID}.provider",
fileName: String = "picture_${System.currentTimeMillis()}",
fileExtension: String = ".png"
): Uri {
val tempFile = File.createTempFile(
fileName, fileExtension, cacheDir
).apply {
createNewFile()
}
return FileProvider.getUriForFile(applicationContext, provider, tempFile)
}
The cache folder is being used in this example with cacheDir
. If changing here to filesDir
, be sure to change in the cache-path
on file_paths.xml
to files-path
.
Now in Composable Screen
you can have something like this:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val context = LocalContext.current
var currentPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) currentPhotoUri = tempPhotoUri
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) {
tempPhotoUri = context.createTempPictureUri()
cameraLauncher.launch(tempPhotoUri)
} else print("camera permission is denied")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(visible = currentPhotoUri.toString().isNotEmpty()) {
// from coil library
AsyncImage(
modifier = Modifier.size(size = 240.dp),
model = currentPhotoUri,
contentDescription = null
)
}
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "Take a photo with Camera")
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论