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

تجربة طريقة "إنشاء"
‫Jetpack Compose هي مجموعة أدوات واجهة المستخدم التي يُنصح باستخدامها على Android. تعرَّف على كيفية استخدام لوحة المفاتيح في ميزة "الكتابة".

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

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

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

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

الصورة المتحركة التلقائية للعناصر المتزامنة

بدءًا من المستوى 37.2 لواجهة برمجة التطبيقات والإصدارات الأحدث، يمكن للتطبيقات التي تستخدم تعديلات تلقائية على تخطيط النافذة (مثل SOFT_INPUT_ADJUST_RESIZE أو SOFT_INPUT_ADJUST_PAN عند عرض طريقة الإدخال باستخدام WindowInsetsController.show(WindowInsets.Type.ime())) بدون تنفيذ WindowInsetsAnimationCompat.Callback مخصّص الاستفادة من الرسوم المتحركة المتزامنة على مستوى النظام إطارًا بإطار. عند تفعيل هذه الميزة، يتم تلقائيًا تطبيق الهوامش الداخلية على كل إطار وتفعيل عملية تخطيط، ما يؤدي إلى إنشاء انتقال سلس أثناء تحرّك لوحة المفاتيح.

لتفعيل هذا السلوك، أضِف علامة <property> التالية إلى نشاطك أو تطبيقك في ملف AndroidManifest.xml:

<property
    android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
    android:value="true" />

تنفيذ الرسوم المتحركة الخاصة بالحواف الداخلية خارج سلسلة التعليمات البرمجية الرئيسية

بشكلٍ تلقائي، يؤدي تسجيل WindowInsetsAnimationCompat.Callback إلى فرض تشغيل الرسوم المتحركة الخاصة بإضافة مساحة داخل النافذة في سلسلة المحادثات الرئيسية للتطبيق. ويكون ذلك ضروريًا إذا كان تطبيقك يحدّث ويُزامن تخطيط العرض باستمرار أثناء الرسوم المتحركة (مثلما يحدث في الطريقة onProgress). ومع ذلك، إذا كان تطبيقك يحتاج فقط إلى تتبّع عمليات الانتقال بين مراحل النشاط (مثل بدء أو إنهاء تحريك أداة IME)، ولا يحتاج إلى تعديل طرق العرض إطارًا تلو الآخر، قد يؤدي التشغيل على سلسلة التعليمات الرئيسية إلى زيادة الحمل غير الضروري وتقطُّع الحركة، خاصةً إذا كانت سلسلة التعليمات الرئيسية مشغولة.

بدءًا من المستوى 37.2 لواجهة برمجة التطبيقات والإصدارات الأحدث، يمكنك استخدام ViewTreeObserver.WindowInsetsAnimationListener لمراقبة عمليات الانتقال هذه بدلاً من ذلك. وبما أنّ أداة معالجة الأحداث هذه لا تتلقّى تعديلات على مستوى كل إطار (ما مِن عملية ردّ onProgress)، يمكن للنظام تنفيذ الصور المتحركة الخاصة بالحواف على سلسلة تعليمات مخصّصة للصور المتحركة بدلاً من سلسلة التعليمات الرئيسية، ما يؤدي إلى انتقال أكثر سلاسة.

Kotlin

val listener = object : ViewTreeObserver.WindowInsetsAnimationListener {
  override fun onPrepare(animation: WindowInsetsAnimation) {
    // Handle preparation before animation starts
  }

  override fun onEnd(animation: WindowInsetsAnimation) {
    // Clean up temporary changes
  }
}

// Add the listener
view.viewTreeObserver.addWindowInsetsAnimationListener(listener)

// Remove the listener when no longer needed
view.viewTreeObserver.removeWindowInsetsAnimationListener(listener)

Java

ViewTreeObserver.WindowInsetsAnimationListener listener =
    new ViewTreeObserver.WindowInsetsAnimationListener() {
      @Override
      public void onPrepare(@NonNull WindowInsetsAnimation animation) {
        // Handle preparation before animation starts
      }

      @Override
      public void onEnd(@NonNull WindowInsetsAnimation animation) {
        // Clean up temporary changes
      }
    };

// Add the listener
view.getViewTreeObserver().addWindowInsetsAnimationListener(listener);

// Remove the listener when no longer needed
view.getViewTreeObserver().removeWindowInsetsAnimationListener(listener);

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

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

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

مراجع إضافية