Thay đổi chế độ lấy nét

Đôi khi, cần ghi đè hành vi lấy tiêu điểm mặc định của các phần tử trên màn hình của bạn. Ví dụ: bạn nên nhóm các thành phần kết hợp lại ngăn chặn tập trung vào một thành phần kết hợp nhất định, yêu cầu trọng tâm rõ ràng vào một thành phần kết hợp, lấy hoặc thả tiêu điểm hoặc tiêu điểm chuyển hướng khi vào hoặc thoát. Chiến dịch này mô tả cách thay đổi hành vi của tiêu điểm khi không phải là chế độ mặc định của bạn.

Điều hướng nhất quán qua các nhóm trọng điểm

Đôi khi, Jetpack Compose không dự đoán ngay mục tiếp theo chính xác cho điều hướng bằng thẻ, đặc biệt là khi Composables mẹ phức tạp như thẻ và vào danh sách.

Mặc dù tính năng tìm kiếm tiêu điểm thường tuân theo thứ tự khai báo của Composables, điều này là không thể 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 trình đơn có thể cuộn theo chiều ngang không hiển thị đầy đủ. Nội dung 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 nhất với điểm bắt đầu của như được hiển thị bên dưới, thay vì tiếp tục trên đường dẫn mà bạn mong muốn điều hướng một chiều:

Ảnh động về một ứng dụng hiển thị thanh điều hướng ngang ở trên cùng và danh sách các mục ở bên dưới.
Hình 1. Ảnh động về một ứng dụng hiển thị thanh điều hướng ngang ở trên cùng và danh sách các mục ở bên dưới

Trong ví dụ này, rõ ràng là các nhà phát triển không có ý định tập trung vào chuyển từ thẻ Sô cô la sang hình ảnh đầu tiên bên dưới rồi quay lại Thẻ Bánh ngọt. Thay vào đó, họ muốn tập trung tiếp tục vào các thẻ cho đến khi thẻ cuối cùng rồi tập trung vào nội dung bên trong:

Ảnh động về một ứng dụng hiển thị thanh điều hướng ngang ở trên cùng và danh sách các mục ở bên dưới.
Hình 2. Ảnh động về một ứng dụng hiển thị thanh điều hướng ngang ở trên cùng và danh sách các mục ở bên dưới

Trong trường hợp cần phải có một nhóm thành phần kết hợp cần lấy trọng tâm tuần tự, như trong hàng Thẻ ở ví dụ trước, bạn cần gói Composable trong phần tử 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 cho hướng – nếu một phần tử từ một nhóm khác ở gần hơn so với một phần tử không hiển thị hoàn toàn mục trong nhóm hiện tại, điều hướng sẽ chọn mục gần nhất. Để tránh tình trạng này hành vi, bạn có thể áp dụng đối tượng sửa đổi focusGroup().

FocusGroup làm cho toàn bộ nhóm trông giống như một thực thể duy nhất về tiêu điểm, nhưng nhóm đó sẽ không nhận được tiêu điểm — thay vào đó, trẻ gần nhất sẽ lấy tiêu điểm. Bằng cách này, điều hướng biết chuyển đến vùng hiển thị 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 SweetsCard mục, ngay cả khi SweetsCards hoàn toàn hiển thị với người dùng 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 đều được lấy làm tâm điểm để điều hướng 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 xuất hiện, hãy lấy tiêu điểm hoạt động điều hướng sẽ hiển thị quảng cáo đó cuối cùng. Tuy nhiên, việc thêm một đối tượng sửa đổi như vậy khiến nó không được chỉ có thể phát hiện, nhưng cũng sẽ có được tiêu điểm ngay sau FilterChipB, vì mà người dùng mong đợi.

Đặt một 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 bạn muốn thêm cụ thể hành vi có thể làm tâm điểm cho một thành phần kết hợp, hãy 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")
}

Đặt một thành phần kết hợp không thể làm tâm điểm

Có thể có những tình huống trong đó một số thành phần của bạn không nên tham gia 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 phạm vi làm tâm đ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 tiêu điểm bàn phím với FocusRequester

Trong một số trường hợp, bạn nên yêu cầu rõ ràng tiêu điểm dưới dạng 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 khởi động lại hay không điền vào biểu mẫu và nếu họ nhấn vào "có" bạn muốn lấy nét lại trường đầu tiên của biểu mẫu đó.

Việc đầu tiên cần làm là liên kế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 mã sau thì đối tượng FocusRequester được liên kết với TextField bằng cách đặt một giá trị đố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 yêu cầu lấy tiêu điểm thực tế. Bạn nên gọi phương thức này bên ngoài ngữ cảnh Composable (nếu không, hệ thống sẽ thực thi lại ở mọi quá trình kết hợp lại). Đoạn mã sau cho biết cách yêu cầu hệ thống di chuyển tiêu điểm bàn phím khi nút này được đã nhấp vào:

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 tiêu điểm để hướng dẫn người dùng cung cấp đúng dữ liệu cho ứng dụng của bạn cần thực hiện tác vụ của mình— ví dụ: nhận được địa chỉ email hoặc số điện thoại hợp lệ số. Mặc dù trạng thái lỗi thông báo cho người dùng về những gì đang xảy ra, bạn có thể cần trường có thông tin không chính xác để luôn lấy tiêu điểm cho đến khi trường được đã được khắc phục.

Để lấy nét, bạn có thể gọi phương thức captureFocus() và hãy giải phóng phương thức đó sau bằng phương thức freeFocus(), như trong ví dụ sau ví dụ:

val textField = FocusRequester()

TextField(
    value = text,
    onValueChange = {
        text = it

        if (it.length > 3) {
            textField.captureFocus()
        } else {
            textField.freeFocus()
        }
    },
    modifier = Modifier.focusRequester(textField)
)

Quyền ư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, do đó, khi bạn hàng đợi chúng, mỗi Modifier ở bên trái (hoặc trên cùng) sẽ gói Modifier theo sau bên phải (hoặc bên dưới). Điều này có nghĩa là Modifier thứ hai nằm bên trong phần tử đầu tiên, để khi khai báo hai focusProperties, chỉ phần tử trên cùng một hoạt động, vì các phần sau nằm trong lớp trên cùng.

Để làm rõ khái niệm này, hãy xem 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ì thuộc tính này có trong phần trước; do đó, item1 sẽ là một mật khẩu đã sử dụng.

Khi tận dụng phương pháp này, cha mẹ cũng có thể đặt lại hành vi về trạng thái mặc định theo sử 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 nằm trong cùng một chuỗi đối tượng sửa đổi. Phụ huynh thành phần kết hợp có thể ghi đè thuộc tính tâm điểm của thành phần kết hợp con. Ví dụ: hãy xem xét FancyButton sau đâ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 tâm điểm bằng cách đặt canFocus thành true:

FancyButton(Modifier.focusProperties { canFocus = true })

Giống như mọi Modifier, các lệnh liên quan đến tiêu điểm sẽ hoạt động khác nhau tuỳ theo thứ tự bạn khai báo chúng. Ví dụ: mã như sau sẽ làm cho 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 có thể làm tâm điểm.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

Bạn cần nhớ rằng focusRequester được liên kết với có thể lấy tiêu điểm bên dưới nó trong hệ phân cấp, vì vậy focusRequester này trỏ đến phần tử con đầu tiên có thể làm tâm điểm. Trong trường hợp không có biểu tượng nào, cảnh báo sẽ không trỏ đến bất kỳ vị trí nào. Tuy nhiên, vì Box có thể làm tâm điểm (nhờ đối tượng sửa đổi focusable()), bạn có thể điều hướng đến vị trí đó bằng tính năng điều hướng hai chiều.

Một ví dụ khác là một trong hai cách sau đây sẽ hoạt động, dưới dạng onFocusChanged() đối tượng sửa đổi là 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 truy cập 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ư như trong ảnh động dưới đây:

Ảnh động của một màn hình cho thấy 2 cột gồm các nút được đặt cạnh nhau và tạo ảnh động cho tiêu điểm từ cột này sang cột khác.
Hình 3. Ảnh động của một màn hình cho thấy 2 cột các nút được đặt cạnh nhau và tạo ảnh động cho tiêu điểm từ cột này sang cột khác

Trước khi chúng ta tìm hiểu sâu về cách tạo đối tượng này, điều quan trọng là phải hiểu chế độ cài đặt mặc định của tính năng tìm kiếm tiêu điểm. Sau khi tìm kiếm tiêu điểm, bạn không cần sửa đổi gì cả chuyển đến mục Clickable 3, nhấn DOWN trên D-Pad (hoặc mục tương đương phím mũi tên) sẽ di chuyển tiêu điểm đến bất kỳ mục nào hiển thị bên dưới Column, đang rời khỏi nhóm và bỏ qua nút ở bên phải. Nếu không có Các mục có thể làm tâm điểm có sẵn, tiêu điểm không di chuyển đi đâu nhưng vẫn ở trên Clickable 3.

Để thay đổi hành vi này và cung cấp khả năng điều hướng như mong muốn, bạn có thể tận dụng Đối tượng sửa đổi focusProperties, giúp bạn quản lý những gì sẽ xảy ra khi tiêu điểm công cụ tìm kiếm sẽ nhập 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ể chuyển tiêu điểm đến một Composable cụ thể bất cứ khi nào tiêu điểm đó nhập 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 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 vị trí thứ hai:

Ảnh động của một màn hình cho thấy 2 cột gồm các nút được đặt cạnh nhau và tạo ảnh động cho tiêu điểm từ cột này sang cột khác.
Hình 4. Ảnh động của một màn hình cho thấy 2 cột các nút được đặt cạnh nhau và tạo ảnh động cho tiêu điểm từ cột này sang cột khác

Trong ảnh GIF này, sau khi tiêu điểm đạt đến Clickable 3 Composable trong Column 1, mục tiếp theo được lấy tiêu điểm là Clickable 4 trong Column khác. Hành vi này có thể đạt được bằng cách kết hợp focusDirection với enterexit các giá trị bên trong đối tượng sửa đổi focusProperties. Cả hai đều cần một lambda nhận dưới dạng tham số, hướng của tiêu điểm và trả về FocusRequester. Hàm lambda này có thể hoạt động theo ba cách khác nhau: trả về FocusRequester.Cancel ngăn tiêu điểm tiếp tục, trong khi FocusRequester.Default không thay đổi hành vi. Cung cấp thay vì FocusRequester được đính kèm vào một Composable khác làm cho tiêu điểm chuyển đến vị trí đó Composable cụ thể.

Thay đổi hướng tiến của trọng tâm

Để chuyển tiêu điểm đến mục tiếp theo hoặc về một hướng chính xác, bạn có thể tận dụng đối tượng sửa đổi onPreviewKey và ngụ ý LocalFocusManager để chuyển tâm đ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 một Đã phát hiện thấy thao tác nhấn phím tab, tiêu điểm sẽ chuyển sang phần tử tiếp theo trong tiêu điểm danh sách. Mặc dù đây không phải là điều bạn thường cần định cấu hình, nhưng điều quan trọng là biết được hoạt động bên trong của hệ thống để có thể thay đổi chế độ mặc định hành vi.

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 tâm điểm lên mục được chỉ định hoặc theo hướng ngụ ý trong tham số hàm.