تقدّم مكتبة "نافذة ضمن النافذة" في Jetpack حلاً مبسطًا وفعّالاً لمطوّري تطبيقات Android لتنفيذ وظيفة "نافذة ضمن النافذة"، خاصةً لتطبيقات تشغيل الوسائط وتطبيقات التواصل عبر الفيديو وتطبيقات التنقّل. من خلال توفير واجهة برمجة تطبيقات موحّدة، تساعد المكتبة في إزالة رمز النص النموذجي والأخطاء الشائعة داخل التطبيق وتحسين الجودة الإجمالية لتجربة المستخدمين في وضع "نافذة ضمن النافذة".
تسهّل مكتبة "نافذة ضمن النافذة" في Jetpack استخدام واجهات برمجة التطبيقات الحالية الخاصة بميزة "نافذة ضمن النافذة" من خلال معالجة العديد من التحديات الرئيسية وحالات عدم الاتساق في منظومة Android المتكاملة، وهي:
- تجزئة نظام التشغيل: تعالج المكتبة تلقائيًا الاختلافات في طلبات البيانات من واجهة برمجة التطبيقات "نافذة ضمن النافذة" في مختلف إصدارات Android، مثل استخدام
enterPictureInPictureModeقبل Android 12 وisAutoEnterEnabledبعده، لذلك لا يحتاج المطوّرون إلى إدارة الاختلافات بين الإصدارات. - مَعلمات غير صحيحة لوضع "نافذة ضمن النافذة": يوفّر هذا الخيار حلاً موحّدًا لضبط مَعلمات وضع "نافذة ضمن النافذة" بشكل صحيح، مثل
setSourceRectHint، وذلك لإنشاء رسوم متحركة سلسة وعالية الجودة أثناء تشغيل الوسائط. - عمليات معاودة الاتصال الموحّدة لحالة "نافذة ضمن النافذة": تدمج هذه العمليات
onPictureInPictureModeChangedوonPictureInPictureUiStateChangedفي واجهة موحّدة واحدة لمعاودة الاتصال (PictureInPictureDelegate.OnPictureInPictureEventListener) من أجل تبسيط إدارة الحالة وواجهة المستخدم. - تقليل الرمز النموذجي: تقلّل المكتبة من مقدار الرمز النموذجي المتكرّر من خلال توفير مجموعات محدّدة مسبقًا من
RemoteActionsلحالات الاستخدام الشائعة، مثل عناصر التحكّم في التشغيل وإجراءات مكالمات الفيديو. - التوافق مع الإصدارات المستقبلية: يتم توفير ميزات إضافية في وضع "نافذة ضمن النافذة" من خلال مكتبة Jetpack، ما يتيح للمستخدمين الاستفادة من وظائف إضافية بأقل جهد ممكن.
سير عمل نقل البيانات
تحديد فئة حالة استخدام التطبيق ومنطق ميزة "نافذة ضمن النافذة" القديم:
الفئات: تشغيل الفيديو أو التنقّل أو مكالمة الفيديو
المنطق القديم لتحديد وضع "نافذة ضمن النافذة":
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams.
2. إعداد AndroidManifest
تأكَّد من أنّ النشاط الذي يدخل وضع "نافذة ضمن النافذة" يعلن عن إمكانية استخدامه في AndroidManifest.xml مع configChanges اللازمَين لمنع عمليات إعادة التشغيل غير الضرورية:
<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
3- إعداد البيئة
أضِف الاعتمادات المطلوبة إلى ملف build.gradle:
dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }
استخدِم أحدث مكتبات AndroidX للتبعيات، وراجِع صفحة الإصدارات للحصول على هذه المعلومات.
4. اختيار النموذج وإعداده
اختَر نموذج التنفيذ الذي يناسب حالة استخدام التطبيق على أفضل نحو:
- التنقّل ومكالمة الفيديو:
BasicPictureInPicture؛ لا تتوفّر عادةً إمكانية تغيير الحجم بسلاسة، ولا تحتاج إلى تلميح مستطيل المصدر. - تشغيل الفيديو:
VideoPlaybackPictureInPicture; يتتبّع تلقائيًا حدود عرض المشغّل للحصول على تلميح المستطيل المصدر ويتيح تغيير الحجم بسلاسة تلقائيًا.
لاستخدام مكتبة Jetpack، عليك استبدال عملية التنفيذ الحالية المخصّصة لوضع "نافذة ضمن النافذة" بواجهات برمجة التطبيقات الخاصة بمكتبة Jetpack. سيختلف مدى تعقيد عملية التنفيذ وتكلفتها استنادًا إلى عملية التنفيذ الحالية للتطبيق.
توضّح الأقسام التالية بعض حالات الاستخدام النموذجية لميزة "نافذة ضمن النافذة" وخطوات التنفيذ اللازمة:
التنقل
يُعلم التطبيق المكتبة بحالة التنقّل النشطة أو غير النشطة ويضبط نسبة العرض إلى الارتفاع، وتتولّى مكتبة Jetpack بقية المهام.
الاختلافات الرئيسية:
- ليس من الضروري التمييز بين ميزة "الإدخال التلقائي" و"الإدخال القديم" من جهة التطبيق.
- واجهات رد الاتصال الموحّدة
- تمت إضافة أداة إنشاء
PictureInPictureParamsجديدة للتوافق مع الأنظمة القديمة.
مكالمة فيديو
يُعلم التطبيق المكتبة بحالة المكالمة النشطة أو غير النشطة ويضبط نسبة العرض إلى الارتفاع.
الاختلافات الرئيسية:
- ليس من الضروري التمييز بين ميزة "الإدخال التلقائي" و"الإدخال القديم" من جهة التطبيق.
- واجهات رد الاتصال الموحّدة
- تمت إضافة أداة إنشاء
PictureInPictureParamsجديدة للتوافق مع الأنظمة القديمة. - رموز الإجراءات الموحّدة لمكالمة الفيديو
5. نقل الرموز
- منطق الإدخال: استبدِل المنطق الخاص بواجهة برمجة التطبيقات، مثل
setAutoEnterEnabledللإصدار 12 من نظام التشغيل Android والإصدارات الأحدث، أوonUserLeaveHintللإصدار 11 من نظام التشغيل Android والإصدارات الأقدم بالرمزsetEnabled. يجب تشغيل هذا الإجراء كلما تغيّرت حالة الأهلية لاستخدام ميزة "نافذة ضمن النافذة". - عمليات ردّ الاتصال: يمكنك دمج
onPictureInPictureModeChanged(تبديل التنسيق) وonPictureInPictureUiStateChanged(الصور المتحركة/الحالات) في عملية ردّ اتصال موحّدة مستندة إلى الأحداثonPictureInPictureEvent. - الإجراءات والمَعلمات: عدِّل المَعلمات باستخدام
setActionsوsetAspectRatioفي نسخة النموذج كلما تغيّرت.
أنماط التنفيذ المرجعية
أمثلة على عمليات التنفيذ
التنقّل ومكالمة الفيديو
class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: BasicPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = BasicPictureInPicture(this) // BasicPictureInPicture is ideal for Navigation and Video call use cases. pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ } PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ } PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ } } } }
تشغيل الفيديو
class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = VideoPlaybackPictureInPicture(this) pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { ContentScreen(pictureInPictureImpl) } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ } PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ } PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ } PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ } PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ } } } @Composable fun ContentScreen(pipController: VideoPlaybackPictureInPicture) { DisposableEffect(pipController) { onDispose { pipController.close() } } } }