Pencere içinde pencere (PIP), çoğunlukla video oynatma için kullanılan özel bir çoklu pencere modu türüdür. Kullanıcının uygulamalar arasında gezinirken veya ana ekranda içeriklere göz atarken ekranın bir köşesine sabitlenmiş küçük bir pencerede video izlemesini sağlar.
PiP, sabitlenmiş video yer paylaşımı penceresi sağlamak için Android 7.0'da kullanıma sunulan çoklu pencere API'lerinden yararlanır. Uygulamanıza PiP eklemek için etkinliğinizi kaydetmeniz, gerektiğinde etkinliğinizi PiP moduna geçirmeniz, kullanıcı arayüzü öğelerinin gizlendiğinden ve etkinlik PiP modundayken video oynatmanın devam ettiğinden emin olmanız gerekir.
Bu kılavuzda, Compose video uygulamasıyla uygulamanıza Compose'da PiP'nin nasıl ekleneceği açıklanmaktadır. Bu en iyi uygulamaları uygulamalı olarak görmek için Socialite uygulamasına bakın.
Uygulamanızı PiP için ayarlama
AndroidManifest.xml
dosyanızın etkinlik etiketinde aşağıdakileri yapın:
- Uygulamanızda PiP kullanacağınızı belirtmek için
supportsPictureInPicture
ekleyip bu bilgiyitrue
olarak ayarlayın. Etkinliğinizin düzen yapılandırma değişikliklerini gerçekleştirmesini belirtmek için
configChanges
ekleyiporientation|screenLayout|screenSize|smallestScreenSize
olarak ayarlayın. Bu sayede, PiP modu geçişleri sırasında düzen değişiklikleri olduğunda etkinliğiniz yeniden başlatılmaz.<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
Oluşturma kodunuzda aşağıdakileri yapın:
- Bu uzantıyı
Context
sitesine ekleyin. Etkinliğe erişmek için kılavuz boyunca bu uzantıyı birden çok kez kullanacaksınız.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") }
Android 12 öncesi sürüm için izin verilen uygulamaya PiP ekleme
Android 12 öncesi için PiP eklemek istiyorsanız addOnUserLeaveHintProvider
kullanın. Android 12 öncesi için PiP eklemek üzere şu adımları uygulayın:
- Bu koda yalnızca R tarihine kadar O sürümlerinde erişilebilmesi için bir sürüm kapısı ekleyin.
- Anahtar olarak
Context
içeren birDisposableEffect
kullanın. DisposableEffect
içinde, lambda kullanılarakonUserLeaveHintProvider
tetiklendiğinde sergilenecek davranışı tanımlayın. Lambda'da,findActivity()
üzerindenenterPictureInPictureMode()
numarasını arayın vePictureInPictureParams.Builder().build()
numarasını iletin.findActivity()
kullanarakaddOnUserLeaveHintListener
ekleyin ve lambdayı geçin.onDispose
oyununda,findActivity()
kullanarakremoveOnUserLeaveHintListener
değerini ekleyin ve lambda'yı geçin.
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_TAG, "API does not support PiP") }
Android sonrası 12 sürümü için izin verilen uygulamaya PiP ekleyin
Android 12'den sonra PictureInPictureParams.Builder
, uygulamanın video oynatıcısına iletilen bir düzenleyici aracılığıyla eklenir.
- Bir
modifier
oluşturun ve buna ilişkinonGloballyPositioned
numarasını arayın. Düzen koordinatları sonraki bir adımda kullanılacaktır. PictureInPictureParams.Builder()
için bir değişken oluşturun.- SDK'nın S veya daha yüksek bir sürüm olup olmadığını kontrol etmek için
if
ifadesi ekleyin. Bu durumda, oluşturucuyasetAutoEnterEnabled
ekleyin ve kaydırma işleminden sonra PiP moduna girmek için bunutrue
olarak ayarlayın. Böylece,enterPictureInPictureMode
üzerinden geçmekten daha akıcı bir animasyon sağlanır. setPictureInPictureParams()
numaralı telefonu aramak içinfindActivity()
numaralı telefonu kullanın.builder
üzerindenbuild()
numarasını arayıp gönderin.
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)
Düğme aracılığıyla PiP ekleme
PiP moduna tek bir düğmeye tıklayarak girmek için findActivity()
numaralı telefondan enterPictureInPictureMode()
numaralı telefonu arayın.
Parametreler, PictureInPictureParams.Builder
öğesine yapılan önceki çağrılar tarafından zaten ayarlandığı için oluşturucuda yeni parametreler ayarlamanız gerekmez. Ancak, düğme tıklandığında herhangi bir parametreyi
değiştirmek isterseniz bu parametreleri burada ayarlayabilirsiniz.
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!") }
Kullanıcı arayüzünüzü PiP modunda işleme
PiP moduna girdiğinizde, kullanıcı arayüzünüzün PiP modunda nasıl görüneceğini belirtmediğiniz sürece uygulamanızın tüm kullanıcı arayüzü PiP penceresine girer.
Öncelikle, uygulamanızın ne zaman PiP modunda olduğunu bilmeniz gerekir. Bunun için OnPictureInPictureModeChangedProvider
kullanabilirsiniz.
Aşağıdaki kod, uygulamanızın PiP modunda olup olmadığını belirtir.
@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 } }
Artık uygulama PiP moduna girdiğinde gösterilecek kullanıcı arayüzü öğelerini değiştirmek için rememberIsInPipMode()
kullanabilirsiniz:
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() }
Uygulamanızın doğru zamanlarda PiP moduna girdiğinden emin olun
Uygulamanız aşağıdaki durumlarda PiP moduna geçmemelidir:
- Videonun durdurulup durdurulduğu veya duraklatıldığı.
- Uygulamanın, video oynatıcıdan farklı bir sayfasındaysanız.
Uygulamanızın ne zaman PiP moduna girdiğini kontrol etmek için mutableStateOf
kullanarak video oynatıcının durumunu izleyen bir değişken ekleyin.
Videonun oynatılma durumuna göre açma/kapatma durumu
Video oynatıcının oynatılma durumuna göre durumu değiştirmek için video oynatıcıya bir işleyici ekleyin. Oynatıcının oynayıp oynamadığına bağlı olarak durum değişkeninizin durumunu değiştirin:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
Oynatıcının serbest bırakılıp bırakılmadığına bağlı olarak durum değiştirme
Oynatıcı serbest bırakıldığında durum değişkeninizi false
olarak ayarlayın:
fun releasePlayer() { shouldEnterPipMode = false }
PiP moduna girilip girilmeyeceğini tanımlamak için durumu kullan (Android 12 öncesi)
- 12 öncesi PiP'yi eklerken
DisposableEffect
kullanıldığından,rememberUpdatedState
tarihine kadar durum değişkeni olaraknewValue
ayarlanmış bir değişken oluşturmanız gerekir. Bu işlem, güncellenmiş sürümünDisposableEffect
içinde kullanılmasını sağlar. OnUserLeaveHintListener
tetiklendiğinde sergilediği davranışı tanımlayan lambda'da,enterPictureInPictureMode()
çağrısının etrafına durum değişkeni içeren birif
ifadesi ekleyin: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_TAG, "API does not support PiP") }
PiP moduna girilip girilmeyeceğini tanımlamak için durumu kullan (Android 12 sonrası)
Eyalet değişkeninizi setAutoEnterEnabled
öğesine iletin. Böylece uygulamanız yalnızca doğru zamanda PiP moduna girer:
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)
Akıcı bir animasyon uygulamak için setSourceRectHint
kullanma
setSourceRectHint
API, PiP moduna girmek için daha yumuşak bir animasyon oluşturur. Bu özellik, Android 12 ve sonraki sürümlerde PiP modundan çıkmak için daha akıcı bir animasyon da oluşturur.
PiP'ye geçişten sonra etkinliğin görünür olan alanını belirtmek için bu API'yi PiP oluşturucuya ekleyin.
setSourceRectHint()
öğesinibuilder
öğesine yalnızca durum, uygulamanın PiP moduna girmesi gerektiğini tanımlıyorsa ekleyin. Bu, uygulamanın PiP'ye girmesi gerekmediğindesourceRect
hesaplamasının önüne geçer.sourceRect
değerini ayarlamak için değiştiricidekionGloballyPositioned
işlevinden verilenlayoutCoordinates
özelliğini kullanın.builder
öğesinesetSourceRectHint()
yöntemini çağırın vesourceRect
değişkenini iletin.
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)
PiP penceresinin en boy oranını ayarlamak için setAspectRatio
öğesini kullanın
PiP penceresinin en boy oranını ayarlamak için belirli bir en boy oranı seçebilir veya oynatıcının video boyutunun genişliğini ve yüksekliğini kullanabilirsiniz. Media3 oynatıcı kullanıyorsanız en boy oranını ayarlamadan önce oynatıcının boş olmadığından ve oynatıcının video boyutunun VideoSize.UNKNOWN
değerine eşit olmadığından emin olun.
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)
Özel bir oynatıcı kullanıyorsanız oynatıcınıza özgü söz dizimini kullanarak oynatıcının yüksekliği ve genişliğindeki en boy oranını ayarlayın. Oynatıcınız başlatma sırasında yeniden boyutlandırılırsa ve en boy oranının geçerli sınırlarının dışına çıkarsa uygulamanızın çökeceğini unutmayın. Bir media3 oynatıcıda olduğu gibi, en boy oranının ne zaman hesaplanabileceği konusunda kontroller eklemeniz gerekebilir.
Uzaktan işlem ekle
PiP pencerenize kontroller (oynatma, duraklatma vb.) eklemek isterseniz eklemek istediğiniz her kontrol için bir RemoteAction
oluşturun.
- Yayın kontrolleriniz için sabit değerler ekleyin:
// 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
- PiP pencerenizdeki kontroller için bir
RemoteActions
listesi oluşturun. - Daha sonra, bir
BroadcastReceiver
ekleyin ve her bir düğmenin işlemlerini ayarlamak içinonReceive()
öğesini geçersiz kılın. Alıcıyı ve uzak işlemleri kaydetmek için birDisposableEffect
kullanın. Oynatıcı imha edildiğinde alıcının kaydını silin.@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) } } }
- Uzaktan gerçekleştirdiğiniz işlemlerin listesini
PictureInPictureParams.Builder
'a aktarın: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)
Sonraki adımlar
Bu kılavuzda, Compose'da hem Android 12 öncesi hem de Android 12 sonrası sürümler için PiP eklemeyle ilgili en iyi uygulamaları öğrendiniz.
- Composer PiP ile ilgili en iyi uygulamaları çalışırken görmek için Socialite uygulamasına göz atın.
- Daha fazla bilgi için PIP tasarım kılavuzuna bakın.