تتيح الشاشات الكبيرة غير المطوية وحالات الطي الفريدة تجارب جديدة للمستخدمين على الأجهزة القابلة للطي. لإعداد تطبيقك ليكون متوافقًا مع الأجهزة القابلة للطي، استخدِم مكتبة 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);
}
بعد التأكّد من أنّ الجهاز في وضعية سطح الطاولة، عدِّل تخطيط تطبيقك وفقًا لذلك. بالنسبة إلى تطبيقات الوسائط، يعني ذلك عادةً وضع عناصر التشغيل في الجزء الظاهر من الشاشة مباشرةً بعد فتح التطبيق، ووضع عناصر التحكّم والمحتوى التكميلي بعد ذلك مباشرةً لتوفير تجربة مشاهدة أو استماع بدون استخدام اليدين.
في نظام التشغيل Android 15 (المستوى 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
استرداد مقاييس النافذة الحالية والحد الأقصى لها. على غرار WindowMetrics
الذي تم تقديمه في المستوى 30 لواجهة برمجة التطبيقات، يوفّر WindowManager
WindowMetrics
حدود النافذة، ولكن تتوافق واجهة برمجة التطبيقات مع الإصدارات القديمة وصولاً إلى المستوى 14 لواجهة برمجة التطبيقات.
راجِع مقالة استخدام فئات أحجام النوافذ.
مراجع إضافية
نماذج
- Jetpack WindowManager: مثال على كيفية استخدام مكتبة Jetpack WindowManager
- Jetcaster : تنفيذ وضعية سطح المكتب باستخدام Compose
الدروس التطبيقية حول الترميز
- دَعْم الأجهزة القابلة للطي والمزودة بشاشتين من خلال مكتبة Jetpack WindowManager
- تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager