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

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

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

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

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

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

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

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

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

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

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

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

الشكل 2. ترجمة فورية ثنائية الشاشة تعرض محتوى مختلفًا على الشاشتَين الأمامية والخلفية

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

يمكنك الاستفادة من وضع الشاشة الخلفية ووضع الشاشة المزدوجة من خلال واجهات برمجة تطبيقات 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 مع نوعَين من العمليات:

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

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}");
    }
}

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

مصادر إضافية