تقدّم الأجهزة القابلة للطي تجارب مشاهدة فريدة. يتيح لك وضع العرض الخلفي ووضع الشاشة المزدوجة إنشاء ميزات عرض خاصة للأجهزة القابلة للطي، مثل معاينة الصورة الذاتية بواسطة الكاميرا الخلفية وشاشات عرض متزامنة ومختلفة على الشاشة الداخلية والخارجية.
وضع الشاشة الخلفية
عندما يكون الجهاز القابل للطي غير مطوي، يتم عادةً تنشيط الشاشة الداخلية فقط. يتيح لك وضع "الشاشة الخلفية" نقل نشاط معيّن إلى الشاشة الخارجية للجهاز القابل للطي، الذي عادةً ما يكون بعيدًا عن المستخدم عندما يكون الجهاز غير مطوي. يتم إيقاف الشاشة الداخلية تلقائيًا.
وهناك تطبيق جديد يتيح عرض معاينة الكاميرا على الشاشة الخارجية ليتمكّن المستخدمون من التقاط صور ذاتية باستخدام الكاميرا الخلفية التي توفّر عادةً أداءً أفضل لالتقاط الصور.
لتفعيل وضع الشاشة الخلفية، يستجيب المستخدمون لمربّع حوار للسماح للتطبيق بالتبديل بين الشاشات، على سبيل المثال:
ينشئ النظام مربّع الحوار، لذلك لا تتطلّب عملية التطوير من جانبك. تظهر مربّعات حوار مختلفة استنادًا إلى حالة الجهاز. على سبيل المثال، يوجِّه النظام المستخدمين إلى فتح الجهاز في حال إغلاق الجهاز. لا يمكنك تخصيص مربّع الحوار، ولكن يمكن أن يختلف على أجهزة من مصنّعين أصليين مختلفين.
يمكنك تجربة وضع الشاشة الخلفية باستخدام تطبيق "كاميرا Pixel Fold". يمكنك الاطّلاع على نموذج لتنفيذ هذا الوضع في الدرس التطبيقي حول الترميز استكشاف تجربة الكاميرا.
وضع 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
، الذي يُستخدم لبدء وضع Dual ScreenWindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
، الذي يُستخدم لتفعيل وضع "الشاشة الخلفية"
في ما يلي مثال على طريقة التعرّف على المتغيّرات لوضع الشاشة الخلفية ووضع Dual Screen في النشاط الرئيسي لتطبيقك:
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 لإعلام المستخدمين بكيفية تفعيل وضع 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:
أفلام مُقترَحة لك
- ملاحظة: يظهر نص الرابط عند إيقاف JavaScript
- تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager