تصویر در تصویر (PiP) نوع خاصی از حالت چند پنجره ای است که بیشتر برای پخش ویدئو استفاده می شود. این امکان را به کاربر می دهد که در یک پنجره کوچک که به گوشه ای از صفحه سنجاق شده است، هنگام حرکت بین برنامه ها یا مرور محتوا در صفحه اصلی، ویدیویی را تماشا کند.
PiP از APIهای چندپنجرهای که در Android 7.0 در دسترس هستند برای ارائه پنجره همپوشانی ویدئویی پین شده استفاده میکند. برای افزودن PiP به برنامه خود، باید فعالیت خود را ثبت کنید، فعالیت خود را در صورت نیاز به حالت PiP تغییر دهید و مطمئن شوید که عناصر رابط کاربری پنهان هستند و پخش ویدیو زمانی که فعالیت در حالت 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 on مرخصی را برای نسخه پیش از اندروید 12 اضافه کنید
برای افزودن PiP برای نسخه پیش از اندروید 12، از addOnUserLeaveHintProvider
استفاده کنید. برای افزودن PiP برای نسخه پیش از اندروید 12 این مراحل را دنبال کنید:
- یک نسخه گیت اضافه کنید تا این کد فقط در نسخه های O تا R قابل دسترسی باشد.
- از یک
DisposableEffect
باContext
به عنوان کلید استفاده کنید. - در داخل
DisposableEffect
، رفتار زمانی کهonUserLeaveHintProvider
با استفاده از لامبدا راه اندازی می شود را تعریف کنید. در لامبدا،enterPictureInPictureMode()
درfindActivity()
فراخوانی کنید و ازPictureInPictureParams.Builder().build()
عبور دهید. -
addOnUserLeaveHintListener
با استفاده ازfindActivity()
اضافه کنید و لامبدا را ارسال کنید. - در
onDispose
،removeOnUserLeaveHintListener
با استفاده ازfindActivity()
اضافه کنید و لامبدا را وارد کنید.
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 on مرخصی را برای پس از اندروید 12 اضافه کنید
پس از اندروید 12، PictureInPictureParams.Builder
از طریق یک اصلاح کننده که به پخش کننده ویدیوی برنامه ارسال می شود، اضافه می شود.
- یک
modifier
بسازید و روی آن باonGloballyPositioned
تماس بگیرید. مختصات طرح در مرحله بعدی استفاده خواهد شد. - یک متغیر برای
PictureInPictureParams.Builder()
ایجاد کنید. - برای بررسی اینکه آیا SDK S یا بالاتر است، یک عبارت
if
اضافه کنید. اگر چنین است،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!") }
رابط کاربری خود را در حالت PiP مدیریت کنید
وقتی وارد حالت PiP میشوید، کل رابط کاربری برنامه شما وارد پنجره PiP میشود، مگر اینکه مشخص کنید رابط کاربری شما چگونه باید در حالت 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()
استفاده کنید تا هنگام ورود برنامه به حالت PiP، عناصر UI را تغییر دهید:
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
ردیابی کند.
وضعیت را بر اساس پخش ویدیو تغییر دهید
برای تغییر وضعیت بر اساس اینکه پخش کننده ویدیو در حال پخش است، یک شنونده در پخش کننده ویدیو اضافه کنید. وضعیت متغیر حالت خود را بر اساس اینکه بازیکن در حال بازی است یا خیر تغییر دهید:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
وضعیت را بر اساس آزاد شدن بازیکن تغییر دهید
وقتی پخش کننده آزاد شد، متغیر حالت خود را روی false
قرار دهید:
fun releasePlayer() { shouldEnterPipMode = false }
استفاده از حالت برای تعیین اینکه آیا حالت PiP وارد شده است (قبل از اندروید 12)
- از آنجایی که افزودن PiP pre-12 از یک
DisposableEffect
استفاده میکند، باید یک متغیر جدید توسطrememberUpdatedState
باnewValue
مجموعهای بهعنوان متغیر حالت ایجاد کنید. این اطمینان حاصل می کند که نسخه به روز شده درDisposableEffect
استفاده می شود. در لامبدا که رفتار را هنگام راهاندازی
OnUserLeaveHintListener
تعریف میکند، یک دستورif
با متغیر state در اطراف فراخوانی به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 وارد شده است (پس از اندروید 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 قابل مشاهده است را نشان دهید.
- فقط در صورتی که حالت تعریف می کند که برنامه باید وارد حالت PiP شود،
setSourceRectHint()
را بهbuilder
اضافه کنید. این از محاسبه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 استفاده می کنید، قبل از تنظیم نسبت تصویر، بررسی کنید که پخش کننده خالی نباشد و اندازه ویدیوی پخش کننده با 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)
اگر از پخش کننده سفارشی استفاده می کنید، با استفاده از نحو مخصوص پخش کننده خود، نسبت ابعاد را بر روی ارتفاع و عرض پخش کننده تنظیم کنید. توجه داشته باشید که اگر اندازه پخش کننده شما در حین تنظیم اولیه تغییر اندازه دهد، اگر از محدوده معتبر نسبت ابعاد خارج شود، برنامه شما از کار می افتد. ممکن است لازم باشد بررسی هایی را در زمانی که می توان نسبت تصویر را محاسبه کرد، اضافه کنید، مشابه روشی که برای پخش کننده media3 انجام می شود.
افزودن اقدامات از راه دور
اگر میخواهید کنترلهایی (پخش، مکث و غیره) را به پنجره 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 را هم قبل از اندروید 12 و هم پس از اندروید 12 یاد گرفتید.
- برای مشاهده بهترین شیوه های Compose PiP در عمل، به برنامه Socialite مراجعه کنید.
- برای اطلاعات بیشتر به راهنمای طراحی PiP مراجعه کنید.