إنّ الشاشات الكبيرة غير المطوية والحالة الفريدة المطوية توفّر للمستخدمين تجربة جديدة. أجهزة قابلة للطي. لجعل تطبيقك متوافقًا مع الأجهزة القابلة للطي، استخدِم مكتبة Jetpack WindowManager التي توفّر واجهة برمجة تطبيقات لميزات نوافذ الأجهزة القابلة للطي، مثل الطيات والمفصلات. عندما يكون تطبيقك مؤهَّلاً للطي، يمكنه تعديل تنسيقه. لتجنب وضع المحتوى المهم في منطقة الطيات أو المفصلات واستخدام الطيات والمفصلات كفواصل طبيعية.
يمكن أن يساعد معرفة ما إذا كان الجهاز متوافقًا مع أوضاع معيّنة، مثل وضعه على سطح مستوٍ أو على شكل كتاب، في توجيه القرارات بشأن إتاحة تنسيقات مختلفة أو توفير ميزات معيّنة.
معلومات النافذة
واجهة WindowInfoTracker
في Jetpack WindowManager تعرض النافذة
معلومات التخطيط. تعرض الطريقة windowLayoutInfo()
في الواجهة علامة
مصدر بيانات WindowLayoutInfo
التي تحدِّد تطبيقك بشأن جهاز قابل للطيّ.
حالة الطي في الجهاز. تنشئ الطريقة WindowInfoTracker#getOrCreate()
مثال لـ WindowInfoTracker
.
يقدّم WindowManager إمكانية جمع بيانات WindowLayoutInfo
باستخدام
عمليات Kotlin وعمليات الاستدعاء في Java.
مسارات Kotlin
لبدء جمع البيانات باستخدام WindowLayoutInfo
وإيقافها، يمكنك استخدام نموذج قابل لإعادة التشغيل
الكوروتين المدرك لمراحل النشاط الذي يتم فيه تضمين مجموعة الرموز repeatOnLifecycle
يتم تنفيذه عندما لا تقل دورة الحياة عن STARTED
، ويتم إيقافها عندما
دورة الحياة هي STOPPED
. تتم إعادة بدء تنفيذ مجموعة الرموز تلقائيًا
عندما تكون دورة الحياة STARTED
مرة أخرى. في المثال التالي، تشير مجموعة الرموز
تجمع بيانات "WindowLayoutInfo
" وتستخدمها:
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
.windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}
عمليات معاودة الاتصال بلغة Java
تتيح لك طبقة التوافق مع طلبات إعادة الاتصال المضمّنة في التبعية
androidx.window:window-java
جمع تحديثات
WindowLayoutInfo
بدون استخدام مسار Kotlin. تتضمن الأداة
الفئة WindowInfoTrackerCallbackAdapter
، التي تعدِّل
WindowInfoTracker
لإتاحة تسجيل (وإلغاء تسجيل) معاودة الاتصال إلى
ستتلقّى تحديثات WindowLayoutInfo
، مثل:
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private ActivitySplitLayoutBinding binding;
private final LayoutStateChangeCallback layoutStateChangeCallback =
new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
windowInfoTracker =
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoTracker.addWindowLayoutInfoListener(
this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoTracker
.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
public void accept(WindowLayoutInfo newLayoutInfo) {
SplitLayoutActivity.this.runOnUiThread( () -> {
// Use newLayoutInfo to update the layout.
});
}
}
}
إتاحة RxJava
إذا كنت تستخدم حاليًا RxJava
(الإصدار 2
أو 3
)،
يمكنك الاستفادة من العناصر التي تتيح لك استخدام
Observable
أو Flowable
لجمع تحديثات WindowLayoutInfo
بدون استخدام مسار Kotlin.
تتضمّن طبقة التوافق التي يوفّرها تبعيتي androidx.window:window-rxjava2
و
androidx.window:window-rxjava3
الطريقتَين
WindowInfoTracker#windowLayoutInfoFlowable()
و
WindowInfoTracker#windowLayoutInfoObservable()
، اللتين تتيحان لتطبيقكتلقّي تحديثات WindowLayoutInfo
، على سبيل المثال:
class RxActivity: AppCompatActivity {
private lateinit var binding: ActivityRxBinding
private var disposable: Disposable? = null
private lateinit var observable: Observable<WindowLayoutInfo>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Create a new observable.
observable = WindowInfoTracker.getOrCreate(this@RxActivity)
.windowLayoutInfoObservable(this@RxActivity)
}
@Override
protected void onStart() {
super.onStart();
// Subscribe to receive WindowLayoutInfo updates.
disposable?.dispose()
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
@Override
protected void onStop() {
super.onStop();
// Dispose of the WindowLayoutInfo observable.
disposable?.dispose()
}
}
ميزات الشاشات القابلة للطي
تتيح الفئة WindowLayoutInfo
من Jetpack WindowManager ميزات
نافذة عرض متاحة كقائمة بعناصر DisplayFeature
.
FoldingFeature
هو نوع من DisplayFeature
يقدّم معلومات
حول الشاشات القابلة للطي، بما في ذلك ما يلي:
state
: حالة الجهاز مطويًا،FLAT
أوHALF_OPENED
orientation
: اتجاه الطي أو المفصّلة،HORIZONTAL
أوVERTICAL
occlusionType
: ما إذا كان الجزء المُثنى أو المفصل يخفي جزءًا من الشاشة،NONE
أوFULL
isSeparating
: ما إذا كان الطي أو المفصل ينشئان منطقتَي عرض منطقيتَين، صحيح أو خطأ
يُبلغ الجهاز القابل للطي الذي يكون HALF_OPENED
دائمًا عن isSeparating
على أنّه صحيح
لأنّ الشاشة مُقسّمة إلى منطقتَي عرض. بالإضافة إلى ذلك، يكون الخيار isSeparating
صحيحًا دائمًا على جهاز مزوّد بشاشتَين عندما يمتد التطبيق على كلتا
الشاشتَين.
تمثّل سمة FoldingFeature
bounds
(المكتسَبة من DisplayFeature
)
المستطيل الحدودي لميزة قابلة للطي، مثل الطية أو المفصل.
يمكن استخدام الحدود لوضع العناصر على الشاشة بالنسبة إلى العنصر:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from WindowInfoTracker when the lifecycle is // STARTED and stops collection when the lifecycle is STOPPED. WindowInfoTracker.getOrCreate(this@MainActivity) .windowLayoutInfo(this@MainActivity) .collect { layoutInfo -> // New posture information. val foldingFeature = layoutInfo.displayFeatures .filterIsInstance<FoldingFeature>() .firstOrNull() // Use information from the foldingFeature object. } } } }
Java
private WindowInfoTrackerCallbackAdapter windowInfoTracker; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... windowInfoTracker = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this)); } @Override protected void onStart() { super.onStart(); windowInfoTracker.addWindowLayoutInfoListener( this, Runnable::run, layoutStateChangeCallback); } @Override protected void onStop() { super.onStop(); windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Use newLayoutInfo to update the Layout. List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures(); for (DisplayFeature feature : displayFeatures) { if (feature instanceof FoldingFeature) { // Use information from the feature object. } } } }
وضع "التثبيت على سطح مستوٍ"
وباستخدام المعلومات المضمّنة في عنصر FoldingFeature
، سيتمكّن تطبيقك
في أوضاع مثل "التثبيت على سطح مستوٍ"، حيث يتم وضع الهاتف على سطح،
في وضع أفقي والشاشة القابلة للطي نصف مفتوحة.
يوفر وضع "التثبيت على سطح مستوٍ" للمستخدمين سهولة تشغيل هواتفهم بدون يحملون الهاتف في أيديهم. يُعدّ وضع الجهاز على سطح مستوٍ مثاليًا لمشاهدة الوسائط والتقاط الصور وإجراء مكالمات الفيديو.

يمكنك استخدام FoldingFeature.State
وFoldingFeature.Orientation
لتحديد
ما إذا كان الجهاز في وضع "التثبيت على سطح مستوٍ":
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Java
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
بعد معرفة وضع الجهاز على سطح مستوٍ، عدِّل تنسيق تطبيقك وفقًا لذلك. بالنسبة إلى تطبيقات الوسائط، يعني ذلك عادةً وضع أدوات التشغيل فوق الشاشة ووضع عناصر التحكّم والمحتوى الإضافي تحتها مباشرةً لتوفير تجربة مشاهدة أو استماع بدون استخدام اليدين.
في الإصدار 15 من Android (المستوى 35 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك استدعاء واجهة برمجة تطبيقات متزامنة لتحديد ما إذا كان الجهاز متوافقًا مع وضع "على سطح مستوٍ" بغض النظر عن حالة الجهاز الحالية.
تقدّم واجهة برمجة التطبيقات قائمة بالوضعيات التي يتيح الجهاز قياسها. إذا كانت القائمة يتضمن وضع "الشاشة المسطحة"، يمكنك تقسيم تصميم التطبيق لدعم وضعية وإجراء اختبارات A/B على واجهة المستخدم في تطبيقك للتخطيطات على الطاولة وملء الشاشة.
Kotlin
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(TABLE_TOP)) { // Device supports tabletop posture. } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures(); if (postures.contains(SupportedPosture.TABLETOP)) { // Device supports tabletop posture. } }
أمثلة
تطبيق
MediaPlayerActivity
: التعرّف على كيفية استخدام Media3 Exoplayer وWindowManager لإنشاء فيديو قابل للطي لاعب.تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager: درس تطبيقي: تعرَّف على كيفية تنفيذ وضع "التثبيت على سطح مستوٍ" لتطبيقات التصوير. برنامج تلفزيوني عدسة الكاميرا في النصف العلوي من الشاشة (فوق الجزء المرئي) عناصر التحكم في النصف السفلي (الجزء السفلي غير المرئي من الصفحة).
وضع الكتاب
من الميزات الفريدة الأخرى القابلة للطيّ هي وضعية الكتاب، حيث يكون الجهاز مفتوحًا نصفه. والمفصلة عمودية. إنّ وضع الكتاب هو أمر رائع لقراءة الكتب الإلكترونية. من خلال استخدام وضع "كتاب"، يمكنك قراءة صفحتَين على شاشة كبيرة قابلة للطيّ مثل كتاب مفتوح.
يمكن استخدامه أيضًا للتصوير الفوتوغرافي إذا أردت التقاط صورة لوجه مختلف. أثناء التقاط الصور بدون لمس الجهاز.
تنفيذ وضعية الكتاب باستخدام الأساليب نفسها المستخدمة في وضع "التثبيت على سطح مستوٍ" ويتمثل اختلافهم الوحيد في أنّ الرمز البرمجي يجب أن يتحقّق من أنّ اتجاه ميزة الطي هو عمودي بدلاً من أفقي:
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
Java
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
تغييرات حجم النافذة
يمكن أن تتغير منطقة عرض التطبيق نتيجة لتغيير إعدادات الجهاز، على سبيل المثال، عندما يكون الجهاز مطويًا أو غير مطوي أو يتم تدويره أو عندما تكون النافذة تغيير حجمها في وضع النوافذ المتعددة.
تتيح لك فئة Jetpack WindowManager WindowMetricsCalculator
retrieving current and maximum window metrics. مثل النظام الأساسي
WindowMetrics
الذي تم تقديمه في المستوى 30 لواجهة برمجة التطبيقات، يقدّم WindowManager
WindowMetrics
حدود النافذة، ولكن واجهة برمجة التطبيقات متوافقة مع الإصدارات القديمة
حتى المستوى 14 لواجهة برمجة التطبيقات.
راجِع مقالة استخدام فئات حجم النوافذ.
مصادر إضافية
نماذج
- Jetpack WindowManager: مثال على كيفية استخدام Jetpack مكتبة WindowManager
- Jetcaster: تنفيذ وضع "على سطح مستوٍ" باستخدام Compose
الدروس التطبيقية حول الترميز
- إتاحة استخدام الأجهزة القابلة للطي والمزوّدة بشاشتين من خلال مكتبة Jetpack WindowManager
- تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager