تتيح الشاشات الكبيرة غير المطوية والأوضاع الفريدة للطي تجارب جديدة للمستخدمين على الأجهزة القابلة للطي. لجعل تطبيقك متوافقًا مع الأجهزة القابلة للطي، استخدِم مكتبة 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
: ما إذا كان الطي أو المفصل ينشئان منطقتَي عرض منطقيتَين، صحيح أو خطأ
يتم دائمًا الإبلاغ عن isSeparating
على الجهاز القابل للطي باعتباره "صحيح" بسبب HALF_OPENED
لأن الشاشة مقسمة إلى منطقتَي عرض. بالإضافة إلى ذلك، يكون الخيار 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 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك استدعاء واجهة برمجة تطبيقات متزامنة لتحديد ما إذا كان الجهاز متوافقًا مع وضع "على سطح مستوٍ" بغض النظر عن حالة الجهاز الحالية.
تقدّم واجهة برمجة التطبيقات قائمة بالوضعيات التي يتيح الجهاز رصدها. إذا كانت القائمة تتضمّن وضع الجهاز على سطح مستوٍ، يمكنك تقسيم تنسيق تطبيقك ليتوافق مع الوضع وإجراء اختبارات أ/ب على واجهة مستخدم تطبيقك لتنسيقَي الوضع على سطح مستوٍ وملء الشاشة.
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