من رو در شبکههای اجتماعی با شناسه @2hamed پیدا کنید و در گیتهاب.
آموزش ساخت Custom View در اندروید
احتمالا بارها پیش اومده که در برنامهای که مینویسید به المانی نیاز پیدا کنید که در میان ابزارها و Viewهای خود اندروید وجود نداشته باشه. در این مواقع به احتمال بسیار بسیار زیاد میتونید چیزی که میخواید رو در میان هزاران کتابخانه اوپن سورسی که در اینترنت موجود هست پیدا کنید.
اما احتمالا مواقعی هم بوده که چیزی رو که میخواستید پیدا نکردید و یا اگر هم پیدا کردید دقیقا اون چیزی نیست که میخواید. در این جور مواقع ۲ راه پیش رو دارید. یا اینکه به طراحتون بگید که طرح رو بر اساس ابزاری که موجود هست تغییر بده یا اینکه دست به کار بشید و خودتون اون چیزی که لازم دارید رو بسازید.
در این مطلب ما قصد داریم از پایه ترین حالت ممکن یک Custom View بسازیم. ابتدا قبل از هر کاری ببینیم چیزی که قراره بسازیم چی خواهد بود.
همونطور که میبینید چیزی که قراره ساخته بشه یک دکمه ضبط با چهار حالت بیکار، آماده، در حال ضبط و در حال بارگزاری خواهد بود.
نکته: من در این آموزش از زبان Kotlin استفاده میکنم که اگر با کاتلین آشنا نیستید پیشنهاد میکنم هر چه سریعتر دست به کار بشید و یاد گرفتنش رو شروع کنید.
مرحله ۱: اکستند از ویو
اولین کاری که باید انجام بدیم ساختن کلاس Viewمون هست که خوب مشخصا باید از کلاس View اندروید اکستند بشه. اسم ویو رو همونطور که میبینید RecordButton گذاشتیم.
در توضیح کد بالا باید بگم که ما در اینجا دو تا از Constructorهای کلاس View رو override میکنیم. این ۲ constructor معمولا پرکاربردترین حالتها هستند که البته حالتهای دیگری هم وجود داره که استفاده متفاوتی دارند.
مرحله ۲: محاسبه ابعاد و اندازهها
در این مرحله ما ابتدا به سراغ ایجاد سادهترین حالتمون میریم. یعنی مود بیکار یا Idle که از دو تا دایره تشکیل شده. دایره کوچکتر مرکز سفید رنگمون هست و دایره بزرگتر حاشیه خاکستری رنگ.
ابتدا قبل از شروع کشیدن دایرهها لازمه که بدونیم ویوی ما دارای چه ابعادی هست. اینکه عرض و ارتفاع چه مقداری هست که ما بتونیم مرکز دایره و شعاعش رو مشخص کنیم. برای اینکه بتونیم این محاسبات رو انجام بدیم باید بدونیم که ابعاد ویوی ما چیا هستن و برای اینکار متود onMeasure
رو override میکنیم.
توضیح: در قسمت بالایی کد متغیرهای رو تعریف میکنیم که قراره مرکز ویو و شعاع دایره بزرگ رو تو خودشون نگه دارند. در خط اول onMeasure
هم super.onMeasure
رو صدا میزنیم که اجباری هست در این حالت و حتما باید حضور داشته باشه. توی اون if این شرط رو چک میکنیم که حتما اندروید به ویوی ما ابعادش رو تخصیص داده باشه و اگر هر کدوم از ارتفاع یا عرض، صفر بودن منتظر دوباره صدا زده شدن متود onMeasure
میشیم.
داخل شرط هم که مشخصه مرکز ویو رو با تقسیم کردن عرض و ارتفاع بر ۲ به دست میاریم. شعاع هم برابر میذاریم با ۹۵ درصد کوچکترین بعد. یعنی ۹۵ درصد ارتفاع یا عرض، هر کدوم که کوچکتر بود و همچنین مقدار padding
رو هم ازش کم میکنیم.
تذکر: اینکه چرا من ۹۵ درصد رو انتخاب کردم کاملا به سلیقه شخصی بر میگرده و شما میتونید هر چیزی بذاریدش.
مرحله ۳: مشخص کردن رنگ و مشخصات اجزاء
الان نوبت مشخص کردنه رنگ و بقیه مشخصات دایره و قوسمون رسیده. برای اینکار از کلاس Paint
استفاده میکنیم.
آبجکت innerPaint
مربوط به دایره سفید رنگ وسط و outerPaint
مربوط به دایره بیرونی هستش. نظر شما رو به radiusDiff
هم جلب میکنم که مشخص میکنه دایره بیرونی ما چقدر بزرگتر از داخلیه باشه. مشخصهی isAntiAlias
هم برای اینه که دایرههای ما اون حالت روان خودشون رو داشته باشن و اصطلاحا پیکسلی (pixelated) نشن.
مرحله ۴: نقاشی روی بوم
مرحله کشیدن یا Draw میشه گفت مهمترین مرحله تو ساخت یک Custom View به حساب میاد که با override کردن متود onDraw
انجام میشه. در اینجا با یک آبجکت Canvas سر و کار داریم که مثل یک بوم نقاشی کاملا خالی هستش و میتونیم هر چیزی رو داخلش نقاشی کنیم.این آبجکت Canvas چیزی هست که در نهایت به کاربر نمایش داده میشه.
در اینجا ما ابزارهایی داریم که با کمک اونها میتونیم نقاشی کنیم که در این مرحله ما فقط از ابزار کشیدن دایره استفاده میکنیم. متود Canvas.drawCircle
با دریافت مختصات x و y مرکز و مقدار شعاع و یک آبجکت Paint
روی بوم دایره میکشه.
تذکر: یک نکته مهم که اینجا لازمه یاداور بشم اینه که توی متود onDraw
به هیچ وجه نباید allocation انجام بشه. به این معنی که هیچ آبجکت جدیدی نباید ساخته بشه و بهش حافظه تخصیص داده بشه. به طور کلی هیچ کار سنگینی نباید انجام بشه و این نکته به این دلیله که اندروید برای اینکه بتونه کیفیت انیمیشنها و نمایش تصویرش رو حفظ کنه نیاز داره که در هر ثانیه، ۶۰ بار صفحه رو آپدیت کنه و این یعنی ما فقط فرصت داریم در فاصله یک فریم شکل مورد نظرمون رو بکشیم که میشه چیزی حدود ۱۶ میلیثانیه. هر کاری بیش از این مقدار باعث ایجاد لگ میشه. پیشنهاد میکنم این مطلب رو در مورد این نکته بخونید.
مرحله ۵: افزودن حالتهای آماده و در حال ضبط
در این مرحله ما نیاز داریم که حالتهای مختلفی رو نمایش بدیم. بهترین ابزاری که میتونید برای ذخیره و نمایش چند حالت خاص استفاده کنید از نظر من enum class
هست. اگر نمیدونید enum ها چی هستن پیشنهاد میکنم این مطلب رو در سکان آکادمی بخونید.
اگر خاطرتون باشه ما بطور کل ۴ حالت متفاوت داشتیم. حالتهای بیکار، آماده، در حال ضبط و در حال بارگزاری که به ترتیب با Idle، Ready، Recording و Loading مشخص میشن. و کلاس enum به صورت زیر خواهد بود.
آیتم جدید دیگری که باهاش روبرو هستیم نمایش یک مربع قرمز برای حالت در حال ضبط هست. برای کشیدن یک مربع روی بوم ما نیاز به یک آبجکت از کلاس RectF
داریم که چهار لبه مربعمون رو مشخص میکنه و برای اینکه بتونیم درست تشخیص بدیم که کجا این مربع قرار میگیره به سراغ متود onMeasure
میریم.
توضیحات: در این قسمت به غیر از متغیرهای قبلی ما دو متغیر جدید recordRadius
که برای اندازه دایره و مربع قرمز و recordRect
برای مختصات مربع قرمز داریم. من در اینجا مقدار recordRadius
رو برابر با یک چهارم دایره بزرگ قرار دادم و با کم و زیاد کردن recordRadius
از مختصات مرکز، ابعاد recordRect
رو نیز مشخص کردم. recordPaint
هم که برای مشخص کردن رنگ قرمز استفاده شده.
حالا دوباره نوبت نقاشی روی بوم رسیده.
مرحله ۶: در حال بارگزاری
برای حالت در حال بارگزاری یا Loading ما به ابزار جدیدی به نام Arc (قوس) احتیاج داریم. چون با توجه به درصد پیشرفت (progress) ممکنه لازم باشه فقط مقدار کمی از دایره بیرونی کشیده بشه. خوشبختانه کشیدن یک قوس بسیار راحته. یک قوس هم مثل یک مربع نیاز به یک آبجکت RectF
داره که مشخص کنه در کجا این قوس کشیده بشه. پر واضحه که ابعاد این مربع، در متود onMeasure
مشخص میشه.
و سپس نقاشی...
توضیحات: برای کشیدن یک قوس ما علاوه بر داشتن RectF
نیاز به زاویه هم داریم که خیلی راحت مقدار progress رو تبدیل به کسر ۱۰۰ کرده و ضربدر 360 درجه میکنیم. مثلا با داشتن مقدار ۵۰ برای progress ابتدا ۵۰ رو تقسیم بر ۱۰۰ میکنیم که به عدد ۰.۵ میرسیم و سپس با ضرب در ۳۶۰ به زاویه ۱۸۰ میرسیم.
مرحله ۷: جمع بندی و تمیز کاری
نکته دیگری که لازمه بهش اشاره کنیم به روزرسانی View در مرحلهای بعد از ایجاد اون هست. برای مثال ممکنه بخوایم از مود آماده به مود در حال ضبط بریم یا اینکه بخوایم درصد پیشرفت رو به روز کنیم. برای اینکه ما به ویو خبر بدیم که مقادیر تغییر کرده و نیازه که از ابتدا مرحله draw انجام بشه میتونیم از متود invalidate()
استفاده کنیم. به این صورت که هر زمان هر کدوم از مقادیر ذکر شده تغییر کرد ما invalidate رو صدا بزنیم. به این صورت:
یه مورد دیگه اینکه ما برای تغییر واحد dp به px از فانکشن dpToPx
استفاده کردیم که تعریفش رو در ادامه میبینید.
fun dpToPx(dp: Int) = (dp * resources.displayMetrics.density).toInt()
تمام! شما الان یک CustomView ساختید که همه اجزاش از پایه درست شدن. ساختن CustomView برای ایجاد اشکال پیچیده خیلی بهینهتر و سریعتر از استفاده از Layoutهای پیچیده و تو هم هست. امیدوارم که توضیحاتم واضح بوده باشه و اگر فکر میکنید قسمتی رو گنگ توضیح دادم حتما در قسمت نظرات یاآور بشید که اصلاحش کنم.
برای اون دسته از برنامهنویسان علاقه مند هم به عنوان تمرین میتونید خودتون قسمت بعدی که نمایش درصد پیشرفت داخل ویو هست رو انجام بدین.
همونطور که میبینید در اکثر مواقع ساختن یک CustomView خلاصه میشه به استفاده و ترکیب همین ابزارهای ساده که دونستشون کمک میکنه کدهای بهتری و در نتیجه پروژههای بهتری رو بتونیم ایجاد کنیم.
مثل همیشه تمام سورس این آموزش در مخزن گیتهاب زیر موجود است.
مطلبی دیگر از این انتشارات
راه های افزایش مهارت های برنامه نویسی اندروید (قسمت 1)
مطلبی دیگر از این انتشارات
نکات و سوالات مصاحبه اندرویدی
مطلبی دیگر از این انتشارات
باگ اندروید استدیو ۳.۴.۱