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

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

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

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

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

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

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

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

مراجع إضافية