Tạo bố cục danh sách-chi tiết bằng tính năng nhúng hoạt động và Material Design

1. Giới thiệu

Màn hình lớn cho phép bạn cải thiện trải nghiệm, cũng như hiệu suất sử dụng của người dùng thông qua bố cục và giao diện người dùng mà bạn tạo cho ứng dụng. Nhưng nếu bạn thiết kế ứng dụng của mình cho màn hình nhỏ của điện thoại không thể gập lại, ứng dụng có thể sẽ không tận dụng được diện tích màn hình vượt trội mà máy tính bảng, thiết bị có thể gập lại và thiết bị ChromeOS cung cấp.

Việc cập nhật ứng dụng để khai thác tối đa màn hình lớn có thể tốn thời gian và chi phí, đặc biệt là đối với các ứng dụng cũ dựa trên nhiều hoạt động.

Tính năng nhúng hoạt động có trong Android 12L (API cấp 32) cho phép các ứng dụng dựa trên hoạt động hiển thị nhiều hoạt động cùng lúc trên màn hình lớn để tạo bố cục hai ngăn (chẳng hạn như danh sách-chi tiết (list-detail)). Bạn không cần phải viết lại mã Kotlin hoặc Java. Bạn sẽ thêm một số phần phụ thuộc, tạo tệp cấu hình XML, triển khai trình khởi chạy và bổ sung thêm cho tệp kê khai ứng dụng. Hoặc nếu thích lập trình, bạn chỉ cần thêm một vài lệnh gọi API Jetpack WindowManager vào phương thức onCreate() của hoạt động chính trong ứng dụng.

Điều kiện tiên quyết

Để hoàn thành lớp học lập trình này, bạn cần có kinh nghiệm trong các lĩnh vực sau:

  • Xây dựng ứng dụng Android
  • Làm việc với các hoạt động
  • Viết mã XML
  • Làm việc với Android Studio, bao gồm cả thiết lập thiết bị ảo

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ cập nhật ứng dụng dựa trên hoạt động để hỗ trợ bố cục hai ngăn động tương tự như SlidingPaneLayout. Trên màn hình nhỏ, các ứng dụng sẽ phủ (xếp chồng) lên nhau trong cửa sổ tác vụ.

Các hoạt động A, B và C xếp chồng trong cửa sổ tác vụ.

Trên màn hình lớn, dựa trên thông số mà bạn thiết lập, ứng dụng sẽ hiển thị đồng thời hai hoạt động trên màn hình, cạnh nhau hoặc trên cùng và dưới cùng.

4b27b07b7361d6d8.png

Kiến thức bạn sẽ học được

Cách triển khai nhúng hoạt động theo hai cách:

  • Sử dụng tệp cấu hình XML
  • Sử dụng lệnh gọi API Jetpack WindowManager

Bạn cần có

  • Phiên bản Android Studio gần đây
  • Trình mô phỏng hoặc điện thoại Android
  • Máy tính bảng hoặc trình mô phỏng Android nhỏ
  • Máy tính bảng hoặc trình mô phỏng Android lớn

2. Thiết lập

Tải ứng dụng mẫu

Bước 1: Sao chép kho lưu trữ

Sao chép kho lưu trữ Git của các lớp học lập trình màn hình lớn:

git clone https://github.com/android/large-screen-codelabs

hoặc tải xuống và huỷ lưu trữ tệp zip của lớp học lập trình màn hình lớn:

Tải mã nguồn xuống

Bước 2: Kiểm tra các tệp nguồn của lớp học lập trình

Chuyển đến thư mục activity-embedding.

Bước 3: Mở dự án của lớp học lập trình

Trong Android Studio, hãy mở dự án Kotlin hoặc Java

Danh sách tệp cho thư mục hoạt động trong tệp repo và zip.

Thư mục activity-embedding trong tệp repo và tệp zip chứa hai dự án Android Studio: một bằng ngôn ngữ Kotlin, một bằng ngôn ngữ Java. Hãy mở dự án tuỳ chọn. Các đoạn mã của lớp học lập trình này được cung cấp bằng cả hai ngôn ngữ.

Tạo thiết bị ảo

Nếu bạn không có điện thoại, máy tính bảng nhỏ hoặc máy tính bảng lớn chạy Android API cấp 32 trở lên, hãy mở Trình quản lý thiết bị trong Android Studio và tạo bất cứ thiết bị ảo nào sau đây mà bạn cần:

  • Điện thoại — Pixel 6, API cấp 32 trở lên
  • Máy tính bảng nhỏ — 7 WSVGA (Máy tính bảng), API cấp 32 trở lên
  • Máy tính bảng lớn – Pixel C, API cấp 32 trở lên

3. Chạy ứng dụng

Ứng dụng mẫu hiện danh sách các mục. Khi người dùng chọn một mục, ứng dụng sẽ hiện thông tin về mục đó.

Ứng dụng này bao gồm ba hoạt động:

  • ListActivity – Chứa danh sách các mục trong RecyclerView
  • DetailActivity – Hiện thông tin về một mục trong danh sách khi mục đó được chọn từ danh sách
  • SummaryActivity — Hiện thông tin tóm tắt khi mục trong danh sách Tóm tắt được chọn

Hành vi không được nhúng hoạt động

Chạy ứng dụng mẫu để xem hành vi của ứng dụng mà không cần nhúng hoạt động:

  1. Chạy ứng dụng mẫu trên máy tính bảng lớn hoặc trình mô phỏng Pixel C. Hoạt động chính (Danh sách) xuất hiện:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng dọc. Hoạt động Danh sách chạy trên toàn màn hình.

  1. Chọn một mục trong danh sách để chạy hoạt động phụ (Chi tiết). Hoạt động Chi tiết phủ lên hoạt động Danh sách:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng dọc. Hoạt động Chi tiết chạy trên toàn màn hình.

  1. Xoay máy tính bảng sang hướng ngang. Hoạt động phụ vẫn phủ lên hoạt động chính và chiếm toàn bộ màn hình:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng ngang. Hoạt động Chi tiết chạy trên toàn màn hình.

  1. Chọn nút điều khiển Quay lại (mũi tên hướng sang trái trong thanh ứng dụng) để trở lại danh sách.
  2. Chọn mục cuối cùng trong danh sách (Tóm tắt) để chạy hoạt động Tóm tắt dưới dạng hoạt động phụ. Hoạt động Tóm tắt sẽ bao phủ hoạt động Danh sách:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng dọc. Hoạt động Tóm tắt chạy trên toàn màn hình.

  1. Xoay máy tính bảng sang hướng ngang. Hoạt động phụ vẫn phủ lên hoạt động chính và chiếm toàn bộ màn hình:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng ngang. Hoạt động Tóm tắt chạy trên toàn màn hình.

Hành vi khi nhúng hoạt động

Khi bạn hoàn tất lớp học lập trình này, hướng ngang sẽ hiển thị danh sách và thông tin chi tiết về các hoạt động cạnh nhau theo bố cục danh sách-chi tiết:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng ngang. Các hoạt động Danh sách và Chi tiết trong bố cục danh sách-chi tiết.

Tuy nhiên, bạn sẽ định cấu hình hoạt động Tóm tắt để hiển thị toàn màn hình, mặc dù hoạt động này được khởi chạy từ trong phần phân tách. Hoạt động Tóm tắt sẽ chồng lên phần phân tách:

Máy tính bảng lớn có ứng dụng mẫu chạy theo hướng ngang. Hoạt động Tóm tắt chạy trên toàn màn hình.

4. Thông tin khái quát

Tính năng nhúng hoạt động chia cửa sổ tác vụ của ứng dụng thành hai vùng chứa: chính và phụ. Bất cứ hoạt động nào cũng có thể khởi tạo phần phân tách bằng cách khởi chạy một hoạt động khác. Hoạt động được khởi tạo sẽ chiếm vùng chứa chính; hoạt động được khởi chạy sẽ chiếm vùng chứa phụ.

Hoạt động chính có thể khởi chạy thêm các hoạt động khác trong vùng chứa phụ. Sau đó, các hoạt động trong cả hai vùng chứa có thể khởi chạy các hoạt động trong vùng chứa tương ứng của chúng. Mỗi vùng chứa có thể chứa một ngăn xếp hoạt động. Để biết thêm thông tin, hãy xem hướng dẫn Nhúng hoạt động dành cho nhà phát triển.

Bạn có thể định cấu hình ứng dụng để hỗ trợ tính năng nhúng hoạt động bằng cách tạo tệp cấu hình XML, hoặc thực hiện các lệnh gọi API Jetpack WindowManager. Chúng ta sẽ bắt đầu từ phương pháp tạo tệp cấu hình XML.

5. Cấu hình XML

Các vùng chứa và phần phân tách hoạt động được thư viện Jetpack WindowManager tạo và quản lý dựa trên các quy tắc phân tách mà bạn tạo trong tệp cấu hình XML.

Thêm phần phụ thuộc WindowManager

Bật ứng dụng mẫu để truy cập vào thư viện WindowManager bằng cách thêm phần phụ thuộc thư viện đó vào tệp build.gradle cấp mô-đun của ứng dụng, ví dụ:

build.gradle

 implementation 'androidx.window:window:1.2.0'

Thông báo cho hệ thống

Cho hệ thống biết ứng dụng của bạn đã triển khai tính năng nhúng hoạt động.

Thêm thuộc tính android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED vào phần tử <application> của tệp kê khai ứng dụng và thiết lập giá trị thành true:

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
            android:value="true" />
    </application>
</manifest>

Nhà sản xuất thiết bị (OEM) sử dụng chế độ cài đặt này để bật các tính năng tuỳ chỉnh cho những ứng dụng hỗ trợ tính năng nhúng hoạt động. Ví dụ: thiết bị có thể chạy các hoạt động nằm trong khung viền hộp thư, chỉ hiển thị theo chiều dọc (xem android:screenOrientation) trên màn hình ngang để định hướng cho việc chuyển đổi sang hoạt động được nhúng theo bố cục hai ngăn diễn ra suôn sẻ:

Tính năng nhúng hoạt động với ứng dụng chỉ hiển thị theo chiều dọc trên màn hình ngang. Hoạt động A (nằm trong khung viền hộp thư, chỉ hiển thị theo chiều dọc) khởi chạy hoạt động B được nhúng.

Tạo tệp cấu hình

Tạo tệp tài nguyên XML có tên main_split_config.xml trong thư mục res/xml của ứng dụng, trong đó resources là thành phần gốc.

Thay đổi không gian tên XML thành:

main_split_config.xml

xmlns:window="http://schemas.android.com/apk/res-auto"

Quy tắc phân tách cặp hoạt động

Thêm quy tắc phân tách sau đây vào tệp cấu hình:

main_split_config.xml

<!-- Define a split for the named activity pair. -->
<SplitPairRule
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithSecondary="never"
    window:finishSecondaryWithPrimary="always">
  <SplitPairFilter
      window:primaryActivityName=".ListActivity"
      window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Quy tắc này sẽ thực hiện những việc sau:

  • Định cấu hình các tuỳ chọn phân tách cho các hoạt động có chung phần phân tách:
  • splitRatio – Chỉ định xem hoạt động chính sẽ chiếm bao nhiêu phần (33%) trong cửa sổ tác vụ, không gian còn lại sẽ được để dành cho hoạt động phụ.
  • splitMinWidthDp – Chỉ định chiều rộng màn hình tối thiểu (840) cần thiết cho việc hiển thị đồng thời cả hai hoạt động trên màn hình. Đơn vị tính là pixel không phụ thuộc vào mật độ màn hình (dp).
  • finishPrimaryWithSecondary – Chỉ định liệu các hoạt động trong vùng chứa phân tách chính có kết thúc (không bao giờ) khi tất cả hoạt động trong vùng chứa phụ kết thúc.
  • finishSecondaryWithPrimary – Chỉ định xem các hoạt động trong vùng chứa phân tách phụ có kết thúc (luôn luôn) hay không khi tất cả hoạt động trong hoạt động vùng chứa chính kết thúc.
  • Bao gồm một bộ lọc phân tách xác định các hoạt động có chung cửa sổ tác vụ. Hoạt động chính là ListActivity; hoạt động phụ là DetailActivity.

Quy tắc giữ chỗ

Hoạt động giữ chỗ sẽ chiếm vùng chứa phụ của phần phân tách hoạt động khi không có nội dung nào cho vùng chứa đó, chẳng hạn như khi phần phân tách danh sách-chi tiết mở ra nhưng mục danh sách chưa được chọn. (Để biết thêm thông tin, hãy xem Hoạt động giữ chỗ trong hướng dẫn Nhúng hoạt động dành cho nhà phát triển.)

Thêm quy tắc giữ chỗ sau vào tệp cấu hình:

main_split_config.xml

<!-- Automatically launch a placeholder for the detail activity. -->
<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity"
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithPlaceholder="always"
    window:stickyPlaceholder="false">
  <ActivityFilter
      window:activityName=".ListActivity"/>
</SplitPlaceholderRule>

Quy tắc này sẽ thực hiện những việc sau:

  • Xác định hoạt động giữ chỗ, PlaceholderActivity (chúng ta sẽ tạo hoạt động này trong bước tiếp theo)
  • Định cấu hình các tuỳ chọn cho hoạt động giữ chỗ:
  • splitRatio – Chỉ định xem hoạt động chính sẽ chiếm bao nhiêu phần (33%) trong cửa sổ tác vụ, không gian còn lại sẽ được để dành cho hoạt động giữ chỗ. Thông thường, giá trị này phải khớp với tỷ lệ phân tách của quy tắc phân tách cặp hoạt động có liên kết với hoạt động giữ chỗ.
  • splitMinWidthDp – Chỉ định chiều rộng màn hình tối thiểu (840) cần thiết để hoạt động giữ chỗ xuất hiện trên màn hình cùng với hoạt động chính. Thông thường, giá trị này phải khớp với chiều rộng tối thiểu của quy tắc phân tách cặp hoạt động có liên kết với hoạt động giữ chỗ. Đơn vị tính là pixel không phụ thuộc vào mật độ màn hình (dp).
  • finishPrimaryWithPlaceholder – Chỉ định xem các hoạt động trong vùng chứa phân tách phụ có kết thúc (luôn luôn) hay không khi tất cả hoạt động trong hoạt động vùng chứa chính kết thúc.
  • stickyPlaceholder – Cho biết liệu có nên giữ lại (false) hoạt động giữ chỗ trên màn hình làm hoạt động hàng đầu hay không khi màn hình được đổi kích thước từ hai ngăn xuống còn một ngăn (ví dụ: khi thiết bị gập lại).
  • Bao gồm bộ lọc hoạt động chỉ định hoạt động (ListActivity) mà hoạt động giữ chỗ chia sẻ cửa sổ tác vụ.

Hoạt động giữ chỗ đại diện cho hoạt động phụ của quy tắc phân tách cặp hoạt động có hoạt động chính giống như hoạt động trong bộ lọc hoạt động giữ chỗ (xem "Quy tắc phân tách cặp hoạt động" trong phần "Cấu hình XML" của lớp học lập trình này).

Quy tắc hoạt động

Quy tắc hoạt động là những quy tắc chung. Các hoạt động mà bạn muốn chiếm toàn bộ cửa sổ tác vụ (có nghĩa là không bao giờ thuộc về một phần phân tách) có thể được chỉ định bằng quy tắc hoạt động. (Để biết thêm thông tin, hãy xem Chế độ toàn cửa sổ trong hướng dẫn Nhúng hoạt động dành cho nhà phát triển.)

Chúng ta sẽ làm cho hoạt động tóm tắt lấp đầy toàn bộ cửa sổ tác vụ, chồng lên phần phân tách. Thao tác quay lại sẽ khiến hoạt động trở về phần phân tách.

Hãy thêm quy tắc hoạt động sau đây vào tệp cấu hình:

main_split_config.xml

<!-- Activities that should never be in a split. -->
<ActivityRule
    window:alwaysExpand="true">
  <ActivityFilter
      window:activityName=".SummaryActivity"/>
</ActivityRule>

Quy tắc này sẽ thực hiện những việc sau:

  • Xác định hoạt động sẽ được hiển thị toàn màn hình (SummaryActivity).
  • Định cấu hình các tuỳ chọn cho hoạt động:
  • alwaysExpand – Chỉ định xem liệu hoạt động có cần mở rộng để lấp đầy tất cả không gian hiển thị có sẵn hay không.

Tệp nguồn

Tệp cấu hình XML hoàn chỉnh sẽ có dạng như sau:

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res-auto">

    <!-- Define a split for the named activity pair. -->
    <SplitPairRule
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithSecondary="never"
        window:finishSecondaryWithPrimary="always">
      <SplitPairFilter
          window:primaryActivityName=".ListActivity"
          window:secondaryActivityName=".DetailActivity"/>
    </SplitPairRule>

    <!-- Automatically launch a placeholder for the detail activity. -->
    <SplitPlaceholderRule
        window:placeholderActivityName=".PlaceholderActivity"
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithPlaceholder="always"
        window:stickyPlaceholder="false">
      <ActivityFilter
          window:activityName=".ListActivity"/>
    </SplitPlaceholderRule>

    <!-- Activities that should never be in a split. -->
    <ActivityRule
        window:alwaysExpand="true">
      <ActivityFilter
          window:activityName=".SummaryActivity"/>
    </ActivityRule>

</resources>

Tạo một hoạt động giữ chỗ

Bạn cần tạo một hoạt động mới để làm hoạt động giữ chỗ được chỉ định trong tệp cấu hình XML. Hoạt động có thể rất đơn giản—chỉ là cho người dùng biết nội dung cuối cùng sẽ xuất hiện tại đây.

Tạo hoạt động trong thư mục nguồn chính của ứng dụng mẫu.

Trong Android Studio, hãy làm như sau:

  1. Nhấp chuột phải (nhấp nút phụ) vào thư mục nguồn của ứng dụng mẫu com.example.activity_embedding
  2. Chọn New > Activity > Empty Views Activity (Mới > Hoạt động > Hoạt động của thành phần hiển thị trống
  3. Đặt tên cho hoạt động là PlaceholderActivity
  4. Chọn Finish (Hoàn tất).

Android Studio sẽ tạo hoạt động trong gói ứng dụng mẫu, thêm hoạt động vào tệp kê khai ứng dụng và tạo tệp tài nguyên bố cục có tên activity_placeholder.xml trong thư mục res/layout.

  1. Trong tệp AndroidManifest.xml của ứng dụng mẫu, hãy thiết lập nhãn cho hoạt động giữ chỗ thành một chuỗi trống:

AndroidManifest.xml

<activity
    android:name=".PlaceholderActivity"
    android:exported="false"
    android:label="" />
  1. Thay thế nội dung của tệp bố cục activity_placeholder.xml trong thư mục res/layout bằng nội dung sau:

activity_placeholder.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PlaceholderActivity">

  <TextView
      android:id="@+id/textViewPlaceholder"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/placeholder_text"
      android:textSize="36sp"
      android:textColor="@color/obsidian"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. Cuối cùng, hãy thêm tài nguyên chuỗi sau vào tệp tài nguyên strings.xml trong thư mục res/values:

strings.xml

<string name="placeholder_text">Placeholder</string>

Tạo trình khởi tạo

Thành phần WindowManager RuleController phân tích cú pháp các quy tắc được xác định trong tệp cấu hình XML và cung cấp các quy tắc đó cho hệ thống.

Trình khởi tạo của thư viện Startup của Jetpack cho phép RuleController truy cập vào tệp cấu hình.

Thư viện Startup thực hiện việc khởi tạo thành phần khi khởi động ứng dụng. Quá trình khởi tạo phải diễn ra trước khi mọi hoạt động bắt đầu để RuleController có quyền truy cập vào các quy tắc phân tách và có thể áp dụng các quy tắc này nếu cần.

Thêm phần phụ thuộc thư viện Startup

Để bật chức năng khởi động, hãy thêm phần phụ thuộc thư viện Startup vào tệp build.gradle cấp mô-đun của ứng dụng mẫu, ví dụ:

build.gradle

implementation 'androidx.startup:startup-runtime:1.1.1'

Triển khai trình khởi tạo cho RuleController

Tạo tuỳ chọn triển khai giao diện Startup Initializer (Trình khởi tạo khi bắt đầu).

Trong Android Studio, hãy làm như sau:

  1. Nhấp chuột phải (nhấp nút phụ) vào thư mục nguồn của ứng dụng mẫu com.example.activity_embedding
  2. Chọn New > Kotlin Class/File (Mới > Lớp/tệp Kotlin) hoặc New > Java Class (Mới > Lớp Java)
  3. Đặt tên cho lớp là SplitInitializer
  4. Nhấn phím Enter – Android Studio sẽ tạo lớp trong gói ứng dụng mẫu.
  5. Thay thế nội dung của tệp lớp bằng:

SplitInitializer.kt

package com.example.activity_embedding

import android.content.Context
import androidx.startup.Initializer
import androidx.window.embedding.RuleController

class SplitInitializer : Initializer<RuleController> {

  override fun create(context: Context): RuleController {
    return RuleController.getInstance(context).apply {
      setRules(RuleController.parseRules(context, R.xml.main_split_config))
    }
  }

  override fun dependencies(): List<Class<out Initializer<*>>> {
    return emptyList()
  }
}

SplitInitializer.java

package com.example.activity_embedding;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.startup.Initializer;
import androidx.window.embedding.RuleController;
import java.util.Collections;
import java.util.List;

public class SplitInitializer implements Initializer<RuleController> {

   @NonNull
   @Override
   public RuleController create(@NonNull Context context) {
      RuleController ruleController = RuleController.getInstance(context);
      ruleController.setRules(
          RuleController.parseRules(context, R.xml.main_split_config)
      );
      return ruleController;
   }

   @NonNull
   @Override
   public List<Class<? extends Initializer<?>>> dependencies() {
       return Collections.emptyList();
   }
}

Trình khởi tạo cung cấp các quy tắc phân tách cho thành phần RuleController bằng cách truyền mã nhận dạng của tệp tài nguyên XML chứa định nghĩa ((main_split_config) vào phương thức parseRules() của thành phần. Phương thức setRules() sẽ thêm các quy tắc đã phân tích cú pháp vào RuleController.

Tạo trình cung cấp cho quá trình khởi tạo

Trình cung cấp sẽ gọi quá trình khởi tạo quy tắc phân tách.

Thêm androidx.startup.InitializationProvider vào phần tử <application> của tệp kê khai của ứng dụng mẫu làm trình cung cấp và tham chiếu đến SplitInitializer:

AndroidManifest.xml

<provider android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- Make SplitInitializer discoverable by InitializationProvider. -->
    <meta-data android:name="${applicationId}.SplitInitializer"
        android:value="androidx.startup" />
</provider>

InitializationProvider khởi tạo SplitInitializer, từ đó gọi các phương thức RuleController phân tích cú pháp tệp cấu hình XML (main_split_config.xml) và thêm các quy tắc vào RuleController (xem phần "Triển khai trình khởi chạy cho RuleController" bên trên).

InitializationProvider khám phá và khởi chạy SplitInitializer trước khi phương thức onCreate() của ứng dụng thực thi. Và do đó, các quy tắc phân tách có hiệu lực khi hoạt động chính của ứng dụng bắt đầu.

Tệp nguồn

Dưới đây là tệp kê khai ứng dụng hoàn chỉnh:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

  <application
      android:allowBackup="true"
      android:dataExtractionRules="@xml/data_extraction_rules"
      android:fullBackupContent="@xml/backup_rules"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/Theme.Activity_Embedding"
      tools:targetApi="32">
    <activity
        android:name=".ListActivity"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity
        android:name=".DetailActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".SummaryActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".PlaceholderActivity"
        android:exported="false"
        android:label="" />
    <property
        android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
        android:value="true" />
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
      <!-- Make SplitInitializer discoverable by InitializationProvider. -->
      <meta-data
          android:name="${applicationId}.SplitInitializer"
          android:value="androidx.startup" />
    </provider>
  </application>

</manifest>

Phím tắt cho quá trình khởi tạo

Nếu bạn muốn kết hợp cấu hình XML với các API WindowManager, để triển khai đơn giản hơn, bạn có thể loại bỏ trình khởi chạy thư viện Startup và trình cung cấp tệp kê khai.

Sau khi bạn tạo tệp cấu hình XML, hãy làm như sau:

Bước 1: Tạo lớp con của Application

Lớp con của ứng dụng của bạn sẽ là lớp đầu tiên được tạo thực thể khi quy trình cho ứng dụng được tạo. Bạn sẽ thêm các quy tắc phân tách vào RuleController trong phương thức onCreate() của lớp con để đảm bảo các quy tắc có hiệu lực trước khi chạy bất cứ hoạt động nào.

Trong Android Studio, hãy làm như sau:

  1. Nhấp chuột phải (nhấp nút phụ) vào thư mục nguồn của ứng dụng mẫu com.example.activity_embedding
  2. Chọn New > Kotlin Class/File (Mới > Lớp/tệp Kotlin) hoặc New > Java Class (Mới > Lớp Java)
  3. Đặt tên cho lớp là SampleApplication
  4. Nhấn phím Enter – Android Studio sẽ tạo lớp trong gói ứng dụng mẫu
  5. Mở rộng lớp này từ siêu kiểu Application

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

}

Bước 2: Khởi tạo RuleController

Thêm các quy tắc phân tách từ tệp cấu hình XML vào RuleController trong phương thức onCreate() của lớp con ứng dụng.

Để thêm quy tắc vào RuleController, hãy làm như sau:

  1. Nhận thực thể singleton của RuleController
  2. Sử dụng phương thức parseRules() của RuleController (dưới dạng phương thức tĩnh bằng Java hoặc phương thức đồng hành Kotlin) để phân tích cú pháp tệp XML
  3. Thêm các quy tắc đã phân tích cú pháp vào RuleController bằng phương thức setRules()

SampleApplication.kt

override fun onCreate() {
  super.onCreate()
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config))
}

SampleApplication.java

@Override
public void onCreate() {
  super.onCreate();
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config));
}

Bước 3: Thêm tên lớp con vào tệp kê khai

Thêm tên lớp con vào phần tử <application> của tệp kê khai ứng dụng:

AndroidManifest.xml

<application
    android:name=".SampleApplication"
    . . .

Chạy ứng dụng!

Tạo bản dựng và chạy ứng dụng mẫu

Trên điện thoại không gập lại được, các hoạt động luôn được xếp chồng — ngay cả theo hướng ngang:

Hoạt động Chi tiết (phụ) được xếp chồng lên trên hoạt động Danh sách (chính) trên điện thoại theo hướng dọc. Hoạt động Chi tiết (phụ) được xếp chồng lên trên hoạt động Danh sách (chính) trên điện thoại theo hướng ngang.

Trên Android 13 (API cấp 33) trở xuống, tính năng nhúng hoạt động không được bật trên các điện thoại không gập lại được, bất kể thông số kỹ thuật chiều rộng tối thiểu của phần phân tách.

Tính năng nhúng hoạt động có được hỗ trợ cho điện thoại không gập lại được trên các cấp độ API cao hơn hay không là tuỳ thuộc vào việc nhà sản xuất thiết bị có bật tính năng nhúng hoạt động hay không.

Trên máy tính bảng nhỏ hoặc trình mô phỏng 7 WSVGA (Máy tính bảng), hai hoạt động được xếp chồng theo hướng dọc, nhưng sẽ xuất hiện cạnh nhau theo hướng ngang:

Các hoạt động Danh sách và Chi tiết được xếp chồng theo hướng dọc trên máy tính bảng nhỏ. Các hoạt động Danh sách và Chi tiết nằm cạnh nhau theo hướng ngang trên máy tính bảng nhỏ.

Trên máy tính bảng lớn hoặc trình mô phỏng Pixel C, các hoạt động được xếp chồng theo hướng dọc (xem "Tỷ lệ khung hình" bên dưới), nhưng sẽ xuất hiện cạnh nhau theo hướng ngang:

Các hoạt động Danh sách và Chi tiết được xếp chồng theo hướng dọc trên máy tính bảng lớn. Các hoạt động Danh sách và Chi tiết nằm cạnh nhau theo hướng ngang trên máy tính bảng lớn.

Hoạt động Tóm tắt sẽ hiển thị toàn màn hình ở chế độ ngang ngay cả khi được khởi chạy từ trong phần phân tách:

Hoạt động Tóm tắt phủ lên phần phân tách theo hướng ngang trên máy tính bảng lớn.

Tỷ lệ khung hình

Ngoài chiều rộng tối thiểu, các phần phân tách hoạt động được kiểm soát bằng tỷ lệ khung hình của màn hình. Thuộc tính splitMaxAspectRatioInPortraitsplitMaxAspectRatioInLandscape chỉ định tỷ lệ khung hình hiển thị tối đa (chiều dài:chiều rộng) mà phần phân tách hoạt động được hiển thị. Các thuộc tính này đại diện cho các thuộc tính maxAspectRatioInPortraitmaxAspectRatioInLandscape của SplitRule.

Nếu tỷ lệ khung hình của màn hình vượt quá giá trị ngưỡng của một trong hai hướng, thì các phần tách sẽ bị tắt, bất kể chiều rộng màn hình là bao nhiêu. Giá trị mặc định cho hướng dọc là 1,4 (xem SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT). Giá trị này ngăn không cho màn hình cao, hẹp hiển thị các phần phân tách. Theo mặc định, các phần phân tách luôn được cho phép theo hướng ngang (xem SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT).

Trình mô phỏng PIxel C có chiều rộng hiển thị dọc là 900 dp, rộng hơn chế độ cài đặt splitMinWidthDp trong tệp cấu hình XML của ứng dụng mẫu, do đó trình mô phỏng sẽ hiển thị phần phân tách hoạt động. Tuy nhiên, tỷ lệ khung hình của Pixel C ở hướng dọc lớn hơn 1,4. Điều này ngăn các phần phân tách hoạt động hiển thị theo hướng dọc.

Bạn có thể thiết lập tỷ lệ khung hình tối đa cho màn hình dọc và ngang trong tệp cấu hình XML trong phần tử SplitPairRuleSplitPlaceholderRule, ví dụ:

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res/android">

  <!-- Define a split for the named activity pair. -->
  <SplitPairRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
 </SplitPairRule>

  <SplitPlaceholderRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
  </SplitPlaceholderRule>

</resources>

Trên máy tính bảng lớn có chiều rộng màn hình dọc lớn hơn hoặc bằng 840dp hoặc trình mô phỏng Pixel C, các hoạt động sẽ nằm cạnh nhau theo hướng dọc nhưng được xếp chồng theo hướng ngang:

Các hoạt động Danh sách và Chi tiết nằm cạnh nhau theo hướng dọc trên máy tính bảng lớn. Các hoạt động Danh sách và Chi tiết được xếp chồng theo hướng ngang trên máy tính bảng lớn.

Mở rộng kiến thức

Hãy thử thiết lập tỷ lệ khung hình trong ứng dụng mẫu như hướng dẫn ở trên để xem hướng dọc và ngang. Hãy kiểm tra chế độ cài đặt với máy tính bảng lớn (nếu chiều rộng màn hình dọc là 840dp trở lên) hoặc trình mô phỏng Pixel C. Bạn sẽ thấy hoạt động được phân tách theo hướng dọc nhưng không nằm ngang.

Xác định tỷ lệ khung hình dọc của máy tính bảng lớn (tỷ lệ khung hình của Pixel C lớn hơn 1,4 một chút. Thiết lập splitMaxAspectRatioInPortrait thành giá trị cao hơn và thấp hơn tỷ lệ khung hình. Chạy ứng dụng và xem kết quả mà bạn nhận được.

6. API WindowManager

Bạn có thể bật tính năng nhúng hoạt động hoàn toàn trong mã bằng một phương thức duy nhất được gọi trong phương thức onCreate() của hoạt động khởi tạo phần phân tách. Nếu bạn thích làm việc bằng mã hơn là XML, thì đây là cách để thực hiện.

Thêm phần phụ thuộc WindowManager

Ứng dụng của bạn cần quyền truy cập vào thư viện WindowManager, cho dù bạn đang triển khai dựa trên XML hay sử dụng lệnh gọi API. Xem phần "Cấu hình XML" của lớp học lập trình này để biết cách thêm phần phụ thuộc WindowManager vào ứng dụng.

Thông báo cho hệ thống

Bất kể sử dụng tệp cấu hình XML hay các lệnh gọi API WindowManager, ứng dụng của bạn đều phải thông báo cho hệ thống rằng ứng dụng đã triển khai tính năng nhúng hoạt động. Xem phần "Cấu hình XML" của lớp học lập trình này để biết cách thông báo cho hệ thống khi triển khai.

Tạo lớp để quản lý phần phân tách

Trong phần này của lớp học lập trình, bạn sẽ triển khai phần phân tách hoạt động hoàn toàn trong một phương thức đối tượng tĩnh hoặc đối tượng đồng hành duy nhất mà bạn sẽ gọi từ hoạt động chính của ứng dụng mẫu, ListActivity.

Tạo một lớp có tên là SplitManager bằng một phương thức có tên là createSplit bao gồm tham số context (một số lệnh gọi API yêu cầu tham số):

SplitManager.kt

class SplitManager {

    companion object {

        fun createSplit(context: Context) {
        }
}

SplitManager.java

class SplitManager {

    static void createSplit(Context context) {
    }
}

Gọi phương thức này trong phương thức onCreate() của một lớp con của lớp Application.

Để biết thông tin chi tiết về lý do và cách tạo lớp con Application, hãy xem "Lối tắt khởi tạo" trong phần "Cấu hình XML" của lớp học lập trình này.

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    SplitManager.createSplit(this)
  }
}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    SplitManager.createSplit(this);
  }
}

Tạo một quy tắc phân tách:

API bắt buộc:

SplitPairRule xác định quy tắc phân tách cho một cặp hoạt động.

SplitPairRule.Builder tạo một SplitPairRule. Trình tạo này lấy một nhóm đối tượng SplitPairFilter làm đối số. Bộ lọc sẽ chỉ định thời điểm áp dụng quy tắc.

Bạn đăng ký quy tắc đó với một thực thể singleton của thành phần RuleController, giúp cung cấp các quy tắc phân tách hoạt động cho hệ thống.

Để tạo quy tắc phân tách, hãy làm như sau:

  1. Tạo bộ lọc phân tách cặp hoạt động xác định ListActivityDetailActivity là các hoạt động có chung phần phân tách:

SplitManager.kt / createSplit()

val splitPairFilter = SplitPairFilter(
    ComponentName(context, ListActivity::class.java),
    ComponentName(context, DetailActivity::class.java),
    null
)

SplitManager.java / createSplit()

SplitPairFilter splitPairFilter = new SplitPairFilter(
    new ComponentName(context, ListActivity.class),
    new ComponentName(context, DetailActivity.class),
    null
);

Bộ lọc có thể đưa một thao tác theo ý định (tham số thứ ba) để khởi chạy hoạt động phụ. Nếu bạn đưa một thao tác theo ý định, bộ lọc sẽ kiểm tra thao tác đó cùng với tên hoạt động. Đối với các hoạt động trong ứng dụng của riêng bạn, có thể bạn sẽ không lọc thao tác theo ý định, vì vậy đối số có thể rỗng.

  1. Thêm bộ lọc vào một nhóm bộ lọc:

SplitManager.kt / createSplit()

val filterSet = setOf(splitPairFilter)

SplitManager.java / createSplit()

Set<SplitPairFilter> filterSet = new HashSet<>();
filterSet.add(splitPairFilter);
  1. Tạo thuộc tính bố cục cho phần phân tách:

SplitManager.kt / createSplit()

val splitAttributes: SplitAttributes = SplitAttributes.Builder()
      .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
      .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
      .build()

SplitManager.java / createSplit()

SplitAttributes splitAttributes = new SplitAttributes.Builder()
  .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
  .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
  .build();

SplitAttributes.Builder tạo một đối tượng chứa các thuộc tính bố cục:

  • setSplitType: Xác định cách phân bổ khu vực màn hình có sẵn cho từng vùng chứa hoạt động. Kiểu tỷ lệ phân tách chỉ định tỷ lệ màn hình được chiếm bởi vùng chứa chính; vùng chứa phụ chiếm khu vực hiển thị còn lại.
  • setLayoutDirection: Chỉ định cách bố trí các vùng chứa hoạt động so với một vùng chứa khác, vùng chứa chính được ưu tiên.
  1. Xây dựng quy tắc phân tách cặp hoạt động:

SplitManager.kt / createSplit()

val splitPairRule = SplitPairRule.Builder(filterSet)
      .setDefaultSplitAttributes(splitAttributes)
      .setMinWidthDp(840)
      .setMinSmallestWidthDp(600)
      .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
      .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
      .setClearTop(false)
      .build()

SplitManager.java / createSplit()

SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
  .setDefaultSplitAttributes(splitAttributes)
  .setMinWidthDp(840)
  .setMinSmallestWidthDp(600)
  .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
  .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
  .setClearTop(false)
  .build();

SplitPairRule.Builder tạo và định cấu hình quy tắc:

  • filterSet: Chứa các bộ lọc phân tách cặp hoạt động xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động có cùng phần phân tách. Trong ứng dụng mẫu, ListActivityDetailActivity được chỉ định trong bộ lọc phân tách cặp hoạt động (xem các bước trước đó).
  • setDefaultSplitAttributes: Áp dụng các thuộc tính bố cục cho quy tắc.
  • setMinWidthDp: Thiết lập chiều rộng màn hình tối thiểu (tính bằng pixel không phụ thuộc vào mật độ, dp) cho phép phân tách.
  • setMinSmallestWidthDp: Thiết lập giá trị tối thiểu (tính bằng dp) mà giá trị nhỏ hơn trong hai kích thước màn hình phải cho phép phân tách, bất kể hướng của thiết bị.
  • setFinishPrimaryWithSecondary: Việc hoàn tất mọi hoạt động trong vùng chứa phụ ảnh hưởng đến các hoạt động trong vùng chứa chính NEVER cho biết hệ thống không nên kết thúc các hoạt động chính khi tất cả hoạt động trong vùng chứa phụ kết thúc. (Xem Kết thúc hoạt động.)
  • setFinishSecondaryWithPrimary: Việc hoàn tất mọi hoạt động trong vùng chứa chính ảnh hưởng đến các hoạt động trong vùng chứa phụ ALWAYS cho biết hệ thống phải luôn kết thúc các hoạt động trong vùng chứa phụ khi tất cả hoạt động trong vùng chứa chính kết thúc. (Xem Kết thúc hoạt động.)
  • setClearTop: Chỉ định xem liệu tất cả các hoạt động trong vùng chứa phụ đã hoàn tất hay chưa khi một hoạt động mới được chạy trong vùng chứa đó Giá trị False chỉ định rằng các hoạt động mới được xếp chồng lên trên các hoạt động đã có trong vùng chứa phụ.
  1. Lấy bản sao singleton của WindowManager RuleController và thêm quy tắc:

SplitManager.kt / createSplit()

val ruleController = RuleController.getInstance(context)
ruleController.addRule(splitPairRule)

SplitManager.java / createSplit()

RuleController ruleController = RuleController.getInstance(context);
ruleController.addRule(splitPairRule);

Tạo quy tắc giữ chỗ

API bắt buộc:

SplitPlaceholderRule xác định quy tắc cho một hoạt động chiếm vùng chứa phụ khi không có sẵn nội dung nào đối với vùng chứa đó. Để tạo một hoạt động giữ chỗ, hãy xem phần "Tạo hoạt động giữ chỗ" trong phần "Cấu hình XML" của lớp học lập trình này. (Để biết thêm thông tin, hãy xem Hoạt động giữ chỗ trong hướng dẫn Nhúng hoạt động dành cho nhà phát triển.)

SplitPlaceholderRule.Builder tạo một SplitPlaceholderRule. Trình tạo này lấy một nhóm đối tượng ActivityFilter làm đối số. Các đối tượng sẽ chỉ định các hoạt động liên kết với quy tắc giữ chỗ. Nếu bộ lọc khớp với một hoạt động đã bắt đầu, thì hệ thống sẽ áp dụng quy tắc giữ chỗ.

Bạn sẽ đăng ký quy tắc đó với thành phần RuleController.

Để tạo quy tắc giữ chỗ phần phân tách, hãy làm như sau:

  1. Tạo một ActivityFilter:

SplitManager.kt / createSplit()

val placeholderActivityFilter = ActivityFilter(
    ComponentName(context, ListActivity::class.java),
    null
)

SplitManager.java / createSplit()

ActivityFilter placeholderActivityFilter = new ActivityFilter(
    new ComponentName(context, ListActivity.class),
    null
);

Bộ lọc liên kết quy tắc với hoạt động chính của ứng dụng mẫu (ListActivity). Vì vậy, khi không có nội dung chi tiết nào trong bố cục danh sách-chi tiết, hoạt động giữ chỗ sẽ điền vào vùng nội dung chi tiết.

Bộ lọc có thể đưa một thao tác theo ý định (tham số thứ hai) để khởi chạy hoạt động được liên kết (khởi chạy ListActivity). Nếu bạn đưa một thao tác theo ý định, bộ lọc sẽ kiểm tra thao tác đó cùng với tên hoạt động. Đối với các hoạt động trong ứng dụng của riêng bạn, có thể bạn sẽ không lọc thao tác theo ý định, vì vậy đối số có thể rỗng.

  1. Thêm bộ lọc vào một nhóm bộ lọc:

SplitManager.kt / createSplit()

val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

SplitManager.java / createSplit()

Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
placeholderActivityFilterSet.add(placeholderActivityFilter);
  1. Tạo một SplitPlaceholderRule:

SplitManager.kt / createSplit()

val splitPlaceholderRule = SplitPlaceholderRule.Builder(
      placeholderActivityFilterSet,
      Intent(context, PlaceholderActivity::class.java)
    ).setDefaultSplitAttributes(splitAttributes)
     .setMinWidthDp(840)
     .setMinSmallestWidthDp(600)
     .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
     .build()

SplitManager.java / createSplit()

SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
  placeholderActivityFilterSet,
  new Intent(context, PlaceholderActivity.class)
).setDefaultSplitAttributes(splitAttributes)
 .setMinWidthDp(840)
 .setMinSmallestWidthDp(600)
 .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
 .build();

SplitPlaceholderRule.Builder tạo và định cấu hình quy tắc:

  • placeholderActivityFilterSet: Chứa các bộ lọc hoạt động xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động liên kết với hoạt động giữ chỗ.
  • Intent: Chỉ định khởi chạy hoạt động giữ chỗ.
  • setDefaultSplitAttributes: Áp dụng các thuộc tính bố cục cho quy tắc.
  • setMinWidthDp: Thiết lập chiều rộng màn hình tối thiểu (tính bằng pixel không phụ thuộc vào mật độ, dp) cho phép phân tách.
  • setMinSmallestWidthDp: Thiết lập giá trị tối thiểu (tính bằng dp) mà giá trị nhỏ hơn trong hai kích thước màn hình phải cho phép phân tách, bất kể hướng của thiết bị.
  • setFinishPrimaryWithPlaceholder: Thiết lập mức độ ảnh hưởng của việc hoàn tất hoạt động giữ chỗ đến các hoạt động trong vùng chứa chính. ALWAYS cho biết hệ thống phải luôn hoàn tất các hoạt động trong vùng chứa chính khi hoạt động giữ chỗ hoàn tất. (Xem Kết thúc hoạt động.)
  1. Thêm quy tắc vào RuleController của WindowManager:

SplitManager.kt / createSplit()

ruleController.addRule(splitPlaceholderRule)

SplitManager.java / createSplit()

ruleController.addRule(splitPlaceholderRule);

Tạo quy tắc hoạt động

API bắt buộc:

Bạn có thể sử dụng ActivityRule để xác định quy tắc cho một hoạt động chiếm toàn bộ cửa sổ tác vụ, chẳng hạn như hộp thoại phương thức. (Để biết thêm thông tin, hãy xem phần Phương thức toàn cửa sổ trong hướng dẫn Nhúng hoạt động dành cho nhà phát triển.)

SplitPlaceholderRule.Builder tạo một SplitPlaceholderRule. Trình tạo này lấy một nhóm đối tượng ActivityFilter làm đối số. Các đối tượng sẽ chỉ định các hoạt động liên kết với quy tắc giữ chỗ. Nếu bộ lọc khớp với một hoạt động đã bắt đầu, thì hệ thống sẽ áp dụng quy tắc giữ chỗ.

Bạn sẽ đăng ký quy tắc đó với thành phần RuleController.

Để tạo quy tắc hoạt động, hãy làm như sau:

  1. Tạo một ActivityFilter:

SplitManager.kt / createSplit()

val summaryActivityFilter = ActivityFilter(
    ComponentName(context, SummaryActivity::class.java),
    null
)

SplitManager.java / createSplit()

ActivityFilter summaryActivityFilter = new ActivityFilter(
    new ComponentName(context, SummaryActivity.class),
    null
);

Bộ lọc chỉ định hoạt động sẽ được áp dụng quy tắc, SummaryActivity.

Bộ lọc có thể đưa một thao tác theo ý định (tham số thứ hai) để khởi chạy hoạt động được liên kết (khởi chạy SummaryActivity). Nếu bạn đưa một thao tác theo ý định, bộ lọc sẽ kiểm tra thao tác đó cùng với tên hoạt động. Đối với các hoạt động trong ứng dụng của riêng bạn, có thể bạn sẽ không lọc thao tác theo ý định, vì vậy đối số có thể rỗng.

  1. Thêm bộ lọc vào một nhóm bộ lọc:

SplitManager.kt / createSplit()

val summaryActivityFilterSet = setOf(summaryActivityFilter)

SplitManager.java / createSplit()

Set<ActivityFilter> summaryActivityFilterSet = new HashSet<>();
summaryActivityFilterSet.add(summaryActivityFilter);
  1. Tạo một ActivityRule:

SplitManager.kt / createSplit()

val activityRule = ActivityRule.Builder(summaryActivityFilterSet)
      .setAlwaysExpand(true)
      .build()

SplitManager.java / createSplit()

ActivityRule activityRule = new ActivityRule.Builder(
    summaryActivityFilterSet
).setAlwaysExpand(true)
 .build();

ActivityRule.Builder tạo và định cấu hình quy tắc:

  • summaryActivityFilterSet: Chứa các bộ lọc hoạt động xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động mà bạn muốn loại trừ khỏi phần phân tách.
  • setAlwaysExpand: Chỉ định xem liệu hoạt động có cần mở rộng để lấp đầy tất cả không gian hiển thị có sẵn hay không.
  1. Thêm quy tắc vào RuleController của WindowManager:

SplitManager.kt / createSplit()

ruleController.addRule(activityRule)

SplitManager.java / createSplit()

ruleController.addRule(activityRule);

Chạy ứng dụng!

Tạo bản dựng và chạy ứng dụng mẫu

Ứng dụng phải hoạt động giống như khi được tuỳ chỉnh bằng tệp cấu hình XML.

Hãy xem phần "Chạy chương trình!" trong phần "Cấu hình XML" của lớp học lập trình này.

Mở rộng kiến thức

Hãy thử thiết lập tỷ lệ khung hình trong ứng dụng mẫu bằng cách sử dụng phương thức setMaxAspectRatioInPortraitsetMaxAspectRatioInLandscape của SplitPairRule.BuilderSplitPlaceholderRule.Builder. Chỉ định các giá trị bằng các thuộc tính và phương thức của lớp EmbeddingAspectRatio, ví dụ:

SplitPairRule.Builder(filterSet)
  . . .
  .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
  . . .
.build()

Kiểm tra chế độ cài đặt với máy tính bảng lớn hoặc trình mô phỏng Pixel C.

Xác định tỷ lệ khung hình dọc của máy tính bảng lớn (tỷ lệ khung hình của Pixel C lớn hơn 1,4 một chút). Thiết lập tỷ lệ khung hình tối đa theo hướng dọc thành các giá trị cao hơn và thấp hơn so với tỷ lệ khung hình của máy tính bảng hoặc Pixel C. Hãy thử dùng thuộc tính ALWAYS_ALLOWALWAYS_DISALLOW.

Chạy ứng dụng và xem kết quả mà bạn nhận được.

Để biết thêm thông tin, hãy xem phần "Tỷ lệ khung hình" trong phần "Cấu hình XML" của lớp học lập trình này.

7. Thành phần điều hướng tuân theo nguyên tắc của Material Design

Nguyên tắc của Material Design nêu rõ các thành phần điều hướng của nhiều kích thước màn hình – một dải điều hướng cho màn hình có kích thước lớn hơn hoặc bằng 840 dp, một thanh điều hướng ở dưới cùng cho các màn hình có kích thước nhỏ hơn 840 dp.

fb47462060f4818d.gif

Với tính năng nhúng hoạt động, bạn không sử dụng được các phương thức WindowManager getCurrentWindowMetrics()getMaximumWindowMetrics() để xác định độ rộng màn hình vì các chỉ số cửa sổ do các phương thức này trả về mô tả ngăn hiển thị có chứa hoạt động được nhúng đã gọi các phương thức đó.

Để nhận kích thước chính xác của ứng dụng hỗ trợ tính năng nhúng hoạt động, hãy dùng một hàm tính thuộc tính phần chia táchSplitAttributesCalculatorParams.

Xoá các dòng sau nếu bạn đã thêm vào phần trước.

main_split_config.xml

<SplitPairRule
    . . .
    window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
    window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
    . . .>
</SplitPairRule>

<SplitPlaceholderRule
    . . .

    window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
    window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
    . . .>
<SplitPlaceholderRule/>

Thành phần điều hướng linh hoạt

Để linh hoạt chuyển đổi các thành phần điều hướng dựa vào kích thước màn hình, hãy dùng một hàm tính SplitAttributes. Hàm tính này phát hiện các thay đổi về hướng thiết bị và kích thước cửa sổ, đồng thời tính lại các kích thước màn hình sao cho phù hợp. Chúng tôi sẽ tích hợp một hàm tính với một SplitController để kích hoạt các thay đổi về thành phần điều hướng sao cho phù hợp với kích thước màn hình đã thay đổi.

Tạo bố cục điều hướng

Trước tiên, hãy tạo một trình đơn mà chúng ta sẽ dùng để điền dải điều hướng và thanh điều hướng.

Trong thư mục res/menu, tạo một tệp tài nguyên trình đơn mới có tên nav_menu.xml. Thay thế nội dung của tệp trình đơn bằng:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navigation_home"
        android:title="Home" />
    <item
        android:id="@+id/navigation_dashboard"
        android:title="Dashboard" />
    <item
        android:id="@+id/navigation_settings"
        android:title="Settings" />
</menu>

Tiếp theo, thêm một thanh điều hướng và dải điều hướng vào bố cục. Đặt chế độ hiển thị của thanh và dải điều hướng này thành gone vì ban đầu chúng bị ẩn. Sau này, chúng tôi sẽ hiển thị chúng theo kích thước bố cục.

activity_list.xml

<com.google.android.material.navigationrail.NavigationRailView
     android:id="@+id/navigationRailView"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent"
     app:menu="@menu/nav_menu"
     android:visibility="gone" />

<com.google.android.material.bottomnavigation.BottomNavigationView
   android:id="@+id/bottomNavigationView"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   app:menu="@menu/nav_menu"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   android:visibility="gone" />

Viết một hàm để xử lý hoạt động chuyển đổi giữa thanh điều hướng và dải điều hướng.

ListActivity.kt/setWiderScreenNavigation()

private fun setWiderScreenNavigation(useNavRail: Boolean) {
   val navRail = findViewById(R.id.navigationRailView)
   val bottomNav = findViewById(R.id.bottomNavigationView)
   if (useNavRail) {
       navRail.visibility = View.VISIBLE
       bottomNav.visibility = View.GONE
   } else {
       navRail.visibility = View.GONE
       bottomNav.visibility = View.VISIBLE
   }
}

ListActivity.java/setWiderScreenNavigation()

private void setWiderScreenNavigation(boolean useNavRail) {
   NavigationRailView navRail = findViewById(R.id.navigationRailView);
   BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);
   if (useNavRail) {
       navRail.setVisibility(View.VISIBLE);
       bottomNav.setVisibility(View.GONE);
   } else {
       navRail.setVisibility(View.GONE);
       bottomNav.setVisibility(View.VISIBLE);
   }
}

Hàm tính thuộc tính phần chia tách

SplitController nhận thông tin về các lượt chia tách hoạt động hiện hoạt, đồng thời cung cấp những điểm tương tác để tuỳ chỉnh các lượt chia tách đó và tạo lượt chia tách mới.

Ở những phần trước, chúng ta đã đặt các thuộc tính mặc định cho các lượt chia tách bằng cách chỉ định splitRatio cùng những thuộc tính khác trong các thẻ <SplitPairRule><SplitPlaceHolderRule> trong tệp XML hoặc bằng cách sử dụng các API SplitPairRule.Builder#setDefaultSplitAttributes()SplitPlaceholderRule.Builder#setDefaultSplitAttributes().

Các thuộc tính phần chia tách mặc định được áp dụng nếu WindowMetrics của vùng chứa chính đáp ứng các yêu cầu về kích thước SplitRule, đó là minWidthDp, minHeightDpminSmallestWidthDp.

Chúng ta sẽ đặt một hàm tính thuộc tính phần chia tách để thay thế các thuộc tính phần chia tách mặc định. Hàm tính này cập nhật các cặp chia tách hiện có sau khi thấy một thay đổi về trạng thái của cửa sổ hoặc thiết bị, chẳng hạn như thay đổi về hướng hoặc thay đổi về trạng thái gập.

Nhờ vậy, các nhà phát triển có thể biết trạng thái của thiết bị/cửa sổ và đặt các thuộc tính phần chia tách tuỳ theo trường hợp, bao gồm cả hướng dọc và ngang cũng như tư thế trên mặt bàn.

Khi tạo một hàm tính thuộc tính phần chia tách, nền tảng truyền một đối tượng SplitAttributesCalculatorParams đến hàm setSplitAttributesCalculator(). Thuộc tính parentWindowMetrics cung cấp các chỉ số về cửa sổ ứng dụng.

Trong mã sau, hoạt động sẽ kiểm tra xem các điều kiện ràng buộc mặc định có được đáp ứng hay không, tức là chiều rộng > 840 dp và chiều rộng nhỏ nhất > 600 dp. Khi những điều kiện đó được đáp ứng, các hoạt động được nhúng vào một bố cục hai ngăn và ứng dụng sẽ dùng một dải điều hướng thay vì thanh điều hướng ở dưới cùng. Nếu không, các hoạt động sẽ hiển thị ở chế độ toàn màn hình với một thanh điều hướng ở dưới cùng.

ListActivity.kt/setSplitAttributesCalculator()

SplitController.getInstance(this).setSplitAttributesCalculator
       params ->

   if (params.areDefaultConstraintsSatisfied) {
       // When default constraints are satisfied, use the navigation rail.
       setWiderScreenNavigation(true)
       return@setSplitAttributesCalculator params.defaultSplitAttributes
   } else {
       // Use the bottom navigation bar in other cases.
       setWiderScreenNavigation(false)
       // Expand containers if the device is in portrait or the width is less than 840 dp.
       SplitAttributes.Builder()
           .setSplitType(SPLIT_TYPE_EXPAND)
           .build()
   }
}

ListActivity.java/setSplitAttributesCalculator()

SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
   if (params.areDefaultConstraintsSatisfied()) {
       // When default constraints are satisfied, use the navigation rail.
       setWiderScreenNavigation(true);
       return params.getDefaultSplitAttributes();
   } else {
       // Use the bottom navigation bar in other cases.
       setWiderScreenNavigation(false);
       // Expand containers if the device is in portrait or the width is less than 600 dp.
       return new SplitAttributes.Builder()
               .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
               .build();
   }
});

Tốt lắm, ứng dụng hỗ trợ tính năng nhúng hoạt động giờ đã tuân thủ các nguyên tắc của Material Design về thành phần điều hướng!

8. Xin chúc mừng!

Rất tốt! Bạn đã tối ưu hoá một ứng dụng dựa trên hoạt động theo bố cục danh sách-chi tiết trên màn hình lớn và đã thêm thành phần điều hướng tuân theo nguyên tắc của Material Design.

Bạn đã tìm hiểu 2 cách triển khai tính năng nhúng hoạt động:

  • Sử dụng tệp cấu hình XML
  • Thực hiện lệnh gọi API Jetpack
  • Triển khai thành phần điều hướng linh hoạt với tính năng Nhúng hoạt động

Và bạn đã không viết lại bất cứ mã nguồn Kotlin hoặc Java nào của ứng dụng.

Thông qua tính năng nhúng hoạt động, bạn đã sẵn sàng tối ưu hoá các ứng dụng cải thiện hiệu suất cho màn hình lớn!

9. Tìm hiểu thêm