Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ระบบ >> Android

ทำให้แอนิเมชัน Kotlin Android เข้าถึงได้: คู่มือปฏิบัติจริง

เมื่อค้นหาตัวอย่างสำหรับการมีส่วนร่วม Android ครั้งแรก พบตัวอย่างน้อยมากสำหรับแอนิเมชันที่เขียนด้วย Kotlin นอกจากนี้ยังมีตัวอย่างโค้ดน้อยมากเกี่ยวกับการคำนึงถึงการเข้าถึงภายในแอนิเมชันแบบเนทีฟ

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

ทำให้แอนิเมชัน Kotlin Android เข้าถึงได้: คู่มือปฏิบัติจริง
GIF ของผลลัพธ์แอปสุดท้าย

การเข้าถึง 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()

เสร็จสิ้น!

ทำให้แอนิเมชัน Kotlin Android เข้าถึงได้: คู่มือปฏิบัติจริง
GIF ของผลลัพธ์แอปสุดท้าย

และเราทำได้แล้ว! เราถึงแอนิเมชันสุดท้ายของเรา! ด้วยเพียงไม่กี่บรรทัดเพิ่มเติม เราเพิ่มความครอบคลุม a11y อย่างมาก และมีส่วนขยายที่ลื่นไหลแสดงสัญลักษณ์ Kotlin และ Android ?

ขอบคุณที่อ่าน ?

นี่คือบทความอื่นๆ ที่ผมเขียน gầnๆ นี้:

  • ปรับแต่งการทดสอบ CodeceptJS E2E
  • การทดสอบ React ด้วย Jest และ Enzyme II

ข้อมูลเพิ่มเติมที่มีประโยชน์

  • บทความ androidx ที่ยอดเยี่ยมของ Joe Birch เกี่ยวกับการสำรวจ KTX สำหรับ Android
  • บทช่วยสอนการเข้าถึง Android: เริ่มต้นใช้งาน