เมื่อค้นหาตัวอย่างสำหรับการมีส่วนร่วม Android ครั้งแรก พบตัวอย่างน้อยมากสำหรับแอนิเมชันที่เขียนด้วย Kotlin นอกจากนี้ยังมีตัวอย่างโค้ดน้อยมากเกี่ยวกับการคำนึงถึงการเข้าถึงภายในแอนิเมชันแบบเนทีฟ
ลองมาดูกันดีกว่า! เราจะมาดูการเขียนแอนิเมชัน 'ขยาย' แบบเนทีฟใน Kotlin และพูดคุยเกี่ยวกับวิธีช่วยเหลือผู้ใช้ที่เปิด TalkBack หรือขยายขนาดข้อความ โค้ดทั้งหมดพร้อมใช้งานในที่เก็บตัวอย่างนี้ ซึ่งสร้าง Activity เดียวพร้อมมุมมองที่มีแอนิเมชันอยู่ภายใน โค้ดนี้ร่วมเขียนกับ Calum Turner

การเข้าถึง Android (a11y)
อุปกรณ์ Android ทุกเครื่องมาพร้อมกับอ่านหน้าจอในตัวชื่อ TalkBack สามารถเปิดใช้งานได้จากการตั้งค่าอุปกรณ์ และมีคู่มือการใช้งานครั้งแรกในตัว นิ้วทักท้าว (gestures) ใช้สำหรับนำทางรอบหน้าจอ โดยมีคำอธิบายขององค์ประกอบที่ได้รับโฟกัสอ่านออกเสียง ถ้าไม่มีสิ่งนี้ แอปจะใช้ไม่ได้สำหรับผู้ใช้บ้วนหรืออ่อนแอทางสายตาหลายคน
สิ่งสำคัญคือองค์ประกอบที่ถูกต้องต้องสามารถโฟกัสได้ มีคำอธิบาย และการเปลี่ยนแปลงของมุมมองต้องถูกประกาศ
ในเมนูการตั้งค่าเดียวกัน ขนาดแบบอักษรพื้นฐานเริ่มต้นสามารถปรับได้ โดยปรับสเกลจาก 1.0 มุมมองควรตอบสนองต่อการเปลี่ยนแปลงขนาดตัวอักษรนี้ โดยองค์ประกอบทั้งหมดยังคงอยู่และทำงานได้
เลย์เอาต์
เราจะไม่ดูรายละเอียดการจัดสไตล์ของเลย์เอาต์ที่นี่ เนื่องจากเฉพาะตัวสำหรับตัวอย่างนี้ แต่จุดสำคัญด้านการเข้าถึงมีค่าใช้เน้น
มีการใช้สองพร็อพเพอร์ตี้: android:contentDescription และ android:importantForAccessibility
contentDescription คือสิ่งที่อ่านออกมาเมื่อองค์ประกอบได้รับโฟกัส สำหรับ ImageView ใดๆ ที่ได้รับโฟกัส นี่เป็นสิ่งจำเป็น มิฉะนั้นอ่านหน้าจอจะอ่าน 'ไม่มีฉลาก' (unlabelled) ซึ่งไม่มีประโยชน์ต่อผู้ใช้
ถ้าเป็นปุ่ม จะอ่าน '<คำอธิบาย> ปุ่ม กดสองครั้งเพื่อเปิดใช้งาน' โดยค่าเริ่มต้น แต่สำหรับไอคอน ImageView ของเรา เราระบุการกระทำด้วยตนเอง เนื่องจากเราไม่มีค่าเริ่มต้นนี้
android:contentDescription="กดเพื่อสลับข้อมูลบุคคลเพิ่มเติม"
เรายังใช้ importantForAccessibility:no เพื่อปิดโฟกัสสำหรับ TextView '+' เนื่องจากข้อความใต้แบดจ์สองอันให้คำอธิบายอยู่แล้ว ดังนั้น '+' จึงทำให้สับสนมากกว่าช่วยเหลือถ้าอ่านออกเสียง
สำหรับทั้งสองอย่าง การทดสอบด้วยมือบนอุปกรณ์จริงที่เปิด TalkBack เป็นตัวบ่งชี้ที่ดีที่สุดว่าบริบทเข้าใจได้หรือไม่โดยไม่ต้องมองภาพ
แอนิเมชันขยาย
แอนิเมชันของเราจะทำงานเมื่อแตะไอคอน 'ข้อมูล' สลับการขยายส่วนรายละเอียด
เราจะทำทั้งหมดภายใน Activity เดียวเพื่อให้โฟกัสที่โค้ดแอนิเมชัน ในแอปจริง มุมมองที่นำไปใช้มักจะอยู่ใน Fragment หรือ Recycler View ของตัวเอง ดังนั้นโครงสร้างโค้ดที่นามธรรมมากกว่าจะถูกใช้
การตั้งค่า Listener
ภายใน onCreate ของ Activity ตัวอย่าง เราต้องตั้ง Listener บนไอคอนและส่งผ่านมุมมองที่จะสลับ
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 เพื่อเคลื่อนจากค่าเริ่มต้นไปยังค่าเป้าหมายสิ้นสุด และตั้งระยะเวลาเป็นมิลลิวินาที ระยะเวลานี้ขึ้นอยู่กับการทดสอบด้วยมือในภายหลังสำหรับความรู้สึก UX
ValueAnimator
.ofInt(startHeight, targetHeight)
.setDuration(200)
เราเชื่อมระยะเวลากับความสูงด้วย Update Listener ขอให้วาดเลย์เอาต์ใหม่หลังการอัปเดตแต่ละครั้ง และปรับความสูงทุกครั้ง
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 ได้ง่ายมาก
สุดท้ายเราเริ่มแอนิเมชัน! ตอนนี้เรามีส่วนเนื้อหาที่ขยายและหดตัวเมื่อแตะไอคอนแล้ว!
แอนิเมชันที่ลื่นไหลกว่า
แม้แอนิเมชันของเราจะทำงานได้แล้ว ขั้นตอนเพิ่มเติมที่ดีคือเพิ่ม Interpolator เพื่อให้การเคลื่อนไหวรู้สึก tự nhiênมากขึ้น
expandAnimator.interpolator = FastOutSlowInInterpolator()
เพิ่มการเข้าถึงของเรา
เราจะเพิ่มสองสิ่งสุดท้ายเพื่อช่วยผู้ใช้ a11y
แรก เราสามารถช่วยการนำทางด้วย AccessibilityEvent
expandAnimator.doOnEnd {
if (!isToggled) body.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
isToggled = !isToggled
}
นี่หมายความว่าเมื่อแอนิเมชันเคลื่อนจากปิดไปเปิด โฟกัสจะกระโดดทันทีไปที่รายการแรกในส่วนเนื้อหา ในที่นี้คือคำอธิบาย ในเลย์เอาต์เราตั้งคำอธิบายของการกระทำไอคอนข้อมูล แต่เนื่องจากเราอาจไม่พึ่งพาตัวบ่งชี้ภาพให้ผู้ใช้ไปยังรายการถัดไป เราจึงจัดการให้พวกเขา
สอง เราอนุญาตขนาดตัวอักษรที่แตกต่างกัน ความสูงที่วัดได้จาก measure() ไม่คำนึงถึงการปรับสเกลตัวอักษรในการตั้งค่าการเข้าถึงของอุปกรณ์ ดังนั้นเมื่ออยู่ในสเกลใหญ่ ส่วนท้ายของคำอธิบายจะถูกตัดเพราะใหญ่เกินกว่าจะพอดี
เราสามารถเข้าถึงการปรับสเกลตัวอักษรทางโปรแกรม และปรับสเกลความสูงตามนี้ เราแปลงเป็นจำนวนเต็ม เนื่องจากการปรับสเกลตัวอักษรอาจให้ค่า float ซึ่งไม่ทำงานเป็นความสูงเลย์เอาต์
val a11yFontScale = body.context.resources.configuration.fontScale
val maxHeight = ((body.measuredHeight + body.paddingTop + body.paddingBottom) * a11yFontScale).toInt()
เสร็จสิ้น!

และเราทำได้แล้ว! เราถึงแอนิเมชันสุดท้ายของเรา! ด้วยเพียงไม่กี่บรรทัดเพิ่มเติม เราเพิ่มความครอบคลุม a11y อย่างมาก และมีส่วนขยายที่ลื่นไหลแสดงสัญลักษณ์ Kotlin และ Android ?
ขอบคุณที่อ่าน ?
นี่คือบทความอื่นๆ ที่ผมเขียน gầnๆ นี้:
- ปรับแต่งการทดสอบ CodeceptJS E2E
- การทดสอบ React ด้วย Jest และ Enzyme II
ข้อมูลเพิ่มเติมที่มีประโยชน์
- บทความ androidx ที่ยอดเยี่ยมของ Joe Birch เกี่ยวกับการสำรวจ KTX สำหรับ Android
- บทช่วยสอนการเข้าถึง Android: เริ่มต้นใช้งาน