تقدّم الأجهزة القابلة للطي تجارب مشاهدة فريدة. يتيح لك وضع الشاشة الخلفية ووضع Dual Screen إنشاء ميزات عرض خاصة للأجهزة القابلة للطي، مثل معاينة صور السيلفي الملتقَطة بالكاميرا الخلفية وعرض شاشات مختلفة في الوقت نفسه على الشاشتَين الداخلية والخارجية.
وضع الشاشة الخلفية
عند فتح جهاز قابل للطي، تكون الشاشة الداخلية فقط هي النشطة عادةً. يتيح لك وضع الشاشة الخلفية نقل نشاط إلى الشاشة الخارجية لجهاز قابل للطي، والتي تكون عادةً غير مواجهة للمستخدم أثناء فتح الجهاز. ويتم إيقاف الشاشة الداخلية تلقائيًا.
يمكنك عرض معاينة الكاميرا على الشاشة الخارجية، ما يتيح للمستخدمين التقاط صور سيلفي باستخدام الكاميرا الخلفية، التي تقدّم عادةً أداءً أفضل بكثير من الكاميرا الأمامية في التقاط الصور.
لتفعيل وضع الشاشة الخلفية، يجب أن يستجيب المستخدمون لمربّع حوار للسماح للتطبيق بتبديل الشاشات، على سبيل المثال:
ينشئ النظام مربّع الحوار، لذا لا تحتاج إلى إجراء أي عملية تطوير. تظهر مربّعات حوار مختلفة حسب حالة الجهاز. على سبيل المثال، يطلب النظام من المستخدمين فتح الجهاز إذا كان مغلقًا. لا يمكنك تخصيص مربّع الحوار، وقد يختلف على الأجهزة من مصنّعين مختلفين.
يمكنك تجربة وضع الشاشة الخلفية باستخدام تطبيق "الكاميرا" على Pixel Fold. يمكنك الاطّلاع على نموذج للتنفيذ في الدرس التطبيقي تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام مكتبة Jetpack WindowManager.
وضع Dual Screen
يتيح لك وضع Dual Screen عرض المحتوى على كلتا شاشتَي جهاز قابل للطي في الوقت نفسه. يتوفّر وضع Dual Screen على Pixel Fold الذي يعمل بالإصدار 14 من Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.
من الأمثلة على حالات الاستخدام ميزة "الترجمة الفورية" في وضع Dual Screen.
تفعيل الأوضاع آليًا
يمكنك الوصول إلى وضع الشاشة الخلفية ووضع Dual Screen من خلال واجهات برمجة التطبيقات Jetpack WindowManager، بدءًا من إصدار المكتبة 1.2.0-beta03.
أضِف تبعية WindowManager إلى ملف build.gradle الخاص بوحدة تطبيقك:
Kotlin
dependencies {
// Define window_version in your project's build configuration.
implementation("androidx.window:window:$window_version")
}
أنيق
dependencies {
// TODO: 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 مع نوعَين من العمليات:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA: تُستخدَم لبدء وضع Dual ScreenWindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA: تُستخدَم لبدء وضع الشاشة الخلفية
في ما يلي مثال على كيفية تعريف متغيّرات لوضع الشاشة الخلفية ووضع 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 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 argument.
تستخدم واجهة برمجة التطبيقات طريقة المتتبِّع: عند تقديم طلب لمشاركة العرض للمحتوى على الشاشة الأخرى لجهاز قابل للطي، تبدأ جلسة يتم إرجاعها من خلال طريقة onSessionStarted() للمتتبِّع. عند إغلاق الـ
جلسة، تتلقّى تأكيدًا في طريقة onSessionEnded().
لإنشاء متتبِّع، عليك تنفيذ واجهة WindowAreaPresentationSessionCallback:
Kotlin
class ExampleActivity : ComponentActivity(), WindowAreaPresentationSessionCallback {
Java
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
يحتاج المتتبِّع إلى تنفيذ الطرق onSessionStarted() وonSessionEnded(),
وonContainerVisibilityChanged(). تُعلمك طرق معاودة الاتصال بحالة الجلسة وتتيح لك تعديل التطبيق وفقًا لذلك.
تتلقّى طريقة معاودة الاتصال onSessionStarted() عنصر WindowAreaSessionPresenter كـ
معلَمة. هذه المعلَمة هي الحاوية التي تتيح لك الوصول إلى مساحة نافذة وعرض المحتوى. يمكن للنظام إغلاق العرض تلقائيًا عندما يغادر المستخدم نافذة التطبيق الرئيسية، أو يمكن إغلاق العرض من خلال استدعاء WindowAreaSessionPresenter#close().
بالنسبة إلى عمليات معاودة الاتصال الأخرى، يمكنك ببساطة التحقّق من أي أخطاء في نص الدالة وتسجيل الحالة:
Kotlin
override fun onSessionStarted(session: WindowAreaSessionPresenter) { windowAreaSession = session session.setContentView(ComposeView(session.context).apply { setContent { MyScreen() } }) } 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.
وضع الشاشة الخلفية
على غرار مثال وضع Dual Screen، يُغلِق المثال التالي للدالة
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.
تعمل واجهة برمجة التطبيقات Rear Display API بطريقة المتتبِّع: عند طلب نقل المحتوى إلى الشاشة الأخرى، تبدأ جلسة يتم عرضها من خلال طريقة onSessionStarted() للمتتبِّع. إذا أردت بدلاً من ذلك العودة إلى الشاشة الداخلية (الأكبر)، عليك إغلاق الجلسة، وستتلقّى تأكيدًا في طريقة onSessionEnded().
Kotlin
override fun onSessionStarted(session: WindowAreaSession) { 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: