1. Giới thiệu
Thông qua bàn phím cứng, người dùng có thể tương tác với ứng dụng của bạn, thường là trên các thiết bị có màn hình lớn như máy tính bảng và thiết bị ChromeOS, nhưng cũng có thể tương tác trên các thiết bị XR. Điều quan trọng là người dùng có thể di chuyển hiệu quả trong ứng dụng bằng bàn phím cứng cũng như bằng màn hình cảm ứng. Ngoài ra, khi thiết kế ứng dụng cho TV và màn hình trên ô tô (có thể không hỗ trợ phương thức nhập bằng cách chạm mà thay vào đó là sử dụng D-pad hoặc bộ mã hoá xoay), bạn cần áp dụng các nguyên tắc di chuyển bằng bàn phím tương tự.
Compose giúp bạn xử lý thông tin đầu vào từ bàn phím cứng, D-pad và bộ mã hoá xoay theo cách thống nhất. Một nguyên tắc quan trọng để mang lại trải nghiệm tốt cho những người dùng các phương thức nhập này là họ có thể di chuyển tiêu điểm bàn phím một cách trực quan và nhất quán đến thành phần mà mình muốn tương tác.
Trong lớp học lập trình này, bạn sẽ tìm hiểu những nội dung sau:
- Cách triển khai các mô hình quản lý tiêu điểm bàn phím phổ biến để di chuyển trực quan và nhất quán
- Cách kiểm tra xem tiêu điểm bàn phím có di chuyển như mong đợi hay không
Điều kiện tiên quyết
- Có kinh nghiệm dùng Compose để tạo ứng dụng
- Có kiến thức cơ bản về Kotlin, bao gồm cả lambda và coroutine
Sản phẩm bạn sẽ tạo ra
Bạn triển khai các mô hình quản lý tiêu điểm bàn phím thông thường sau đây:
- Di chuyển tiêu điểm bàn phím — Từ đầu đến cuối, từ trên xuống dưới theo hình chữ z
- Tiêu điểm ban đầu hợp lý – Đặt tiêu điểm vào phần tử trên giao diện người dùng mà người dùng có khả năng tương tác
- Khôi phục tiêu điểm – Di chuyển tiêu điểm đến phần tử trên giao diện người dùng mà người dùng từng tương tác
Kiến thức bạn sẽ học được
- Kiến thức cơ bản về việc quản lý tiêu điểm trong Compose
- Cách đặt một phần tử trên giao diện người dùng làm mục tiêu lấy tiêu điểm
- Cách yêu cầu tiêu điểm để di chuyển một phần tử trên giao diện người dùng
- Cách di chuyển tiêu điểm bàn phím đến phần tử nhất định trong một nhóm phần tử trên giao diện người dùng
Bạn cần có
- Android Studio Ladybug trở lên
- Bất cứ thiết bị nào sau đây để chạy ứng dụng mẫu:
- Một thiết bị có màn hình lớn cùng với bàn phím cứng
- Một thiết bị Android ảo dành cho thiết bị có màn hình lớn, chẳng hạn như trình mô phỏng có thể đổi kích thước
2. Thiết lập
- Nhân bản kho lưu trữ large-screen-codelabs trên GitHub:
git clone https://github.com/android/large-screen-codelabs
Ngoài ra, bạn có thể tải xuống và giải nén tệp zip trong kho lưu trữ large-screen-codelabs. Cách làm như sau:
- Chuyển đến thư mục
focus-management-in-compose
. - Trong Android Studio, hãy mở dự án. Thư mục
focus-management-in-compose
chứa một dự án. - Nếu bạn không dùng máy tính bảng, thiết bị gập Android hoặc thiết bị ChromeOS có bàn phím cứng, hãy mở Device Manager (Trình quản lý thiết bị) trong Android Studio rồi tạo thiết bị Resizable (Có thể đổi kích thước) trong danh mục Phone (Điện thoại).
Hình 1. Định cấu hình trình mô phỏng có thể đổi kích thước trong Android Studio.
3. Khám phá mã khởi đầu
Dự án này có 2 mô-đun:
- start – Chứa mã khởi đầu của dự án. Bạn sẽ thay đổi mã này để hoàn tất lớp học lập trình.
- solution — Chứa mã hoàn chỉnh cho lớp học lập trình này.
Ứng dụng mẫu gồm 3 thẻ:
- Focus target (Mục tiêu lấy tiêu điểm)
- Focus traversal order (Thứ tự duyệt qua tiêu điểm)
- Focus group (Nhóm tiêu điểm)
Thẻ Focus target (Mục tiêu lấy tiêu điểm) sẽ xuất hiện khi ứng dụng khởi chạy.
Hình 2. Thẻ Focus target (Mục tiêu lấy tiêu điểm) sẽ xuất hiện khi ứng dụng khởi chạy.
Gói ui
chứa mã giao diện người dùng sau đây mà bạn tương tác:
App.kt
– Triển khai thẻtab.FocusTargetTab.kt
– Chứa mã cho thẻ Focus target (Mục tiêu lấy tiêu điểm)tab.FocusTraversalOrderTab.kt
– Chứa mã cho thẻ Focus traversal order (Thứ tự duyệt qua tiêu điểm)tab.FocusGroup.kt
– Chứa mã cho thẻ Focus group (Nhóm tiêu điểm)FocusGroupTabTest.kt
– Một tệp kiểm thử đo lường chotab.FocusTargetTab.kt
(Tệp nằm trong thư mụcandroidTest
)
4. Mục tiêu lấy tiêu điểm
Mục tiêu lấy tiêu điểm là một phần tử trên giao diện người dùng mà tiêu điểm bàn phím có thể di chuyển đến. Người dùng có thể di chuyển tiêu điểm bàn phím bằng phím Tab
hoặc các phím định hướng (mũi tên):
- Phím
Tab
– Tiêu điểm di chuyển đến mục tiêu lấy tiêu điểm tiếp theo/trước đó theo một chiều. - Phím định hướng – Tiêu điểm có thể di chuyển theo hai chiều: lên, xuống, trái và phải.
Các thẻ là mục tiêu lấy tiêu điểm. Trong ứng dụng mẫu, nền của các thẻ được cập nhật trực quan khi thẻ lấy tiêu điểm.
Hình 3. Nền của thành phần sẽ thay đổi khi tiêu điểm di chuyển đến một mục tiêu lấy tiêu điểm.
Theo mặc định, các phần thử tương tác trên giao diện người dùng là mục tiêu lấy tiêu điểm
Theo mặc định, thành phần tương tác là mục tiêu lấy tiêu điểm. Nói cách khác, phần tử trên giao diện người dùng là mục tiêu lấy tiêu điểm nếu người dùng có thể nhấn vào phần tử đó.
Ứng dụng mẫu có 3 thẻ nội dung trong thẻ Focus target (Mục tiêu lấy tiêu điểm). Thẻ nội dung 1 và thẻ nội dung 3 là mục tiêu lấy tiêu điểm; riêng thẻ nội dung 2 thì không. Nền của thẻ nội dung 3 được cập nhật khi người dùng di chuyển tiêu điểm từ thẻ nội dung 1 bằng phím Tab
.
Hình 4. Mục tiêu lấy tiêu điểm của ứng dụng không bao gồm thẻ nội dung 2.
Sửa đổi thẻ nội dung 2 thành mục tiêu lấy tiêu điểm
Bạn có thể đặt thẻ nội dung 2 làm mục tiêu lấy tiêu điểm bằng cách thay đổi thẻ này thành một phần tử tương tác trên giao diện người dùng. Cách dễ nhất là dùng đối tượng sửa đổi clickable
như sau:
- Mở
FocusTargetTab.kt
trong góitabs
- Sửa đổi thành phần kết hợp
SecondCard
bằng đối tượng sửa đổiclickable
như sau:
@Composable
fun FocusTargetTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
SecondCard(
modifier = Modifier
.width(240.dp)
.clickable(onClick = onClick)
)
ThirdCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
}
}
Chạy ứng dụng
Giờ đây, người dùng có thể di chuyển tiêu điểm sang thẻ nội dung 2 ngoài thẻ nội dung 1 và thẻ nội dung 3. Bạn có thể thử trên thẻ Focus target (Mục tiêu lấy tiêu điểm); xác nhận rằng bạn có thể di chuyển tiêu điểm từ thẻ nội dung 1 sang thẻ nội dung 2 bằng phím Tab
.
Hình 5. Di chuyển tiêu điểm từ thẻ nội dung 1 sang thẻ nội dung 2 bằng phím Tab
.
5. Duyệt qua tiêu điểm theo hình chữ z
Người dùng mong muốn tiêu điểm bàn phím di chuyển từ trái sang phải và từ trên xuống dưới trong phần cài đặt ngôn ngữ viết từ trái sang phải. Thứ tự duyệt qua tiêu điểm này được gọi là hình chữ z.
Tuy nhiên, Compose không xác định mục tiêu lấy tiêu điểm tiếp theo của phím Tab
dựa vào bố cục, mà thay vào đó sử dụng cách duyệt qua tiêu điểm theo một chiều dựa trên thứ tự của các lệnh gọi hàm có khả năng kết hợp.
Duyệt qua tiêu điểm theo một chiều
Thứ tự duyệt qua tiêu điểm theo một chiều được xác định dựa trên thứ tự của các lệnh gọi hàm có khả năng kết hợp, chứ không phải bố cục ứng dụng.
Trong ứng dụng mẫu, tiêu điểm di chuyển trên thẻ Focus traversal order (Thứ tự duyệt qua tiêu điểm) theo thứ tự sau:
- Thẻ nội dung 1
- Thẻ nội dung 4
- Thẻ nội dung 3
- Thẻ nội dung 2
Hình 6. Quá trình duyệt qua tiêu điểm tuân theo thứ tự của các hàm có khả năng kết hợp.
Hàm FocusTraversalOrderTab
triển khai thẻ Focus traversal order (Thứ tự duyệt qua tiêu điểm) của ứng dụng mẫu. Hàm này gọi các hàm có khả năng kết hợp cho thẻ nội dung: FirstCard
, FourthCard
, ThirdCard
và SecondCard
theo thứ tự đó.
@Composable
fun FocusTraversalOrderTab(
modifier: Modifier = Modifier
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
FourthCard(
onClick = onClick,
modifier = Modifier
.width(240.dp)
.offset(x = 256.dp)
)
ThirdCard(
onClick = onClick,
modifier = Modifier
.width(240.dp)
.offset(y = (-151).dp)
)
}
SecondCard(
modifier = Modifier.width(240.dp)
)
}
}
Di chuyển tiêu điểm theo hình chữ z
Bạn có thể tích hợp cách di chuyển tiêu điểm theo hình chữ z trong thẻ Focus traversal order (Thứ tự duyệt qua tiêu điểm) của ứng dụng mẫu theo các bước sau:
- Mở
tabs.FocusTraversalOrderTab.kt
- Xoá đối tượng sửa đổi bù trừ khỏi các thành phần kết hợp
ThirdCard
vàFourthCard
. - Thay đổi bố cục của thẻ từ 1 hàng 2 cột (hiện tại) thành 1 cột 2 hàng.
- Di chuyển các thành phần kết hợp
FirstCard
vàSecondCard
sang hàng đầu tiên. - Di chuyển các thành phần kết hợp
ThirdCard
vàFourthCard
sang hàng thứ hai.
Mã đã sửa đổi sẽ có dạng như sau:
@Composable
fun FocusTraversalOrderTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(240.dp),
)
SecondCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
ThirdCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
FourthCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
}
}
}
Chạy ứng dụng
Giờ đây, người dùng có thể di chuyển tiêu điểm từ phải sang trái, từ trên xuống dưới theo hình chữ z. Bạn có thể dùng phím Tab
để xác nhận rằng tiêu điểm di chuyển theo thứ tự sau trong thẻ Focus traversal order (Thứ tự duyệt qua tiêu điểm):
- Thẻ nội dung 1
- Thẻ nội dung 2
- Thẻ nội dung 3
- Thẻ nội dung 4
Hình 7. Duyệt qua tiêu điểm theo hình chữ z.
6. focusGroup
Tiêu điểm di chuyển từ thẻ nội dung 1 sang thẻ nội dung 3 trên thẻ Focus group (Nhóm tiêu điểm) bằng phím định hướng right
. Người dùng có thể hơi bối rối khi thấy cách di chuyển này vì 2 thẻ nội dung không nằm cạnh nhau.
Hình 8. Di chuyển tiêu điểm ngoài mong muốn từ thẻ nội dung 1 sang thẻ nội dung 3.
Duyệt qua tiêu điểm theo hai chiều dựa trên thông tin bố cục
Thao tác nhấn phím định hướng sẽ kích hoạt quá trình duyệt qua tiêu điểm theo hai chiều. Đây là cách di chuyển tiêu điểm phổ biến trên TV khi người dùng tương tác với ứng dụng của bạn bằng D-pad. Thao tác nhấn phím định hướng trên bàn phím cũng kích hoạt quá trình duyệt qua tiêu điểm theo hai chiều vì các phím này mô phỏng thao tác di chuyển bằng D-pad.
Trong quá trình duyệt qua tiêu điểm theo hai chiều, hệ thống tham chiếu đến thông tin hình học của các phần tử trên giao diện người dùng và xác định mục tiêu lấy tiêu điểm để di chuyển tiêu điểm. Ví dụ: tiêu điểm sẽ di chuyển đến thẻ nội dung 1 từ thẻ Focus target (Mục tiêu lấy tiêu điểm) bằng phím định hướng down
, và thao tác nhấn phím định hướng lên sẽ di chuyển tiêu điểm đến thẻ Focus target (Mục tiêu lấy tiêu điểm).
Hình 9. Duyệt qua tiêu điểm bằng phím định hướng xuống và lên.
Quá trình duyệt qua tiêu điểm theo hai chiều không quay vòng, trái ngược với quá trình duyệt qua tiêu điểm theo một chiều bằng phím Tab
. Ví dụ: người dùng không thể di chuyển tiêu điểm bằng phím định hướng xuống khi thẻ nội dung 2 được lấy tiêu điểm.
Hình 10. Phím định hướng xuống không thể di chuyển tiêu điểm khi thẻ nội dung 2 được lấy tiêu điểm.
Mục tiêu lấy mục tiêu ở cùng cấp
Mã sau đây triển khai màn hình được đề cập ở trên. Có 4 mục tiêu lấy tiêu điểm: FirstCard
, SecondCard
, ThirdCard
và FourthCard
. 4 mục tiêu lấy tiêu điểm này ở cùng cấp và ThirdCard
là mục đầu tiên ở bên phải FirstCard
trong bố cục. Đó là lý do tiêu điểm di chuyển từ thẻ nội dung 1 sang thẻ nội dung 3 bằng phím định hướng right
.
@Composable
fun FocusGroupTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier,
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
SecondCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
ThirdCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
FourthCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
}
}
}
Nhóm mục tiêu lấy tiêu điểm bằng đối tượng sửa đổi focusGroup
Bạn có thể thay đổi cách di chuyển tiêu điểm gây nhầm lẫn theo các bước sau:
- Mở
tabs.FocusGroup.kt
- Sửa đổi hàm có khả năng kết hợp
Column
trong hàm có khả năng kết hợpFocusGroupTab
bằng đối tượng sửa đổifocusGroup
.
Mã đã cập nhật sẽ có dạng như sau:
@Composable
fun FocusGroupTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier,
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.focusGroup(),
) {
SecondCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
ThirdCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
FourthCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
}
}
}
Đối tượng sửa đổi focusGroup
tạo một nhóm tiêu điểm gồm các mục tiêu lấy tiêu điểm bên trong thành phần đã sửa đổi. Các mục tiêu lấy tiêu điểm trong cũng như ngoài nhóm tiêu điểm này ở các cấp khác nhau và không có mục tiêu lấy tiêu điểm nào được đặt ở bên phải của thành phần kết hợp FirstCard
. Do đó, tiêu điểm không di chuyển đến bất kỳ thẻ nội dung nào từ thẻ nội dung 1 bằng phím định hướng right
.
Chạy ứng dụng
Giờ đây, tiêu điểm không di chuyển từ thẻ nội dung 1 sang thẻ nội dung 3 bằng phím định hướng right
trong thẻ Focus group (Nhóm tiêu điểm) của ứng dụng mẫu.
7. Yêu cầu lấy tiêu điểm
Người dùng không thể sử dụng bàn phím hoặc D-pad để chọn các phần tử tuỳ ý trên giao diện người dùng để tương tác. Người dùng cần di chuyển tiêu điểm bàn phím đến một thành phần tương tác thì mới tương tác được với phần tử đó.
Ví dụ: người dùng cần di chuyển tiêu điểm từ thẻ Focus target (Mục tiêu lấy tiêu điểm) sang thẻ nội dung 1 thì mới tương tác được với thẻ nội dung này. Bạn có thể giảm số lượng thao tác để bắt đầu tác vụ chính của người dùng bằng cách đặt tiêu điểm ban đầu một cách hợp lý.
Hình 11. 3 lần nhấn phím Tab
sẽ di chuyển tiêu điểm đến thẻ nội dung 1.
Yêu cầu lấy tiêu điểm bằng FocusRequester
Bạn có thể sử dụng FocusRequester
để yêu cầu lấy tiêu điểm để di chuyển một phần tử trên giao diện người dùng. Bạn phải liên kết đối tượng FocusRequester
với một phần tử trên giao diện người dùng trước khi gọi phương thức requestFocus()
.
Đặt tiêu điểm ban đầu thành thẻ nội dung 1
Bạn có thể đặt tiêu điểm ban đầu thành thẻ nội dung 1 theo các bước sau:
- Mở
tabs.FocusTarget.kt
- Khai báo giá trị
firstCard
trong hàm có khả năng kết hợpFocusTargetTab
và khởi chạy giá trị này bằng đối tượngFocusRequester
được trả về từ hàmremember
. - Dùng đối tượng sửa đổi
focusRequester
để sửa đổi hàm có khả năng kết hợpFirstCard
. - Chỉ định giá trị
firstCard
làm đối số của đối tượng sửa đổifocusRequester
. - Gọi hàm có khả năng kết hợp
LaunchedEffect
bằng giá trịUnit
và gọi phương thức requestFocus() qua giá trịfirstCard
trong biểu thức lambda được truyền vào hàm có khả năng kết hợpLaunchedEffect
.
Đối tượng FocusRequester
được tạo và liên kết với một phần tử trên giao diện người dùng ở bước thứ hai và thứ ba. Ở bước thứ năm, tiêu điểm được yêu cầu di chuyển đến phần tử được liên kết trên giao diện người dùng khi thành phần kết hợp FocusdTargetTab
được soạn lần đầu.
Mã đã cập nhật sẽ có dạng như sau:
@Composable
fun FocusTargetTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val firstCard = remember { FocusRequester() }
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
) {
FirstCard(
onClick = onClick,
modifier = Modifier
.width(240.dp)
.focusRequester(focusRequester = firstCard)
)
SecondCard(
modifier = Modifier
.width(240.dp)
.clickable(onClick = onClick)
)
ThirdCard(
onClick = onClick,
modifier = Modifier.width(240.dp)
)
}
LaunchedEffect(Unit) {
firstCard.requestFocus()
}
}
Chạy ứng dụng
Giờ đây, tiêu điểm bàn phím sẽ di chuyển đến thẻ nội dung 1 trong thẻ Focus target (Mục tiêu lấy tiêu điểm) khi thẻ này được chọn. Bạn có thể thử bằng cách chuyển đổi thẻ. Ngoài ra, thẻ nội dung 1 sẽ được chọn khi ứng dụng khởi chạy.
Hình 12. Tiêu điểm sẽ di chuyển đến thẻ nội dung 1 khi bạn chọn thẻ Focus target (Mục tiêu lấy tiêu điểm).
8. Di chuyển tiêu điểm đến thẻ đã chọn
Bạn có thể chỉ định mục tiêu lấy tiêu điểm khi tiêu điểm bàn phím đang chuyển vào một nhóm tiêu điểm. Ví dụ: bạn có thể di chuyển tiêu điểm đến thẻ đã chọn khi người dùng di chuyển tiêu điểm đến hàng trong thẻ.
Bạn có thể triển khai hành vi này theo các bước sau:
- Mở
App.kt
. - Khai báo giá trị
focusRequesters
trong hàm có khả năng kết hợpApp
. - Khởi chạy giá trị
focusRequesters
bằng giá trị trả về của hàmremember
trả về danh sách các đối tượngFocusRequester
. Độ dài của danh sách được trả về phải bằng độ dài củaScreens.entries
. - Liên kết từng đối tượng
FocusRequester
của giá trịfocusRequester
với thành phần kết hợpTab
bằng cách sửa đổi thành phần kết hợp Tab bằng đối tượng sửa đổifocusRequester
. - Sửa đổi thành phần kết hợp PrimaryTabRow bằng đối tượng sửa đổi
focusProperties
vàfocusGroup
. - Truyền một biểu thức lambda vào đối tượng sửa đổi
focusProperties
và liên kết thuộc tínhenter
với một biểu thức lambda khác. - Trả về FocusRequester (được lập chỉ mục bằng giá trị
selectedTabIndex
trong giá trịfocusRequesters
) từ biểu thức lambda liên kết với thuộc tínhenter
.
Mã đã sửa đổi sẽ có dạng như sau:
@Composable
fun App(
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
var selectedScreen by rememberSaveable { mutableStateOf(Screen.FocusTarget) }
val selectedTabIndex = Screen.entries.indexOf(selectedScreen)
val focusRequesters = remember {
List(Screen.entries.size) { FocusRequester() }
}
Column(modifier = modifier) {
PrimaryTabRow(
selectedTabIndex = selectedTabIndex,
modifier = Modifier
.focusProperties {
enter = {
focusRequesters[selectedTabIndex]
}
}
.focusGroup()
) {
Screen.entries.forEachIndexed { index, screen ->
Tab(
selected = screen == selectedScreen,
onClick = { selectedScreen = screen },
text = { Text(stringResource(screen.title)) },
modifier = Modifier.focusRequester(focusRequester = focusRequesters[index])
)
}
}
when (selectedScreen) {
Screen.FocusTarget -> {
FocusTargetTab(
onClick = context::onCardClicked,
modifier = Modifier.padding(32.dp),
)
}
Screen.FocusTraversalOrder -> {
FocusTraversalOrderTab(
onClick = context::onCardClicked,
modifier = Modifier.padding(32.dp)
)
}
Screen.FocusRestoration -> {
FocusGroupTab(
onClick = context::onCardClicked,
modifier = Modifier.padding(32.dp)
)
}
}
}
}
Bạn có thể kiểm soát cách di chuyển tiêu điểm bằng đối tượng sửa đổi focusProperties
. Trong biểu thức lambda được truyền vào đối tượng sửa đổi, hãy sửa đổi FocusProperties. Đối tượng này được hệ thống tham chiếu trong lúc chọn mục tiêu lấy tiêu điểm khi người dùng nhấn phím Tab
hoặc phím định hướng, trong trường hợp phần tử đã sửa đổi trên giao diện người dùng được lấy tiêu điểm.
Khi bạn đặt thuộc tính enter
, hệ thống sẽ đánh giá biểu thức lambda được đặt thành thuộc tính này và chuyển đến phần tử được liên kết với đối tượng FocusRequester
(do biểu thức lambda được đánh giá trả về) trên giao diện người dùng.
Chạy ứng dụng
Giờ đây, tiêu điểm bàn phím sẽ di chuyển đến thẻ đã chọn khi người dùng di chuyển tiêu điểm đến hàng trong thẻ. Bạn có thể thử theo các bước sau:
- Chạy ứng dụng
- Chọn thẻ Focus group (Nhóm tiêu điểm)
- Di chuyển tiêu điểm đến thẻ nội dung 1 bằng phím định hướng
down
. - Di chuyển tiêu điểm bằng phím định hướng
up
.
Hình 13. Tiêu điểm sẽ chuyển đến thẻ đã chọn.
9. Khôi phục tiêu điểm
Người dùng mong muốn có thể dễ dàng tiếp tục một tác vụ khi tác vụ đó bị gián đoạn. Tính năng khôi phục tiêu điểm hỗ trợ khôi phục sau khi bị gián đoạn. Tính năng khôi phục tiêu điểm sẽ di chuyển tiêu điểm bàn phím đến phần tử đã được chọn trước đó trên giao diện người dùng.
Một trường hợp sử dụng điển hình của tính năng khôi phục tiêu điểm là màn hình chính của các ứng dụng phát video trực tuyến. Màn hình này có nhiều danh sách nội dung video, chẳng hạn như phim trong một danh mục hoặc các tập của một chương trình truyền hình. Người dùng xem qua các danh sách và tìm thấy nội dung thú vị. Đôi khi, người dùng quay lại danh sách đã kiểm tra trước đó và tiếp tục duyệt xem danh sách nêu trên. Với tính năng khôi phục tiêu điểm, người dùng có thể tiếp tục duyệt web mà không cần di chuyển tiêu điểm bàn phím đến mục cuối cùng mà họ đã xem trong danh sách.
Đối tượng sửa đổi focusRestorer khôi phục tiêu điểm về một nhóm tiêu điểm
Dùng đối tượng sửa đổi focusRestorer
để lưu và khôi phục tiêu điểm về một nhóm tiêu điểm. Khi tiêu điểm rời khỏi nhóm tiêu điểm, tiêu điểm sẽ lưu trữ một tệp tham chiếu đến mục đã được lấy tiêu điểm trước đó. Sau đó, khi tiêu điểm quay lại nhóm tiêu điểm, tiêu điểm sẽ được khôi phục về mục đã được lấy làm tiêu điểm trước đó.
Tích hợp tính năng khôi phục tiêu điểm bằng thẻ Focus group (Nhóm tiêu điểm)
Thẻ Focus group (Nhóm tiêu điểm) của ứng dụng mẫu có một hàng chứa thẻ nội dung 2, thẻ nội dung 3 và thẻ nội dung 4.
Hình 14. Nhóm tiêu điểm chứa thẻ nội dung 2, thẻ nội dung 3 và thẻ nội dung 4.
Bạn có thể tích hợp tính năng khôi phục tiêu điểm trong hàng theo các bước sau:
- Mở
tab.FocusGroupTab.kt
- Dùng đối tượng sửa đổi
focusRestorer
để sửa đổi thành phần kết hợpRow
trong thành phần kết hợpFocusGroupTab
. Bạn nên gọi đối tượng sửa đổi này trước đối tượng sửa đổifocusGroup
.
Mã đã sửa đổi sẽ có dạng như sau:
@Composable
fun FocusGroupTab(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier,
) {
FirstCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.focusRestorer()
.focusGroup(),
) {
SecondCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
ThirdCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
FourthCard(
onClick = onClick,
modifier = Modifier.width(208.dp)
)
}
}
}
Chạy ứng dụng
Giờ đây, hàng trong thẻ Focus group (Nhóm tiêu điểm) sẽ khôi phục tiêu điểm và bạn có thể thử theo các bước sau:
- Chọn thẻ Focus group (Nhóm tiêu điểm)
- Di chuyển tiêu điểm đến thẻ nội dung 1
- Di chuyển tiêu điểm đến thẻ nội dung 4 bằng phím
Tab
- Di chuyển tiêu điểm đến thẻ nội dung 1 bằng phím định hướng
up
- Nhấn phím
Tab
Tiêu điểm bàn phím di chuyển đến thẻ nội dung 4 khi đối tượng sửa đổi focusRestorer
lưu tệp tham chiếu của thẻ nội dung đó và khôi phục tiêu điểm khi tiêu điểm bàn phím chuyển vào nhóm tiêu điểm được đặt thành hàng.
Hình 15. Tiêu điểm quay lại thẻ nội dung 4 sau khi nhấn phím định hướng lên, theo sau là nhấn phím Tab
.
10. Viết chương trình kiểm thử
Bạn có thể kiểm thử tính năng quản lý tiêu điểm bàn phím đã triển khai bằng các chương trình kiểm thử. Compose cung cấp một API để kiểm tra xem một phần tử trên giao diện người dùng có được lấy tiêu điểm hay không và thực hiện thao tác nhấn phím đối với các thành phần trên giao diện người dùng. Hãy tham khảo lớp học lập trình Kiểm thử trong Jetpack Compose để biết thêm thông tin.
Kiểm thử thẻ Focus target (Mục tiêu lấy tiêu điểm)
Bạn đã sửa đổi hàm có khả năng kết hợp FocusTargetTab
để đặt thẻ nội dung 2 làm mục tiêu lấy tiêu điểm trong phần trước. Hãy viết một chương trình kiểm thử cho quá trình triển khai mà bạn đã thực hiện theo cách thủ công trong phần trước. Bạn có thể viết chương trình kiểm thử theo các bước sau:
- Mở
FocusTargetTabTest.kt
. Bạn sẽ sửa đổi hàmtestSecondCardIsFocusTarget
trong các bước sau. - Yêu cầu tiêu điểm di chuyển đến thẻ nội dung 1 bằng cách gọi phương thức
requestFocus
trên đối tượngSemanticsNodeInteraction
cho thẻ nội dung 1. - Đảm bảo rằng thẻ nội dung 1 được lấy tiêu điểm bằng phương thức
assertIsFocused()
. - Thực hiện thao tác nhấn phím
Tab
bằng cách gọi phương thứcpressKey
với giá trịKey.Tab
bên trong biểu thức lambda được truyền vào phương thứcperformKeyInput
. - Kiểm thử xem tiêu điểm bàn phím có di chuyển đến thẻ nội dung 2 hay không bằng cách gọi phương thức
assertIsFocused()
trên đối tượngSemanticsNodeInteraction
cho thẻ nội dung 2.
Mã đã cập nhật sẽ có dạng như sau:
@OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
@Test
fun testSecondCardIsFocusTarget() {
composeTestRule.setContent {
LocalInputModeManager
.current
.requestInputMode(InputMode.Keyboard)
FocusTargetTab(onClick = {})
}
val context = InstrumentationRegistry.getInstrumentation().targetContext
// Ensure the 1st card is focused
composeTestRule
.onNodeWithText(context.getString(R.string.first_card))
.requestFocus()
.performKeyInput { pressKey(Key.Tab) }
// Test if focus moves to the 2nd card from the 1st card with Tab key
composeTestRule
.onNodeWithText(context.getString(R.string.second_card))
.assertIsFocused()
}
Chạy ứng dụng
Bạn có thể chạy chương trình kiểm thử bằng cách nhấp vào biểu tượng tam giác hiển thị ở bên trái phần khai báo lớp FocusTargetTest
. Hãy tham khảo phần Chạy kiểm thử trong bài viết Kiểm thử trong Android Studio để biết thêm thông tin.
11. Xin chúc mừng
Rất tốt! Bạn đã tìm hiểu các thành phần để quản lý tiêu điểm bàn phím:
- Mục tiêu lấy tiêu điểm
- Duyệt qua tiêu điểm
Bạn có thể kiểm soát thứ tự duyệt qua tiêu điểm bằng các đối tượng sửa đổi sau đây trong Compose:
- Đối tượng sửa đổi
focusGroup
- Đối tượng sửa đổi
focusProperties
Bạn đã triển khai mô hình điển hình cho trải nghiệm người dùng bằng bàn phím cứng, tiêu điểm ban đầu và khôi phục tiêu điểm. Các mô hình này được triển khai bằng cách kết hợp các API sau:
- Lớp
FocusRequester
- Đối tượng sửa đổi
focusRequester
- Đối tượng sửa đổi
focusRestorer
- Hàm có khả năng kết hợp
LaunchedEffect
Bạn có thể kiểm thử trải nghiệm người dùng đã triển khai bằng các tệp kiểm thử đo lường. Compose cung cấp các phương pháp để thực hiện thao tác nhấn phím và kiểm tra xem SemanticsNode
có tiêu điểm bàn phím hay không.