简单的Image Picker:使用Jetpack Compose无需权限申请

图片

学习如何在Android应用中轻松选择、存储和加载本地图像,无需繁琐的权限处理。 作为一名Android开发者,我知道在应用中实现本地图像选择时处理权限可能会让人感到沮丧。这就是为什么我想与你分享一种使用Kotlin和Jetpack Compose简化本地图像选择的方法。在本教程中,我将向你展示如何选择图像、将其URI存储在本地数据库中,并在用户下次打开应用程序时加载图像,而无需繁琐的权限处理。让我们开始吧!

创建Image Picker composable

首先,您需要添加coil依赖项,以便使用URI加载图像(您可以使用您熟悉的任何其他库),然后同步您的项目,以确保成功添加了依赖项。

implementation("io.coil-kt:coil-compose:2.4.0")
@Composable
fun ImagePicker() {
    var imageUri: Uri? by remember {
        mutableStateOf(null)
    }   
    val launcher =
        rememberLauncherForActivityResult(contract = ActivityResultContracts.OpenDocument()) {
            it?.let { uri ->
              imageUri = uri
            }
        }
    Column(
        modifier = Modifier
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically)
    ) {
        AsyncImage(
            model = imageUri,
            contentDescription = null,
            modifier = Modifier
                .size(200.dp),
            contentScale = ContentScale.Crop
        )
        Button(
            onClick = {
                launcher.launch(arrayOf("image/*"))
            }
        ) {
            Text(text = "Pick Image")
        }
    }
}

在提供的代码中,我们定义了ImagePicker组合函数,负责显示用户界面元素并处理图像选择逻辑。

首先,我们声明一个可变状态变量imageUri,用于保存所选图像的URI。

接下来,我们使用rememberLauncherForActivityResult定义了一个launcher。此launcher负责启动图像选择器活动并接收结果。我们使用ActivityResultContracts.OpenDocument()接口,允许用户从设备存储中选择文档(在本例中为图像文件)。当用户选择图像时,调用传递给launcher的lambda函数,并将选定的URI赋值给imageUri变量。

然后,我们定义了一个简单的界面来使用coil显示加载的图像,使用AsyncImage组合来显示选定的图像。我们将imageUri作为model参数传递,当imageUri更改时,它会自动加载和显示图像。为简单起见,contentDescription设置为null。

在图像下方,我们有一个按钮,触发图像选择过程。当点击按钮时,通过调用launcher.launch(arrayOf("image/*"))来启动launcher。这将打开图像选择器活动,允许用户从设备图库中选择图像。

图片

实现Image Url本地存储

为了简单起见,我们将使用DataStore来快速存储和加载图像URI,但你可以使用Room或其他你熟悉的本地存储方法。要使用DataStore,你需要添加以下依赖:

implementation 'androidx.datastore:datastore-preferences:1.0.0'

然后,我们将创建一个类来处理图像 URI 的存储和检索操作:

class StoreData {
    private val Context.storeData: DataStore<Preferences> by preferencesDataStore(name = "data")
    suspend fun storeImage(context: Context, value: String) {
        context.storeData.edit { preferences ->
            preferences[stringPreferencesKey("image")] = value
        }
    }
    suspend fun getImage(context: Context): Flow<String?> {
        return context.storeData.data.map {
            preferences ->
            preferences[stringPreferencesKey("image")]
        }
    }
}

首先,我们使用 preferencesDataStore 委托声明了一个类型为 DataStore<Preferences> 的 storeData 属性。该属性用于访问与提供的名称(”data”)相关联的 DataStore 实例。DataStore 允许我们持久地存储键值对。

接下来,我们有一个挂起函数 storeImage,它接受一个 Context 和一个值(图像 URI)作为参数。在函数内部,我们使用 storeData 的 edit 函数来修改偏好设置。我们使用键 “image” 将图像 URI 存储在偏好设置中。

类似地,我们还有另一个挂起函数 getImage,它接受一个 Context 作为参数。该函数返回一个 Flow<String?>,即可空字符串的流。在函数内部,我们使用 storeData 的 data 属性从中检索图像 URI。然后,我们通过映射偏好设置来提取与键 “image” 相关联的图像 URI 值。

通过使用这个 StoreData 类,您可以轻松地在本地存储中存储和检索图像 URI。

将Image Picker与存储整合

@Composable
fun ImagePicker() {
    var imageUri: Uri? by remember { mutableStateOf(null) }
    val context = LocalContext.current
    val dataStore = remember { StoreData() }
    val scope = rememberCoroutineScope()
    val launcher =
        rememberLauncherForActivityResult(contract = ActivityResultContracts.OpenDocument()) {
            it?.let { uri ->
                context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION )
                scope.launch {
                    dataStore.storeImage(context, uri.toString())
                }
            }
        }
    Column(
        modifier = Modifier
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically)
    ) {
        AsyncImage(
            model = imageUri,
            contentDescription = null,
            modifier = Modifier
                .size(200.dp),
            contentScale = ContentScale.Crop
        )
        Button(
            onClick = {
                launcher.launch(arrayOf("image/*"))
            }
        ) {
            Text(text = "Pick Image")
        }
    }
    LaunchedEffect(key1 = true) {
       dataStore.getImage(context).collect {
            if (it != null)
                imageUri = Uri.parse(it)
        }
    }
}

在提供的代码中,我们对 ImagePicker composable 进行了修改和添加,以将其与存储功能集成在一起。让我们逐步了解这些更改的目的。

  1. 1. 我们在 composable 内部获取了当前上下文的访问权限。
  2. 2. 使用 remember { StoreData() } 创建了 StoreData 类的实例。这将初始化存储类,使我们能够存储和检索图像 URI。
  3. 3. 创建了一个协程作用域,用于启动协程。
  4. 4. 当用户选择一个图像(不为 null)时,我们必须调用 takePersistableUriPermission 授予图像 URI 所需的读取权限。
  5. 5. 然后,我们使用 scope.launch 启动一个协程,使用 storeImage 函数将图像 URI 存储在 dataStore 中。
  6. 6. 使用 LaunchedEffect,它仅在初始组合时启动一次。
  7. 7. 在 LaunchedEffect 内部,我们使用 getImage(context).collect 从 dataStore 中收集图像 URI 的流。如果检索到的 URI 不为 null,我们将其解析为 Uri 对象并将其分配给 imageUri

通过实现这些修改和添加,ImagePicker composable 现在与存储功能集成在一起。它允许用户选择图像,在存储中自动保存选定的图像 URI,并在重新打开应用程序时加载图像 URI,为在 Android 应用程序中处理图像提供了无缝体验。

图片

缺点

在存储图像 URI 而没有对图像本身进行本地复制的方法中存在一个缺点,即如果原始图像被删除或从其原始位置移动,存储的 URI 将不再有效。这可能导致图像链接损坏,并在未来尝试加载图像时可能出现问题。

总结

总之,我们已经了解了如何使用 Kotlin 和 Jetpack Compose 轻松处理 Android 应用程序中的本地图像选择。通过集成用户友好的图像选择器和存储解决方案,我们消除了复杂的权限处理需求,同时实现了对图像 URI 的无缝存储和检索。这种方法简化了开发过程,增强了用户体验,使得创建能够无缝处理本地图像选择的吸引人应用程序变得更加容易。

GitHub

https://github.com/oussama-dz/LocalImagePicker
阅读原文


作者简介: Android技术达人 近10年一线开发经验 关注并分享Android、Kotlin新技术,新框架 多年Android底层框架修改经验,对Framework、Server、Binder等架构有深入理解。欢迎关注微信公众号:虎哥Lovedroid

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/202191.html

联系我们
联系我们
分享本页
返回顶部