ควบคุมและทำให้แป้นพิมพ์ของซอฟต์แวร์เคลื่อนไหว

เมื่อใช้ WindowInsetsCompat แอปจะค้นหาและควบคุมแป้นพิมพ์บนหน้าจอ (หรือที่เรียกว่า IME) ได้คล้ายกับวิธีที่โต้ตอบกับแถบระบบ นอกจากนี้ แอปยังใช้ WindowInsetsAnimationCompat เพื่อสร้างการเปลี่ยนที่ราบรื่นเมื่อเปิดหรือปิดแป้นพิมพ์ซอฟต์แวร์ได้ด้วย

รูปที่ 1 ตัวอย่าง 2 รายการของการเปลี่ยนจากเปิดเป็นปิดของแป้นพิมพ์ซอฟต์แวร์

สิ่งที่ต้องมีก่อน

ก่อนตั้งค่าการควบคุมและภาพเคลื่อนไหวสำหรับแป้นพิมพ์ซอฟต์แวร์ ให้กำหนดค่าแอปให้แสดงแบบเต็มหน้าจอ ซึ่งจะช่วยให้จัดการส่วนแทรกของหน้าต่างระบบได้ เช่น แถบระบบและแป้นพิมพ์บนหน้าจอ

ตรวจสอบการแสดงซอฟต์แวร์แป้นพิมพ์

ใช้ 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 ภาพเคลื่อนไหวของแป้นพิมพ์ที่ซิงค์กัน
  • ตัวอย่างที่มีป้ายกำกับว่า "ไม่ได้ซิงค์" ในรูปที่ 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 จะเรียกใช้เมื่อภาพเคลื่อนไหวของส่วนแทรกเริ่มขึ้นและก่อนที่มุมมองจะจัดวางใหม่เนื่องจากภาพเคลื่อนไหว คุณสามารถใช้เพื่อบันทึกสถานะเริ่มต้น ซึ่งในกรณีนี้คือพิกัดด้านล่างของมุมมอง

รูปภาพแสดงพิกัดด้านล่างของสถานะเริ่มต้นของมุมมองรูท
รูปที่ 3 ใช้ 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 ให้กับมุมมองใดก็ตาม ระบบจะเรียกใช้การเรียกกลับแล้ว ณ จุดนี้ ตอนนี้เป็นโอกาสที่ดีในการบันทึกสถานะสุดท้ายของพร็อพเพอร์ตี้มุมมอง

รูปภาพแสดงพิกัดด้านล่างของสถานะสุดท้ายของมุมมอง
รูปที่ 4 ใช้ 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 ซึ่งเป็นตําแหน่งเลย์เอาต์เดิม

รูปที่ 5 ใช้ 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 หรือไม่ก็ได้ ระบบจะเรียกใช้เมธอดนี้หลังจากภาพเคลื่อนไหวสิ้นสุดลง นี่เป็นโอกาสที่ดีในการล้างการเปลี่ยนแปลงชั่วคราว

แหล่งข้อมูลเพิ่มเติม