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

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

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

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

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

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

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

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

نافذة المهمة المقسّمة

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

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

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

الشكل 2. نشاطان بجانب بعضهما البعض وآخر فوقهما

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

الشكل 3. يبدأ النشاط (أ) النشاط (ب) على الجانب.

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

  • إلى جانب نشاط آخر في أعلى الشاشة:

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

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

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

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

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

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

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

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

للتنقّل بالإيماءات:

  • ‫Android 14 (المستوى 34 لواجهة برمجة التطبيقات) والإصدارات الأقدم: يتم إرسال حدث الرجوع إلى النشاط الذي تم فيه تنفيذ الإيماءة. عندما يمرّر المستخدمون سريعًا من الجانب الأيسر من الشاشة، يتم إرسال حدث الرجوع إلى النشاط في اللوحة اليمنى من النافذة المقسّمة. عندما يمرّر المستخدمون سريعًا من الجانب الأيسر من الشاشة، يتم إرسال حدث الرجوع إلى النشاط في اللوحة اليمنى.

  • ‫Android 15 (المستوى 35 لواجهة برمجة التطبيقات) والإصدارات الأحدث

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

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

التصميم المتعدد اللوحات

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

اتجاه التقسيم

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

يمكنك تحديد اتجاه التقسيم باستخدام آلة حاسبة SplitController SplitAttributes. تحسب الآلة الحاسبة قيمة SplitAttributes SplitRule النشط.

استخدِم الآلة الحاسبة لتقسيم الحاوية الرئيسية في اتجاهات مختلفة لحالات الأجهزة المختلفة، على سبيل المثال:

Kotlin

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val parentConfiguration = params.parentConfiguration
        val builder = SplitAttributes.Builder()
        return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build()
        } else {
            // Fallback to expand the secondary container.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        Configuration parentConfiguration = params.getParentConfiguration();
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build();
        } else {
            // Fallback to expand the secondary container.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

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

Kotlin

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val tag = params.splitRuleTag
        val parentWindowMetrics = params.parentWindowMetrics
        val parentConfiguration = params.parentConfiguration
        val foldingFeatures =
            params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>()
        val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null
        val builder = SplitAttributes.Builder()
        builder.setSplitType(SPLIT_TYPE_HINGE)
        return@setSplitAttributesCalculator if (feature?.isSeparating == true) {
            // Horizontal split for tabletop posture.
            builder
                .setSplitType(SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) {
                        SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                    } else {
                        SplitAttributes.LayoutDirection.LOCALE
                    }
                )
                .build()
        } else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else {
            // No split for tall displays.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        String tag = params.getSplitRuleTag();
        WindowMetrics parentWindowMetrics = params.getParentWindowMetrics();
        Configuration parentConfiguration = params.getParentConfiguration();
        List<FoldingFeature> foldingFeatures =
            params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter(
                    item -> item instanceof FoldingFeature)
                .map(item -> (FoldingFeature) item)
                .collect(Collectors.toList());
        FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null;
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        builder.setSplitType(SplitType.SPLIT_TYPE_HINGE);
        if (feature != null && feature.isSeparating()) {
            // Horizontal slit for tabletop posture.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL
                        ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                        : SplitAttributes.LayoutDirection.LOCALE)
                .build();
        }
        else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else {
            // No split for tall displays.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

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

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

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

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

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

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

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

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

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

يمكن ترتيب الأنشطة فوق بعضها لأنّ WindowManager يرتب الأنشطة حسب ترتيب z في اللوحة الثانوية فوق الأنشطة في اللوحة الأساسية.

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

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

تقسيم النشاط الذي يحتوي على الأنشطة A وB وC مع وضع C فوق B

ما يؤدي إلى ترتيب الأنشطة التالي حسب المحور z في المهمة نفسها:

مجموعة النشاطات الثانوية التي تحتوي على النشاط C مكدّسًا فوق النشاط B
          يتم ترتيب مجموعة الأنشطة الثانوية فوق مجموعة الأنشطة الأساسية التي تحتوي على النشاط A.

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

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

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

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

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

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

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

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

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

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

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

الاتجاه الرأسي الثابت

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

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

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

الشكل 13. يبدأ النشاط A في الوضع العمودي الثابت النشاط B على الجانب.

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

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

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

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

يُرجى اتّباع الخطوات التالية:

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

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

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

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

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

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    في الإصدار 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 ويتيح القواعد للنظام. توفّر مكتبة Startup في 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 API

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

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

      Kotlin

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

      Java

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

    6. أنشئ عنصرًا نائبًا للحاوية الثانوية عندما لا يتوفّر محتوى:

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

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );

    8. أضِف الفلتر إلى مجموعة فلاتر:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);

    9. إنشاء 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(this, 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(): تضبط هذه السمة الحد الأدنى للقيمة (بوحدات dp) التي يجب أن يبلغها أصغر بُعدَين للعرض للسماح بالتقسيم بغض النظر عن اتجاه الجهاز.
      • setMaxAspectRatioInPortrait(): تضبط هذه السمة الحد الأقصى لنسبة العرض إلى الارتفاع (الارتفاع:العرض) في الوضع العمودي الذي يتم عرض تقسيمات النشاط فيه. ملاحظة: القيمة التلقائية هي 1.4، ما يؤدي إلى ملء الأنشطة نافذة المهام في الوضع العمودي على معظم الأجهزة اللوحية. يمكنك الاطّلاع أيضًا على SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT و setMaxAspectRatioInLandscape(). القيمة التلقائية للوضع الأفقي هي ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): تحدّد هذه السمة الطريقة التي يؤثّر بها إنهاء نشاط العنصر النائب في الأنشطة في الحاوية الأساسية. يشير ALWAYS إلى أنّه على النظام دائمًا إنهاء الأنشطة في الحاوية الأساسية عند انتهاء العنصر النائب (راجِع إنهاء الأنشطة).
      • setSticky(): تحدّد ما إذا كان النشاط النائب يظهر في أعلى حزمة الأنشطة على الشاشات الصغيرة بعد أن يظهر النائب لأول مرة في شاشة مقسّمة بعرض أدنى كافٍ.
    10. أضِف القاعدة إلى WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);

  2. حدِّد الأنشطة التي يجب ألا تكون جزءًا من عملية التقسيم أبدًا:

    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 متعددة. يعرض النظام نشاطًا من التطبيق المضيف ونشاطًا مضمّنًا من تطبيق آخر على الشاشة جنبًا إلى جنب أو في الأعلى والأسفل، تمامًا كما هو الحال في تضمين نشاط تطبيق واحد.

على سبيل المثال، يمكن أن يضمّن تطبيق &quot;الإعدادات&quot; نشاط اختيار الخلفية من تطبيق WallpaperPicker:

الشكل 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 من خلال تنفيذ مهمة Gradle signingReport. ملخّص الشهادة هو الملف المرجعي لشهادة 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(context).isActivityEmbedded(activity);
}

قيود الحد الأدنى للحجم

يطبّق نظام التشغيل Android الحد الأدنى للارتفاع والعرض المحدّدَين في بيان التطبيق ضمن العنصر <layout> على الأنشطة المضمّنة. إذا لم يحدّد التطبيق الحد الأدنى للارتفاع والعرض، يتم تطبيق القيم التلقائية للنظام (sw220dp).

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

<activity-alias>

لكي يعمل تضمين النشاط الموثوق أو غير الموثوق مع العنصر <activity-alias>، يجب تطبيق android:knownActivityEmbeddingCerts أو android:allowUntrustedActivityEmbedding على النشاط المستهدف بدلاً من الاسم المستعار. تستند السياسة التي تتحقّق من الأمان على خادم النظام إلى العلامات التي تم ضبطها على الهدف، وليس الاسم المستعار.

التطبيق المضيف

تنفِّذ التطبيقات المضيفة عملية تضمين الأنشطة على مستوى التطبيقات بالطريقة نفسها التي تنفِّذ بها عملية تضمين الأنشطة داخل تطبيق واحد. تحدّد العناصر SplitPairRule وSplitPairFilter أو ActivityRule وActivityFilter الأنشطة المضمّنة وتقسيمات نافذة المهام. يتم تحديد قواعد التقسيم بشكل ثابت في XML أو في وقت التشغيل باستخدام طلبات Jetpack WindowManager API.

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

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

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

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

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

الشكل 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?) {
    super.onCreate(savedInstanceState)
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(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 : AppCompatActivity() {
    fun onOpenSubdetail() {
        startActivity(Intent(this, SubdetailActivity::class.java))
    }
}

Java

public class DetailActivity  extends AppCompatActivity {
    void onOpenSubdetail() {
        startActivity(new Intent(this, SubdetailActivity.class));
    }
}

يتم وضع نشاط التفاصيل الفرعية فوق نشاط التفاصيل، ما يؤدي إلى إخفائه:

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

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

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

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

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

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

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

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

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

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

يجب إزالة الشاشة (أ) من سجلّ الرجوع في مثل هذه الحالات.

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

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

inner class MenuActivity : AppCompatActivity() {
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity extends AppCompatActivity{
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

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

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

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

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

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

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

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

لإنشاء تقسيم جديد، شغِّل النشاط الجديد بجانب الحاوية الثانوية الحالية. عليك تحديد إعدادات كل من تقسيمَي A/B وB/C وتشغيل النشاط C بشكل عادي من B:

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

Kotlin

class B : AppCompatActivity() {
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B extends AppCompatActivity{
    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 {
    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) {
    super.onCreate(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. إيماءة التمرير السريع لإنهاء النشاط B
الشكل 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>

تقسيم يحتوي على النشاطَين A وB تم إنهاء A، ما يتيح لـ B أن يشغل النافذة بأكملها.

تقسيم يحتوي على النشاطَين A وB تم إغلاق النافذة B، وبقيت النافذة A تشغل النافذة بأكملها.

إكمال الأنشطة معًا

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

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

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

تقسيم يحتوي على النشاطَين A وB تم الانتهاء من A، وبقي B وحده
          في نافذة المهمة.

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

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

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

تقسيم يحتوي على النشاطَين A وB تم الانتهاء من B، وبقيت A وحدها
          في نافذة المهام.

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

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

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

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

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

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

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

مجموعة الأنشطة الثانوية التي تحتوي على النشاط C المكدّس فوق B
          مكدّسة فوق مجموعة الأنشطة الأساسية التي تحتوي على النشاط
          A.

ويتم تحديد إعدادات الانقسام من خلال إعدادات النشاطَين (أ) و(ب):

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

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

تقسيم مع النشاط A في الحاوية الأساسية والنشاطين B وC في الحاوية الثانوية، مع وضع C فوق B ينهي C النشاط، ويبقى A وB في
          النشاط المقسّم.

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

تقسيم مع النشاط A في الحاوية الأساسية والنشاطين B وC في الحاوية الثانوية، مع وضع C فوق B ينتهي B، ويبقى A وC في النشاط المنقسم.

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

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

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

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

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

تقسيم مع النشاط A في الحاوية الأساسية والنشاطين B وC في الحاوية الثانوية، مع وضع C فوق B ينهي C النشاط، ويبقى A وB في
          النشاط المقسّم.

تقسيم مع النشاط A في الحاوية الأساسية والنشاطين B وC في الحاوية الثانوية، مع وضع C فوق B ينتهي B، ويبقى A وC في النشاط المنقسم.

تقسيم مع النشاط A في الحاوية الأساسية والنشاطين B وC في الحاوية الثانوية، مع وضع C فوق B ينتهي تنفيذ A، ما يؤدي أيضًا إلى إنهاء تنفيذ B وC.

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

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

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

خصائص التقسيم الديناميكي

يتيح الإصدار 15 من نظام التشغيل Android (المستوى 35 لواجهة برمجة التطبيقات) والإصدارات الأحدث المتوافقة مع الإصدار 1.4 من Jetpack WindowManager والإصدارات الأحدث ميزات ديناميكية تتيح إمكانية ضبط تقسيمات تضمين الأنشطة، بما في ذلك:

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

توسيع اللوحة

تتيح ميزة توسيع اللوحة للمستخدمين ضبط مقدار مساحة الشاشة المخصّصة للنشاطَين في تصميم ذي لوحتَين.

لتخصيص مظهر فاصل النافذة وضبط النطاق القابل للسحب للفاصل، اتّبِع الخطوات التالية:

  1. إنشاء مثيل من DividerAttributes

  2. خصِّص سمات الفاصل باتّباع الخطوات التالية:

    • color: لون فاصل اللوحة القابلة للسحب.

    • widthDp: تمثّل هذه السمة عرض فاصل اللوحة القابلة للسحب. اضبط القيمة على WIDTH_SYSTEM_DEFAULT للسماح للنظام بتحديد عرض الفاصل.

    • نطاق السحب: الحد الأدنى للنسبة المئوية التي يمكن أن يشغلها أي من اللوحين على الشاشة يمكن أن تتراوح بين 0.33 و0.66. اضبط القيمة على DRAG_RANGE_SYSTEM_DEFAULT للسماح للنظام بتحديد نطاق السحب.

    Kotlin

    val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
    
    if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
        splitAttributesBuilder.setDividerAttributes(
            DividerAttributes.DraggableDividerAttributes.Builder()
                .setColor(getColor(R.color.divider_color))
                .setWidthDp(4)
                .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
                .build()
        )
    }
    val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

    Java

    SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);
    
    if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
        splitAttributesBuilder.setDividerAttributes(
          new DividerAttributes.DraggableDividerAttributes.Builder()
            .setColor(ContextCompat.getColor(this, R.color.divider_color))
            .setWidthDp(4)
            .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
            .build()
        );
    }
    SplitAttributes _splitAttributes = splitAttributesBuilder.build();

تثبيت مجموعة النشاطات

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

لتفعيل تثبيت حزمة الأنشطة في تطبيقك، اتّبِع الخطوات التالية:

  1. أضِف زرًا إلى ملف التنسيق للنشاط الذي تريد تثبيته، على سبيل المثال، نشاط التفاصيل لتنسيق قائمة التفاصيل:

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. في طريقة onCreate() الخاصة بالنشاط، اضبط أداة معالجة النقر على الزر:

    Kotlin

    val pinButton: Button = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext)
            .pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) -> {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext())
            .pinTopActivityStack(getTaskId(), pinSplitRule);
    });

تعتيم مربّع الحوار بملء الشاشة

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

في الإصدار 1.4 من WindowManager والإصدارات الأحدث، يتم تعتيم نافذة التطبيق بأكملها تلقائيًا عند فتح مربّع حوار (راجِع EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

لتعتيم الحاوية الخاصة بالنشاط الذي فتح مربع الحوار فقط، استخدِم EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

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

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

التحقّق من توفّر ميزة التقسيم في وقت التشغيل

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

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

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

<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 WindowManager في Jetpack. اضبط القيمة على false إذا كان تطبيقك ينفّذ ميزة تضمين الأنشطة، أو إذا كنت تريد منع النظام من تطبيق قواعد تضمين الأنشطة على تطبيقك. اضبط القيمة على true للسماح للنظام بتطبيق ميزة تضمين الأنشطة التي يحدّدها النظام على تطبيقك.

القيود والأحكام والتنبيهات

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

مراجع إضافية