החל מגרסה 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 באפליקציות. כדי לתמוך ב-PiP באפליקציה, צריך לרשום את פעילות הסרטונים במניפסט על ידי הגדרת android:supportsPictureInPicture
ל-true
. בנוסף, צריך לציין שהפעילות מטפלת בשינויים בהגדרות של הפריסה, כדי שהפעילות לא תופעל מחדש כשיש שינויים בפריסה במהלך מעברים למצב PiP.
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
מעבר לפעילות במצב 'תמונה בתוך תמונה'
החל מ-Android 12, אפשר להעביר את הפעילות למצב PiP על ידי הגדרת הדגל setAutoEnterEnabled
לערך true
. כשמשתמשים בהגדרה הזו, הפעילות עוברת באופן אוטומטי למצב PiP לפי הצורך, בלי צורך לבצע קריאה מפורשת ל-enterPictureInPictureMode()
ב-onUserLeaveHint
. בנוסף, כך אפשר ליהנות ממעברים חלקים יותר. פרטים נוספים זמינים במאמר שיפור מעברים ל-PIP באמצעות ניווט באמצעות תנועות.
אם אתם מטרגטים את 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 עוברות למצב PiP אם המשתמש לוחץ על לחצן דף הבית או על לחצן הפריטים האחרונים בזמן שהאפליקציה מבצעת ניווט. אפשר לתפוס את המקרה הזה על ידי שינוי ברירת המחדל של onUserLeaveHint()
:
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
מומלץ: לספק למשתמשים חוויית מעבר חלקה ל-PiP
ב-Android 12 נוספו שיפורים משמעותיים במראה של המעברים האנימציה בין מסך מלא לחלונות PiP. מומלץ מאוד להטמיע את כל השינויים הרלוונטיים. אחרי שתעשו זאת, השינויים האלה יותאמו באופן אוטומטי למסכים גדולים כמו מכשירי גלילה וטאבלטים, בלי שתצטרכו לבצע פעולות נוספות.
אם האפליקציה לא כוללת את העדכונים הרלוונטיים, המעברים ב-PIP עדיין יפעלו, אבל האנימציות יהיו פחות חלקות. לדוגמה, מעבר ממצב מסך מלא למצב PiP עלול לגרום לחלון PiP להיעלם במהלך המעבר, לפני שהוא יופיע שוב כשהמעבר יושלם.
השינויים האלה כוללים את הדברים הבאים.
- שיפור מעברים חלקים יותר למצב PIP דרך ניווט באמצעות תנועות
- הגדרת
sourceRectHint
מתאים לכניסה וליציאה ממצב PIP - השבתת שינוי גודל חלק של תוכן שאינו וידאו
כדאי לעיין בדוגמה ל-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
המעודכן. האפליקציה לא ממתינה להפעלה החוזרת שלonUserLeaveHint
(כפי שהיא הייתה עושה ב-Android 11).לדוגמה, יכול להיות שתרצו להפעיל את הפונקציה
setPictureInPictureParams
בהפעלה הראשונה ובכל הפעלה שאחריה, אם יחס הגובה-רוחב ישתנה.קוראים ל-
setAutoEnterEnabled(false)
, אבל רק במקרה הצורך. לדוגמה, סביר להניח שלא תרצו להיכנס למצב PIP אם ההפעלה הנוכחית מושהית.
מגדירים 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
לפני שהמערכת מתחילה את המעבר ליציאה. כשהמערכת עומדת לצאת ממצב PiP, היררכיית התצוגה של הפעילות מסודרת בהתאם להגדרות היעד שלה (לדוגמה, מסך מלא). האפליקציה יכולה לצרף מאזין לשינוי פריסה לתצוגת הבסיס או לתצוגת היעד שלה (למשל תצוגת נגן הווידאו) כדי לזהות את האירוע ולעדכן את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. האפשרות הזו שימושית לאפליקציות שיש בהן רכיבי ממשק משתמש שמופיעים בשכבת-על מעל ממשק המשתמש הראשי, שממשיך לפעול במצב PiP.
מפתחים משתמשים בקריאה החוזרת (callback) onPictureInPictureModeChanged()
כדי להגדיר לוגיקה שמפעילה או משביתה את החשיפה של רכיבי ממשק המשתמש שמופיעים בשכבה העליונה.
הקריאה החוזרת הזו מופעלת כשהאנימציה של הכניסה או היציאה מ-PiP מסתיימת.
החל מגרסה 15 של Android, הכיתה PictureInPictureUiState
כוללת מצב חדש.
במצב החדש הזה של ממשק המשתמש, אפליקציות שמטרגטות ל-Android 15 מבחינות בקריאה החוזרת (callback) של Activity#onPictureInPictureUiStateChanged()
עם isTransitioningToPip()
ברגע שהאנימציה של PiP מתחילה.
יש הרבה רכיבי ממשק משתמש שלא רלוונטיים לאפליקציה כשהיא במצב PiP, למשל תצוגות או פריסות שכוללות מידע כמו הצעות, סרטונים קרובים, דירוגים ושמות. כשהאפליקציה עוברת למצב PiP, משתמשים ב-callback של 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. } }
החלפת המצב המהירה של הרכיבים הלא רלוונטיים בממשק המשתמש (בחלון PiP) עוזרת להבטיח אנימציה חלקה יותר ללא הבהוב של מעבר ל-PiP.
אפשר לשנות את הערך המוגדר מראש של פונקציות ה-callbacks האלה כדי לצייר מחדש את רכיבי ממשק המשתמש של הפעילות. חשוב לזכור שבמצב 'תמונה בתוך תמונה', הפעילות שלכם מוצגת בחלון קטן. כשהאפליקציה נמצאת במצב PiP, המשתמשים לא יכולים לקיים אינטראקציה עם רכיבי ממשק המשתמש שלה, ויכול להיות שיהיה קשה לראות את הפרטים של רכיבי ממשק המשתמש הקטנים. פעילויות של הפעלת סרטונים עם ממשק משתמש מינימלי מספקות את חוויית המשתמש הטובה ביותר.
אם האפליקציה שלכם צריכה לספק פעולות בהתאמה אישית ל-PiP, תוכלו לעיין בקטע הוספת אמצעי בקרה בדף הזה. מסירים רכיבים אחרים בממשק המשתמש לפני שהפעילות עוברת למצב PiP, ומחזירים אותם כשהפעילות חוזרת למסך מלא.
הוספת פקדים
אפשר להציג פקדים בחלון PiP כשהמשתמש פותח את התפריט של החלון (על ידי הקשה על החלון במכשיר נייד או בחירה בתפריט בשלט הרחוק של הטלוויזיה).
אם לאפליקציה יש סשן מדיה פעיל, יופיעו הפקדים להפעלה, להשהיה, לקטע הבא ולקטע הקודם.
אפשר גם לציין פעולות בהתאמה אישית באופן מפורש על ידי יצירה של PictureInPictureParams
באמצעות PictureInPictureParams.Builder.setActions()
לפני הכניסה למצב PIP, והעברת הפרמטרים כשנכנסים למצב PIP באמצעות 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 נמוך. לפני שמשתמשים ב-PiP באפליקציה, צריך לבדוק אם היא זמינה באמצעות קריאה ל-hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
.
התכונה 'תמונה בתוך תמונה' מיועדת לפעילויות שבהן מוצג סרטון במסך מלא. כשעוברים את הפעילות למצב 'תמונה בתוך תמונה', כדאי להימנע מהצגת תוכן שאינו תוכן וידאו. לעקוב אחרי הרגעים שבהם הפעילות עוברת למצב PiP ולהסתיר רכיבים בממשק המשתמש, כפי שמתואר בקטע טיפול בממשק המשתמש במצב PiP.
כשפעילות נמצאת במצב PIP, כברירת מחדל היא לא מקבלת את המיקוד של הקלט. כדי לקבל אירועי קלט במצב PIP, משתמשים ב-MediaSession.setCallback()
.
מידע נוסף על השימוש ב-setCallback()
זמין במאמר הצגת כרטיס 'מה שומעים עכשיו?'.
כשהאפליקציה נמצאת במצב PiP, הפעלת הסרטון בחלון PiP עשויה לגרום להפרעות באודיו באפליקציה אחרת, כמו אפליקציית נגן מוזיקה או אפליקציית חיפוש קולי. כדי למנוע זאת, צריך לבקש להתמקד באודיו כשמתחילים להפעיל את הסרטון ולטפל בהתראות על שינוי ההתמדה באודיו, כפי שמתואר בקטע ניהול ההתמדה באודיו. אם מופיעה התראה על אובדן המיקוד של האודיו במצב 'תמונה בתוך תמונה', משהים או מפסיקים את הפעלת הסרטון.
כשהאפליקציה עומדת לעבור למצב 'תמונה בתוך תמונה', חשוב לזכור שרק הפעילות העליונה עוברת למצב הזה. במצבים מסוימים, כמו במכשירים עם כמה חלונות, יכול להיות שהפעילות שלמטה תוצג עכשיו ותהיה שוב גלויה לצד הפעילות ב-PIP. יש לטפל בפנייה הזו בהתאם, כולל הפעילות הבאה לקבלת קריאה חוזרת מסוג onResume()
או onPause()
. יכול להיות גם שהמשתמש יבצע אינטראקציה עם הפעילות. לדוגמה, אם מוצגת פעילות של רשימת סרטונים ופעילות של סרטון פעיל במצב PiP, המשתמש יכול לבחור סרטון חדש מהרשימה והפעילות ב-PiP אמורה להתעדכן בהתאם.
קוד לדוגמה נוסף
כדי להוריד אפליקציה לדוגמה שנכתבה ב-Kotlin, אפשר לעיין במאמר Android PictureInPicture Sample (Kotlin).