جاسازی فعالیت

با جاسازی فعالیت، برنامه‌ها در دستگاه‌های دارای صفحه‌نمایش بزرگ بهینه‌سازی می‌شوند و پنجره تکلیف برنامه بین دو فعالیت یا دو نمونه از یک فعالیت تقسیم می‌شود.

شکل ۱. برنامه «تنظیمات» با فعالیت‌ها در کنار هم.

اگر برنامه شما از چندین فعالیت تشکیل شده است، جاسازی فعالیت به شما امکان می‌دهد تجربه کاربری بهتری در رایانه‌های لوحی، دستگاه‌های تاشو، و دستگاه‌های ChromeOS ارائه دهید.

برای جاسازی فعالیت نیازی به بازسازی کد نیست. با ایجاد فایل پیکربندی XML یا با انجام فراخوانی‌های API Jetpack WindowManager، تعیین می‌کنید که برنامه شما فعالیت‌هایش را چگونه نمایش دهد—پهلو به پهلو یا روی هم.

پشتیبانی از صفحه‌نمایش‌های کوچک به‌طور خودکار حفظ می‌شود. وقتی برنامه شما در دستگاهی با صفحه‌نمایش کوچک باشد، فعالیت‌ها روی هم چیده می‌شوند. در صفحه‌های بزرگ، فعالیت‌ها پهلوبه‌پهلو نمایش داده می‌شوند. سیستم براساس پیکربندی‌ای که ایجاد کرده‌اید ارائه را تعیین می‌کند—به منطق شاخه‌ای نیاز نیست.

جاسازی فعالیت از تغییرات جهت دستگاه پشتیبانی می‌کند و به‌طور یکپارچه در دستگاه‌های تاشو کار می‌کند، فعالیت‌ها را با تا شدن و باز شدن دستگاه روی هم می‌چیند و از روی هم برمی‌دارد.

جاسازی فعالیت در اکثر دستگاه‌های دارای صفحه‌نمایش بزرگ که Android 12L (سطح میانای برنامه‌سازی کاربردی ۳۲) و بالاتر را اجرا می‌کنند پشتیبانی می‌شود.

پنجره تکلیف دونیمه

جاسازی فعالیت پنجره وظیفه برنامه را به دو محتوی تقسیم می‌کند: اصلی و فرعی. ظرف‌ها فعالیت‌هایی را که از فعالیت اصلی یا از فعالیت‌های دیگری که ازقبل در ظرف‌ها هستند راه‌اندازی شده‌اند دربرمی‌گیرند.

فعالیت‌ها با راه‌اندازی در محتوی ثانویه چیده می‌شوند و محتوی ثانویه در صفحه‌های کوچک روی محتوی اصلی چیده می‌شود، بنابراین چیدن فعالیت‌ها و پیمایش به عقب با ترتیب فعالیت‌هایی که ازقبل در برنامه شما ساخته شده است سازگار است.

جاسازی فعالیت به شما امکان می‌دهد فعالیت‌ها را به روش‌های مختلف نمایش دهید. برنامه شما می‌تواند پنجره تکلیف را با راه‌اندازی هم‌زمان دو فعالیت در کنار هم یا یکی بالای دیگری تقسیم کند:

شکل ۲. دو فعالیت در کنار هم و یکی بالای دیگری.

فعالیتی که کل پنجره تکلیف را اشغال کرده است می‌تواند با راه‌اندازی فعالیت جدید در کنار آن، صفحه دونیمه ایجاد کند:

شکل ۳. فعالیت A فعالیت B را در کنار شروع می‌کند.

فعالیت‌هایی که ازقبل در حالت دونیمه هستند و پنجره تکلیف را هم‌رسانی می‌کنند می‌توانند فعالیت‌های دیگر را به روش‌های زیر راه‌اندازی کنند:

  • در کنار و بالای فعالیت دیگر:

    شکل ۴. فعالیت A فعالیت C را در کنار فعالیت B شروع می‌کند.
  • به‌سمت کنار، و تقسیم را به‌سمت کنار جابه‌جا کنید، فعالیت اصلی قبلی را پنهان کنید:

    شکل ۵. «فعالیت B» فعالیت C را در کنار شروع می‌کند و تقسیم را به‌سمت کنار می‌برد.
  • فعالیتی را در همان مکان در بالا راه‌اندازی کنید؛ یعنی در همان پشته فعالیت:

    شکل ۶. فعالیت B فعالیت C را بدون پرچم‌های هدف اضافی شروع می‌کند.
  • راه‌اندازی فعالیت تمام‌پنجره در همان تکلیف:

    شکل ۷. فعالیت A یا فعالیت B فعالیت C را شروع می‌کند که پنجره تکلیف را پر می‌کند.

پیمایش به عقب

انواع مختلف برنامه‌ها می‌توانند قوانین مختلفی برای پیمایش به عقب در حالت پنجره تقسیم صفحه داشته باشند که این قوانین به وابستگی‌های بین فعالیت‌ها یا نحوه راه‌اندازی رویداد برگشت توسط کاربران بستگی دارد، برای مثال:

  • با هم رفتن: اگر فعالیت‌ها مرتبط باشند و یکی بدون دیگری نباید نشان داده شود، پیمایش به عقب می‌تواند طوری پیکربندی شود که هر دو را تمام کند.
  • تنها رفتن: اگر فعالیت‌ها کاملاً مستقل باشند، پیمایش به عقب در یک فعالیت بر وضعیت فعالیت دیگر در پنجره تکلیف تأثیر نمی‌گذارد.

وقتی از پیمایش با دکمه استفاده می‌کنید، رویداد برگشت به آخرین فعالیت کانونی‌شده ارسال می‌شود.

برای پیمایش اشاره‌ای:

  • ‫Android 14 (سطح API 34) و پایین‌تر — رویداد برگشت به فعالیتی که اشاره در آن رخ داده است ارسال می‌شود. وقتی کاربران از سمت چپ صفحه تند می‌کشند، رویداد برگشت به فعالیت در قاب سمت راست پنجره دونیمه ارسال می‌شود. وقتی کاربران از سمت راست صفحه تند می‌کشند، رویداد برگشت به فعالیت در قاب سمت راست ارسال می‌شود.

  • ‫Android 15 (سطح میانای برنامه‌سازی کاربردی ۳۵) و بالاتر

    • هنگام کار با چند فعالیت از یک برنامه، اشاره فعالیت بالایی را بدون توجه به جهت تند کشیدن تکمیل می‌کند و تجربه‌ای یکپارچه‌تر ارائه می‌دهد.

    • در سناریوهایی که شامل دو فعالیت از برنامه‌های مختلف (روپوش) می‌شود، رویداد بازگشت به آخرین فعالیت در کانون هدایت می‌شود و با عملکرد پیمایش دکمه‌ای هماهنگ است.

چیدمان چندپانلی

‫Jetpack WindowManager به شما امکان می‌دهد چیدمان چندپانه‌ای با جاسازی فعالیت در دستگاه‌های دارای صفحه‌نمایش بزرگ با Android 12L (میانای برنامه کاربردی سطح ۳۲) یا بالاتر و در برخی‌از دستگاه‌های دارای نسخه‌های قدیمی‌تر پلاتفرم بسازید. برنامه‌های موجود که به‌جای چیدمان‌های مبتنی بر نما یا قطعه، بر چندین فعالیت استوار هستند، مثل SlidingPaneLayout می‌توانند تجربه کاربری بهبودیافته‌ای در صفحه‌نمایش بزرگ ارائه دهند بدون اینکه کد منبع را بازسازی کنند.

یک مثال رایج، تقسیم فهرست-جزئیات است. برای اطمینان از ارائه با کیفیت بالا، سیستم فعالیت فهرست را شروع می‌کند و سپس برنامه بلافاصله فعالیت جزئیات را شروع می‌کند. سیستم انتقال منتظر می‌ماند تا هر دو فعالیت ترسیم شوند، سپس آن‌ها را با هم نمایش می‌دهد. برای کاربر، این دو فعالیت به‌صورت یک فعالیت راه‌اندازی می‌شوند.

شکل ۸. دو فعالیت به‌طور هم‌زمان در چیدمان چندپانلی شروع شد.

مشخصه‌های تقسیم‌شده

می‌توانید مشخص کنید پنجره تکلیف چگونه بین محتویات تقسیم‌شده توزیع شود و محتویات نسبت‌به یکدیگر چگونه چیده شوند.

برای قوانینی که در فایل پیکربندی XML تعریف شده‌اند، ویژگی‌های زیر را تنظیم کنید:

  • ‫splitRatio: نسبت‌های محتوی را تنظیم می‌کند. این مقدار عددی شناور در بازه باز (۰٫۰، ۱٫۰) است.
  • splitLayoutDirection: مشخص می‌کند که چطور محتویات تقسیم‌شده نسبت‌به یکدیگر چیده شوند. مقادیر شامل:
    • ‫ltr: چپ به راست
    • ‫rtl: راست به چپ
    • ‫locale: ltr یا rtl از تنظیمات منطقه‌ای تعیین می‌شود

برای مثال‌ها، بخش پیکربندی XML را ببینید.

برای قوانینی که بااستفاده از WindowManager APIs ایجاد شده‌اند، SplitAttributes شیئی با SplitAttributes.Builder ایجاد کنید و روش‌های سازنده زیر را فراخوانی کنید:

  • setSplitType(): نسبت‌های محتوی‌های تقسیم‌شده را تنظیم می‌کند. برای آرگومان‌های معتبر، ازجمله روش SplitAttributes.SplitType.ratio()، به SplitAttributes.SplitType مراجعه کنید.
  • ‫setLayoutDirection(): چیدمان محتوی‌ها را تنظیم می‌کند. برای مقادیر ممکن، به SplitAttributes.LayoutDirection مراجعه کنید.

برای مثال‌ها، بخش میانای برنامه‌سازی کاربردی مدیر پنجره را ببینید.

شکل ۹. دو تقسیم فعالیت از چپ به راست چیده شده‌اند اما با نسبت‌های تقسیم متفاوت.

جهت تقسیم

ابعاد و نسبت ابعادی نمایشگر موقعیت فعالیت‌ها را در تقسیمات جاسازی فعالیت تعیین می‌کند. در نمایشگرهای افقی بزرگ، فعالیت‌ها در کنار هم نمایش داده می‌شوند؛ در نمایشگرهای عمودی بلند یا در حالت میزکار در دستگاه‌های تاشو، فعالیت‌ها در بالا و پایین هم نمایش داده می‌شوند.

بااستفاده از ماشین‌حساب SplitController SplitAttributes می‌توانید جهت تقسیم را مشخص کنید. ماشین‌حساب SplitAttributes را برای SplitRule فعال محاسبه می‌کند.

از ماشین‌حساب برای تقسیم کردن محتوی والد در جهت‌های مختلف برای وضعیت‌های مختلف دستگاه استفاده کنید، برای مثال:

کاتلین

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

جاوا

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

در دستگاه‌های تاشو، اگر دستگاه در حالت افقی باشد می‌توانید صفحه‌نمایش را به‌صورت عمودی تقسیم کنید، اگر دستگاه در حالت عمودی باشد می‌توانید یک فعالیت را نمایش دهید، و اگر دستگاه در حالت ایستاده باشد می‌توانید صفحه‌نمایش را به‌صورت افقی تقسیم کنید:

کاتلین

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

جاوا

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

جای‌بان‌ها

فعالیت‌های جای‌بان فعالیت‌های ثانویه خالی هستند که بخشی از یک فعالیت تقسیم‌شده را اشغال می‌کنند. درنهایت قرار است با فعالیت دیگری که حاوی محتوا است جایگزین شوند. برای مثال، فعالیت جای‌بان می‌تواند تا زمانی که موردی از فهرست انتخاب شود، در بخش فرعی فعالیت تقسیم‌شده در چیدمان تفصیلی-فهرستی قرار بگیرد، و در آن زمان، فعالیتی که حاوی اطلاعات تفصیلی برای مورد فهرست انتخاب‌شده است جای‌بان را جایگزین می‌کند.

به‌طور پیش‌فرض، سیستم جای‌بان‌ها را فقط زمانی نمایش می‌دهد که فضای کافی برای تقسیم فعالیت وجود داشته باشد. جای‌بان‌ها به‌طور خودکار وقتی اندازه نمایشگر به عرض یا ارتفاعی تغییر می‌کند که برای نمایش یک تقسیم بسیار کوچک است، تکمیل می‌شوند. وقتی فضا اجازه دهد، سیستم جای‌بان را با وضعیت دوباره‌راه‌اندازی‌شده بازراه‌اندازی می‌کند.

شکل ۱۰. دستگاه تاشو درحال تا شدن و باز شدن. جای‌بان فعالیت تمام شده است و با تغییر اندازه نمایش بازآفرینی می‌شود.

بااین‌حال، stickyPlaceholder مشخصه SplitPlaceholderRule یا setSticky() روش SplitPlaceholder.Builder می‌تواند رفتار پیش‌فرض را ملغی کند. وقتی مشخصه یا روش مقدار true را مشخص می‌کند، سیستم جای‌بان را به‌عنوان فعالیت بالایی در پنجره تکلیف وقتی نمایشگر از نمایشگر دونیمه به نمایشگر تک‌نیمه تغییر اندازه می‌دهد نشان می‌دهد (برای مثال، پیکربندی دونیمه را ببینید).

شکل ۱۱. دستگاه تاشو درحال تا شدن و باز شدن. جای‌بان فعالیت چسبنده است.

تغییرات اندازه پنجره

وقتی تغییرات پیکربندی دستگاه باعث می‌شود پهنای پنجره تکلیف کاهش یابد و برای چیدمان چندپانلی به‌اندازه کافی بزرگ نباشد (برای مثال، وقتی دستگاه تاشو با صفحه‌نمایش بزرگ از اندازه رایانه لوحی به اندازه تلفن تا می‌شود یا اندازه پنجره برنامه در حالت چندپنجره‌ای تغییر می‌کند)، فعالیت‌های غیرجای‌بان در پانل ثانویه پنجره تکلیف روی فعالیت‌های پانل اصلی چیده می‌شود.

فعالیت‌های جای‌بان فقط زمانی نشان داده می‌شوند که پهنای نمایش کافی برای تقسیم وجود داشته باشد. در صفحه‌نمایش‌های کوچک‌تر، جای‌بان به‌طور خودکار بسته می‌شود. وقتی مساحت نمایش دوباره به‌اندازه کافی بزرگ شود، جای‌بان دوباره ایجاد می‌شود. (بخش جای‌بان‌ها را ببینید.)

چیدن فعالیت‌ها امکان‌پذیر است زیرا WindowManager فعالیت‌ها را در قاب ثانویه بالاتر از فعالیت‌ها در قاب اصلی قرار می‌دهد.

چندین فعالیت در قاب دوم

فعالیت B فعالیت C را در همان مکان و بدون پرچم‌های هدف اضافی شروع می‌کند:

فعالیت تقسیم‌شده حاوی فعالیت‌های A،‏ B، و C که C روی B چیده شده است.

که منجر به ترتیب z فعالیت‌ها در تکلیف یکسان می‌شود:

پشته فعالیت ثانویه حاوی فعالیت C که روی B چیده شده است.
          پشته ثانویه روی پشته فعالیت اصلی
          حاوی فعالیت A چیده شده است.

بنابراین، در پنجره وظیفه کوچک‌تر، برنامه به یک فعالیت واحد با C در بالای پشته کوچک می‌شود:

پنجره کوچکی که فقط فعالیت C را نشان می‌دهد.

پیمایش به عقب در پنجره کوچک، در فعالیت‌های چیده شده روی هم پیمایش می‌کند.

اگر پیکربندی پنجره تکلیف به اندازه بزرگ‌تری که می‌تواند چند قاب را در خود جای دهد بازیابی شود، فعالیت‌ها دوباره در کنار هم نمایش داده می‌شوند.

جداکننده‌های پشته‌ای

فعالیت B فعالیت C را در کنار شروع می‌کند و تقسیم را به‌صورت جانبی تغییر می‌دهد:

پنجره تکلیف فعالیت‌های A و B را نشان می‌دهد، سپس فعالیت‌های B و C را نشان می‌دهد.

نتیجه ترتیب z فعالیت‌ها در یک وظیفه است:

فعالیت‌های A،‏ B، و C در یک پشته واحد. فعالیت‌ها به ترتیب زیر از بالا به پایین چیده می‌شوند: C،‏ B،‏ A.

در پنجره تکلیف کوچک‌تر، برنامه به یک فعالیت واحد با C در بالا کوچک می‌شود:

پنجره کوچکی که فقط فعالیت C را نشان می‌دهد.

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

تنظیم مانیفست android:screenOrientation به برنامه‌ها امکان می‌دهد فعالیت‌ها را به جهت عمودی یا افقی محدود کنند. برای بهبود تجربه کاربر در دستگاه‌های دارای صفحه‌نمایش بزرگ مثل رایانه‌های لوحی و دستگاه‌های تاشو، سازندگان دستگاه (OEM) می‌توانند درخواست‌های جهت صفحه‌نمایش را نادیده بگیرند و برنامه را در جهت عمودی در نمایشگرهای افقی یا در جهت افقی در نمایشگرهای عمودی کادربندی کنند.

شکل ۱۲. فعالیت‌های قاب‌بندی‌شده: عمودی ثابت در دستگاه افقی (چپ)، افقی ثابت در دستگاه عمودی (راست).

به‌همین ترتیب، وقتی جاسازی فعالیت فعال باشد، تولیدکنندگان تجهیزات اصلی می‌توانند دستگاه‌ها را سفارشی‌سازی کنند تا فعالیت‌های عمودی ثابت را در جهت افقی در صفحه‌نمایش‌های بزرگ (عرض ≥ ۶۰۰ پیکسل) قاب‌بندی کنند. وقتی فعالیت ثابت عمودی فعالیت دومی را راه‌اندازی می‌کند، دستگاه می‌تواند دو فعالیت را در نمایشگر دوپانلی کنار هم نمایش دهد.

شکل ۱۳. فعالیت A با جهت‌گیری ثابت عمودی فعالیت B را در کنار شروع می‌کند.

همیشه android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED دارایی را به فایل مانیفست برنامه خود اضافه کنید تا به دستگاه‌ها اطلاع دهید که برنامه شما از فعالیت جاسازی‌شده پشتیبانی می‌کند (بخش پیکربندی تقسیم را ببینید). دستگاه‌های سفارشی‌سازی‌شده توسط سازنده تجهیزات اصلی می‌توانند تعیین کنند که آیا فعالیت‌های ثابت عمودی را قاب‌بندی کنند یا نه.

پیکربندی تقسیم

قوانین تقسیم، تقسیم فعالیت‌ها را پیکربندی می‌کنند. قوانین تقسیم را در فایل پیکربندی XML یا با انجام تماس‌های API 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> در فایل مانیفست برنامه اضافه کنید و مقدار را روی «درست» تنظیم کنید، برای مثال:

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

    در نسخه 1.1.0-alpha06 و نسخه‌های جدیدتر WindowManager، تعبیه فعالیت غیرفعال است، مگر اینکه دارایی به مانیفست اضافه شود و روی درست تنظیم شود.

    همچنین، سازندگان دستگاه از این تنظیم برای فعال کردن قابلیت‌های سفارشی برای برنامه‌هایی که از جاسازی فعالیت پشتیبانی می‌کنند استفاده می‌کنند. برای مثال، دستگاه‌ها می‌توانند فعالیت فقط عمودی را در نمایشگرهای افقی با افزودن نوارهای سیاه در بالا و پایین نمایش دهند تا فعالیت را برای انتقال به چیدمان دو پانلی هنگام شروع فعالیت دوم جهت‌دهی کنند (به جهت‌گیری ثابت عمودی مراجعه کنید).

پیکربندی XML

برای ایجاد پیاده‌سازی مبتنی بر XML از جاسازی فعالیت، مراحل زیر را تکمیل کنید:

  1. فایل منبع XML بسازید که کارهای زیر را انجام دهد:

    • فعالیت‌هایی را تعریف می‌کند که یک تقسیم را هم‌رسانی می‌کنند
    • گزینه‌های تقسیم را پیکربندی می‌کند
    • وقتی محتوا دردسترس نیست، جای‌بانی برای ظرف ثانویه تقسیم ایجاد می‌کند
    • فعالیت‌هایی را مشخص می‌کند که هرگز نباید بخشی از تقسیم باشند

    برای مثال:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. مقداردهنده اولیه ایجاد کنید.

    عنصر WindowManager RuleController فایل پیکربندی XML را تجزیه می‌کند و قوانین را برای سیستم دردسترس قرار می‌دهد. کتابخانه Jetpack StartupInitializer فایل XML را در RuleController هنگام راه‌اندازی برنامه دردسترس قرار می‌دهد تا قوانین هنگام شروع هر فعالیتی اعمال شوند.

    برای ایجاد یک مقداردهنده اولیه، مراحل زیر را انجام دهید:

    1. وابستگی جدیدترین کتابخانه «راه‌اندازی Jetpack» را به فایل build.gradle سطح واحد خود اضافه کنید، برای مثال:

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

    2. کلاسی بسازید که رابط Initializer را پیاده‌سازی کند.

      مقداردهنده اولیه با گذراندن شناسه فایل پیکربندی XML (main_split_config.xml) به روش RuleController.parseRules()، قوانین تقسیم را برای RuleController دردسترس قرار می‌دهد.

      کاتلین

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

      جاوا

      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 قبل‌از اینکه متد onCreate() برنامه فراخوانی شود، SplitInitializer را کشف و مقداردهی اولیه می‌کند. درنتیجه، وقتی فعالیت اصلی برنامه شروع می‌شود، قوانین تقسیم اعمال می‌شود.

WindowManager API

با چند فراخوانی API می‌توانید جاسازی فعالیت را به‌صورت برنامه‌ریزی‌شده پیاده‌سازی کنید. تماس‌ها را در روش onCreate() زیرکلاس Application برقرار کنید تا مطمئن شوید قوانین قبل‌از راه‌اندازی هر فعالیتی اعمال می‌شوند.

برای ایجاد کردن تقسیم فعالیت به‌صورت برنامه‌ریزی‌شده، این کارها را انجام دهید:

  1. ایجاد قانون تقسیم:

    1. SplitPairFilter که فعالیت‌هایی را که تقسیم را هم‌رسانی می‌کنند شناسایی می‌کند ایجاد کنید:

      کاتلین

      val splitPairFilter = SplitPairFilter(
          ComponentName(this, ListActivity::class.java),
          ComponentName(this, DetailActivity::class.java),
          null
      )

      جاوا

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );

    2. فیلتر را به مجموعه فیلتر اضافه کنید:

      کاتلین

      val filterSet = setOf(splitPairFilter)

      جاوا

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      ```

    3. مشخصه‌های چیدمان را برای این تقسیم ایجاد کنید:

      کاتلین

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      جاوا

      SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder شیئی حاوی چیدمان ویژگی‌ها ایجاد می‌کند:

      • setSplitType(): تعریف می‌کند که چگونه فضای نمایشگر دردسترس به هر ظرف فعالیت اختصاص داده می‌شود. نوع تقسیم نسبت، نسبت فضای نمایشگر دردسترس اختصاص‌یافته به محتوی اصلی را مشخص می‌کند؛ محتوی فرعی بقیه فضای نمایشگر دردسترس را اشغال می‌کند.
      • setLayoutDirection(): مشخص می‌کند که چطور ظرف‌های فعالیت نسبت‌به یکدیگر چیده شوند، ظرف اصلی اول.
    4. ساختن SplitPairRule:

      کاتلین

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

      جاوا

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      SplitPairRule.Builder قانون را ایجاد و پیکربندی می‌کند:

      • filterSet: حاوی فیلترهای جفت تقسیم‌شده‌ای است که با شناسایی فعالیت‌هایی که تقسیم را هم‌رسانی می‌کنند، تعیین می‌کند چه زمانی قانون اعمال شود.
      • setDefaultSplitAttributes(): مشخصه‌های چیدمان را به قانون اعمال می‌کند.
      • setMinWidthDp(): حداقل عرض نمایشگر (برحسب پیکسل‌های مستقل از تراکم، dp) را که امکان تقسیم را فراهم می‌کند تنظیم می‌کند.
      • setMinSmallestWidthDp(): حداقل مقدار (برحسب پیکسل مستقل از تراکم) را تنظیم می‌کند که کوچک‌ترین ابعاد نمایش باید داشته باشد تا صرف‌نظر از جهت دستگاه، تقسیم صفحه فعال شود.
      • setMaxAspectRatioInPortrait(): حداکثر نسبت ابعادی نمایشگر (ارتفاع:عرض) را در جهت عمودی تنظیم می‌کند که برای آن فعالیت‌ها تقسیم می‌شوند. اگر نسبت ابعادی نمایشگر عمودی از حداکثر نسبت ابعادی فراتر رود، صرف‌نظر از عرض نمایشگر، تقسیم غیرفعال می‌شود. توجه: مقدار پیش‌فرض ۱٫۴ است که باعث می‌شود فعالیت‌ها در حالت عمودی در اکثر رایانه‌های لوحی کل پنجره تکلیف را اشغال کنند. همچنین SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT و setMaxAspectRatioInLandscape() را ببینید. مقدار پیش‌فرض برای افقی ALWAYS_ALLOW است.
      • setFinishPrimaryWithSecondary(): تعیین می‌کند که اتمام همه فعالیت‌ها در محتوی ثانویه چگونه بر فعالیت‌های موجود در محتوی اصلی تأثیر می‌گذارد. NEVER نشان می‌دهد که سیستم نباید فعالیت‌های اصلی را وقتی همه فعالیت‌های ظرف ثانویه تمام می‌شود تکمیل کند (به تکمیل فعالیت‌ها مراجعه کنید).
      • setFinishSecondaryWithPrimary(): تعیین می‌کند که چگونه پایان یافتن همه فعالیت‌ها در محتوی اصلی بر فعالیت‌های موجود در محتوی ثانویه تأثیر می‌گذارد. ALWAYS نشان می‌دهد که سیستم باید همیشه فعالیت‌های موجود در محتوی فرعی را وقتی همه فعالیت‌های موجود در محتوی اصلی تمام شد تکمیل کند (به تکمیل فعالیت‌ها مراجعه کنید).
      • setClearTop(): مشخص می‌کند که آیا همه فعالیت‌های موجود در ظرف ثانویه وقتی فعالیت جدیدی در ظرف راه‌اندازی می‌شود تمام می‌شوند یا نه. مقدار false مشخص می‌کند که فعالیت‌های جدید روی فعالیت‌های موجود در محتوی فرعی چیده می‌شوند.
    5. نمونه تک‌تایی WindowManager را دریافت کنید RuleController، و قانون را اضافه کنید:

      کاتلین

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

      جاوا

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

    6. وقتی محتوا دردسترس نیست، یک جای‌بان برای محتوی فرعی ایجاد کنید:

    7. ActivityFilter ایجاد کنید که فعالیتی را که جای‌بان با آن پنجره تقسیم تکلیف را هم‌رسانی می‌کند شناسایی کند:

      کاتلین

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

      جاوا

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

    8. فیلتر را به مجموعه فیلتر اضافه کنید:

      کاتلین

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      جاوا

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

    9. ایجاد یک SplitPlaceholderRule:

      کاتلین

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

      جاوا

      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(): حداکثر نسبت ابعادی نمایشگر (ارتفاع:عرض) را در جهت عمودی که برای آن تقسیم‌های فعالیت نمایش داده می‌شود تنظیم می‌کند. توجه: مقدار پیش‌فرض ۱٫۴ است که باعث می‌شود فعالیت‌ها پنجره تکلیف را در جهت عمودی در اکثر رایانه‌های لوحی پر کنند. همچنین SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT و setMaxAspectRatioInLandscape() را ببینید. مقدار پیش‌فرض برای حالت افقی ALWAYS_ALLOW است.
      • setFinishPrimaryWithPlaceholder(): تنظیم می‌کند که پایان فعالیت جای‌بان چگونه بر فعالیت‌های در محتوی اصلی تأثیر می‌گذارد. «همیشه» نشان می‌دهد که سیستم باید همیشه فعالیت‌های موجود در محتوی اصلی را وقتی جای‌بان تمام می‌شود تکمیل کند (به تکمیل فعالیت‌ها مراجعه کنید).
      • setSticky(): تعیین می‌کند که آیا فعالیت جای‌بان پس‌از اینکه جای‌بان برای اولین‌بار در یک تقسیم با حداقل عرض کافی ظاهر شد، در بالای پشته فعالیت در نمایشگرهای کوچک ظاهر شود یا نه.
    10. قانون را به WindowManager RuleController اضافه کنید:

      کاتلین

      ruleController.addRule(splitPlaceholderRule)

      جاوا

      ruleController.addRule(splitPlaceholderRule);

  2. فعالیت‌هایی را که نباید هرگز بخشی از تقسیم باشند مشخص کنید:

    1. ActivityFilter ایجاد کنید که فعالیتی را شناسایی کند که باید همیشه کل ناحیه نمایش تکلیف را اشغال کند:

      کاتلین

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

      جاوا

      ActivityFilter expandedActivityFilter = new ActivityFilter(
          new ComponentName(this, ExpandedActivity.class),
          null
      );

    2. فیلتر را به مجموعه فیلتر اضافه کنید:

      کاتلین

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      جاوا

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);

    3. ایجاد ActivityRule:

      کاتلین

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

      جاوا

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

      ActivityRule.Builder قانون را ایجاد و پیکربندی می‌کند:

      • expandedActivityFilterSet: شامل فیلترهای فعالیت است که با شناسایی فعالیت‌هایی که می‌خواهید از تقسیم‌ها مستثنی کنید، تعیین می‌کنند چه زمانی قانون اعمال شود.
      • setAlwaysExpand(): مشخص می‌کند که آیا فعالیت باید کل پنجره تکلیف را پر کند یا نه.
    4. قانون را به WindowManager RuleController اضافه کنید:

      کاتلین

      ruleController.addRule(activityRule)

      جاوا

      ruleController.addRule(activityRule);

جاسازی بین‌برنامه‌ای

در Android 13 (سطح میانای برنامه‌سازی کاربردی ۳۳) و بالاتر، برنامه‌ها می‌توانند فعالیت‌های برنامه‌های دیگر را جاسازی کنند. جاسازی فعالیت بین‌برنامه‌ای یا بین UID ادغام دیداری فعالیت‌ها از چندین برنامه Android را امکان‌پذیر می‌کند. سیستم فعالیت برنامه میزبان و فعالیت جاسازی‌شده از برنامه دیگر را در کنار هم یا بالا و پایین در صفحه‌نمایش نشان می‌دهد، درست مثل جاسازی فعالیت تک‌برنامه‌ای.

برای مثال، برنامه «تنظیمات» می‌تواند فعالیت انتخابگر کاغذدیواری را از برنامه WallpaperPicker جاسازی کند:

شکل ۱۴. برنامه «تنظیمات» (منو در سمت راست) با انتخابگر تصویر زمینه به‌عنوان فعالیت جاسازی‌شده (سمت چپ).

مدل تراست

فرایندهای میزبان که فعالیت‌های برنامه‌های دیگر را جاسازی می‌کنند می‌توانند ارائه فعالیت‌های جاسازی‌شده را، ازجمله اندازه، موقعیت، برش، و شفافیت، بازتعریف کنند. میزبان‌های مخرب می‌توانند از این قابلیت برای گمراه کردن کاربران و ایجاد کلیک‌ربایی یا دیگر حملات پوشش میانای کاربر استفاده کنند.

برای جلوگیری از سوءاستفاده از جاسازی فعالیت بین‌برنامه‌ای، 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>

مالکان برنامه می‌توانند با اجرای تکلیف Gradle signingReport، خلاصه گواهینامه SHA را دریافت کنند. خلاصه گواهینامه اثر انگشت SHA-256 بدون دونقطه‌های جداکننده است. برای اطلاعات بیشتر، اجرای گزارش امضا و اصالت‌سنجی مشتری را ببینید.

میزبان‌های نامطمئن

برای اینکه به هر برنامه‌ای اجازه دهید فعالیت‌های برنامه‌تان را جاسازی کند و ارائه آن‌ها را کنترل کند، ویژگی android:allowUntrustedActivityEmbedding را در عناصر <activity> یا <application> در مانیفست برنامه مشخص کنید، برای مثال:

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

مقدار پیش‌فرض این مشخصه «نادرست» است که از جاسازی فعالیت بین‌برنامه‌ای جلوگیری می‌کند.

اصالت‌سنجی سفارشی

برای کاهش خطرات جاسازی فعالیت غیرقابل‌اعتماد، سازوکار درستی‌سنجی سفارشی ایجاد کنید که هویت میزبان را درستی‌سنجی کند. اگر گواهینامه‌های میزبان را می‌دانید، از کتابخانه androidx.security.app.authenticator برای اصالت‌سنجی استفاده کنید. اگر میزبان پس‌از جاسازی فعالیت شما اصالت‌سنجی کند، می‌توانید محتوای واقعی را نمایش دهید. اگر این‌طور نیست، می‌توانید به کاربر اطلاع دهید که کنش مجاز نیست و محتوا را مسدود کنید.

از روش ActivityEmbeddingController#isActivityEmbedded() در کتابخانه Jetpack WindowManager برای بررسی اینکه آیا میزبان فعالیت شما را جاسازی می‌کند یا نه استفاده کنید، برای مثال:

کاتلین

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

جاوا

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 یا در زمان اجرا بااستفاده از تماس‌های API «مدیر پنجره» Jetpack تعریف می‌شوند.

اگر برنامه میزبانی بخواهد فعالیتی را جاسازی کند که برای جاسازی بین‌برنامه‌ای موافقت نکرده است، فعالیت کل محدوده تکلیف را اشغال می‌کند. در نتیجه، برنامه‌های میزبان باید بدانند که آیا فعالیت‌های هدف اجازه جاسازی بین‌برنامه‌ای را می‌دهند یا نه.

اگر فعالیت جاسازی‌شده‌ای فعالیت جدیدی را در همان تکلیف شروع کند و فعالیت جدید برای جاسازی بین‌برنامه‌ای موافقت نکرده باشد، فعالیت به‌جای اینکه روی فعالیت در محتوی جاسازی‌شده رونهاد شود، کل محدوده تکلیف را اشغال می‌کند.

برنامه میزبان می‌تواند فعالیت‌های خود را بدون محدودیت جاسازی کند، به‌شرطی که فعالیت‌ها در همان تکلیف راه‌اندازی شوند.

نمونه‌های تقسیم

دونیمه کردن از تمام‌پنجره

شکل ۱۵. فعالیت A فعالیت B را در کنار شروع می‌کند.

بازسازی لازم نیست. می‌توانید پیکربندی تقسیم را به‌صورت ایستا یا در زمان اجرا تعریف کنید و سپس Context#startActivity() را بدون هیچ پارامتر اضافی فراخوانی کنید.

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

تقسیم‌بندی براساس پیش‌فرض

وقتی صفحه مقصد برنامه‌ای به‌گونه‌ای طراحی شده باشد که در صفحه‌نمایش‌های بزرگ به دو ظرف تقسیم شود، تجربه کاربری در بهترین حالت خود است که هر دو فعالیت به‌طور هم‌زمان ایجاد و ارائه شوند. بااین‌حال، تا زمانی که کاربر با فعالیت در محتوی اصلی تعامل برقرار نکند (برای مثال، کاربر موردی را از منو پیمایش انتخاب کند)، محتوا ممکن است برای محتوی فرعی شکاف دردسترس نباشد. فعالیت جای‌بان می‌تواند تا زمانی که محتوا در محتوی ثانویه تقسیم نمایش داده شود، جای خالی را پر کند (به بخش جای‌بان‌ها مراجعه کنید).

شکل ۱۶. دونیمه با باز کردن هم‌زمان دو فعالیت ایجاد شد. یک فعالیت جای‌بان است.

برای ایجاد شکاف با جای‌بان، جای‌بانی ایجاد کنید و آن را با فعالیت اصلی مرتبط کنید:

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

وقتی برنامه‌ای هدف دریافت می‌کند، فعالیت هدف می‌تواند به‌عنوان بخش دوم فعالیت دونیمه نشان داده شود؛ برای مثال، درخواستی برای نمایش صفحه جزئیات با اطلاعات مربوط به موردی از فهرست. در نمایشگرهای کوچک، جزئیات در پنجره کامل کار نشان داده می‌شود؛ در دستگاه‌های بزرگ‌تر، در کنار فهرست.

شکل ۱۷. جزئیات فعالیت پیوند عمیق به‌تنهایی در صفحه‌نمایش کوچک نشان داده می‌شود، اما همراه با فعالیت فهرست در صفحه‌نمایش بزرگ نشان داده می‌شود.

درخواست راه‌اندازی باید به فعالیت اصلی هدایت شود و فعالیت جزئیات هدف باید در یک تقسیم راه‌اندازی شود. سیستم به‌طور خودکار براساس عرض نمایشگر موجود، ارائه صحیح—روی‌هم یا کنار هم—را انتخاب می‌کند.

کاتلین

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.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>

بخش ویژگی‌های پیکربندی را ببینید.

چندین فعالیت در محتویات تقسیم‌شده

انباشتن چندین فعالیت در یک محتوی تقسیم‌شده به کاربران امکان می‌دهد به محتوای عمیق دسترسی پیدا کنند. برای مثال، با تقسیم فهرست-جزئیات، کاربر ممکن است نیاز داشته باشد به بخش جزئیات فرعی برود اما فعالیت اصلی را در جای خود نگه دارد:

شکل ۱۸. فعالیت در جای خود در بخش دوم پنجره تکلیف باز شد.

کاتلین

class DetailActivity : AppCompatActivity() {
    fun onOpenSubdetail() {
        startActivity(Intent(this, SubdetailActivity::class.java))
    }
}

جاوا

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

فعالیت جزئیات فرعی در بالای فعالیت جزئیات قرار می‌گیرد و آن را پنهان می‌کند:

سپس کاربر می‌تواند با پیمایش به عقب ازطریق پشته، به سطح جزئیات قبلی برگردد:

شکل ۱۹. فعالیت از بالای دسته برداشته شد.

وقتی فعالیت‌ها از فعالیتی در همان محتوی ثانویه راه‌اندازی می‌شوند، فعالیت‌ها به‌طور پیش‌فرض روی هم قرار می‌گیرند. فعالیت‌هایی که از محتوی اصلی در یک تقسیم فعال راه‌اندازی می‌شوند، در محتوی ثانویه در بالای پشته فعالیت نیز قرار می‌گیرند.

فعالیت‌ها در تکلیف جدید

وقتی فعالیت‌های پنجره وظیفه تقسیم‌شده فعالیت‌هایی را در وظیفه‌ای جدید شروع می‌کنند، وظیفه جدید از وظیفه‌ای که شامل تقسیم است جدا می‌شود و در پنجره‌ای کامل نمایش داده می‌شود. صفحه «موارد اخیر» دو تکلیف را نشان می‌دهد: تکلیف در صفحه تقسیم‌شده و تکلیف جدید.

شکل ۲۰. فعالیت C را در تکلیف جدیدی از فعالیت B شروع کنید.

جایگزینی فعالیت

فعالیت‌ها می‌توانند در پشته محتوی ثانویه جایگزین شوند؛ برای مثال، وقتی فعالیت اصلی برای پیمایش سطح بالا استفاده می‌شود و فعالیت ثانویه مقصد انتخابی است. هر انتخاب از پیمایش سطح بالا باید فعالیت جدیدی را در محتوی ثانویه شروع کند و فعالیت یا فعالیت‌هایی را که قبلاً در آنجا بوده است بردارد.

شکل ۲۱. فعالیت پیمایش سطح بالا در قاب اصلی جایگزین فعالیت‌های مقصد در قاب ثانویه می‌شود.

اگر برنامه فعالیت را در محتوی ثانویه وقتی انتخاب پیمایش تغییر می‌کند تکمیل نکند، پیمایش به عقب ممکن است وقتی تقسیم ازهم باز می‌شود (وقتی دستگاه تا می‌شود) گیج‌کننده باشد. برای مثال، اگر منویی در قاب اصلی و صفحه‌های A و B روی هم در قاب فرعی داشته باشید، وقتی کاربر تلفن را می‌بندد، B روی A و A روی منو قرار می‌گیرد. وقتی کاربر از B به عقب برمی‌گردد، به‌جای منو، A ظاهر می‌شود.

در چنین مواردی، صفحه A باید از پشته برگشت برداشته شود.

عملکرد پیش‌فرض هنگام راه‌اندازی در کنار در یک محتوی جدید روی یک تقسیم موجود این است که محتوی‌های ثانویه جدید را در بالا قرار دهد و محتوی‌های قدیمی را در پشته پشتی حفظ کند. می‌توانید تقسیم‌ها را پیکربندی کنید تا ظرف‌های ثانویه قبلی را با clearTop پاک کند و فعالیت‌های جدید را به‌طور عادی راه‌اندازی کند.

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

کاتلین

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

جاوا

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

یا اینکه از همان فعالیت ثانویه استفاده کنید و از فعالیت اصلی (منو) قصد جدیدی ارسال کنید که به همان نمونه حل‌وفصل شود اما وضعیت یا به‌روزرسانی رابط کاربری را در محتوی ثانویه راه‌اندازی کند.

چند جداکننده

برنامه‌ها می‌توانند با راه‌اندازی فعالیت‌های اضافی در کنار، پیمایش عمیق چندسطحی ارائه دهند.

وقتی فعالیتی در محتوی ثانویه فعالیت جدیدی را در کنار راه‌اندازی می‌کند، شکاف جدیدی در بالای شکاف موجود ایجاد می‌شود.

شکل ۲۲. فعالیت B فعالیت C را در کنار شروع می‌کند.

پشته برگشت حاوی همه فعالیت‌هایی است که قبلاً باز شده‌اند، بنابراین کاربران می‌توانند پس‌از اتمام C به تقسیم A/B پیمایش کنند.

فعالیت‌های A،‏ B، و C در یک پشته. فعالیت‌ها به ترتیب زیر از بالا به پایین چیده شده‌اند: 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>

کاتلین

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

جاوا

public class B extends AppCompatActivity{
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

واکنش به تغییرات وضعیت تقسیم

فعالیت‌های مختلف در یک برنامه می‌توانند عناصر رابط کاربری داشته باشند که عملکرد یکسانی دارند؛ برای مثال، کنترلی که پنجره‌ای حاوی تنظیمات حساب را باز می‌کند.

شکل ۲۳. فعالیت‌های مختلف با عناصر واسط کاربر عملکردی یکسان.

اگر دو فعالیت که عنصر رابط کاربری مشترک دارند در حالت تقسیم صفحه باشند، نمایش عنصر در هر دو فعالیت اضافی و شاید گیج‌کننده است.

شکل ۲۴. عناصر واسط کاربر در تقسیم فعالیت تکراری است.

برای اینکه بدانید فعالیت‌ها چه زمانی در یک تقسیم قرار دارند، جریان SplitController.splitInfoList را بررسی کنید یا شنونده‌ای را با SplitControllerCallbackAdapter برای تغییرات در وضعیت تقسیم ثبت کنید. سپس، واسط کاربر را متناسب با آن تنظیم کنید:

کاتلین

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

جاوا

@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() ثبت‌نامشان را لغو کنند.

پنجره مقید تمام‌صفحه

برخی‌از فعالیت‌ها تا زمانی که کنش مشخصی انجام نشود، کاربران را از تعامل با برنامه منع می‌کنند؛ برای مثال، فعالیت صفحه ورود به سیستم، صفحه تأیید خط‌مشی، یا پیام خطا. باید از نمایش فعالیت‌های مُدال در نمای دونیمه جلوگیری شود.

بااستفاده از پیکربندی expand (ازهم بازکردن)، می‌توان فعالیت را مجبور کرد که همیشه پنجره تکلیف را پر کند:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

تکمیل فعالیت‌ها

کاربران می‌توانند با کشیدن انگشت از لبه نمایشگر، فعالیت‌ها را در هر دو طرف صفحه دونیمه تکمیل کنند:

شکل ۲۵. اشاره تند کشیدن فعالیت B را تمام می‌کند.
شکل ۲۶. اشاره تند کشیدن برای پایان دادن به فعالیت A.

اگر دستگاه برای استفاده از دکمه بازگشت به‌جای پیمایش با اشاره تنظیم شده باشد، ورودی به فعالیت کانونی ارسال می‌شود—فعالیتی که آخرین‌بار لمس یا راه‌اندازی شده است.

تأثیر تکمیل همه فعالیت‌ها در یک محتوی بر محتوی مخالف به پیکربندی تقسیم بستگی دارد.

مشخصه‌های پیکربندی

می‌توانید مشخصه‌های قانون جفت تفکیک را مشخص کنید تا پیکربندی کنید که چگونه تکمیل همه فعالیت‌ها در یک طرف تفکیک بر فعالیت‌های طرف دیگر تفکیک تأثیر می‌گذارد. مشخصه‌ها عبارت‌اند از:

  • ‫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 چیده شده است. ‫A تمام می‌شود، همچنین
          B و C تمام می‌شوند.

و وقتی شکاف پیکربندی شده باشد تا با هم اولیه و ثانویه را تمام کند:

<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 نیز تمام می‌شوند.

تغییر خصوصیات تقسیم در زمان اجرا

خصوصیات یک شکاف فعال و قابل‌مشاهده را نمی‌توان تغییر داد. تغییر قوانین تقسیم بر راه‌اندازی‌های فعالیت اضافی و محتوی‌های جدید تأثیر می‌گذارد، اما بر تقسیم‌های فعال و موجود تأثیر نمی‌گذارد.

برای تغییر دادن ویژگی‌های تقسیم‌های فعال، فعالیت یا فعالیت‌های جانبی را در تقسیم تمام کنید و با پیکربندی جدید دوباره در کنار راه‌اندازی کنید.

خصوصیت‌های تقسیم پویا

‫Android 15 (سطح API 35) و نسخه‌های بالاتر که توسط Jetpack WindowManager 1.4 و نسخه‌های بالاتر پشتیبانی می‌شوند، ویژگی‌های پویایی ارائه می‌دهند که امکان پیکربندی تقسیم‌های جاسازی فعالیت را فراهم می‌کنند، ازجمله:

  • ازهم بازکردن قاب: تقسیم‌کننده تعاملی و قابل‌کشیدنی به کاربران امکان می‌دهد اندازه قاب‌ها را در ارائه تقسیم‌شده تغییر دهند.
  • سنجاق کردن پشته فعالیت: کاربران می‌توانند محتوا را در یک محفظه سنجاق کنند و ناوبری در محفظه را از ناوبری در محفظه دیگر جدا کنند.
  • کم‌نور کردن تمام‌صفحه کادر گفتگو: هنگام نمایش کادر گفتگو، برنامه‌ها می‌توانند مشخص کنند که آیا کل پنجره تکلیف کم‌نور شود یا فقط محتوی که کادر گفتگو را باز کرده است.

ازهم بازکردن قاب

با گسترش پانل، کاربران می‌توانند مقدار فضای صفحه‌نمایش اختصاص‌داده‌شده به دو فعالیت در چیدمان دوپانلی را تنظیم کنند.

برای سفارشی‌سازی کردن ظاهر تقسیم‌کننده پنجره و تنظیم محدوده قابل‌کشیدن تقسیم‌کننده، کارهای زیر را انجام دهید:

  1. ایجاد نمونه‌ای از DividerAttributes

  2. سفارشی‌سازی مشخصه‌های جداکننده:

    • color: رنگ جداکننده قاب کشیدنی.

    • widthDp: عرض جداکننده قاب قابل‌کشیدن. روی WIDTH_SYSTEM_DEFAULT تنظیم کنید تا سیستم عرض جداکننده را تعیین کند.

    • کشیدن محدوده: حداقل درصد صفحه‌نمایش که هریک از قاب‌ها می‌توانند اشغال کنند. می‌تواند از ۰٫۳۳ تا ۰٫۶۶ متغیر باشد. روی DRAG_RANGE_SYSTEM_DEFAULT تنظیم کنید تا سیستم محدوده کشش را تعیین کند.

    کاتلین

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

    جاوا

    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() فعالیت، شنونده onclick را روی دکمه تنظیم کنید:

    کاتلین

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

    جاوا

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

کم‌نور کردن تمام‌صفحه چارگوش گفتگو

فعالیت‌ها معمولاً نمایشگرهایشان را کم‌نور می‌کنند تا توجه را به کادر گفتگو جلب کنند. در فعالیت جاسازی، هر دو بخش نمایشگر دو بخشی باید کم‌نور شوند، نه فقط بخشی که حاوی فعالیتی است که کادر گفتگو را باز کرده است، تا تجربه رابط کاربری یکپارچه‌ای داشته باشید.

با WindowManager نسخه ۱.۴ و بالاتر، وقتی گفتگویی باز می‌شود، کل پنجره برنامه به‌طور پیش‌فرض کم‌نور می‌شود (به EmbeddingConfiguration.DimAreaBehavior.ON_TASK مراجعه کنید).

برای کم‌نور کردن فقط محتوی فعالیت بازکننده کادر گفتگو، از EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK استفاده کنید.

استخراج فعالیت از پنجره دونیمه به تمام‌صفحه

پیکربندی جدیدی ایجاد کنید که پنجره کامل فعالیت جانبی را نمایش دهد، و سپس فعالیت را با هدفی که به همان نمونه منتهی می‌شود مجدداً راه‌اندازی کنید.

بررسی پشتیبانی از تقسیم در زمان اجرا

جاسازی فعالیت در Android 12L (میانای برنامه کاربردی سطح ۳۲) و بالاتر پشتیبانی می‌شود، اما در برخی‌از دستگاه‌هایی که نسخه‌های قدیمی‌تر پلاتفرم را اجرا می‌کنند نیز دردسترس است. برای بررسی دردسترس بودن ویژگی در زمان اجرا، از دارایی SplitController.splitSupportStatus یا روش SplitController.getSplitSupportStatus() استفاده کنید:

کاتلین

if (SplitController.getInstance(this).splitSupportStatus ==
    SplitController.SplitSupportStatus.SPLIT_AVAILABLE
) {
    // Device supports split activity features.
}

جاوا

if (SplitController.getInstance(this).getSplitSupportStatus() ==
    SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
    // Device supports split activity features.
}

اگر تقسیم صفحه پشتیبانی نشود، فعالیت‌ها در بالای پشته فعالیت راه‌اندازی می‌شوند (با پیروی از مدل جاسازی غیرفعالیت).

جلوگیری از ملغی شدن سیستم

تولیدکنندگان دستگاه‌های Android (تولیدکنندگان اصلی تجهیزات، یا OEMها) می‌توانند جاسازی فعالیت را به‌عنوان تابعی از سیستم دستگاه پیاده‌سازی کنند. سیستم قوانین تقسیم را برای برنامه‌های چندفعالیتی مشخص می‌کند و رفتار پنجره‌بندی برنامه‌ها را ملغی می‌کند. لغو سیستم برنامه‌های چندفعالیتی را به حالت جاسازی فعالیت تعریف‌شده توسط سیستم می‌برد.

جاسازی فعالیت سیستم می‌تواند ارائه برنامه را ازطریق چیدمان‌های چند پانلی، مانند فهرست-جزئیات، بدون هیچ تغییری در برنامه بهبود دهد. بااین‌حال، جاسازی فعالیت سیستم ممکن است باعث چیدمان‌های نادرست برنامه، اشکالات، یا تداخل با جاسازی فعالیت پیاده‌سازی‌شده توسط برنامه نیز شود.

برنامه شما می‌تواند با تنظیم کردن 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>

نام دارایی در شیء Jetpack WindowManager WindowProperties تعریف شده است. اگر برنامه‌تان جاسازی فعالیت را پیاده‌سازی می‌کند، یا اگر می‌خواهید به روشی دیگر از اعمال قوانین جاسازی فعالیت سیستم بر برنامه‌تان جلوگیری کنید، مقدار را روی false تنظیم کنید. مقدار را روی true تنظیم کنید تا به سیستم اجازه دهید جاسازی فعالیت تعریف‌شده توسط سیستم را بر برنامه‌تان اعمال کند.

محدودیت‌ها، موانع، و هشدارها

  • فقط برنامه میزبان تکلیف، که به‌عنوان مالک فعالیت ریشه در تکلیف شناسایی می‌شود، می‌تواند فعالیت‌های دیگر را در تکلیف سازمان‌دهی و جاسازی کند. اگر فعالیت‌هایی که از جاسازی و تقسیم پشتیبانی می‌کنند در کاری اجرا شوند که متعلق به برنامه دیگری باشد، جاسازی و تقسیم برای آن فعالیت‌ها کار نخواهد کرد.
  • فعالیت‌ها فقط در یک کار واحد می‌توانند سازمان‌دهی شوند. راه‌اندازی فعالیت در یک تکلیف جدید همیشه آن را در پنجره گسترده جدیدی خارج از هرگونه تقسیم صفحه موجود قرار می‌دهد.
  • فقط فعالیت‌های موجود در یک فرایند می‌توانند سازمان‌دهی شوند و در یک تقسیم قرار بگیرند. ‫SplitInfo بازخوان فقط فعالیت‌هایی را گزارش می‌کند که متعلق به همان فرایند باشند، زیرا راهی برای اطلاع از فعالیت‌های موجود در فرایندهای مختلف وجود ندارد.
  • هر جفت یا قانون فعالیت تکی فقط برای راه‌اندازی فعالیت‌هایی اعمال می‌شود که پس‌از ثبت قانون اتفاق می‌افتد. درحال‌حاضر هیچ راهی برای به‌روزرسانی تقسیم‌های موجود یا ویژگی‌های بصری آن‌ها وجود ندارد.
  • پیکربندی فیلتر جفت تقسیم باید با هدف‌هایی که هنگام راه‌اندازی کامل فعالیت‌ها استفاده می‌شود مطابقت داشته باشد. تطبیق در نقطه‌ای رخ می‌دهد که فعالیت جدیدی از فرایند برنامه شروع می‌شود، بنابراین ممکن است از نام‌های مؤلفه‌ای که بعداً در فرایند سیستم هنگام استفاده از هدف‌های ضمنی حل‌وفصل می‌شوند مطلع نباشد. اگر نام یک عنصر در زمان راه‌اندازی مشخص نباشد، می‌توان به‌جای آن از کارت عام استفاده کرد («*/*») و براساس کنش هدف فیلتر کردن را انجام داد.
  • درحال‌حاضر هیچ راهی برای انتقال فعالیت‌ها بین محتویات یا داخل و خارج از تقسیم‌ها پس‌از ایجاد آن‌ها وجود ندارد. تقسیم‌ها فقط توسط کتابخانه WindowManager وقتی فعالیت‌های جدید با قوانین منطبق راه‌اندازی می‌شوند ایجاد می‌شوند، و وقتی آخرین فعالیت در یک محفظه تقسیم تمام می‌شود، تقسیم‌ها ازبین می‌روند.
  • فعالیت‌ها می‌توانند هنگام تغییر پیکربندی مجدداً راه‌اندازی شوند، بنابراین وقتی یک تقسیم ایجاد یا حذف می‌شود و محدوده‌های فعالیت تغییر می‌کند، فعالیت می‌تواند ازطریق تخریب کامل نمونه قبلی و ایجاد نمونه جدید انجام شود. درنتیجه، توسعه‌دهندگان برنامه باید در مواردی مثل راه‌اندازی فعالیت‌های جدید از برگشتی‌های چرخه حیات دقت کنند.
  • دستگاه‌ها باید رابط افزونه‌های پنجره را برای پشتیبانی از جاسازی فعالیت داشته باشند. تقریباً همه دستگاه‌های دارای صفحه‌نمایش بزرگ که Android 12L (سطح میانای برنامه‌سازی کاربردی ۳۲) یا بالاتر را اجرا می‌کنند این میانای کاربری را دارند. بااین‌حال، برخی‌از دستگاه‌های صفحه بزرگ که قادر به اجرای چند فعالیت نیستند، واسط افزونه‌های پنجره را ندارند. اگر دستگاه صفحه‌نمایش بزرگ از حالت چندپنجره‌ای پشتیبانی نکند، ممکن است از جاسازی فعالیت پشتیبانی نکند.

منابع بیشتر