سفارشی سازی نحوه نمایش آیتم های لیست ایجاد شده
در این درس از دوره آموزشی توسعه اپلیکیشنهای اندروید، تمرکز بر سفارشیسازی نمایش آیتمهای لیست است که یکی از مهارتهای کلیدی برای خلق تجربه کاربری حرفهای محسوب میشود. شما یاد میگیرید چگونه با بهرهگیری از آداپترهای سفارشی و طراحی فایلهای layout، ظاهر و ساختار آیتمهای لیست را به شکلی جذاب و متناسب با نیاز کاربران تنظیم کنید. این آموزش به شما نشان میدهد که چگونه دادهها را بهصورت پویا و کاربرپسند ارائه دهید، تا نهتنها از نظر بصری زیبا باشند، بلکه کارایی اپلیکیشن را نیز ارتقا دهند. با تسلط بر این تکنیکها، قادر خواهید بود لیستهایی حرفهای و مؤثر در اپلیکیشنهای اندروید پیادهسازی کنید که اطلاعات را به بهترین شکل به کاربران منتقل کنند.
تا اینجا کار، برنامه ؛ یک لیست ساده از رشته ها رو نمایش میده، ولی من میخوام که ظاهر این لیست رو جذابتر کنم و همینطور داده های پیچیده تری رو ساپورت کنم.
خب من روی پروژه های به نام Customize List کار میکنم، و قصد دارم نمایش لیستم رو سفارشی کنم و مقادیر متنی متنوعی رو همراه با چندین تصویر نمایش بدم. من از قبل یک فولدر assets با یک تعداد تصویر به این پروژه اضافه کردم. اول از همه، فولدر assets رو ساختم. همونطور که در ویدیوی قبلی از این دوره آموزشی نحوه ساختن این فولدر رو آموزش دادم، یک فولدر assets میتونه شامل تصاویر و یا فایل های دیگه ای که قصد دارید به برنامه تون اضافه کنید باشه.
این فایل های image، resource ID ندارند، به جاش موقع استفاده اونها رو با نام فایل شون آدرس دهی و فراخونی میکنم. به علاوه من یک جفت کلاس جاوا هم اضافه کردم. کلاس product که نشون دهنده یک شی پیچیده با چهار مقدار هست، که سه تاشون از نوع string یا رشته هستن و یکیش هم یک عدد از نوع double هستش. رشته های نسبت داده شده به این کلاس؛ product ID، یعنی آیدی محصول، name که همون نامش میشه و description که یک توضیح مختصر در مورد محصول میشه هستن. به علاوه یک مقدار عددی هم داره که مربوط میشه به قیمت اون محصول. همونطور که میبینید برای هرکدوم از این ویژگی ها متد get تعریف شده و این کلاس یک متد سازنده هم داره که همونطور که قبلا هم گفتم این متد به ما این امکان رو میده که از این کلاس نمونه هایی ایجاد کنیم. اگه قبلا برنامه نویسی شی گرا کار کرده باشید کاملا متوجه اجزا این کلاس میشید. در غیر این صورت با کمی دقت متوجه میشید که این کلاس در واقع نماینده یک محصول هست که چهار ویژگی داره: آیدی، نام، شرح و قیمت محصول. متد های getter این کلاس برای این تعریف میشن که بتونیم زمانی که داده ای از این نوع تعریف کردیم ویژگی های اون داده رو با استفاده از این متد ها بدست بیاریم. البته در این مدل کلاس ها متدهای setter هم برای دادن مقدار به شی ای از جنس این کلاس تعریف میشه ولی چون در این برنامه نیازی ندیدم متدهای setter رو اضافه نکردم. اگه تمایل داشتید میتونید با مطالعه مباحث برنامه نویسی مربوط به شی گرایی یا object oriented اطلاعات بیشتری راجع به این موضوع کسب کنید. خب برمیگردیم سر بحث خودمون.
یک کلاس دیگه هم با نام Data Provider وجود داره. که این کلاس یک لیست از اشیای پیچیده ایجاد میکنه و هر شی یک نمونه از همون کلاس product هستش. این کلاس شامل دو بخش استاتیک هستش، یک list و یک map که میان و داده های رو در فرم مون exposes می کنن. متغیر لیست یک لیست مرتب از داده ها هست و متغیر map یک مجموعه از جفت مقادیر کلید هستش. به طوری که اولی یک string یا رشته کلید هست و آیتم بعدی هم یک نمونه از کلاس product هستش. اینم بدونید منظور از کلید این هست که تنها یک داده با این مقدار وجود داره.
نهایتا، من یک فایل لایوت جدید، با نام list_item.xml اضافه کردم. این لایوت نشون دهنده ظاهر هر سطر از لیست من هستش. با یک component و شی image view یا تصویر در سمت چپ شروع میشه، و بعد همونطور که می بینید با یک linear layout سمت راستش که اشیای داخلش به صورت افقی چیده شدن ادامه پیدا می کنه. داخل linear layout دو تا text view قرار داره، یکی اش برای نام مربوط به آیتم لباس و یکی هم برای قیمت اون محصول. و برای هر کدوم از این text viewها یک متن الکی گذاشتم تا فعلا ببینم که ظاهرش چطوری بنظر میاد.
هر سطر از لیست ویو قرار هست به این شکل ظاهر بشه، با یک تصویر در سمت چپ و دو تا مقدار text در سمت راست تصویر که به صورت افقی چیده شدن. خب بیایید بریم ببینیم چطوری میشه اینها رو بهم متصل کرد.
به کلاس Main Activity میرم، زمانی که اکتیویتی بالا میاد، اول باید یک نمونه از کلاس Array Adapter ایجاد بشه. اجزای این آداپتر طراحی شده، نمونه ای از این کلاس String هستن، و بعدش هم از یک لایوتی که از Android SDK اومده بود استفاده کردم، و دلیلش هم این بود که از یک لیست ساده از رشته ها برای هر سطر استفاده میکردم.
ولی حالا میخوام که این Array Adapter رو با Array Adapter سفارشی شده خودم جایگزین کنم، و میدونم که باید از کلاس Product ام و فایل لایوتی که ساختم استفاده کنم. پس برای انجام اینکارها، از پکیج اصلی فولدر جاوا ام شروع میکنم و روش راست کلیک میکنم و از مسیر New با انتخاب Java Class یک کلاس جاوا جدید ایجاد میکنم. و در پنجره Create New Class اسمش رو Product ListAdapter میذارم. این کامنت های عمومی که به صورت اتوماتیک وجود دارند رو حذف میکنم و بعد به کلاس یک بند extends اضافه میکنم.
قصد دارم که این کلاس رو از کلاس Array Adapter، extend کنم، و میخوام این سوپر کلاس خاص از نوع کلی Product باشه، و Product همون کلاسی هست که خودم به صورت دستی و سفارشی ساختمش، پس کلاس آداپتر سفارشی من یک زیر کلاس از کلاس Array Adapter هستش که هر کدوم از آیتم های اون نوعی از کلاس product سفارشی من هستن. به عبارت دیگه هر کد رو از آیتم های کلاس آداپتر ام شامل لیستی از اشیای از جنس product هستش. در واقع لیستی از محصولات با ویژگی هایی که تو کلاس product تعریف کردم. توجه داشته باشید، یک ارور اینجا ظاهر شده، و من با استفاده از دکمه های آلت و اینتر و آپشن و return به ترتیب در ویندوز و مک از راهنمای اندروید استفاده میکنم و میبینم که یک پیشنهاد با عبارت create a constructor matching the superclass برای ساخت یک سازنده مطابق با سوپر کلاس بهمون داده.
انتخابش میکنم و میبینم که سازنده های مختلفی وجود داره. من میخوام این یکی رو که سه تا آرگومان داره انتخاب کنم، آرگومان هاش هم یک context یا محتوا، یک resource ID و یک مجموعه از اشیا هستن. ممکنه که همه این سینتکس ها رو روی صفحه نمایش تون نبینید، ولی با باز کردن صفحه این پنجره به سمت راست میشه گزینه های این دیالوگ رو به صورت کامل دید. من اون نسخه از سازنده رو انتخاب و اوکی رو میزنم. همونطور که میبینید این متد سازنده یک آرایه از product ها میخواهد ولی من قصد دارم اونو تغییر بدم.
به جای یک آرایه ساده، میخوام این یک لیست از product ها باشه، دلیلش هم نحوه گرفتن داده از کلاس Data Provider هستش چون نوع داده خروجی این کلاس از جنس لیست هستش. پس این رو به لیست تغییر میدم و نوع داده این لیست هم Product میذارم، یعنی لیستی از محصولات، و از ایمپورت شدن لیست به کلاسم مطمئن میشم، اینجا تو قسمت ایمپورت ها. بعد یک لیست از product ها رو به عنوان جزیی از این کلاس ایجاد میکنم و private تعریفش میکنم و اسمش رو products میذارم. کلمه private درواقع سطح دسترسی متغیر رو تعیین میکنه. متغیری که private تعریف بشه همونطور که از اسمش پیداست فقط در اون کلاس شناخته میشه و در خارج از کلاس خودش هیچگونه دسترسی بهش نداریم.
بعد، همون چیزی رو که متد سازنده فراخونی کرده، با تایپ products = objects بعنوان مرجعی برای داده ها ذخیرش میکنم. به عبارت دیگه اومدم مقداری رو که متد سازنده برمیگردونه میذارمش توی products تا توی این کلاس ذخیره بشه تا بتونم بعدا ازش استفاده کنم؛ و حالا من با این کار یک مرجع دائمی برای همه داده هایی که بهشون نیاز دارم رو تعریف کردم. بعد باید متدی با نام get View رو فراخونی کنم. این متد به صورت اتوماتیک وقتی کاربر لیست رو به سمت بالا و پایین اسکرول میکنه فراخونی میشه، و به ازای هر سطر از لیست ویو این متد یکبار فراخونی میشه تا اطلاعات رو از product یا محصول جاری بگیره و بعدش هر کدوم از این اطلاعات رو بصورت بصری و کنترل شده در لایوت List_item.xml أم نمایشش بده.
من برای شروع نام متد get View رو تایپ میکنم، و اون رو از لیستی که بهم پیشنهاد میشه، انتخاب میکنم. و کد سفارشی شده خودم رو اینجا اضافه میکنم. همونطور که میبینید متد get View آرگومانی با نام convert View میگیره. این ویو نماینده یک سطر از لیست ویو هستش. این متغیر هم میتونه مقدار null رو بگیره و هم میتونه نشون دهنده لایوتی که از قبل ساختیم باشه. دلیلش هم این که لیست ویو میخواد با توجه به یک الگویی آیتم هاش رو یکی یکی ایجاد کنه، و قرار نیست که برای هر سطری که کاربر میبینه یکبار یک نمونه از این ویو رو ایجاد و تنظیم کنیم. در واقع داریم با نوشتن این کدها در متد get View ساختار کلی هر ردیف رو مشخص میکنیم.
خب من یک کد شرطی اینجا بکار میبرم، و چک میکنم که این ویوی جاری خالی هست یا نه و میگم که درصورتی که خالی هست و برابر null هستش نمونه ای ازش ایجاد بشه. تایپ میکنم convertView = و بعد کلاس Layout Inflater رو فراخونی میکنم. اگه خاطرتون باشه قبلا این کلاس رو برای آماده کردن لایوت برای لود شدن اکتیویتی استفاده میکردیم ولی این بار از سینتکسی متفاوت استفاده کنم. تایپ میکنم LayoutInflator .from و این یک context یا محتوا نیاز داره، پس متد get Context رو وارد میکنم، و بعد کرسر رو به آخر جمله منتقل میکنم و یک دات میذارم و در خط بعد متد به نام inflate رو فراخونی میکنم، و به لایوت شخصی سازی شده خودم ارجاعش میدم که مسیرش R .layout.list_item هستش.
خب دو تا آرگومان دیگه هم میخواد. اول parent، یا والد، که درواقع این آرگومان داره میگه که این عملیات مربوط به View Group جاری هستش و بعد هم یک مقدار بولین false که مربوط به تغییرات اکتیویتی قبلی میشه. (توضیح این موضوع فراتر از آموزش ما هست و بحثش کمی پیچیده است پس ازش میگذرم.) پس من الان یک لایوت دارم که میتونم باهاش کار کنم، و میخوام مقادیر لازم رو از اطلاعات موجود در آیتم جاری بگیرم و اونها رو به عنوان کنترل های بصری معرفی کنم. توجه کنید که در آرگومان های متد get View یک آرگومان به نام position دریافت میشه، که از نوع integer هم هست.
این متغیر به من موقعیت فعلی آیتمی که به داده نیاز داره رو میده که این آیتم در واقع شماره سطر جاری از لیست ویو هستش. از این position برای بدست آوردن اطلاعات محصول جاری از داده هام استفاده میکنم. یعنی محصولی که قرار هست توی این سطر نمایش داده بشه. من یک متغیر از نوع داده product تعریف میکنم، و اسمش رو product میذارم، و مقدارش رو با products.get بدست میارم. این products همینی هست که این بالا تعریفش کردم. و آرگومانش هم که واضح هست باید position بدم. خب حالا من یک ارجاع به آیتم محصول مورد نظرم دارم و میخوام که تیتر اون محصول رو نمایش بدم.
من یک شی Text View ایجاد میکنم، و اسمش رو name Text میذارم. این اشاره داره به نام آیتم لباس فعلی. با فراخونی متد convertView .findViewById برای این Text View یک ارجاعی به این Text View موجود در لایوتم میدم، و ورودی این متد رو R .id.nameText میذارم و برای اطمینان دادن از نوع بازگشتی اش از این شئ Text View کستش میکنم. بعد میگم که nameText.setText ورودیش رو product .getName وارد میکنم، میبینید که من با این کارم تونستم یک متد getter از آیتم product ام فراخونی کردم.
بعد میام پایین سراغ این جمله return و شی convert View رو بعنوان متغیر بازگشتی بهش میدم. خب، این همه کاری بود که برای تعیین مقادیر کنترل های بصری باید انجام میدادیم. من بعدا بهتون نشون میدم چطوری بقیه کنترل ها رو سفارشی کنید. ولی الان باید به کلاس Main Activity برم و کد مربوط به ساختار Array Adapter رو که داده ها رو به لیست ویو متصل میکرد تغییر بدم.
این کدهای قبلی رو که از کلاس استاندارد ArrayAdapter استفاده می کردن رو کامنت میکنم، و میام این بالا و یک متغیر private از جنس list که جنس اشیاءش product هست تعریف میکنم و یکبار دیگه اسمش رو products میذارم و مقدارش رو با فراخونی فیلد product Listاز کلاس Data Provider تعیین میکنم. خب حالا من یک مرجع دائمی از داده هایی که میخوام باهاشون کار کنم دارم و میام پایین سراغ متد on Create ام و یک نمونه از کلاس سفارشی Product ListAdapter ام ایجاد میکنم.
اسمش هم عین قبل adapter میذارم و با عبارت new ProductListAdapter معرفی اش میکنم. من به سه تا آرگومان نیاز دارم، عبارت this رو به عنوان context وارد میکنم و بعد R .layout.list_item رو به عنوان لایوت مورد نظر، و در آخر products ام. این کد رو میبندم و مطمئن میشم که اروری وجود نداشته باشه و الان آماده ام تا برای بار اول نمایش این لیست جدید رو تست کنم.
و این هم نتیجه. درحال حاضر برای همه آیتم ها یک تصویر و یک قیمت خورده شده ولی می بینید که نمایش لیستم با نام های سفارشی برای هر محصول با موفقیت انجام شده، مرحله بعدی این هست که قیمت ها و همینطور تصاویر مربوط به هر آیتم رو سفارشی کنیم و من در ویدئوی بعدی از این دوره آموزشی این رو آموزش میدم.