ספריית Jetpack של מצב "תמונה בתוך תמונה" (PiP) מציעה פתרון יעיל ומשופר למפתחי אפליקציות ל-Android שרוצים להטמיע פונקציונליות של PiP, במיוחד באפליקציות להפעלת מדיה, לתקשורת בווידאו ולניווט. הספרייה מספקת ממשק API מאוחד, וכך עוזרת למנוע קוד שחוזר על עצמו (boilerplate), באגים נפוצים באפליקציה, ומשפרת את האיכות הכוללת של חוויית המשתמש ב-PiP.
ספריית Jetpack של תמונה בתוך תמונה מאפשרת להשתמש בממשקי ה-API הקיימים של תמונה בתוך תמונה, ונותנת מענה לכמה אתגרים וחוסר עקביות בסביבה העסקית של Android:
- פרגמנטציה של מערכת ההפעלה: הספרייה מטפלת באופן אוטומטי בהבדלים בקריאות ל-API של מצב תמונה בתוך תמונה בגרסאות השונות של Android. לדוגמה, היא משתמשת ב-
enterPictureInPictureModeלפני Android 12 וב-isAutoEnterEnabledאחרי, כך שהמפתחים לא צריכים לנהל את ההבדלים בין הגרסאות. - פרמטרים שגויים של PiP: מספק פתרון אחיד להגדרה נכונה של פרמטרים של PiP, לדוגמה
setSourceRectHint, כדי ליצור אנימציות חלקות ואיכותיות במהלך הפעלת המדיה. - קריאות חוזרות מאוחדות של מצב PiP: הממשק מאחד את
onPictureInPictureModeChangedואתonPictureInPictureUiStateChangedלממשק קריאה חוזרת מאוחד אחד (PictureInPictureDelegate.OnPictureInPictureEventListener) כדי לפשט את ניהול המצב וממשק המשתמש. - הפחתת קוד שחוזר על עצמו (boilerplate): הספרייה מפחיתה את כמות הקוד שחוזר על עצמו על ידי הצעת קבוצות מוגדרות מראש של
RemoteActionsלתרחישים נפוצים, כמו רכיבי UI להפעלה ופעולות של שיחות וידאו. - התאמה לעתיד: תכונות נוספות של PiP מסופקות דרך ספריית Jetpack, כך שמי שמשתמש בה יכול לגשת לפונקציונליות נוספת במינימום מאמץ או בלי מאמץ בכלל.
תהליך ההעברה
מזהים את קטגוריית התרחיש לדוגמה של האפליקציה ואת הלוגיקה הקודמת של PiP:
קטגוריות: הפעלת וידאו, ניווט או שיחת וידאו.
לוגיקת PiP מדור קודם לזיהוי:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams.
2. הגדרת AndroidManifest
כדי למנוע הפעלות מחדש מיותרות, צריך לוודא שה-Activity שנכנס למצב תמונה בתוך תמונה (PIP) מצהיר על תמיכה ב-AndroidManifest.xml עם configChanges הנדרש:
<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
3. הגדרת הסביבה
מוסיפים את יחסי התלות הנדרשים אל build.gradle:
dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }
כדאי להשתמש בספריות AndroidX העדכניות ביותר ליחסי התלות, ולעיין בדף הגרסאות כדי לקבל את המידע הזה.
4. בחירת תבנית והפעלה
בוחרים את תבנית ההטמעה שהכי מתאימה לתרחיש לדוגמה של האפליקציה:
- ניווט ושיחת וידאו:
BasicPictureInPicture; בדרך כלל אין תמיכה בשינוי גודל חלק, ואין צורך ברמז למלבן המקור. - הפעלת סרטונים:
VideoPlaybackPictureInPicture; עוקב באופן אוטומטי אחרי גבולות התצוגה של הנגן כדי לקבל רמז למלבן המקור, ומאפשר שינוי גודל חלק כברירת מחדל.
כדי להשתמש בספריית Jetpack, צריך להחליף את ההטמעה הקיימת של תמונה בתוך תמונה בהטמעה של ממשקי ה-API של ספריית Jetpack. המורכבות והעלות של ההטמעה ישתנו בהתאם להטמעה הנוכחית של האפליקציה.
בקטעים הבאים מתוארים כמה מהתרחישים הנפוצים לדוגמה של PiP והשלבים הנדרשים להטמעה:
ניווט
האפליקציה מודיעה לספרייה על מצב הניווט (פעיל או לא פעיל) ומגדירה את יחס הגובה-רוחב. ספריית Jetpack מטפלת בכל השאר.
thought ההבדלים העיקריים:
- אין צורך להבדיל בין כניסה אוטומטית לכניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
-
PictureInPictureParamsחדש ליצירת תאימות לדורות קודמים.
שיחת וידאו
האפליקציה מעדכנת את הספרייה לגבי מצב השיחה (פעילה או לא פעילה) ומגדירה את יחס הגובה-רוחב.
thought ההבדלים העיקריים:
- אין צורך להבדיל בין כניסה אוטומטית לכניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
-
PictureInPictureParamsחדש ליצירת תאימות לדורות קודמים. - סמלי פעולות סטנדרטיים לשיחת וידאו.
5. העברת קוד
- לוגיקת כניסה: מחליפים את הלוגיקה הספציפית ל-API, כמו
setAutoEnterEnabledל-Android מגרסה 12 ומעלה, אוonUserLeaveHintל-Android מגרסה 11 ומטה, ב-setEnabled. הפעילו את זה בכל פעם שסטטוס הזכאות ל-PiP משתנה. - קריאות חוזרות (callbacks): מאחדים את
onPictureInPictureModeChanged(החלפת פריסה) ואתonPictureInPictureUiStateChanged(אנימציה/מצבים) לקריאה חוזרת מאוחדת שמבוססת על אירועיםonPictureInPictureEvent. - פעולות ופרמטרים: מעדכנים את הפרמטרים באמצעות
setActionsו-setAspectRatioבמופע של התבנית בכל פעם שהם משתנים.
תבניות להטמעת קובצי עזר
דוגמאות להטמעות.
ניווט ושיחת וידאו
class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: BasicPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = BasicPictureInPicture(this) // BasicPictureInPicture is ideal for Navigation and Video call use cases. pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ } PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ } PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ } } } }
הפעלת סרטון
class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = VideoPlaybackPictureInPicture(this) pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { ContentScreen(pictureInPictureImpl) } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ } PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ } PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ } PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ } PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ } } } @Composable fun ContentScreen(pipController: VideoPlaybackPictureInPicture) { DisposableEffect(pipController) { onDispose { pipController.close() } } } }