کنترل و پویانمایی صفحه‌کلید نرم‌افزاری

امتحان کردن روش Compose
Jetpack Compose جعبه‌ابزار واسط کاربر توصیه‌شده برای Android است. با نحوه کار با صفحه‌کلید در Compose آشنا شوید.

بااستفاده از WindowInsetsCompat، برنامه شما می‌تواند صفحه‌کلید مجازی (که به آن IME نیز گفته می‌شود) را مشابه نحوه تعامل آن با نوارهای سیستم، پرسمان و کنترل کند. برنامه شما همچنین می‌تواند از WindowInsetsAnimationCompat استفاده کند تا هنگام باز یا بسته شدن صفحه‌کلید نرم‌افزاری، انتقال‌های یکپارچه ایجاد کند.

شکل ۱. دو نمونه از انتقال باز-بسته صفحه‌کلید نرم‌افزاری.

پیش‌نیازها

قبل‌از راه‌اندازی کنترل و پویانمایی برای صفحه‌کلید نرم‌افزاری، برنامه‌تان را برای نمایش لبه‌به‌لبه پیکربندی کنید. این کار به آن اجازه می‌دهد درون‌نشست‌های پنجره سیستم مانند نوار سیستم و صفحه‌کلید مجازی را مدیریت کند.

پویانمایی پیش‌فرض درج‌های همگام‌سازی‌شده

از سطح میانای برنامه‌سازی کاربردی ۳۷.۲ و بالاتر، برنامه‌هایی که از تنظیمات پیش‌فرض چیدمان پنجره (مانند SOFT_INPUT_ADJUST_RESIZE یا SOFT_INPUT_ADJUST_PAN هنگام نمایش IME بااستفاده از 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 شروع یا پایان پویانمایی می‌کند) و نیازی به به‌روزرسانی نماها فریم‌به‌فریم ندارد، اجرا در رشته اصلی می‌تواند باعث سربار غیرضروری و لکنت پویانمایی شود—به‌ویژه اگر رشته اصلی در غیر این صورت مشغول باشد.

از سطح میانای برنامه‌سازی کاربردی ۳۷.۲ و بالاتر، می‌توانید از ViewTreeObserver.WindowInsetsAnimationListener برای مشاهده این انتقال‌ها استفاده کنید. چون این شنونده به‌روزرسانی‌های پیشرفت فریم‌به‌فریم را دریافت نمی‌کند (بدون callback onProgress)، سیستم می‌تواند پویانمایی درج را به‌جای رشته اصلی در یک رشته پویانمایی اختصاصی اجرا کند که منجر به انتقال روان‌تر می‌شود.

کاتلین

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)

جاوا

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 برای بررسی نمایان بودن صفحه‌کلید نرم‌افزاری استفاده کنید.

کاتلین

val insets = ViewCompat.getRootWindowInsets(view) ?: return
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom

جاوا

WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;

یا می‌توانید از ViewCompat.setOnApplyWindowInsetsListener برای مشاهده تغییرات در رؤیت‌پذیری صفحه‌کلید نرم‌افزاری استفاده کنید.

کاتلین

ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
  val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
  val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
  insets
}

جاوا

ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
  boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
  int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
  return insets;
});

همگام‌سازی پویانمایی با صفحه‌کلید نرم‌افزاری

وقتی کاربر روی فیلد ورودی نوشتار تک‌ضرب می‌زند، صفحه‌کلید از پایین صفحه به جای خود می‌لغزد، همان‌طور که در مثال زیر نشان داده شده است:

شکل ۲. پویانمایی صفحه‌کلید همگام‌سازی‌شده.
  • نمونه‌ای که در شکل ۲ با برچسب «Unsynchronized» (همگام‌سازی‌نشده) مشخص شده است، رفتار پیش‌فرض در Android 10 (سطح API‏ ۲۹) را نشان می‌دهد که در آن فیلد نوشتاری و محتوای برنامه به‌جای همگام‌سازی با پویانمایی صفحه‌کلید، به‌سرعت در جای خود قرار می‌گیرند—رفتاری که می‌تواند ازنظر بصری ناخوشایند باشد.

  • در Android نسخه ۱۱ (سطح میانای برنامه‌سازی کاربردی ۳۰) و بالاتر، می‌توانید از WindowInsetsAnimationCompat برای همگام‌سازی انتقال برنامه با لغزاندن صفحه‌کلید به بالا و پایین از پایین صفحه استفاده کنید. این حالت همان‌طور که در نمونه با برچسب «همگام‌سازی‌شده» در شکل ۲ نشان داده شده است، روان‌تر به‌نظر می‌رسد.

WindowInsetsAnimationCompat.Callback را با نمایی که باید با پویانمایی صفحه‌کلید همگام‌سازی شود پیکربندی کنید.

کاتلین

ViewCompat.setWindowInsetsAnimationCallback(
  view,
  object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
    // Override methods.
  }
)

جاوا

ViewCompat.setWindowInsetsAnimationCallback(
    view,
    new WindowInsetsAnimationCompat.Callback(
        WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP
    ) {
      // Override methods.
    });

چندین روش برای لغو کردن در WindowInsetsAnimationCompat.Callback وجود دارد، ازجمله onPrepare()، onStart()، onProgress()، و onEnd(). قبل‌از هرگونه تغییر در طرح‌بندی، با فراخوانی onPrepare() شروع کنید.

onPrepare زمانی فراخوانی می‌شود که پویانمایی حاشیه‌ها درحال شروع شدن است و قبل‌از اینکه نماها به‌دلیل پویانمایی دوباره چیده شوند. می‌توانید از آن برای ذخیره کردن حالت شروع استفاده کنید، که در این مورد، مختصات پایین نما است.

تصویری که مختصات پایین حالت شروع نمای ریشه را نشان می‌دهد.
شکل ۳. استفاده از onPrepare() برای ضبط وضعیت شروع.

تکه کد زیر نمونه‌ای از فراخوانی onPrepare را نشان می‌دهد:

کاتلین

var startBottom = 0f

override fun onPrepare(
  animation: WindowInsetsAnimationCompat
) {
  startBottom = view.bottom.toFloat()
}

جاوا

float startBottom;

@Override
public void onPrepare(
    @NonNull WindowInsetsAnimationCompat animation
) {
  startBottom = view.getBottom();
}

وقتی پویانمایی درج شروع می‌شود، onStart فراخوانده می‌شود. می‌توانید از آن برای تنظیم همه ویژگی‌های نما در وضعیت نهایی تغییرات چیدمان استفاده کنید. اگر OnApplyWindowInsetsListener فراخوانی برگشتی برای هرکدام از نماها تنظیم شده باشد، درحال‌حاضر در این نقطه فراخوانی شده است. این زمان مناسبی برای ذخیره کردن وضعیت پایانی ویژگی‌های نما است.

تصویری که مختصات پایین حالت پایانی نما را نشان می‌دهد
شکل ۴. استفاده از onStart() برای ضبط وضعیت نهایی.

تکه‌کد زیر نمونه‌ای از فراخوانی onStart را نشان می‌دهد:

کاتلین

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
}

جاوا

float endBottom;

@NonNull
@Override
public WindowInsetsAnimationCompat.BoundsCompat onStart(
    @NonNull WindowInsetsAnimationCompat animation,
    @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds
) {
  endBottom = view.getBottom();
  return bounds;
}

onProgress زمانی فراخوانی می‌شود که تورفتگی‌ها به‌عنوان بخشی از اجرای پویانمایی تغییر کنند، بنابراین می‌توانید آن را لغو کنید و در هر فریم درطول پویانمایی صفحه‌کلید مطلع شوید. ویژگی‌های نما را به‌روز کنید تا نما هم‌زمان با صفحه‌کلید پویانمایی شود.

همه تغییرات چیدمان در این مرحله کامل شده است. برای مثال، اگر از View.translationY برای تغییر نما استفاده کنید، مقدار به‌تدریج برای هر فراخوانی این روش کاهش می‌یابد و درنهایت به 0 موقعیت چیدمان اصلی می‌رسد.

شکل ۵. استفاده از onProgress() برای همگام‌سازی پویانمایی‌ها.

تکه‌کد زیر نمونه‌ای از فراخوانی onProgress را نشان می‌دهد:

کاتلین

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
}

جاوا

@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 را لغو کنید. این روش پس‌از پایان پویانمایی فراخوانی می‌شود. اکنون زمان خوبی برای پاک کردن تغییرات موقت است.

منابع بیشتر