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

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

مصادر إضافية