ActivityResultContracts的TakePicture始终返回false作为结果。

huangapple go评论60阅读模式
英文:

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

我假设你想在应用程序内部的某些临时任务中拍照,为了实现这个目标,你需要了解你的代码中缺少的一些步骤:

  1. 由于你需要创建一个 File 并将其提供给相机应用程序,你需要使用 File Provider 创建规则并在 Manifest 文件中声明它。
  2. 创建一个 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:

  1. 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 the Manifest file.
  2. The function that creates a File and exposes its Uri with the FileProvider.

Lets start with file_paths.xml (inside res/xml):

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;paths&gt;
    &lt;cache-path
        name=&quot;cache_pictures&quot;
        path=&quot;/&quot; /&gt;

&lt;/paths&gt;

I'm using cache-path here following the idea of keeping the files temporarily.

In the Manifest file:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.CAMERA&quot; /&gt;

    &lt;application ...&gt;

        &lt;activity .../&gt;

        &lt;provider
            android:name=&quot;androidx.core.content.FileProvider&quot;
            android:authorities=&quot;${applicationId}.provider&quot;
            android:exported=&quot;false&quot;
            android:grantUriPermissions=&quot;true&quot;&gt;
            &lt;meta-data
                android:name=&quot;android.support.FILE_PROVIDER_PATHS&quot;
                android:resource=&quot;@xml/file_paths&quot; /&gt;
        &lt;/provider&gt;

    &lt;/application&gt;

&lt;/manifest&gt;

Extension to create a File and return a Uri:

fun Context.createTempPictureUri(
    provider: String = &quot;${BuildConfig.APPLICATION_ID}.provider&quot;,
    fileName: String = &quot;picture_${System.currentTimeMillis()}&quot;,
    fileExtension: String = &quot;.png&quot;
): 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 -&gt;
            if (success) currentPhotoUri = tempPhotoUri
        }
    )

    val cameraPermissionState = rememberPermissionState(
        permission = Manifest.permission.CAMERA,
        onPermissionResult = { granted -&gt;
            if (granted) {
                tempPhotoUri = context.createTempPictureUri()
                cameraLauncher.launch(tempPhotoUri)
            } else print(&quot;camera permission is denied&quot;)
        }
    )

    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 = &quot;Take a photo with Camera&quot;)
        }
    }
}

huangapple
  • 本文由 发表于 2023年2月8日 22:37:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75387353.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定