Android 嵌入式照片选择器,让体验更加丝滑

823 阅读4分钟

许多应用程序希望在选择照片或视频时为用户提供高度集成和无缝的体验。Embedded Photo Picker(嵌入式照片选择器)为用户提供了一种无缝且注重隐私的方式来选择照片和视频,并且就在你的应用界面内。


为什么需要官方照片选择器?

传统方案的痛点

过去,App 要让用户选择照片,通常有两种方式:

方式一:申请存储权限

// 需要声明危险权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

问题是:用户一旦授权,App 就能访问所有照片。这既是隐私风险,也让很多用户直接拒绝授权。

方式二:自己实现选择器

// 自建选择器需要大量代码
class CustomPhotoPickerActivity : AppCompatActivity() {
    // 需要处理权限、加载图片、分页、缩略图、多选...
    // 还要适配不同厂商的相册结构
    // 维护成本极高
}

问题是:开发成本高,用户体验参差不齐,而且仍然需要权限

官方方案的优势

特性自建方案官方 Photo Picker
需要权限✅ 必须❌ 完全不需要
云端照片❌ 仅本地✅ Google Photos
开发成本极低
用户信任高(系统级UI)
安全性自行保证系统沙箱保护

核心理念:App 只能访问用户明确选择的那几张照片,其他照片完全看不到。


嵌入式 vs 标准照片选择器

Google 其实早在 Android 13 就推出了 Photo Picker,但那是弹窗式的——用户点击后会跳转到系统界面选择。

新的 Embedded Photo Picker 是革命性的升级:

┌─────────────────────────────────────┐
│  你的 App 界面                       │
│  ┌─────────────────────────────────┐│
│  │  消息输入框...                   ││
│  └─────────────────────────────────┘│
│  ┌─────────────────────────────────┐│
│  │  📷  📷  📷    ← 嵌入式选择器    ││
│  │  📷  📷  📷       就在这里!     ││
│  └─────────────────────────────────┘│
└─────────────────────────────────────┘

核心差异:

特性标准 Photo Picker嵌入式 Photo Picker
交互方式跳转系统界面嵌入 App 内部
App 状态进入后台暂停保持前台活跃
选择体验单次选择完成可持续选择/取消
UI 定制无法定制支持主题色
实时反馈选完才知道实时更新

Google Messages 已经采用了这个方案,体验非常丝滑——照片选择器就在输入框下方,选中的照片实时显示缩略图,取消选择后缩略图立即消失。


如何接入?

设备要求

  • Android 14(API 34)或更高
  • SDK Extensions 版本 15+
  • 不满足条件的设备自动回退到标准 Photo Picker

添加依赖

// build.gradle.kts

// Jetpack Compose 项目(推荐)
implementation("androidx.photopicker:photopicker-compose:1.0.0-alpha01")

// 传统 Views 项目
implementation("androidx.photopicker:photopicker:1.0.0-alpha01")

Compose 实现

@Composable
fun MessageScreen() {
    val coroutineScope = rememberCoroutineScope()
    val pickerState = rememberEmbeddedPhotoPickerState()

    // 已选择的照片
    var selectedUris by remember { mutableStateOf(emptyList<Uri>()) }

    Column {
        // 你的消息列表
        MessageList()

        // 输入框
        MessageInput()

        // 嵌入式照片选择器
        EmbeddedPhotoPicker(
            state = pickerState,
            modifier = Modifier
                .fillMaxWidth()
                .height(300.dp),
            onUriPermissionGranted = { uris ->
                // 用户选择了新照片
                selectedUris = selectedUris + uris
            },
            onUriPermissionRevoked = { uris ->
                // 用户取消选择
                selectedUris = selectedUris - uris.toSet()
            },
            onSelectionComplete = {
                // 用户完成选择(可选)
            }
        )
    }
}

自定义主题色

val featureInfo = EmbeddedPhotoPickerFeatureInfo.Builder()
    .setAccentColor(0xFF6200EE.toInt())  // 你的品牌色
    .build()

EmbeddedPhotoPicker(
    state = pickerState,
    embeddedPhotoPickerFeatureInfo = featureInfo,
    // ...
)

⚠️ 注意:主题色必须完全不透明,亮度值需在 0.05 ~ 0.9 之间。

Views 实现

如果你的项目还在用传统 Views:

布局文件:

<androidx.photopicker.EmbeddedPhotoPickerView
    android:id="@+id/photoPicker"
    android:layout_width="match_parent"
    android:layout_height="300dp" />

Activity 代码:

class MainActivity : AppCompatActivity() {

    private lateinit var picker: EmbeddedPhotoPickerView
    private var session: EmbeddedPhotoPickerSession? = null

    private val listener = object : EmbeddedPhotoPickerStateChangeListener {

        override fun onSessionOpened(newSession: EmbeddedPhotoPickerSession) {
            session = newSession
        }

        override fun onUriPermissionGranted(uris: List<Uri>) {
            // 处理新选择的照片
            handleSelectedPhotos(uris)
        }

        override fun onUriPermissionRevoked(uris: List<Uri>) {
            // 处理取消选择
            removePhotos(uris)
        }

        override fun onSelectionComplete() {
            // 隐藏选择器(可选)
        }

        override fun onSessionError(throwable: Throwable) {
            // 错误处理
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        picker = findViewById(R.id.photoPicker)
        picker.addEmbeddedPhotoPickerStateChangeListener(listener)
    }
}

image.png

image.png


高级用法

同步取消选择状态

当用户在你的 UI 中移除已选照片时,需要通知选择器同步状态:

// Compose
coroutineScope.launch {
    pickerState.deselectUris(urisToRemove)
}

// Views
session?.requestRevokeUriPermission(urisToRemove)

处理配置变更

// 屏幕旋转时
session?.notifyConfigurationChanged(newConfig)

// 展开/收起状态
session?.notifyPhotoPickerExpanded(isExpanded)

// 尺寸变化
session?.notifyResized(newWidth, newHeight)

检测设备支持

fun isEmbeddedPickerAvailable(): Boolean {
    return Build.VERSION.SDK_INT >= 34 &&
           SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15
}

最佳实践

1. 把选择器当作独立元素设计

由于安全原因,系统会阻止任何覆盖在选择器上的 UI 元素。设计时要把它当作一个「神圣不可侵犯」的区域。

2. 提供回退方案

@Composable
fun PhotoPickerWrapper() {
    if (isEmbeddedPickerAvailable()) {
        EmbeddedPhotoPicker(/* ... */)
    } else {
        // 回退到标准 Photo Picker
        StandardPhotoPickerButton(/* ... */)
    }
}

3. 实时反馈用户选择

// 在输入框上方显示已选照片缩略图
LazyRow {
    items(selectedUris) { uri ->
        AsyncImage(
            model = uri,
            modifier = Modifier
                .size(60.dp)
                .clickable {
                    // 点击可移除
                    coroutineScope.launch {
                        pickerState.deselectUris(listOf(uri))
                    }
                }
        )
    }
}

总结

Embedded Photo Picker 的核心价值:

  1. 零权限 —— 彻底告别「请求访问所有照片」
  2. 云端集成 —— 用户的 Google Photos 也能选
  3. 原生体验 —— 嵌入 App,不再跳转
  4. 开发简单 —— 几行代码搞定

这不仅是 API 的更新,更是 Android 隐私保护理念的又一次进化。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连!有问题也欢迎在评论区交流 👇