Android 8.0(API 수준 26)부터 Android에서 활동을 PIP 모드로 실행할 수 있습니다. PIP는 동영상 재생, 영상 통화, 탐색에 사용되는 특수한 유형의 멀티 윈도우 모드입니다. 사용자가 기본 화면에서 앱 간에 이동하거나 콘텐츠를 둘러보는 동안 기존 활동 창을 화면 모서리에 고정할 수 있습니다.
PIP는 Android 7.0에서 사용할 수 있는 멀티 윈도우 API를 활용하여 고정 동영상 오버레이 창을 제공합니다. PIP를 앱에 추가하려면 PIP를 지원하는 활동을 등록하고 필요한 경우 활동을 PIP 모드로 전환하며 활동이 PIP 모드일 때 UI 요소를 숨기고 동영상 재생이 지속되게 해야 합니다.
PiP 창은 시스템에서 선택한 모서리의 화면 최상위 레이어에 표시됩니다.
PiP는 Android 14 (API 수준 34) 이상을 실행하는 호환되는 Android TV OS 기기에서도 지원됩니다. 유사점이 많지만 TV에서 PIP 모드를 사용할 때는 추가로 고려해야 할 사항이 있습니다.
사용자가 PIP 창과 상호작용하는 방법
사용자는 PIP 창을 다른 위치로 드래그할 수 있습니다. Android 12부터 사용자는 다음 작업도 수행할 수 있습니다.
창을 한 번 탭하여 전체 화면 전환, 닫기 버튼, 설정 버튼 및 앱에서 제공하는 맞춤 작업(예: 재생 컨트롤 기능)을 표시합니다.
창을 두 번 탭하여 현재 PIP 크기와 최대 또는 최소 PIP 크기 간에 전환합니다. 예를 들어 최대화된 창을 두 번 탭하면 최소화되고 그 반대의 경우도 마찬가지입니다.
창을 왼쪽 또는 오른쪽 가장자리로 드래그하여 숨깁니다. 창을 숨김 해제하려면 숨겨진 창의 표시되는 부분을 탭하거나 드래그합니다.
손가락으로 모으거나 펼쳐 확대/축소하여 PIP 창 크기를 조절합니다.
앱에서 현재 활동이 PIP 모드로 전환되는 시점을 제어합니다. 다음은 몇 가지 예입니다.
사용자가 홈 버튼을 탭하거나 홈으로 위로 스와이프하면 활동이 PIP 모드로 전환될 수 있습니다. 이렇게 하면 사용자가 다른 활동을 동시에 실행하는 동안에도 Google 지도에서 경로를 계속 표시할 수 있습니다.
사용자가 동영상에서 뒤로 이동하여 다른 콘텐츠를 탐색할 때 앱에서 동영상을 PIP 모드로 전환할 수 있습니다.
사용자가 콘텐츠 에피소드의 끝을 시청하는 동안 앱에서 동영상을 PIP 모드로 전환할 수 있습니다. 기본 화면에는 시리즈의 다음 에피소드에 관한 홍보 또는 요약 정보가 표시됩니다.
사용자가 동영상을 보는 동안 앱이 추가 콘텐츠를 대기열에 올리는 방법을 제공할 수 있습니다. 기본 화면에 콘텐츠 선택 활동이 표시되는 동안 동영상은 PIP 모드로 계속 재생됩니다.
PIP 지원 선언
기본적으로 시스템에서는 앱의 PIP를 자동으로 지원하지 않습니다. 앱에서 PIP 모드를 지원하려면 android:supportsPictureInPicture를 true로 설정하여 매니페스트에 동영상 활동을 등록하세요. 또한 활동이 레이아웃 구성 변경을 처리하도록 지정하여 PiP 모드 전환 중에 레이아웃 변경이 발생할 때 활동이 다시 실행되지 않도록 합니다.
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
Jetpack으로 PIP 구현
Jetpack PIP 모드 라이브러리를 사용하여 PIP 모드 환경을 구현하세요. 통합을 간소화하고 일반적인 인앱 문제를 줄여줍니다. 사용 예는 플랫폼 샘플 앱을 참고하세요. 하지만 플랫폼 API를 사용하여 PIP를 구현하려면 다음 문서를 참고하세요.
활동을 PIP로 전환
Android 12부터 setAutoEnterEnabled 플래그를 true로 설정하여 활동을 PIP 모드로 전환할 수 있습니다. 이 설정을 사용하면 onUserLeaveHint에서 enterPictureInPictureMode()를 명시적으로 호출하지 않아도 활동이 필요에 따라 자동으로 PIP 모드로 전환됩니다. 이렇게 하면 훨씬 더 원활한 전환을 제공할 수 있다는 이점이 있습니다. 자세한 내용은 동작 탐색에서 PIP 모드로 더 원활하게 전환을 참고하세요.
Android 11 이하를 타겟팅하는 경우 활동은 enterPictureInPictureMode()를 호출하여 PIP 모드로 전환해야 합니다. 예를 들어 다음 코드는 사용자가 앱 UI에서 전용 버튼을 클릭할 때 활동을 PIP 모드로 전환합니다.
override fun onActionClicked(action: Action) {
if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
activity?.enterPictureInPictureMode()
return
}
}
활동을 백그라운드로 전환하는 대신 PiP 모드로 전환하는 로직을 포함할 수 있습니다. 예를 들어 앱에서 탐색하는 동안 사용자가 홈 또는 최근 버튼을 누르면 Google 지도가 PIP 모드로 전환됩니다. onUserLeaveHint()를 재정의하여 이 케이스를 포착할 수 있습니다.
override fun onUserLeaveHint() {
if (iWantToBeInPipModeNow()) {
enterPictureInPictureMode()
}
}
권장사항: 사용자에게 세련된 PIP 모드 전환 환경 제공
Android 12에서는 전체 화면과 PIP 창 간의 애니메이션 전환에 상당한 외관 개선사항이 추가되었습니다. 적용 가능한 모든 변경사항을 구현하는 것이 좋습니다. 구현하면 이러한 변경사항이 추가 작업 없이 폴더블 및 태블릿과 같은 대형 화면에 맞게 자동으로 확장됩니다.
앱에 관련 업데이트가 포함되어 있지 않으면 PiP 전환은 계속 작동하지만 애니메이션이 세련되지 않습니다. 예를 들어 전체 화면에서 PIP 모드로 전환하면 전환 중에 PIP 창이 사라졌다가 전환이 완료되면 다시 나타날 수 있습니다.
변경사항은 다음과 같습니다.
- 동작 탐색에서 PIP 모드로 더 원활하게 전환
- PIP 모드 시작 및 종료를 위한 적절한
sourceRectHint설정 - 동영상 콘텐츠 이외의 콘텐츠에 대한 원활한 크기 조절 사용 중지
세련된 전환 환경을 사용 설정하는 방법은 Android Kotlin PictureInPicture 샘플을 참고하세요.
동작 탐색에서 PIP 모드로 더 매끄럽게 전환
Android 12부터 setAutoEnterEnabled 플래그는 동작 탐색을 사용하여 PIP 모드에서 동영상 콘텐츠로 전환할 때 훨씬 더 부드러운 애니메이션을 제공합니다(예: 전체 화면에서 홈으로 위로 스와이프할 때).
다음 단계를 완료하여 이 변경사항을 적용합니다.
setAutoEnterEnabled를 사용하여PictureInPictureParams.Builder를 구성합니다.
setPictureInPictureParams(PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.setSourceRectHint(sourceRectHint)
.setAutoEnterEnabled(true)
.build())
Note: When `setAutoEnterEnabled` is enabled, you don't need to explicitly
call [`enterPictureInPictureMode`][5] in [`onUserLeaveHint`][6].
최신
PictureInPictureParams를 사용하여setPictureInPictureParams를 조기에 호출합니다. 앱은onUserLeaveHint콜백을 기다리지 않습니다 (Android 11에서와 마찬가지로).예를 들어 가로세로 비율이 변경된 경우 첫 번째 재생 및 이후 재생에서
setPictureInPictureParams를 호출할 수 있습니다.setAutoEnterEnabled(false)에 전화를 걸되 필요한 경우에만 전화를 겁니다. 예를 들어 현재 재생이 일시중지 상태인 경우 PiP로 전환하지 않는 것이 좋습니다.
PIP 모드 시작 및 종료를 위한 적절한 sourceRectHint 설정
Android 8.0에서 PIP가 도입되면서 setSourceRectHint는 PIP 모드로 전환한 후 표시되는 활동 영역(예: 동영상 플레이어의 동영상 뷰 경계)을 나타냈습니다.
Android 12에서는 시스템이 sourceRectHint를 사용하여 PIP 모드를 시작하고 종료할 때 훨씬 더 부드러운 애니메이션을 구현합니다.
PiP 모드 시작 및 종료를 위해 sourceRectHint를 올바르게 설정하려면 다음 단계를 따르세요.
- 적절한 경계를
sourceRectHint로 사용하여PictureInPictureParams을 생성합니다. 동영상 플레이어에 레이아웃 변경 리스너도 연결하는 것이 좋습니다.
val mOnLayoutChangeListener =
OnLayoutChangeListener { v: View?, left: Int,
top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop:
Int, oldRight: Int, oldBottom: Int ->
val sourceRectHint = Rect()
mYourVideoView.getGlobalVisibleRect(sourceRectHint)
val builder = PictureInPictureParams.Builder()
.setSourceRectHint(sourceRectHint)
setPictureInPictureParams(builder.build())
}
mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
- 필요한 경우 시스템이 종료 전환을 시작하기 전에
sourceRectHint를 업데이트합니다. 시스템이 PIP 모드를 종료하려고 하면 활동의 뷰 계층 구조가 대상 구성 (예: 전체 화면)에 배치됩니다. 앱은 루트 뷰 또는 타겟 뷰 (예: 동영상 플레이어 뷰)에 레이아웃 변경 리스너를 연결하여 이벤트를 감지하고 애니메이션이 시작되기 전에sourceRectHint를 업데이트할 수 있습니다.
// Listener is called immediately after the user exits PiP but before animating.
playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom ->
if (left != oldLeft
|| right != oldRight
|| top != oldTop
|| bottom != oldBottom) {
// The playerView's bounds changed, update the source hint rect to
// reflect its new bounds.
val sourceRectHint = Rect()
playerView.getGlobalVisibleRect(sourceRectHint)
setPictureInPictureParams(
PictureInPictureParams.Builder()
.setSourceRectHint(sourceRectHint)
.build()
)
}
}
동영상 콘텐츠 이외의 콘텐츠에 원활한 크기 조절 사용 중지
Android 12에서는 setSeamlessResizeEnabled 플래그를 추가하여 PiP 창에서 동영상 콘텐츠가 아닌 콘텐츠의 크기를 조절할 때 훨씬 더 부드러운 크로스페이딩 애니메이션을 제공합니다. 이전에는 PIP 창에서 동영상 콘텐츠가 아닌 콘텐츠의 크기를 조절하면 시각적 아티팩트가 거슬릴 수 있었습니다.
동영상 콘텐츠의 원활한 크기 조절을 사용 설정하려면 다음 단계를 따르세요.
setPictureInPictureParams(PictureInPictureParams.Builder()
.setSeamlessResizeEnabled(true)
.build())
PIP 중 UI 처리
활동이 PIP 모드로 전환되거나 PIP 모드를 종료하면 시스템은 Activity.onPictureInPictureModeChanged() 또는 Fragment.onPictureInPictureModeChanged()를 호출합니다.
Android 15에서는 PIP 모드로 전환할 때 더 원활하게 전환할 수 있도록 변경사항이 도입되었습니다. 이는 기본 UI 위에 UI 요소가 오버레이되어 PIP 모드로 전환되는 앱에 유용합니다.
개발자는 onPictureInPictureModeChanged() 콜백을 사용하여 오버레이된 UI 요소의 공개 상태를 전환하는 로직을 정의합니다. 이 콜백은 PiP 시작 또는 종료 애니메이션이 완료되면 트리거됩니다. Android 15부터 PictureInPictureUiState 클래스에 새로운 상태가 포함됩니다.
이 새로운 UI 상태를 사용하면 Android 15를 타겟팅하는 앱은 PIP 애니메이션이 시작되는 즉시 isTransitioningToPip()로 호출되는 Activity#onPictureInPictureUiStateChanged() 콜백을 관찰합니다. 앱이 PiP 모드일 때 앱과 관련이 없는 UI 요소가 많습니다. 예를 들어 추천, 다음 동영상, 평점, 제목과 같은 정보가 포함된 뷰 또는 레이아웃이 있습니다. 앱이 PIP 모드로 전환되면 onPictureInPictureUiStateChanged() 콜백을 사용하여 이러한 UI 요소를 숨깁니다. 앱이 PIP 창에서 전체 화면 모드로 전환되면 다음 예와 같이 onPictureInPictureModeChanged() 콜백을 사용하여 이러한 요소를 숨기기 해제합니다.
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
if (pipState.isTransitioningToPip()) {
// Hide UI elements.
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (!isInPictureInPictureMode) {
// Unhide UI elements.
}
}
이처럼 관련 없는 UI 요소 (PiP 창의 경우)를 빠르게 전환하면 더 부드럽고 깜박임 없는 PiP 시작 애니메이션을 보장할 수 있습니다.
이러한 콜백을 재정의하여 활동의 UI 요소를 다시 그립니다. PIP 모드에서는 활동이 작은 창에 표시된다는 점에 유의하세요. 앱이 PIP 모드일 때는 사용자가 앱의 UI 요소와 상호작용할 수 없으며 작은 UI 요소의 세부정보를 확인하기 어려울 수 있습니다. 최소한의 UI가 포함된 동영상 재생 활동에서 최고의 사용자 환경을 제공합니다.
앱에서 PIP 모드에 맞춤 작업을 제공해야 하는 경우 이 페이지의 컨트롤 추가를 참고하세요. 활동이 PIP 모드로 전환되기 전에 다른 UI 요소를 삭제하고 활동이 다시 전체 화면으로 전환되면 복원합니다.
제어 기능 추가
PIP 창에서는 사용자가 휴대기기의 창을 탭하거나 TV 리모컨에서 메뉴를 선택하여 창 메뉴를 열 때 제어 기능을 표시할 수 있습니다.
앱에 활성 미디어 세션이 있는 경우 재생, 일시중지, 다음, 이전 컨트롤이 표시됩니다.
또한 PIP 모드로 전환하기 전에 PictureInPictureParams.Builder.setActions()를 사용하여 PictureInPictureParams를 빌드하여 맞춤 액션을 명시적으로 지정하고 enterPictureInPictureMode(android.app.PictureInPictureParams) 또는 setPictureInPictureParams(android.app.PictureInPictureParams)를 사용하여 PIP 모드로 전환할 때 매개변수를 전달할 수 있습니다. 주의하세요. getMaxNumPictureInPictureActions()개 이상 추가하려고 하면 최대 개수만 표시됩니다.
PIP 모드에서 동영상 재생 계속하기
활동이 PIP 모드로 전환되면 시스템은 활동을 일시중지 상태로 전환하고 활동의 onPause() 메서드를 호출합니다. PIP 모드로 전환하는 동안 활동이 일시중지된 경우 동영상 재생이 일시중지되지 않고 계속 재생되어야 합니다.
Android 7.0 (API 수준 24) 이상에서는 시스템이 활동의 onStop() 및 onStart()를 호출할 때 동영상 재생을 일시중지하고 다시 시작해야 합니다. 이렇게 하면 onPause()에서 앱이 PIP 모드인지 확인하고 명시적으로 재생을 계속할 필요가 없습니다.
setAutoEnterEnabled 플래그를 true로 설정하지 않았고 onPause() 구현에서 재생을 일시중지해야 하는 경우 isInPictureInPictureMode()를 호출하여 PIP 모드를 확인하고 재생을 적절하게 처리합니다. 예를 들면 다음과 같습니다.
override fun onPause() {
super.onPause()
// If called while in PiP mode, don't pause playback.
if (isInPictureInPictureMode) {
// Continue playback.
} else {
// Use existing playback logic for paused activity behavior.
}
}
활동이 PIP 모드에서 전체 화면 모드로 다시 전환되면 시스템은 활동을 재개하고 onResume() 메서드를 호출합니다.
PIP에 단일 재생 활동 사용
앱에서 동영상 재생 활동이 PIP 모드인 동안 사용자가 기본 화면에서 콘텐츠를 탐색할 때 새 동영상을 선택할 수 있습니다. 사용자에게 혼란을 줄 수 있는 새 활동을 실행하는 대신 기존 재생 활동에서 새 동영상을 전체 화면 모드로 재생합니다.
동영상 재생 요청에 단일 활동이 사용되고 필요에 따라 PIP 모드로 전환되거나 PIP 모드에서 전환되도록 하려면 매니페스트에서 활동의 android:launchMode를 singleTask로 설정합니다.
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
활동에서 onNewIntent()를 재정의하고 새 동영상을 처리하여 필요한 경우 기존 동영상 재생을 중지합니다.
카메라 앱의 PIP 모드 지원
카메라 앱에 PiP를 사용 설정하려면 onPause()가 호출될 때 카메라를 닫지 않음으로써 카메라가 PiP 모드에서 활성 상태를 유지하도록 해야 합니다.
override fun onPause() {
super.onPause()
// Don't close the camera if the app is entering PiP mode
if (!isInPictureInPictureMode) {
closeCamera()
}
}
다른 사용 사례와 마찬가지로 필수적이지 않은 UI 요소 (예: 컨트롤, 오버레이)를 숨기고 카메라를 제어하는 맞춤 액션 (예: 녹화 중지, 카메라 뒤집기)을 추가합니다.
원활한 전환을 위해 sourceRectHint 계산
카메라 뷰파인더의 정확한 화면 좌표와 함께 정확한 sourceRectHint를 제공하는 것은 원활한 시작 애니메이션을 위해 필수적입니다. 다음과 같이 getGlobalVisibleRect()를 사용하여 미리보기 뷰에서 경계를 가져올 수 있습니다.
val previewView = findViewById<View>(R.id.preview_view)
val globalRect = Rect()
// Ensure the view is laid out before calling getGlobalVisibleRect() to get valid screen coordinates.
previewView.getGlobalVisibleRect(globalRect)
val params = PictureInPictureParams.Builder()
.setSourceRectHint(globalRect)
.build()
setPictureInPictureParams(params)
추가 고려사항
RAM이 부족한 기기에서는 PIP가 사용 중지될 수 있습니다. 앱에서 PIP를 사용하려면 hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)를 호출하여 PIP를 사용할 수 있는지 확인하세요.
PIP는 전체 화면 동영상을 재생하는 활동에 사용합니다. 활동을 PIP 모드로 전환할 때 동영상 콘텐츠 이외의 콘텐츠는 표시하지 않습니다. PIP 모드 중 UI 처리에 설명된 대로 활동이 PIP 모드로 전환되는 시점을 추적하고 UI 요소를 숨깁니다.
활동이 PIP 모드에 있으면 기본적으로 입력 포커스를 받지 않습니다. PiP 모드에서 입력 이벤트를 수신하려면 MediaSession.setCallback()를 사용합니다.
setCallback() 사용에 관한 자세한 내용은 나우 플레잉 카드 표시를 참고하세요.
앱이 PIP 모드에 있을 때 PIP 창에서 동영상을 재생하면 음악 플레이어 앱이나 음성 검색 앱과 같은 다른 앱과 오디오 간섭이 발생할 수 있습니다. 이를 방지하려면 동영상 재생을 시작할 때 오디오 포커스를 요청하고 오디오 포커스 관리에 설명된 대로 오디오 포커스 변경 알림을 처리하세요. PIP 모드에서 오디오 포커스 손실 알림을 받으면 동영상 재생을 일시중지하거나 중지합니다.
앱이 PIP 모드로 전환될 때 최상위 활동만 PIP 모드로 전환됩니다. 멀티 윈도우 기기와 같은 일부 상황에서는 기본 활동이 이제 표시되고 PiP 활동과 함께 다시 표시될 수 있습니다. 기본 활동이 onResume() 또는 onPause() 콜백을 수신하는 것을 포함하여 이 케이스를 적절하게 처리해야 합니다.
또한 사용자가 그 활동과 상호작용할 수도 있습니다.
예를 들어 동영상 목록 활동이 표시되고 재생 중인 동영상 활동이 PIP 모드에 있는 경우 사용자가 목록에서 새 동영상을 선택할 수 있으며 PIP 활동은 이에 따라 업데이트되어야 합니다.
추가 샘플 코드
Kotlin으로 작성된 샘플 앱을 다운로드하려면 Android PictureInPicture 샘플(Kotlin)을 참고하세요.