Flutter 접근성 적용 시 참고사항 1부
안녕하세요, 엔비전스입니다.
기업마다 앱을 개발할 때 출시 목적 및 여러 상황 등 다양한 조건을 고려하여 언어와 프레임워크를 선택합니다. 즉 그만큼 선택할 수 있는 언어와 프레임워크의 범위가 점점 넓어지고 있다는 것입니다. 이러한 다양한 개발 트랜드를 아는데, 새로운 언어나 프레임워크의 접근성 API 적용과 관련한 정보를 다루지 않고는 못 배기겠지요?
이번에는 모바일 크로스플랫폼 개발로 핫한 Flutter로 앱 개발 시 참고해야 할 사항들을 정리하는 글을 발행하게 되었습니다.
Flutter는 Dart라는 한 언어로 여러 플랫폼을 개발하는 장점이 있는 반면 Android, iOS의 스크린 리더는 코드를 처리하는 방식이 많이 다릅니다. 따라서 여러 네이티브 위젯이나 접근성 적용을 위해 Semantics 위젯 사용시에는 각 스크린 리더가 해당 요소를 어떻게 처리하는지를 아는 것이 중요합니다. 이는 시각장애인 사용자는 오로지 스크린 리더가 읽어주는 내용에 의존하여 정보를 파악해야 하기 때문입니다.
본 아티클에서는 여러 위젯의 스크린 리더 처리 방식에 대한 설명과 함께 접근성 대응을 위한 여러 해결방안들을 일부 포함하고 있습니다. 해결방안은 iOS, Android 스크린 리더에서 공통적으로 최대한 적용할 수 있는 방법을 제시합니다.
이는 특정 접근성 이슈를 해결하려고 할 때 여러 커스텀 위젯마다 Android, iOS를 별도로 분리해서 조건문으로 접근성 이슈를 처리하는 것은 자칫 실수할 확률이 크기 때문입니다. 따라서 하나의 접근성 구현만으로 두 모바일 플랫폼 사용자에게 최대한 동일한 사용자 경험을 제공할 수 있는 방향으로 정리를 해 보았습니다.
Semantics 처리에 관하여
각 플랫폼마다 커스텀 위젯에 대한 접근성 대응을 하기 위한 대안을 제공하고 있습니다. Flutter는 Semantics라는 위젯을 기존 위젯에 덧씌워서 커스텀 위젯에 대한 접근성을 적용할 수 있습니다. Semantics 위젯을 사용할 때 알아 두어야 할 특징을 정리했습니다.
커스텀 위젯을 위한 요소 유형 및 상태정보
- Android 네이티브와 마찬가지로 Image에 GestureDetector 위젯을 이용한 onTap 이벤트를 구현하는 경우에는 이미지에 클릭 리스너가 추가되어 TalkBack에서는 버튼이라고 읽어줍니다. 그러나 VoiceOver에서는 이미지라고만 읽어주므로 Semantics 내에서 button속성을 true를 설정하여 아이폰에서도 버튼으로 읽어줄 수 있도록 합니다. 코드 예시는 이미지 위젯에 온탭 적용 시 참고사항 팁을 참고합니다.
- Semantics 위젯 내에서 checked 속성을 true로 설정하면 체크박스로 읽어주게 됩니다. 참고로 체크박스는 iOS에서는 전환버튼으로 읽어주며 값(value)는 켜짐, 꺼짐입니다. 라디오 버튼의 경우에도 iOS에서는 전환 버튼으로 읽어줍니다. 다만 라디오버튼의 경우 켜짐 상태에서는 두 번 탭을 해도 꺼짐 상태로 변경되지 않습니다. 이는 iOS 네이티브에는 체크박스, 라디오버튼이 없고 오로지 selected Trait로 처리하기 때문에 가장 비슷한 요소로 적용한 것 같습니다.
- 특정 요소가 버튼이면서 섹션 제목이기도 한 경우 기존 버튼 위젯 상위에 Semantics 위젯을 추가하고 header 속성을 true로 설정하면 버튼 제목이라고 읽어줍니다. 다만 ListView와 같은 위젯에서는 이미지와 텍스트와 같은 별도로 분리된 요소의 접근성 초점이 하나로 합쳐지게 되는데 버튼과 같은 요소 유형을 주고 싶을 때에는 이미지는 excludeSemantics: true로 설정하여 접근성 트리에서 제거하고 텍스트에 버튼 유형을 주어 적용이 가능합니다.
- 커스텀 라디오버튼을 구현할 때에는 모든 라디오버튼을 버튼으로 설정하고 선택된 라디오버튼에는 selected속성을 true, 선택되지 않은 라디오버튼에는 false로 Semantics 위젯에서 구현함으로써 적용이 가능합니다. Semantics 내에서 tooltip 속성으로 ⅓ 2/3과 같은 라디오 개수 그룹 정보를 주면 조금 더 명확한 레이아웃에 대한 이해를 도울 수 있습니다. 커스텀 탭 구현 시에도 이와 같은 방법을 사용할 수 있습니다. 참가로 Semantics 위젯에는 탭, 라디오버튼에 대한 요소 유형이 없습니다. 물론 그룹으로 묶을 수 있는 inMutuallyExclusiveGroup 속성을 제공하고 있으나 테스트 한 결과 아직까지는 읽어주는 것에 영향을 미치지는 않은 것을 확인하였습니다.
- 커스텀 슬라이더 구현 시에는 Semantics 위젯 내에서 onIncrease, onDecrease 메서드를 통해 한 손가락 위로 쓸기, 아래로 쓸기 했을 때의 기능을 정의할 수 있습니다. 이때 slider 요소 유형을 제공하지 않아도 onIncrease, onDecrease 이벤트만 구현되어 있으면 자동으로 슬라이더라고 요소 유형을 읽어주게 됩니다. 단 안드로이드에서는 버튼 유형을 함께 추가하여도 버튼은 읽어주지 못합니다. 또한 해당 슬라이더에 liveRegion true 속성을 주어야 슬라이더 조정 시 변경되는 값을 읽어주게 됩니다. 자세한 코드 예시는 커스텀 슬라이더 접근성 적용 팁을 참고합니다.
접근성 힌트
- onPressed, onChanged와 같은 이벤트나 GestureDetector > onTap과 같은 이벤트 적용시 접근성 초점을 받는 요소가 해당 이벤트를 수신하는 요소의 하위인 경우에는 클릭 리스너 혹은 롱클릭 리스너가 있더라도 TalkBack에서는 ‘활성화 하려면 두 번 탭하세요’와 같은 힌트 메시지를 출력하지 못합니다. 관련해서는 클릭 리스너가 있음에도 TalkBack에서 힌트 메시지를 읽어주지 않을 때의 점검 사항 팁을 참고합니다.
- Android 네이티브와 마찬가지로 클릭, 롱클릭에 대한 힌트를 Semantics 위젯에 포함된 onTapHint, onLongPressHint 속성을 사용하여 변경할 수 있습니다. 그러나 접근성 초점이 반드시 해당 이벤트를 수신받는 곳이어야 변경된 힌트가 적용되며 힌트 자체가 없는 경우에는 해당 메서드를 사용하여 힌트를 변경하여도 적용되지 않습니다. 단 iOS는 클릭 리스너에 대한 힌트 자체가 없기 때문에 적용되지 않습니다.
- 네이티브 탭 구현 시 지원 언어에 한국어를 추가하면 탭, 4개쭝 2번째와 같이 해당 정보를 대체 텍스트 형태로 읽어줍니다.
- Semantics 위젯 내의 hint 속성을 사용하면 iOS VoiceOver에서도 접근성 힌트를 제공할 수 있습니다. 그러나 이렇게 구현하면 안드로이드에서는 레이블, 힌트, 요소 유형, 클릭 리스너에 대한 힌트까지 읽게 되어서 TalkBack 사용자는 중복되는 메시지를 연속해서 들어야 하는 번거로움이 따르게 됩니다. 그러나 이 부분은 현재로서는 어쩔 수가 없을 것 같습니다.
기타
- Tooltip 속성을 사용하여 텍스트를 추가하면 Android의 경우에는 요소 유형 뒤에, iOS는 레이블 뒤에, 요소 유형 앞에 읽어줍니다.
- 네이티브 탭 구현 시 지원 언어에 한국어를 추가하면 탭, 4개쭝 2번째와 같이 해당 정보를 대체 텍스트 형태로 읽어줍니다.
- Semantics 위젯 중 link는 안드로이드 접근성 요소 유형 정보에서 지원하지 않기 때문에 TalkBack에서는 버튼으로 읽어줍니다.
다음 회차에서 계속 이어집니다.