Sử dụng Thư viện Jetpack Hình trong hình

Thư viện Jetpack Hình trong hình (PiP) cung cấp một giải pháp hợp lý và mạnh mẽ cho nhà phát triển ứng dụng Android để triển khai chức năng PiP, đặc biệt là cho các ứng dụng phát nội dung nghe nhìn, giao tiếp video và điều hướng. Bằng cách cung cấp một API hợp nhất, thư viện này giúp loại bỏ mã nguyên mẫu, các lỗi thường gặp trong ứng dụng và cải thiện chất lượng tổng thể của trải nghiệm người dùng PiP.

Thư viện PiP Jetpack hỗ trợ các API PiP hiện có bằng cách giải quyết một số thách thức và điểm không nhất quán chính trong hệ sinh thái Android:

  • Sự phân mảnh hệ điều hành: Thư viện tự động xử lý sự khác biệt trong các lệnh gọi API PiP trên nhiều phiên bản Android, chẳng hạn như sử dụng enterPictureInPictureMode trước Android 12 và isAutoEnterEnabled sau Android 12, nhờ đó nhà phát triển không cần quản lý sự khác biệt về phiên bản.
  • Tham số PiP không chính xác: Thư viện này cung cấp một giải pháp hợp nhất để đặt chính xác các tham số PiP, chẳng hạn như setSourceRectHint, nhằm tạo ảnh động mượt mà và chất lượng cao trong quá trình phát nội dung nghe nhìn.
  • Lệnh gọi lại trạng thái PiP hợp nhất: Hợp nhất onPictureInPictureModeChanged và onPictureInPictureUiStateChanged thành một giao diện lệnh gọi lại hợp nhất (PictureInPictureDelegate.OnPictureInPictureEventListener) để đơn giản hoá việc quản lý trạng thái và giao diện người dùng.
  • Giảm mã nguyên mẫu: Thư viện này giảm lượng mã nguyên mẫu lặp lại bằng cách cung cấp các nhóm RemoteActions được xác định trước cho các trường hợp sử dụng phổ biến, chẳng hạn như bộ điều khiển chế độ phát và thao tác gọi video.
  • Đảm bảo khả năng tương thích trong tương lai: Các tính năng khác của chế độ Hình trong hình được cung cấp thông qua thư viện Jetpack, cho phép người dùng sử dụng các chức năng bổ sung mà không cần nỗ lực nhiều.

Quy trình di chuyển

Xác định danh mục trường hợp sử dụng của ứng dụng và logic PiP cũ:

Danh mục: Phát video, Chỉ đường hoặc Cuộc gọi video.

Logic PiP cũ cần xác định:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Cấu hình AndroidManifest

Đảm bảo Hoạt động chuyển sang chế độ PiP khai báo hỗ trợ trong AndroidManifest.xml bằng configChanges cần thiết để tránh khởi động lại không cần thiết:

<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>

3. Thiết lập môi trường

Thêm các phần phụ thuộc bắt buộc vào build.gradle:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Sử dụng các thư viện AndroidX mới nhất cho các phần phụ thuộc và tham khảo trang bản phát hành để biết thông tin đó.

4. Chọn và khởi chạy mẫu

Chọn mẫu triển khai phù hợp nhất với trường hợp sử dụng của ứng dụng:

  • Điều hướng và gọi video: BasicPictureInPicture; thường không hỗ trợ việc đổi kích thước liền mạch và bạn không cần gợi ý về hình chữ nhật nguồn.
  • Phát video: VideoPlaybackPictureInPicture; tự động theo dõi ranh giới chế độ xem của trình phát cho gợi ý hình chữ nhật nguồn và bật tính năng đổi kích thước liền mạch theo mặc định.

Để sử dụng Thư viện Jetpack, hãy thay thế cách triển khai PiP tuỳ chỉnh hiện có bằng các API Thư viện Jetpack. Mức độ phức tạp và chi phí áp dụng sẽ khác nhau tuỳ theo cách triển khai hiện tại của ứng dụng.

Các phần sau đây mô tả một số trường hợp sử dụng thông thường của PiP và các bước triển khai cần thiết:

Ứng dụng thông báo cho thư viện về trạng thái hoạt động hoặc không hoạt động của tính năng chỉ đường và đặt tỷ lệ khung hình. Thư viện Jetpack sẽ xử lý phần còn lại.

Những điểm khác biệt chính:

  1. Không cần phân biệt giữa auto-enter và legacy-enter ở phía ứng dụng.
  2. Giao diện gọi lại tổng hợp.
  3. Trình tạo PictureInPictureParams mới để có khả năng tương thích ngược.

Gọi video

Ứng dụng thông báo cho thư viện về trạng thái hoạt động hoặc không hoạt động của cuộc gọi và đặt tỷ lệ khung hình.

Những điểm khác biệt chính:

  1. Không cần phân biệt giữa auto-enter và legacy-enter ở phía ứng dụng.
  2. Giao diện gọi lại tổng hợp.
  3. Trình tạo PictureInPictureParams mới để có khả năng tương thích ngược.
  4. Biểu tượng thao tác chuẩn hoá cho cuộc gọi video.

5. Di chuyển mã

  • Logic nhập: Thay thế logic dành riêng cho API, chẳng hạn như setAutoEnterEnabled cho Android 12 trở lên hoặc onUserLeaveHint cho Android 11 trở xuống bằng setEnabled. Kích hoạt tính năng này bất cứ khi nào trạng thái đủ điều kiện sử dụng chế độ Hình trong hình thay đổi.
  • Lệnh gọi lại: Hợp nhất onPictureInPictureModeChanged (bật/tắt bố cục) và onPictureInPictureUiStateChanged (ảnh động/trạng thái) thành một lệnh gọi lại dựa trên sự kiện hợp nhất onPictureInPictureEvent.
  • Hành động và tham số: Cập nhật tham số bằng setActions và setAspectRatio trên thực thể mẫu bất cứ khi nào các tham số này thay đổi.

Mẫu triển khai tham khảo

Ví dụ về cách triển khai.

Điều hướng và cuộc gọi video

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Phát lại video

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}