وقت اینه که دست های خودمون رو با Epoxy رنگی کنیم [سری آموزش پیاده سازی لیست های با ساختار پیچیده با استفاده از کتابخانه Epoxy در Android]

اگر با Epoxy آشنایی ندارید می تونید یه سری به صفحه گیت هاب این کتابخانه بزنید https://github.com/airbnb/epoxy یا همین جا بمونید و ادامه بدید !توجه کنید که در این سری آموزش فرض شده است که با RecyclerView آشنا هستید ، در غیر این صورت می توانید از طریق آموزش های موجود در اینترنت نسبت به یادگیری این موارد اقدام کنید .What is Epoxy ?اپوکسی یک لایه Abstraction است که بر روی کتابخانه RecyclerView بنا شده است (اینی که گفتی یعنی چه ؟!!)بزارید راحت باشیمهمیشه وقتی صحبت از لیست ها باشه ناخود آگاه به تعدادی آیتم که دقیقا شبیه هم هستند فک می کنیم . مثلا لیستی از نام ، شماره ، غذا ، ماشین و ... و برای اجرای این لیست عادی این مراحل رو طی می کنیم : (صرفا به عنوان مثال)داخل لایه activity یک RecyclerView اضافه میکنیم یک لایه برای آیتم های که قراره داخل لیست نشون داده بشن می سازیمیک Adapter از نوع RecylerView.Adapter می سازیم یک layoutManager می سازیم که بسته به نیاز می تونیم LinearyLayoutManager باشه یا GridLayoutManager یا ...و تمام این موارد رو بهم متصل می کنیم تا لیست درست بشه یک فرایند تکراری به شدت خسته کننده با کدهایی که هر بار مجبوری تکرار کنی (البته روش هایی برای کم کردن میزان این کد ها هست ولی به این سری آموزش ربطی نداره)چیزی که شاید کمتر بهش فک کنیم اینه که اگر حتی 100,000،000 آیتم به Adapter اضافه کنیم باز هم اسکرول نرم و بدون لگی رو داریم و تعداد آیتم ها هیچ تاثیری نداره (این از برکات استفاده از RecyclerView ِ) .حالا فرض کنید که ازتون بخوان یک لیست با دو نوع لایه متفاوت بسازید . خوب خیلی راحت می تونید لایه جدید رو داخل فولدر layout تعریف کنید و متد getItemViewType اداپتر رو override کنید و مشخص کنید که برای هر position کدام لایه استفاده بشه و داخل onCreateViewHolder لایه مورد نظر رو inflate کنید . به این صورت : final int VIEW_TYPE_1 = 0; final int VIEW_TYPE_2 = 1; @Override public int getItemViewType(int position) { if (position % 2 = 0) { return 1; } return 0; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // لایه نوع یک View itemLayoutViewType1 = LayoutInflater.from(parent.getContext()).inflate( R.layout.item_type_1, parent, false); // لایه نوع دو View itemLayoutViewType2 = LayoutInflater.from(parent.getContext()).inflate( R.layout.item_type_2, parent, false); switch (viewType) { case VIEW_TYPE_1 : ViewType1Holder type1Holder = new ViewType1Holder (itemLayoutViewType1); return type1Holder ; default : ViewType2Holder type2Holder = new ViewType2Holder (itemLayoutViewType2); return type2Holder ; } class ViewType1Holder extends RecyclerView.ViewHolder { ... } class ViewType2Holder extends RecyclerView.ViewHolder { ... }کار به اینجا ختم نمیشه و باید داخل onBindViewHolder هم منطقی مناسب رو اضافه کنید :@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { if (holder instanceof ViewType1Holder ) { ... } if (holder instanceof ViewType2Holder ) { ... } }حالا اگر درخواست دیگری مبنی بر اضافه کردن نوع سومی از لایه بشه چی ؟ به زودی می بینید که با اضافه کردن viewType های بیشتر به لیست ، حجم Adapter زیاد میشه و خوانایی کدها و کمتر میشه و نتیجتا نگهداریش سخت میشه . دقت کنید که ممکنه لایه نوع n ام خودش یک لیست باشه (nested lists) و برای این مورد نیاز است کد و اقدامات بیشتری انجام داده بشه . استفاده از nested لیست ها در طراحی برنامه اندرویدی خیلی مرسوم هستند . مثلا اکثر برنامه فروشگاهی مانند google play و دیجی کالا یا موارد دیگر از این ساختار استفاده می کنند . لیست های تو در تو (nested list) :تصویر بالا رو ببینید ، لیست اصلی یک لیست عمودی است و آیتم های موجود در این لیست خودشان لیست های افقی هستند . درست کردن یک همچین لیستی کار تقریبا ساده ای است . کافیه دو Adapter جداگانه تعریف کنیم که یکی از آنها لیست مادر را اداپت کند و اون یکی هم لیست ها فرزند رو . داخل onBindViewHolder لیست مادر باید یک همچین منطقی اضافه کنیم : childRecyclerView.setHasFixedSize(true); childRecyclerView.setLayoutManager(new ChildLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)); childRecyclerView.setAdapter(adapter); childRecyclerView.setRecycledViewPool(viewPool); تعیین کردیم که سایز تمام آیتم ها یکسانه (setHasFixedSize) . اینطوری لیست مادر دیگه لیست لازم نیست با اضافه کردن آیتم جدید به محاسبات بپردازه کافیه از اندازه های موجود استفاده کنه و تمام لیست های فرزند به یک viewPool یکسان متصل هستند اینطوری آیتم های موجود در داخل لیست های فرزند بین اونها به اشتراک گذاشته می شه (recycle میشه). در نگاه اول هیچ مشکلی خاصی با پیاده سازی لیست های تو در تو نداریم . ولی مشکلات به زودی نمایان می شوند .گفتیم که viewPool آیتم های موجود در لیست های فرزند رو recycle میکنه و دوباره استفاده میکنه . یعنی ممکنه آیتمی که الان داخل لیست n ام داریم می بینیم قبلا داخل لیست m ام بوده باشه . توجه کنید که viewPool وقتی کاربرد داره که آیتم ها یکسان باشند درست مث google play که 90 درصد یا بیشتر آیتم های موجود در لیست های افقی یکسان هستند . وجود آیتم های یکسان باعث میشه که اسکرول نرم و بدون لگی داشته باشیم . اما با در نظر گرفتن لایه های متفاوت برای هر کدام از لیست های افقی دیگه این اسکرول رو نرم رو نخواهیم داشت و با لگ های طولانی مواجه می شیم . علتش اینکه هیچ view ای بازیافت نمیشه و لیست ها افقی مجبورا هر بار آیتم هاشون رو inflate کنند که زمان بره . مشکل بعدی اینه که state لیست های افقی ذخیره نمیشه و scroll position خودشون رو زمانی که از لیست اصلی detach بشن از دست میدن .البته برای این مورد میتونیم منطقی بنویسیم که state ها رو ذخیره کنه و بعدا اینا رو برگردونیم .لیست با چند view typeخوب اینجاست که Epoxy خود نمایی میکنه برای ساخت لیست هایی با آیتم های با ساختار پیچیده و همزمان تعداد viewType های متفاوت (در حد اینکه هیچ آیتم یکسانی نداشته باشیم !) از Epoxy استفاده می کنیم . ادامه دارد ...