تضمين الأنشطة

يساعد تضمين النشاط في تحسين التطبيقات على الأجهزة ذات الشاشات الكبيرة من خلال تقسيم نافذة مهام التطبيق بين نشاطين أو حالتين من نفس النشاط.

الشكل 1. تطبيق الإعدادات مع الأنشطة جنبًا إلى جنب.

إذا كان تطبيقك يتألّف من أنشطة متعدّدة، سيتيح لك تضمين النشاط توفير تجربة محسّنة للمستخدم على الأجهزة اللوحية والأجهزة القابلة للطي وأجهزة ChromeOS.

لا يتطلب تضمين الأنشطة إعادة هيكلة الرمز. يمكنك تحديد كيفية عرض تطبيقك لأنشطته جنبًا إلى جنب أو بشكلٍ مجمّع من خلال إنشاء ملف إعداد XML أو إجراء طلبات بيانات من واجهة برمجة التطبيقات Jetpack WindowManager.

يتم الحفاظ على دعم الشاشات الصغيرة تلقائيًا. عندما يكون تطبيقك على جهاز بشاشة صغيرة، تكون الأنشطة مكدسة واحدة فوق الأخرى. على الشاشات الكبيرة، يتم عرض الأنشطة جنبًا إلى جنب. يحدد النظام العرض التقديمي بناءً على التهيئة التي قمت بإنشائها - لا حاجة إلى منطق تشعّبي.

تسمح ميزة "تضمين الأنشطة" بالتغيرات في اتجاه الجهاز وتعمل بسلاسة على الأجهزة القابلة للطي، فضلاً عن أنشطة تكديس الجهاز وفك تكديسه عند طي الجهاز وفتحه.

تتوفّر ميزة تضمين الأنشطة على معظم الأجهزة ذات الشاشات الكبيرة التي تعمل بنظام التشغيل Android 12L (المستوى 32 لواجهة برمجة التطبيقات) والإصدارات الأحدث.

تقسيم نافذة المَهمّة

يؤدي تضمين النشاط إلى تقسيم نافذة مهام التطبيق إلى حاويتين: أساسية وثانوية. تحتوي الحاويات على أنشطة تم إطلاقها من النشاط الرئيسي أو من أنشطة أخرى موجودة بالفعل في الحاويات.

يتم تكديس الأنشطة في الحاوية الثانوية عند إطلاقها، ويتم تكديس الحاوية الثانوية على الحاوية الأساسية على الشاشات الصغيرة، بحيث يتوافق تكديس الأنشطة والتنقل الخلفي مع ترتيب الأنشطة المضمّنة بالفعل في تطبيقك.

يتيح لك تضمين الأنشطة عرض الأنشطة بعدة طرق. يمكن لتطبيقك تقسيم نافذة المهام عن طريق تشغيل نشاطين جنبًا إلى جنب في الوقت نفسه:

الشكل 2. نشاطان جنبًا إلى جنب.

أو، يمكن أن يؤدي النشاط الذي يشغل نافذة المهمة بأكملها إلى إنشاء تقسيم عن طريق إطلاق نشاط جديد إلى جانب:

الشكل 3. يبدأ النشاط "أ" النشاط "ب" إلى جانبه.

يمكن للأنشطة المنقسمة بالفعل والتي تتم مشاركتها في نافذة مهمة تشغيل أنشطة أخرى بالطرق التالية:

  • بجانب نشاط آخر:

    الشكل 4. يبدأ النشاط "أ" النشاط "ج" إلى الجانب بدلاً من النشاط "ب".
  • إلى الجانب، وتحويل القسمين إلى الجانبين، وإخفاء النشاط الأساسي السابق:

    الشكل 5. يبدأ النشاط "ب" النشاط "ج" إلى الجانب وينقل الجانبَين إلى الجانبَين.
  • إطلاق نشاط في مكانه في الأعلى؛ أي في نفس حزمة الأنشطة:

    الشكل 6. يبدأ النشاط "ب" النشاط "ج" بدون علامات نية إضافية.
  • لتشغيل نافذة كاملة لنشاط في المهمة نفسها:

    الشكل 7. يؤدي النشاط "أ" أو النشاط "ب" إلى بدء النشاط "ج" الذي يملأ نافذة المهمة.

الرجوع إلى الصفحة السابقة

يمكن أن يكون للأنواع المختلفة من التطبيقات قواعد مختلفة للرجوع إلى الوراء في حالة نافذة مهمة مقسمة بناءً على التبعيات بين الأنشطة أو كيفية تشغيل المستخدمين للحدث الخلفي، على سبيل المثال:

  • التنفيذ معًا: إذا كانت الأنشطة مرتبطة ببعضها، وكان يجب عدم عرض أحدهما بدون الآخر، يمكن ضبط ميزة التنقل للخلف لإنهاء الاثنين.
  • تنفيذ الأمر بمفردك: إذا كانت الأنشطة مستقلة تمامًا، لا يؤثر التنقل للخلف في النشاط في حالة نشاط آخر في نافذة المهمة.

يتم إرسال الحدث العكسي إلى آخر نشاط محل التركيز عند استخدام التنقّل باستخدام الأزرار. باستخدام التنقل القائم على الإيماءات، يتم إرسال الحدث الخلفي إلى النشاط الذي حدثت فيه الإيماءة.

تصميم متعدد الأجزاء

يتيح لك Jetpack WindowManager إنشاء نشاط يتضمّن تنسيقًا متعدد الأجزاء على الأجهزة ذات الشاشات الكبيرة التي تعمل بالإصدار 12L من نظام التشغيل Android (المستوى 32 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، وعلى بعض الأجهزة التي تتضمّن إصدارات سابقة من النظام الأساسي. يمكن للتطبيقات الحالية التي تستند إلى أنشطة متعددة بدلاً من الأجزاء أو التنسيقات المستندة إلى العرض، مثل SlidingPaneLayout، تحسين تجربة المستخدم على الشاشات الكبيرة بدون إعادة تنظيم رمز المصدر.

من الأمثلة الشائعة تقسيم القائمة مع التفاصيل. لضمان عرض تقديمي عالي الجودة، يبدأ النظام نشاط القائمة، ثم يبدأ التطبيق نشاط التفاصيل على الفور. ينتظر نظام الانتقال حتى يتم رسم كلا النشاطين، ثم يعرضهما معًا. بالنسبة للمستخدم، يتم تشغيل النشاطين كواحد.

الشكل 8. تم بدء نشاطَين في الوقت نفسه بتنسيق متعدد الأجزاء.

تقسيم السمات

يمكنك تحديد كيفية متناسبة نافذة المهام بين الحاويات المقسمة وكيفية وضع الحاويات بالنسبة إلى بعضها البعض.

بالنسبة إلى القواعد المحدّدة في ملف إعداد XML، اضبط السمات التالية:

  • splitRatio: لضبط تناسب الحاوية. والقيمة هي رقم نقطة عائمة في الفاصل الزمني المفتوح (0.0، 1.0).
  • splitLayoutDirection: لتحديد كيفية وضع حاويات التقسيم بالنسبة إلى بعضها البعض. تشمل القيم ما يلي:
    • ltr: من اليسار إلى اليمين
    • rtl: من اليمين إلى اليسار
    • locale: يتم تحديد إما ltr أو rtl من إعداد اللغة.

راجِع ضبط XML أدناه للحصول على أمثلة.

بالنسبة إلى القواعد التي يتم إنشاؤها باستخدام واجهات برمجة تطبيقات WindowManager، يمكنك إنشاء كائن SplitAttributes باستخدام SplitAttributes.Builder وطلب طرق الإنشاء التالية:

  • setSplitType(): لضبط نِسب الحاويات المقسّمة. راجِع SplitAttributes.SplitType للاطّلاع على الوسيطات الصالحة، بما في ذلك طريقة SplitAttributes.SplitType.ratio().
  • setLayoutDirection(): لضبط تنسيق الحاويات راجِع SplitAttributes.LayoutDirection للاطّلاع على القيم المحتملة.

يُرجى الاطّلاع على WindowManager API أدناه للحصول على أمثلة.

الشكل 9. تم وضع تقسيمَين للأنشطة من اليسار إلى اليمين ولكن بنِسب تقسيم مختلفة.

العناصر النائبة

أنشطة العنصر النائب هي أنشطة ثانوية فارغة تشغل مساحة من تقسيم النشاط. من المفترض في النهاية استبدالها بنشاط آخر يحتوي على محتوى. على سبيل المثال، يمكن أن يشغل نشاط العنصر النائب الجانب الثانوي من تقسيم النشاط بتنسيق قائمة تفاصيل حتى يتم تحديد عنصر من القائمة، وعندها يتم استبدال العنصر النائب بنشاط يحتوي على المعلومات التفصيلية لعنصر القائمة المحدد.

لا يعرض النظام تلقائيًا العناصر النائبة إلا عند توفّر مساحة كافية لتقسيم النشاط. تنتهي العناصر النائبة تلقائيًا عندما يتغير حجم العرض إلى عرض أو ارتفاع صغير جدًا بحيث لا يمكن عرض تقسيم. عندما تسمح المساحة، يعيد النظام تشغيل العنصر النائب مع إعادة تهيئة.

الشكل 10. الجهاز القابل للطي والفتح والطي تم الانتهاء من نشاط العنصر النائب وإعادة إنشائه كتغييرات في حجم العرض.

مع ذلك، يمكن أن تلغي السمة stickyPlaceholder لطريقة SplitPlaceholderRule أو setSticky() في SplitPlaceholder.Builder السلوك التلقائي. عندما تحدّد السمة أو الطريقة قيمة true، يعرض النظام العنصر النائب بصفته أعلى نشاط في نافذة المهام عندما يتم تغيير حجم الشاشة إلى عرض أحادي اللوحة من عرض مكوّن من لوحتَين (راجِع ضبط الإعدادات للاطّلاع على مثال).

الشكل 11. الجهاز القابل للطي والفتح والطي يكون نشاط العنصر النائب ثابتًا.

التغييرات في حجم النوافذ

عندما تؤدي التغييرات في إعدادات الجهاز إلى تقليل عرض نافذة المهام بحيث لا يكون حجمها كبيرًا بما يكفي لاستخدام تنسيق متعدد الأجزاء (على سبيل المثال، عندما يتم طي شاشة كبيرة جهاز قابل للطي من حجم الجهاز اللوحي إلى حجم الهاتف أو يتم تغيير حجم نافذة التطبيق في وضع النوافذ المتعددة)، يتم تكديس الأنشطة غير النائبة في الجزء الثانوي من نافذة المهمة فوق الأنشطة في الجزء الأساسي.

لا يتم عرض أنشطة العناصر النائبة إلا عند وجود عرض شاشة كافٍ للتقسيم. في الشاشات الأصغر حجمًا، يتم إغلاق العنصر النائب تلقائيًا. عندما تصبح منطقة العرض كبيرة بما يكفي مرة أخرى، تتم إعادة إنشاء العنصر النائب. (اطّلِع على العناصر النائبة أعلاه.)

يمكن تجميع الأنشطة لأنّ WindowManager يرتب الأنشطة في الجزء الثانوي فوق الأنشطة في الجزء الأساسي.

أنشطة متعددة في اللوحة الثانوية

يبدأ النشاط "ب" النشاط "ج" في مكانه بدون علامات نية إضافية:

تم تقسيم النشاط الذي يحتوي على الأنشطة "أ" و"ب" و"ج" مع تكديس الأنشطة "ج"
          فوق النشاط "ب".

مما ينتج عنه الترتيب z التالي للأنشطة في نفس المهمة:

حزمة الأنشطة الثانوية التي تحتوي على النشاط "ج" مكدسة أعلى النشاط "ب"
          يتم تكديس الحزمة الثانوية فوق حزمة الأنشطة الأولية التي تحتوي على النشاط "أ".

لذلك، في نافذة مهام أصغر، يتقلص التطبيق إلى نشاط واحد مع وجود C في الجزء العلوي من المكدس:

نافذة صغيرة تعرض النشاط "ج" فقط

يؤدي الانتقال للخلف في النافذة الأصغر إلى التنقل عبر الأنشطة المكدسة فوق بعضها البعض.

إذا تمت استعادة تهيئة نافذة المهمة إلى حجم أكبر يمكنه استيعاب أجزاء متعددة، يتم عرض الأنشطة جنبًا إلى جنب مرة أخرى.

تقسيمات مكدسة

يبدأ النشاط "ب" النشاط "ج" إلى الجانب ويحول الانقسام إلى الجانبين:

نافذة مهام تعرض النشاطين "أ" و"ب"، ثم النشاطان "ب" و"ج".

والنتيجة هي الترتيب z التالي للأنشطة في نفس المهمة:

الأنشطة "أ" و"ب" و"ج" في حزمة واحدة يتم تكديس الأنشطة بالترتيب التالي من أعلى إلى أسفل: ج، ب، أ.

في نافذة مهام أصغر، يتقلص التطبيق إلى نشاط واحد مع وجود حرف C في الأعلى:

نافذة صغيرة تعرض النشاط "ج" فقط

اتجاه عمودي ثابت

يتيح إعداد البيان android:screenOrientation للتطبيقات تقييد الأنشطة باتجاه عمودي أو أفقي. لتحسين تجربة المستخدم على الأجهزة ذات الشاشات الكبيرة، مثل الأجهزة اللوحية والأجهزة القابلة للطي، يمكن للمصنّعين الأصليين للأجهزة (OEM) تجاهل طلبات اتجاه الشاشة والشاشة الأفقية للتطبيق في الاتجاه العمودي على الشاشات الأفقية أو الاتجاه الأفقي على الشاشات العمودية.

الشكل 12. الأنشطة بوضع أفقي: وضع عمودي ثابت على الجهاز الأفقي (لليسار)، ووضع أفقي ثابت على الجهاز العمودي (لليمين).

وبالمثل، عندما تكون ميزة "تضمين النشاط" مفعّلة، يمكن للمصنّعين الأصليين للأجهزة تخصيص الأجهزة للأنشطة العمودية الثابتة في الوضع الأفقي على الشاشات الكبيرة (أكبر من 600 بكسل مستقل الكثافة). عند بدء نشاط ثانٍ في الوضع العمودي الثابت، يمكن للجهاز عرض النشاطَين جنبًا إلى جنب في عرض مؤلف من جزأين.

الشكل 13. يبدأ النشاط العمودي الثابت "أ" النشاط "ب" إلى الجانب.

أضِف السمة android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED دائمًا إلى ملف بيان التطبيقات لإعلام الأجهزة بأنّ تطبيقك يتيح تضمين الأنشطة (راجِع إعدادات التقسيم أدناه). ويمكن للأجهزة المخصّصة التي يقدّمها المصنّعون الأصليون بعد ذلك تحديد ما إذا كان سيتم استخدام الأنشطة في الوضع العمودي الثابت للعرض على شاشة عريضة أفقيًا أم لا.

إعدادات التقسيم

تعمل قواعد التقسيم على ضبط عمليات تقسيم الأنشطة. يمكنك تحديد قواعد التقسيم في ملف إعداد XML أو من خلال إجراء طلبات البيانات من واجهة برمجة التطبيقات في Jetpack WindowManager.

وفي كلتا الحالتين، يجب أن يصل تطبيقك إلى مكتبة WindowManager وأن يُعلِم النظام بأنّ التطبيق قد نفَّذ عملية تضمين الأنشطة.

قم بما يلي:

  1. أضِف أحدث تبعية لمكتبة WindowManager إلى ملف build.gradle على مستوى الوحدة في تطبيقك، على سبيل المثال:

    implementation 'androidx.window:window:1.1.0-beta02'

    توفّر مكتبة WindowManager جميع المكوّنات المطلوبة لتضمين النشاط.

  2. أبلِغ النظام بأنّ تطبيقك قد نفَّذ عملية تضمين النشاط.

    أضِف السمة android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED إلى العنصر <Application> في ملف بيان التطبيق، واضبط القيمة على "صحيح"، على سبيل المثال:

    <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>
    

    في الإصدار 1.1.0-alpha06 والإصدارات الأحدث من WindowManager، يتم إيقاف تقسيمات تضمين الأنشطة ما لم تتم إضافة الخاصية إلى البيان وضبطها على "صحيح".

    وتستخدم الشركات المصنّعة للأجهزة أيضًا هذا الإعداد لتفعيل إمكانات مخصّصة للتطبيقات التي تتيح تضمين النشاط. على سبيل المثال، يمكن للأجهزة إبقاء النشاط في الوضع العمودي فقط في وضع العرض الأفقي لتوجيه النشاط للانتقال إلى تنسيق من لوحتين عند بدء نشاط ثانٍ (راجِع الاتجاه العمودي الثابت).

إعدادات XML

لإنشاء عملية تنفيذ مستندة إلى XML لتضمين النشاط، أكمِل الخطوات التالية:

  1. أنشئ ملف موارد XML يؤدي ما يلي:

    • تحديد الأنشطة التي تشترك في التقسيم
    • تتيح هذه السياسة ضبط خيارات التقسيم.
    • ينشئ عنصرًا نائبًا للحاوية الثانوية للقسم عندما لا يكون المحتوى متاحًا.
    • تحدد الأنشطة التي لا ينبغي أن تكون جزءًا من التقسيم

    مثلاً:

    <!-- 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>
    
  2. إنشاء م إعداد.

    يحلّل مكوّن WindowManager RuleController ملف الإعداد بتنسيق XML ويتيح القواعد للنظام. توفّر مكتبة البدء في Jetpack Initializer ملف XML لـ RuleController عند بدء تشغيل التطبيق، بحيث تسري القواعد عند بدء أي أنشطة.

    لإنشاء مهيئ، قم بما يلي:

    1. أضِف أحدث تبعية لمكتبة Jetpack Startup إلى ملف build.gradle على مستوى الوحدة، على سبيل المثال:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. إنشاء فئة تنفّذ واجهة 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();
        }
      }
      
  3. أنشئ موفّر محتوى لتعريفات القواعد.

    يمكنك إضافة 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 وتضبط إعداداتها قبل طلب الإجراء onCreate() في التطبيق. ونتيجةً لذلك، تسري قواعد التقسيم عند بدء النشاط الرئيسي للتطبيق.

واجهة برمجة تطبيقات WindowManager

يمكنك تضمين النشاط آليًا باستخدام عدد قليل من طلبات البيانات من واجهة برمجة التطبيقات. يمكنك إجراء الطلبات بطريقة onCreate() ضمن فئة فرعية من Application لضمان سريان القواعد قبل بدء أي أنشطة.

لإنشاء تقسيم نشاط آليًا، اتّبِع الخطوات التالية:

  1. إنشاء قاعدة تقسيم:

    1. أنشئ 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
      );
      
    2. أضِف الفلتر إلى مجموعة فلاتر:

      Kotlin

      val filterSet = setOf(splitPairFilter)
      

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      
    3. إنشاء سمات تنسيق للقسمة:

      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: تحدِّد هذه السمة كيفية ترتيب حاويات الأنشطة بالنسبة إلى بعضها البعض، على أنّها الحاوية الأساسية أولاً.
    4. إنشاء 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: يضبط هذا الخيار الحدّ الأدنى للقيمة (بالبكسل الكثافة) التي يجب أن تؤدي بها القيمة الأصغر لسمتَي العرض إلى تفعيل التقسيم بغض النظر عن اتجاه الجهاز.
      • setMaxAspectRatioInPortrait: لضبط الحد الأقصى لنسبة العرض إلى الارتفاع (الارتفاع:العرض) في الاتجاه العمودي الذي يتم عرض تقسيمات الأنشطة له. إذا كانت نسبة العرض إلى الارتفاع للشاشة العمودية تتجاوز الحد الأقصى لنسبة العرض إلى الارتفاع، يتم إيقاف التقسيمات بغض النظر عن عرض الشاشة. ملاحظة: القيمة التلقائية هي 1.4، ما يؤدي إلى شغل الأنشطة في نافذة المهمة بأكملها في الاتجاه العمودي على معظم الأجهزة اللوحية. يمكنك أيضًا الاطّلاع على SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT وsetMaxAspectRatioInLandscape. تكون القيمة التلقائية للوضع الأفقي هي ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary: لضبط كيفية تأثير الانتهاء من جميع الأنشطة في الحاوية الثانوية على الأنشطة في الحاوية الأساسية تشير السمة NEVER إلى أنه يجب ألا ينهي النظام الأنشطة الأساسية عند انتهاء جميع الأنشطة في الحاوية الثانوية (اطّلع على إنهاء الأنشطة).
      • setFinishSecondaryWithPrimary: لضبط كيفية تأثير الانتهاء من جميع الأنشطة في الحاوية الأساسية على الأنشطة في الحاوية الثانوية. تشير السمة ALWAYS إلى أنه يجب أن ينهي النظام دائمًا الأنشطة في الحاوية الثانوية عند انتهاء جميع الأنشطة في الحاوية الأساسية (اطّلع على إنهاء الأنشطة).
      • setClearTop: تحدِّد هذه السياسة ما إذا كانت جميع الأنشطة في الحاوية الثانوية قد اكتملت عند إطلاق نشاط جديد في الحاوية. تشير القيمة "خطأ" إلى أنّ الأنشطة الجديدة يتم تكديسها أعلى الأنشطة المتوفّرة في الحاوية الثانوية.
    5. احصل على نسخة افتراضية من WindowManager RuleController، وأضِف القاعدة:

      Kotlin

      val ruleController = RuleController.getInstance(this)
      ruleController.addRule(splitPairRule)
      

      Java

      RuleController ruleController = RuleController.getInstance(this);
      ruleController.addRule(splitPairRule);
      
  2. أنشئ عنصرًا نائبًا للحاوية الثانوية عندما لا يتوفّر المحتوى:

    1. أنشِئ عنصر ActivityFilter يحدِّد النشاط الذي يشارك معه العنصر النائب تقسيم نافذة المهمة:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )
      

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
      
    2. أضِف الفلتر إلى مجموعة فلاتر:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
      

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
      
    3. إنشاء 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: يحتوي على فلاتر الأنشطة التي تحدِّد وقت تطبيق القاعدة عن طريق تحديد الأنشطة التي يرتبط بها النشاط النائب.
      • Intent: تحدِّد هذه السمة بدء النشاط النائب.
      • setDefaultSplitAttributes: تطبّق سمات التنسيق على القاعدة.
      • setMinWidthDp: لضبط الحد الأدنى لعرض شاشة العرض (بوحدات بكسل مستقلة الكثافة، dp) الذي يسمح بالتقسيم.
      • setMinSmallestWidthDp: لضبط الحدّ الأدنى للقيمة (بالبكسل الكثافة) التي يجب أن تسمح بها القيمة الأصغر لسمتَي العرض، بغض النظر عن اتجاه الجهاز.
      • setMaxAspectRatioInPortrait: لضبط الحد الأقصى لنسبة العرض إلى الارتفاع (الارتفاع:العرض) في الاتجاه العمودي الذي يتم عرض تقسيمات الأنشطة له. ملاحظة: القيمة التلقائية هي 1.4، ما يؤدي إلى ملء نافذة المهمة في الاتجاه العمودي على معظم الأجهزة اللوحية. راجِع أيضًا SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT وsetMaxAspectRatioInLandscape. تكون القيمة التلقائية للوضع الأفقي هي ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder: لضبط كيفية تأثير الانتهاء من نشاط العنصر النائب في الأنشطة في الحاوية الأساسية. يشير "دائمًا" إلى أن النظام يجب أن ينهي دائمًا الأنشطة في الحاوية الأساسية عند انتهاء العنصر النائب (راجِع إنهاء الأنشطة).
      • setSticky: تحدِّد هذه السياسة ما إذا كان نشاط العنصر النائب يظهر في أعلى حزمة الأنشطة على شاشات صغيرة بعد ظهور العنصر النائب للمرة الأولى في تقسيم بحدّ أدنى كافٍ للعرض.
    4. إضافة القاعدة إلى WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)
      

      Java

      ruleController.addRule(splitPlaceholderRule);
      
  3. تحديد الأنشطة التي يجب ألا تكون جزءًا من التقسيم:

    1. يمكنك إنشاء ActivityFilter يحدّد النشاط الذي يجب أن يشغل دائمًا منطقة عرض المهام بالكامل:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )
      

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
      
    2. أضِف الفلتر إلى مجموعة فلاتر:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)
      

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
      
    3. إنشاء ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()
      

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();
      

      ينشئ ActivityRule.Builder القاعدة ويضبطها:

      • expandedActivityFilterSet: يحتوي على فلاتر الأنشطة التي تحدِّد وقت تطبيق القاعدة عن طريق تحديد الأنشطة التي تريد استبعادها من عمليات التقسيم.
      • setAlwaysExpand: لتحديد ما إذا كان يجب أن يملأ النشاط نافذة المهام بالكامل.
    4. إضافة القاعدة إلى WindowManager RuleController:

      Kotlin

      ruleController.addRule(activityRule)
      

      Java

      ruleController.addRule(activityRule);
      

التضمين عبر التطبيقات

في نظام التشغيل Android 13 (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكن للتطبيقات تضمين أنشطة من تطبيقات أخرى. يتيح تضمين النشاط على مستوى عدة تطبيقات أو مع معرّف فريد دمجًا مرئيًا للأنشطة من تطبيقات Android متعدّدة. يعرض النظام نشاطًا للتطبيق المضيف ونشاطًا مضمّنًا من تطبيق آخر على الشاشة جنبًا إلى جنب أو أعلاها أو أسفلها تمامًا كما هو الحال عند تضمين نشاط تطبيق واحد.

على سبيل المثال، يمكن أن يضمِّن تطبيق "الإعدادات" نشاط أداة اختيار الخلفية من تطبيق Animation Picker:

الشكل 14. تطبيق الإعدادات (القائمة على اليمين) مع أداة اختيار الخلفية كنشاط مضمّن (على اليسار)

نموذج الثقة

يمكن لعمليات المضيف التي تتضمّن أنشطة من تطبيقات أخرى إعادة تحديد طريقة عرض الأنشطة المضمّنة، بما في ذلك الحجم والموضع والاقتصاص والشفافية. يمكن للمضيفين الضارين استخدام هذه الإمكانية لتضليل المستخدمين وإنشاء تمويه النقر أو هجمات أخرى لمعالجة واجهة المستخدم.

لمنع إساءة استخدام تضمين النشاط على جميع التطبيقات، يطلب 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 عن طريق تشغيل مهمة signingReport Gradle. ملخص الشهادة هو الملف المرجعي لخوارزمية SHA-256 بدون النقطتَين الفاصلة. لمزيد من المعلومات، يُرجى الاطّلاع على تشغيل تقرير التوقيع ومصادقة العميل.

المضيفون غير الموثوق بهم

للسماح لأي تطبيق بتضمين أنشطة تطبيقك والتحكّم في طريقة عرضه، حدِّد السمة android:allowUntrustedActivityEmbedding في العنصرَين <activity> أو <application> في بيان التطبيق، على سبيل المثال:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

القيمة التلقائية للسمة هي "خطأ"، ما يمنع تضمين النشاط بين التطبيقات.

المصادقة المخصّصة

للحدّ من خطر تضمين النشاط غير الموثوق به، يمكنك إنشاء آلية مصادقة مخصّصة تعمل على التحقق من هوية المضيف. إذا كنت تعرف شهادات المضيف، استخدِم مكتبة 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 WindowManager.

إذا حاول أحد التطبيقات المضيفة تضمين نشاط لم يوافق على التضمين في التطبيقات المختلفة، يشغل النشاط حدود المهمة بالكامل. ونتيجةً لذلك، تحتاج التطبيقات المضيفة إلى معرفة ما إذا كانت الأنشطة المستهدفة تسمح بالتضمين عبر التطبيقات.

إذا بدأ نشاط مضمّن نشاطًا جديدًا في المهمة نفسها ولم يوافق النشاط الجديد على التضمين في تطبيقات متعددة، يشغل النشاط حدود المهمة بالكامل بدلاً من تراكب النشاط في الحاوية المضمّنة.

يمكن للتطبيق المضيف تضمين أنشطته الخاصة بدون قيود ما دام يتم بدء الأنشطة في المهمة نفسها.

أمثلة على التقسيم

التقسيم من النافذة الكاملة

الشكل 15. يبدأ النشاط "أ" النشاط "ب" إلى جانبه.

ليس عليك إعادة الهيكلة. يمكنك تحديد إعدادات التقسيم بشكل ثابت أو في وقت التشغيل ثم طلب Context#startActivity() بدون أي معلَمات إضافية.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

التقسيم تلقائيًا

عندما يتم تصميم الصفحة المقصودة لتطبيق ما لتقسيمها إلى حاويتين على شاشات كبيرة، تكون تجربة المستخدم أفضل عند إنشاء كلا النشاطين وتقديمهما في وقت واحد. ومع ذلك، قد لا يكون المحتوى متاحًا للحاوية الثانوية للقسمة حتى يتفاعل المستخدم مع النشاط في الحاوية الأساسية (على سبيل المثال، يختار المستخدم عنصرًا من قائمة التنقل). يمكن أن يملأ نشاط العنصر النائب الفراغ إلى أن يتم عرض المحتوى في الحاوية الثانوية للقسمة (راجع العناصر النائبة أعلاه).

الشكل 16. تم إنشاء التقسيم عن طريق فتح نشاطين في الوقت نفسه. أحد الأنشطة هو عنصر نائب.

لإنشاء تقسيم باستخدام عنصر نائب، أنشئ عنصرًا نائبًا واربطه بالنشاط الأساسي:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

عندما يتلقّى التطبيق هدفًا، يمكن عرض النشاط المستهدف على أنّه الجزء الثانوي من تقسيم النشاط، مثل طلب عرض شاشة تفاصيل تتضمّن معلومات عن عنصر من قائمة. على الشاشات الصغيرة، يتم عرض التفاصيل في نافذة المهام الكاملة؛ على الأجهزة الأكبر، بجانب القائمة.

الشكل 17. نشاط تفصيلي للروابط المؤدية إلى صفحات في التطبيق يظهر على شاشة صغيرة فقط، ولكن مع نشاط قائمة على شاشة كبيرة

يجب توجيه طلب الإطلاق إلى النشاط الرئيسي، ويجب إطلاق نشاط التفاصيل المستهدفة في تقسيم. يختار النظام تلقائيًا العرض التقديمي الصحيح - المكدس أو جنبًا إلى جنب - بناءً على عرض الشاشة المتاح.

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));
}

قد تكون وجهة الرابط لصفحة في التطبيق هي النشاط الوحيد الذي يجب أن يتوفّر للمستخدم في حزمة التنقّل للخلف، وقد تحتاج إلى تجنُّب رفض نشاط التفاصيل وترك النشاط الرئيسي فقط:

شاشة كبيرة تتضمّن نشاط القائمة ونشاط التفاصيل جنبًا إلى جنب.
          يتعذّر على ميزة &quot;التنقل للخلف&quot; إغلاق النشاط التفصيلي وترك نشاط القائمة
          على الشاشة.

شاشة صغيرة تتضمّن نشاطًا تفصيليًا فقط يتعذّر على ميزة &quot;التنقل للخلف&quot; إغلاق نشاط التفاصيل والكشف عن نشاط القائمة.

بدلاً من ذلك، يمكنك إكمال كلا النشاطَين في الوقت نفسه باستخدام سمة finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

ويمكنك الاطّلاع على سمات الإعداد أدناه.

أنشطة متعددة في الحاويات المقسّمة

يتيح تكديس أنشطة متعددة في حاوية مقسمة للمستخدمين الوصول إلى المحتوى العميق. على سبيل المثال، من خلال تقسيم القائمة مع تفاصيل، قد يحتاج المستخدم إلى الانتقال إلى قسم التفاصيل الفرعية مع إبقاء النشاط الأساسي في مكانه.

الشكل 18. تم فتح النشاط في مكانه في الجزء الثانوي من نافذة المهمة.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

يتم وضع نشاط التفاصيل الفرعية أعلى نشاط التفاصيل، مما يخفيه:

يمكن للمستخدم بعد ذلك العودة إلى مستوى التفاصيل السابق عن طريق الانتقال مرة أخرى عبر المكدس:

الشكل 19. تمت إزالة النشاط من أعلى الحزمة.

يكون تكديس الأنشطة فوق بعضها البعض هو السلوك الافتراضي عند إطلاق الأنشطة من نشاط في نفس الحاوية الثانوية. إنّ الأنشطة التي يتم إطلاقها من الحاوية الأساسية داخل تقسيم نشط تنتهي أيضًا في الحاوية الثانوية أعلى حزمة الأنشطة.

الأنشطة في مهمة جديدة

عندما تبدأ الأنشطة في نافذة تقسيم المهام الأنشطة في مهمة جديدة، تكون المهمة الجديدة منفصلة عن المهمة التي تتضمن التقسيم ويتم عرض النافذة الكاملة. تعرض شاشة "العناصر الأخيرة" مهمتين: المهمة في التقسيم والمهمة الجديدة.

الشكل 20. ابدأ النشاط "ج" في مهمة جديدة من النشاط "ب".

استبدال النشاط

يمكن استبدال الأنشطة في حزمة الحاوية الثانوية، على سبيل المثال، عندما يتمّ استخدام النشاط الأساسي للتنقّل على المستوى الأعلى وكان النشاط الثانوي وجهة محدَّدة. ينبغي أن يبدأ كل تحديد من تنقل المستوى الأعلى نشاطًا جديدًا في الحاوية الثانوية وإزالة النشاط أو الأنشطة التي كانت موجودة سابقًا.

الشكل 21. يحل نشاط التنقل في المستوى الأعلى في اللوحة الأساسية محل أنشطة الوجهة في اللوحة الثانوية.

إذا لم ينهي التطبيق النشاط في الحاوية الثانوية عند تغيير اختيار التنقّل، قد يكون الانتقال للخلف أمرًا مربكًا عند تصغير التقسيم (عندما يكون الجهاز مطويًا). على سبيل المثال، إذا كانت لديك قائمة في الجزء الأساسي والشاشتين A وB مكدسة في الجزء الثانوي، فعندما يطي المستخدم الهاتف، يكون B أعلى من A، و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)));
    }
}

وبدلاً من ذلك، استخدِم النشاط الثانوي نفسه، ومن النشاط الأساسي (القائمة) يرسِل أغراضًا جديدة تفصِل إلى الحالة نفسها مع تشغيل تحديث للحالة أو واجهة المستخدم في الحاوية الثانوية.

تقسيمات متعددة

يمكن أن توفر التطبيقات التنقل العميق متعدد المستويات من خلال إطلاق أنشطة إضافية جانبًا.

عندما يبدأ نشاط في حاوية ثانوية نشاطًا جديدًا في الجانب، يتم إنشاء تقسيم جديد فوق التقسيم الحالي.

الشكل 22. يبدأ النشاط ب النشاط ج إلى الجانب.

تحتوي الحزمة الخلفية على جميع الأنشطة التي تم فتحها مسبقًا، بحيث يمكن للمستخدمين الانتقال إلى قسم A/B بعد الانتهاء من C.

الأنشطة &quot;أ&quot; و&quot;ب&quot; و&quot;ج&quot; في حزمة. يتم تجميع الأنشطة بالترتيب التالي من أعلى إلى أسفل: ج، ب، أ.

لإنشاء قسمة جديدة، ابدأ النشاط الجديد بجانب الحاوية الثانوية الحالية. وضح الإعدادات لكل من قسمي A/B وB/C ونشاط الإطلاق "ج" الذي عادةً ما يكون من "ب":

<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));
    }
}

التفاعل مع التغييرات في حالة التقسيم

يمكن أن تشتمل الأنشطة المختلفة في أحد التطبيقات على عناصر واجهة المستخدم التي تؤدي الوظيفة نفسها؛ على سبيل المثال، عنصر تحكم يفتح نافذة تحتوي على إعدادات الحساب.

الشكل 23. أنشطة مختلفة مع عناصر واجهة مستخدم متطابقة من الناحية الوظيفية.

إذا كان هناك نشاطان يحتويان على عنصر مشترك مشترك بين عنصر واجهة المستخدم في انقسام، سيكون عرض العنصر في كلا النشاطين مكررًا وربما مربكًا.

الشكل 24. عناصر مكرّرة لواجهة المستخدم في تقسيم النشاط

لمعرفة متى تكون الأنشطة مقسّمة، تحقَّق من مسار 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>

إنهاء الأنشطة

يمكن للمستخدمين إنهاء الأنشطة على أي من جانبي الشاشة عن طريق التمرير سريعًا من حافة الشاشة:

الشكل 25. إيماءة التمرير السريع لإنهاء النشاط "ب".
الشكل 26. إيماءة التمرير السريع لإنهاء النشاط "أ"

إذا تم إعداد الجهاز لاستخدام زر الرجوع بدلاً من التنقل بالإيماءات، فيتم إرسال الإدخال إلى النشاط محل التركيز، وهو النشاط الذي تم لمسه أو تشغيله مؤخرًا.

يعتمد تأثير إنهاء جميع الأنشطة في الحاوية على الحاوية المقابلة على تهيئة التقسيم.

سمات الإعداد

يمكنك تحديد سمات قاعدة زوج التقسيم لضبط مدى تأثير إنهاء جميع الأنشطة على جانب واحد من التقسيم على الأنشطة على الجانب الآخر من القسمة. السمات هي:

  • 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>

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;أ&quot;، وترك &quot;ب&quot;
          لتشغل النافذة بأكملها.

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت المجموعة &quot;ب&quot;، وتركت الحرف A
          لتشغل النافذة بأكملها.

إنهاء الأنشطة معًا

إنهاء الأنشطة في الحاوية الأساسية تلقائيًا عند انتهاء جميع الأنشطة في الحاوية الثانوية:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;ب&quot;، ما يؤدي أيضًا إلى
          الانتهاء من الصيغة &quot;أ&quot;، ما يؤدي إلى ترك نافذة المهمة فارغة.

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;أ&quot;، مع ترك &quot;ب&quot; وحدها في نافذة المهمة.

إنهاء الأنشطة في الحاوية الثانوية تلقائيًا عند انتهاء جميع الأنشطة في الحاوية الأساسية:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;أ&quot;، ما يؤدي أيضًا إلى
          الانتهاء من &quot;ب&quot;، ما سيؤدي إلى ترك نافذة المهمة فارغة.

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت المجموعة &quot;ب&quot;، مع ترك الحرف &quot;أ&quot; وحده
          في نافذة المهمة.

إنهاء الأنشطة معًا عند انتهاء جميع الأنشطة إما في الحاوية الأساسية أو الثانوية:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;أ&quot;، ما يؤدي أيضًا إلى
          الانتهاء من &quot;ب&quot;، ما سيؤدي إلى ترك نافذة المهمة فارغة.

تقسيم يحتوي على النشاطَين &quot;أ&quot; و&quot;ب&quot;. اكتملت عملية &quot;ب&quot;، ما يؤدي أيضًا إلى
          الانتهاء من الصيغة &quot;أ&quot;، ما يؤدي إلى ترك نافذة المهمة فارغة.

إكمال أنشطة متعددة في الحاويات

إذا تم تكديس أنشطة متعددة في حاوية مقسّمة، لن يؤدي إكمال نشاط في أسفل الحزمة إلى إنهاء الأنشطة تلقائيًا في الأعلى.

على سبيل المثال، إذا كان هناك نشاطان في الحاوية الثانوية، تكون ج:

يتم تكديس حزمة الأنشطة الثانوية التي تحتوي على النشاط &quot;ج&quot; المكدس فوق النشاط &quot;ب&quot;
          فوق حزمة الأنشطة التكميلية التي تحتوي على النشاط
          &quot;أ&quot;.

ويتم تحديد تهيئة التقسيم من خلال ضبط النشاطين "أ" و"ب":

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

الانتهاء من النشاط العلوي يحتفظ بالتقسيم.

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في المرحلة الثانوية، مع تكديس C على رأس &quot;ب&quot;. ينتهي العنصر C، مع ترك A وB في قسم النشاط.

لا يؤدي الانتهاء من النشاط السفلي (الجذر) للحاوية الثانوية إلى إزالة الأنشطة الموجودة فوقها؛ وبالتالي، يحتفظ أيضًا بالتقسيم.

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في المرحلة الثانوية، مع تكديس C على رأس &quot;ب&quot;. تنتهي المجموعة &quot;ب&quot; مع ترك &quot;أ&quot; و&quot;ج&quot; في قسم النشاط.

يتم أيضًا تنفيذ أي قواعد إضافية لإنهاء الأنشطة معًا، مثل إنهاء النشاط الثانوي مع الأساسي:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في حاوية ثانوية، مع تكديس &quot;ج&quot; على رأس الحاوية &quot;ب&quot; خطو حرف &quot;أ&quot; ونهاية &quot;ب&quot; و&quot;ج&quot; أيضًا

وعند ضبط التقسيم على أن يتم الانتهاء من الأساسيات الأساسية والثانوية معًا:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في المرحلة الثانوية، مع تكديس C على رأس &quot;ب&quot;. ينتهي العنصر C، مع ترك A وB في قسم النشاط.

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في المرحلة الثانوية، مع تكديس C على رأس &quot;ب&quot;. تنتهي المجموعة &quot;ب&quot; مع ترك &quot;أ&quot; و&quot;ج&quot; في قسم النشاط.

التقسيم مع النشاط &quot;أ&quot; في الحاوية الأساسية والأنشطة &quot;ب&quot; و&quot;ج&quot; في المرحلة الثانوية، مع تكديس C على رأس &quot;ب&quot;. الانتهاء من &quot;أ&quot; و&quot;ج&quot; أيضًا

تغيير خصائص التقسيم عند وقت التشغيل

لا يمكن تغيير خصائص التقسيم النشط والمرئي حاليًا. يؤثر تغيير قواعد التقسيم على عمليات إطلاق الأنشطة الإضافية والحاويات الجديدة، ولكن ليس على التقسيمات الحالية والنشطة.

لتغيير خصائص التقسيمات النشطة، يمكنك إنهاء النشاط الجانبي أو الأنشطة في القسمة وبدء التنفيذ مرة أخرى باستخدام إعدادات جديدة.

استخراج نشاط من تقسيم إلى نافذة كاملة

يجب إنشاء عملية ضبط جديدة تعرض النافذة الكاملة للنشاط الجانبي، ثم إعادة تشغيل النشاط بهدف يحلّ المشكلة نفسها في المثيل نفسه.

التأكّد من توفُّر إمكانية التقسيم في وقت التشغيل

تتوفّر ميزة تضمين الأنشطة على أجهزة Android 12L (المستوى 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.
}

وفي حال لم تكن التقسيمات متاحة، يتم بدء الأنشطة في أعلى حزمة الأنشطة (باتّباع نموذج تضمين عدم النشاط).

منع تجاوز النظام

يمكن للشركات المصنّعة لأجهزة Android (المصنّعين الأصليين للأجهزة أو المصنّعين الأصليين للأجهزة) تنفيذ تضمين الأنشطة كوظيفة لنظام الجهاز. يحدّد النظام قواعد التقسيم للتطبيقات المتعددة الأنشطة، ما يؤدي إلى تجاوز سلوك إنشاء النوافذ. يؤدي إلغاء النظام إلى فرض وضع تضمين النشاط الذي يحدّده النظام على التطبيقات المتعددة الأنشطة.

يمكن أن يؤدي تضمين أنشطة النظام إلى تحسين عرض التطبيق من خلال تنسيقات متعددة الأجزاء، مثل تفاصيل القائمة، بدون أي تغييرات في التطبيق. ومع ذلك، قد يؤدي تضمين نشاط النظام أيضًا إلى تخطيطات غير صحيحة للتطبيق أو أخطاء أو تعارضات مع تضمين الأنشطة التي ينفّذها التطبيق.

يمكن لتطبيقك منع تضمين نشاط النظام أو السماح به من خلال ضبط خاصية في ملف بيان التطبيق، على سبيل المثال:

<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>

يتم تحديد اسم الخاصية في الكائن WindowProperties في Jetpack WindowManager. يمكنك ضبط القيمة على false إذا كان تطبيقك ينفِّذ تضمين الأنشطة، أو إذا كنت تريد منع النظام من تطبيق قواعد تضمين الأنشطة على تطبيقك. اضبط القيمة على true للسماح للنظام بتطبيق تضمين النشاط المحدّد من خلال النظام على تطبيقك.

القيود والقيود والتنبيهات

  • يمكن فقط للتطبيق المضيف للمهمة، الذي تم تحديده كمالك للنشاط الجذري في المهمة، تنظيم الأنشطة الأخرى وتضمينها في المهمة. وإذا كانت الأنشطة التي تتيح التضمين والتقسيم تعمل في مهمة تنتمي إلى تطبيق مختلف، لن يعمل التضمين والتقسيم مع تلك الأنشطة.
  • يمكن تنظيم الأنشطة في مهمة واحدة فقط. يؤدي بدء نشاط في مهمة جديدة دائمًا إلى وضعه في نافذة موسعة جديدة خارج أي تقسيمات حالية.
  • يمكن تنظيم الأنشطة في نفس العملية فقط وتقسيمها. لا تعرض ميزة معاودة الاتصال SplitInfo إلا الأنشطة التي تنتمي إلى العملية نفسها، بما أنّه لا تتوفّر طريقة لمعرفة الأنشطة في عمليات مختلفة.
  • لا ينطبق كل زوج أو قاعدة نشاط فردية إلا على عمليات إطلاق النشاط التي تحدث بعد تسجيل القاعدة. ولا توجد طريقة حاليًا لتعديل التقسيمات الحالية أو خصائصها المرئية.
  • يجب أن تتطابق تهيئة فلتر الزوج المقسّم مع الأغراض المستخدمة عند إطلاق الأنشطة بالكامل. وتحدث المطابقة عند بدء نشاط جديد من عملية التطبيق، لذلك قد لا يعرف أسماء المكوّنات التي يتم حلّها لاحقًا في عملية النظام عند استخدام الأهداف الضمنية. وإذا لم يكن اسم المكوّن معروفًا في وقت الإطلاق، يمكن استخدام حرف بدل بدلاً منه ("*/*") ويمكن تطبيق الفلترة استنادًا إلى الإجراء المقصود.
  • ولا تتوفّر حاليًا طريقة لنقل الأنشطة بين الحاويات أو داخل وخارج التقسيمات بعد إنشائها. لا يتم إنشاء التقسيمات إلا بواسطة مكتبة WindowManager عند إطلاق أنشطة جديدة ذات قواعد مطابقة، ويتم إتلاف الأقسام عند الانتهاء من آخر نشاط في حاوية مقسمة.
  • يمكن إعادة تشغيل الأنشطة بعد تغيير الإعدادات، لذلك عند إنشاء عملية تقسيم أو إزالتها وتغيير حدود النشاط، يمكن أن يخضع النشاط للتدمير الكامل للمثيلات السابقة وإنشاء المثيل السابق. نتيجة لذلك، يجب أن يكون مطورو التطبيقات حريصين بشأن أشياء مثل إطلاق أنشطة جديدة من عمليات معاودة الاتصال في مراحل النشاط.
  • يجب أن تتضمّن الأجهزة واجهة إضافات النوافذ لإتاحة تضمين النشاط. تشتمل تقريبًا جميع الأجهزة ذات الشاشات الكبيرة التي تعمل بنظام التشغيل Android 12L (المستوى 32 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث على الواجهة. ومع ذلك، لا تتضمّن بعض الأجهزة ذات الشاشات الكبيرة غير قادرة على تشغيل أنشطة متعدّدة واجهة إضافات النوافذ. إذا كان الجهاز ذو الشاشة الكبيرة لا يتيح وضع النوافذ المتعددة، قد لا يتيح تضمين النشاط.

مراجع إضافية