Android 최초 기여에 대한 예제를 조사할 때 Kotlin으로 작성된 애니메이션에 대한 예제는 거의 없었습니다. 네이티브 애니메이션 내에서 접근성 고려 사항에 대한 코드 예제도 거의 없었습니다.
자! Kotlin에서 기본 '확장' 애니메이션을 작성하는 방법을 살펴보고 TalkBack 또는 확대된 텍스트가 켜져 있는 사용자를 지원하는 방법에 대해 이야기해 보겠습니다. 모든 코드는 이 예제 리포지토리에서 사용할 수 있으며 그 안에 애니메이션 보기가 있는 단일 활동을 만듭니다. 이 코드의 기반이 되는 코드는 Calum Turner와 공동으로 작성되었습니다.
안드로이드 접근성(a11y)
모든 Android 기기에는 TalkBack이라는 스크린 리더가 내장되어 있습니다. 이것은 장치의 설정에서 켤 수 있으며 최초 사용 가이드가 내장되어 있습니다. 제스처는 페이지를 탐색하는 데 사용되며 초점이 맞춰진 요소에 대한 설명을 소리내어 읽어줍니다. 이것이 없으면 많은 시각 장애가 있는 사용자가 앱을 사용할 수 없게 됩니다.
가장 중요한 것은 올바른 요소에 초점을 맞출 수 있고, 설명이 있으며, 보기에 대한 변경 사항이 발표된다는 것입니다.
동일한 설정 메뉴 내에서 기본 기본 글꼴 크기를 1.0부터 조정할 수 있습니다. 보기는 모든 요소가 여전히 존재하고 작동하는 이러한 글꼴 크기 변경에 반응해야 합니다.
레이아웃
레이아웃의 세부적인 스타일은 이 예제에서만 볼 수 있으므로 여기에서 살펴보지 않겠지만 접근성 터치는 강조할 가치가 있습니다.
두 가지 속성이 사용됩니다. android:contentDescription
및 android:importantForAccessibility
.
contentDescription
요소가 포커스를 얻을 때 읽히는 것입니다. 포커스를 얻는 모든 ImageView의 경우 이것이 필수적입니다. 그렇지 않으면 스크린 리더가 사용자에게 쓸모없는 '레이블이 지정되지 않은' 내용을 대신 읽어줍니다.
이것이 버튼이라면 기본적으로 '
android:contentDescription="tap to toggle extra person information"
importantForAccessibility:no
도 사용합니다. '+' TextView에 대한 포커스를 끄려면 두 개의 배지 아래에 있는 텍스트가 설명을 제공하므로 '+'는 소리 내어 읽을 때 도움이 되기보다 혼란스럽습니다.
이 두 가지 경우 모두 TalkBack이 켜진 실제 기기에서 수동 테스트를 수행하는 것이 시각적 요소 없이 컨텍스트가 의미가 있는지 여부를 가장 잘 알 수 있습니다.
확장 애니메이션
애니메이션은 '정보' 아이콘 탭에서 활성화되어 세부정보 섹션 확장을 토글합니다.
애니메이션 코드에만 집중할 수 있도록 단일 활동 내에서 이 모든 작업을 수행합니다. 실제 앱에서는 이것이 적용되는 뷰가 자체 프래그먼트 또는 리사이클러 뷰 내에 있을 가능성이 높으므로 보다 추상화된 코드 구조가 사용됩니다.
리스너 설정
예제 활동의 onCreate
내에서 먼저 아이콘에 리스너를 설정하고 전환할 보기를 전달해야 합니다.
infoIcon.setOnClickListener { toggleCardBody(root.personEntryBody) }
또한 보기가 토글되었는지 추적하기 위해 클래스 내에 변수를 설정하여 처음에 닫히도록 설정했습니다.
private var isToggled = false
확장 애니메이션 전환
레이아웃 내에서 높이를 personEntryBody
으로 설정했습니다. 0dp
으로 .
이 열기를 토글하려면 설정할 새 높이, 애니메이션의 길이, 애니메이션의 각 순간에 있어야 하는 높이를 알아야 합니다.
그런 다음 isToggled
을 설정해야 합니다. 역으로 변환하고 다시 탭하면 역으로 작동하는지 확인합니다.
private fun toggleCardBody(body: View) {
body.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val maxHeight = body.measuredHeight + body.paddingTop + body.paddingBottom
val startHeight = if (isToggled) maxHeight else 0
val targetHeight = if (isToggled) 0 else maxHeight
val expandAnimator = ValueAnimator
.ofInt(startHeight, targetHeight)
.setDuration(200)
expandAnimator.addUpdateListener {
val value = it.animatedValue as Int
body.layoutParams.height = value
body.requestLayout()
}
expandAnimator.doOnEnd {
isToggled = !isToggled
}
expandAnimator.start()
}
뷰가 처음 그려질 때 높이는 0이므로 레이아웃을 다시 측정하여 새 크기를 계산해야 합니다.
Android 보기 레이아웃 문서에 설명된 대로 measure()
을 사용할 수 있습니다. 정보 아이콘을 탭할 때마다 다시 측정하기 위해 보기에 할당한 레이아웃 매개변수와 함께.
최대 높이를 계산하려면 측정된 높이에 포함되지 않은 상단 및 하단 패딩을 수동으로 추가해야 합니다.
isToggled
에 따라 다름 그런 다음 0에서 시작하는지 아니면 확장된 최대 높이에서 시작하는지 알 수 있으므로 반대 대상 높이도 알 수 있습니다.
Value Animator를 사용하여 시작 값에서 목표 끝 값으로 이동하고 지속 시간을 ms 단위로 설정합니다. 이 기간은 순전히 UX 느낌에 대한 이후의 수동 테스트를 기반으로 합니다.
ValueAnimator
.ofInt(startHeight, targetHeight)
.setDuration(200)
업데이트 리스너를 사용하여 지속 시간을 높이에 연결하여 업데이트할 때마다 새 레이아웃이 그려지도록 요청하고 매번 높이를 조정합니다.
expandAnimator.addUpdateListener {
val value = it.animatedValue as Int
body.layoutParams.height = value
body.requestLayout()
}
expandAnimator.doOnEnd {
isToggled = !isToggled
}
expandAnimator.start()
Kotlin을 사용하면서 androidx
도 추가합니다. 라이브러리를 build.gradle
doOnEnd
의 이점을 누리기 위해 확대. 이를 통해 isToggled
를 매우 쉽게 역전시킬 수 있습니다. 변수.
드디어 애니메이션을 시작합니다! 이미 아이콘 터치로 확장 및 축소되는 몸체가 있습니다!
더 부드러운 애니메이션
우리의 애니메이션은 기술적으로 그대로 작동하지만 좋은 추가 단계는 움직임이 더 자연스럽게 느껴지도록 보간기를 추가하는 것입니다.
expandAnimator.interpolator = FastOutSlowInInterpolator()
접근성 향상
11세 사용자를 돕기 위해 마지막으로 두 가지를 추가할 예정입니다.
먼저 AccessibilityEvent
를 사용하여 탐색을 도울 수 있습니다. .
expandAnimator.doOnEnd {
if (!isToggled) body.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
isToggled = !isToggled
}
즉, 애니메이션이 닫힌 상태에서 열린 상태로 이동할 때 포커스가 본문의 첫 번째 항목(이 경우 설명)의 포커스로 즉시 이동합니다. 레이아웃에서 정보 아이콘 작업에 대한 설명을 설정했지만 사용자가 다음 항목으로 이동하는 시각적 표시기에 의존할 수 없기 때문에 이를 처리할 수 있습니다.
두 번째로 다양한 글꼴 크기를 허용합니다. measure()
에서 반환된 측정 높이 는 장치 접근성 설정에서 설정된 글꼴 크기를 고려하지 않습니다. 따라서 대규모로 설정하면 설명의 하단이 너무 커서 맞지 않게 잘립니다.
프로그래밍 방식으로 글꼴 크기에 액세스하고 이를 기반으로 높이를 조절할 수 있습니다. 글꼴 크기가 레이아웃 높이로 작동하지 않는 부동 소수점이 될 수 있으므로 정수로 변환합니다.
val a11yFontScale = body.context.resources.configuration.fontScale
val maxHeight = ((body.measuredHeight + body.paddingTop + body.paddingBottom) * a11yFontScale).toInt()
완료!
그리고 거기에 우리의 최종 애니메이션이 도착했습니다! 몇 줄만 추가하면 적용 범위가 크게 늘어났고 Kotlin 및 Android 배지를 표시하는 부드럽게 확장되는 섹션이 생겼습니다.
읽어주셔서 감사합니다?
다음은 제가 최근에 작성한 다른 몇 가지 사항입니다.
- CodeceptJS E2E 테스트 사용자 정의
- Jest 및 Enzyme II로 반응 테스트
유용한 추가 기능
- Android용 KTX 탐색에 대한 Joe Birch의 훌륭한 androidx 게시물
- Android 접근성 튜토리얼:시작하기