포커스 타겟 그룹화

focusGroup 수정자를 사용하여 여러 포커스 타겟을 그룹화할 수 있습니다. 그룹화를 사용하면 포커스 계층 구조를 관리하고, 논리적 UI 영역 (예: 툴바, 대화상자 작업, 카드 목록)을 격리하고, 포커스가 영역에 진입하거나 영역에서 나갈 때 이를 가로챌 수 있습니다.

표적 집단 개념: 진입, 종료, 기본 순회

포커스 그룹은 하위 포커스 타겟 주위에 일관된 탐색 경계를 설정합니다. 포커스가 이 경계를 넘어 이동하는 방식을 이해하려면 다음 두 가지 주요 개념을 알아야 합니다.

  • Enter: 포커스 그룹 외부의 요소에서 그룹 내부의 요소로 포커스가 전환될 때 발생하는 이벤트입니다.
  • Exit: 포커스가 그룹의 마지막 내부 요소를 벗어나 외부 포커스 타겟으로 전환될 때 발생하는 이벤트입니다.
표적 집단 경계에서 Enter 및 Exit 전환을 보여주는 다이어그램
그림 1. 진입 및 종료 이벤트가 있는 포커스 그룹

기본 포커스 순회 동작

기본적으로 포커스 그룹이 외부에서 포커스를 받으면 다음이 적용됩니다.

  1. 그룹에 진입: Compose가 내부 하위 포커스 타겟을 자동으로 선택합니다. 1차원 (탭) 탐색의 경우 시각적 모양 순서에서 첫 번째 하위 요소를 선택합니다. 2차원 (화살표 키 또는 D패드) 탐색의 경우 들어오는 방향에 기하학적으로 가장 가까운 하위 요소를 선택합니다.
  2. 그룹 내 탐색: 탐색 키를 반복해서 누르면 그룹으로 둘러싸인 형제 포커스 타겟 간에 포커스가 이동합니다.
  3. 그룹 나가기: 사용자가 그룹 내의 마지막 포커스 타겟을 지나면 다음 키 누름으로 그룹을 나가고 포커스가 그룹 외부의 인접한 포커스 타겟으로 이동합니다.

포커스 진입 리디렉션 맞춤설정

많은 UI 디자인에서 기본 진입 타겟 (예: 외관 순서의 첫 번째 하위 요소)은 사용자에게 가장 논리적이거나 편리한 선택이 아닙니다.

의도 및 사용 사례

왼쪽에 '취소' 버튼이 있고 오른쪽에 기본 '확인' 버튼이 있는 확인 대화상자를 고려하세요. 사용자가 양식 필드에서 버튼 표시줄로 아래로 이동하는 경우:

  • 기본 동작: 시각적 표시 순서에서 첫 번째 버튼이므로 포커스가 '취소'에 배치됩니다.
  • 의도한 동작: '확인'이 기본 권장 작업이므로 사용자가 필요한 경우 왼쪽을 눌러 '취소'를 선택할 수 있도록 하면서 포커스 순서의 앞부분에서 버튼 그룹으로 들어갈 때 포커스가 '확인'에 바로 배치되도록 하는 것이 좋습니다.
버튼 그룹의 기본 포커스 항목
그림 2. 기본 포커스 진입 타겟이 있는 취소 및 확인 버튼

기타 일반적인 사용 사례는 다음과 같습니다.

  • TabRow를 입력할 때 현재 선택된 탭으로 포커스를 직접 이동
  • 미디어 재생 막대에 진입할 때 포커스를 '재생 / 일시중지' 컨트롤로 바로 이동

onEnter 및 FocusRequester를 사용한 구현

표적 집단에 진입할 때 포커스를 리디렉션하려면 다음 단계를 따르세요.

  1. 관련 포커스 타겟을 둘러싸는 상위 컨테이너에 focusGroup 수정자를 적용합니다.
  2. 컨테이너에 focusProperties 수정자를 적용하고 onEnter 콜백을 구성합니다.
  3. onEnter 콜백 (FocusEnterExitScope에 범위 지정)에서 focusRequester를 사용하여 기본 하위 요소와 연결된 FocusRequester에서 requestFocus()를 호출합니다.

val defaultChildRequester = remember { FocusRequester() }

Row(
    modifier = Modifier
        .focusGroup()
        .focusProperties {
            // Intercept entry into this group and redirect focus to the primary action
            onEnter = {
                defaultChildRequester.requestFocus()
            }
        }
) {
    Button(onClick = { /* Cancel action */ }) {
        Text("Cancel")
    }
    Button(
        onClick = { /* Confirm action */ },
        modifier = Modifier.focusRequester(defaultChildRequester)
    ) {
        Text("Confirm (Default)")
    }
}

onEnter 및 onExit 내에서 cancelFocusChange()를 호출하여 경계 간 포커스 전환을 완전히 차단하거나 requestedFocusDirection를 검사하여 사용자가 Tab, Down 또는 Left를 사용하여 탐색했는지에 따라 라우팅을 맞춤설정할 수도 있습니다.

스크롤 가능한 컨테이너는 기본적으로 포커스 그룹입니다.

Jetpack Compose에서 LazyRow, LazyColumn, LazyVerticalGrid과 같은 스크롤 가능한 컨테이너는 자동으로 포커스 그룹 역할을 합니다. onEnter 또는 onExit 콜백을 설정하기 위해 focusGroup 수정자를 명시적으로 적용할 필요는 없습니다.