إتاحة وضع النوافذ على سطح المكتب

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

في الشكل 1، يمكنك الاطّلاع على تنظيم الشاشة مع تفعيل ميزة "العرض في نافذة". ملاحظات:

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

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

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

الشكل 2: اضغط مع الاستمرار على شريط التحكّم في نافذة التطبيق واسحبه لتفعيل ميزة "العرض في نافذة".

يمكن للمستخدمين أيضًا تفعيل ميزة "العرض في نافذة" من القائمة التي تظهر أسفل شريط التحكّم عند النقر عليه أو استخدامه أو استخدام اختصار لوحة المفاتيح مفتاح Meta (Windows أو Command أو بحث) + Ctrl + السهم للأسفل.

يخرج المستخدمون من ميزة "العرض في نافذة" عن طريق إغلاق جميع النوافذ النشطة أو عن طريق سحب شريط التحكّم في أعلى نافذة سطح المكتب وسحب التطبيق إلى أعلى الشاشة. يؤدي اختصار لوحة المفاتيح Meta + H أيضًا إلى الخروج من ميزة "العرض في نافذة" وتشغيل التطبيقات في وضع ملء الشاشة مرة أخرى.

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

إمكانية تغيير الحجم ووضع التوافق

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

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

يتم تغيير حجم واجهة مستخدم التطبيقات التي تم الإعلان عنها على أنّها غير قابلة لتغيير الحجم (أي resizeableActivity = false) مع الحفاظ على نسبة العرض إلى الارتفاع نفسها.

الشكل 4: تغيير حجم واجهة مستخدم تطبيق غير قابل لتغيير الحجم مع تغيير حجم النافذة

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

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

لمزيد من المعلومات عن وضع التوافق لتطبيقات الكاميرا، يُرجى الاطّلاع على وضع التوافق مع الجهاز.

الشكل 5: تحافظ عدسة الكاميرا على نسبة العرض إلى الارتفاع مع تغيير حجم النافذة.

حواف رأسية قابلة للتخصيص

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

Chrome قبل وبعد تنفيذ العناوين المخصّصة
الشكل 6. Chrome قبل تنفيذ العناوين المخصّصة وبعده

التنفيذ

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

window.insetsController?.setSystemBarsAppearance(
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
)

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

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CaptionBar() {
    if (WindowInsets.isCaptionBarVisible) {
        Row(
            modifier = Modifier
                .windowInsetsTopHeight(WindowInsets.captionBar)
                .fillMaxWidth()
                .background(if (isSystemInDarkTheme()) Color.White else Color.Black),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Caption Bar Title",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier.padding(4.dp)
            )
        }
    }
}

  • setSystemBarsAppearance(appearance,mask): يضبط النمط المرئي لأشرطة النظام. يحدّد المعلمة الأولى علامات المظهر المستهدَفة، بينما تعمل المعلمة الثانية كقناع للتحكّم في العلامات المحدّدة التي يتم تعديلها.

  • windowInsetsTopHeight(): يضبط تلقائيًا ارتفاع العنصر القابل للإنشاء ليتطابق مع شريط العنوان في النظام، ما يساعد الخلفية المخصّصة على ملء منطقة العنوان بدون وضع قيم البكسل بشكل ثابت.

  • WindowInsets.captionBar: يوفّر أبعاد عناصر التحكّم في ميزة "العرض في نافذة" (إغلاق وتكبير وما إلى ذلك)، ما يسمح بتغيير حجم واجهة المستخدم أو إخفائها تلقائيًا عند الدخول إلى ميزة "العرض في نافذة" أو الخروج منها.

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

واجهة المستخدم

لتجنُّب تداخل واجهة المستخدم مع أزرار النظام، يوفّر Android 15 الـ WindowInsets#getBoundingRects() method. تعرض هذه الطريقة قائمة بعناصر Rect التي تمثّل المناطق التي تشغلها عناصر النظام. وأي مساحة متبقية في شريط العنوان هي منطقة آمنة يمكنك وضع محتوى مخصّص فيها بأمان.

يمكنك تبديل مظهر عناصر التسميات التوضيحية في النظام للمظهرَين الفاتح والداكن باستخدام APPEARANCE_LIGHT_CAPTION_BARS. يمكنك الوصول إلى الحواف باستخدام WindowInsets.Companion.captionBar() في Compose أو WindowInsets.Type.captionBar() في طريقة العرض.

لمزيد من المعلومات، يُرجى الاطّلاع على لمحة عن حواف النوافذ.

تعدّد المهام ودعم فتح نسختين من التطبيق

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

بدءًا من Android 15، يمكنك استخدام PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI. من خلال ضبط هذه السمة في AndroidManifest.xml، يمكنك تحديد أنّ واجهة مستخدم النظام يجب أن توفّر خيارات (مثل زر "نافذة جديدة") لتشغيل التطبيق في مثيلات متعددة.

<application>
    <property
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</application>

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

إدارة مثيلات التطبيق باستخدام إيماءات السحب

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

الشكل 7: بدء مثيل جديد من Chrome عن طريق سحب علامة تبويب خارج نافذة سطح المكتب

نقل البيانات باستخدام السحب والإفلات

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

يقدّم Android 15 علامتَين رئيسيتَين لميزة "العرض في نافذة" على نمط سطح المكتب و فتح نسختين من التطبيق:

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

Modifier.dragAndDropSource { _ ->
    DragAndDropTransferData(
        clipData = ClipData.newPlainText("label", "Your data"),
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
    )
}

  • DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG: تسمح للمستخدمين ببدء مثيل جديد من تطبيقك عن طريق إفلات المحتوى المسحوب في منطقة فارغة من الشاشة، إذا لم تتعامل أي نافذة أخرى مع عملية الإفلات.
    • عند استخدام هذه العلامة، عليك توفير IntentSender باستخدام ClipData.Item.Builder#setIntentSender()، الذي يستخدمه النظام لـ تشغيل النشاط الجديد في حال حدوث عملية إفلات لم تتم معالجتها.

Modifier.dragAndDropSource { _ ->
    val intent = Intent.makeMainActivity(activity.componentName).apply {
        putExtra("EXTRA_ITEM_ID", itemId)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
                Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
    }

    val pendingIntent = PendingIntent.getActivity(
        activity, 0, intent, PendingIntent.FLAG_IMMUTABLE
    )

    val data = ClipData(
        "Item $itemId",
        arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT),
        ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build()
    )

    DragAndDropTransferData(
        clipData = data,
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or
                View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
    )
}

تلقّي البيانات المنقولة

لقبول البيانات من مثيل آخر، استخدِم المعدِّل dragAndDropTarget. عليك طلب الأذونات بشكل صريح إذا كانت البيانات واردة من مثيل أو تطبيق مختلفَين.

Modifier.dragAndDropTarget(
    shouldStartDragAndDrop = { event ->
        event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
    },
    target = object : DragAndDropTarget {
        override fun onDrop(event: DragAndDropEvent): Boolean {
            requestDragAndDropPermissions(activity, event.toAndroidDragEvent())
            val clipData = event.toAndroidDragEvent().clipData
            val item = clipData?.getItemAt(0)?.text
            if (item != null) {
                // Process the dropped text item here
            }
            return item != null
        }
    }
)

الخطوات الرئيسية:

  • الفلتر: استخدِم shouldStartDragAndDrop للتحقّق مما إذا كان نوع MIME للبيانات الواردة متوافقًا.
  • الأذونات: استدعِ requestDragAndDropPermissions(event) للوصول إلى الـ بيانات.
  • المعالجة: استخرِج البيانات في onDrop معاودة الاتصال.

تحسينات إضافية

يمكنك تخصيص عمليات تشغيل التطبيقات ونقل التطبيقات من ميزة "العرض في نافذة" إلى وضع ملء الشاشة.

تحديد الحجم والموضع التلقائيَين

لا تحتاج جميع التطبيقات، حتى إذا كانت قابلة لتغيير الحجم، إلى نافذة كبيرة لتقديم قيمة للمستخدم. يمكنك استخدام الطريقة ActivityOptions#setLaunchBounds() لتحديد حجم وموضع تلقائيَين عند تشغيل نشاط.

الدخول إلى وضع ملء الشاشة من مساحة سطح المكتب

يمكن أن تنتقل التطبيقات إلى وضع ملء الشاشة عن طريق استدعاء Activity#requestFullScreenMode(). تعرض هذه الطريقة التطبيق في وضع ملء الشاشة مباشرةً من ميزة "العرض في نافذة".