הטמעת פעילויות מבצעת אופטימיזציה של אפליקציות במכשירים עם מסך גדול על ידי חלוקת חלון המשימות של האפליקציה בין שתי פעילויות או בין שני מופעים של אותה פעילות.
אם האפליקציה כוללת מספר פעילויות, הטמעת פעילויות מאפשרת לך לספק חוויית משתמש משופרת בטאבלטים, במכשירים מתקפלים ובמכשירי ChromeOS.
אין צורך לבצע רפאקציה של הקוד כדי להטמיע פעילויות. אתם קובעים איך האפליקציה תציג את הפעילויות שלה – זה לצד זה או מוערמות – על ידי יצירת קובץ תצורה של XML או על ידי שליחת קריאות ל-API של Jetpack WindowManager.
התמיכה במסכים קטנים נשמרת באופן אוטומטי. כשהאפליקציה מותקנת במכשיר עם מסך קטן, הפעילויות נערמות אחת על גבי השנייה. במסכים גדולים, הפעילויות מוצגות זו לצד זו. המערכת קובעת את הצגת הנתונים על סמך ההגדרה שיצרתם – לא נדרשת לוגיקה של הסתעפות.
הטמעת הפעילות מתאימה לשינויים בכיוון המכשיר ופועלת בצורה חלקה במכשירים מתקפלים, כך שאפשר לערום פעילויות ולפרוס אותן כשהמכשיר מתקפל ונפתח.
הטמעת פעילות נתמכת ברוב המכשירים עם מסך גדול שפועלת בהם מערכת Android 12L (רמת API 32) ומעלה.
חלון משימה מפוצל
הטמעת הפעילות מפצלת את חלון המשימות של האפליקציה לשני קונטיינרים: ראשי ומשני. הקונטיינרים מכילים פעילויות שהופעלו מהפעילות העיקרית, או מפעילויות אחרות שכבר קיימות בקונטיינרים.
הפעילויות נערמות בקונטיינר המשני כשהן מופעלות, והקונטיינר המשני נערם מעל הקונטיינר הראשי במסכים קטנים, כך שהערמה של פעילויות וניווט חזרה תואמים לסדר הפעילויות שכבר מובנות באפליקציה.
הטמעת פעילויות מאפשרת להציג פעילויות במגוון דרכים. האפליקציה יכולה לפצל את חלון המשימות על ידי הפעלה של שתי פעילויות בו-זמנית זו לצד זו:
לחלופין, אפשר ליצור חלוקה של פעילות שממלאת את כל חלון המשימות על ידי הפעלת פעילות חדשה לצד:
פעילויות שכבר נמצאות מפוצלות ושיתוף חלון משימות יכולות לפעול פעילויות אחרות בדרכים הבאות:
בצד, מעל פעילות אחרת:
לצידה, מזיזים את הפיצול הצידה, וכך מסתירים את התמונה הראשית הקודמת פעילות:
להפעיל פעילות במקום העליון, כלומר באותה רשימת פעילויות (activity stack):
הפעלת פעילות בחלון מלא באותה משימה:
ניווט חזרה
לסוגים שונים של אפליקציות יכולים להיות כללי ניווט אחורה שונים במצב של חלון משימות מפוצל, בהתאם ליחסי התלות בין הפעילויות או לאופן שבו המשתמשים מפעילים את אירוע החזרה אחורה. לדוגמה:
- פעילות משותפת: אם הפעילויות קשורות זו לזו, ואסור להציג אחת מהן בלי השני, ניתן להגדיר את הניווט האחורי כך שיסיים את שניהם.
- פעילות עצמאית: אם הפעילויות עצמאיות לחלוטין, ניווט חזרה בפעילות לא משפיע על המצב של פעילות אחרת בחלון המשימות.
האירוע back נשלח לפעילות האחרונה שבה המשתמש התמקד כשמשתמשים בניווט באמצעות לחצנים.
לניווט מבוסס-תנועות:
Android 14 (רמת API 34) ומטה – האירוע הקודם נשלח אל הפעילות שבה התרחשה התנועה. כשמשתמשים מחליקים מצד ימין של המסך, אירוע החזרה לאחור נשלח לפעילות בחלונית הימנית של החלון המפוצל. כשמשתמשים מחליקים מהצד השמאלי של האירוע 'הקודם' נשלח לפעילות שבחלונית שמשמאל.
Android בגרסה 15 (רמת API 35) ואילך
כשמדובר בפעילויות מרובות מאותה אפליקציה, התנועה מסיים את הפעילות המובילה ללא קשר לכיוון ההחלקה, ומספק לחוויית שימוש מאוחדת יותר.
בתרחישים שכוללים שתי פעילויות מאפליקציות שונות (שכבת-על), אירוע החזרה לאחור מופנה לפעילות האחרונה שבה התמקדו, בהתאם להתנהגות של ניווט באמצעות לחצנים.
פריסת חלוניות מרובות
Jetpack WindowManager מאפשר ליצור פעילות שמוטמעת בתצוגה עם כמה חלונות במכשירים עם מסך גדול עם Android 12L (API ברמה 32) ואילך, ובמכשירים מסוימים עם גרסאות קודמות של הפלטפורמה. אפליקציות קיימות שמבוססות על כמה פעילויות במקום על קטעים או על פריסות שמבוססות על תצוגות, כמו SlidingPaneLayout
, יכולות לספק חוויית משתמש משופרת במסך גדול בלי לבצע רפאקציה של קוד המקור.
דוגמה נפוצה לכך היא פיצול של רשימה לפרטים. כדי להבטיח איכות גבוהה של הצגה, המערכת מפעילה את הפעילות של הרשימה, ואז האפליקציה מפעילה מיד את הפעילות של הפרטים. מערכת המעבר ממתינה עד ששניהם פעילויות מצוירות, ואז מציג אותן יחד. מבחינת המשתמש, שתי הפעילויות מופעלות כפעילות אחת.
פיצול מאפיינים
אתם יכולים לציין את היחס בין החלונות של המשימות לבין הקונטיינרים המפוצלים, ואת אופן הפריסה של הקונטיינרים ביחס זה לזה.
לכללים שמוגדרים בקובץ תצורה של XML, מגדירים את המאפיינים הבאים:
splitRatio
: הגדרת הפרופורציות של הקונטיינר. הערך הוא מספר נקודה צפה (float) בטווח הפתוח (0.0, 1.0).splitLayoutDirection
: מציין את אופן הפריסה של הקונטיינרים המפוצלים ביחס זה לזה. הערכים כוללים:ltr
: משמאל לימיןrtl
: מימין לשמאלlocale
: הערךltr
אוrtl
נקבע לפי הגדרת השפה והאזור
דוגמאות מופיעות בקטע הגדרת XML.
לכללים שנוצרו באמצעות ממשקי ה-API של windowManager, יש ליצור SplitAttributes
אובייקט עם SplitAttributes.Builder
וקריאה ל-builder הבא
אמצעי תשלום:
setSplitType()
: הגדרת הפרופורציות של המאגרים המפוצלים. אפשר לעיין במאמרSplitAttributes.SplitType
כדי לראות אילו ארגומנטים חוקיים, כולל השיטהSplitAttributes.SplitType.ratio()
.setLayoutDirection()
: הגדרת הפריסה של הקונטיינרים. הערכים האפשריים מפורטים במאמרSplitAttributes.LayoutDirection
.
דוגמאות מופיעות בקטע WindowManager API.
placeholders
פעילויות placeholder הן פעילויות משניות ריקות שממלאות אזור של חלוקת פעילות. בסופו של דבר, הם נועדו להחליף אותם בפעילות אחרת שמכיל תוכן. לדוגמה, פעילות placeholder יכולה לתפוס את הצד המשני של פעילות שמפוצלת בתצוגת רשימה עם פרטים, עד שבוחרים פריט מהרשימה. בשלב הזה, פעילות שמכילה את פרטי הפריט שנבחר ברשימה מחליפה את ה-placeholder.
כברירת מחדל, המערכת מציגה placeholders רק כשיש מספיק מקום לחלוקת פעילות. ה-placeholders מסתיימים באופן אוטומטי כשגודל התצוגה הרוחב או הגובה קטנים מדי מכדי להציג פיצול. אם יהיה מספיק מקום, המערכת מפעילה מחדש את ה-placeholder עם מצב שעבר אתחול.
עם זאת, המאפיין stickyPlaceholder
של שיטת SplitPlaceholderRule
או setSticky()
של SplitPlaceholder.Builder
יכול לבטל את התנהגות ברירת המחדל. כשהערך של המאפיין או השיטה הוא true
, המערכת מציגה את placeholder כפעילות העליונה בחלון המשימות כשהגודל של המסך משתנה מחלון עם שתי חלוניות לחלון עם חלונית אחת (לדוגמה, ראו הגדרת פיצול).
שינויים בגודל החלון
כששינויים בהגדרות המכשיר מצמצמים את רוחב חלון המשימות כך שהוא לא גדול מספיק לפריסת חלונות מרובים (לדוגמה, כשמכשיר מתקפל עם מסך גדול מתקפל מטאבלט לגודל טלפון או כשמשנים את הגודל של חלון האפליקציה במצב חלונות מרובים), הפעילויות שאינן placeholders בחלונית המשנית של חלון המשימות נערמות מעל הפעילויות בחלונית הראשית.
פעילויות של Placeholder מוצגות רק כשיש מספיק רוחב תצוגה מפוצל. במסכים קטנים יותר, ה-placeholder נסגר באופן אוטומטי. כאשר אזור התצוגה שוב יהיה גדול מספיק, ה-placeholder נוצר מחדש. (אפשר לעיין ב placeholders.)
אפשר להצמיד פעילויות כי WindowManager מסדר את הפעילויות בחלונית המשנית לפי סדר 'z' מעל הפעילויות בחלונית הראשית.
פעילויות מרובות בחלונית המשנית
פעילות ב' מפעילה את פעילות ג' במקום ללא דגלים נוספים של כוונת השימוש:
התוצאה היא סדר z של הפעילויות באותה משימה:
לכן, בחלון משימות קטן יותר, האפליקציה מצטמצמת לפעילות אחת עם C בחלק העליון של הסטאק:
ניווט חזרה בחלון הקטן יותר מאפשר לנווט בין הפעילויות שממוזערות זו על גבי זו.
אם התצורה של חלון המשימה משוחזרת לגודל גדול יותר שיכול כדי לכלול חלוניות מרובות, הפעילויות מוצגות שוב זו לצד זו.
פלחים מוערמים
פעילות ב' מתחילה את פעילות ג' לצד ומזיזה את הפיצול הצידה:
התוצאה היא סדר z הבא של הפעילויות באותה משימה:
בחלון משימות קטן יותר, האפליקציה מצטמצמת לפעילות אחת עם C בראש:
כיוון לאורך קבוע
הגדרת המניפסט android:screenOrientation מאפשרת לאפליקציות להגביל לאורך או לרוחב. כדי לשפר את חוויית השימוש במכשירים עם מסך גדול, כמו טאבלטים ומכשירים מתקפלים, יצרני המכשירים (OEM) יכולים להתעלם מהבקשות לגבי כיוון המסך ולהציג את האפליקציה בפורמט letterbox בכיוון לאורך במסכים בכיוון לרוחב, או בכיוון לרוחב במסכים בכיוון לאורך.
באופן דומה, כשהתכונה 'הטמעת פעילות' מופעלת, יצרני ציוד מקורי יכולים להתאים אישית מכשירים כך שיציגו פעילויות בפורמט letterbox לאורך בפריסה לרוחב במסכים גדולים (רוחב של 600dp ומעלה). כשפעילות בפורמט ניצב קבועה מפעילה פעילות שנייה, המכשיר יכול להציג את שתי הפעילויות זו לצד זו במסך עם שני חלונות.
יש להוסיף תמיד את android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
לנכס בקובץ המניפסט של האפליקציה כדי ליידע את המכשירים שהאפליקציה תומכת בהם
הטמעת פעילות (למידע נוסף על הגדרת הפיצול
). לאחר מכן, במכשירים בהתאמה אישית של יצרן ציוד מקורי אפשר יהיה לקבוע אם להציג בפורמט letterbox פעילויות בפורמט לאורך קבוע.
הגדרת פיצול
כללים לפיצול מגדירים פיצולי פעילות. מגדירים את כללי הפיצול בקובץ תצורה של XML או באמצעות קריאות ל-API של WindowManager ב-Jetpack.
בכל מקרה, האפליקציה חייבת לגשת לספריית windowManager וצריך ליידע אותה על כך המערכת שבה הוטמעה הפעילות באפליקציה.
מבצעים את הפעולות הבאות:
הוספת התלות האחרונה של ספריית windowManager ברמת המודול של האפליקציה קובץ
build.gradle
, לדוגמה:implementation 'androidx.window:window:1.1.0-beta02'
ספריית windowManager מספקת את כל הרכיבים שנדרשים לפעילות הטמעה אוטומטית.
מודיעים למערכת שהאפליקציה הטמיעה הטמעת פעילות.
מוסיפים את המאפיין
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
לאלמנט <application> בקובץ המניפסט של האפליקציה ומגדירים את הערך כ-true, לדוגמה:<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> </application> </manifest>
ב-WindowManager גרסה 1.1.0-alpha06 ואילך, הטמעת פעילות מפוצלת מושבתים אלא אם הנכס מתווסף למניפסט ומוגדר כ-True.
בנוסף, יצרני המכשירים משתמשים בהגדרה הזו כדי להפעיל יכולות בהתאמה אישית לאפליקציות שתומכות בהטמעת פעילות. לדוגמה, במכשירים עם מסך אופקי, פעילות בפורמט לאורך בלבד מוצגת בפורמט letterbox כדי להתאים את הפעילות למעבר לפריסה של שני חלונות כשפעילות שנייה מתחילה (ראו כיוון קבוע לרוחב).
הגדרות XML
כדי ליצור הטמעה של הטמעת פעילויות שמבוססת על XML:
יוצרים קובץ משאבים בפורמט XML שמבצע את הפעולות הבאות:
- הגדרה של פעילויות שחולקות פיצול
- הגדרת אפשרויות הפיצול
- יוצר placeholder למאגר המשני של הפיצול כשהתוכן לא זמין
- מציין פעילויות שאף פעם לא צריכות להיות חלק מחלוק
לדוגמה:
<!-- main_split_config.xml --> <resources xmlns:window="http://schemas.android.com/apk/res-auto"> <!-- Define a split for the named activities. --> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="always" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".ListActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> <!-- Specify a placeholder for the secondary container when content is not available. --> <SplitPlaceholderRule window:placeholderActivityName=".PlaceholderActivity" window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:stickyPlaceholder="false"> <ActivityFilter window:activityName=".ListActivity"/> </SplitPlaceholderRule> <!-- Define activities that should never be part of a split. Note: Takes precedence over other split rules for the activity named in the rule. --> <ActivityRule window:alwaysExpand="true"> <ActivityFilter window:activityName=".ExpandedActivity"/> </ActivityRule> </resources>
יצירת מאתחל.
הרכיב WindowManager
RuleController
מנתח את קובץ התצורה של ה-XML ומאפשר למערכת לגשת לכללים. ספריית Startup של JetpackInitializer
הופכת את קובץ ה-XML לזמין ל-RuleController
בזמן הפעלת האפליקציה, כך שהכללים ייכנסו לתוקף כשהפעילויות יתחילו.כדי ליצור מאתחלת:
מוסיפים את יחסי התלות העדכניים ביותר של ספריית Jetpack Startup לקובץ
build.gradle
ברמת המודול, לדוגמה:implementation 'androidx.startup:startup-runtime:1.1.1'
יוצרים מחלקה שמטמיעה את הממשק
Initializer
.ה-initializer הופך את כללי הפיצול לזמינים ל-
RuleController
על ידי העברת המזהה של קובץ התצורה של ה-XML (main_split_config.xml
) לשיטהRuleController.parseRules()
.Kotlin
class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
Java
public class SplitInitializer implements Initializer<RuleController> { @NonNull @Override public RuleController create(@NonNull Context context) { RuleController ruleController = RuleController.getInstance(context); ruleController.setRules( RuleController.parseRules(context, R.xml.main_split_config) ); return ruleController; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
יוצרים ספק תוכן להגדרות הכללים.
מוסיפים את
androidx.startup.InitializationProvider
לקובץ המניפסט של האפליקציה בתור<provider>
. מוסיפים הפניה להטמעה של המפעילRuleController
,SplitInitializer
:<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>
InitializationProvider
מזהה ומאתחל אתSplitInitializer
לפני קוראים ל-methodonCreate()
של האפליקציה. כתוצאה מכך, כללי הפיצול כשהפעילות העיקרית של האפליקציה מתחילה.
WindowManager API
ניתן לבצע הטמעת פעילות באופן פרוגרמטי באמצעות מספר קטן של ממשקי API
שיחות. צריך לבצע את הקריאות בשיטה onCreate()
של תת-סוג של Application
כדי לוודא שהכללים ייכנסו לתוקף לפני שהפעילויות יופעלו.
כדי ליצור חלוקה פרוגרמטית של פעילות:
יוצרים כלל פיצול:
יצירת
SplitPairFilter
שמזהה את הפעילויות שחולקות את הפיצול:Kotlin
val splitPairFilter = SplitPairFilter( ComponentName(this, ListActivity::class.java), ComponentName(this, DetailActivity::class.java), null )
Java
SplitPairFilter splitPairFilter = new SplitPairFilter( new ComponentName(this, ListActivity.class), new ComponentName(this, DetailActivity.class), null );
מוסיפים את המסנן לקבוצת מסננים:
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
יוצרים מאפייני פריסה לפיצול:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
final SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builder
יוצר אובייקט שמכיל פריסה :setSplitType()
: הגדרת אזור התצוגה הזמין שהוקצו לכל מאגר פעילות. סוג הפיצול לפי יחס מציין את היחס של אזור התצוגה הזמין שהוקצה לקונטיינר הראשי. קונטיינר משני תופס את שאר אזור התצוגה הזמין.setLayoutDirection()
: קובעת את אופן הפריסה של קונטיינרי הפעילות ביחס זה לזה, קונטיינר ראשי קודם.
יוצרים
SplitPairRule
:Kotlin
val splitPairRule = SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build()
Java
SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build();
הפונקציה
SplitPairRule.Builder
יוצרת ומגדירה את הכלל:filterSet
: מכיל מסננים של צמדי פילוח שמאפשרים לקבוע מתי להחיל את הכלל על ידי זיהוי פעילויות שיש להן פילוח משותף.setDefaultSplitAttributes()
: החלת מאפייני פריסה על הכלל.setMinWidthDp()
: הגדרת רוחב המסך המינימלי (בפיקסלים בלתי תלויים בדחיסות, dp) שמאפשר פיצול.setMinSmallestWidthDp()
: מגדיר את הערך המינימלי (ב-dp) שצריך להיות למאפיין הקטן מבין שני מאפייני התצוגה כדי לאפשר פיצול, ללא קשר לכיוון המכשיר.setMaxAspectRatioInPortrait()
: הגדרת יחס הגובה-רוחב המקסימלי של התצוגה (גובה:רוחב) בכיוון לאורך שבו מוצגים חלוקות הפעילות. אם יחס הגובה-רוחב של תצוגה בפורמט לאורך חורג מיחס הגובה-רוחב המקסימלי, הפיצולים יושבתו ללא קשר לרוחב התצוגה. הערה: ערך ברירת המחדל הוא 1.4, וכתוצאה מכך הפעילויות תופסות את כל חלון המשימות ברוב הטאבלטים כשהמסך בכיוון לאורך. מידע נוסף זמין במאמריםSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
ו-setMaxAspectRatioInLandscape()
. ערך ברירת המחדל של לרוחבALWAYS_ALLOW
.setFinishPrimaryWithSecondary()
: מגדיר איך מסיימים הכול פעילויות במאגר התגים המשני משפיעות על הפעילויות מאגר ראשי. הערךNEVER
מציין שהמערכת לא צריכה לסיים את הפעילויות הראשיות כשכל הפעילויות בקונטיינר המשני מסתיימות (ראו סיום פעילויות).setFinishSecondaryWithPrimary()
: ההגדרה קובעת איך סיום כל הפעילויות בקונטיינר הראשי משפיע על הפעילויות בקונטיינר המשני. הערךALWAYS
מציין שהמערכת תמיד צריכה לסיים את הפעילויות בקונטיינר המשני כשכל הפעילויות בקונטיינר הראשי מסתיימות (ראו סיום פעילויות).setClearTop()
: מציין אם כל הפעילויות המאגר המשני מסתיים כשפעילות חדשה מופעלת מאגר התגים. הערךfalse
מציין שהפעילויות החדשות נערמות מעל הפעילויות שכבר נמצאות במיכל המשני.
מקבלים את מופע היחיד של WindowManager
RuleController
ומוסיפים את הכלל:Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
יוצרים placeholder לקונטיינר המשני כשהתוכן לא זמין:
יוצרים את השדה
ActivityFilter
שמזהה את הפעילות שאיתה המקום הזמין לשמירת תוכן משתף את החלוקה של חלון המשימות:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
מוסיפים את המסנן לקבוצת מסננים:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
יוצרים
SplitPlaceholderRule
:Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(context, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
הפונקציה
SplitPlaceholderRule.Builder
יוצרת ומגדירה את הכלל:placeholderActivityFilterSet
: מכיל מסנני פעילות שמאפשרים לקבוע מתי להחיל את הכלל על ידי זיהוי הפעילויות שפעילות placeholder משויכת אליהן.Intent
: מציין את ההפעלה של הפעילות מסוג placeholder.setDefaultSplitAttributes()
: המערכת מחילה מאפייני פריסה על הכלל.setMinWidthDp()
: הגדרת רוחב התצוגה המינימלי (בפיקסלים שלא תלויים בדחיסות, dp) שמאפשר פיצול.setMinSmallestWidthDp()
: הגדרת הערך המינימלי (ב-dp) שיוצג הערך הקטן מבין השניים המימדים חייבים לאפשר פיצול ללא קשר למכשיר לכיוון מסוים.setMaxAspectRatioInPortrait()
: הגדרת היחס הגובה-רוחב המקסימלי של התצוגה (גובה:רוחב) בכיוון לאורך שבו מוצגים חלוקות הפעילות. הערה: ערך ברירת המחדל הוא 1.4, וכתוצאה מכך הפעילויות ממלאות את חלון המשימות ברוב הטאבלטים בכיוון לאורך. עוד באותו הקשרSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
ו-setMaxAspectRatioInLandscape()
ערך ברירת המחדל לרוחב הואALWAYS_ALLOW
.setFinishPrimaryWithPlaceholder()
: מגדירה איך סיום הפעילות מסוג placeholder משפיע על הפעילויות במאגר הראשי. הערך ALWAYS מציין שהמערכת תמיד צריכה לסיים את הפעילויות במאגר הראשי כשהסמל החלופי מסתיים (ראו סיום פעילויות).setSticky()
: קובע אם הפעילות של ה-placeholder מופיעה מעל מקבץ הפעילות במסכים קטנים ה-placeholder הופיע לראשונה בפיצול עם מספיק מינימום רוחב.
מוסיפים את הכלל ל-windowManager
RuleController
:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
ציון פעילויות שאף פעם לא יכולות להיות חלק מפיצול:
יוצרים
ActivityFilter
שמזהה פעילות שצריכה תמיד לתפוס את כל אזור התצוגה של המשימות:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
מוסיפים את המסנן לקבוצת מסננים:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
יוצרים
ActivityRule
:Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
הפונקציה
ActivityRule.Builder
יוצרת ומגדירה את הכלל:expandedActivityFilterSet
: מכיל מסנני פעילות שמזהים את הפעילויות שרוצים להחריג מהחלוקות, ומאפשרים לקבוע מתי להחיל את הכלל.setAlwaysExpand()
: קובע אם הפעילות תמלא את כל חלון המשימות.
מוסיפים את הכלל ל-WindowManager
RuleController
:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
הטמעה באפליקציות שונות
ב-Android מגרסה 13 (רמת API 33) ואילך, אפליקציות יכולות להטמיע פעילויות ממקורות אחרים תרגום מכונה. הטמעת פעילות חוצת-אפליקציות או חוצת-UID מאפשרת שילוב חזותי של פעילויות מכמה אפליקציות ל-Android. מציגה פעילות של האפליקציה המארחת ופעילות מוטמעת של אפליקציה אחרת במסך לצד המסך או בחלק העליון והתחתון, בדיוק כמו באפליקציה אחת פעילויות.
לדוגמה, אפליקציית ההגדרות יכולה להטמיע את הפעילות של בורר הטפטים מאפליקציית WallpaperPicker:
מודל אמון
תהליכים מארחים שמטמיעים פעילויות מאפליקציות אחרות יכולים להגדיר מחדש הצגת הפעילויות המוטמעות, כולל גודל, מיקום, חיתוך במידה הולמת. מארחים זדוניים יכולים להשתמש ביכולת הזו כדי להטעות משתמשים וליצור הונאת clickjacking או התקפות אחרות שמתמקדות בממשק המשתמש.
כדי למנוע שימוש לרעה בהטמעת פעילויות בין אפליקציות, מערכת Android דורשת מהאפליקציות להביע הסכמה להטמעת הפעילויות שלהן. אפליקציות יכולות לסווג מארחים כמהימנים או שהן לא מהימנות.
מארחים מהימנים
כדי לאפשר לאפליקציות אחרות להטמיע את הפעילות מהאפליקציה שלכם ולשלוט באופן מלא בהצגתה, צריך לציין את אישור ה-SHA-256 של אפליקציית המארח במאפיין android:knownActivityEmbeddingCerts
של האלמנטים <activity>
או <application>
בקובץ המניפסט של האפליקציה.
מגדירים את הערך של android:knownActivityEmbeddingCerts
כמחרוזת:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
או, כדי לציין אישורים מרובים, מערך של מחרוזות:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
שמפנה למשאב כמו זה:
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
בעלי אפליקציות יכולים לקבל תקציר אישורי SHA על ידי הפעלת Gradle
משימה אחת (signingReport
). תקציר האישורים הוא טביעת האצבע SHA-256 ללא
את הנקודתיים. מידע נוסף זמין במאמרים הרצת דוח חתימה ואימות הלקוח.
מארחים לא מהימנים
כדי לאפשר לכל אפליקציה להטמיע את הפעילויות של האפליקציה שלכם ולשלוט בהצגתן, צריך לציין את המאפיין android:allowUntrustedActivityEmbedding
ברכיבים <activity>
או <application>
במניפסט של האפליקציה, לדוגמה:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
ערך ברירת המחדל של המאפיין הוא False, ולכן מונע פעילות בכל האפליקציות הטמעה אוטומטית.
אימות מותאם אישית
כדי למזער את הסיכון בהטמעת פעילות לא מהימנה, כדאי ליצור
מנגנון אימות שמאמת את זהות המארח. אם אתם יודעים מהם האישורים של המארח, תוכלו להשתמש בספרייה androidx.security.app.authenticator
כדי לבצע אימות. אם המארח יבצע אימות לאחר הטמעת הפעילות שלך, תוכל
להציג את התוכן עצמו. אם לא, תוכלו להודיע למשתמש שהפעולה לא אושרה ולחסום את התוכן.
אפשר להשתמש בשיטה ActivityEmbeddingController#isActivityEmbedded()
מהספרייה Jetpack WindowManager כדי לבדוק אם מארח הטמיע את הפעילות שלכם, לדוגמה:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity); }
הגבלת גודל מינימלי
מערכת Android מחילה על פעילויות מוטמעות את הגובה והרוחב המינימליים שצוינו באלמנט <layout>
במניפסט של האפליקציה. אם אפליקציה
ללא ציון גובה ורוחב מינימליים, ערכי ברירת המחדל של המערכת יחולו
sw220dp
.
אם המארח מנסה לשנות את גודל המכל המוטמע לגודל קטן יותר המינימלי, המאגר המוטמע מתרחב כדי למלא את כל גבולות המשימה.
<activity-alias>
כדי שהטמעת פעילויות מהימנה או לא מהימנה תפעל עם
רכיב <activity-alias>
, android:knownActivityEmbeddingCerts
או
צריך להחיל את android:allowUntrustedActivityEmbedding
על פעילות היעד
ולא את הכינוי. המדיניות שמאמתת את האבטחה בשרת המערכת מבוססת על הדגלים שהוגדרו ביעד, ולא על הכינוי החלופי.
אפליקציית המארח
אפליקציות מארחות מטמיעות פעילויות חוצות-אפליקציות באותו אופן שבו הן מטמיעות
להטמיע פעילות באפליקציה יחידה. SplitPairRule
ו-
SplitPairFilter
או ActivityRule
ו-ActivityFilter
אובייקטים
להגדיר פעילויות מוטמעות ופיצולים של חלונות משימות. כללי הפיצול הוגדרו
באופן סטטי ב-XML או בזמן ריצה באמצעות Jetpack
קריאות ל-WindManager API.
אם אפליקציית מארח מנסה להטמיע פעילות שלא הביעה הסכמה להטמעה בין אפליקציות, הפעילות תופסת את כל גבולות המשימה. לכן, אפליקציות המארח צריכות לדעת אם פעילויות היעד מאפשרות הטמעה בכמה אפליקציות.
אם פעילות מוטמעת מתחילה פעילות חדשה באותה משימה, והפעילות החדשה לא הביעה הסכמה להטמעה בין אפליקציות, הפעילות תופסת את כל גבולות המשימה במקום להופיע כשכבת-על לפעילות במאגר המוטמע.
אפליקציה מארחת יכולה להטמיע את הפעילויות שלה ללא הגבלה, כל עוד הפעילויות מופעלות באותה משימה.
דוגמאות לפיצולים
פיצול ממסך מלא
אין צורך בשיפור מבנה הקוד. אפשר לקבוע את ההגדרות של הפיצול
באופן סטטי או בזמן ריצה, ואז מפעילים את הפונקציה Context#startActivity()
ללא
פרמטרים נוספים.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
פיצול כברירת מחדל
כשדף הנחיתה של אפליקציה מתוכנן כך שיחולק לשני קונטיינרים במסכים גדולים, חוויית המשתמש הטובה ביותר מתקבלת כאשר שתי הפעילויות נוצרות ומוצגות בו-זמנית. עם זאת, יכול להיות שהתוכן לא יהיה זמין לקונטיינר המשני של הפיצול עד שהמשתמש יתקשר עם הפעילות בקונטיינר הראשי (לדוגמה, המשתמש בוחר פריט מתפריט ניווט). פעילות placeholder יכולה למלא את החסר עד שהתוכן מוצגות במאגר המשני של הפיצול (ראו placeholders).
כדי ליצור פיצול עם placeholder, צריך ליצור placeholder ולשייך אותו אל הפעילות העיקרית:
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
פיצול קישורי עומק
כשאפליקציה מקבלת Intent, אפשר להציג את פעילות היעד בתור חלק משני בחלוקת פעילות; לדוגמה, בקשה להצגת פרטים של מסך עם מידע על פריט מרשימה. במסכים קטנים, הפרטים מוצגים בחלון המשימות המלא. במכשירים גדולים יותר, הם מוצגים לצד הרשימה.
צריך לנתב את בקשת ההפעלה לפעילות הראשית, ולהפעיל את פעילות פרטי היעד באמצעות חלוקה. המערכת בוחרת באופן אוטומטי את אופן ההצגה הנכון – בערימה או זה לצד זה – על סמך רוחב המסך הזמין.
Kotlin
override fun onCreate(savedInstanceState Bundle?) { . . . RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
יכול להיות שיעדים של קישורי עומק הם הפעילויות היחידות שצריכות להיות זמינות למשתמש בסטאק הניווט לאחור, ורצוי להימנע מסגירה של פעילות הפרטים ולהשאיר רק את הפעילות הראשית:
במקום זאת, אפשר לסיים את שתי הפעילויות בו-זמנית באמצעות
מאפיין finishPrimaryWithSecondary
:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
פרטים נוספים זמינים בקטע מאפייני הגדרה.
פעילויות מרובות בקונטיינרים מפוצלים
סידור כמה פעילויות בקונטיינר מפוצל מאפשר למשתמשים לגשת לנתונים עמוקים תוכן. לדוגמה, כשמחלקים את המסך לרשימה ולפרטים, יכול להיות שהמשתמש יצטרך לעבור לקטע פרטים משני בלי שהפעילות הראשית תיעלם:
Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
הפעילות ברמת הפירוט המשני ממוקמת מעל הפעילות ברמת הפירוט, ומסתירה אותה:
המשתמש יוכל לחזור לרמת הפירוט הקודמת על ידי ניווט חזרה דרך המקבץ:
יצירת סטאק של פעילויות זוהי התנהגות ברירת המחדל כשפעילויות מופעלות מפעילות באותו מאגר משני. פעילויות שמתחילות בקונטיינר הראשי במסגרת חלוקה פעילה מסתיימות גם הן בקונטיינר המשני בחלק העליון של רשימת הפעילויות.
פעילויות במשימה חדשה
כשפעילויות בחלון משימות מפוצל מתחילות פעילויות במשימה חדשה, המשימה נפרדת מהמשימה שכוללת את הפיצול, ומוצגת במלואה חלון. במסך 'שיחות אחרונות' מוצגות שתי משימות: המשימה בחלק היחסי והמשימה החדשה למשימה הזו.
החלפת פעילות
אפשר להחליף פעילויות בסטאק הקונטיינר המשני. לדוגמה, כשהפעילות הראשית משמשת לניווט ברמה העליונה והפעילות המשנית היא יעד שנבחר. כל בחירה מהניווט ברמה העליונה אמורה להתחיל פעילות חדשה בקונטיינר המשני ולהסיר את הפעילות או הפעילויות שהיו שם קודם.
אם האפליקציה לא מסיימת את הפעילות במאגר התגים המשני כאשר שינויים בבחירת הניווט, הניווט אחורה עלול להיות מבלבל כשהפיצול מכווצת (כשהמכשיר מקופל). לדוגמה, אם יש לכם תפריט החלונית הראשית ומסכים A ו-B מוערמים בחלונית המשנית, כשהמשתמש מקפל את הטלפון, B מעל מכשיר A ו-A מעל התפריט. כשהמשתמש חוזר מ-B, תפריט A מופיע במקום התפריט.
במקרים כאלה, צריך להסיר את מסך א' מהמקבץ האחורי.
ברירת המחדל כשמפעילים בצד מאגר חדש מעל פיצול קיים היא להציב את המאגרים המשניים החדשים בחלק העליון ולהשאיר את המאגרים הישנים בסטאק האחורי. אפשר להגדיר את הפיצ'רים כדי למחוק את ההגדרה הקודמת
מאגרים משניים עם clearTop
ומפעילים פעילויות חדשות כרגיל.
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
class MenuActivity { . . . fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity { . . . void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
לחלופין, להשתמש באותה פעילות משנית ומתוך התפריט הראשי (התפריט) פעילות שולחת מנגנוני Intent חדשים שמפנים לאותו מופע אך מפעילים מצב או עדכון ממשק משתמש במאגר התגים המשני.
מספר פילוחים
אפליקציות יכולות לספק ניווט מעמיק ברמות מרובות על ידי הפעלת פעילויות נוספות בצד.
כשפעילות בקונטיינר משני מפעילה פעילות חדשה בצד, נוצר פיצול חדש מעל הפיצול הקיים.
סטאק החזרה מכיל את כל הפעילויות שנפתחו בעבר, כך שמשתמשים יכולים לנווט אל חלוקת ה-A/B אחרי שהם מסיימים את הפעילות C.
כדי ליצור חלוקה חדשה, מפעילים את הפעילות החדשה לצד התוכן הקיים מאגר משני. להצהיר על התצורות של הפיצולים A/B ו-B/C ומפעילים את פעילות ג' בדרך כלל מ-B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B { . . . fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B { . . . void onOpenC() { startActivity(new Intent(this, C.class)); } }
תגובה לשינויים במצב מפוצל
לפעילויות שונות באפליקציה יכולים להיות רכיבי ממשק משתמש שמבצעים את אותה הפונקציה. לדוגמה, רכיב בקרה שפותח חלון שמכיל את הגדרות החשבון.
אם שתי פעילויות שיש להן רכיב ממשק משתמש משותפות נמצאות בפיצול, אז: מיותר ואולי מבלבל כדי להציג את הרכיב בשתי הפעילויות.
כדי לדעת מתי פעילויות נמצאות במצב חלוקה, בודקים את התהליך SplitController.splitInfoList
או רושמים מאזין עם SplitControllerCallbackAdapter
לשינויים במצב החלוקה. לאחר מכן, משנים את ממשק המשתמש בהתאם:
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
אפשר להפעיל פונקציות רפיטיביות בכל מצב במחזור החיים, אבל בדרך כלל הן מופעלות במצב STARTED
כדי לחסוך במשאבים (מידע נוסף זמין במאמר שימוש בפונקציות רפיטיביות ב-Kotlin עם רכיבים שמותאמים למחזור החיים).
אפשר לבצע קריאות חזרה בכל מצב במחזור החיים, כולל כשפעילות מופסקת. בדרך כלל, המאזינים צריכים להיות רשומים ב-onStart()
ולא רשומים ב-onStop()
.
חלון מודאלי
פעילויות מסוימות מונעות ממשתמשים לקיים אינטראקציה עם האפליקציה עד לביצוע פעולה מסוימת. לדוגמה, פעילות במסך ההתחברות, מסך אישור המדיניות או הודעת שגיאה. יש למנוע פעילויות מודאליות להופיע בפיצול.
אפשר לאלץ פעילות למלא תמיד את חלון המשימה באמצעות מקש ההרחבה תצורה:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
סיום הפעילויות
המשתמשים יכולים לסיים את הפעילויות בכל צד של החלוקה על ידי החלקה מהקצה של המסך:
אם המכשיר מוגדר לשימוש בלחצן 'הקודם' במקום בניווט באמצעות תנועות, הקלט נשלח לפעילות המודגשת – הפעילות שנגעה בה או הושק לאחרונה.
ההשפעה של סיום כל הפעילויות בקונטיינר על הצד הנגדי הקונטיינר תלוי בתצורה המפוצלת.
מאפייני תצורה
ניתן לציין מאפיינים של כלל מפוצל כדי להגדיר איך לסיים את התהליך פעילויות בצד אחד של הפיצול משפיעות על הפעילויות בצד השני את הפיצול. המאפיינים הם:
window:finishPrimaryWithSecondary
— איך סיום כל הפעילויות בקונטיינר המשני משפיע על הפעילויות בקונטיינר הראשיwindow:finishSecondaryWithPrimary
– איך לסיים את כל הפעילויות ב- המאגר הראשי משפיע על הפעילויות במאגר המשני
דוגמאות לערכים אפשריים של המאפיינים:
always
– תמיד צריך לסיים את הפעילויות בקונטיינר המשויךnever
– אף פעם לא מסיימים את הפעילויות במאגר התגים המשויךadjacent
– מסיימים את הפעילויות במאגר המשויך כאשר שני המאגרים מוצגים בסמוך זה לזה, אבל לא כאשר שני קונטיינרים מוערמים
לדוגמה:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
הגדרות ברירת המחדל
כשכל הפעילויות בקונטיינר אחד בגימור מפוצל, המאגר הנותר תופסת את כל החלון:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
מסיימים את הפעילויות ביחד
לסיים את הפעילויות בקונטיינר הראשי באופן אוטומטי כשכל הפעילויות בקונטיינר המשני מסתיימות:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
השלמת הפעילויות במאגר המשני באופן אוטומטי לאחר פעילויות בסיום המאגר הראשי:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
לסיים פעילויות ביחד כשכל הפעילויות בקונטיינר הראשי או המשני מסתיימות:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
סיום פעילויות מרובות בקונטיינרים
אם יש כמה פעילויות בקונטיינר מפוצל, סיום פעילות בחלק התחתון של הערימה לא מסיים באופן אוטומטי את הפעילויות בחלק העליון.
לדוגמה, אם שתי פעילויות נמצאות במאגר המשני, C מעל B:
ותצורת החלוקה מוגדרת על ידי תצורת הפעילויות א' וב':
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
סיום הפעילות העליונה שומר על הפיצול.
סיום הפעילות התחתונה (שורש) של הקונטיינר המשני לא מסיר את הפעילויות שמעליו, ולכן גם מאפשר לשמור על הפיצול.
כללים נוספים להשלמת פעילויות ביחד, כמו סיום מתבצעות גם פעילות משנית עם הראשי:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
כשהחלוקה מוגדרת לסיום של המודעה הראשית והמשנית יחד:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
שינוי מאפייני הפיצול בזמן הריצה
לא ניתן לשנות את המאפיינים של פיצול פעיל וגלוי. שינוי של כללי פיצול משפיעים על השקות נוספות של פעילויות ועל מאגרים חדשים, אבל לא קיימים ופעילים.
כדי לשנות את המאפיינים של פיצולים פעילים, מסיימים את הפעילות שבצד או את הפעילויות שבפיצול ומפעילים שוב לצד עם תצורה חדשה.
מאפייני חלוקה דינמיים
ב-Android 15 (רמת API 35) ואילך, עם תמיכה ב-Jetpack WindowManager 1.4 ואילך, יש תכונות דינמיות שמאפשרות לקבוע את חלוקת הטמעת הפעילות, כולל:
- הרחבת חלוניות: מחלק אינטראקטיבי שניתן לגרירה מאפשר למשתמשים לשנות את הגודל של החלונות במצגת מחולקת.
- הצמדת פעילות: המשתמשים יכולים להצמיד את התוכן במאגר אחד. לבודד את הניווט במאגר מהניווט במאגר האחר.
- הכהיית תיבת דו-שיח במסך מלא: כשמוצגת תיבת דו-שיח, האפליקציות יכולות לציין אם להכהות את כל חלון המשימות או רק את המארז שבו נפתחה תיבת הדו-שיח.
הרחבת חלוניות
הרחבת החלוניות מאפשרת למשתמשים להתאים את גודל שטח המסך שהוקצה שתי הפעילויות בפריסה של שתי חלוניות.
כדי להתאים אישית את המראה של מחיצת החלון ולהגדיר את המחיצה טווח שניתן לגרירה, מבצעים את הפעולות הבאות:
יוצרים מופע של
DividerAttributes
מתאימים אישית את מאפייני ההפרדה:
color
: הצבע של מפריד החלונית שניתן לגרור.widthDp
: הרוחב של מפריד החלונית שניתן לגרור. הגדרה לערךWIDTH_SYSTEM_DEFAULT
כדי לאפשר למערכת לזהות את המחיצה רוחב.טווח גרירה: האחוז המינימלי של המסך שכל אחד מהחלונות יכול לתפוס. הטווח יכול להיות בין 0.33 ל-0.66. מגדירים את הערך ל-
DRAG_RANGE_SYSTEM_DEFAULT
כדי לאפשר למערכת לקבוע את טווח הגרירה.
Kotlin
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { splitAttributesBuilder.setDividerAttributes( DividerAttributes.DraggableDividerAttributes.Builder() .setColor(getColor(context, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
Java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(context, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes splitAttributes = splitAttributesBuilder.build();
הצמדת פעילות
הצמדת פעילות מאפשרת למשתמשים להצמיד אחד מהחלונות המפוצלים, כך שהפעילות תישאר כפי שהיא בזמן שהמשתמשים מנווטים בחלון השני. פעילות התכונה 'הצמדה' מספקת חוויה משופרת של ריבוי משימות.
כדי להפעיל את הצמדת הפעילות באפליקציה:
מוסיפים לחצן לקובץ הפריסה של הפעילות שרוצים להצמיד, למשל הפעילות של פרטי הפריסה של רשימה:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/detailActivity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".DetailActivity"> <TextView android:id="@+id/textViewItemDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="36sp" android:textColor="@color/obsidian" app:layout_constraintBottom_toTopOf="@id/pinButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/pinButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pin_this_activity" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/> </androidx.constraintlayout.widget.ConstraintLayout>
בשיטה
onCreate()
של הפעילות, הגדרת ה-listen onclick ב- לחצן:Kotlin
pinButton = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule) }
Java
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) => { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule); });
עמעום המסך במסך מלא
בדרך כלל, פעילויות מעמעמות את התצוגה כדי למשוך תשומת לב לתיבת דו-שיח. כשממקמים פעילות, שתי החלונות של התצוגה עם שתי החלונות צריכים להתעמעם, ולא רק החלון שמכיל את הפעילות שפתחה את תיבת הדו-שיח, כדי ליצור חוויית משתמש מאוחדת.
ב-windowManager 1.4 ואילך, חלון האפליקציה כולו מעומעם כברירת מחדל כאשר
נפתחת תיבת דו-שיח (ראו EmbeddingConfiguration.DimAreaBehavior.ON_TASK
).
כדי להכהות רק את המאגר של הפעילות שפתחה את תיבת הדו-שיח, משתמשים ב-EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
.
חילוץ פעילות מפיצול לחלון מלא
יוצרים מערך הגדרות אישיות חדש שמציג את החלון המלא של הפעילות הצדדית, ולאחר מכן מפעילים מחדש את הפעילות עם Intent שמפנה לאותו מופע.
בדיקה אם יש תמיכה מפוצלת בזמן ריצה
יש תמיכה בהטמעת פעילות ב-Android 12L (רמת API 32) ואילך, אבל היא זמינה גם במכשירים מסוימים עם גרסאות פלטפורמה קודמות. כדי לבדוק:
בסביבת זמן הריצה של התכונה, השתמשו
נכס SplitController.splitSupportStatus
או
אמצעי תשלום SplitController.getSplitSupportStatus()
:
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
Java
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
אם אין תמיכה בחלוקות, הפעילויות יופעלו מעל ל-activity stack (בהתאם למודל ההטמעה ללא פעילות).
מניעת שינוי מברירת המחדל של המערכת
היצרנים של מכשירי Android (יצרני ציוד מקורי או יצרני ציוד מקורי), יכולים להטמיע הטמעת פעילות כפונקציה של מערכת המכשיר. המערכת מציינת כללי פיצול לאפליקציות עם כמה פעילויות, ומבטלת את התנהגות החלונות של האפליקציות. המערכת מאלצת אפליקציות עם ריבוי פעילויות מצב של הטמעת פעילות בהגדרת המערכת.
הטמעת פעילות במערכת יכולה לשפר את הצגת האפליקציות באמצעות חלוניות מרובות פריסות אימייל, כמו list-detail, ללא שינויים באפליקציה. אבל, הטמעת פעילות של המערכת עלולה גם לגרום לפריסות שגויות של האפליקציה, לבאגים או מתנגשות עם הטמעת פעילות שהוטמעה על ידי האפליקציה.
האפליקציה יכולה למנוע הטמעה של פעילות מערכת או לאפשר אותה על ידי הגדרת מאפיין בקובץ המניפסט של האפליקציה. לדוגמה:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
שם המאפיין מוגדר ב-Jetpack windowManager WindowProperties
לאובייקט. מגדירים את הערך כ-false
אם האפליקציה מטמיעה הטמעת פעילות, או אם רוצים למנוע בדרך אחרת את החלת הכללים של הטמעת הפעילות על האפליקציה. מגדירים את הערך כ-true
כדי לאפשר למערכת להחיל על האפליקציה הטמעת פעילות שהוגדרה על ידי המערכת.
מגבלות, הגבלות וסייגים
- רק אפליקציית המארח של המשימה, שזוהתה בתור הבעלים של פעילות הבסיס במשימה, יכולה לארגן ולטמיע פעילויות אחרות במשימה. אם פעילויות שתומכות בהטמעה ובפיצולים פועלות במשימה ששייכת לאפליקציה אחרת, הטמעה ופיצולים לא יפעלו הפעילויות האלה.
- אפשר לארגן פעילויות רק בתוך משימה אחת. איך מתחילים פעילות במשימה חדשה, היא תמיד מציגה בחלון מורחב חדש בחלקים הקיימים.
- אפשר לארגן ולחלק רק פעילויות באותו תהליך.
קריאה חוזרת (callback) של
SplitInfo
מדווחת רק על פעילויות ששייכות לאותה כתובת מכיוון שאין דרך לדעת על פעילויות תהליכים. - כל כלל פעילות של זוג או יחיד חל רק על הפעלות של פעילות ש מתרחשת אחרי שהכלל נרשם. כרגע אין דרך לעדכן פיצולים קיימים או את המאפיינים החזותיים שלהם.
- תצורת המסנן של הצמד המפוצל חייבת להתאים לאובייקטים של Intent שמשמשים כאשר ולהשיק את הפעילויות שלו. ההתאמה מתרחשת בנקודה שבה מתחילה פעילות חדשה מתהליך האפליקציה, כך שיכול להיות שהיא לא תדע על שמות הרכיבים שמתקבלים מאוחר יותר בתהליך המערכת כשמשתמשים בכוונות משתמשים מרומזות. אם שם הרכיב לא ידוע בזמן ההשקה, אפשר להשתמש בתו אסימון ("*/*") ולבצע סינון על סמך פעולת הכוונה.
- בשלב זה אין אפשרות להעביר פעילויות בין קונטיינרים או להעביר אותן לחלוקות או מהן אחרי שהן נוצרו. ספריית WindowManager יוצרת חלוקות רק כשמפעילים פעילויות חדשות עם כללים תואמים, והן נמחקות כשהפעילות האחרונה בקונטיינר המפוצל מסתיימת.
- אפשר להפעיל מחדש פעילויות כשהגדרות האישיות משתנות. לכן, כשיוצרים או מסירים פיצול וערכי הגבול של הפעילות משתנים, הפעילות יכולה לעבור תהליך של השמדה מוחלטת של המכונה הקודמת ויצירה של מכונה חדשה. כתוצאה מכך, מפתחי אפליקציות צריכים להיזהר מדברים כמו משיקים פעילויות חדשות מקריאות חוזרות (callback) במחזור החיים.
- כדי לתמוך בפעילות, המכשירים צריכים לכלול את הממשק של תוספי החלונות הטמעה אוטומטית. כמעט כל המכשירים עם מסך גדול שפועלת בהם מערכת Android 12L (רמת API) 32) ומעלה כוללים את הממשק. עם זאת, במכשירים מסוימים עם מסך גדול שלא יכולים להריץ כמה פעילויות, ממשק התוספים של החלונות לא מופיע. אם מכשיר עם מסך גדול לא תומך בריבוי חלונות במצב כזה, ייתכן שהוא לא תומך בהטמעת פעילויות.
מקורות מידע נוספים
- Codelabs:
- תוכנית לימודים – הטמעת פעילות
- אפליקציה לדוגמה – הטמעת פעילות