تقدّم الأجهزة القابلة للطي تجارب مشاهدة فريدة. يتيح لك وضع "العرض الخلفي" و"وضع الشاشة المزدوجة" إنشاء ميزات عرض خاصة للأجهزة القابلة للطي، مثلاً معاينة الصور الذاتية بالكاميرا الخلفية والشاشات المتزامنة والمختلفة على الشاشات الداخلية والخارجية.
وضع العرض الخلفي
عندما يكون الجهاز القابل للطي غير مطوي، يتم عادةً تشغيل الشاشة الداخلية فقط. يتيح لك وضع العرض الخلفي نقل أحد الأنشطة إلى الشاشة الخارجية لجهاز قابل للطي، بحيث يكون الجهاز عادةً بعيدًا عن المستخدم عندما يكون الجهاز غير مطوي. يتم إيقاف الشاشة الداخلية تلقائيًا.
هناك تطبيق جديد يتمثل في عرض معاينة الكاميرا على الشاشة الخارجية، حتى يتمكن المستخدمون من التقاط صور ذاتية باستخدام الكاميرا الخلفية، والتي عادةً ما توفر أداءً أفضل بكثير في التقاط الصور.
لتفعيل وضع العرض الخلفي، يستجيب المستخدمون لمربّع الحوار للسماح للتطبيق بتبديل الشاشات، على سبيل المثال:
ينشئ النظام مربع الحوار، لذا لا حاجة إلى إجراء أي تطوير من جانبك. تظهر مربعات حوار مختلفة حسب حالة الجهاز، على سبيل المثال، يوجّه النظام المستخدمين إلى فتح الجهاز إذا كان الجهاز مغلقًا. لا يمكنك تخصيص مربّع الحوار، ولكن يمكن أن يختلف على الأجهزة التابعة لمصنّعين أصليين مختلفين.
يمكنك تجربة وضع العرض الخلفي باستخدام تطبيق كاميرا Pixel Fold. يمكنك الاطّلاع على نموذج لتنفيذه في الدرس التطبيقي حول الترميز فتح تجربة الكاميرا.
وضع Dual Screen
يتيح لك وضع Dual Screen عرض المحتوى على كلتا شاشتَي العرض القابل للطي في الوقت نفسه. يتوفّر وضع Dual Screen على هواتف Pixel Fold التي تعمل بنظام التشغيل Android 14 (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.
من أمثلة حالات الاستخدام ميزة "الترجمة الفورية" كشاشة مزدوجة.
تفعيل الأوضاع آليًا
يمكنك استخدام وضع العرض الخلفي ووضع الشاشة المزدوجة من خلال واجهات برمجة تطبيقات 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-الإصدار التجريبي 03، يتيح Jetpack WindowManager نوعين من العمليات:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
: يُستخدم لبدء وضع Dual ScreenWindowAreaCapability.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 currently available to be enabled. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { // The selected display mode is currently available to 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 currently available to be enabled. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) { // The selected display mode is currently available to 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 لإعلام المستخدمين بكيفية تفعيل وضع الشاشة المزدوجة أو إيقافه.
للحصول على عينة عملية، اطلع على 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}"); } }
للحفاظ على الاتّساق في المنظومة المتكاملة، استخدِم الرمز الرسمي للكاميرا الخلفية لإطلاع المستخدمين على كيفية تفعيل وضع العرض الخلفي أو إيقافه.
مراجع إضافية
- اكتشِف الدرس التطبيقي حول الترميز الخاص بتجربة الكاميرا.
- ملخّص الحزمة من
androidx.window.area
- نموذج رمز Jetpack WindowManager: