포커스 동작 변경

요소의 기본 포커스 동작을 재정의해야 할 때가 있습니다. 화면에 나타납니다. 예를 들어 컴포저블을 그룹화하거나 컴포저블이 특정 컴포저블에 포커스를 요청하고, 그 중 하나에 명시적으로 포커스 요청 들어가거나 나올 때 포커스를 캡처 또는 해제하거나 포커스를 리디렉션합니다. 이 섹션에서는 기본값이 아닌 경우 포커스 동작을 변경하는 방법을 설명합니다. 알 수 있습니다

포커스 그룹으로 일관된 탐색 기능 제공

Jetpack Compose가 올바른 다음 항목을 즉시 추측하지 못하는 경우도 탭 탐색, 특히 복잡한 상위 Composables(예: 탭 및 목록이 중요합니다.

포커스 검색은 일반적으로 Composables의 선언 순서를 따르지만 Composables 중 하나가 완전히 보이지 않는 가로 스크롤이 가능합니다. 이 내용은 다음에서 확인할 수 있습니다. 다음 예시를 참조하세요.

Jetpack Compose는 화면의 경로를 따라 이동하지 말고 단방향 탐색:

상단 가로 탐색 메뉴와 그 아래에 항목 목록을 표시하는 앱의 애니메이션입니다.
그림 1. 상단 가로 탐색 메뉴와 그 아래에 항목 목록을 보여주는 앱의 애니메이션
를 통해 개인정보처리방침을 정의할 수 있습니다.

이 예에서는 개발자가 앱에 초점을 맞출 의도가 없었다는 것을 초콜릿 탭에서 아래의 첫 번째 이미지로 이동한 다음 페이스트리 탭. 그 대신, 팀이 보고 싶어 할 때까지 내부 콘텐츠에 초점을 맞추세요.

상단 가로 탐색 메뉴와 그 아래에 항목 목록을 표시하는 앱의 애니메이션입니다.
그림 2. 상단 가로 탐색 메뉴와 그 아래에 항목 목록을 보여주는 앱의 애니메이션
를 통해 개인정보처리방침을 정의할 수 있습니다.

컴포저블 그룹에 포커스를 두는 것이 중요한 상황 순차적으로, 이전 예의 탭 행과 같이 focusGroup() 수정자가 있는 상위 요소의 Composable는 다음과 같습니다.

LazyVerticalGrid(columns = GridCells.Fixed(4)) {
    item(span = { GridItemSpan(maxLineSpan) }) {
        Row(modifier = Modifier.focusGroup()) {
            FilterChipA()
            FilterChipB()
            FilterChipC()
        }
    }
    items(chocolates) {
        SweetsCard(sweets = it)
    }
}

양방향 탐색은 주어진 방향 - 다른 그룹의 요소가 완전히 표시되지 않는 요소보다 가까운 경우 항목을 선택하면 탐색에서 가장 가까운 항목을 선택합니다. 이를 방지하려면 focusGroup() 수정자를 적용하면 됩니다.

FocusGroup는 전체 그룹이 포커스 측면에서 단일 항목처럼 보이게 합니다. 그룹 자체는 포커스를 받지 않으며, 대신 가장 가까운 하위 그룹이 대신 초점을 맞출 수 있습니다. 이렇게 하면 탐색이 완전하지 않은 지도로 이동하는 것을 알 수 있습니다. 항목을 삭제할 수 있습니다.

여기서는 FilterChip의 세 인스턴스가 SweetsCard 항목(SweetsCards이 다음 항목에 완전히 표시되는 경우에도 해당) 사용자 및 일부 FilterChip이(가) 숨겨져 있을 수 있습니다. 이는 focusGroup 수정자는 포커스 관리자에게 항목의 순서를 조정하도록 지시합니다. 탐색이 더 쉽고 일관성이 있도록 UI에 중점을 둡니다.

focusGroup 수정자가 없는 경우 FilterChipC가 표시되지 않으면 포커스 내비게이션이 마지막에 잡습니다. 그러나 이러한 수정자를 추가하면 검색만 가능하지만 FilterChipB 직후에 포커스를 획득합니다. 사용자가 예상할 수 있는 내용입니다.

컴포저블을 포커스 가능하게 만들기

Button 또는 연결된 clickable 수정자 예를 들어 포커스 가능 동작을 컴포저블에 적용하려면 focusable 수정자를 사용하면 됩니다.

var color by remember { mutableStateOf(Green) }
Box(
    Modifier
        .background(color)
        .onFocusChanged { color = if (it.isFocused) Blue else Green }
        .focusable()
) {
    Text("Focusable 1")
}

컴포저블에 포커스 불가능 만들기

일부 요소가 참여해서는 안 되는 상황이 있을 수 있습니다. 초점을 맞추고 있습니다. 드물지만 canFocus property를 활용할 수 있습니다. Composable를 포커스 가능에서 제외합니다.

var checked by remember { mutableStateOf(false) }

Switch(
    checked = checked,
    onCheckedChange = { checked = it },
    // Prevent component from being focused
    modifier = Modifier
        .focusProperties { canFocus = false }
)

FocusRequester를 사용하여 키보드 포커스 요청

경우에 따라서는 사용자 상호작용입니다. 예를 들어 사용자에게 기기를 다시 시작하고 싶은지 물어볼 수 있습니다. 양식을 작성하고 '예'를 누르면 첫 번째 필드에 다시 포커스를 알 수 있습니다.

가장 먼저 해야 할 일은 FocusRequester 객체를 컴포저블 함수를 선택합니다. 다음 코드에서 스니펫, FocusRequester 객체는 TextField Modifier.focusRequester라는 수정자를 사용합니다.

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

FocusRequester의 requestFocus 메서드를 호출하여 실제 포커스 요청을 보낼 수 있습니다. 이 메서드는 Composable 컨텍스트 외부에서 호출해야 합니다. 그러지 않으면 리컴포지션마다 다시 실행됩니다. 다음 스니펫 버튼이 클릭되었을 때 시스템에 키보드 포커스를 이동하도록 요청하는 방법을 클릭수:

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")
}

포커스 캡처 및 해제

포커스를 활용하여 사용자가 앱에 적합한 데이터를 제공하도록 안내할 수 있습니다. 올바른 이메일 주소 또는 전화 받기와 같은 작업을 수행해야 합니다. 있습니다. 오류 상태는 사용자에게 상황을 알려주지만 잘못된 정보가 있는 필드가 필요한 경우 데이터가 수집될 때까지 수정되었습니다.

포커스를 캡처하려면 captureFocus() 메서드를 호출하면 됩니다. 다음과 같이 나중에 freeFocus() 메서드를 사용하여 해제합니다. 예:

val textField = FocusRequester()

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

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

포커스 수정자의 우선순위

Modifiers는 하위 요소가 하나만 있는 요소로 보일 수 있으므로 큐에 추가할 때 왼쪽 또는 상단의 각 Modifier가 다음에 오는 Modifier를 래핑합니다. 오른쪽 (또는 아래)에 위치해 있습니다. 즉, 두 번째 Modifier가 두 개의 focusProperties를 선언할 때 다음 항목이 최상위에 포함되어 있기 때문입니다.

개념을 더 명확히 하려면 다음 코드를 참고하세요.

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

이 경우 item2를 오른쪽 포커스로 나타내는 focusProperties가 이전 문구에 포함되어 있으므로 사용해서는 안 됩니다. 따라서 item1가 있습니다.

이 접근 방식을 활용하면 부모는 FocusRequester.Default 사용:

Modifier
    .focusProperties { right = Default }
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

상위 요소가 동일한 수정자 체인의 일부가 아니어도 됩니다. 부모 하위 컴포저블의 포커스 속성을 덮어쓸 수 있습니다. 예를 들어 버튼에 포커스를 둘 수 없도록 하는 다음 FancyButton를 고려하세요.

@Composable
fun FancyButton(modifier: Modifier = Modifier) {
    Row(modifier.focusProperties { canFocus = false }) {
        Text("Click me")
        Button(onClick = { }) { Text("OK") }
    }
}

사용자는 canFocustrue로 설정하여 이 버튼을 다시 포커스 가능하게 만들 수 있습니다.

FancyButton(Modifier.focusProperties { canFocus = true })

모든 Modifier와 마찬가지로 포커스 관련 항목은 순서에 따라 다르게 동작합니다. 이를 선언할 수 있습니다 예를 들어 다음과 같은 코드는 Box 포커스 가능하지만 FocusRequester는 이 포커스 가능 항목과 연결되어 있지 않습니다. 포커스 가능 함수 뒤에 선언되어야 합니다.

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

focusRequester는 첫 번째 포커스 가능하므로 이 focusRequester는 첫 번째 포커스 가능 하위 요소 사용할 수 있는 항목이 없는 경우 아무것도 가리키지 않습니다. 그러나 focusable() 수정자 덕분에 Box는 포커스 가능하므로 양방향 탐색을 사용하여 해당 페이지로 이동할 수 있습니다.

또 다른 예로 onFocusChanged() 수정자는 focusable() 또는 focusTarget() 수정자

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

들어가거나 나갈 때 포커스 리디렉션

경우에 따라 다음과 같이 매우 구체적인 종류의 탐색을 제공해야 할 때가 있습니다. 아래 애니메이션에서 볼 수 있습니다.

나란히 놓인 두 개의 열과 한 열에서 다른 열로 포커스를 애니메이션으로 보여주는 화면의 애니메이션입니다.
그림 3. 나란히 놓인 두 개의 열과 한 열에서 다른 열로 포커스를 애니메이션 처리하는 화면의 애니메이션
를 통해 개인정보처리방침을 정의할 수 있습니다.

만드는 방법을 알아보기 전에 먼저 Cloud Functions의 포커스 검색의 동작을 보여 줍니다. 수정하지 않고 포커스 검색이 D패드 (또는 동등한 항목)의 DOWN를 눌러 Clickable 3 항목에 도달합니다. 화살표 키)을 누르면 포커스가 Column 아래에 표시된 항목으로 이동합니다. 그룹에서 나가고 오른쪽 사람은 무시합니다. 값이 없는 경우 포커스 가능 항목을 사용할 수 있는 경우 포커스가 어디로도 이동하지 않고 Clickable 3

이 동작을 변경하고 의도한 탐색을 제공하려면 focusProperties 수정자: 포커스가 있을 때 발생하는 작업을 관리하는 데 도움이 됩니다. 검색에서 Composable를 입력하거나 종료합니다.

val otherComposable = remember { FocusRequester() }

Modifier.focusProperties {
    exit = { focusDirection ->
        when (focusDirection) {
            Right -> Cancel
            Down -> otherComposable
            else -> Default
        }
    }
}

특정 Composable가 들어올 때마다 포커스를 특정 Composable로 보낼 수 있음 계층 구조의 특정 부분에서 나갑니다(예: UI에 두 개의 요소가 첫 번째 열이 처리될 때마다 포커스는 두 번째로 전환됩니다.

나란히 놓인 두 개의 열과 한 열에서 다른 열로 포커스를 애니메이션으로 보여주는 화면의 애니메이션입니다.
그림 4. 나란히 놓인 두 개의 열과 한 열에서 다른 열로 포커스를 애니메이션 처리하는 화면의 애니메이션
를 통해 개인정보처리방침을 정의할 수 있습니다.

이 gif에서 포커스가 Column 1의 Clickable 3 Composable에 도달하면 포커스가 있는 다음 항목은 다른 ColumnClickable 4입니다. 이 동작 focusDirectionenterexit와 결합하여 얻을 수 있습니다. 값을 focusProperties 수정자 내로 변경할 수 있습니다. 둘 다 를 매개변수로 사용하면 포커스가 맞춰지고 FocusRequester 이 람다는 세 가지 방식으로 작동할 수 있습니다. FocusRequester.Cancel는 포커스가 계속되는 것을 중지하며, FocusRequester.Default는 동작을 변경하지 않습니다. 대신 다른 Composable에 연결된 FocusRequester를 사용하면 해당 항목으로 포커스가 점프합니다. 구체적인 Composable

포커스 진행 방향 변경

다음 항목이나 정확한 방향으로 포커스를 이동하려면 onPreviewKey 수정자를 활용하고 LocalFocusManagermoveFocus 수정자를 사용하여 포커스를 진행합니다.

다음 예는 포커스 메커니즘의 기본 동작을 보여줍니다. tab 키 누름이 감지되면 포커스가 포커스의 다음 요소로 이동합니다. 목록. 일반적으로 구성할 필요는 없지만 시스템의 내부 작동 방식을 파악하여 기본 이미지를 있습니다.

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
        }
    }
)

이 샘플에서 focusManager.moveFocus() 함수는 포커스를 지정된 항목 또는 함수 매개변수에 내포된 방향으로 이동합니다.