Đôi khi, bạn cần phải ghi đè hành vi lấy nét mặc định của các phần tử trên màn hình. Ví dụ: có thể bạn muốn nhóm các thành phần kết hợp, ngăn chặn tiêu điểm vào một thành phần kết hợp nhất định, yêu cầu lấy tiêu điểm một cách rõ ràng, chụp hoặc huỷ tiêu điểm hoặc chuyển hướng tiêu điểm khi vào hoặc thoát. Phần này mô tả cách thay đổi hành vi của tiêu điểm khi các giá trị mặc định không phải là điều bạn cần.
Mang đến khả năng điều hướng nhất quán thông qua các nhóm tâm điểm
Đôi khi, Jetpack Compose không đoán ngay mục tiếp theo chính xác cho các thao tác bằng thẻ, đặc biệt là khi Composables
mẹ phức tạp như các thẻ và danh sách xuất hiện.
Mặc dù tính năng tìm kiếm tâm điểm thường tuân theo thứ tự khai báo của Composables
, nhưng điều này không thể thực hiện được trong một số trường hợp, chẳng hạn như khi một trong các Composables
trong hệ phân cấp là một thanh cuộn ngang không hiển thị đầy đủ. Điều này được thể hiện trong ví dụ bên dưới.
Jetpack Compose có thể quyết định tập trung vào mục tiếp theo gần điểm bắt đầu màn hình nhất, như minh hoạ dưới đây, thay vì tiếp tục trên đường dẫn mà bạn mong đợi đối với thao tác điều hướng một chiều:
Trong ví dụ này, rõ ràng là các nhà phát triển không có ý định chuyển tiêu điểm từ thẻ Sô-cô-la sang hình ảnh đầu tiên bên dưới, sau đó quay lại thẻ Bánh ngọt. Thay vào đó, họ muốn tiêu điểm tiếp tục ở trên các thẻ cho đến thẻ cuối cùng, rồi tập trung vào nội dung bên trong:
Trong những trường hợp quan trọng là một nhóm thành phần kết hợp phải được lấy tiêu điểm theo tuần tự, như trong hàng Thẻ của ví dụ trước, bạn cần gói Composable
trong một thành phần mẹ có đối tượng sửa đổi focusGroup()
:
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
Điều hướng hai chiều sẽ tìm thành phần kết hợp gần nhất theo hướng nhất định – nếu một phần tử từ một nhóm khác gần hơn một mục không hiển thị đầy đủ trong nhóm hiện tại, thì thành phần điều hướng sẽ chọn mục gần nhất. Để tránh hành vi này, bạn có thể áp dụng đối tượng sửa đổi focusGroup()
.
FocusGroup
làm cho cả nhóm trông giống như một thực thể duy nhất về mặt tiêu điểm, nhưng bản thân nhóm đó sẽ không nhận được tiêu điểm. Thay vào đó, thành phần con gần nhất sẽ được lấy tiêu điểm. Bằng cách này, tính năng điều hướng sẽ biết chuyển đến mục không hiển thị đầy đủ trước khi rời khỏi nhóm.
Trong trường hợp này, 3 thực thể của FilterChip
sẽ được lấy làm tâm điểm trước các mục SweetsCard
, ngay cả khi người dùng hoàn toàn nhìn thấy SweetsCards
và một số FilterChip
có thể bị ẩn. Điều này xảy ra vì đối tượng sửa đổi focusGroup
yêu cầu trình quản lý tiêu điểm điều chỉnh thứ tự các mục được lấy tiêu điểm để thao tác dễ dàng và nhất quán hơn với giao diện người dùng.
Nếu không có đối tượng sửa đổi focusGroup
, nếu FilterChipC
không hiển thị, thì tính năng điều hướng tâm điểm sẽ chọn đối tượng đó sau cùng. Tuy nhiên, việc thêm một đối tượng sửa đổi như vậy làm cho đối tượng sửa đổi này không chỉ ở chế độ có thể tìm thấy mà còn lấy được tiêu điểm ngay sau FilterChipB
, như người dùng mong đợi.
Làm cho thành phần kết hợp có thể làm tâm điểm
Một số thành phần kết hợp có thể làm tâm điểm theo thiết kế, chẳng hạn như Nút hoặc thành phần kết hợp có đối tượng sửa đổi clickable
đi kèm. Nếu muốn thêm cụ thể hành vi có thể tập trung vào một thành phần kết hợp, bạn phải sử dụng đối tượng sửa đổi focusable
:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
Làm cho thành phần kết hợp không thể làm tiêu điểm
Có thể có những trường hợp mà một số phần tử của bạn không tham gia vào tiêu điểm. Trong những trường hợp hiếm hoi này, bạn có thể tận dụng canFocus property
để loại trừ Composable
khỏi tiêu điểm.
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
Yêu cầu lấy tiêu điểm bàn phím bằng FocusRequester
Trong một số trường hợp, bạn có thể muốn yêu cầu rõ ràng lấy tiêu điểm làm phản hồi cho một tương tác của người dùng. Ví dụ: bạn có thể hỏi người dùng xem họ có muốn bắt đầu lại điền vào biểu mẫu hay không và nếu họ nhấn "có", thì bạn muốn lấy nét lại trường đầu tiên của biểu mẫu đó.
Điều đầu tiên cần làm là liên kết một đối tượng FocusRequester
với thành phần kết hợp mà bạn muốn di chuyển tiêu điểm bàn phím đến. Trong đoạn mã sau, đối tượng FocusRequester
được liên kết với TextField
bằng cách đặt một đối tượng sửa đổi có tên là Modifier.focusRequester
:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
Bạn có thể gọi phương thức requestFocus
của FocusRequester để gửi các yêu cầu lấy tiêu điểm thực tế. Bạn phải gọi phương thức này bên ngoài ngữ cảnh Composable
(nếu không, phương thức này sẽ được thực thi lại ở mỗi lần kết hợp lại). Đoạn mã sau đây cho biết cách yêu cầu hệ thống di chuyển tiêu điểm bàn phím khi bạn nhấp vào nút:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
Chụp và thả tiêu điểm
Bạn có thể tận dụng tính năng tập trung để hướng dẫn người dùng cung cấp dữ liệu phù hợp mà ứng dụng của bạn cần để thực hiện nhiệm vụ, chẳng hạn như thu thập địa chỉ email hoặc số điện thoại hợp lệ. Mặc dù trạng thái lỗi sẽ thông báo cho người dùng về điều đang diễn ra, nhưng có thể bạn cần phải giữ trường có thông tin không chính xác cho trường này cho đến khi được khắc phục.
Để lấy tiêu điểm, bạn có thể gọi phương thức captureFocus()
và giải phóng phương thức đó sau đó bằng phương thức freeFocus()
, như trong ví dụ sau:
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
Mức độ ưu tiên của đối tượng sửa đổi tiêu điểm
Modifiers
có thể được xem là các phần tử chỉ có một phần tử con. Vì vậy, khi bạn đưa các phần tử đó vào hàng đợi, mỗi Modifier
ở bên trái (hoặc trên cùng) sẽ gói Modifier
ở bên phải (hoặc bên dưới). Điều này có nghĩa là Modifier
thứ hai nằm trong phần tử đầu tiên, để khi khai báo hai focusProperties
, chỉ một cái trên cùng hoạt động, vì các phần tử sau đây được chứa ở trên cùng.
Để làm rõ hơn khái niệm này, hãy xem đoạn mã sau:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Trong trường hợp này, focusProperties
cho biết item2
là tiêu điểm phù hợp sẽ không được sử dụng vì đã có trong tiêu điểm trước đó; do đó, item1
sẽ là tiêu điểm được sử dụng.
Khi sử dụng phương pháp này, cha mẹ cũng có thể đặt lại hành vi về mặc định bằng cách dùng FocusRequester.Default
:
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Thành phần mẹ không nhất thiết phải thuộc cùng một chuỗi đối tượng sửa đổi. Thành phần kết hợp mẹ có thể ghi đè thuộc tính tiêu điểm của thành phần kết hợp con. Chẳng hạn hãy xem xét FancyButton
này khiến nút không thể làm tâm điểm:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
Người dùng có thể đặt lại nút này làm tiêu điểm bằng cách đặt canFocus
thành true
:
FancyButton(Modifier.focusProperties { canFocus = true })
Giống như mọi Modifier
, các sự kiện liên quan đến tâm điểm sẽ hoạt động theo cách khác nhau dựa trên thứ tự bạn khai báo các sự kiện đó. Chẳng hạn, mã như sau giúp Box
có thể làm tâm điểm, nhưng FocusRequester
không liên kết với thành phần có thể làm tâm điểm này vì nó được khai báo sau thành phần có thể làm tâm điểm.
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
Bạn cần nhớ là focusRequester
liên kết với thành phần con có thể làm tâm điểm đầu tiên bên dưới nó trong hệ phân cấp. Vì vậy, focusRequester
này sẽ trỏ đến thành phần con có thể làm tâm điểm đầu tiên. Trong trường hợp không có giá trị nào, Analytics sẽ không trỏ đến bất kỳ giá trị nào.
Tuy nhiên, vì Box
có thể làm tâm điểm (nhờ đối tượng sửa đổi focusable()
) nên bạn có thể chuyển đến lớp này bằng cách điều hướng hai chiều.
Một ví dụ khác là cả hai cách sau đây đều hoạt động hiệu quả, vì đối tượng sửa đổi onFocusChanged()
đề cập đến phần tử có thể làm tâm điểm đầu tiên xuất hiện sau đối tượng sửa đổi focusable()
hoặc focusTarget()
.
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
Chuyển hướng tiêu điểm khi vào hoặc thoát
Đôi khi, bạn cần cung cấp một loại điều hướng rất cụ thể, chẳng hạn như loại hiển thị trong ảnh động dưới đây:
Trước khi tìm hiểu cách tạo tính năng này, bạn cần hiểu hành vi mặc định của tính năng tìm kiếm tâm điểm. Nếu không sửa đổi, thì một khi tính năng tìm kiếm tâm điểm chuyển đến mục Clickable 3
, thao tác nhấn DOWN
trên D-Pad (hoặc phím mũi tên tương đương) sẽ di chuyển tiêu điểm đến bất kỳ mục nào hiển thị bên dưới Column
, thoát khỏi nhóm đó và bỏ qua mục ở bên phải. Nếu không có mục có thể làm tâm điểm, tiêu điểm sẽ không di chuyển đến bất cứ đâu mà vẫn nằm trên Clickable 3
.
Để thay đổi hành vi này và cung cấp thành phần điều hướng như mong muốn, bạn có thể tận dụng đối tượng sửa đổi focusProperties
. Công cụ này giúp bạn quản lý những gì sẽ xảy ra khi tính năng tìm kiếm tâm điểm chuyển vào hoặc thoát khỏi Composable
:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
Bạn có thể hướng tiêu điểm đến một Composable
cụ thể bất cứ khi nào nó vào hoặc thoát khỏi một phần nhất định của hệ phân cấp – ví dụ: khi giao diện người dùng của bạn có hai cột và bạn muốn đảm bảo rằng bất cứ khi nào cột đầu tiên được xử lý, tiêu điểm sẽ chuyển sang cột thứ hai:
Trong ảnh gif này, sau khi tiêu điểm đến Clickable 3 Composable
trong Column
1, mục tiếp theo được lấy làm tâm điểm là Clickable 4
trong một Column
khác. Bạn có thể thực hiện hành vi này bằng cách kết hợp focusDirection
với các giá trị enter
và exit
bên trong đối tượng sửa đổi focusProperties
. Cả hai đều cần một lambda lấy tham số làm tham số hướng đến từ tiêu điểm và trả về FocusRequester
. Hàm lambda này có thể hoạt động theo 3 cách: việc trả về FocusRequester.Cancel
sẽ ngăn tiêu điểm tiếp tục, trong khi FocusRequester.Default
không thay đổi hành vi của nó. Thay vào đó, việc cung cấp FocusRequester
được đính kèm vào một Composable
khác sẽ khiến tiêu điểm chuyển đến Composable
cụ thể đó.
Thay đổi hướng tiến triển của trọng tâm
Để chuyển tiêu điểm đến mục tiếp theo hoặc hướng tới một hướng chính xác, bạn có thể sử dụng đối tượng sửa đổi onPreviewKey
và ngụ ý LocalFocusManager
để chuyển sang tiêu điểm bằng Đối tượng sửa đổi moveFocus
.
Ví dụ sau đây cho thấy hành vi mặc định của cơ chế lấy nét: khi phát hiện thao tác nhấn phím tab
, tiêu điểm sẽ chuyển đến phần tử tiếp theo trong danh sách tâm điểm. Mặc dù đây không phải là việc bạn thường cần định cấu hình, nhưng điều quan trọng là bạn phải biết hoạt động bên trong của hệ thống để có thể thay đổi hành vi mặc định.
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
Trong mẫu này, hàm focusManager.moveFocus()
chuyển tiêu điểm đến mục được chỉ định hoặc đến hướng ngụ ý trong tham số hàm.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Phản ứng để tập trung
- Tiêu điểm trong Compose
- Thay đổi thứ tự truyền tải tiêu điểm