يعد ViewPager أحد المكونات الأكثر شهرة والأكثر استخدامًا في مكتبة دعم Android. جميع أبسط الدواسات ، onboardings والمتزلجون عليها. في فبراير 2019 ، أصدر فريق التطوير AndroidX ViewPager2. دعونا نلقي نظرة على ما كانت عليه هذه المتطلبات الأساسية وما هي مزايا الإصدار المحدث من المكون.

ViewPager 2
في وقت كتابة هذا
المنشور (يوليو 2019) ، تتوفر نسخة تجريبية من
ViewPager2 ، مما يعني أنه يمكن إصلاح المشكلات المذكورة أدناه وتحسين الوظيفة وتوسيع نطاقها. يعد المطورون في المستقبل بإضافة دعم لـ TabLayout (في حين أنه لا يمكنه العمل إلا مع الإصدار الأول) ، وتحسين أداء المهايئ ، وإجراء العديد من التصحيحات الطفيفة ووضع اللمسات الأخيرة على الوثائق.
التكامل
لا يتم تزويد المكون بحزم قياسية ، ولكنه متصل بشكل منفصل. للقيام بذلك ، أضف السطر التالي إلى كتلة التبعيات في البرنامج النصي للنموذج الخاص بالوحدة النمطية:
implementation "androidx.viewpager2:viewpager2:1.0.0-beta02"
تطبيق
لنبدأ بالأخبار السارة: التحول من الإصدار الأول إلى الإصدار الثاني بسيط بقدر الإمكان ويتلخص في تغيير الواردات. لم يتم لمس بناء الجملة القديم الجيد: طريقة
getCurrentItem () تُرجع الصفحة الحالية ،
ViewPager2.onPageChangeCallback يسمح
لك بالاشتراك في
حالة الاستدعاء ، ولا يزال المحول مثبتًا عبر
setAdapter ().
يجدر الحفر أعمق ، حيث يصبح من الواضح أن أجهزة الاستدعاء الأولى والثانية ليس لها أي شيء مشترك باستثناء الواجهات. الإلمام بتنفيذ الأسلوب setAdapter () لا يترك مجالًا للشك:
public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); }
نعم ، ViewPager2 مجرد غلاف على
RecyclerView . من ناحية ، هذه إضافة كبيرة ، من ناحية أخرى - إنها تضيف صداعًا. تم إخفاء
RecyclerView كإصدار منشور مع ظهور
PagerSnapHelper . هذا الفصل يغير فيزياء التمرير. عندما يحرر المستخدم إصبعه ، يحسب
PagerSnapHelper عنصر القائمة الأقرب إلى السطر الأوسط من القائمة ،
وبواسطة الرسوم المتحركة السلس تقوم بضبطه في الوسط تمامًا. وبالتالي ، إذا كان التمرير حادًا بدرجة كافية ، فتمرير القائمة إلى العنصر التالي ، وإلا - مع عودة الرسوم المتحركة إلى حالتها الأصلية.
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

عند استخدام PagerSnapHelper ، تأكد من ضبط عرض وارتفاع RecyclerView نفسه ، وكذلك جميع ViewHolders الخاص به ، على MATCH_PARENT. وإلا ، فإن سلوك SnapHelper لن يكون متوقعًا ، فقد تحدث الأخطاء في أماكن غير متوقعة تمامًا. كل هذا يجعل إنشاء دائري من عناصر صغيرة الطول تستغرق وقتًا طويلاً ، وإن كان ذلك ممكنًا.
بالنظر إلى كل ما سبق ، ستظهر الأداة في الشكل كما يلي:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" />
في نفس الحزمة مثل
ViewPager2 ، يمكننا أيضًا العثور على فئة
ScrollEventAdapter ، والتي تساعد في الحفاظ على استمرارية بناء الجملة.
يقوم ScrollEventAdapter بتنفيذ
RecyclerView.OnScrollListener ويحول أحداث التمرير
إلى أحداث
OnPageChangeCallback .
@Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... }
الآن
لا يتم تمثيل
OnPageChangeCallback من خلال واجهة ، ولكن بواسطة فئة مجردة ، والتي تسمح لك بتجاوز الأساليب الضرورية فقط (في معظم الحالات ، تحتاج فقط إلى
n اختيار الصفحات (Int) ، والذي يعمل عند تحديد صفحة معينة):
main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) {
ملامح
الجدير بالذكر هو الأسلوب
setPageTransformer () ، والذي يأخذ
ViewPager2.PageTransformer كمعلمة. يقوم بتعيين
رد اتصال لكل حدث تحديد صفحة ويعمل على تعيين الرسوم المتحركة الخاصة به لهذه الصفحة.
رد الاتصال يتلقى
عرض الصفحة الحالية ورقمها كمدخل. الأقرب إلى هذه الطريقة هو
ItemAnimator من
RecyclerView .
في الإصدارات الجديدة من المكتبة ، تم إضافة تطبيقين للمحول:
CompositePageTransformer و
MarginPageTransformer . الأول هو المسؤول عن الجمع بين المحولات من أجل تطبيق العديد من التحويلات على جهاز استدعاء واحد في آن واحد ، والثاني عن المسافة البادئة بين الصفحات:

بالإضافة إلى ذلك ، تدعم الأداة الجديدة تغييرات الاتجاه: ببساطة عن طريق استدعاء طريقة
setOrientation () ، يمكنك تحويل جهاز النداء الخاص بك إلى قائمة رأسية مع الضربات الشديدة من أعلى إلى أسفل:
main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL)
يحدث هذا مرة أخرى بفضل الانتقال إلى
RecyclerView : تحت الغطاء ،
يتم استدعاء تغيير في اتجاه
LayoutManager ، وهو المسؤول عن عرض عناصر القائمة. تجدر الإشارة إلى أن تفويض عدد كبير من المهام لفئات أخرى قد استفاد من المكون الجديد: أصبح إدراجه أكثر إحكاما وقراءة.
هذه ليست نهاية المرح. في أحد التحديثات ، تلقى
ViewPager2 دعمًا لـ
ItemDecoration : فئة
مساعدة لتزيين
طريقة عرض الأطفال. يمكن استخدام هذه الآلية لرسم فواصل بين العناصر والحدود وتسليط الضوء على الخلية.
يوجد بالفعل الكثير من التطبيقات الجاهزة للديكور ، لأنه تم استخدامها بنجاح لسنوات عديدة عند العمل مع
RecyclerView المعتاد. جميع التطورات قابلة للتطبيق الآن على أجهزة الاستدعاء. من خارج الصندوق ، يتوفر تطبيق قياسي لفواصل النداء:
main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) )
إلى جانب التحديث التالي في مايو 2019 ، أضاف
ViewPager2 طريقة مهمة أخرى:
setOffscreenPageLimit (Int) . هو المسؤول عن عدد العناصر التي سيتم تهيئة اليمين إلى اليسار والوسط في النداء. على الرغم من أن
RecyclerView مسؤول عن التخزين المؤقت للعرض وعرضه افتراضيًا ، إلا أنه باستخدام هذه الطريقة يمكنك تعيين عدد العناصر المطلوب تحميله بشكل صريح.
محول
الخلف الإيديولوجي لمحول النداء الأول هو
FragmentStateAdapter : واجهات التفاعل وتسمية الفصل هي نفسها تقريبًا. التغييرات أثرت فقط على تسمية بعض الطرق. إذا كان من الضروري في وقت سابق تطبيق
getItem (الموضع) للدالة المجردة لإرجاع مثيل
الجزء المطلوب للموضع المحدد ، ويمكن تفسير هذه التسمية بطريقتين ، والآن تمت إعادة تسمية هذه الوظيفة إلى
createFragment (الموضع) . يتم توفير العدد الإجمالي للشظايا كما كان من قبل بواسطة الدالة
getCount () .
من التغييرات الهيكلية المهمة على الواجهة ، تجدر الإشارة أيضًا إلى أن المحول لديه الآن القدرة على التحكم في دورة حياة عناصره ، لذلك ، إلى جانب
FragmentManager في المنشئ ، يقبل
كائن دورة حياة ، إما
نشاط أو
جزء . لهذا السبب ، من أجل الأمان ، تم
التصريح عن
طريقتي saveState () و
restState () نهائياً وإغلاقهما للميراث.
فئة
FragmentViewHolder مسؤولة عن تخزين الأجزاء داخل
RecyclerView .
يستدعي الأسلوب
onCreateViewHolder () FragmentStateAdapter FragmentViewHolder.create () .
static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); }
عندما يتم
استدعاء أسلوب
onBindViewHolder () ، فإن معرف العنصر في الموضع الحالي
ومعرف ViewHolder مرتبطان ، لإرفاق الجزء به:
final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position);
وأخيرًا ، عند إرفاق حاوية من
ViewHolder بالتسلسل الهرمي
للعرض ، يتم تنفيذ
FragmentTransaction ، بإضافة
جزء إلى الحاوية:
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... }
وبالتالي ،
يظهر استخدامان لـ
ViewPager2 : من خلال وراثة فئة المحول ، إما مباشرةً من
RecyclerView.Adapter أو من
FragmentStateAdapter .
بالتأكيد سيكون لديك سؤال: لماذا تستخدم جهاز النداء الثاني مع Fragments ومحول لهم عندما يكون هناك إصدار أول يعمل بشكل طبيعي؟ يعد
ViewPager بعيدًا عن "الرمز النقطي الفضي" عند العمل مع قوائم البيانات الديناميكية الكبيرة. يُعد هذا أمرًا رائعًا لإنشاء مجموعات متجانسة تحتوي على مجموعة ثابتة من الصور أو الشعارات ، ولكن تتغذى الأخبار المرقّمة على تحميل المنشورات الإعلانية ، وتؤدي التصفية إلى إنشاء وحوش قبيحة تدعمها بشدة. عاجلاً أم آجلاً ، ستصادف بالتأكيد رغبة ملحة في إعادة كتابة كل شيء على
RecyclerView . الآن لست مضطرًا إلى القيام بذلك ، لأن النداء نفسه قد تحول إلى ذلك ، مستعارًا قدراته القوية للعمل مع قوائم ديناميكية ، بينما يلفها في بناء الجملة المعتاد.
الشيء الوحيد الذي يمكن أن يقدمه
PagerAdapter لنا هو طريقة
notifyDataSetChanged () ، والتي تفرض على
ViewPager إعادة رسم كل عناصر القائمة المقدمة. قد تلاحظ بشكل معقول أنه لا يوجد أحد يمنعنا من تخزين قائمة بمواقع العناصر الحالية وإعادة
POSITION_UNCHANGED من طريقة
getItemPosition () لهم ، هذا كل شيء. ومع ذلك ، لا يمكن تسمية هذا الحل بأنه جميل ، بل إنه مرهق إلى حد ما ، علاوة على ذلك ، من الصعب التوسع في تلك الحالات عندما تتغير العناصر في القائمة باستمرار ، وليس فقط تضاف إلى النهاية. يحتوي
FragmentStateAdapter على ترسانة كاملة من أساليب
RecyclerView.Adapter ، بحيث يمكن تكوين منطق عناصر إعادة الرسم بشكل أكثر مرونة. علاوة على ذلك ، مع
FragmentStateAdapter ، يمكنك استخدام
DiffUtil ، والذي يسمح لك بأتمتة عمل الإخطار بالتغييرات بشكل كامل تقريبًا.

تحذير! لإعلام ... طرق العمل بشكل صحيح (باستثناء notifyDataSetChanged ) ، يجب إعادة تعريف أساليب getItemId (Int) و onetsItem (طويلة) . يتم ذلك لأن التطبيق الافتراضي ينظر فقط إلى رقم الصفحة ، وإذا قمت ، على سبيل المثال ، بإضافة عنصر جديد بعد العنصر الحالي ، فلن تتم إضافته ، لأن getItemId سيبقى دون تغيير. مثال على تجاوز هاتين الطريقتين بناءً على قائمة عناصر النوع Int :
override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) }
السبب الرئيسي لظهور
ViewPager2 هو الإحجام عن إعادة اختراع العجلة. من ناحية ، من
الواضح أن فريق تطوير
AndroidX جاهز للتخلي عن
ViewPager المتقادم الآن وبالتأكيد لن يستثمر في توسيع وظائفه. نعم ولماذا؟ بعد كل شيء ،
RecyclerView يعرف بالفعل كل ما هو مطلوب. من ناحية أخرى ، من الواضح أن إزالة وإنهاء الدعم لهذا المكون المستخدم على نطاق واسع لن يضيف ولاء المجتمع.
لتلخيص:
ViewPager2 هو بالتأكيد يستحق الاهتمام ، على الرغم من أنه في الوقت الراهن لا يخلو من عيوب خطيرة.
سلبيات
- الرطوبة وعدد كبير من الأخطاء (يعذر إصدار النسخة التجريبية) ؛
- التقارب. RecyclerView هو حقل خاص في ViewPager2 ، يحرمنا من العديد من الفرص: من المستحيل تنفيذ السحب السريع أو السحب والإفلات (يتصل ItemTouchHelper مباشرة بـ RecyclerView ) ، لا يمكنك إعادة تعريف ItemAnimator بأي شكل من الأشكال ، لا يمكنك الوصول إلى LayoutManager مباشرة ولا تستخدم LayoutManager مباشرة. ومع ذلك ، مع إصدار إصدارات جديدة من المكون ، يتزايد عدد أساليب الواجهة الموروثة من RecyclerView (على سبيل المثال ، ItemDecoration ) ، ويمكننا أن نأمل في إضافة الطرق المفقودة في المستقبل.
الأشياء الجيدة
- دعم لجميع مزايا RecyclerView.Adapter : الجمع بين عناصر من أنواع مختلفة في قائمة واحدة ، إضافة وإزالة العناصر مباشرة أثناء التمرير السريع ، الرسوم المتحركة لمحتويات القائمة عند التغيير ؛
- دعم الطيف الكامل للإخطار ... الطرق والحساب التلقائي للتغييرات باستخدام DiffUtil ؛
- سهولة الانتقال بسبب استمرارية بناء الجملة ؛
- دعم الاتجاه الرأسي والأفقي "خارج الصندوق" ؛
- دعم RTL ؛
- دعم itemdecorator .
- دعم برامج التمرير خلال fakeScrollBy () ؛
- القدرة على ضبط عدد العناصر المحملة يدويًا ؛
- القدرة على استخدام أي من الحلول مفتوحة المصدر الجاهزة لتقليل كود التعليمات ، وهو أمر لا مفر منه عند كتابة RecyclerView.Adapter المخصص. على سبيل المثال ، EasyAdapter .
كملخص ، أريد أن أقول إن
ViewPager2 يستحق بالفعل نظرة فاحصة. هذا هو حل واعد ، الموسعة ، والوظيفية. وعلى الرغم من أنه من السابق لأوانه إطلاق
عنصر واجهة مستخدم جديد في الإنتاج ، إلا أنه من الآمن أن نقول إنه بعد الإصدار الكامل ، يمكنه ويجب أن يحل محل سلفه تمامًا.
بالنسبة لأولئك الذين
يتسمون بالجرأة والحسم ،
والذين ألهمتهم المقالة للتجربة ، ظهر
PagerSnapHelper في الإصدار الثامن والعشرين من
مكتبة الدعم ، مما يعني أنه يمكنك استخدامه مع
RecyclerView عن طريق إنشاء
ViewPager2 بنفسك .
عملية
عينة من
ViewPager2 و
FragmentStateAdapter .
ملاحظات الإصدار الرسمية ViewPager2