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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

التنقّل الخلفي

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

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

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

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

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

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

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

سمات التقسيم

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

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

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

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

بالنسبة إلى القواعد التي تم إنشاؤها باستخدام واجهات برمجة تطبيقات WindowManager API، يمكنك إنشاء كائن 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، B، A.

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

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

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

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

الشكل 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>
    

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

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

إعدادات 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: لضبط الحد الأدنى لعرض الشاشة (بوحدات بكسل مستقلة الكثافة، بكسل مستقل الكثافة) يؤدي إلى تفعيل التقسيم.
      • setMinSmallestWidthDp: لضبط الحدّ الأدنى للقيمة (بوحدة بكسل مستقلة الكثافة) التي يجب أن تستوفيها القيم الأصغر من سمتَي العرض من أجل تفعيل التقسيم بغض النظر عن اتجاه الجهاز.
      • setMaxAspectRatioInPortrait: لضبط الحد الأقصى لنسبة العرض إلى الارتفاع (height:width) في الاتجاه العمودي الذي يتم به عرض تقسيمات الأنشطة. إذا تجاوزت نسبة العرض إلى الارتفاع لشاشة عمودية الحد الأقصى لنسبة العرض إلى الارتفاع، يتم إيقاف التقسيمات بغض النظر عن عرض الشاشة. ملاحظة: القيمة التلقائية هي 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: لضبط الحد الأدنى لعرض العرض (بوحدات بكسل مستقلة الكثافة، وحدة بكسل مستقلة الكثافة) الذي يسمح بالتقسيم.
      • setMinSmallestWidthDp: لضبط الحدّ الأدنى للقيمة (بوحدة بكسل مستقلة الكثافة) التي يجب أن تحتويها الأصغر من سمتَي العرض للسماح بالتقسيم بغض النظر عن اتجاه الجهاز.
      • setMaxAspectRatioInPortrait: لضبط الحد الأقصى لنسبة العرض إلى الارتفاع (height:width) في الاتجاه العمودي الذي يتم به عرض تقسيمات الأنشطة. ملاحظة: القيمة التلقائية هي 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 المتعدّدة. يعرض النظام نشاطًا للتطبيق المضيف ونشاطًا مضمّنًا من تطبيق آخر على الشاشة جنبًا إلى جنب أو أعلى وأسفل، كما هو الحال في تضمين نشاط التطبيق الواحد.

على سبيل المثال، يمكن لتطبيق "الإعدادات" تضمين نشاط أداة اختيار الخلفية من تطبيق Background 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));
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

الشكل 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;. انتهت النقطة A، وتركت B لتشغل النافذة بأكملها.

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

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

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

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

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

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

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

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

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

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

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

<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;. اكتملت العملية B، ما يؤدي أيضًا إلى إنهاء A، ما يؤدي إلى ترك نافذة المهمة فارغة.

إنهاء أنشطة متعددة في حاويات

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

تغيير خصائص التقسيم في وقت التشغيل

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

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

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

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

التحقّق من إمكانية التقسيم في وقت التشغيل

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

إنّ تضمين أنشطة النظام يمكن أن يحسّن من عرض التطبيق من خلال تنسيقات متعددة الأجزاء، مثل 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>

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

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

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

مصادر إضافية