Ngữ nghĩa trong Compose

Thành phần kết hợp mô tả giao diện người dùng của ứng dụng và được tạo ra bằng cách chạy thành phần kết hợp. Cấu trúc là một cấu trúc cây bao gồm các thành phần kết hợp mô tả giao diện người dùng.

Bên cạnh Cấu trúc, có một cây song song, gọi là ngữ nghĩa cây. Cây này mô tả giao diện người dùng theo một cách khác dễ hiểu đối với các dịch vụ Hỗ trợ tiếp cận và cho Thử nghiệm khung. Các dịch vụ hỗ trợ tiếp cận sử dụng cây này để mô tả ứng dụng cho người dùng một nhu cầu cụ thể. Khung kiểm thử sử dụng cây để tương tác với ứng dụng của bạn và khẳng định về điều đó. Cây Ngữ nghĩa không chứa thông tin về cách vẽ các thành phần kết hợp, nhưng có chứa thông tin về ý nghĩa ngữ nghĩa của các thành phần kết hợp.

Một hệ phân cấp giao diện người dùng điển hình và cây ngữ nghĩa
Hình 1. Một hệ phân cấp giao diện người dùng điển hình và cây ngữ nghĩa.

Nếu ứng dụng của bạn bao gồm các thành phần kết hợp và đối tượng sửa đổi từ nền tảng Compose và thư viện tài liệu, thì cây ngữ nghĩa sẽ được tự động điền và tạo cho bạn. Tuy nhiên, khi thêm thành phần kết hợp tuỳ chỉnh cấp thấp, bạn phải để cung cấp ngữ nghĩa theo cách thủ công. Cũng có thể có các trường hợp mà cây của bạn không thể hiện chính xác hoặc đầy đủ ý nghĩa của các phần tử trên màn hình, trong trường hợp đó, bạn có thể điều chỉnh cây.

Xem xét ví dụ về thành phần kết hợp lịch tuỳ chỉnh này:

Một thành phần kết hợp lịch tuỳ chỉnh với các phần tử ngày có thể chọn
Hình 2. Một thành phần kết hợp lịch tuỳ chỉnh với các phần tử ngày có thể chọn.

Trong ví dụ này, toàn bộ lịch được triển khai dưới dạng thành phần kết hợp cấp thấp, bằng cách sử dụng Layout có thể kết hợp và vẽ trực tiếp vào Canvas. Nếu bạn không làm gì khác, các dịch vụ hỗ trợ tiếp cận sẽ không nhận đủ dữ liệu thông tin về nội dung của thành phần kết hợp và lựa chọn của người dùng trong lịch. Ví dụ: nếu người dùng nhấp vào ngày chứa 17, khung hỗ trợ tiếp cận sẽ chỉ nhận được thông tin mô tả cho toàn bộ quyền kiểm soát lịch. Trong trường hợp này, dịch vụ hỗ trợ tiếp cận TalkBack sẽ thông báo "Lịch" hoặc chỉ tốt hơn một chút là "Lịch tháng Tư" và người dùng sẽ không biết chọn ngày nào. Để thành phần kết hợp này trở nên phù hợp hơn có thể truy cập được, bạn sẽ cần thêm thông tin ngữ nghĩa theo cách thủ công.

Thuộc tính ngữ nghĩa

Tất cả các nút trong cây giao diện người dùng có một số ý nghĩa ngữ nghĩa đều có một nút song song trong cây Ngữ nghĩa. Nút trong cây Ngữ nghĩa chứa các thuộc tính truyền tải ý nghĩa của thành phần kết hợp tương ứng. Ví dụ: Text thành phần kết hợp chứa một thuộc tính ngữ nghĩa text, vì đó là ý nghĩa của thành phần kết hợp đó. Icon chứa thuộc tính contentDescription (nếu được đặt bởi nhà phát triển) truyền tải văn bản ý nghĩa của Icon. Các thành phần kết hợp và đối tượng sửa đổi được xây dựng dựa trên nền tảng Compose thư viện đã đặt các thuộc tính có liên quan cho bạn. Đặt (không bắt buộc) hoặc tự ghi đè các thuộc tính bằng semantics và Đối tượng sửa đổi clearAndSetSemantics. Ví dụ: thêm tùy chỉnh hành động hỗ trợ tiếp cận vào một nút, cung cấp một trạng thái thay thế mô tả cho một phần tử bật/tắt được hoặc cho biết rằng một văn bản nhất định thành phần kết hợp phải được coi là tiêu đề.

Để trực quan hoá cây Ngữ nghĩa, hãy dùng Công cụ Layout Inspector hoặc dùng Phương thức printToLog() bên trong kiểm thử. Thao tác này sẽ in dòng Cây ngữ nghĩa trong Logcat.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun MyTest() {
        // Start the app
        composeTestRule.setContent {
            MyTheme {
                Text("Hello world!")
            }
        }
        // Log the full semantics tree
        composeTestRule.onRoot().printToLog("MY TAG")
    }
}

Kết quả của lần kiểm thử này sẽ là:

    Printing with useUnmergedTree = 'false'
    Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
     |-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
       Text = '[Hello world!]'
       Actions = [GetTextLayoutResult]

Cân nhắc cách các thuộc tính ngữ nghĩa truyền tải ý nghĩa của một thành phần kết hợp. Hãy cân nhắc sử dụng Switch. Đây là giao diện mà người dùng thấy:

Hình 3. Nút chuyển ở trạng thái "Bật" và "Tắt" trạng thái..

Để mô tả ý nghĩa của phần tử này, bạn có thể nói như sau: "This là một Switch, một phần tử có thể bật/tắt ở chế độ "On" (Bật) trạng thái. Bạn có thể nhấp vào đó tương tác với nó."

Đây chính xác là những thuộc tính ngữ nghĩa được dùng. Nút ngữ nghĩa của phần tử Chuyển đổi này chứa các thuộc tính sau, như được minh hoạ bằng Layout Inspector (Trình kiểm tra bố cục):

Layout Inspector cho thấy các thuộc tính ngữ nghĩa của thành phần kết hợp Switch (Chuyển đổi)
Hình 4. Layout Inspector cho thấy các thuộc tính ngữ nghĩa của thành phần kết hợp Switch (Chuyển đổi).

Role cho biết loại phần tử. StateDescription mô tả cách "Bật" trạng thái cần được tham chiếu. Theo mặc định, đây là phiên bản được bản địa hoá của từ "Bật", nhưng có thể làm điều này cụ thể hơn (ví dụ: "Đã bật") dựa trên vào ngữ cảnh. ToggleableState là trạng thái hiện tại của Nút chuyển. Chiến lược phát hành đĩa đơn Thuộc tính OnClick tham chiếu phương thức dùng để tương tác với phần tử này. Để danh sách đầy đủ các thuộc tính ngữ nghĩa, hãy xem SemanticsProperties . Để biết danh sách đầy đủ các Hành động hỗ trợ tiếp cận, hãy xem đối tượng SemanticsActions.

Việc theo dõi các thuộc tính ngữ nghĩa của từng thành phần kết hợp trong ứng dụng của bạn sẽ mở ra rất nhiều khả năng mạnh mẽ. Một số ví dụ:

  • TalkBack sử dụng các thuộc tính để đọc to nội dung hiển thị trên màn hình và cho phép người dùng tương tác mượt mà với nó. Đối với thành phần kết hợp Switch (Chuyển đổi), TalkBack có thể nói: "Bật; Chuyển đổi; nhấn đúp để bật/tắt". Người dùng có thể nhấn đúp vào để tắt Nút chuyển.
  • Khung kiểm thử sử dụng các thuộc tính để tìm nút, tương tác với chúng và đưa ra xác nhận. Một chương trình kiểm thử mẫu cho Nút chuyển có thể là:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Cây Ngữ nghĩa đã hợp nhất và chưa hợp nhất

Như đã đề cập trước đó, mỗi thành phần kết hợp trong cây Giao diện người dùng có thể không có hoặc có nhiều thuộc tính ngữ nghĩa. Khi một thành phần kết hợp chưa đặt thuộc tính ngữ nghĩa, không được đưa vào cây Ngữ nghĩa. Bằng cách đó, cây Ngữ nghĩa chỉ chứa các nút thực sự chứa ý nghĩa ngữ nghĩa. Tuy nhiên, đôi khi, để truyền đạt ý nghĩa chính xác của nội dung hiển thị trên màn hình, bạn cũng nên hợp nhất một số cây phụ của nút và coi chúng là một. Bằng cách đó bạn có thể giải thích về toàn bộ tập hợp nút, thay vì xử lý từng nút nút con riêng lẻ. Theo quy tắc chung, mỗi nút trong cây này đại diện cho một phần tử có thể lấy tiêu điểm khi sử dụng các dịch vụ Hỗ trợ tiếp cận.

Ví dụ về thành phần kết hợp đó là Button. Bạn có thể nêu lý do về một nút dưới dạng một phần tử, mặc dù phần tử này có thể chứa nhiều nút con:

Button(onClick = { /*TODO*/ }) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = null
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

Trong cây Ngữ nghĩa, các thuộc tính của các thành phần con của nút được hợp nhất, và nút này được biểu thị dưới dạng một nút lá đơn trên cây:

Biểu diễn ngữ nghĩa một lá hợp nhất
Hình 5. Biểu diễn ngữ nghĩa một lá hợp nhất.

Các thành phần kết hợp và đối tượng sửa đổi có thể cho biết rằng chúng muốn hợp nhất các thuộc tính ngữ nghĩa của phần tử con bằng cách gọi Modifier.semantics (mergeDescendants = true) {}. Việc đặt thuộc tính này thành true cho biết rằng thuộc tính ngữ nghĩa cần được hợp nhất. Trong ví dụ về Button, Button sử dụng đối tượng sửa đổi clickable trong nội bộ bao gồm Đối tượng sửa đổi semantics. Do đó, các nút con của nút này đã được hợp nhất. Hãy đọc tài liệu về khả năng hỗ trợ tiếp cận để tìm hiểu thêm về thời điểm bạn nên thay đổi hành vi hợp nhất trong thành phần kết hợp.

Một số đối tượng sửa đổi và thành phần kết hợp trong Thư viện nền tảng và thư viện Material Compose có bộ thuộc tính này. Ví dụ: đối tượng sửa đổi clickabletoggleable sẽ tự động hợp nhất các phần tử con. Ngoài ra, ListItem hoạt động tương ứng sẽ hợp nhất các thành phần con.

Kiểm tra cây cối

Thực tế, cây ngữ nghĩa là hai cây khác nhau. Có một ngữ nghĩa hợp nhất cây này hợp nhất các nút con khi bạn đặt mergeDescendants thành true. Ngoài ra còn có cây Ngữ nghĩa chưa hợp nhất không áp dụng hợp nhất, nhưng giữ nguyên mọi nút. Các dịch vụ hỗ trợ tiếp cận dùng cây chưa hợp nhất và áp dụng các thuật toán hợp nhất riêng của chúng, xem xét mergeDescendants thuộc tính này. Theo mặc định, khung kiểm thử sử dụng cây đã hợp nhất.

Bạn có thể kiểm tra cả hai cây bằng phương thức printToLog(). Theo mặc định và như trong các ví dụ trước đó, cây hợp nhất sẽ được ghi lại. Để in cây chưa hợp nhất thay vào đó, hãy đặt tham số useUnmergedTree của trình so khớp onRoot() thành true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

Layout Inspector cho phép bạn hiển thị cả Ngữ nghĩa hợp nhất và chưa hợp nhất bằng cách chọn cây ưa thích trong bộ lọc chế độ xem:

Các tuỳ chọn khung hiển thị của Layout Inspector, cho phép hiển thị cả cây Ngữ nghĩa hợp nhất và chưa hợp nhất
Hình 6. Các tuỳ chọn khung hiển thị của Layout Inspector, cho phép hiển thị cả cây Ngữ nghĩa hợp nhất và chưa hợp nhất.

Đối với mỗi nút trong cây của bạn, Layout Inspector hiển thị cả Ngữ nghĩa hợp nhất và Ngữ nghĩa chưa hợp nhất đã đặt trên nút đó trong bảng thuộc tính:

Hợp nhất và đặt thuộc tính ngữ nghĩa
Hình 7. Hợp nhất và thiết lập thuộc tính ngữ nghĩa.

Theo mặc định, các trình so khớp trong Khung kiểm thử sẽ sử dụng cây Ngữ nghĩa đã hợp nhất. Đó là lý do bạn có thể tương tác với Button bằng cách so khớp văn bản bên trong nó:

composeTestRule.onNodeWithText("Like").performClick()

Ghi đè hành vi này bằng cách đặt tham số useUnmergedTree của so khớp với true, giống như với trình so khớp onRoot.

Hành vi hợp nhất

Khi một thành phần kết hợp cho biết rằng các thành phần con của nó nên được hợp nhất, thì việc hợp nhất này xảy ra chính xác như thế nào?

Mỗi thuộc tính ngữ nghĩa có một chiến lược hợp nhất xác định. Ví dụ: Thuộc tính ContentDescription thêm tất cả các giá trị con trong ContentDescription vào một danh sách. Kiểm tra chiến lược hợp nhất của một thuộc tính ngữ nghĩa bằng cách kiểm tra Triển khai mergePolicy trong SemanticsProperties.kt. Cơ sở lưu trú có thể lấy giá trị mẹ hoặc giá trị con, hợp nhất các giá trị thành một danh sách hoặc chuỗi, hoàn toàn không cho phép hợp nhất và gửi một ngoại lệ hoặc bất kỳ hợp nhất tuỳ chỉnh.

Một lưu ý quan trọng là các thành phần con đã đặt mergeDescendants = true sẽ không được đưa vào hoạt động hợp nhất. Hãy xem ví dụ sau:

Mục danh sách có hình ảnh, một số văn bản và biểu tượng dấu trang
Hình 8. Mục danh sách có hình ảnh, một số văn bản và biểu tượng dấu trang.

Sau đây là một mục danh sách có thể nhấp vào. Khi người dùng nhấn vào hàng, ứng dụng sẽ chuyển đến trang chi tiết bài viết, nơi người dùng có thể đọc bài viết. Bên trong mục danh sách, có một nút để đánh dấu bài viết, biểu mẫu phần tử có thể nhấp vào được lồng, nên nút này sẽ xuất hiện riêng biệt trong cây đã hợp nhất. Nội dung còn lại trong hàng được hợp nhất:

Cây hợp nhất chứa nhiều văn bản trong một danh sách bên trong nút Hàng. Cây chưa hợp nhất chứa các nút riêng cho mỗi thành phần kết hợp bằng Văn bản.
Hình 9. Cây hợp nhất chứa nhiều văn bản trong một danh sách bên trong nút Hàng. Cây chưa hợp nhất chứa các nút riêng biệt cho mỗi thành phần kết hợp Văn bản.

Điều chỉnh cây Ngữ nghĩa

Như đã đề cập trước đó, bạn có thể ghi đè hoặc xoá một số thuộc tính ngữ nghĩa hoặc thay đổi hành vi hợp nhất của cây. Điều này đặc biệt phù hợp khi bạn đang tạo thành phần tuỳ chỉnh của riêng mình. Nếu không đặt giá trị chính xác thuộc tính và hành vi hợp nhất, ứng dụng của bạn có thể không truy cập được và các bài kiểm thử hành động khác với mong đợi của bạn. Để đọc thêm về một số trường hợp sử dụng phổ biến trong đó bạn nên điều chỉnh cây Ngữ nghĩa, hãy đọc bài viết Hỗ trợ tiếp cận . Nếu bạn muốn tìm hiểu thêm về kiểm thử, hãy xem phần kiểm thử hướng dẫn.

Tài nguyên khác