Hỗ trợ tính năng Hình trong hình (PiP)

Stay organized with collections Save and categorize content based on your preferences.

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.

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. Lưu trữ cửa sổ bằng cách kéo cửa sổ sang 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 (ở chế độ di chuyển bằng nút) hoặc vuốt lên trên để về màn hình chính (ở chế độ thao tác bằng cử chỉ). (Đâ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 việc hỗ trợ tính năng hình trong hình

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, 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ế độ hình trong hình

Để chuyển sang chế độ PiP, hoạt động phải gọi enterPictureInPictureMode(). Ví dụ: mã sau đây sẽ chuyển một hoạt động sang chế độ PiP khi người dùng nhấp vào một nút riê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();
    }
}

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, bạn có thể sử dụng cờ setAutoEnterEnabled để chuyển đổi mượt mà hơn sang chế độ PiP khi vuốt lên màn hình chính trong chế độ thao tác bằng cử chỉ.

Để triển khai tính năng này, hãy làm như sau:

  1. Sử dụng setAutoEnterEnabled để tạo PictureInPictureParams.Builder, như sau:

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Gọi setPictureInPictureParams thông báo sớm PictureInPictureParams. Ứng dụng không nên đợi lệnh gọi lại onUserLeaveHint (như đã xảy ra trong Android 11).

    Chẳng hạn, có thể ứng dụng muố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.

  3. Hãy gọi cho setAutoEnterEnabled(false) nếu cần. Ví dụ: có thể một ứng dụng video không nhập PiP nếu hoạt động phát hiện tại đang ở trạng thái tạm dừng.

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ế độ PiP, hệ thống sẽ gọi Activity.onPictureInPictureModeChanged() hoặc Fragment.onPictureInPictureModeChanged().

Bạn nên 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 giao diện người dùng của ứng dụng khi đang ở chế độ PiP và có thể khó thấy chi tiết về các thành phầ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 ở mức tối thiểu sẽ đem 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 các hành động tuỳ chỉnh cho PiP, hãy xem nội dung Thêm tuỳ chọn kiểm soát trong tài liệu 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 này khi hoạt động chuyển sang chế độ toàn màn hình:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
    }
}

Java

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

Hỗ trợ ảnh động mượt mà hơn khi thoát khỏi chế độ PiP

Kể từ Android 12, cờ SourceRectHint sẽ được dùng lại để triển khai ảnh động mượt mà hơn khi thoát khỏi chế độ PiP. Khi thoát, hệ thống sẽ tạo ảnh động bằng sourceRectHint hiện có, cho dù đó là Rect gốc dùng để nhập PiP hay Rect đã cập nhật do ứng dụng cung cấp.

Để triển khai tính năng này, hãy cập nhật ứng dụng như sau:

  1. Tiếp tục tạo PictureInPictureParams bằng sourceRectHintaspectRatio để ảnh động xuất hiện mượt mà.

  2. 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 chế độ xem gốc hoặc chế độ xem mục tiêu (chẳng hạn như chế độ xem trình phát video) để phát hiện sự kiện và cập nhật sourceRectHint 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());
        }
    });
    

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õ các hành động 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.

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ờ setSeamlessResizeEnabled được đặt thành true theo mặc định cho khả năng tương thích ngược. Hãy thiết lập thành true đối với nội dung video và thay đổi thành false đối với nội dung không phải video.

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:

  setPictureInPictureParams(new PictureInPictureParams.Builder()
          .setSeamlessResizeEnabled(false)
          .build());

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 ở 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()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 phải tạm dừng phát ở chế độ triển khai onPause(), hãy kiểm tra chế độ PiP bằng cách gọi isInPictureInPictureMode() và xử lý chế độ phát sao cho 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 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ế độ hình trong h ình.

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 Cách hiển thị thẻ Phát hiện nhạc

Khi ứng dụng ở chế độ PiP, việc phát video trong cửa sổ PiP có thể gây nhiễu âm thanh với ứng dụng khác, chẳng hạn như ứng dụng trình phát nhạc hoặc ứng dụng tìm kiếm bằng giọng nói. Để tránh tình trạng này, hãy yêu cầu cấp quyền phát âm thanh khi bạn bắt đầu phát video và xử lý các thông báo thay đổi quyền phát âm thanh theo mô tả trong bài viết 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 PiP. 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.

Mã mẫu khác

Để tải ứng dụng mẫu viết cho Android, hãy xem phần Mẫu Hình trong hình. Để tải ứng dụng mẫu viết bằng Kotlin, hãy xem phần Mẫu PictureInPicture cho Android (Kotlin).