באמצעות WindowInsetsCompat, האפליקציה יכולה לשלוח שאילתות למקלדת הווירטואלית (שנקראת גם IME) ולשלוט בה, בדומה לאופן שבו היא מתקשרת עם סרגלי המידע. האפליקציה יכולה גם להשתמש ב-WindowInsetsAnimationCompat כדי ליצור מעברים חלקים כשמקלדת וירטואלית נפתחת או נסגרת.
דרישות מוקדמות
לפני שמגדירים את השליטה ואת האנימציה של המקלדת הווירטואלית, צריך להגדיר את האפליקציה כך שתוצג מקצה לקצה. כך האפליקציה יכולה לטפל בשוליים פנימיים של חלון המערכת, כמו סרגלי המערכת והמקלדת הווירטואלית.
אנימציית ברירת מחדל של שוליים פנימיים מסונכרנים
החל מרמת API 37.2 ומעלה, אפליקציות שמשתמשות בהתאמות פריסת החלון שמוגדרות כברירת מחדל (כמו 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" />
ביצוע אנימציות של תמונות ממוזערות מחוץ ל-thread הראשי
כברירת מחדל, כשרושמים WindowInsetsAnimationCompat.Callback, המערכת מוודאת שאנימציית החלון הפנימי תפעל בשרשור הראשי של האפליקציה. הפעולה הזו נדרשת אם האפליקציה מתעדכנת ומסנכרנת באופן רציף את פריסת התצוגה שלה במהלך האנימציה (כמו בשיטה onProgress). עם זאת, אם האפליקציה צריכה רק להאזין למעברים במחזור החיים (למשל, כשה-IME מתחיל או מסיים את האנימציה) ולא צריכה לעדכן את התצוגות פריים אחר פריים, הפעלה בשרשור הראשי עלולה לגרום לתקורה מיותרת ולגמגום באנימציה – במיוחד אם השרשור הראשי עמוס בפעולות אחרות.
מרמת API 37.2 ואילך, אפשר להשתמש ב-ViewTreeObserver.WindowInsetsAnimationListener כדי לעקוב אחרי המעברים האלה. מכיוון שהמאזין הזה לא מקבל עדכוני התקדמות מפריים לפריים (אין קריאה חוזרת של onProgress), המערכת יכולה להריץ את אנימציית ההזחה ב-thread ייעודי לאנימציה ולא ב-thread הראשי, וכך המעבר יהיה חלק יותר.
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 מוצגת התנהגות ברירת המחדל ב-Android 10 (רמת API 29), שבה שדה הטקסט והתוכן של האפליקציה קופצים למקומם במקום להסתנכרן עם האנימציה של המקלדת – התנהגות שעלולה להיות לא נעימה לצפייה.
ב-Android 11 (API ברמה 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 נקראת כשאנימציה של שוליים פנימיים מתחילה ולפני שהתצוגות מסודרות מחדש בגלל אנימציה. אפשר להשתמש בה כדי לשמור את מצב ההתחלה, שבמקרה הזה הוא הקואורדינטה התחתונה של התצוגה.
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 נקראת כשאנימציית שוליים מתחילה. אפשר להשתמש בו כדי להגדיר את כל מאפייני התצוגה למצב הסופי של שינויי הפריסה. אם הגדרתם קריאה חוזרת (callback) של OnApplyWindowInsetsListener לאחת מהתצוגות, היא כבר נקראת בשלב הזה. זה הזמן לשמור את מצב הסיום של מאפייני התצוגה.
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 למיקום הפריסה המקורי.
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. השיטה הזו מופעלת אחרי שהאנימציה מסתיימת. זה הזמן המתאים למחוק שינויים זמניים.
מקורות מידע נוספים
- WindowInsetsAnimation ב-GitHub.