Mengontrol dan menganimasikan keyboard virtual

Coba cara Compose
Jetpack Compose adalah toolkit UI yang direkomendasikan untuk Android. Pelajari cara menggunakan keyboard di Compose.

Dengan menggunakan WindowInsetsCompat, aplikasi Anda dapat membuat kueri dan mengontrol keyboard virtual (juga disebut IME) dengan cara yang mirip dengan cara berinteraksi dengan kolom sistem. Aplikasi Anda juga dapat menggunakan WindowInsetsAnimationCompat untuk membuat transisi yang lancar saat keyboard software dibuka atau ditutup.

Gambar 1. Dua contoh transisi buka-tutup keyboard software.

Prasyarat

Sebelum menyiapkan kontrol dan animasi untuk keyboard virtual, konfigurasi aplikasi Anda untuk menampilkan layar penuh. Hal ini memungkinkan menangani inset jendela sistem seperti panel sistem dan keyboard di layar.

Memeriksa visibilitas software keyboard

Gunakan WindowInsets untuk memeriksa visibilitas keyboard software.

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;

Atau, Anda dapat menggunakan ViewCompat.setOnApplyWindowInsetsListener untuk mengamati perubahan pada visibilitas keyboard software.

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;
});

Menyinkronkan animasi dengan keyboard virtual

Pengguna mengetuk kolom input teks sehingga keyboard meluncur ke tempatnya dari bagian bawah layar, seperti yang ditunjukkan dalam contoh berikut:

Gambar 2. Animasi keyboard yang disinkronkan.
  • Contoh berlabel "Tidak Tersinkronisasi" pada gambar 2 menunjukkan perilaku default di Android 10 (level API 29), di mana kolom teks dan konten aplikasi berpindah ke tempatnya, bukan disinkronkan dengan animasi keyboard—perilaku yang dapat mengganggu secara visual.

  • Di Android 11 (Level API 30) dan yang lebih tinggi, Anda dapat menggunakan WindowInsetsAnimationCompat untuk menyinkronkan transisi aplikasi dengan keyboard yang meluncur naik dan turun dari bagian bawah layar. Tampilannya lebih lancar, seperti yang ditunjukkan dalam contoh berlabel "Disinkronkan" pada gambar 2.

Konfigurasi WindowInsetsAnimationCompat.Callback dengan tampilan yang akan disinkronkan dengan animasi keyboard.

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.
    });

Ada beberapa metode yang perlu diganti di WindowInsetsAnimationCompat.Callback, yaitu onPrepare(), onStart(), onProgress(), dan onEnd(). Mulai dengan memanggil onPrepare() sebelum perubahan tata letak apa pun.

onPrepare dipanggil saat animasi inset dimulai dan sebelum tampilan ditata ulang karena animasi. Anda dapat menggunakannya untuk menyimpan status awal, yang dalam hal ini adalah koordinat bawah tampilan.

Gambar yang menampilkan koordinat bawah status awal tampilan root.
Gambar 3. Menggunakan onPrepare() untuk merekam status awal.

Cuplikan berikut menunjukkan contoh panggilan ke 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 dipanggil saat animasi inset dimulai. Anda dapat menggunakannya untuk menyetel semua properti tampilan ke status akhir perubahan tata letak. Jika Anda telah menetapkan callback OnApplyWindowInsetsListener ke salah satu tampilan, callback tersebut sudah dipanggil pada saat ini. Ini adalah saat yang tepat untuk menyimpan status akhir properti tampilan.

Gambar yang menampilkan koordinat bawah status akhir tampilan
Gambar 4. Menggunakan onStart() untuk merekam status akhir.

Cuplikan berikut menunjukkan contoh panggilan ke 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 dipanggil saat inset berubah sebagai bagian dari menjalankan animasi, sehingga Anda dapat menggantinya dan menerima notifikasi di setiap frame selama animasi keyboard. Perbarui properti tampilan sehingga tampilan dianimasikan secara sinkron dengan keyboard.

Semua perubahan tata letak telah selesai pada tahap ini. Misalnya, jika Anda menggunakan View.translationY untuk menggeser tampilan, nilai akan berkurang secara bertahap untuk setiap panggilan metode ini dan akhirnya mencapai 0 ke posisi tata letak asli.

Gambar 5. Menggunakan onProgress() untuk menyinkronkan animasi.

Cuplikan berikut menunjukkan contoh panggilan ke 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;
}

Secara opsional, Anda dapat mengganti onEnd. Metode ini dipanggil setelah animasi berakhir. Sekarang adalah saat yang tepat untuk menghapus perubahan sementara.

Referensi lainnya