这一节主要了解一下Compose中的图片选择器,图片选择器是一种允许用户从设备相册或文件系统中选择图片的组件,它简化了媒体文件访问流程,简单总结如下:
API:
ActivityResultContracts.GetMultipleContents()
用于从系统选择多个媒体文件的契约(Contract)。它允许用户通过系统文件选择器同时选择多张图片、视频或其他类型的文件,并返回这些文件的Uri列表。
ActivityResultContracts.PickMultipleVisualMedia()
Android13(API 33)引入的专用契约,用于从系统媒体库中多选图片或视频。它提供了更优化的媒体选择体验,自动处理权限请求,并支持按类型过滤。
栗子:
package com.example.test0121.imagepicker
import android.net.Uri
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.accompanist.permissions.ExperimentalPermissionsApi
@OptIn(ExperimentalPermissionsApi::class, ExperimentalLayoutApi::class)
@Composable
fun ImagePickerDemo() {
val context = LocalContext.current
val maxImages = 6
var selectedImages by remember { mutableStateOf<List<Uri>>(emptyList()) }
val isAndroid15OrHigher = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
val mediaPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickMultipleVisualMedia()
) { uris ->
selectedImages = uris
}
val legacyImagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents()
) { uris ->
selectedImages = uris.take(maxImages)
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "多图选择器测试",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 16.dp)
)
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (selectedImages.size < maxImages) {
Box(
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
.clickable {
if (isAndroid15OrHigher) {
mediaPickerLauncher.launch(
PickVisualMediaRequest(
mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly,
)
)
} else {
legacyImagePickerLauncher.launch("image/*")
}
},
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
painter = painterResource(android.R.drawable.ic_menu_camera),
contentDescription = "添加图片",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(30.dp)
)
Text(
text = "添加图片",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
selectedImages.forEachIndexed { index, uri ->
Box(modifier = Modifier.size(100.dp)) {
AsyncImage(
model = ImageRequest.Builder(context)
.data(uri)
.crossfade(true)
.build(),
contentDescription = "选择的图片 $index",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(8.dp))
)
IconButton(
onClick = { selectedImages = selectedImages.filterIndexed { i, _ -> i != index } },
modifier = Modifier
.size(28.dp)
.align(Alignment.TopEnd)
.background(Color.Black.copy(alpha = 0.5F), CircleShape)
.padding(2.dp)
) {
Icon(
painter = painterResource(android.R.drawable.ic_delete),
contentDescription = "删除",
tint = Color.White,
modifier = Modifier.size(16.dp)
)
}
}
}
}
Text(
text = "已选择 ${selectedImages.size}/$maxImages 张图片",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(top = 12.dp)
)
}
}
清单文件:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="QueryAllPackagesPermission" /> <!-- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" -->
<!-- tools:ignore="ScopedStorage" /> -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />