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

باستخدام أداة 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. ويتم استدعاء هذه الطريقة بعد انتهاء الرسوم المتحركة. هذا هو الوقت المناسب لإزالة أي تغييرات مؤقتة.

مصادر إضافية