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

ينشئ النظام مربّع الحوار، لذا ليس عليك إجراء أي تطوير. تظهر مربّعات حوار مختلفة حسب حالة الجهاز، على سبيل المثال، يوجّه النظام المستخدمين إلى فتح الجهاز إذا كان مغلقًا. لا يمكنك التخصيص مربع الحوار، ويمكن أن يختلف على أجهزة من مصنّعين أصليين مختلفين.
يمكنك تجربة وضع "الشاشة الخلفية" باستخدام تطبيق كاميرا Pixel Fold. يمكنك الاطّلاع على نموذج لتنفيذ ذلك في الدرس التطبيقي تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager.
وضع "الشاشة المزدوجة"
يتيح لك وضع Dual Screen عرض المحتوى على كلتا شاشتَي الجهاز القابل للطيّ عند في نفس الوقت. يتوفّر وضع Dual Screen على هواتف Pixel Fold التي تعمل بنظام التشغيل Android 14. (المستوى 34 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث
ومن الأمثلة على حالات الاستخدام ميزة "الترجمة الفورية" في وضع Dual Screen.

تفعيل الأوضاع آليًا
يمكنك الوصول إلى وضع الشاشة الخلفية ووضع الشاشة المزدوجة من خلال جهاز Jetpack. واجهات برمجة تطبيقات WindowManager بدءًا من إصدار المكتبة 1.2.0-beta03
أضِف تبعية WindowManager إلى ملف build.gradle
لوحدة تطبيقك:
رائع
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Kotlin
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
نقطة الدخول هي WindowAreaController
، والتي توفر
المعلومات والسلوكيات المتعلقة بنقل النوافذ بين الشاشات أو بين الشاشات
عرض مناطق على الجهاز. تسمح لك دالة WindowAreaController
بطلب البحث في قائمة
كائنات WindowAreaInfo
المتاحة.
استخدِم WindowAreaInfo
للوصول إلى WindowAreaSession
، وهي واجهة تمثل ميزة منطقة النافذة النشطة. استخدِم WindowAreaSession
لتحديد
مدى توفّر WindowAreaCapability
معيّن.
ترتبط كل إمكانية بوحدة WindowAreaCapability.Operation
معيّنة.
في الإصدار 1.2.0-beta03، يتوافق Jetpack WindowManager مع نوعَين من العمليات:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
، وهي يُستخدَم لبدء وضع الشاشة المزدوجةWindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
، الذي يُستخدَم لبدء وضع العرض الخلفي
في ما يلي مثال على كيفية تحديد متغيّرات لوضع الشاشة الخلفية و وضع الشاشة المزدوجة في النشاط الرئيسي لتطبيقك:
Kotlin
private lateinit var windowAreaController: WindowAreaController private lateinit var displayExecutor: Executor private var windowAreaSession: WindowAreaSession? = null private var windowAreaInfo: WindowAreaInfo? = null private var capabilityStatus: WindowAreaCapability.Status = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
Java
private WindowAreaControllerCallbackAdapter windowAreaController = null; private Executor displayExecutor = null; private WindowAreaSessionPresenter windowAreaSession = null; private WindowAreaInfo windowAreaInfo = null; private WindowAreaCapability.Status capabilityStatus = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED; private WindowAreaCapability.Operation dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA; private WindowAreaCapability.Operation rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;
وفي ما يلي طريقة إعداد المتغيّرات في طريقة onCreate()
في
النشاط:
Kotlin
displayExecutor = ContextCompat.getMainExecutor(this) windowAreaController = WindowAreaController.getOrCreate() lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowAreaController.windowAreaInfos .map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } } .onEach { info -> windowAreaInfo = info } .map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED } .distinctUntilChanged() .collect { capabilityStatus = it } } }
Java
displayExecutor = ContextCompat.getMainExecutor(this); windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate()); windowAreaController.addWindowAreaInfoListListener(displayExecutor, this); windowAreaController.addWindowAreaInfoListListener(displayExecutor, windowAreaInfos -> { for(WindowAreaInfo newInfo : windowAreaInfos){ if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){ windowAreaInfo = newInfo; capabilityStatus = newInfo.getCapability(presentOperation).getStatus(); break; } } });
قبل بدء عملية، تحقَّق من توفّر ميزة معيّنة:
Kotlin
when (capabilityStatus) { WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { // The selected display mode is not supported on this device. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> { // The selected display mode is not available. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { // The selected display mode is available and can be enabled. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> { // The selected display mode is already active. } else -> { // The selected display mode status is unknown. } }
Java
if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) { // The selected display mode is not supported on this device. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) { // The selected display mode is not available. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) { // The selected display mode is available and can be enabled. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) { // The selected display mode is already active. } else { // The selected display mode status is unknown. }
وضع Dual Screen
يغلق المثال التالي الجلسة إذا كانت الميزة نشطة، أو
يستدعي الدالة presentContentOnWindowArea()
في حال عدم تنشيطها:
Kotlin
fun toggleDualScreenMode() { if (windowAreaSession != null) { windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.presentContentOnWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaPresentationSessionCallback = this ) } } }
Java
private void toggleDualScreenMode() { if(windowAreaSession != null) { windowAreaSession.close(); } else { Binder token = windowAreaInfo.getToken(); windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this); } }
يُرجى ملاحظة استخدام النشاط الرئيسي للتطبيق كوسيطة
WindowAreaPresentationSessionCallback
.
تستخدم واجهة برمجة التطبيقات أسلوب المستمع: عند تقديم طلب بعرض المحتوى
إلى الشاشة الأخرى للجهاز القابل للطي، يتم بدء جلسة
من خلال طريقة onSessionStarted()
المستمعة. عند إغلاق
الجلسة، ستتلقّى رسالة تأكيد في طريقة onSessionEnded()
.
لإنشاء مستمع، نفِّذ واجهة WindowAreaPresentationSessionCallback
:
Kotlin
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
Java
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
على المستمع تنفيذ onSessionStarted()
، onSessionEnded(),
وonContainerVisibilityChanged()
. تُعلمك طرق الاستدعاء
بحالة الجلسة وتتيح لك تعديل التطبيق وفقًا لذلك.
يتلقّى ردّ الاتصال onSessionStarted()
WindowAreaSessionPresenter
كأحد
الوسيطات. الوسيطة هي الحاوية التي تتيح لك الوصول إلى منطقة نافذة
وعرض المحتوى. يمكن للنظام إغلاق عرض الشرائح تلقائيًا
عندما يغادر المستخدم نافذة التطبيق الأساسية، أو يمكن إغلاق عرض الشرائح
باستخدام WindowAreaSessionPresenter#close()
.
بالنسبة إلى الاستدعاءات الأخرى، ببساطة، تحقق فقط من نص الدالة الأخطاء وتسجيل الحالة:
Kotlin
override fun onSessionStarted(session: WindowAreaSessionPresenter) { windowAreaSession = session val view = TextView(session.context) view.text = "Hello world!" session.setContentView(view) } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } } override fun onContainerVisibilityChanged(isVisible: Boolean) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible") }
Java
@Override public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) { windowAreaSession = session; TextView view = new TextView(session.getContext()); view.setText("Hello world, from the other screen!"); session.setContentView(view); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } } @Override public void onContainerVisibilityChanged(boolean isVisible) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible); }
للحفاظ على الاتساق في المنظومة المتكاملة، استخدِم رمزDual Screen الرسمي لتوجيه المستخدمين إلى كيفية تفعيل وضع Dual Screen أو إيقافه.
للاطّلاع على نموذج عمل، يُرجى الاطّلاع على DualscreenActivity.kt.
وضع العرض الخلفي
على غرار مثال وضع الشاشة المزدوجة، يغلق المثال التالي لدالة
toggleRearDisplayMode()
الجلسة إذا كانت الميزة
مفعّلة، أو يستدعي الدالة transferActivityToWindowArea()
بخلاف ذلك:
Kotlin
fun toggleRearDisplayMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo?.getActiveSession( operation ) } windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.transferActivityToWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaSessionCallback = this ) } } }
Java
void toggleDualScreenMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo.getActiveSession( operation ) } windowAreaSession.close() } else { Binder token = windowAreaInfo.getToken(); windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this); } }
وفي هذه الحالة، يُستخدَم النشاط المعروض على أنّه WindowAreaSessionCallback
.
وهو أسهل من ذي قبل لأن الاستدعاء لا يتلقى مقدم العرض
تسمح بعرض المحتوى على مساحة نافذة ولكن بدلاً من ذلك تنقل المحتوى بالكامل
نشاط في منطقة أخرى وهي:
Kotlin
override fun onSessionStarted() { Log.d(logTag, "onSessionStarted") } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } }
Java
@Override public void onSessionStarted(){ Log.d(logTag, "onSessionStarted"); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } }
للحفاظ على اتّساق المنظومة المتكاملة، استخدِم كاميرا الكاميرا الخلفية. للإشارة إلى كيفية تفعيل وضع الشاشة الخلفية أو إيقافه للمستخدمين.
مصادر إضافية
- تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager درس تطبيقي حول الترميز
- ملخّص حزمة
androidx.window.area
- نموذج رمز Jetpack WindowManager البرمجي: