ספריית Jetpack לתמונה בתוך תמונה (PiP) מציעה פתרון יעיל וחזק למפתחי אפליקציות ל-Android להטמעת פונקציונליות של PiP, במיוחד להפעלת מדיה, לתקשורת בווידאו ולאפליקציות ניווט. הספרייה מספקת API מאוחד, ועוזרת למנוע קוד חוזר, באגים נפוצים באפליקציה ולשפר את האיכות הכוללת של חוויית המשתמש בתכונה 'תמונה בתוך תמונה'.
ספריית Jetpack של תמונה בתוך תמונה (PiP) מאפשרת שימוש בממשקי ה-API הקיימים של PiP, ומטפלת בכמה אתגרים ואי-התאמות מרכזיים בסביבת Android:
- פיצול מערכת ההפעלה: הספרייה מטפלת אוטומטית בהבדלים בקריאות ל-PiP API בגרסאות שונות של Android, למשל שימוש ב-
enterPictureInPictureModeלפני Android 12 וב-isAutoEnterEnabledאחרי, כך שמפתחים לא צריכים לנהל הבדלים בגרסאות. - פרמטרים שגויים של PiP: הוא מספק פתרון אחיד להגדרה נכונה של פרמטרים של PiP, למשל
setSourceRectHint, כדי ליצור אנימציות חלקות ואיכותיות במהלך הפעלת המדיה. - קריאות חוזרות (callback) מאוחדות למצב 'תמונה בתוך תמונה': הקריאות החוזרות
onPictureInPictureModeChangedו-onPictureInPictureUiStateChangedאוחדו לממשק קריאות חוזרות (callback) מאוחד יחיד (PictureInPictureDelegate.OnPictureInPictureEventListener) כדי לפשט את ניהול המצב וממשק המשתמש. - צמצום של קוד סטנדרטי (בוילרפלייט): הספרייה מצמצמת את כמות הקוד הסטנדרטי החוזר על עצמו, ומציעה קבוצות מוגדרות מראש של
RemoteActionsלתרחישי שימוש נפוצים, כמו אמצעי בקרה להפעלה ופעולות של שיחות וידאו. - הכנה לעתיד: תכונות נוספות של 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) בהטמעה של ממשקי Jetpack API. מורכבות ההטמעה והעלות שלה משתנות בהתאם להטמעה הנוכחית של האפליקציה.
בקטעים הבאים מתוארים כמה תרחישי שימוש אופייניים של PiP ושלבי ההטמעה הנדרשים:
ניווט
האפליקציה מעדכנת את הספרייה לגבי המצב הפעיל או הלא פעיל של הניווט ומגדירה את יחס הגובה-רוחב. ספריית Jetpack מטפלת בכל השאר.
ההבדלים העיקריים:
- אין צורך להבחין בין כניסה אוטומטית לבין כניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
- כלי חדש ליצירת
PictureInPictureParamsלתאימות לאחור.
שיחת וידאו
האפליקציה מעדכנת את הספרייה לגבי מצב השיחה (פעילה או לא פעילה) ומגדירה את יחס הגובה-רוחב.
ההבדלים העיקריים:
- אין צורך להבחין בין כניסה אוטומטית לבין כניסה מדור קודם בצד האפליקציה.
- ממשקי קריאה חוזרת מאוחדים.
- כלי חדש ליצירת
PictureInPictureParamsלתאימות לאחור. - סמלי פעולות סטנדרטיים לשיחות וידאו.
5. העברת קוד
- לוגיקת כניסה: מחליפים לוגיקה ספציפית ל-API, כמו
setAutoEnterEnabledל-Android 12 ומעלה, אוonUserLeaveHintל-Android 11 ומטה, ב-setEnabled. הפעלת הטריגר הזה בכל פעם שסטטוס הזכאות לתמונה בתוך תמונה משתנה. - Callbacks: איחוד של
onPictureInPictureModeChanged(החלפת פריסה) ושלonPictureInPictureUiStateChanged(אנימציה/מצבים) ל-callback מאוחד שמבוסס על אירועיםonPictureInPictureEvent. - פעולות ופרמטרים: מעדכנים את הפרמטרים באמצעות
setActionsו-setAspectRatioבמופע של התבנית בכל פעם שהם משתנים. - טיפול מיוחד בסרטונים: באפליקציות של סרטונים, משתמשים ב-
setPlayerViewכדי לעדכן באופן אוטומטי את רמז המלבן של המקור וכדי להבטיח מעברים חלקים. ` ### 6. הסרת המשאבים
ב-VideoPlaybackPictureInPicture, מתקשרים אל close ב-onDispose או ב-onDestroy כדי לשחרר משאבים כמו כלי מעקב אחר צפיות.
דפוסי הטמעה לדוגמה
דוגמאות להטמעות.
ניווט ושיחות וידאו
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() } } } }