التحكّم في لوحة مفاتيح البرنامج وتحريكها

باستخدام WindowInsetsCompat، يمكن لتطبيقك الاستعلام عن لوحة المفاتيح على الشاشة (المعروفة أيضًا باسم IME) والتحكّم فيها على نحو مشابه لطريقة تفاعله مع أشرطة النظام. يمكن لتطبيقك أيضًا استخدام رمز WindowInsetsAnimationCompat لإنشاء انتقالات سلسة عند فتح لوحة المفاتيح أو إغلاقها.

الشكل 1. مثالان على لوحة المفاتيح البرمجية انتقال من الفتح إلى الإغلاق

المتطلّبات الأساسية

قبل ضبط عناصر التحكّم والرسوم المتحركة للوحة المفاتيح، اضبط تطبيقك على العرض من الحافة إلى الحافة. يتيح ذلك للتطبيق التعامل مع أجزاء نافذة النظام المضمّنة، مثل أشرطة النظام ولوحة المفاتيح على الشاشة.

التحقّق من مستوى ظهور برنامج لوحة المفاتيح

استخدِم WindowInsets للتحقّق من مستوى ظهور شاشة keyboard.

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 (المستوى 29 من واجهة برمجة التطبيقات)، حيث يتم فجأة التمرير إلى حقل النص ومحتوى التطبيق بدلاً من مزامنته مع التمويه المتحرك للوحة المفاتيح، وهو سلوك قد يكون مزعجًا من الناحية المرئية.

  • في الإصدار 11 من نظام Android (المستوى 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. يتمّ استدعاء هذه الطريقة بعد انتهاء الصورة المتحركة. هذا هو الوقت المناسب لإزالة أي تغييرات مؤقتة.

مصادر إضافية