Kể từ Android 8.0 (API cấp 26), Android sẽ cho phép chạy các hoạt động ở chế độ Hình trong hình (PiP). PiP là một loại chế độ đặc biệt dành cho nhiều cửa sổ, chủ yếu dùng để phát video. Tính năng này cho phép người dùng xem video trong một cửa sổ nhỏ được ghim vào góc màn hình trong khi di chuyển giữa các ứng dụng hoặc duyệt xem nội dung trên màn hình chính.
PiP tận dụng các API nhiều cửa sổ có trong Android 7.0 để cung cấp cửa sổ lớp phủ của video đã ghim. Để thêm PiP vào ứng dụng, bạn cần đăng ký các hoạt động hỗ trợ PiP, chuyển hoạt động sang Chế độ PiP (nếu cần) và đảm bảo ẩn đi các thành phần giao diện người dùng cũng như tiếp tục phát video khi hoạt động đó được ở chế độ PiP.
Cửa sổ PiP xuất hiện ở lớp trên cùng của màn hình, ở một góc mà hệ thống chọn.
Chế độ PiP cũng được hỗ trợ trên các thiết bị Android TV OS tương thích chạy Android 14 (API cấp 34) trở lên. Mặc dù có nhiều điểm tương đồng, nhưng vẫn có một số điểm cần cân nhắc khác khi sử dụng PiP trên TV.
Cách người dùng có thể tương tác với cửa sổ PiP
Người dùng có thể kéo cửa sổ PiP đến một vị trí khác. Kể từ Android 12, người dùng cũng có thể:
Nhấn một lần vào cửa sổ để hiện nút bật/tắt chế độ toàn màn hình, nút đóng, nút cài đặt và các hành động tuỳ chỉnh mà ứng dụng của bạn cung cấp (ví dụ: nút điều khiển trình phát).
Nhấn đúp vào cửa sổ đó để chuyển đổi giữa kích thước PiP hiện tại và kích thước PiP tối đa hoặc tối thiểu. Ví dụ: thao tác nhấn đúp vào một cửa sổ được phóng to sẽ thu nhỏ màn hình và ngược lại.
Lưu trữ cửa sổ bằng cách kéo cửa sổ vào cạnh trái hoặc phải. Để huỷ lưu trữ cửa sổ, hãy nhấn vào phần hiển thị của cửa sổ đã lưu trữ hoặc kéo cửa sổ đó ra.
Đổi kích thước cửa sổ PiP bằng cách chụm để thu phóng.
Ứng dụng của bạn kiểm soát thời điểm hoạt động hiện tại chuyển sang chế độ PiP. Dưới đây là một số ví dụ:
Một hoạt động có thể chuyển sang chế độ PiP khi người dùng nhấn vào nút màn hình chính hoặc vuốt lên trên để về màn hình chính. Đây là cách Google Maps không ngừng hiển thị thông tin đường đi trong khi người dùng chạy một hoạt động khác cùng một lúc.
Ứng dụng của bạn có thể chuyển một video sang chế độ PiP khi người dùng điều hướng từ video để duyệt xem nội dung khác.
Ứng dụng của bạn có thể chuyển video sang chế độ PiP trong khi người dùng xem đến cuối tập nội dung. Màn hình chính cho thấy thông tin quảng bá hoặc tóm tắt về tập tiếp theo trong loạt video.
Ứng dụng của bạn có thể giúp người dùng thêm nội dung bổ sung vào hàng đợi trong khi xem video. Video sẽ tiếp tục phát ở chế độ PiP trong khi màn hình chính hiện một hoạt động lựa chọn nội dung.
Khai báo tính năng hỗ trợ PiP
Theo mặc định, hệ thống không tự động hỗ trợ tính năng PiP cho ứng dụng. Nếu bạn muốn hỗ trợ tính năng PiP trong ứng dụng của mình, hãy đăng ký hoạt động video trong tệp kê khai bằng cách đặt android:supportsPictureInPicture
thành true
. Ngoài ra, hãy chỉ định rằng hoạt động của bạn xử lý các thay đổi đối với cấu hình bố cục để hoạt động của bạn không chạy lại khi các thay đổi về bố cục xảy ra trong quá trình chuyển đổi chế độ PiP.
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
Chuyển hoạt động sang chế độ PiP
Kể từ Android 12, bạn có thể chuyển hoạt động sang chế độ PiP bằng cách đặt cờ setAutoEnterEnabled
thành true
. Với chế độ cài đặt này, một hoạt động sẽ tự động chuyển sang chế độ PiP khi cần mà không cần gọi rõ ràng enterPictureInPictureMode()
trong onUserLeaveHint
. Ngoài ra, điểm cải tiến này còn giúp quá trình chuyển đổi diễn ra suôn sẻ hơn nhiều. Để biết thông tin chi tiết, hãy xem phần Khiến việc chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ.
Nếu bạn đang nhắm đến Android 11 trở xuống, thì một hoạt động phải gọi enterPictureInPictureMode()
để chuyển sang chế độ PiP. Ví dụ: Mã sau đây chuyển một hoạt động sang chế độ PiP khi người dùng nhấp vào một nút chuyên dụng trong giao diện người dùng của ứng dụng:
Kotlin
override fun onActionClicked(action: Action) { if (action.id.toInt() == R.id.lb_control_picture_in_picture) { activity?.enterPictureInPictureMode() return } }
Java
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPictureMode(); return; } ... }
Bạn nên thêm logic chuyển chế độ hoạt động sang chế độ PiP, thay vì chuyển vào chế độ nền. Ví dụ: Google Maps sẽ chuyển sang chế độ PiP nếu người dùng nhấn nút màn hình chính hoặc nút gần đây trong khi ứng dụng đang đi theo chỉ dẫn. Bạn có thể xác định trường hợp này bằng cách ghi đè
onUserLeaveHint()
:
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
Nên: mang đến cho người dùng trải nghiệm chuyển đổi PiP chỉn chu
Android 12 đã bổ sung các điểm cải tiến đáng kể về giao diện cho hiệu ứng chuyển đổi ảnh động giữa cửa sổ toàn màn hình và cửa sổ PiP. Bạn nên triển khai tất cả các thay đổi có thể áp dụng; sau khi bạn thực hiện việc này, các thay đổi này sẽ tự động điều chỉnh theo màn hình lớn như màn hình gập và máy tính bảng mà không cần làm gì thêm.
Nếu ứng dụng của bạn không có các bản cập nhật phù hợp, thì hiệu ứng chuyển đổi PiP vẫn hoạt động nhưng các ảnh động sẽ ít được trau chuốt hơn. Ví dụ: việc chuyển từ chế độ toàn màn hình sang chế độ PiP có thể khiến cửa sổ PiP biến mất trong quá trình chuyển đổi trước khi cửa sổ này xuất hiện trở lại khi quá trình chuyển đổi hoàn tất.
Những thay đổi này bao gồm:
- Khiến việc chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ
- Đặt
sourceRectHint
thích hợp để chuyển vào và thoát khỏi chế độ PiP - Tắt tính năng đổi kích thước liền mạch cho nội dung không phải video
Hãy tham khảo mẫu ImageInImage trong Android làm tài liệu tham khảo để mang lại trải nghiệm chuyển đổi chỉn chu.
Khiến việc chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ
Kể từ Android 12, cờ setAutoEnterEnabled
sẽ cung cấp ảnh động mượt mà hơn nhiều để chuyển đổi sang nội dung video ở chế độ PiP bằng thao tác bằng cử chỉ, ví dụ: khi vuốt lên màn hình chính từ chế độ toàn màn hình.
Hãy hoàn tất các bước sau để thực hiện thay đổi này và tham khảo mẫu này để tham khảo:
Sử dụng
setAutoEnterEnabled
để tạoPictureInPictureParams.Builder
:Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build());
Gọi
setPictureInPictureParams
thông báo sớmPictureInPictureParams
. Ứng dụng không đợi lệnh gọi lạionUserLeaveHint
(như đã xảy ra trong Android 11).Ví dụ: Bạn nên gọi
setPictureInPictureParams
trong lần phát đầu tiên và mọi lần phát sau nếu tỷ lệ khung hình thay đổi.Gọi
setAutoEnterEnabled(false)
, nhưng chỉ khi cần thiết. Ví dụ: bạn có thể không muốn chuyển sang chế độ PiP nếu hoạt động phát hiện tại đang ở trạng thái tạm dừng.
Đặt sourceRectHint
thích hợp để chuyển sang và thoát khỏi chế độ PiP
Bắt đầu từ khi tính năng PiP ra mắt trong Android 8.0, setSourceRectHint
cho biết khu vực hoạt động hiển thị sau khi chuyển sang
chế độ hình trong hình, ví dụ: giới hạn xem video trong trình phát video.
Với Android 12, hệ thống sử dụng sourceRectHint
để triển khai ảnh động mượt mà hơn nhiều khi cả khi vào và thoát khỏi chế độ PiP.
Cách đặt sourceRectHint
đúng cách để chuyển sang và thoát khỏi chế độ PiP:
Tạo
PictureInPictureParams
bằng cách sử dụng các giới hạn thích hợp nhưsourceRectHint
. Bạn cũng nên đính kèm trình nghe thay đổi bố cục vào trình phát video:Kotlin
val mOnLayoutChangeListener = OnLayoutChangeListener { v: View?, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop: Int, newRight: Int, newBottom: Int -> val sourceRectHint = Rect() mYourVideoView.getGlobalVisibleRect(sourceRectHint) val builder = PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) setPictureInPictureParams(builder.build()) } mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
Java
private final View.OnLayoutChangeListener mOnLayoutChangeListener = (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight, newBottom) -> { final Rect sourceRectHint = new Rect(); mYourVideoView.getGlobalVisibleRect(sourceRectHint); final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint); setPictureInPictureParams(builder.build()); }; mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
Nếu cần, hãy cập nhật
sourceRectHint
trước khi hệ thống bắt đầu quá trình chuyển đổi thoát. Khi hệ thống sắp thoát chế độ PiP, hệ phân cấp chế độ xem của hoạt động sẽ được đưa vào cấu hình đích (ví dụ: toàn màn hình). Ứng dụng có thể đính kèm trình nghe thay đổi bố cục vào khung hiển thị gốc hoặc khung hiển thị mục tiêu (chẳng hạn như khung hiển thị trình phát video) để phát hiện sự kiện và cập nhậtsourceRectHint
trước khi ảnh động bắt đầu.Kotlin
// 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() ) } }
Java
// Listener is called right after the user exits PiP but before animating. playerView.addOnLayoutChangeListener((v, 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. final Rect sourceRectHint = new Rect(); playerView.getGlobalVisibleRect(sourceRectHint); setPictureInPictureParams( new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build()); } });
Tắt tính năng đổi kích thước liền mạch cho nội dung không phải video
Android 12 bổ sung cờ setSeamlessResizeEnabled
, cung cấp ảnh động mờ dần trên nhiều màn hình khi đổi kích thước nội dung không phải video trong cửa sổ PiP. Trước đây, việc đổi kích thước nội dung không phải video trong cửa sổ PiP có thể tạo ra các thành phần hình ảnh khó hiểu.
Cách tắt tính năng đổi kích thước liền mạch cho nội dung không phải video:
Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build());
Xử lý giao diện người dùng trong chế độ PiP
Khi hoạt động chuyển sang hoặc thoát khỏi chế độ Hình trong hình (PiP), hệ thống sẽ gọi Activity.onPictureInPictureModeChanged()
hoặc Fragment.onPictureInPictureModeChanged()
.
Android 15 ra mắt các thay đổi đảm bảo quá trình chuyển đổi mượt mà hơn khi chuyển sang chế độ PiP. Điều này có lợi cho các ứng dụng có các thành phần trên giao diện người dùng được phủ lên giao diện người dùng chính, sau đó chuyển sang PiP.
Nhà phát triển sử dụng lệnh gọi lại onPictureInPictureModeChanged()
để xác định logic bật/tắt chế độ hiển thị của các thành phần giao diện người dùng được phủ lên.
Lệnh gọi lại này được kích hoạt khi hoàn tất ảnh động nhập hoặc thoát PiP.
Kể từ Android 15, lớp PictureInPictureUiState
sẽ bao gồm một trạng thái mới.
Với trạng thái giao diện người dùng mới này, các ứng dụng nhắm đến Android 15 sẽ quan sát lệnh gọi lại Activity#onPictureInPictureUiStateChanged()
được gọi bằng isTransitioningToPip()
ngay khi ảnh động PiP bắt đầu.
Có nhiều thành phần trên giao diện người dùng không liên quan đến ứng dụng khi ở chế độ PiP, chẳng hạn như khung hiển thị hoặc bố cục có chứa thông tin như đề xuất, video sắp ra mắt, điểm xếp hạng và tiêu đề. Khi ứng dụng chuyển sang chế độ PiP, hãy sử dụng lệnh gọi lại onPictureInPictureUiStateChanged()
để ẩn các thành phần trên giao diện người dùng này. Khi ứng dụng chuyển sang chế độ toàn màn hình từ cửa sổ PiP, hãy sử dụng lệnh gọi lại onPictureInPictureModeChanged()
để hiển thị các phần tử này, như trong các ví dụ sau:
Kotlin
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Java
@Override public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Kotlin
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
Java
@Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
Nút bật/tắt chế độ hiển thị nhanh các thành phần giao diện người dùng không liên quan (đối với cửa sổ PiP) giúp đảm bảo ảnh động chuyển đổi PiP mượt mà và không nhấp nháy.
Ghi đè các lệnh gọi lại này để vẽ lại các thành phần giao diện người dùng của hoạt động. Xin lưu ý rằng ở chế độ PiP, hoạt động của bạn xuất hiện trong một cửa sổ nhỏ. Người dùng không thể tương tác với các thành phần trên giao diện người dùng của ứng dụng khi ứng dụng ở chế độ PiP và có thể khó thấy chi tiết về các thành phần trên giao diện người dùng nhỏ. Các hoạt động phát video trên giao diện người dùng tối thiểu mang lại trải nghiệm người dùng tốt nhất.
Nếu ứng dụng của bạn cần cung cấp thao tác tuỳ chỉnh cho tính năng Hình trong hình, hãy xem phần Thêm chế độ điều khiển trên trang này. Xoá các thành phần giao diện người dùng khác trước khi hoạt động chuyển sang chế độ PiP và khôi phục các thành phần đó khi hoạt động chuyển sang chế độ toàn màn hình trở lại.
Thêm chế độ điều khiển
Cửa sổ PiP có thể hiện các tuỳ chọn điều khiển khi người dùng mở trình đơn của cửa sổ (bằng cách nhấn vào cửa sổ trên thiết bị di động hoặc chọn menu (trình đơn) trong điều khiển từ xa của TV).
Nếu ứng dụng có một phiên hoạt động nội dung đa phương tiện đang hoạt động, thì các nút điều khiển phát, tạm dừng, tiếp theo và trước đó sẽ xuất hiện.
Bạn cũng có thể chỉ định rõ ràng các thao tác tuỳ chỉnh bằng cách tạo PictureInPictureParams
bằng PictureInPictureParams.Builder.setActions()
trước khi chuyển sang chế độ PiP và truyền các thông số khi bạn chuyển sang chế độ PiP bằng cách sử dụng enterPictureInPictureMode(android.app.PictureInPictureParams)
hoặc setPictureInPictureParams(android.app.PictureInPictureParams)
.
Hãy thận trọng. Nếu cố thêm nhiều hơn getMaxNumPictureInPictureActions()
, bạn sẽ chỉ nhận được số lượng tối đa.
Tiếp tục phát video khi ở chế độ PiP
Khi hoạt động của bạn chuyển sang chế độ PiP, hệ thống sẽ đặt hoạt động ở trạng thái tạm dừng và gọi phương thức onPause()
của hoạt động đó. Bạn không nên tạm dừng phát video mà hãy tiếp tục phát nếu hoạt động đó bị tạm dừng khi chuyển sang chế độ PiP.
Với Android 7.0 trở lên, bạn nên tạm dừng và tiếp tục phát video khi hệ thống gọi onStop()
và onStart()
của hoạt động của bạn. Bằng cách này, bạn có thể tránh phải kiểm tra xem ứng dụng của mình có đang ở chế độ PiP không trên onPause()
hay không và tiếp tục phát một cách rõ ràng.
Nếu bạn chưa đặt cờ setAutoEnterEnabled
thành true
và cần tạm dừng phát trong quá trình triển khai onPause()
, hãy kiểm tra chế độ Hình trong hình bằng cách gọi isInPictureInPictureMode()
và xử lý chế độ phát một cách phù hợp. Ví dụ:
Kotlin
override fun onPause() { super.onPause() // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode) { // Continue playback. } else { // Use existing playback logic for paused activity behavior. } }
Java
@Override public void onPause() { // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode()) { // Continue playback. ... } else { // Use existing playback logic for paused activity behavior. ... } }
Khi hoạt động của bạn chuyển từ chế độ PiP trở về chế độ toàn màn hình, hệ thống sẽ tiếp tục hoạt động của bạn và gọi phương thức onResume()
.
Sử dụng một hoạt động phát duy nhất cho tính năng PiP
Trong ứng dụng của bạn, người dùng có thể chọn một video mới khi duyệt xem nội dung trên màn hình chính, trong khi một hoạt động phát video đang ở chế độ PiP. Hãy phát video mới trong hoạt động phát hiện có ở chế độ toàn màn hình thay vì chạy hoạt động mới có thể khiến người dùng nhầm lẫn.
Để đảm bảo sử dụng một hoạt động đơn cho các yêu cầu phát video và chuyển sang hoặc tắt chế độ PiP nếu cần, hãy đặt android:launchMode
của hoạt động thành singleTask
trong tệp kê khai:
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
Trong hoạt động của bạn, hãy ghi đè onNewIntent()
và xử lý video mới, dừng mọi hoạt động phát video hiện có, nếu cần.
Các phương pháp hay nhất
Bạn có thể tắt tính năng PiP trên các thiết bị có dung lượng RAM thấp. Trước khi ứng dụng của bạn sử dụng tính năng PiP, hãy kiểm tra để đảm bảo rằng tính năng này có sẵn bằng cách gọi hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
.
PiP được sử dụng cho các hoạt động phát video toàn màn hình. Khi chuyển đổi hoạt động sang chế độ PiP, hãy tránh hiện nội dung nào khác ngoài nội dung video. Theo dõi hoạt động của bạn khi chuyển sang chế độ PiP và ẩn các thành phần trên giao diện người dùng, như mô tả trong bài viết Xử lý giao diện người dùng trong chế độ PiP.
Khi ở một chế độ PiP, theo mặc định, hoạt động sẽ không thu thập được quyền phát đầu vào. Để nhận sự kiện đầu vào khi ở chế độ PiP, hãy sử dụng MediaSession.setCallback()
.
Để biết thêm thông tin về cách sử dụng setCallback()
, hãy xem phần Hiện thẻ Phát hiện nhạc.
Khi ứng dụng của bạn ở chế độ PiP, việc phát video trong cửa sổ PiP có thể gây ra sự can thiệp âm thanh với một ứng dụng khác, chẳng hạn như ứng dụng phát nhạc hoặc ứng dụng tìm kiếm bằng giọng nói. Để tránh điều này, hãy yêu cầu quyền phát âm thanh khi bạn bắt đầu phát video và xử lý thông báo thay đổi quyền phát âm thanh, như mô tả trong phần Quản lý quyền phát âm thanh. Nếu bạn nhận được thông báo về việc mất quyền phát âm thanh khi ở chế độ PiP, hãy tạm dừng hoặc dừng phát video.
Khi ứng dụng của bạn sắp chuyển sang chế độ PiP, hãy lưu ý rằng chỉ hoạt động hàng đầu mới được chuyển vào chế độ hình trong hình. Trong một số trường hợp như trên các thiết bị nhiều cửa sổ, có thể hoạt động bên dưới sẽ xuất hiện và hiển thị lại cùng với hoạt động PiP. Bạn nên xử lý trường hợp này một cách phù hợp, bao gồm cả hoạt động dưới đây khi nhận được lệnh gọi lại onResume()
hoặc onPause()
. Cũng có thể người dùng có thể tương tác với hoạt động. Ví dụ: nếu bạn có một hoạt động danh sách video đang hiển thị và hoạt động phát video trong chế độ PiP, có thể người dùng sẽ chọn video mới trong danh sách và hoạt động PiP phải được cập nhật tương ứng.
Các đoạn mã mẫu khác
Để tải ứng dụng mẫu viết bằng Kotlin, hãy xem phần Mẫu PictureInPicture cho Android (Kotlin).