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

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

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

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

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

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

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

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

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

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

يتيح لك وضع Dual Screen عرض المحتوى على كلتا شاشتَي الجهاز القابل للطيّ عند في نفس الوقت. يتوفّر وضع Dual Screen على هواتف Pixel Fold التي تعمل بنظام التشغيل Android 14. (المستوى 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 الرسمي لتوجيه المستخدمين إلى كيفية تفعيل وضع 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}");
    }
}

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

مصادر إضافية