دليل مطور Android إلى نمط التنقل الجزئي
نشرت: 2022-03-11على مر السنين ، رأيت العديد من تطبيقات أنماط التنقل المختلفة في Android. كانت بعض التطبيقات تستخدم الأنشطة فقط ، بينما كانت الأنشطة الأخرى مختلطة مع الأجزاء و / أو طرق العرض المخصصة.
يعتمد أحد تطبيقات نمط التنقل المفضلة لدي على فلسفة "نشاط واحد - عدة أجزاء" ، أو ببساطة نمط التنقل بين الأجزاء ، حيث تكون كل شاشة في التطبيق عبارة عن جزء ملء الشاشة وكل هذه الأجزاء أو معظمها مضمنة في نشاط واحد.
لا يبسط هذا النهج كيفية تنفيذ التنقل فحسب ، بل إنه يتمتع بأداء أفضل بكثير وبالتالي يوفر تجربة مستخدم أفضل.
في هذه المقالة سنلقي نظرة على بعض تطبيقات أنماط التنقل الشائعة في Android ، ثم نقدم نمط التنقل المستند إلى Fragment ، للمقارنة والتباين مع الآخرين. تم تحميل تطبيق تجريبي يطبق هذا النمط على GitHub.
عالم الأنشطة
يتم تنظيم تطبيق Android النموذجي الذي يستخدم الأنشطة فقط في هيكل يشبه الشجرة (بشكل أكثر دقة في الرسم البياني الموجه) حيث يبدأ نشاط الجذر بواسطة المشغل. أثناء التنقل في التطبيق ، هناك مكدس نشاط للخلف يحتفظ به نظام التشغيل.
يظهر مثال بسيط في الرسم البياني أدناه:
النشاط A1 هو نقطة الدخول في تطبيقنا (على سبيل المثال ، يمثل شاشة البداية أو القائمة الرئيسية) ومنه يمكن للمستخدم الانتقال إلى A2 أو A3. عندما تحتاج إلى التواصل بين الأنشطة ، يمكنك استخدام startActivityForResult () أو ربما تشارك عنصر منطق عمل يمكن الوصول إليه عالميًا بينهما.
عندما تحتاج إلى إضافة نشاط جديد ، عليك القيام بالخطوات التالية:
- حدد النشاط الجديد
- قم بتسجيله في AndroidManifest.xml
- افتحه باستخدام startActivity () من نشاط آخر
بالطبع مخطط التنقل هذا هو نهج مبسط إلى حد ما. يمكن أن يصبح الأمر معقدًا للغاية عندما تحتاج إلى معالجة المكدس الخلفي أو عندما تضطر إلى إعادة استخدام نفس النشاط عدة مرات ، على سبيل المثال عندما ترغب في التنقل بين المستخدم من خلال بعض شاشات البرامج التعليمية ولكن في الواقع تستخدم كل شاشة نفس النشاط مثل يتمركز.
لحسن الحظ ، لدينا أدوات تسمى المهام وبعض الإرشادات للتنقل المناسب في المكدس الخلفي.
بعد ذلك ، مع مستوى API 11 ، ظهرت شظايا ...
عالم الشظايا
قدم Android أجزاءً في Android 3.0 (المستوى 11 من واجهة برمجة التطبيقات) ، لدعم تصميمات واجهة المستخدم الأكثر ديناميكية ومرونة على الشاشات الكبيرة ، مثل الأجهزة اللوحية. نظرًا لأن شاشة الجهاز اللوحي أكبر بكثير من شاشة الهاتف ، فهناك مساحة أكبر لدمج مكونات واجهة المستخدم وتبادلها. تسمح الأجزاء بهذه التصاميم دون الحاجة إلى إدارة التغييرات المعقدة في التسلسل الهرمي للعرض. من خلال تقسيم مخطط النشاط إلى أجزاء ، تصبح قادرًا على تعديل مظهر النشاط في وقت التشغيل والحفاظ على تلك التغييرات في حزمة خلفية يديرها النشاط. - مقتبس من دليل واجهة برمجة تطبيقات Google للأجزاء.
سمحت هذه اللعبة الجديدة للمطورين ببناء واجهة مستخدم متعددة الأجزاء وإعادة استخدام المكونات في أنشطة أخرى. يحب بعض المطورين هذا بينما لا يحب الآخرون ذلك. إنه نقاش شائع ما إذا كان يجب استخدام الأجزاء أم لا ، لكنني أعتقد أن الجميع يتفقون على أن الأجزاء جلبت تعقيدًا إضافيًا وأن المطورين يحتاجون حقًا إلى فهمها من أجل استخدامها بشكل صحيح.
كابوس جزء ملء الشاشة في Android
بدأت أرى المزيد والمزيد من الأمثلة حيث لم تكن الأجزاء تمثل جزءًا من الشاشة فقط ، ولكن في الواقع كانت الشاشة بأكملها جزءًا من نشاط. بمجرد أن رأيت تصميمًا يحتوي كل نشاط على جزء واحد بملء الشاشة بالضبط ولا شيء أكثر ، والسبب الوحيد لوجود هذه الأنشطة هو استضافة هذه الأجزاء. بجانب عيب التصميم الواضح ، هناك مشكلة أخرى في هذا النهج. ألق نظرة على الرسم التخطيطي أدناه:
كيف يمكن لـ A1 التواصل مع F1؟ يتمتع البئر A1 بالتحكم الكامل في F1 منذ أن أنشأ F1. يمكن لـ A1 تمرير حزمة ، على سبيل المثال ، عند إنشاء F1 أو يمكنه استدعاء طرقه العامة. كيف يمكن لـ F1 التواصل مع A1؟ حسنًا ، هذا أكثر تعقيدًا ، ولكن يمكن حله باستخدام نمط رد الاتصال / المراقب حيث يشترك A1 في F1 ويبلغ F1 A1.
ولكن كيف يتواصل A1 و A2 مع بعضهما البعض؟ تمت تغطية هذا بالفعل ، على سبيل المثال عبر startActivityForResult () .
والآن يأتي السؤال الحقيقي: كيف يمكن أن يتواصل F1 و F2 مع بعضهما البعض؟ حتى في هذه الحالة ، يمكن أن يكون لدينا مكون منطق الأعمال المتاح عالميًا ، بحيث يمكن استخدامه لتمرير البيانات. لكن هذا لا يؤدي دائمًا إلى تصميم أنيق. ماذا لو احتاج F2 إلى تمرير بعض البيانات إلى F1 بطريقة أكثر مباشرة؟ حسنًا ، باستخدام نموذج رد الاتصال ، يمكن لـ F2 إخطار A2 ، ثم ينتهي A2 بنتيجة ويتم التقاط هذه النتيجة بواسطة A1 الذي يُعلم F1.
يحتاج هذا النهج إلى الكثير من التعليمات البرمجية المعيارية وسرعان ما يصبح مصدرًا للبق والألم والغضب.
ماذا لو تمكنا من التخلص من جميع الأنشطة والاحتفاظ بواحد منها فقط والذي يحتفظ ببقية الأجزاء؟
جزء نمط التنقل
على مر السنين بدأت في استخدام نمط "نشاط واحد متعدد الأجزاء" في معظم تطبيقاتي وما زلت أستخدمه. هناك الكثير من المناقشات حول هذا النهج ، على سبيل المثال هنا وهنا. لكن ما فاتني هو مثال ملموس يمكنني رؤيته واختباره بنفسي.
دعونا نلقي نظرة على الرسم البياني التالي:
الآن لدينا نشاط حاوية واحد فقط ولدينا أجزاء متعددة لها هيكل يشبه الشجرة مرة أخرى. تتم معالجة التنقل بينهما بواسطة FragmentManager ، وله مكدس خلفي.
لاحظ أنه ليس لدينا الآن startActivityForResult () ولكن يمكننا تنفيذ نمط رد الاتصال / المراقب. دعونا نرى بعض إيجابيات وسلبيات هذا النهج:
الايجابيات:
1. AndroidManifest.xml أنظف وأكثر قابلية للصيانة
الآن بعد أن أصبح لدينا نشاط واحد فقط ، لم نعد بحاجة إلى تحديث البيان في كل مرة نضيف فيها شاشة جديدة. على عكس الأنشطة ، لا يتعين علينا التصريح عن الأجزاء.
قد يبدو هذا شيئًا بسيطًا ، ولكن بالنسبة للتطبيقات الأكبر التي تحتوي على أكثر من 50 نشاطًا ، يمكن أن يؤدي ذلك إلى تحسين إمكانية قراءة ملف AndroidManifest.xml بشكل كبير.

انظر إلى ملف البيان الخاص بتطبيق المثال الذي يحتوي على عدة شاشات. لا يزال ملف البيان بسيطًا للغاية.
<?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>2. إدارة الملاحة المركزية
في مثال الكود الخاص بي ، سترى أنني أستخدم NavigationManager والذي في حالتي يتم حقنه في كل جزء. يمكن استخدام هذا المدير كمكان مركزي للتسجيل وإدارة المكدس الخلفي وما إلى ذلك ، لذلك يتم فصل سلوكيات التنقل عن بقية منطق الأعمال ولا تنتشر في تطبيقات الشاشات المختلفة.
دعنا نتخيل موقفًا نرغب فيه في بدء شاشة حيث يمكن للمستخدم تحديد بعض العناصر من قائمة الأشخاص. قد ترغب أيضًا في تمرير بعض حجج التصفية مثل العمر والمهنة والجنس.
في حالة الأنشطة ، يجب أن تكتب:
Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);ثم عليك تحديد onActivityResult في مكان ما أدناه والتعامل مع النتيجة.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }مشكلتي الشخصية مع هذا النهج هي أن هذه الحجج "إضافات" وليست إلزامية ، لذلك يجب أن أتأكد من أن النشاط المتلقي يتعامل مع جميع الحالات المختلفة عندما يكون هناك نقص إضافي. في وقت لاحق عندما يتم إجراء بعض عمليات إعادة البناء ولم تعد هناك حاجة إلى "العمر" الإضافي على سبيل المثال ، عندئذٍ يجب أن أبحث في كل مكان في الكود حيث أبدأ هذا النشاط وأتأكد من صحة جميع الإضافات.
علاوة على ذلك ، أليس من الأجمل أن تصل النتيجة (قائمة الأشخاص) في شكل _List
في حالة التنقل المستند إلى الأجزاء ، يكون كل شيء أكثر وضوحًا. كل ما عليك فعله هو كتابة طريقة في NavigationManager تسمى startPersonSelectorFragment () مع الوسائط الضرورية وتنفيذ رد الاتصال.
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });أو مع RetroLambda
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);3. أفضل وسائل الاتصال بين الشاشات
بين الأنشطة ، لا يمكننا مشاركة سوى حزمة يمكنها الاحتفاظ بأولويات أو بيانات متسلسلة. الآن باستخدام الأجزاء ، يمكننا تنفيذ نمط رد الاتصال حيث على سبيل المثال ، يمكن لـ F1 الاستماع إلى F2 الذي يمر بكائنات عشوائية. يرجى إلقاء نظرة على تنفيذ رد الاتصال للأمثلة السابقة ، والذي يعيد قائمة _List
4. شظايا البناء أقل تكلفة من أنشطة البناء
يصبح هذا واضحًا عند استخدام درج به على سبيل المثال 5 عناصر قائمة وفي كل صفحة يجب عرض الدرج مرة أخرى.
في حالة التنقل بالنشاط البحت ، يجب أن تتضخم كل صفحة وتهيئ الدرج ، وهو أمر مكلف بالطبع.
في الرسم التخطيطي أدناه ، يمكنك رؤية العديد من أجزاء الجذر (FR *) وهي أجزاء ملء الشاشة يمكن الوصول إليها مباشرة من الدرج ، كما يمكن الوصول إلى الدرج فقط عند عرض هذه الأجزاء. كل ما هو على يمين الخط المتقطع في الرسم البياني موجود كمثال على مخطط تنقل عشوائي.
نظرًا لأن نشاط الحاوية يحتوي على الدرج ، فلدينا مثيل درج واحد فقط ، لذلك في كل خطوة تنقل حيث يجب أن يكون الدرج مرئيًا ، لن تضطر إلى تضخيمه وتهيئته مرة أخرى. ما زلت غير مقتنع كيف تعمل كل هذه؟ ألق نظرة على نموذج الطلب الخاص بي والذي يوضح استخدام الدرج.
سلبيات
كان خوفي الأكبر دائمًا هو أنني إذا استخدمت نمط التنقل المستند إلى الأجزاء في مشروع ما ، في مكان ما على الطريق ، سأواجه مشكلة غير متوقعة سيكون من الصعب حلها حول التعقيد الإضافي للأجزاء ومكتبات الطرف الثالث وإصدارات نظام التشغيل المختلفة. ماذا لو اضطررت إلى إعادة بناء كل ما قمت به حتى الآن؟
في الواقع ، كان علي حل المشكلات المتعلقة بالأجزاء المتداخلة ومكتبات الجهات الخارجية التي تستخدم أيضًا أجزاء مثل ShinobiControls و ViewPagers و FragmentStatePagerAdapters.
يجب أن أعترف أن اكتساب الخبرة الكافية مع الشظايا لتكون قادرة على حل هذه المشاكل كانت عملية طويلة نوعًا ما. لكن في كل حالة لم تكن القضية أن الفلسفة سيئة ، لكنني لم أفهم الشذرات جيدًا. ربما إذا فهمت الأجزاء بشكل أفضل مما فهمته ، فلن تواجه هذه المشكلات.
العيب الوحيد الذي يمكنني أن أذكره الآن هو أنه لا يزال بإمكاننا مواجهة مشاكل لن يكون حلها تافهًا نظرًا لعدم وجود مكتبة ناضجة تعرض جميع السيناريوهات المعقدة لتطبيق معقد مع التنقل القائم على الأجزاء.
خاتمة
في هذه المقالة ، رأينا طريقة بديلة لتنفيذ التنقل في تطبيق Android. قمنا بمقارنتها بفلسفة التنقل التقليدية التي تستخدم الأنشطة وشاهدنا بعض الأسباب الوجيهة التي تجعل من المفيد استخدامها على النهج التقليدي.
إذا لم تكن قد قمت بذلك بالفعل ، تحقق من التطبيق التجريبي الذي تم تحميله إلى تطبيق GitHub. لا تتردد في تفرعها أو المساهمة فيها بأمثلة ألطف من شأنها إظهار استخدامها بشكل أفضل.
