کار با فرگمنتها در برنامهنویسی اندروید
در توسعهی نرمافزارهای مدرن اندروید، استفاده از «فرگمنتها» (Fragment) برای طراحی رابط کاربری بسیار مرسوم است. در این آموزش، به مفاهیم پایهی فرگمنتهای اندروید میپردازیم و یک اپلیکیشن برای نمایش تصاویر کمیک توسعه میدهیم.
واژهی فرگمنت به معنای یک بخش از چیزی است که به تنهایی کامل نیست.
فرگمنتها یکی از اجزای اندروید هستند که بخشی از رفتار و رابط یک «اکتیویتی» (Activity) را در خود نگه میدارند. همانطور که از اسمش مشخص است، فرگمنتها جز مستقلی نیستند و باید به یک اکتیویتی متصل شوند. در بسیاری از اوقات، عملکرد فرگمنتها بسیار شبیه به یک اکتیویتی است.
یک لحظه فرض کنید که شما یک اکتیویتی هستید. کلی کار باید انجام دهید، برای سادگی کار میتوانید چند کارگر داشته باشید تا لباسهایتان را بشورند و مالیاتتان را پرداخت کنند و در عوض شما به آنها محلی برای زندگی و غذا دهید. رابطهی بین اکتیویتیها و فرگمنتها تقریبا به همین شکل است.
همانطور که مجبور به استفاده از کارگر برای انجام کارهایتان نیستید، اجباری به استفاده از فرگمنتها هم ندارید. ولی اگر از فرگمنتها استفاده کنید، آنها مزیتهایی را برای شما به همراه خواهند داشت:
- ماژولار بودن: کدهای پیچیدهی هر اکتیویتی بین چند فرگمنت تقسیم میشود و کار سازماندهی و مدیریت آنها را سادهتر میکند.
- قابل استفادهی مجدد بودن: رفتارها و رابط کاربریای که در یک فرگمنت پیادهسازی میشوند را میتوان در چندین اکتیویتی مورد استفاده قرار داد.
- سازگار بودن: میتوان بخشی از رابط کاربری را در یک فرگمنت قرار داد و در اندازهها و حالات مختلف صفحه نمایش، قالب صفحه را تغییر داد.
در این آموزش یک کتابچه از تصاویر کمیک را میسازیم. این اپلیکیشن یک لیست از کمیکها را به صورت جدولی نمایش میدهد. هنگامی که یک کمیک انتخاب شده باشد، اطلاعات آن نمایش داده خواهد شد. در این آموزش موارد زیر را یاد خواهید گرفت:
- چگونگی ساخت و افزودن یک فرگمنت به یک اکتیویتی
- چگونگی ارسال اطلاعات از یک فرگمنت به یک اکتیویتی
- چگونگی افزودن و جابهجایی فرگمنتها
در این آموزش فرض شده است که شما به مقدمات برنامهنویسی اندروید مسلط هستید و مراحل زندگی یک اکتیویتی را میشناسید. اگر در برنامهنویسی اندروید کاملا تازه کار هستید، توصیه میشود ابتدا «آموزش برنامهنویسی اندروید (Android) – مقدماتی» را مشاهده کنید. همچنین در این آموزش از «RecyclerView» استفاده میشود که برای آشنایی با آن میتوانید آموزش «مقدمه کار با متریال دیزاین در برنامه نویسی اندروید» را مطالعه کنید.
شروع کار با فرگمنتها در اندروید
ابتدا فایل شروع پروژه را از این لینک دانلود و استخراج کرده و سپس اندروید استودیو را باز کنید. از اندروید استودیو 3.0 به بالا استفاده کنید.
در صفحهی خوشآمد گویی اندروید استودیو، گزینهی «(.Import Project (Gradle, Eclipse ADT, etc» را انتخاب کنید.
پوشهی اصلی پروژهی شروع را انتخاب کنید و گزینهی OK را بزنید.
اگر پیغامی برای بروزرسانی پلاگین Gradle در پروژه مشاهده کردید، گزینهی Update را بزنید.
اگر پروژهی «All the Rages» را بررسی کنید، چند فایل «resource» شامل «strings.xml»، «activity_main.xml» به همراه فایلهای «drawables» و «layout» را خواهید دید. همچنین چند فایل آمادهی دیگر برای فرگمنتهای شما و کدهای غیر فرگمنتتان نیز وجود دارند و البته در کنار این موارد یک کلاس نیز موجود است، که بعداً میتوانید از آن برای نوشتن کلاسهای اختصاصی خودتان بهره بگیرید.
«MainActivity» میزبان فرگمنتهای کوچک خواهد بود، و «RageComicListFragment» شامل کدهای مورد نیاز برای نمایش کمیکها است تا بتوانید بر روی ساخت فرگمنتها تمرکز کنید.
پروژه را بسازید و اجرا (Build and run) کنید. همانطور که میبینید چیز زیادی در آن نیست. این مساله را به زودی حل خواهیم کرد.
چرخهی زندگی یک فرگمنت در اندروید
همانند یک اکتیویتی، یک فرگمنت نیز چرخهای از «رویدادها» (Event) را دارد که با تغییر وضعیت آن، اتفاق میافتند. برای مثال، یک رویداد هنگامی اجرا میشود که فرگمنت ظاهر و فعال شود، یا هنگامی که استفاده از فرگمنت به پایان برسد و فرگمنت حذف گردد. همچنین درست مانند یک اکتیویتی، میتوانید کدها و رفتارهایی در فرگمنت ایجاد کنید که این رویدادها را صدا کنند.
در تصویر زیر یک نمودار از چرخهی زندگی یک فرگمنت را مشاهده میکنید.
همانطور که مشاهده میکنید، خیلی از این رویدادها درست همانند چرخهی زندگی یک اکتیویتی هستند. برای سهولت کار، در زیر، نمودار چرخهی زندگی یک اکتیویتی را نیز برایتان قرار دادهایم.
زمانی که یک فرگمنت را به اپلیکیشن اضافه میکنید، رویدادهای زیر اتفاق میافتند:
- «onAttach»: زمانی اجرا میشود که فرگمنت به یک اکتیویتی میزبان متصل شود.
- «onCreate»: زمانی اجرا میشود که یک نمونهی جدید از یک فرگمنت ساخته شود. این اتفاق همیشه پس از اتصال فرگمنت به میزبان میافتد (عملکرد فرگمنتها کمی شبیه به ویروسها است).
- «onCreateView»: زمانی اجرا میشود که فرگمنت، «view» خودش را میسازد که به «view» اکتیویتی متصل میشود.
- «onActivityCreated»: زمانی اجرا میشود که اکتیویتی میزبان، رویداد «onCreate» خودش را کامل کند.
- «onStart»: زمانی اجرا میشود که یک فرگمنت برای کاربر قابل دیدن باشد. یک فرگمنت فقط زمانی شروع میشود که اکتیویتی میزبان آن شروع شده باشد. در اغلب اوقات، فرگمنت فورا پس از اکتیویتی شروع میشود.
- «onResume»: هنگامی اجرا میشود که فرگمنت برای کاربر قابلیت دیده شدن و برقراری تعامل داشته باشد. یک فرگمنت فقط هنگامی ادامه پیدا میکند (Resume میشود) که اکتیویتی میزبان آن ادامه پیدا کرده باشد. در اغلب اوقات، فرگمنت فورا پس از اکتیویتی شروع میشود.
تا اینجا رویدادهایی را بررسی کردیم که در هنگام اضافه کردن فرگمنت به اکتیویتی اجرا میشوند. حالا به رویدادهایی میپردازیم که در هنگام حذف یک فرگمنت اتفاق میافتند:
- «onPause»: زمانی اجرا میشود که فرگمنت دیگر قابلیت برقراری تعامل را نداشته باشد. این اتفاق زمانی میافتد که فرگمنت در حال حذف شدن یا جابهجایی باشد، یا اکتیویتی میزبان مکث کند (Pause).
- «onStop»: زمانی اجرا میشود که فرگمنت دیگر قابل دیدن نباشد. این اتفاق زمانی میافتد که فرگمنت در حال حذف شدن یا جابهجایی باشد، یا اکتیویتی میزبان متوقف گردد (Stop).
- «onDestoryView»: هنگامی اجرا میشود که فرگمنت و منابع مربوطه که در «onCreateView» ساخته شدهاند از «view» اکتیویتی حذف گردند و نابود شوند.
- «onDestory»: هنگامی اجرا میشود که فرگمنت در حال انجام پاکسازی نهایی است.
- «onDetach»: زمانی اجرا میشود که فرگمنت از اکتیویتی میزبان جدا شود.
همانطور که مشاهده میکنید، چرخهی زندگی یک فرگمنت با چرخهی زندگی اکتیویتی میزبان آن ارتباط دارد. ولی یک سری رویدادها نیز در آن وجود دارند که مختص به سلسلهمراتب فرگمنت، وضعیت فرگمنت و اتصال آن به اکتیویتی است.
کتابخانهی «V4 Support»
هنگامی که در اندروید از فرگمنتها استفاده میکنید، دو روش برای پیادهسازی آنها وجود دارد. روش اول استفاده از فرگمنتی است که توسط نسخهی پلتفرم ارائه شده است. نسخهی پلتفرم به نسخهی اندروید مورد استفادهی کاربر بستگی دارد. برای مثال، یک دستگاهی که از اندروید 6 استفاده میکند (نسخهی 23 SDK) درواقع از نسخهی 23 پلتفرم استفاده میکند.
روش دوم استفاده از فرگمنت موجود در کتابخانهی «support» است. زمانی که یک کتابخانهی «support» وارد پروژهی خود میکنید، این کتابخانه همانند هر کتابخانهی جانبی دیگری به پروژهی شما اضافه میشود. اینکار در هنگام توسعهی اپلیکیشن برای چندین نسخه از اندروید، دو مزیت دارد.
مزیت اول اینکه کد و عملکرد نرمافزار شما بین دستگاهها و نسخههای مختلف پلتفرم، ثابت خواهد بود. چنین امری باعث میشود که باگها و راهحلهای آنها در نسخههای متفاوت اندروید که از این کتابخانه استفاده میکنند، یکسان باشد.
در رابطه با مزیت دوم هم باید به این نکته اشاره کنیم، هنگامیکه ویژگیهای جدیدی در نسخههای آخر اندروید اضافه میشوند، تیم اندروید معمولا این ویژگیها را به کمک کتابخانههای «support» برای نسخههای قبلی اندروید نیز فراهم میکنند تا توسعهدهندگان بتوانند از آنها در توسعهی نرمافزارهای خود استفاده کنند.
از کدام کتابخانه استفاده کنیم؟
اگر اپلیکیشنی میسازید که چندین نسخه از اندروید در چندین دستگاه را هدف قرار دادهاست، باید از کتابخانهی «support» برای فرگمنتها استفاده کنید. همچنین برای هرچیز دیگری که میخواهید در اپلیکیشن داشته باشید، تا جایی که میتوانید باید از همین کتابخانه استفاده کنید. بیشتر افراد با تجربه در حوزهی برنامهنویسی اندروید و همچنین تیم سازندهی اندروید این کار را مناسب میدانند. تنها هنگامی باید از کتابخانههای مختص به یک پلتفرم استفاده کنید که میخواهید اپلیکیشن خودتان را فقط برای نسخهی خاصی از اندروید توسعه دهید.
به عبارتی دیگر: احتمالا برایتان بهتر است که تا جای ممکن از کتابخانههای «support» استفاده کنید. برای استفاده از فرگمنتها باید از کتابخانهی «v4 support» استفاده کنید.
شروع ساخت یک فرگمنت
اپلیکیشن ما قرار است یک لیست از کمیکها را در شروع خود نمایش دهد و با ضربه زدن به هر کمیک اطلاعاتی راجع به آن ارائه کند. برای شروع، کار را از آخر آغاز میکنیم و ابتدا صفحهی اطلاعات را میسازیم.
فایل پروژه را در اندروید استودیو باز کنید و فایل «fragment_rage_comic_details.xml» را در آدرس «app/res/layout» پیدا کنید. این فایل XML قالب صفحهی اطلاعات کمیک است. همچنین یکی از منابع «drawable» و یک رشته را نیز نمایش میدهد.
وارد زبانهی «Project» در اندروید استودیو شوید و فایل «RageComicDetailsFragment» را پیدا کنید. این کلاس وظیفهی نمایش جزئیات کمیکهای انتخاب شده را بر عهده دارد. کد فعلی که در فایل «RageComicDetailsFragment.kt» قرار دارد، در زیر آمدهاست:
1import android.os.Bundle
2import android.support.v4.app.Fragment
3import android.view.LayoutInflater
4import android.view.View
5import android.view.ViewGroup
6
7//1
8class RageComicDetailsFragment : Fragment() {
9
10//2
11companion object {
12
13fun newInstance(): RageComicDetailsFragment {
14return RageComicDetailsFragment()
15}
16}
17
18//3
19override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
20savedInstanceState: Bundle?): View? {
21return inflater?.inflate(R.layout.fragment_rage_comic_details, container, false)
22}
23
24}
کاری که این کد میکند این است:
- «RageComicDetailsFragment» را به عنوان یک زیر کلاس از «Fragment» میسازد.
- یک متد برای ساخت نمونه از این فرگمنت را ارائه میدهد. این متد، متد «کارخانه» (Factory) نام دارد.
- سلسلهمراتب «view» را میسازد که توسط فرگمنت کنترل میشود.
اکتیویتیها از «()setContentView» برای مشخص کردن فایل XML استفاده میکنند که قالب آنها را تعریف میکند، ولی فرگمنتها سلسلهمراتب «view» خود را در «()onCreateView» میسازند. در این کد ما «LayoutInflater.inflate» را صدا کردهایم تا سلسلهمراتب «RageComicDetailsFragment» را بسازد.
سومین پارامتر «inflate» مشخص میکند که آیا این فرگمنت باید به یک «container» متصل شود یا خیر. یک مخزن یا «container» یک «view» والد است که ساختار «view» فرگمنت را در خود نگه میدارد. همیشه باید این مقدار را برابر با «false» قرار دهید. «FragmentManager» مسائل مربوط به اضافه کردن فرگمنت به مخزن را مدیریت میکند.
به یک جز جدید اشاره کردیم: «FragmentManager». هر اکتیویتی یک «FragmentManager» دارد که فرگمنتها را مدیریت میکند. همچنین یک رابط به ما ارائه میدهد تا بتوانیم به آن فرگمنتها دسترسی داشته باشیم یا آنها را حذف و اضافه کنیم.
به این نکته توجه داشته باشید که با اینکه «RageComicDetailsFragment» یک متد کارخانه برای نمونهسازی دارد (همان «newInstance»)، ولی هیچ «سازندهای» (Constructor) ندارد.
حال این پرسش مطرح است که چرا ما به یک متد کارخانه نیاز داریم ولی متد سازنده نمیخواهیم؟ در پاسخ باید بگوئیم اولا، از آنجایی که هیچ سازندهای تعریف نکردهایم، کامپایلر به طور پیشفرض یک سازندهی خالی میسازد که هیچ آرگومانی ندارد. تنها چیزی که برای یک فرگمنت نیاز دارید همین است، به سازندهی دیگری نیاز ندارید.
دوما، احتمالا این را میدانید که اندروید ممکن است در هنگامی که یک اپلیکیشن به پسزمینه (Background) میرود، اکتیویتی و فرگمنتهای آن را نابود کند و بعدا دوباره از نو بسازد. هنگامی که اکتیویتی دوباره ساخته میشود، «FragmentManager» آن شروع به بازسازی فرگمنتها با استفاده از سازندهی خالی و پیشفرض آنها میکند. اگر آن را پیدا نکند، یک خطا تولید میکند. به این دلایل، بهترین کار این است که هیچوقت سازندهی غیر خالی مشخص نکنیم. درواقع راحتترین کار این است که کلا سازندهای مشخص نکنیم، همانطور که در این کد مشخص نکردهایم.
ولی اگر بخواهیم اطلاعات و یا دادههایی را به فرگمنت بفرستیم چی؟ جواب این سوال را پایینتر خواهید گرفت.
افزودن یک فرگمنت
در اینجا، یک فرگمنت را به سادهترین روش اضافه میکنیم، یعنی آن را به فایل XML قالب میافزاییم.
برای اینکار، فایل «activity_main.xml» را باز کنید و زبانهی «Text» را انتخاب کنید. سپس کد زیر را در داخل «FrameLayout» اصلی اضافه کنید:
1<fragment
2android:id="@+id/details_fragment"
3class="com.raywenderlich.alltherages.RageComicDetailsFragment"
4android:layout_width="match_parent"
5android:layout_height="match_parent"/>
در این گام، یک تگ «<fragment>» به قالب اکتیویتی اضافه و نوع فرگمنتی که کلاس مورد نظر باید بسازد را مشخص کردیم. مشخصهی ID که به «<fragment>» دادهایم توسط «FragmentManager» مورد نیاز است.
اپلیکیشن را بسازید و اجرا کنید. فرگمنت را مشاهده خواهید کرد:
افزودن یک فرگمنت به صورت پویا
ابتدا فایل «activity_man.xml» را مجددا باز کنید و تگ «<fragment>» که اضافه کرده بودید را حذف کنید. این کد را با لیست کمیکها جایگزین خواهید کرد.
فایل «RageComicListFragment.kt» را که کد تمام لیستها را دارد، باز کنید. همانطور که میبینید، «RageComicListFragment» هیچ سازندهای ندارد و فقط یک «()newInstance» دارد.
کد لیستی که در «RageComicListFragment» قرار دارد به یک سری منابع متکی است. باید مطمئن شوید که فرگمنت یک ارجاع صحیح به «Context» دارد تا بتواند به آن منابع دسترسی داشته باشد. برای این امر از «()onAttach» استفاده میکنیم.
فایل «RageComicListFragment.kt» را باز کنید و کد زیر را به قسمت پکیجها اضافه کنید:
1import android.os.Bundle
2import android.support.v7.widget.GridLayoutManager
3
4«GridLayoutManager» به قرارگرفتن آیتمها در موقعیت مناسب در لیست ما کمک میکند. پکیج بعدی برای کدهای استاندارد فرگمنتها از جمله دستورات «override» است.
5دو متدی که در کد زیر آمدهاست را در داخل فایل «RageComicListFragment.kt»، در بالای تعریف «RageComicAdapter» اضافه کنید:
6override fun onAttach(context: Context?) {
7super.onAttach(context)
8
9// Get rage face names and descriptions.
10val resources = context!!.resources
11names = resources.getStringArray(R.array.names)
12descriptions = resources.getStringArray(R.array.descriptions)
13urls = resources.getStringArray(R.array.urls)
14
15// Get rage face images.
16val typedArray = resources.obtainTypedArray(R.array.images)
17val imageCount = names.size
18imageResIds = IntArray(imageCount)
19for (i in 0..imageCount - 1) {
20imageResIds[i] = typedArray.getResourceId(i, 0)
21}
22typedArray.recycle()
23}
24
25override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
26savedInstanceState: Bundle?): View? {
27
28val view: View = inflater!!.inflate(R.layout.fragment_rage_comic_list, container,
29false)
30val activity = activity
31val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view) as RecyclerView
32recyclerView.layoutManager = GridLayoutManager(activity, 2)
33recyclerView.adapter = RageComicAdapter(activity)
34return view
35}
متد «()onAttach» شامل کدهایی است که فرگمنت برای دسترسی به منابع از طریق «Context» نیاز دارد. از آن جایی که این کد در «()onAttach» قرار گرفته است، میتوانید مطمئن باشید که فرگمنت همواره یک «Context» معتبر دارد.
در متد «()onCreateView»، ظاهر «RageComicListFragment» را میسازیم که شامل یک «RecyclerView» است. به علاوه، یک سری کارها روی آن انجام میدهیم.
اگر میخواهید «View» یک فرگمنت را بررسی کنید، «()onCreateView» جای مناسبی است، چراکه «view» تازه در آن ساخته شدهاست.
حالا فایل «MainActivity.kt» را باز کنید و متد «()onCreate» را با کد زیر جایگزین کنید:
1override fun onCreate(savedInstanceState: Bundle?) {
2super.onCreate(savedInstanceState)
3setContentView(R.layout.activity_main)
4
5if (savedInstanceState == null) {
6supportFragmentManager
7.beginTransaction()
8.add(R.id.root_layout, RageComicListFragment.newInstance(), "rageComicList")
9.commit()
10}
11}
در اینجا «RageComicListFragment» به «MainActivity» اضافه میشود. درواقع «FragmentManager» کار اضافه کردن آن را انجام میدهد.
ابتدا «FragmentManager» خود را با ارجاع به «supportFragmentManager» میسازیم، چراکه داریم از فرگمنتهای کتابخانهی «support» استفاده میکنیم.
سپس، از «FragmentManager» میخواهیم که یک جابهجایی را آغاز کند. این کار را با صدا کردن «()beginTransaction» انجام میدهیم. سپس عملیات اضافه کردن را با صدا کردن «add» آغاز میکنیم و مقادیر زیر را به آن میدهیم:
- آیدی «view» در اکتیویتی که قرار است فرگمنت را در خود نگه دارد. اگر یک نگاه به «activity_main.xml» بیندازید، «id/root_layout+@» را مشاهده خواهید کرد.
- نمونهی فرگمنتی که قرار است اضافه شود.
- یک رشته که به عنوان یک آیدی یا تگ برای نمونهی ساخته شده از فرگمنت عمل میکند. اینکار به «FragmentManager» اجازه میدهد که بعدا بتواند فرگمنت را برای شما پیدا کند.
در نهایت، با صدا کردن متد «()commit»، از «FragmentManager» میخواهیم که جابهجایی را انجام دهد. حالا فرگمنت به اکتیویتی اضافه شدهاست.
اپلیکیشن را بسازید و اجرا کنید تا لیست کمیکها را در آغاز نرمافزار مشاهده کنید:
«FragmentManager» کمک کرد تا این کار را توسط «FragmentTransactions» انجام دهیم.
در کد بالا یک دستور «if» وجود دارد که کدهای نمایش فرگمنت را در آن قرار دادهایم. این کد بررسی میکند که آیا این اکتیویتی وضعیت ذخیره شدهای (Saved state) دارد یا خیر. هنگامی که یک اکتیویتی ذخیره شده باشد، تمام فرگمنتهای آن نیز ذخیره میشوند. اگر این بررسی را انجام ندهید، ممکن است در نهایت چندین فرگمنت داشته باشید:
همیشه حواستان به این باشد که وضعیت ذخیره شدهی اکتیویتی چه تاثیری برروی فرگمنتهای شما دارد.
اتصال دادهها
در هنگام کار کردن با این پروژه ممکن است چند چیز توجه شما را جلب کرده باشند:
- یک فایل به نام «DataBidningAdapters»
- یک ارجاع به «dataBinding» در «build.gradle» که ماژول اپلیکیشن است:
1dataBinding {
2enabled = true
3}
- یک بخش «data» در فایل قالب «recycler_item_rage_comic.xml»:
1<layout xmlns:android="http://schemas.android.com/apk/res/android">
2
3<data>
4
5<variable
6name="comic"
7type="com.raywenderlich.alltherages.Comic" />
8</data>
9
10...
11</layout>
- یک کلاس دادهای «Comic»
اگر قبلا از «data binding» استفاده نکرده باشید، ممکن است اینها برایتان سوال باشند. یک نگاه کلی به این مفهوم میاندازیم.
در حالت عادی، زمانی که میخواهیم به یک خصوصیت یک مقدار بدهیم، از کدی همانند کد زیر در اکتیویتیها و فرگمنتهایمان استفاده میکنیم:
1programmer.name = "a purr programmer"
2view.findViewById<TextView>(R.id.name).setText(programmer.name)
مشکلی که در این کد وجود دارد این است که اگر مقدار «name» را در «programmer» تغییر دهیم، باید یک «setText» نیز در «TextView» انجام دهیم تا این مقدار بروزرسانی شود. تصور کنید یک ابزار داشته باشید که بتوانید مقدار یک متغیر در فرگمنت یا اکتیویتی را به یک «view» متصل کنید تا هر تغییری در متغیر، به طور خودکار در «view» نیز اعمال شود. کاری که «data binding» برای ما انجام میدهد نیز همین است.
در اپلیکیشن ما، مقدار «enabled=true» که در «build.gradle» قرار دارد، امکان «data binding» را ایجاد میکند.
کلاس دادهای ما شامل اطلاعاتی است که میخواهیم در فرگمنت از آنها استفاده کنیم و توسط «view»ها نمایش دهیم. فیلد «data» شامل متغیری است که دارای گزینههای «name» و «type» است که نام و نوع متغیری که داریم متصل میکنیم را مشخص میکند. این دادهها توسط نشانهی «{@}» در «view»ها استفاده میشوند. برای مثال، کد زیر یک فیلد متن را با مقداری که فیلد «name» در متغیر «comic» دارد، پر میکند:
1tools:text="@{comic.name}"
حالا که «view» را آماده کردهایم، باید به آن دسترسی داشته باشیم و متغیرها را به آن متصل یا همان «bind» کنیم. در اینجا از «data binding» استفاده میکنیم. هر زمان که یک «view» یک فیلد «data» داشته باشد، این فریمورک به طور خودکار برای آن یک آبجکت برای اتصال تولید میکند. نام این آبجکت به اینگونه ساخته میشود که اسمهایی که به صورت «Snake Case» نوشته شدهاند (حالتی که بین هر کلمه با یک آندرلاین (_) فاصله ایجاد میشود، مانند Fara_Dars) را به «Camel Case» تبدیل میکند (حالتی که هر کلمه با حروف بزرگ شروع میشود، مانند FaraDars) و به آخر آن کلمهی «binding» را اضافه میکند. برای مثال، یک «view» که نام آن «recycler_item_rage_comic.xml» باشد، در حالت «binding» به نام «RecyclerItemRageComicBinding» تغییر پیدا میکند.
1override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
2//1
3val recyclerItemRageComicBinding = RecyclerItemRageComicBinding.inflate(layoutInflater,
4viewGroup, false)
5//2
6val comic = Comic(imageRmesIds[position], names[position], descriptions[position],
7urls[position])
8recyclerItemRageComicBinding.comic = comic
سپس میتوانید «view» را توسط متد «inflate» پر کنید و خصوصیات آن را بر اساس مکانیسم دسترسی، تنظیم کنید.
«Data binding» از الگوی «(Model-View_ViewModel (MVVM» پیروی میکند. این مدل از سه جز تشکیل شدهاست:
- یک «view»: فایل قالب
- یک مدل: کلاس دادهای
- یک «view model» یا «view binder»: فایل اتصال که به طور خودکار تولید میشود.
برقراری ارتباط با اکتیویتی
درست است که فرگمنتها به یک اکتیویتی متصل شدهاند، ولی تا موقعی که شما مجبورشان نکنید، با یک دیگر حرف نمیزنند.
در اپلیکیشن ما، باید زمانی که کاربر یک انتخاب انجام میدهد، «RageComicListFragment» به «MainActivity» اطلاع دهد تا «RageComicDetailsFragment» بتواند آن گزینهی انتخابی را نمایش دهد.
برای شروع، فایل «RageComicListFragment.kt» را باز کنید و کد زیر را در پایین کلاس اضافه کنید:
1interface OnRageComicSelected {
2fun onRageComicSelected(comic: Comic)
3}
این کد یک رابط شنونده (Listener Interface) تعریف میکند که اکتیویتی بتواند به واسطهی آن به فرگمنت گوش دهد. اکتیویتی این رابط را پیادهسازی میکند و زمانی که یک گزینه انتخاب میشود، فرگمنت نیز متد «()onRageComicSelected» را فراخوانی میکند و گزینهی انتخابی را به اکتیویتی ارسال میکند.
فیلد زیر را به زیر فیلدهای از قبل موجود در «RageComicListFragment» اضافه کنید:
1private lateinit var listener: OnRageComicSelected
این فیلد یک ارجاع به شنوندهی فرگمنت است (شنونده در اینجا همان اکتیویتی است).
در متد «()onAttach»، کد زیر را درست در زیر «super.onAttach(context)» اضافه کنید:
1if (context is OnRageComicSelected) {
2listener = context
3} else {
4throw ClassCastException(context.toString() + " must implement OnRageComicSelected.")
5}
این قطعه کد، شنونده را راهاندازی میکند. این کار را در «()onAttach» انجام میدهیم تا مطمئن باشیم که فرگمنت متصل شدهاست. سپس با استفاده از «instanceof» مطمئن میشویم که اکتیویتی، رابط «OnRageComicSelected» را پیادهسازی کردهاست.
اگر نکرده باشد، یک خطا خواهد داد، چراکه ادامهی دستورات قابل اجرا نخواهند بود. سپس اکتیویتی را به عنوان شنوندهی «RageComicListFragment» تعریف میکنیم.
در متد «()onBindViewHolder» که در «RageComicAdapter» قرار دارد، این کد را اضافه کنید:
1viewHolder.itemView.setOnClickListener { listener.onRageComicSelected(comic) }
این کد یک «View.onClickListener» به همهی کمیکها اضافه میکند تا فراخوانی شنونده برای ارسال گزینهی انتخابی انجام شود.
فایل «MainActivity.kt» را باز کنید و تعریف کلاس آن را به شکل زیر بروزرسانی کنید:
1class MainActivity : AppCompatActivity(), RageComicListFragment.OnRageComicSelected {
در اینجا یک خطا خواهید گرفت که از شما میخواهد «MainActivity» را از نوع «abstract» تعریف کنید و یا متد «(OnRageComicSelected(comic: Comic» که از نوع «abstract» است را در آن پیادهسازی کنید. فعلا با این خطا کاری نداشته باشید، بعدا آن را رفع میکنیم.
این کد مشخص میکند که «MainActivity» یک نمونهی پیادهسازی شده (implementation) از رابط «OnRageComicSelected» است.
فعلا فقط میخواهیم یک پیغام نشان دهیم که مشخص کنیم کد کار میکند. دستور «import» زیر را به دستورات موجود فعلی اضافه کنید که بتوانید از پیغامهای «toast» استفاده کنید:
1import android.widget.Toast
2
3سپس این متد را در زیر متد «()onCreate» اضافه کنید:
4override fun onRageComicSelected(comic: Comic) {
5Toast.makeText(this, "Hey, you selected " + comic.name + "!",
6Toast.LENGTH_SHORT).show()
7}
خطای قبلی رفع میشود. حالا نرمافزار را بسازید و اجرا کنید. پس از اجرای اپلیکیشن، روی یکی از کمیکها کلیک کنید. باید یک پیغام «toast» مشاهده کنید که نام آیتم کلیک شده را نمایش دهد:
حالا اکتیویتی و فرگمنت ما با یک دیگر صحبت میکنند.
آرگومانها و جابهجاییهای فرگمنتها
در حال حاضر، «RageComicDetailsFragment» فقط یک سری تصاویر و متون ثابت را نمایش میدهد، ولی فرض کنید که بخواهیم مواردی را به انتخاب کاربر نمایش دهد.
ابتدا، کل کدهای فایل «fragment_rage_comic_details.xml» را با کد زیر جایگذاری کنید:
1<layout xmlns:android="http://schemas.android.com/apk/res/android">
2
3<data>
4
5<variable
6name="comic"
7type="com.raywenderlich.alltherages.Comic" />
8</data>
9
10<ScrollView xmlns:tools="http://schemas.android.com/tools"
11android:layout_width="match_parent"
12android:layout_height="match_parent"
13android:fillViewport="true"
14tools:ignore="RtlHardcoded">
15
16<LinearLayout
17android:layout_width="match_parent"
18android:layout_height="wrap_content"
19android:gravity="center"
20android:orientation="vertical">
21
22<TextView
23android:id="@+id/name"
24style="@style/TextAppearance.AppCompat.Title"
25android:layout_width="wrap_content"
26android:layout_height="wrap_content"
27android:layout_marginBottom="0dp"
28android:layout_marginTop="@dimen/rage_comic_name_margin_top"
29android:text="@{comic.name}" />
30
31<ImageView
32android:id="@+id/comic_image"
33android:layout_width="wrap_content"
34android:layout_height="@dimen/rage_comic_image_size"
35android:layout_marginBottom="@dimen/rage_comic_image_margin_vertical"
36android:layout_marginTop="@dimen/rage_comic_image_margin_vertical"
37android:adjustViewBounds="true"
38android:contentDescription="@null"
39android:scaleType="centerCrop"
40imageResource="@{comic.imageResId}" />
41
42<TextView
43android:id="@+id/description"
44style="@style/TextAppearance.AppCompat.Body1"
45android:layout_width="match_parent"
46android:layout_height="match_parent"
47android:layout_marginBottom="@dimen/rage_comic_description_margin_bottom"
48android:layout_marginLeft="@dimen/rage_comic_description_margin_left"
49android:layout_marginRight="@dimen/rage_comic_description_margin_right"
50android:layout_marginTop="0dp"
51android:autoLink="web"
52android:text="@{comic.text}" />
53
54</LinearLayout>
55
56</ScrollView>
57</layout>
در بالای کد مشاهده میکنید که یک متغیر برای «Comic»هایمان اضافه کردهایم. متن «name» و «description» هم به متغیری به همان نام در شیء «Comic» متصل هستند.
آداپتورهای اتصال (Binding Adapters)
در «ImageView» که تصویر کمیک را نمایش میدهد، تگ زیر قرار دارد:
1imageResource="@{comic.imageResId}"
این کد در واقع با آداپتور اتصالی که در فایل «DataBindingAdapters.kt» ساختهایم ارتباط دارد.
1@BindingAdapter("android:src")
2fun setImageResoruce(imageView: ImageView, resource: Int) {
3imageView.setImageResource(resource)
4}
یک «binding adapter» این امکان را به ما میدهد که کارهایی را روی عناصری انجام دهیم که به طور پیشفرض توسط «data binding» پشتیبانی نمیشوند. در این اپلیکیشن ما داریم یک شماره منبع برای تصویری که قرار است نشان دهیم ذخیره میکنیم، ولی «data binding» به طور پیشفرض اجازهی نمایش یک تصویر از روی آیدی را نمیدهد. برای حل این مشکل از یک آداپتور اتصال استفاده میکنیم که یک ارجاع به آبجکتی که از آن فراخوانی شدهاست، به همراه یک پارامتر، نگه میدارد. از این ارجاع برای صدا کردن «setImageResource» در «imageView» که قرار است تصویر کمیک را نشان دهد، استفاده میکنیم.
حالا که «view» کامل شدهاست، کد زیر را به بالا «RageComicDetailsFragment.kt» اضافه کنید:
1import java.io.Serializable
متد «()newInstance» را با کد زیر جایگزین کنید:
1private const val COMIC = "comic"
2
3fun newInstance(comic: Comic): RageComicDetailsFragment {
4val args = Bundle()
5args.putSerializable(COMIC, comic as Serializable)
6val fragment = RageComicDetailsFragment()
7fragment.arguments = args
8return fragment
9}
یک فرگمنت میتواند پارامترهایی را در هنگام راهاندازی، از طریق آرگومانها دریافت کند، که به وسیلهی خصوصیت «arguments» میتوانیم به آنها دسترسی داشته باشیم. این آرگومانها درواقع یک «Bundle» هستند که جفتهایی به صورت کلید و مقدار را در خود نگه میدارند (درست همانند «Bundle» در «Activity.onSaveInstanceState»).
این «Bundle»ها را میسازیم و مقدار میدهیم، خصوصیت «arguments» را تنظیم میکنیم، و هر موقع به مقادیر آن نیاز داشتیم، از خصوصیت «arguments» برای بازیابی آنها استفاده میکنیم.
همانطور که بالاتر گفتیم، هنگامی که یک فرگمنت بازسازی میشود، سازندهی پیشفرض خالی استفاده میشود که هیچ پارامتری ندارد.
از آنجایی که فرگمنتها میتوانند پارامترهای آغازین خودشان را از طریق آرگومانهای ثابت صدا کنند، بعد از بازسازی فرگمنت نیز میتوانید از آنها استفاده کنید. همچنین کد بالا اطلاعاتی راجع به کمیک انتخاب شده را در آرگومانهای «RageComicDetailsFragment» ذخیره میکند.
کد «import» زیر را به بالای فایل «RageComicDetailsFragment.kt» اضافه کنید:
1import com.raywenderlich.alltherages.databinding.FragmentRageComicDetailsBinding
حالا محتوای «()onCreateView» را با کدهای زیر جایگزین کنید:
1val fragmentRageComicDetailsBinding = FragmentRageComicDetailsBinding.inflate(inflater!!,
2container, false)
3
4val comic = arguments.getSerializable(COMIC) as Comic
5fragmentRageComicDetailsBinding.comic = comic
6comic.text = String.format(getString(R.string.description_format), comic.description, comic.url)
7return fragmentRageComicDetailsBinding.root
از آنجایی که میخواهیم رابط کاربری «RageComicDetailsFragment» را به صورت پویا توسط مجموعهای از کمیکها تکمیل کنیم، یک ارجاع به «FragmentRageComicDetailsBinding» در متد «onCreateView» در فرگمنت ایجاد میکنیم. سپس بین «view» و کمیکی که به «RageComicDetailsFragment» ارسال کردهایم، اتصال ایجاد میکنیم.
در پایان نیز باید هنگامی که کاربر برروی یک آیتم کلیک میکند، به جای نمایش دادن یک پیغام، یک نمونه از «RageComicDetailsFragment» ساخته و به کاربر نمایش دهیم. فایل «MainActivity» را باز کنید و کد داخل «onRageComicSelected» را با کد زیر جایگزین کنید:
1val detailsFragment =
2RageComicDetailsFragment.newInstance(comic)
3supportFragmentManager.beginTransaction()
4.replace(R.id.root_layout, detailsFragment, "rageComicDetails")
5.addToBackStack(null)
6.commit()
این کد بسیار شبیه به کدی که برای اضافه کردن لیست به «MainActivity» نوشتیم است، ولی چند تغییر جزئی و مهم دارد.
- یک نمونه از فرگمنت میسازیم که چند پارامتر مهم دارد.
- به جای «add» از «()replace» استفاده کردیم که فرگمنت فعلی را پاک کند و فرگمنت جدید را قرار دهد.
- از یک متد جدید به نام «()addToBackStack» استفاده کردیم که زیر مجموعهای از «FragmentTransaction» است. فرگمنتها نیز همانند اکتیویتیها یک «back stack» یا همان تاریخچه دارند.
پشتهی فرگمنت از پشتهی اکتیویتی مجزا نیست. اینگونه فکر کنید که یک پشتهی جدید برروی پشتهی قبلی اکتیویتی است.
وقتی بین اکتیویتیها جابهجا میشویم، هرکدام آنها در پشتهی اکتیویتیها قرار میگیرد. هرگاه یک «FragmentTransaction» انجام میدهیم، این امکان را داریم که آن را به پشته اضافه کنیم.
پس «()addToBackStack» چکار میکند؟ فرگمنت جایگزین شده را در پشته اضافه میکند تا هر زمان کاربر دکمهی بازگشت دستگاه را زد، جابهجایی بین آنها بازگردانده شود. در اپلیکیشن ما، زدن دکمهی بازگشت، کاربر را به لیست کامل بازمیگرداند.
استفاده از «()add» در هنگام جابهجایی، امکان استفاده از «()addToBackStack» را از بین میبرد. این بدین معناست که این جابهجایی با کل اکتیویتی، بخشی از یک تاریخچه هستند. اگر کاربر دکمهی بازگشت را بزند، از اپلیکیشن خارج خواهد شد.
حالا اپلیکیشن را بسازید و اجرا کنید. برروی هر کمیک که ضربه بزنید، توضیحات آن را خواهید دید:
در اینجا کار ما به پایان میرسد. اپلیکیشن ما توضیحات هر کمیک را نمایش میدهد.
بعد از این چکار کنیم؟
فایل نهایی پروژه را میتوانید از این لینک دانلود کنید.
چیزهای خیلی بیشتری در زمینهی فرگمنتها هست که یاد بگیرید. همانند هر ابزار و ویژگی دیگری، اول ببینید آیا فرگمنت برای اپلیکیشن شما لازم است، و اگر بود، سعی کنید به بهترین نحو آن را پیادهسازی کنید.
برای افزایش مهارتتان در زمینهی فرگمنتها ، چند کار هست که میتوانید انجام دهید:
- از فرگمنتها در یک «ViewPager» استفاده کنید. خیلی از اپلیکیشنها، همانند «Play Store»، از یک ساختار لغزنده و زبانهبندی شده از طریق «ViewPager» استفاده میکنند.
- از یک «DialogFragment» قویتر و پرکاربردتر به جای یک «AlertDialog» ساده استفاده کنید.
- ببینید یک فرگمنت چگونه با سایر بخشهای اکتیویتی مانند نوار اپلیکیشن ارتباط برقرار میکند.
- یک رابط کاربری تطبیقپذیر را توسط فرگمنتها ایجاد کنید.
- از فرگمنتها برای پیادهسازی یک ساختار سطح بالا و پویا استفاده کنید.
^^
خسته نباشید واقعا عالی بود اما کاش با جاوا توضیح میدادین 🙁
ممنون از سایت خوبتون