إتاحة أوضاع العرض القابلة للطي

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

وضع الشاشة الخلفية

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

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

لتفعيل وضع الشاشة الخلفية، على المستخدمين الرد على مربع حوار للسماح للتطبيق بتبديل الشاشات، على سبيل المثال:

الشكل 1. مربّع حوار النظام للسماح ببدء وضع الشاشة الخلفية

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

يمكنك تجربة وضع الشاشة الخلفية باستخدام تطبيق "الكاميرا" على Pixel Fold. يمكنك الاطّلاع على نموذج للتنفيذ في الدرس التطبيقي تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager.

وضع الشاشة المزدوجة

يتيح لك وضع الشاشة المزدوجة عرض المحتوى على شاشتَي الجهاز القابل للطي في الوقت نفسه. يتوفّر وضع الشاشة المزدوجة على هواتف Pixel Fold التي تعمل بالإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.

من الأمثلة على حالات الاستخدام ميزة "الترجمة الفورية" في وضع Dual Screen.

الشكل 2. تطبيق "الترجمة الفورية" على شاشة مزدوجة يعرض محتوًى مختلفًا على الشاشتَين الأمامية والخلفية

تفعيل الأوضاع آليًا

يمكنك الوصول إلى وضع الشاشة الخلفية ووضع الشاشة المزدوجة من خلال واجهات برمجة التطبيقات Jetpack WindowManager، بدءًا من إصدار المكتبة 1.2.0-beta03.

أضِف تبعية WindowManager إلى ملف build.gradle الخاص بوحدة تطبيقك:

Groovy

dependencies {
    // TODO: Define window_version in your project's build configuration.
    implementation "androidx.window:window:$window_version"
}

Kotlin

dependencies {
    // Define window_version in your project's build configuration.
    implementation("androidx.window:window:$window_version")
}

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

استخدِم WindowAreaInfo للوصول إلى WindowAreaSession، وهي واجهة تمثّل ميزة منطقة النافذة النشطة. استخدِم WindowAreaSession لتحديد مدى توفّر WindowAreaCapability معيّن.

ترتبط كل إمكانية WindowAreaCapability.Operation معيّن. في الإصدار 1.2.0-beta03، تتيح مكتبة Jetpack WindowManager نوعَين من العمليات:

في ما يلي مثال على كيفية تعريف متغيرات لوضع الشاشة الخلفية ووضع الشاشة المزدوجة في النشاط الرئيسي لتطبيقك:

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.
}

وضع الشاشة المزدوجة

يُغلق المثال التالي الجلسة إذا كانت الإمكانية نشطة، أو يستدعي الدالة 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 toggleRearDisplayMode() {
    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}");
    }
}

للحفاظ على التناسق في جميع أنحاء النظام المتكامل، استخدِم رمز الكاميرا الخلفية الرسمي لتوضيح كيفية تفعيل وضع الشاشة الخلفية أو إيقافه للمستخدمين.

مراجع إضافية