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