產品最新消息

嵌入式相片挑選工具

8 分鐘小故事

嵌入式照片選擇器:在您的應用程式中以更方便的方式私密地要求照片和影片

photopicker.png

準備好透過一種令人興奮的新方式使用 Android 照片選擇器來提升您應用程式的使用者體驗吧!全新的嵌入式照片選擇器為用戶提供了一種無縫且注重隱私的方式,可以直接在您的應用程式介面中選擇照片和影片。現在,您的應用程式可以獲得與照片選擇器相同的所有優勢,包括存取雲端內容,這些優勢將直接整合到您的應用程式體驗中。

為什麼是嵌入式?

我們瞭解到,許多應用程式都希望在用戶選擇照片或影片時提供高度整合和無縫的體驗。內建的照片選擇器正是為此而設計的,使用戶無需離開您的應用程式即可快速存取最近的照片。他們還可以瀏覽其首選雲端媒體提供者(例如 Google Photos)中的完整圖庫,包括收藏夾、相簿和搜尋功能。這樣一來,用戶就不需要在應用程式之間切換,也不用擔心他們想要的照片是儲存在本地還是雲端。

完美整合,隱私權再升級

借助嵌入式照片選擇器,您的應用程式無需訪問用戶的照片或視頻,直到用戶實際選擇某些內容為止。這意味著用戶將擁有更高的隱私權,並獲得更流暢的用戶體驗。此外,嵌入式照片選擇器使用戶能夠存取其整個雲端媒體庫,而標準照片權限僅限於本機檔案。

Google 訊息內建的相片挑選器

Google Messages 充分展現了嵌入式照片選擇器的強大功能。以下是他們整合的方式:

  • 直覺的位置: 照片選擇器位於相機按鈕的正下方,使用戶可以清楚地選擇是拍攝新照片還是選擇現有照片。
  • 動態預覽: 使用者點擊照片後,會立即看到一個大預覽,方便使用者確認選擇。如果用戶取消選擇照片,預覽就會消失,從而保持介面簡潔清爽。
  • 展開查看更多內容: 初始視圖已簡化,方便存取最近的照片。不過,用戶可以輕鬆展開照片選擇器,瀏覽並選擇圖庫中的所有照片和視頻,包括來自 Google Photos 的雲端內容。
  • 尊重使用者選擇: 嵌入式照片選擇器僅授予對使用者選擇的特定照片或影片的存取權限,這意味著他們可以完全停止要求照片和影片權限。這樣也避免了「資訊」應用程式需要處理用戶僅授予有限照片和影片存取權限的情況。
gif1.gif
gif2.gif

正式推出

使用 Photo Picker Jetpack 庫 可以輕鬆整合嵌入式照片選擇器。  

Jetpack Compose

首先,將 Jetpack Photo Picker 函式庫作為依賴項新增進來。

implementation("androidx.photopicker:photopicker-compose:1.0.0-alpha01")

EmbeddedPhotoPicker 可組合函數提供了一種機制,可以將嵌入式照片選擇器 UI 直接包含在您的「撰寫」畫面中。這個可組合元件會建立一個 SurfaceView,用於承載嵌入式照片選擇器 UI。它管理與 EmbeddedPhotoPicker 服務的連接,處理用戶交互,並將選定的媒體 URI 傳遞給呼叫應用程式。  

@Composable
fun EmbeddedPhotoPickerDemo() {
    // We keep track of the list of selected attachments
    var attachments by remember { mutableStateOf(emptyList<Uri>()) }

    val coroutineScope = rememberCoroutineScope()
    // We hide the bottom sheet by default but we show it when the user clicks on the button
    val scaffoldState = rememberBottomSheetScaffoldState(
        bottomSheetState = rememberStandardBottomSheetState(
            initialValue = SheetValue.Hidden,
            skipHiddenState = false
        )
    )

    // Customize the embedded photo picker
    val photoPickerInfo = EmbeddedPhotoPickerFeatureInfo
        .Builder()
        // Set limit the selection to 5 items
        .setMaxSelectionLimit(5)
        // Order the items selection (each item will have an index visible in the photo picker)
        .setOrderedSelection(true)
        // Set the accent color (red in this case, otherwise it follows the device's accent color)
        .setAccentColor(0xFF0000)
        .build()

    // The embedded photo picker state will be stored in this variable
    val photoPickerState = rememberEmbeddedPhotoPickerState(
        onSelectionComplete = {
            coroutineScope.launch {
                // Hide the bottom sheet once the user has clicked on the done button inside the picker
                scaffoldState.bottomSheetState.hide()
            }
        },
        onUriPermissionGranted = {
            // We update our list of attachments with the new Uris granted
            attachments += it
        },
        onUriPermissionRevoked = {
            // We update our list of attachments with the Uris revoked
            attachments -= it
        }
    )

       SideEffect {
        val isExpanded = scaffoldState.bottomSheetState.targetValue == SheetValue.Expanded

        // We show/hide the embedded photo picker to match the bottom sheet state
        photoPickerState.setCurrentExpanded(isExpanded)
    }

    BottomSheetScaffold(
        topBar = {
            TopAppBar(title = { Text("Embedded Photo Picker demo") })
        },
        scaffoldState = scaffoldState,
        sheetPeekHeight = if (scaffoldState.bottomSheetState.isVisible) 400.dp else 0.dp,
        sheetContent = {
            Column(Modifier.fillMaxWidth()) {
                // We render the embedded photo picker inside the bottom sheet
                EmbeddedPhotoPicker(
                    state = photoPickerState,
                    embeddedPhotoPickerFeatureInfo = photoPickerInfo
                )
            }
        }
    ) { innerPadding ->
        Column(Modifier.padding(innerPadding).fillMaxSize().padding(horizontal = 16.dp)) {
            Button(onClick = {
                coroutineScope.launch {
                    // We expand the bottom sheet, which will trigger the embedded picker to be shown
                    scaffoldState.bottomSheetState.partialExpand()
                }
            }) {
                Text("Open photo picker")
            }
            LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 64.dp)) {
                // We render the image using the Coil library
                itemsIndexed(attachments) { index, uri ->
                    AsyncImage(
                        model = uri,
                        contentDescription = "Image ${index + 1}",
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.clickable {
                            coroutineScope.launch {
                                // When the user clicks on the media from the app's UI, we deselect it
                                // from the embedded photo picker by calling the method deselectUri
                                photoPickerState.deselectUri(uri)
                            }
                        }
                    )
                }
            }
        }
    }
}

Views

首先,將 Jetpack Photo Picker 函式庫作為依賴項新增進來。

implementation("androidx.photopicker:photopicker:1.0.0-alpha01")

要新增嵌入式照片選擇器,您需要在佈局檔案中新增一個條目。  

<view class="androidx.photopicker.EmbeddedPhotoPickerView"
    android:id="@+id/photopicker"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

並在您的 Activity/Fragment 中初始化它。

// We keep track of the list of selected attachments
private val _attachments = MutableStateFlow(emptyList<Uri>())
val attachments = _attachments.asStateFlow()

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

val pickerListener = object EmbeddedPhotoPickerStateChangeListener {
    override fun onSessionOpened (newSession: EmbeddedPhotoPickerSession) {
        openSession = newSession
    }

    override fun onSessionError (throwable: Throwable) {}

    override fun onUriPermissionGranted(uris: List<Uri>) {
        _attachments += uris
    }

    override fun onUriPermissionRevoked (uris: List<Uri>) {
        _attachments -= uris
    }

    override fun onSelectionComplete() {
        // Hide the embedded photo picker as the user is done with the photo/video selection
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_view)
    
    //
    // Add the embedded photo picker to a bottom sheet to allow the dragging to display the full photo library
    //

    picker = findViewById(R.id.photopicker)
    picker.addEmbeddedPhotoPickerStateChangeListener(pickerListener)
    picker.setEmbeddedPhotoPickerFeatureInfo(
        // Set a custom accent color
        EmbeddedPhotoPickerFeatureInfo.Builder().setAccentColor(0xFF0000).build()
    )
}

您可以呼叫 EmbeddedPhotoPickerSession 的不同方法來與嵌入式選擇器進行互動。

// Notify the embedded picker of a configuration change
openSession.notifyConfigurationChanged(newConfig)

// Update the embedded picker to expand following a user interaction
openSession.notifyPhotoPickerExpanded(/* expanded: */ true)

// Resize the embedded picker
openSession.notifyResized(/* width: */ 512, /* height: */ 256)

// Show/hide the embedded picker (after a form has been submitted)
openSession.notifyVisibilityChanged(/* visible: */ false)

// Remove unselected media from the embedded picker after they have been
// unselected from the host app's UI
openSession.requestRevokeUriPermission(removedUris)

需要注意的是,嵌入式照片選擇器功能僅適用於執行 Android 14(API 等級 34)或更高版本且 SDK Extensions 版本為 15+ 的使用者。 以瞭解更多關於照片選擇器裝置可用性的資訊

為了增強使用者隱私和安全,系統以一種防止任何繪製或覆蓋的方式呈現嵌入式照片選擇器。這種有意為之的設計選擇意味著,您的使用者體驗設計應該將照片選擇器的顯示區域視為一個獨立且專用的元素,就像您規劃廣告橫幅一樣。

如果您有任何回饋或建議,請提交工單至我們的問題追蹤器

繼續閱讀