إنّ الشاشات الكبيرة غير المطوية والحالة الفريدة المطوية توفّر للمستخدمين تجارب جديدة على الأجهزة القابلة للطي. لجعل التطبيق مرئيًا، استخدِم مكتبة Jetpack WindowManager التي توفّر واجهة برمجة تطبيقات مع ميزات نافذة الجهاز القابلة للطي، مثل الطي والمفصلات. عندما يكون تطبيقك محاطًا بالطيّ، يمكنه تعديل تصميمه لتجنُّب وضع محتوى مهم في منطقة الطيّ أو المفصّلة واستخدام الطي والمفصلات كفواصل طبيعية.
معلومات النافذة
تعرض واجهة WindowInfoTracker
في Jetpack WindowManager معلومات تنسيق النافذة. تعرض طريقة windowLayoutInfo()
في الواجهة مصدر بيانات من WindowLayoutInfo
تُعلِم تطبيقك بحالة الطي على جهاز قابل للطي. تنشئ الطريقة WindowInfoTracker
getOrCreate()
مثيلاً لـ WindowInfoTracker
.
يوفِّر تطبيق WindowManager إمكانية جمع بيانات WindowLayoutInfo
باستخدام عمليات استدعاء لغة Kotlin Flows و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 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 windowInfoRepo 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() .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
، يمكن أن يوفّر تطبيقك أوضاعًا مثل وضع "التثبيت على سطح مستوٍ"،
حيث يكون الهاتف على سطح مستوٍ ومفصّلة في الوضع الأفقي والشاشة القابلة للطي مفتوحة نصفها.
يوفر وضع "التثبيت على سطح مستوٍ" للمستخدمين سهولة تشغيل هواتفهم دون حمل الهاتف في أيديهم. يُعدّ وضع "التثبيت على سطح مستوٍ" رائعًا لمشاهدة الوسائط والتقاط الصور وإجراء مكالمات الفيديو.
![تطبيق مشغّل فيديو في وضع "التثبيت على سطح مستوٍ"](https://developer.android.com/static/images/large-screens/gallery/media/tabletop.png?authuser=6&hl=ar)
يمكنك استخدام 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); }
بعد معرفة أنّ الجهاز في وضع "التثبيت على سطح مستوٍ"، يمكنك تعديل تنسيق التطبيق وفقًا لذلك. وبالنسبة إلى تطبيقات الوسائط، يعني هذا عادةً وضع التشغيل في الجزء المرئي من الصفحة وعناصر التحكم في تحديد الموضع والمحتوى التكميلي في الأسفل مباشرةً للحصول على تجربة مشاهدة أو استماع بدون لمس الجهاز.
أمثلة
تطبيق
MediaPlayerActivity
: تعرَّف على طريقة استخدام Media3 Exoplayer و 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
- اكتشاف تجربة الكاميرا
أفلام مُقترَحة لك
- ملاحظة: يظهر نص الرابط عند إيقاف JavaScript
- إتاحة الأجهزة القابلة للطي والأجهزة ذات الشاشات المزدوجة باستخدام Jetpack WindowManager
- تحسين تطبيق الكاميرا على الأجهزة القابلة للطي باستخدام Jetpack WindowManager
- وضع التوافق مع الجهاز