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

تجربة طريقة Compose
‫Jetpack Compose هي مجموعة أدوات واجهة المستخدِم المقترَحة لنظام التشغيل Android. تعرَّف على كيفية استخدام لوحة المفاتيح في Compose.

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

مراجع إضافية