החל מגרסה 8.0 של Android (רמת API 26), אפשר להפעיל פעילויות ב-Android במצב 'תמונה בתוך תמונה' (PiP). התכונה 'תמונה בתוך תמונה' (PiP) היא סוג מיוחד של מצב 'חלונות מרובים', שמשמשים בעיקר להפעלת סרטונים. הוא מאפשר למשתמש לצפות בסרטון בחלון קטן שמוצמד לפינה של המסך, בזמן שהוא עובר בין אפליקציות או גולש בתוכן במסך הראשי.
התכונה 'תמונה בתוך תמונה' משתמשת בממשקי ה-API עם ריבוי חלונות שזמינים ב-Android 7.0 כדי לספק את חלון שכבת-העל של הווידאו המוצמד. כדי להוסיף את התכונה 'תמונה בתוך תמונה' לאפליקציה, צריך לרשום את הפעילויות שתומכות בתכונה הזו, להעביר את הפעילות למצב 'תמונה בתוך תמונה' לפי הצורך ולוודא שרכיבי ממשק המשתמש מוסתרים ושהפעלת הסרטון ממשיכה כשהפעילות במצב 'תמונה בתוך תמונה'.
חלון ה-PiP מופיע בשכבה העליונה של המסך, בפינה שנבחרה על ידי המערכת.
התכונה PiP נתמכת גם במכשירי Android TV OS תואמים עם Android 14 (רמת API 34) ואילך. יש הרבה דמיון בין התכונות, אבל יש כמה שיקולים נוספים כשמשתמשים בצפייה בחלון צף בטלוויזיה.
איך המשתמשים יכולים לבצע פעולות בחלון PiP
המשתמשים יכולים לגרור את חלון ה-PIP למיקום אחר. החל מ-Android 12, המשתמשים יכולים גם:
מקישים הקשה אחת על החלון כדי להציג מתג להצגה במסך מלא, לחצן סגירה, לחצן הגדרות ופעולות בהתאמה אישית שמספקת האפליקציה (לדוגמה, לחצני הפעלה).
מקישים הקשה כפולה על החלון כדי לעבור בין הגודל הנוכחי של התמונה בתוך תמונה לגודל המקסימלי או המינימלי שלה. לדוגמה, הקשה כפולה על חלון שמוגדרת לו תצוגה מוגדלת תקטין אותו, ולהפך.
כדי להסתיר את החלון, גוררים אותו לקצה השמאלי או הימני. כדי להוציא את החלון מהאחסון, מקישים על החלק הגלוי של החלון באחסון או גוררים אותו החוצה.
אפשר לשנות את הגודל של חלון 'תמונה בתוך תמונה' באמצעות תנועת צביטה לשינוי מרחק התצוגה.
האפליקציה קובעת מתי הפעילות הנוכחית תעבור למצב PiP. ריכזנו כאן כמה דוגמאות:
פעילות יכולה לעבור למצב PiP כשהמשתמש מקייש על הלחצן הראשי או מחליק למעלה אל דף הבית. כך מפות Google ממשיכות להציג מסלולים בזמן שהמשתמש מבצע פעילות אחרת בו-זמנית.
האפליקציה יכולה להעביר סרטון למצב 'תמונה בתוך תמונה' כשהמשתמש חוזר מהסרטון כדי לעיין בתוכן אחר.
האפליקציה יכולה להעביר סרטון למצב PiP בזמן שהמשתמש צופה בסוף פרק של תוכן. במסך הראשי מוצגים פרטי קידום מכירות או סיכום של הפרק הבא בסדרה.
האפליקציה שלכם יכולה לספק למשתמשים דרך להוסיף תוכן לתור בזמן שהם צופים בסרטון. הסרטון ממשיך לפעול במצב PiP בזמן שבמסך הראשי מוצגת פעילות לבחירת תוכן.
הצהרה על תמיכה ב-PiP
כברירת מחדל, המערכת לא תומכת באופן אוטומטי ב'תמונה בתוך תמונה' באפליקציות. כדי לתמוך ב-PiP באפליקציה, צריך לרשום את פעילות הסרטונים במניפסט על ידי הגדרת android:supportsPictureInPicture
לערך true
. בנוסף, צריך לציין שהפעילות מטפלת בשינויים בהגדרות של הפריסה, כדי שהפעילות לא תופעל מחדש כשיש שינויים בפריסה במהלך מעברים למצב PiP.
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
מעבר לפעילות במצב 'תמונה בתוך תמונה'
החל מ-Android 12, אפשר להעביר את הפעילות למצב 'תמונה בתוך תמונה' על ידי הגדרת הדגל setAutoEnterEnabled
ל-true
. כשמשתמשים בהגדרה הזו, הפעילות עוברת באופן אוטומטי למצב PiP לפי הצורך, בלי צורך לקרוא באופן מפורש ל-enterPictureInPictureMode()
ב-onUserLeaveHint
. בנוסף, כך אפשר ליהנות ממעברים חלקים יותר. מידע נוסף מופיע במאמר מעבר למצב 'תמונה בתוך תמונה' בצורה חלקה יותר בניווט באמצעות תנועות.
אם אתם מטרגטים את Android מגרסה 11 ומטה, הפעילות צריכה להפעיל את enterPictureInPictureMode()
כדי לעבור למצב PiP. לדוגמה, הקוד הבא מעביר פעילות למצב PiP כשהמשתמש לוחץ על לחצן ייעודי בממשק המשתמש של האפליקציה:
Kotlin
override fun onActionClicked(action: Action) { if (action.id.toInt() == R.id.lb_control_picture_in_picture) { activity?.enterPictureInPictureMode() return } }
Java
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPictureMode(); return; } ... }
כדאי לכלול לוגיקה שמעבירה פעילות למצב PiP במקום להעביר אותה לרקע. לדוגמה, מפות Google עובר למצב 'תמונה בתוך תמונה' אם
המשתמש לוחץ על הלחצן הראשי או על הלחצן האחרון בזמן שהאפליקציה ניווט. אפשר לתפוס את המקרה הזה על ידי שינוי ברירת המחדל של onUserLeaveHint()
:
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
מומלץ: לספק למשתמשים חוויית מעבר מטופחת ב'תמונה בתוך תמונה'
ב-Android 12 נוספו שיפורים משמעותיים במראה של המעברים האנימציה בין מסך מלא לחלונות PiP. אנחנו ממליצים מאוד להטמיע את כל השינויים הרלוונטיים. אחרי שתעשו זאת, השינויים האלה יותאמו באופן אוטומטי למסכים גדולים כמו מכשירי גלילה וטאבלטים, בלי צורך לבצע פעולות נוספות.
אם האפליקציה לא כוללת את העדכונים הרלוונטיים, המעברים ב-PIP עדיין יפעלו, אבל האנימציות יהיו פחות חלקות. לדוגמה, מעבר ממצב מסך מלא למצב PiP עלול לגרום לחלון PiP להיעלם במהלך המעבר, לפני שהוא יופיע שוב כשהמעבר יושלם.
השינויים האלה כוללים את הדברים הבאים.
- שיפור מעברים חלקים יותר למצב PIP דרך ניווט באמצעות תנועות
- הגדרת
sourceRectHint
מתאים לכניסה למצב 'תמונה בתוך תמונה' וליציאה ממנו - השבתת שינוי גודל חלק של תוכן שאינו וידאו
כדאי לעיין בדוגמה ל-Android Kotlin PictureInPicture כדי להבין איך מפעילים חוויית מעבר חלקה.
מעבר חלק יותר למצב PIP באמצעות ניווט באמצעות תנועות
החל מ-Android 12, הדגל setAutoEnterEnabled
מספק אנימציה חלקה יותר במעבר לתוכן וידאו במצב 'תמונה בתוך תמונה' באמצעות ניווט באמצעות תנועות – לדוגמה, כשמחליקים למעלה למסך הבית ממסך מלא.
כדי לבצע את השינוי הזה, צריך לפעול לפי השלבים הבאים ולהיעזר בדוגמה הזו:
משתמשים ב-
setAutoEnterEnabled
כדי ליצור אתPictureInPictureParams.Builder
:Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build());
כדאי להתקשר למספר
setPictureInPictureParams
עם המספר המעודכן שלPictureInPictureParams
מוקדם. האפליקציה לא ממתינה להפעלה החוזרת (callback) שלonUserLeaveHint
(כפי שהיא הייתה עושה ב-Android 11).לדוגמה, יכול להיות שתרצו להפעיל את הפונקציה
setPictureInPictureParams
בהפעלה הראשונה ובכל הפעלה שאחריה, אם יחס הגובה-רוחב ישתנה.קוראים ל-
setAutoEnterEnabled(false)
, אבל רק במקרה הצורך. לדוגמה, סביר להניח שלא תרצו להיכנס ל'תמונה בתוך תמונה' אם ההפעלה הנוכחית נמצאת במצב מושהה.
מגדירים sourceRectHint
מתאים לכניסה וליציאה ממצב PIP
החל מהשקת התכונה 'תמונה בתוך תמונה' ב-Android 8.0, הסמל setSourceRectHint
מציין את אזור הפעילות שמוצג אחרי המעבר למצב 'תמונה בתוך תמונה', למשל, תחום הצפייה בסרטון בנגן וידאו.
ב-Android 12, המערכת משתמשת ב-sourceRectHint
כדי להטמיע אנימציה חלקה יותר גם כשנכנסים למצב PiP וגם כשיוצאים ממנו.
כדי להגדיר את sourceRectHint
בצורה נכונה כדי להיכנס ולצאת ממצב PIP:
יוצרים את
PictureInPictureParams
באמצעות הגבולות המתאימים בתורsourceRectHint
. מומלץ גם לצרף למעקב אחרי שינויים בפריסה של נגן הסרטונים:Kotlin
val mOnLayoutChangeListener = OnLayoutChangeListener { v: View?, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop: Int, newRight: Int, newBottom: Int -> val sourceRectHint = Rect() mYourVideoView.getGlobalVisibleRect(sourceRectHint) val builder = PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) setPictureInPictureParams(builder.build()) } mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
Java
private final View.OnLayoutChangeListener mOnLayoutChangeListener = (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight, newBottom) -> { final Rect sourceRectHint = new Rect(); mYourVideoView.getGlobalVisibleRect(sourceRectHint); final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint); setPictureInPictureParams(builder.build()); }; mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
אם צריך, מעדכנים את
sourceRectHint
לפני שהמערכת מתחילה את המעבר ליציאה. כשהמערכת עומדת לצאת ממצב 'תמונה בתוך תמונה', היררכיית התצוגה של הפעילות מסודרת לפי הגדרות היעד שלה (למשל, מסך מלא). האפליקציה יכולה לצרף מאזין לשינוי פריסה לתצוגת הבסיס או לתצוגת היעד שלה (למשל תצוגת נגן הווידאו) כדי לזהות את האירוע ולעדכן אתsourceRectHint
לפני שהאנימציה מתחילה.Kotlin
// Listener is called immediately after the user exits PiP but before animating. playerView.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) { // The playerView's bounds changed, update the source hint rect to // reflect its new bounds. val sourceRectHint = Rect() playerView.getGlobalVisibleRect(sourceRectHint) setPictureInPictureParams( PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build() ) } }
Java
// Listener is called right after the user exits PiP but before animating. playerView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) { // The playerView's bounds changed, update the source hint rect to // reflect its new bounds. final Rect sourceRectHint = new Rect(); playerView.getGlobalVisibleRect(sourceRectHint); setPictureInPictureParams( new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build()); } });
השבתת שינוי הגודל בצורה חלקה של תוכן שאינו וידאו
ב-Android 12 נוספה הדגל setSeamlessResizeEnabled
, שמאפשר אנימציה חלקה יותר של מעבר הדרגתי כשמשנים את הגודל של תוכן שאינו וידאו בחלון PiP. בעבר, שינוי הגודל של תוכן שאינו וידאו בחלון של 'תמונה בתוך תמונה' יכול היה ליצור פגמים חזותיים מטרידים.
כדי להשבית את התכונה 'שינוי גודל חלק' בתוכן שאינו וידאו:
Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build());
טיפול בממשק המשתמש במצב 'תמונה בתוך תמונה'
כשהפעילות נכנסת למצב 'תמונה בתוך תמונה' (PiP) או יוצאת ממנו, המערכת קוראת ל-Activity.onPictureInPictureModeChanged()
או ל-Fragment.onPictureInPictureModeChanged()
.
ב-Android 15 יש שינויים שמבטיחים מעבר חלק עוד יותר כשנכנסים למצב 'תמונה בתוך תמונה'. האפשרות הזו שימושית לאפליקציות שיש בהן רכיבי ממשק משתמש שמופיעים בשכבת-על מעל ממשק המשתמש הראשי, שממשיך לפעול ב-PiP.
מפתחים משתמשים בקריאה החוזרת (callback) onPictureInPictureModeChanged()
כדי להגדיר לוגיקה שמפעילה או משביתה את החשיפה של רכיבי ממשק המשתמש שמופיעים בשכבה העליונה.
הקריאה החוזרת הזו מופעלת כשהאנימציה של הכניסה או היציאה מ-PiP מסתיימת.
החל מגרסה 15 של Android, הכיתה PictureInPictureUiState
כוללת מצב חדש.
במצב החדש של ממשק המשתמש, אפליקציות שמטרגטות ל-Android 15 מבחינות בקריאה החוזרת (callback) של Activity#onPictureInPictureUiStateChanged()
עם isTransitioningToPip()
ברגע שהאנימציה של PiP מתחילה.
יש הרבה רכיבי ממשק משתמש שלא רלוונטיים לאפליקציה כשהיא במצב PiP, למשל תצוגות או פריסות שכוללות מידע כמו הצעות, סרטונים קרובים, דירוגים ושמות. כשהאפליקציה עוברת למצב PiP, משתמשים בקריאה החוזרת onPictureInPictureUiStateChanged()
כדי להסתיר את רכיבי ממשק המשתמש האלה. כשהאפליקציה עוברת למצב מסך מלא מחלון ה-PiP, משתמשים בקריאה החוזרת (callback) onPictureInPictureModeChanged()
כדי לבטל את ההסתרה של הרכיבים האלה, כפי שמתואר בדוגמאות הבאות:
Kotlin
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Java
@Override public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Kotlin
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
Java
@Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
מתג להצגה מהירה של רכיבים לא רלוונטיים בממשק המשתמש (בחלון של 'תמונה בתוך תמונה') כדי להבטיח אנימציה חלקה יותר של 'תמונה בתוך תמונה', ללא הבהובים.
אפשר לשנות את הערך המוגדר מראש של פונקציות ה-callbacks האלה כדי לצייר מחדש את רכיבי ממשק המשתמש של הפעילות. חשוב לזכור שהפעילות שלכם מוצגת בחלון קטן במצב 'תמונה בתוך תמונה'. כשהאפליקציה נמצאת במצב PiP, המשתמשים לא יכולים לקיים אינטראקציה עם רכיבי ממשק המשתמש שלה, ויכול להיות שיהיה קשה לראות את הפרטים של רכיבי ממשק המשתמש הקטנים. פעילויות של הפעלת סרטונים עם ממשק משתמש מינימלי מספקות את חוויית המשתמש הטובה ביותר.
אם האפליקציה שלכם צריכה לספק פעולות מותאמות אישית ל'תמונה בתוך תמונה', כדאי לעיין בקטע הוספת פקדים בדף הזה. אפשר להסיר רכיבים אחרים בממשק המשתמש לפני שהפעילות נכנסת למצב 'תמונה בתוך תמונה', ולשחזר אותם כשהפעילות מוצגת שוב במסך מלא.
הוספת פקדים
אפשר להציג פקדים בחלון PiP כשהמשתמש פותח את התפריט של החלון (על ידי הקשה על החלון במכשיר נייד או בחירה בתפריט בשלט הרחוק של הטלוויזיה).
אם לאפליקציה יש סשן מדיה פעיל, יופיעו הפקדים להפעלה, להשהיה, לסרטון הבא ולסרטון הקודם.
אפשר גם לציין פעולות מותאמות אישית באופן מפורש על ידי פיתוח הקוד PictureInPictureParams
עם PictureInPictureParams.Builder.setActions()
לפני הכניסה למצב 'תמונה בתוך תמונה', והעברת הפרמטרים כאשר נכנסים למצב 'תמונה בתוך תמונה' באמצעות enterPictureInPictureMode(android.app.PictureInPictureParams)
או setPictureInPictureParams(android.app.PictureInPictureParams)
.
חשוב להיזהר. אם תנסו להוסיף יותר מ-getMaxNumPictureInPictureActions()
, תקבלו רק את המספר המקסימלי.
המשך הפעלת הסרטון במצב PiP
כשהפעילות עוברת למצב PiP, המערכת מעבירה את הפעילות למצב מושהה ומפעילה את השיטה onPause()
של הפעילות. אם הפעילות הושהתה במהלך המעבר למצב 'תמונה בתוך תמונה', ההפעלה של הסרטון לא אמורה להשהות, אלא להמשיך.
ב-Android 7.0 ואילך, צריך להשהות ולהמשיך את ההפעלה של הסרטונים כשהמערכת מפעילה את ה-onStop()
וה-onStart()
של הפעילות. כך תוכלו להימנע מבדיקה אם האפליקציה נמצאת במצב PiP ב-onPause()
ולהמשיך את ההפעלה באופן מפורש.
אם לא הגדרתם את הדגל setAutoEnterEnabled
לערך true
ואתם צריכים להשהות את ההפעלה בהטמעה של onPause()
, תוכלו לבדוק אם מופעל מצב PiP על ידי קריאה ל-isInPictureInPictureMode()
ולנהל את ההפעלה בהתאם. לדוגמה:
Kotlin
override fun onPause() { super.onPause() // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode) { // Continue playback. } else { // Use existing playback logic for paused activity behavior. } }
Java
@Override public void onPause() { // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode()) { // Continue playback. ... } else { // Use existing playback logic for paused activity behavior. ... } }
כשהפעילות עוברת ממצב PIP בחזרה למצב מסך מלא, המערכת ממשיכה את הפעילות ומפעילה את השיטה onResume()
.
שימוש בפעילות הפעלה אחת ל-PiP
באפליקציה, משתמש יכול לבחור סרטון חדש כשמדפדף בתוכן במסך הראשי, בזמן שפעילות של הפעלת סרטון מתבצעת במצב PiP. ניתן להפעיל את הסרטון החדש בפעילות ההפעלה הקיימת במצב מסך מלא, במקום להפעיל פעילות חדשה שעלולה לבלבל את המשתמש.
כדי לוודא שנעשה שימוש בפעילות אחת לבקשות להפעלת וידאו, ושאפשר לעבור למצב PiP או לצאת ממנו לפי הצורך, צריך להגדיר את הערך של android:launchMode
בפעילות ל-singleTask
במניפסט:
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
במהלך הפעילות שלכם, משנים את
onNewIntent()
ומטפלים בסרטון החדש, ומפסיקים את הפעלת הסרטון הקיים במקרה הצורך.
שיטות מומלצות
יכול להיות שהתכונה 'צפייה בחלון צף' תושבת במכשירים עם זיכרון RAM נמוך. לפני שהאפליקציה משתמשת ב'תמונה בתוך תמונה', כדאי לוודא שהיא זמינה בטלפון hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
.
התכונה 'תמונה בתוך תמונה' מיועדת לפעילויות שבהן מוצג סרטון במסך מלא. כשמעבירים את הפעילות למצב 'תמונה בתוך תמונה', חשוב לא להציג שום דבר מלבד תוכן וידאו. לעקוב אחרי הרגעים שבהם הפעילות עוברת למצב PiP ולהסתיר רכיבים בממשק המשתמש, כפי שמתואר בקטע טיפול בממשק המשתמש במצב PiP.
כשפעילות נמצאת במצב 'תמונה בתוך תמונה', כברירת מחדל לא ניתן להתמקד בקלט. כדי לקבל אירועי קלט במצב 'תמונה בתוך תמונה', צריך להשתמש ב-MediaSession.setCallback()
.
מידע נוסף על השימוש ב-setCallback()
זמין במאמר הצגת כרטיס 'מה שומעים עכשיו?'.
כשהאפליקציה נמצאת במצב PiP, הפעלת הסרטון בחלון PiP עלולה לגרום להפרעות באודיו באפליקציה אחרת, כמו אפליקציית נגן מוזיקה או אפליקציית חיפוש קולי. כדי למנוע זאת, צריך לבקש להתמקד באודיו כשמתחילים להפעיל את הסרטון ולטפל בהתראות על שינוי ההתמדה באודיו, כפי שמתואר בקטע ניהול ההתמדה באודיו. אם מופיעה התראה על אובדן המיקוד של האודיו במצב 'תמונה בתוך תמונה', משהים או מפסיקים את הפעלת הסרטון.
כשהאפליקציה עומדת לעבור למצב 'תמונה בתוך תמונה', חשוב לזכור שרק הפעילות העליונה עוברת למצב הזה. במצבים מסוימים, כמו במכשירים עם כמה חלונות, יכול להיות שהפעילות שלמטה תוצג עכשיו ותהיה שוב גלויה לצד הפעילות ב-PIP. יש לטפל בפנייה הזו בהתאם, כולל הפעילות הבאה לקבלת קריאה חוזרת מסוג onResume()
או onPause()
. יכול להיות גם שהמשתמש יבצע פעולה כלשהי בפעילות. לדוגמה, אם מוצגת פעילות של רשימת סרטונים ופעילות של סרטון פעיל במצב PiP, המשתמש יכול לבחור סרטון חדש מהרשימה והפעילות ב-PiP אמורה להתעדכן בהתאם.
קוד לדוגמה נוסף
כדי להוריד אפליקציה לדוגמה שנכתבה ב-Kotlin, אפשר לעיין במאמר Android PictureInPicture Sample (Kotlin).