การแสดงภาพซ้อนภาพ (PIP) คือโหมดหลายหน้าต่างประเภทพิเศษที่ใช้สำหรับการเล่นวิดีโอเป็นส่วนใหญ่ ทำให้ผู้ใช้ดูวิดีโอในหน้าต่างขนาดเล็กที่ตรึงไว้ มุมของหน้าจอขณะไปยังแอปต่างๆ หรือเรียกดูเนื้อหาบน หน้าจอหลัก
PIP ใช้ประโยชน์จาก API หลายหน้าต่างที่มีให้ใช้งานใน Android 7.0 เพื่อให้บริการ หน้าต่างวางซ้อนวิดีโอที่ปักหมุดไว้ หากต้องการเพิ่ม PIP ลงในแอป คุณจะต้องลงทะเบียน กิจกรรม เปลี่ยนกิจกรรมเป็นโหมด PIP ตามความจำเป็น และตรวจสอบว่าองค์ประกอบ UI จะซ่อนไว้และเล่นวิดีโอต่อเมื่อกิจกรรมอยู่ในโหมด PIP
คำแนะนำนี้อธิบายวิธีเพิ่ม PIP ใน Compose ลงในแอปโดยใช้วิดีโอ Compose การใช้งานของคุณ ดูแอป Socialite เพื่อดูรายการเหล่านี้ที่ดีที่สุด การปฏิบัติจริง
ตั้งค่าแอปสำหรับ PiP
ในแท็กกิจกรรมของไฟล์ AndroidManifest.xml
ให้ทำดังนี้
- เพิ่ม
supportsPictureInPicture
และตั้งค่าเป็นtrue
เพื่อประกาศว่าคุณจะเป็น การใช้ PIP ในแอปของคุณ เพิ่ม
configChanges
แล้วตั้งค่าเป็นorientation|screenLayout|screenSize|smallestScreenSize
เพื่อระบุว่ากิจกรรมของคุณจัดการการเปลี่ยนแปลงการกําหนดค่าเลย์เอาต์ วิธีนี้จะทำให้กิจกรรมของคุณ จะไม่เปิดขึ้นอีกครั้งเมื่อเกิดการเปลี่ยนแปลงเลย์เอาต์ในระหว่างการเปลี่ยนโหมด PIP<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
ในโค้ดการเขียน ให้ทําดังนี้
- เพิ่มส่วนขยายนี้ใน
Context
คุณจะใช้ส่วนขยายนี้หลายครั้ง ตลอดทั้งคำแนะนำเพื่อเข้าถึงกิจกรรมinternal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
เพิ่ม PIP ในแอปออกจากแอปสำหรับ Android 12 ล่วงหน้า
หากต้องการเพิ่ม PiP สำหรับ Android ก่อนเวอร์ชัน 12 ให้ใช้ addOnUserLeaveHintProvider
ติดตาม
ขั้นตอนเหล่านี้เพื่อเพิ่ม PIP สำหรับก่อน Android 12
- เพิ่มเกตเวอร์ชันเพื่อให้โค้ดนี้เข้าถึงได้เฉพาะในเวอร์ชัน O จนถึง R
- ใช้
DisposableEffect
ที่มีContext
เป็นคีย์ - ใน
DisposableEffect
ให้กำหนดลักษณะการทำงานเมื่อonUserLeaveHintProvider
จะถูกทริกเกอร์โดยใช้ lambda ใน lambda ให้เรียกenterPictureInPictureMode()
ในfindActivity()
และผ่านไปPictureInPictureParams.Builder().build()
- เพิ่ม
addOnUserLeaveHintListener
โดยใช้findActivity()
แล้วข้ามผ่าน lambda - ใน
onDispose
ให้เพิ่มremoveOnUserLeaveHintListener
โดยใช้findActivity()
และส่งค่า Lambda
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
เพิ่ม PIP ในแอปออกจากแอปสำหรับโพสต์ Android 12
หลัง Android 12 จะมีการเพิ่ม PictureInPictureParams.Builder
ผ่าน
แป้นกดร่วมที่ส่งไปยังโปรแกรมเล่นวิดีโอของแอป
- สร้าง
modifier
แล้วเรียกonGloballyPositioned
ในนั้น เลย์เอาต์ พิกัดจะนำไปใช้ในขั้นตอนถัดไป - สร้างตัวแปรสำหรับ
PictureInPictureParams.Builder()
- เพิ่มคำสั่ง
if
เพื่อตรวจสอบว่า SDK เป็น S หรือสูงกว่า หากใช่ ให้เพิ่มsetAutoEnterEnabled
เป็นเครื่องมือสร้างและตั้งค่าเป็นtrue
เพื่อป้อน PIP เมื่อปัดนิ้ว วิธีนี้ทำให้ภาพเคลื่อนไหวลื่นไหลขึ้นกว่าการดูจนจบenterPictureInPictureMode
- ใช้
findActivity()
เพื่อโทรหาsetPictureInPictureParams()
โทรหาbuild()
ในbuilder
แล้วส่งต่อ
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
เพิ่ม PiP ผ่านปุ่ม
หากต้องการเข้าสู่โหมด PIP ผ่านการคลิกปุ่ม ให้โทรไปที่ enterPictureInPictureMode()
ใน findActivity()
พารามิเตอร์ได้รับการตั้งค่าไว้แล้วโดยการเรียกครั้งก่อนไปยัง
PictureInPictureParams.Builder
เพื่อที่คุณจะได้ไม่ต้องตั้งค่าพารามิเตอร์ใหม่
ในเครื่องมือสร้าง แต่ถ้าคุณต้องการเปลี่ยนพารามิเตอร์ใดๆ บนปุ่ม
ให้คลิก คุณสามารถตั้งค่าได้ที่นี่
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
จัดการ UI ในโหมด PIP
เมื่อเข้าสู่โหมด PIP แล้ว UI ทั้งหมดของแอปจะเข้าสู่หน้าต่าง PIP เว้นแต่คุณจะ ระบุว่า UI ควรมีลักษณะอย่างไรทั้งในและนอกโหมด PIP
ก่อนอื่น คุณต้องทราบว่าแอปอยู่ในโหมด PIP หรือไม่ คุณสามารถใช้
OnPictureInPictureModeChangedProvider
เพื่อให้บรรลุเป้าหมายนี้
โค้ดด้านล่างจะบอกคุณว่าแอปอยู่ในโหมด PIP หรือไม่
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
ตอนนี้คุณสามารถใช้ rememberIsInPipMode()
เพื่อเปิด/ปิดองค์ประกอบ UI ที่จะแสดงได้แล้ว
เมื่อแอปเข้าสู่โหมด PIP ให้ทำดังนี้
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
ตรวจสอบว่าแอปเข้าสู่โหมด PIP ในเวลาที่เหมาะสม
แอปของคุณไม่ควรเข้าสู่โหมด PIP ในสถานการณ์ต่อไปนี้
- หากวิดีโอหยุดหรือหยุดชั่วคราว
- หากคุณอยู่ในหน้าอื่นของแอปซึ่งไม่ใช่โปรแกรมเล่นวิดีโอ
หากต้องการควบคุมเวลาที่แอปเข้าสู่โหมด PIP ให้เพิ่มตัวแปรที่ติดตามสถานะ
ของโปรแกรมเล่นวิดีโอโดยใช้ mutableStateOf
สลับสถานะอิงตามว่าวิดีโอกำลังเล่นหรือไม่
หากต้องการสลับสถานะตามว่าวิดีโอเพลเยอร์กำลังเล่นอยู่หรือไม่ ให้เพิ่ม Listener ลงใน โปรแกรมเล่นวิดีโอ สลับสถานะของตัวแปรสถานะขึ้นอยู่กับว่าโปรแกรมเล่นวิดีโอ กำลังเล่นอยู่หรือไม่
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
สลับสถานะตามที่มีการเปิดใช้โปรแกรมเล่นหรือไม่
เมื่อปล่อยโปรแกรมเล่นวิดีโอแล้ว ให้กำหนดตัวแปรสถานะเป็น false
fun releasePlayer() { shouldEnterPipMode = false }
แสดงสถานะเพื่อระบุว่ามีการเข้าสู่โหมด PIP หรือไม่ (ก่อน Android 12)
- เนื่องจากการเพิ่ม PIP ก่อนปี 12 จะใช้
DisposableEffect
คุณจึงต้องสร้าง ตัวแปรใหม่โดยrememberUpdatedState
ที่ตั้งค่าnewValue
เป็น ตัวแปรสถานะ วิธีนี้จะช่วยให้มั่นใจได้ว่าระบบใช้เวอร์ชันที่อัปเดตแล้วภายในDisposableEffect
ใน Lambda ที่กําหนดลักษณะการทํางานเมื่อมีการเรียกใช้
OnUserLeaveHintListener
ให้เพิ่มคำสั่งif
ที่มีตัวแปรสถานะรอบการเรียกใช้enterPictureInPictureMode()
ดังนี้val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
แสดงสถานะเพื่อระบุว่ามีการเข้าสู่โหมด PIP หรือไม่ (หลัง Android 12)
ส่งตัวแปรสถานะไปยัง setAutoEnterEnabled
เพื่อให้แอปของคุณป้อนเฉพาะ
โหมด PIP ในช่วงเวลาที่เหมาะสม
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
ใช้ setSourceRectHint
เพื่อทำให้ภาพเคลื่อนไหวลื่นไหล
setSourceRectHint
API สร้างภาพเคลื่อนไหวที่ราบรื่นขึ้นสำหรับการเข้าสู่โหมด PIP ใน Android 12 ขึ้นไป แอปนี้จะสร้างภาพเคลื่อนไหวที่ราบรื่นยิ่งขึ้นสำหรับการออกจากโหมด PIP
เพิ่ม API นี้ลงในเครื่องมือสร้าง PIP เพื่อระบุพื้นที่ของกิจกรรม
ที่มองเห็นได้หลังจากเปลี่ยนไปเป็น PIP
- เพิ่ม
setSourceRectHint()
ในbuilder
เฉพาะเมื่อรัฐระบุว่า แอปควรเข้าสู่โหมด PIP การดำเนินการนี้จะหลีกเลี่ยงการคำนวณsourceRect
เมื่อแอป ไม่จำเป็นต้องป้อน PIP - หากต้องการตั้งค่า
sourceRect
ให้ใช้layoutCoordinates
ที่ได้จากฟังก์ชันonGloballyPositioned
ในตัวแก้ไข - โทรหา
setSourceRectHint()
บนbuilder
และโดยสารสายsourceRect
ตัวแปร
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
ใช้ setAspectRatio
เพื่อตั้งค่าสัดส่วนภาพของหน้าต่าง PIP
หากต้องการตั้งค่าสัดส่วนภาพของหน้าต่าง PIP คุณสามารถเลือก
หรือใช้ความกว้างและความสูงของขนาดวิดีโอของโปรแกรมเล่น หากคุณใช้โปรแกรมเล่น media3 ให้ตรวจสอบว่าโปรแกรมเล่นไม่ใช่ค่า Null และขนาดวิดีโอของโปรแกรมเล่นไม่เท่ากับ VideoSize.UNKNOWN
ก่อนตั้งค่าสัดส่วนการแสดงผล
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
หากคุณใช้เพลเยอร์ที่กำหนดเอง ให้ตั้งค่าสัดส่วนภาพสำหรับความสูงและความกว้างของเพลเยอร์โดยใช้ไวยากรณ์เฉพาะสำหรับเพลเยอร์ โปรดทราบว่าหากเพลเยอร์ปรับขนาดระหว่างการเริ่มต้น หากอยู่นอกขอบเขตที่ยอมรับได้ของอัตราส่วนภาพ แอปจะขัดข้อง คุณอาจต้องเพิ่มการตรวจสอบ เมื่อคำนวณสัดส่วนการแสดงผลได้ คล้ายกับที่ทำกับสื่อ 3 โปรแกรมเล่นวิดีโอ
เพิ่มการดำเนินการจากระยะไกล
หากต้องการเพิ่มการควบคุม (เล่น หยุดชั่วคราว ฯลฯ) ลงในหน้าต่าง PiP ให้สร้าง RemoteAction
สำหรับการควบคุมแต่ละรายการที่ต้องการเพิ่ม
- เพิ่มค่าคงที่สำหรับการควบคุมการออกอากาศ
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- สร้างรายการ
RemoteActions
สำหรับการควบคุมในหน้าต่าง PIP - จากนั้นเพิ่ม
BroadcastReceiver
และลบล้างonReceive()
เพื่อตั้งค่า ของแต่ละปุ่ม ใช้DisposableEffect
เพื่อลงทะเบียนตัวรับและการดําเนินการระยะไกล เมื่อกำจัดโปรแกรมเล่นแล้ว ให้ยกเลิกการลงทะเบียน รีซีฟเวอร์@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- ส่งรายการการดำเนินการระยะไกลไปยัง
PictureInPictureParams.Builder
:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
ขั้นตอนถัดไป
ในคู่มือนี้ คุณได้เรียนรู้แนวทางปฏิบัติแนะนำในการเพิ่ม PiP ใน Compose ทั้งก่อนและหลัง Android 12
- ดูแอป Socialite เพื่อดูแนวทางปฏิบัติที่ดีที่สุดของ การทำงานของการเขียน PIP
- ดูข้อมูลเพิ่มเติมได้ที่คำแนะนำด้านการออกแบบ PIP