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

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

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

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

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

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

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

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

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

وضع 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 مع نوعَين من العمليات:

في ما يلي مثال على طريقة التعرّف على المتغيّرات لوضع الشاشة الخلفية ووضع 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}");
    }
}

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

مصادر إضافية