การใช้ WindowInsetsCompat
แอปจะค้นหาและควบคุมแป้นพิมพ์บนหน้าจอ (หรือที่เรียกว่า
IME) ได้ในลักษณะเดียวกับ
วิธีที่โต้ตอบกับแถบระบบ แอปยังใช้
WindowInsetsAnimationCompat
เพื่อสร้างการเปลี่ยนผ่านที่ราบรื่นเมื่อเปิดหรือปิดแป้นพิมพ์ซอฟต์แวร์ได้ด้วย
สิ่งที่ต้องมีก่อน
ก่อนตั้งค่าการควบคุมและภาพเคลื่อนไหวสำหรับแป้นพิมพ์ซอฟต์แวร์ ให้กำหนดค่าแอปเพื่อแสดงแบบขอบจรดขอบ ซึ่งช่วยให้จัดการระยะขอบหน้าต่างของระบบ เช่น แถบระบบและแป้นพิมพ์บนหน้าจอได้
ภาพเคลื่อนไหวการแทรกที่ซิงค์เริ่มต้น
ตั้งแต่ API ระดับ 37.2 ขึ้นไป แอปพลิเคชันที่ใช้การปรับเลย์เอาต์หน้าต่างเริ่มต้น (เช่น SOFT_INPUT_ADJUST_RESIZE หรือ SOFT_INPUT_ADJUST_PAN เมื่อแสดง IME โดยใช้ WindowInsetsController.show(WindowInsets.Type.ime()))
โดยไม่ได้ใช้ WindowInsetsAnimationCompat.Callback ที่กำหนดเองจะได้รับประโยชน์
จากภาพเคลื่อนไหวแบบเฟรมต่อเฟรมที่ซิงค์ระดับระบบ เมื่อเปิดใช้ ฟีเจอร์นี้จะใช้ระยะขอบด้านในกับทุกเฟรมโดยอัตโนมัติและทริกเกอร์การส่งผ่านเลย์เอาต์ ซึ่งจะสร้างการเปลี่ยนผ่านที่ราบรื่นเมื่อแป้นพิมพ์เคลื่อนที่
หากต้องการเปิดใช้ลักษณะการทำงานนี้ ให้เพิ่มแท็ก <property> ต่อไปนี้ลงในกิจกรรมหรือ
แอปพลิเคชันในไฟล์ AndroidManifest.xml
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
android:value="true" />
เรียกใช้ภาพเคลื่อนไหวของ Insets นอกเทรดหลัก
โดยค่าเริ่มต้น การลงทะเบียน
WindowInsetsAnimationCompat.Callback จะบังคับให้
ภาพเคลื่อนไหวของระยะขอบด้านในของหน้าต่างทำงานในเทรดหลักของแอป ซึ่งจำเป็นหากแอปของคุณ
อัปเดตและซิงโครไนซ์เลย์เอาต์มุมมองอย่างต่อเนื่อง
ระหว่างภาพเคลื่อนไหว (เช่น ในเมธอด onProgress) อย่างไรก็ตาม หากแอปของคุณ
ต้องการเพียงแค่ฟังการเปลี่ยนสถานะวงจร (เช่น เมื่อ IME เริ่มหรือ
สิ้นสุดการเคลื่อนไหว) และไม่จำเป็นต้องอัปเดตมุมมองทีละเฟรม การทำงานใน
เทรดหลักอาจทำให้เกิดค่าใช้จ่ายที่ไม่จำเป็นและการกระตุกของภาพเคลื่อนไหว โดยเฉพาะอย่างยิ่งหาก
เทรดหลักทำงานอื่นอยู่
ตั้งแต่ API ระดับ 37.2 ขึ้นไป คุณสามารถใช้
ViewTreeObserver.WindowInsetsAnimationListener
เพื่อสังเกตการเปลี่ยนผ่านเหล่านี้แทนได้ เนื่องจาก Listener นี้ไม่ได้รับการอัปเดตความคืบหน้าแบบทีละเฟรม (ไม่มีการเรียกกลับ onProgress) ระบบจึงเรียกใช้ภาพเคลื่อนไหวขอบในในเทรดภาพเคลื่อนไหวเฉพาะแทนที่จะเป็นเทรดหลักได้ ซึ่งจะทำให้การเปลี่ยนผ่านราบรื่นยิ่งขึ้น
Kotlin
val listener = object : ViewTreeObserver.WindowInsetsAnimationListener { override fun onPrepare(animation: WindowInsetsAnimation) { // Handle preparation before animation starts } override fun onEnd(animation: WindowInsetsAnimation) { // Clean up temporary changes } } // Add the listener view.viewTreeObserver.addWindowInsetsAnimationListener(listener) // Remove the listener when no longer needed view.viewTreeObserver.removeWindowInsetsAnimationListener(listener)
Java
ViewTreeObserver.WindowInsetsAnimationListener listener = new ViewTreeObserver.WindowInsetsAnimationListener() { @Override public void onPrepare(@NonNull WindowInsetsAnimation animation) { // Handle preparation before animation starts } @Override public void onEnd(@NonNull WindowInsetsAnimation animation) { // Clean up temporary changes } }; // Add the listener view.getViewTreeObserver().addWindowInsetsAnimationListener(listener); // Remove the listener when no longer needed view.getViewTreeObserver().removeWindowInsetsAnimationListener(listener);
ตรวจสอบระดับการมองเห็นซอฟต์แวร์แป้นพิมพ์
ใช้ WindowInsets เพื่อตรวจสอบระดับการมองเห็นของคีย์บอร์ด
ซอฟต์แวร์
Kotlin
val insets = ViewCompat.getRootWindowInsets(view) ?: return val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
Java
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
หรือคุณจะใช้
ViewCompat.setOnApplyWindowInsetsListener
เพื่อสังเกตการเปลี่ยนแปลงระดับการมองเห็นของแป้นพิมพ์ซอฟต์แวร์ก็ได้
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom insets }
Java
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; return insets; });
ซิงค์ภาพเคลื่อนไหวกับแป้นพิมพ์ซอฟต์แวร์
เมื่อผู้ใช้แตะช่องป้อนข้อความ แป้นพิมพ์จะเลื่อนจากด้านล่างของหน้าจอมายังตำแหน่งที่ต้องการ ดังที่แสดงในตัวอย่างต่อไปนี้
ตัวอย่างที่ติดป้ายกำกับว่า "ไม่ได้ซิงค์" ในรูปที่ 2 แสดงลักษณะการทำงานเริ่มต้น ใน Android 10 (API ระดับ 29) ซึ่งช่องข้อความและเนื้อหาของแอป จะเลื่อนเข้าที่แทนที่จะซิงค์กับภาพเคลื่อนไหวของแป้นพิมพ์ ซึ่งเป็นลักษณะการทำงานที่อาจทำให้เกิดความไม่สอดคล้องกันทางภาพ
ใน Android 11 (API ระดับ 30) ขึ้นไป คุณสามารถใช้
WindowInsetsAnimationCompatเพื่อซิงค์การเปลี่ยนผ่านของแอปกับ การเลื่อนแป้นพิมพ์ขึ้นและลงจากด้านล่างของหน้าจอ ซึ่งจะดู ราบรื่นขึ้น ดังตัวอย่างที่มีป้ายกำกับว่า "ซิงค์แล้ว" ในรูปที่ 2
กำหนดค่า
WindowInsetsAnimationCompat.Callback
โดยให้มุมมองซิงค์กับภาพเคลื่อนไหวของแป้นพิมพ์
Kotlin
ViewCompat.setWindowInsetsAnimationCallback( view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { // Override methods. } )
Java
ViewCompat.setWindowInsetsAnimationCallback( view, new WindowInsetsAnimationCompat.Callback( WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP ) { // Override methods. });
มีหลายวิธีในการลบล้างใน WindowInsetsAnimationCompat.Callback
กล่าวคือ
onPrepare()
onStart()
onProgress()
และ
onEnd()
เริ่มด้วยการเรียกใช้ onPrepare() ก่อนที่จะมีการเปลี่ยนแปลงเลย์เอาต์
onPrepare จะเรียกใช้เมื่อภาพเคลื่อนไหวของ Insets เริ่มต้นและก่อนที่จะมีการจัดวางมุมมองใหม่เนื่องจากภาพเคลื่อนไหว คุณใช้เพื่อบันทึกสถานะเริ่มต้นได้
ซึ่งในกรณีนี้คือพิกัดล่างของวิว
onPrepare() เพื่อ
บันทึกสถานะเริ่มต้น
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการเรียกใช้ onPrepare
Kotlin
var startBottom = 0f override fun onPrepare( animation: WindowInsetsAnimationCompat ) { startBottom = view.bottom.toFloat() }
Java
float startBottom; @Override public void onPrepare( @NonNull WindowInsetsAnimationCompat animation ) { startBottom = view.getBottom(); }
onStart จะเรียกใช้เมื่อภาพเคลื่อนไหวของระยะขอบเริ่ม คุณใช้เพื่อตั้งค่าพร็อพเพอร์ตี้มุมมองทั้งหมดเป็นสถานะสุดท้ายของการเปลี่ยนแปลงเลย์เอาต์ได้ หากคุณตั้งค่าการเรียกกลับ
OnApplyWindowInsetsListenerไว้ในมุมมองใดก็ตาม ระบบจะเรียกใช้
ที่จุดนี้ ตอนนี้เป็นเวลาที่เหมาะในการบันทึกสถานะสุดท้ายของพร็อพเพอร์ตี้มุมมอง
onStart() เพื่อบันทึก
สถานะสุดท้าย
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการเรียกใช้ onStart
Kotlin
var endBottom = 0f override fun onStart( animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat ): WindowInsetsAnimationCompat.BoundsCompat { // Record the position of the view after the IME transition. endBottom = view.bottom.toFloat() return bounds }
Java
float endBottom; @NonNull @Override public WindowInsetsAnimationCompat.BoundsCompat onStart( @NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds ) { endBottom = view.getBottom(); return bounds; }
onProgress จะเรียกใช้เมื่อขอบเปลี่ยนเป็นส่วนหนึ่งของการเรียกใช้ภาพเคลื่อนไหว
เพื่อให้คุณลบล้างและรับการแจ้งเตือนในทุกเฟรมระหว่างภาพเคลื่อนไหวของคีย์บอร์ดได้ อัปเดตพร็อพเพอร์ตี้ของมุมมองเพื่อให้มุมมองเคลื่อนไหว
พร้อมกับแป้นพิมพ์
ตอนนี้การเปลี่ยนแปลงเลย์เอาต์ทั้งหมดเสร็จสมบูรณ์แล้ว เช่น หากคุณใช้
View.translationY เพื่อเปลี่ยนมุมมอง ค่าจะค่อยๆ ลดลงทุกครั้งที่มีการเรียกใช้เมธอดนี้ และในที่สุดจะไปถึง 0 ที่ตำแหน่งเลย์เอาต์เดิม
onProgress() เพื่อ
ซิงค์ภาพเคลื่อนไหว
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการเรียกใช้ onProgress
Kotlin
override fun onProgress( insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat> ): WindowInsetsCompat { // Find an IME animation. val imeAnimation = runningAnimations.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 } ?: return insets // Offset the view based on the interpolated fraction of the IME animation. view.translationY = (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction) return insets }
Java
@NonNull @Override public WindowInsetsCompat onProgress( @NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations ) { // Find an IME animation. WindowInsetsAnimationCompat imeAnimation = null; for (WindowInsetsAnimationCompat animation : runningAnimations) { if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { imeAnimation = animation; break; } } if (imeAnimation != null) { // Offset the view based on the interpolated fraction of the IME animation. view.setTranslationY((startBottom - endBottom) * (1 - imeAnimation.getInterpolatedFraction())); } return insets; }
คุณเลือกที่จะลบล้าง onEnd ได้ ระบบจะเรียกใช้เมธอดนี้หลังจากภาพเคลื่อนไหว
สิ้นสุดลง ตอนนี้เป็นเวลาที่เหมาะในการล้างการเปลี่ยนแปลงชั่วคราว
แหล่งข้อมูลเพิ่มเติม
- WindowInsetsAnimation ใน GitHub