توفر مكتبة "نافذة ضمن النافذة" في Jetpack حلاً مبسطًا و فعالاً لمطوّري تطبيقات Android من أجل تنفيذ وظيفة "نافذة ضمن النافذة"، لا سيما لتطبيقات تشغيل الوسائط ومكالمات الفيديو والتنقل. من خلال توفير واجهة برمجة تطبيقات موحّدة، تساعد المكتبة في إزالة الرموز النموذجية والأخطاء الشائعة داخل التطبيق وتحسين الجودة العامة لتجربة المستخدم في "نافذة ضمن النافذة".
تسهّل مكتبة "نافذة ضمن النافذة" في Jetpack استخدام واجهات برمجة التطبيقات الحالية لوظيفة "نافذة ضمن النافذة" من خلال معالجة العديد من التحديات الرئيسية والتناقضات في نظام Android:
- تجزئة نظام التشغيل: تعالج المكتبة تلقائيًا الاختلافات في طلبات واجهة برمجة التطبيقات لوظيفة "نافذة ضمن النافذة" على مستوى إصدارات Android المختلفة، مثل استخدام
enterPictureInPictureModeقبل Android 12 وisAutoEnterEnabledبعده، لذلك لا يحتاج المطوّرون إلى إدارة اختلافات الإصدارات. - مَعلمات غير صحيحة لوظيفة "نافذة ضمن النافذة": توفّر المكتبة حلاً موحّدًا لضبط مَعلمات "نافذة ضمن النافذة" بشكل صحيح، على سبيل المثال
setSourceRectHint، لإنشاء رسوم متحركة سلسة وعالية الجودة أثناء تشغيل الوسائط. - عمليات معاودة الاتصال الموحّدة لحالة "نافذة ضمن النافذة": تجمع المكتبة بين
onPictureInPictureModeChangedوonPictureInPictureUiStateChangedفي واجهة معاودة اتصال موحّدة واحدة (PictureInPictureDelegate.OnPictureInPictureEventListener) لإدارة الحالة وواجهة المستخدم بشكل مبسط. - الحد من الرموز النموذجية: تقلّل المكتبة من مقدار الرموز النموذجية المتكررة من خلال توفير مجموعات محدّدة مسبقًا من
RemoteActionsلحالات الاستخدام الشائعة، مثل عناصر التحكّم في التشغيل وإجراءات مكالمات الفيديو. - التوافق مع المستقبل: يتم توفير ميزات إضافية لوظيفة "نافذة ضمن النافذة" من خلال مكتبة Jetpack ، ما يتيح للمستخدمين الوصول إلى وظائف إضافية بأقل جهد أو بدون أي جهد.
إجراءات نقل البيانات
حدِّد فئة حالة استخدام التطبيق ومنطق "نافذة ضمن النافذة" القديم:
الفئات: تشغيل الفيديو أو التنقل أو مكالمة الفيديو
منطق "نافذة ضمن النافذة" القديم الذي يجب تحديده:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams
2. ضبط ملف Manifest
تأكَّد من أنّ النشاط الذي يدخل وضع "نافذة ضمن النافذة" يعلن عن دعمه في 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لإصدار Android 12 والإصدارات الأحدث، أوonUserLeaveHintلإصدار Android 11 والإصدارات الأقدم بالرمزsetEnabled. فعِّل هذا الرمز كلما تغيّرت حالة الأهلية لاستخدام "نافذة ضمن النافذة". - عمليات معاودة الاتصال: ادمِج
onPictureInPictureModeChanged(تبديل التنسيق) وonPictureInPictureUiStateChanged(الرسوم المتحركة/الحالات) في معاودة اتصال موحّدة تستند إلى الأحداثonPictureInPictureEvent. - الإجراءات والمَعلمات: عدِّل المَعلمات باستخدام
setActionsوsetAspectRatioفي نموذج المثال كلما تغيّرت. - التعامل الخاص مع الفيديو: بالنسبة إلى تطبيقات الفيديو، استخدِم
setPlayerViewلأتمتة تعديلات تلميحات مستطيل المصدر وضمان عمليات الانتقال السلسة. ` ### 6. تنظيف
بالنسبة إلى VideoPlaybackPictureInPicture، استخدِم close في
onDispose أو onDestroy لتحرير الموارد، مثل أدوات تتبُّع العرض.
أنماط التنفيذ المرجعية
أمثلة على عمليات التنفيذ
التنقل ومكالمة الفيديو
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() } } } }