Skip to content

画中画 (Picture-in-Picture)

源:画中画支持

画中画 (PiP) 是一种特殊的多窗口模式,最常用于视频播放。当用户按下 Home 键时,应用会缩小成一个小窗口悬浮在屏幕上。

1. 清单配置

xml
<activity
    android:name=".VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />

2. 进入 PiP 模式

在 Activity 中(或者通过 ContextWrapper),调用 enterPictureInPictureMode

kotlin
// 在 VideoPlayer Composable 中
val context = LocalContext.current
val activity = context as? Activity

// 监听 App 切到后台 (如按下 Home 键)
DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_PAUSE) {
            // 尝试进入 PiP 模式而不是暂停
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val params = PictureInPictureParams.Builder()
                    .setAspectRatio(Rational(16, 9))
                    .build()
                activity?.enterPictureInPictureMode(params)
            }
        }
    }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}

3. 适配 PiP UI

当应用处于 PiP 模式时,应该隐藏播放控制条等无关 UI,只保留视频画面。

我们可以监听 LocalConfiguration 并没有直接提供 PiP 状态,通常我们需要在 Activity 中监听 onPictureInPictureModeChanged 并通过 CompositionLocalState 传递给 Compose。

kotlin
// 1. 定义 LocalPipMode
val LocalPipMode = compositionLocalOf { false }

// 2. 在 Activity 中分发
class VideoActivity : ComponentActivity() {
    var isInPipMode by mutableStateOf(false)

    override fun onPictureInPictureModeChanged(
        isInPictureInPictureMode: Boolean,
        newConfig: Configuration
    ) {
        isInPipMode = isInPictureInPictureMode
    }

    override fun onCreate(...) {
        setContent {
            CompositionLocalProvider(LocalPipMode provides isInPipMode) {
                VideoScreen()
            }
        }
    }
}

// 3. 在 UI 中响应
@Composable
fun VideoScreen() {
    val inPip = LocalPipMode.current
    
    Box {
        VideoPlayer()
        
        // 如果不在 PiP 模式,显示控制条
        if (!inPip) {
            VideoControls()
        }
    }
}