ספריית Jetpack לתמונה בתוך תמונה (PiP) מציעה פתרון יעיל וחזק למפתחי אפליקציות ל-Android להטמעת פונקציונליות של PiP, במיוחד להפעלת מדיה, לאפליקציות של תקשורת בווידאו ולאפליקציות ניווט. הספרייה מספקת ממשק API מאוחד, ועוזרת למנוע קוד שחוזר על עצמו (boilerplate), באגים נפוצים באפליקציה ולשפר את האיכות הכוללת של חוויית המשתמש בתכונה 'תמונה בתוך תמונה'.
ספריית Jetpack של תמונה בתוך תמונה (PiP) מאפשרת להשתמש בממשקי ה-API הקיימים של PiP, ומטפלת בכמה אתגרים ואי-התאמות מרכזיים במערכת האקולוגית של Android:
- פיצול מערכת ההפעלה: הספרייה מטפלת אוטומטית בהבדלים בקריאות ל-PiP API בגרסאות שונות של Android, למשל שימוש ב-
enterPictureInPictureModeלפני Android 12 וב-isAutoEnterEnabledאחרי, כך שהמפתחים לא צריכים לנהל הבדלים בגרסאות. - פרמטרים שגויים של PiP: הכלי מספק פתרון מאוחד להגדרה נכונה של פרמטרים של PiP, למשל
setSourceRectHint, כדי ליצור אנימציות חלקות ואיכותיות במהלך הפעלת המדיה. - קריאות חוזרות (callback) מאוחדות למצב PiP: הקריאות החוזרות
onPictureInPictureModeChangedו-onPictureInPictureUiStateChangedאוחדו לממשק קריאות חוזרות מאוחד (PictureInPictureDelegate.OnPictureInPictureEventListener) כדי לפשט את ניהול המצב וממשק המשתמש. - צמצום קוד שחוזר על עצמו (boilerplate): הספרייה מצמצמת את כמות הקוד שחוזר על עצמו (boilerplate) החוזר על עצמו, ומציעה קבוצות מוגדרות מראש של
RemoteActionsלתרחישי שימוש נפוצים, כמו רכיבי UI להפעלה ופעולות של שיחות וידאו. - הכנה לעתיד: תכונות נוספות של PiP מועברות דרך ספריית Jetpack, כך שמשתמשים יכולים לגשת לפונקציונליות נוספת במאמץ מינימלי או ללא מאמץ בכלל.
תהליך העבודה של המיגרציה
זיהוי קטגוריית התרחיש לדוגמה של האפליקציה והלוגיקה של התמונה בתוך תמונה (PiP) מדור קודם:
קטגוריות: הפעלת סרטון, ניווט או שיחת וידאו.
Legacy PiP Logic to Identify:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams.
2. הגדרה של AndroidManifest
כדי למנוע הפעלות מחדש מיותרות, חשוב לוודא שהפעילות שנכנסת למצב תמונה בתוך תמונה מצהירה על תמיכה ב-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, צריך להחליף את ההטמעה הקיימת של תמונה בתוך תמונה (PiP) ב-API של ספריית Jetpack. מורכבות ההטמעה והעלות שלה משתנות בהתאם להטמעה הנוכחית של האפליקציה.
בקטעים הבאים מתוארים כמה תרחישי שימוש אופייניים של PiP ושלבי ההטמעה הנדרשים:
ניווט
האפליקציה מעדכנת את הספרייה לגבי המצב הפעיל או הלא פעיל של הניווט ומגדירה את יחס הגובה-רוחב. ספריית Jetpack מטפלת בכל השאר.
ההבדלים העיקריים:
- אין צורך להבחין בין כניסה אוטומטית לבין כניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
- כלי חדש ליצירת
PictureInPictureParamsלתאימות לאחור.
שיחת וידאו
האפליקציה מעדכנת את הספרייה לגבי הסטטוס של השיחה (פעילה או לא פעילה) ומגדירה את יחס הגובה-רוחב.
ההבדלים העיקריים:
- אין צורך להבחין בין כניסה אוטומטית לבין כניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
- כלי חדש ליצירת
PictureInPictureParamsלתאימות לאחור. - סמלי פעולות סטנדרטיים לשיחות וידאו.
5. העברת קוד
- לוגיקת כניסה: מחליפים לוגיקה ספציפית ל-API, כמו
setAutoEnterEnabledל-Android מגרסה 12 ואילך, אוonUserLeaveHintל-Android מגרסה 11 ומגרסאות קודמות, ב-setEnabled. הפעלת הטריגר הזה בכל פעם שסטטוס הזכאות לתמונה בתוך תמונה משתנה. - Callbacks: איחדנו את
onPictureInPictureModeChanged(החלפת פריסה) ואתonPictureInPictureUiStateChanged(אנימציה/מצבים) ל-callback מאוחד שמבוסס על אירועים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() } } } }