کار با متریال دیزاین در برنامه نویسی اندروید — بخش دوم

در قسمت اول این آموزش با آمادهسازی قالب نمایش و افزودن view آشنا شدیم. در این بخش به بررسی چگونگی استفاده از ابزارهای موجود برای زیباسازی ظاهر برنامه در متریال دیزاین خواهیم پرداخت.
افزودن یک انیمیشن ظاهر شونده (Reveal Animation)
حالا میخواهیم که به کاربر این امکان را بدهیم که بتواند در مورد هریک از این مکانها و تصاویر، یادداشتهایی را بنویسد. برای اینکار، در «activity_detail.xml» یک «edittext» داریم که به طور پیشفرض مخفی است. وقتی کاربر برروی «FAB» (مخفف FloatingActionButton) کلیک میکند، این «edittext» به همراه یک انیمیشن مانند تصویر زیر نمایان میشود.
«DetailActivity» را باز کنید. دو متد هستند که باید در آن پیادهسازی کنید:
- ()revealtEditText
- ()hideEditText
ابتدا، کدهای زیر را به «()revealEditText» اضافه کنید:
val cx = view.right - 30 val cy = view.bottom - 60 val finalRadius = Math.max(view.width, view.height) val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, 0f, finalRadius.toFloat()) view.visibility = View.VISIBLE isEditTextVisible = true anim.start()
دو مقدار «int» که داریم، مقادیر x و y موقعیت view را با کمی تغییر جزئی میگیرند. این تغییر جزئی به کاربر این تصور را میدهد که این انیمیشن دارد از سمت FAB صورت میگیرد. پس از آن، یک شعاع به آن دادهایم که حالت دایرهای شکل را مانند تصویر بالا ایجاد میکند. تمامی این مقادیر x، y و شعاع به نمونه انیمیشن ما ارسال میشوند. این انیمیشن از «ViewAnimationUtils» استفاده میکند که به ما اجازه ساخت این حالت دایرهای را میدهد.
از آنجایی که «EditText» به طور پیشفرض مخفی است، باید به آن مقدار «VISIBLE» را بدهیم و مقدار «isEditTextVisible» را نیز به «true» تغییر دهیم. در نهایت تابع «()start» را در انیمیشن صدا میکنیم. برای اینکه view را از بین ببریم، باید مقادیر زیر را به «()hideEditText» اضافه کنیم:
val cx = view.right - 30 val cy = view.bottom - 60 val initialRadius = view.width val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius.toFloat(), 0f) anim.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) view.visibility = View.INVISIBLE } }) isEditTextVisible = false anim.start()
در این بخش هدف این است که view را مخفی کنیم و انیمیشن دایرهای شکل را در جهت مخالف آن نشان دهیم. برای اینکار شعاع اولیه دایره را برابر با پهنای view و شعاع نهایی را برابر با صفر قرار میدهیم که دایره را کوچک کند. باید ابتدا انیمیشن را اجرا کنیم و سپس view را مخفی کنیم. برای اینکار، یک «animation listener» پیادهسازی میکنیم و در هنگام پایان انیمیشن، view را مخفی میکنیم. حالا نرمافزار را بسازید و اجرا کنید تا عملکرد انیمیشن را ببینید.
اگر کیبورد در صفحه نمایان شود، باید ابتدا آن را از صفحه خارج کنید تا انیمیشن بدون مشکل اجرا شود. برای اینکار، کد «inputManager.showSoftInput» را که در «DetailActivity» قرار دارد را کامنت کنید، البته فراموش نکنید که بعدا آن را از حالت کامنت خارج کنید. و البته نگران آیکون مثبت که در دکمه نیست نباشید، بزودی آن را هم درست میکنیم.
ایجاد انیمیشن برای Floating Action Button
حالا که انیمیشن ما به خوبی کار نمایش دادن و مخفی کردن «edittext» را انجام میدهد، میتوانید FAB را نیز به گونهای تنظیم کنیم که درست همانند تصویر زیر عمل کند:
فایل شروع پروژه دارای هر دو تصاویر وکتور مثبت و تیک است. در ادامه یاد میگیرید که چگونه بین آن دو یک انیمیشن ایجاد کنید (یا اصطلاحا آن را مورف (morph) کنید.)
در پوشه «res/drawables» یک فایل «resource» جدید از طریق منوی «New\Drawable resource file» ایجاد کنید. نام آن را «icn_morph» بگذارید و عنصر اصلی (root element) آن را «animated-vector» انتخاب کنید:
<?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/icn_add"> </animated-vector>
«animated-vector» به یک «android:drawable» نیاز دارد که حتما وجود داشته باشد. در اپلیکیشن ما، «animated vector» به صورت یک علامت به علاوه آغاز میشود و در نهایت به یک علامت تیک تغییر میکند، برای همین «drawable» را برابر با «icn_add» قرار میدهیم. حالا برای اینکه کار تغییر صورت بگیرد، مقادیر زیر را در تگ «animated-vector» اضافه کنید:
<target android:animation="@anim/path_morph" android:name="sm_vertical_line" /> <target android:animation="@anim/path_morph_lg" android:name="lg_vertical_line" /> <target android:animation="@anim/fade_out" android:name="horizontal_line" />
با کد بالا، درواقع داریم خط عمودی علامت مثبت را به یک علامت تیک تغییر میدهیم و خط افقی را محو میکنیم. تصویر زیر گویای این مساله است:
در ادامه، خط عمودی از دو خط تشکیل شده است، یک خط عمودی کوچکتر و یک خط بزرگتر:
از روی تصاویر بالا میتوانید متوجه شوید که با تغییر دو خط اول، یعنی «sm_vertical_line» و «lg_vertical_line»، و رسم آنها در زوایای متفاوت، میتوان یک علامت تیک تشکیل داد. این دقیقا همان کاری است که در قطعه کد بالا انجام دادهایم، همچنین خط افقی را نیز مخفی کردهایم.
پس از آن، باید انیمیشن را برگردانیم و علامت تیک را به یک علامت به علاوه تبدیل کنیم. یک فایل «drawable resource» دیگر ایجاد کنید. اینبار آن را «icn_morph_reverse» بنامید و محتوای آن را با کد زیر جایگزین کنید:
<?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/icn_add"> <target android:animation="@anim/path_morph_reverse" android:name="sm_vertical_line"/> <target android:animation="@anim/path_morph_lg_reverse" android:name="lg_vertical_line" /> <target android:animation="@anim/fade_in" android:name="horizontal_line" /> </animated-vector>
اینبار دو خط عمودی به حالت اولیه خود برمیگردند و خط افقی نیز نمایان میشود که در مجموع یک انیمیشن روان را ایجاد میکنند. حالا باید این انیمیشن را تکمیل کنیم. فایل «DetailActivity.kt» را باز کنید و کد زیر را در متد «()onClick» در پایان دستور «if» و قبل از شروع «else» وارد کنید:
addButton.setImageResource(R.drawable.icn_morph) val animatable = addButton.drawable as Animatable animatable.start()
در این کد ما منبع تصویر را «icn_morph» قرار میدهیم، انیمیشن را از آن استخراج، و در نهایت آن را اجرا میکنیم. در نهایت، کد زیر را در پایان دستور «else» قرار دهید:
addButton.setImageResource(R.drawable.icn_morph_reverse) val animatable = addButton.drawable as Animatable animatable.start()
این کد نیز دقیقا کار کد بالا را انجام میدهد با این تفاوت که منبع تصویر را «icn_morph_reverse» قرار میدهد تا انیمیشن به جهت مخالف اجرا شود. در کنار تغییر آیکون، با کلیک کاربر، متنی که در «todoText» قرار دارد، به «todoAdapter» وارد میشود و لیست فعالیتهای مربوط به آن مکان را بروزرسانی میکند. این مساله به دلیل سفید بودن رنگ متن، هنوز مشخص نیست، ولی در مراحل بعد با تغییر رنگ viewها، به نمایش متن کمک میکنیم. اپلیکیشن را بسازید و اجرا کنید تا تغییرات را ببینید! آیکون FAB بین تیک و به علاوه تغییر میکند.
اضافه کردن رنگهای پویا به viewها توسط API پالت رنگ
حالا وقت آن است که رنگ این View را نیز توسط API پالت رنگ تنظیم کنیم. البته نه رنگهای ثابت، بلکه همانند قبل، از رنگهای پویا استفاده میکنیم!
در «DetailActivity» با اضافه کردن کد زیر به متد «()colorize» آن را تکمیل کنید:
val palette = Palette.from(photo).generate() applyPalette(palette)
همانند قبل، یک رنگ از روی تصویر ساخته میشود (با این تفاوت که این دفعه اینکار به صورت همگام صورت میگیرد) و آن پالت رنگی را به «()applyPallete» ارسال میکنیم. متد «()applyPalette» را با کد زیر جایگزین کنید:
private fun applyPalette(palette: Palette) { window.setBackgroundDrawable(ColorDrawable(palette.getDarkMutedColor(defaultColor))) placeNameHolder.setBackgroundColor(palette.getMutedColor(defaultColor)) revealView.setBackgroundColor(palette.getLightVibrantColor(defaultColor)) }
در این کد داریم از رنگهای تیره خفه (dark muted color)، رنگهای خفه (muted color) و رنگهای پرانرژی روشن (light vibrant color) به عنوان پسزمینه پنجره، محل قرارگیری تیتر و پنجرهای که با FAB نمایان میشود استفاده میکنیم. در نهایت، برای اعمال این زنجیره از تغییرات، کد زیر را به آخر متد «()getPhoto» اضافه کنید:
colorize(photo)
دوباره وقت آن رسیده که نرمافزار را بسازید و اجرا کنید. حالا مشاهده میکنید که رنگ بندی صفحه بر اساس تصویر ما تنظیم میشود:
جابهجایی بین Activityهایی که عناصر یکسان دارند
همه ما آن جابهجاییهایی که بین تصاویر و متنها در اپلیکیشنهای گوگل صورت میگیرد را دیدهایم و برایمان سوال شده که چگونه با متریال دیزاین میتوانیم آنها را بسازیم. در ادامه به یادگیری چگونگی ساخت این انیمیشنهای روان و زیبا میپردازیم. جابهجایی بین اکتیویتیهایی با عناصر یکسان در میان اکتیویتیهایی صورت میگیرد که دارای چند view یکسان هستند. برای مثال میتوان یک تصویر کوچک را به یک تصویر بزرگتر در یک اکتیویتی دیگر تبدیل کرد که باعث میشود آن تصویر همواره در محتوا حاضر باشد.
ما میخواهیم بین لیست که «MainActivity» است و قسمت جزئیات که «DetailActivity» است، عناصر زیر را جابهجا کنیم:
- تصویر آن محل
- تیتر آن محل
- پسزمینه تیتر
فایل «row_places.xml» را باز کنید و تعریف زیر را در تگ «ImageView» که آیدی «placeImage» را دارد اضافه کنید:
android:transitionName="tImage"
سپس کد زیر را به تگ «LinearLayout» که آیدی «placeNameHolder» را دارد اضافه کنید:
android:transitionName="tNameHolder"
توجه کنید که «placeName» فاقد «transition name» است. دلیل آن این است که فرزند «placeNameHolder» است و «placeNameHolder» خودش تمامی فرزندانش را جابهجا میکند. در فایل «activity_detail.xml»، یک «transitionName» به تگ «ImageView» که آی دی «placeImage» را دارد اضافه کنید:
android:transitionName="tImage"
سپس به همان ترتیب، یک «transitionName» به تگ «LinearLayout» که آی «placeNameHolder» را دارد اضافه کنید:
android:transitionName="tNameHolder"
عناصر مشترکی که میخواهید بین اکیتیوتیها جابهجا کنید باید دارای «android:transitionName» ثابت باشند، که این دقیقا همان کاری است که ما در حال انجام آن هستیم. همچنین توجه داشته باشید که اندازه تصویر و ارتفاع «placeNameHolder» در این اکتیویتی بسیار بزرگتر است. باید تغییرات هر سه قالب را به گونهای اعمال کنید که یک تصویر دنبالهدار زیبا را ایجاد کنند. در متد «()onItemClickListener» که در «MainActivity» قرار دارد، کد زیر را بروزرسانی کنید:
override fun onItemClick(view: View, position: Int) { val intent = DetailActivity.newIntent(this@MainActivity, position) // 1 val placeImage = view.findViewById<ImageView>(R.id.placeImage) val placeNameHolder = view.findViewById<LinearLayout>(R.id.placeNameHolder) // 2 val imagePair = Pair.create(placeImage as View, "tImage") val holderPair = Pair.create(placeNameHolder as View, "tNameHolder") // 3 val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this@MainActivity, imagePair, holderPair) ActivityCompat.startActivity(this@MainActivity, intent, options.toBundle()) }
بعد از اضافه کردن این کد، باید کد زیر را به صورت دستی به قسمت پکیجها اضافه کنید، چراکه اندروید استودیو نمیتواند به طور خودکار این پکیج را شناسایی کند.
import android.support.v4.util.Pair
یک سری موارد در این بخش هستند که نیاز به توضیح دارند:
- ما یک نمونه از «placeImage» و «placeImageHolder» با موقعیت آنها در RecyclerView میگیریم. در این بخش به «Kotlin Android Extensions» تکیه نمیکنیم، چراکه به مقدار «placeImage» و «placeNameHolder» در آن view خاص نیاز داریم.
- ما یک «Pair» (به معنای جفت یا زوج) میسازیم که شامل خود view و «transitionName» که به تصویر و «text holder» نسبت دادهایم است. باز هم یادآوری میکنیم که باید پکیج را به صورت دستی اضافه کنید: android.support.v4.util.Pair.
- برای اینکه اکتیویتی را با عناصر مشترک جابهجا کنیم، باید نمونهای که از «Pair» ساختهایم را به اکتیویتی ارسال کنیم و اکتیویتی را توسط «options» آغاز کنیم.
نرم افزار را بسازید و اجرا کنید تا جابهجایی بین «main activity» و «detail activity» را ببینید:
البته انیمیشن ما در دو بخش ایراد دارد:
- دکمه شناور ما به صورت یکدفعه در «DetailActivity» ظاهر میشود.
- اگر برروی یکی از سطرها که زیر «action bar» یا «navigation bar» قرار دارد ضربه بزنید، انیمیشن آن کمی میپرد.
اول مشکل دکمه را حل میکنیم. ابتدا «Detailactivity.kt» را باز کنید و کد زیر را به متد «()windowTransition» اضافه کنید:
window.enterTransition.addListener(object : Transition.TransitionListener { override fun onTransitionEnd(transition: Transition) { addButton.animate().alpha(1.0f) window.enterTransition.removeListener(this) } override fun onTransitionResume(transition: Transition) { } override fun onTransitionPause(transition: Transition) { } override fun onTransitionCancel(transition: Transition) { } override fun onTransitionStart(transition: Transition) { } })
این شنونده که اضافه کردیم زمانی فعال میشود که عمل جابهجایی بین صفحات تمام شود، سپس با استفاده از این شنونده، دکمه FAB را در صفحه نمایان میکنیم. برای اینکه این کار موثر باشد، مقدار «alpha» که مربوط به «FAB» است را در «acitivty_detail.xml» به صفر تغییر دهید:
android:alpha="0.0"
نرمافزار را بسازید و اجرا کنید. متوجه میشوید که FAB بسیار روانتر از قبل نمایان میشود:
حالا به سراغ مشکلات «action bar» و «navigation bar» میرویم. کار را با بروزرسانی «styles.xml» آغاز میکنیم تا قالب نمایش اصلی را به «Theme.AppCompat.Light.NoActionBar» تغییر دهیم:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
از آنجایی که در «styles.xml» هیچ چیزی با عنوان «action bar» تعریف نشدهاست، باید آن را در XMLهای جداگانه اضافه کنید. فایل «activity_main.xml» را باز کنید و کد زیر را در تگ «LinearLayout»، درست در بالای تگ «RecyclerView» اضافه کنید:
<include layout="@layout/toolbar" />
این کد یک قالب «toolbar» به قالب فعلی اضافه میکند (این قالب در پروژه شروع قرار دارد). حالا باید تغییرات مشابه را در قالب «detail activity» نیز ایجاد کنیم. فایل «activity_detail.xml» را باز کنید و کد زیر را در پایان اولین «FrameLayout»، درست قبل از بستن تگ «LinearLayout» داخل آن، اضافه کنید:
<include layout="@layout/toolbar_detail"/>
سپس باید در «MainAcitivty» این نوار ابزار (toolbar) را راهاندازی کنید. کد زیر را در آخر متد «()onCreate» اضافه کنید:
setUpActionBar()
در اینجا ما مقداری که از «findViewById» به دست آمده است را به بخش جدید نسبت میدهیم و سپس «()setUpActionBar» را صدا میزنیم. در حال حاضر این فقط یک متد خالی است. با اضافه کردن کد زیر به «()setUpActionBar» متد را تکمیل میکنیم:
setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(true) supportActionBar?.elevation = 7.0f
در اینجا ما «action bar» را به گونهای تنظیم کردیم که یک نمونه از نوار ابزار شخصیسازی شده خودمان باشد. سپس وضعیت نمایان بودن تیتر را مشخص و دکمه بازگشت به خانه را غیر فعال کردیم. در نهایت یک سایه مناسب با استفاده از «elevation» به آن اضافه کردیم. نرمافزار را بسازید و اجرا کنید. متوجه میشوید که چیز زیادی تغییر نکرده است. ولی چیزی که اهمیت دارد این است که این تغییرات، پایهای را که برای جابهجایی مناسب نیاز داشتیم فراهم کردهاند.
فایل «MainActivity» را باز کنید و «onitemClickListener» فعلی را با کد زیر جایگزین کنید:
private val onItemClickListener = object : TravelListAdapter.OnItemClickListener { override fun onItemClick(view: View, position: Int) { // 1 val transitionIntent = DetailActivity.newIntent(this@MainActivity, position) val placeImage = view.findViewById<ImageView>(R.id.placeImage) val placeNameHolder = view.findViewById<LinearLayout>(R.id.placeNameHolder) // 2 val navigationBar = findViewById<View>(android.R.id.navigationBarBackground) val statusBar = findViewById<View>(android.R.id.statusBarBackground) val imagePair = Pair.create(placeImage as View, "tImage") val holderPair = Pair.create(placeNameHolder as View, "tNameHolder") // 3 val navPair = Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME) val statusPair = Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME) val toolbarPair = Pair.create(toolbar as View, "tActionBar") // 4 val pairs = mutableListOf(imagePair, holderPair, statusPair, toolbarPair) if (navigationBar != null && navPair != null) { pairs += navPair } // 5 val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this@MainActivity, *pairs.toTypedArray()) ActivityCompat.startActivity(this@MainActivity, transitionIntent, options.toBundle()) } }
تغییرات بین حالت قبلی و حالت فعلی به شرح زیر است:
- نام «intent» را تغییر دادیم تا مفهومیتر باشد.
- یک اشاره (reference) به هر دو «navigation bar» و «status bar» ایجاد کردهایم.
- سه نمونه جدید از «Pair» ساختهایم: یکی برای «navigation bar»، یکی برای «status bar»، و دیگری برای «toolbar».
- یک کد برای جلوگیری از وقوع خطای «IllegalArgumentException» نوشتهایم که در برخی از دستگاها نظیر «Galaxy Tab S2» ایجاد میشود که در آن مقدار «navPair» برابر با «null» است.
- در نهایت مقادیری که به اکتیویتی جدید ارسال میشوند را بروزرسانی کردیم تا شامل تمام اشارهها به viewهای جدید شود.
نرمافزار را بسازید و اجرا کنید. حالا متوجه روانشدن هرچه بیشتر انیمیشنهای آن میشوید:
حالا اگر برروی یک سطر در زیر «action bar» (یا همان toolbar که خودمان ساختیم) یا «navigation bar» ضربه بزنید، دیگر در انیمیشن آن پرش وجود ندارد و جابهجایی همراه با سایر عناصر مشترک صورت میگیرد که در نتیجه انیمیشن آن زیباتر میشود. به حالت جدولی نیز بروید و متوجه میشوید که جابهجایی در آن حالت نیز به خوبی انجام میشود.
بعد از این چکار کنیم؟
به خودتان افتخار کنید، شما یک اپلیکیشن با متریال دیزاین کامل ساختهاید! حالا خودتان را به چالش بگیرید و کارهای زیر را امتحان کنید:
- از «StaggeredLayoutManager» استفاده کنید تا حالت جدول را از 2 ستون به 3 ستون تغییر دهید.
- با API پالت رنگی آزمایش انجام دهید و در هر دو «MainActivity» و «DetailActivity» حالات مختلف را امتحان کنید.
- یک دکمه به لیست مکانها اضافه کنید و آن را به عنوان یک عنصر مشترک به «detail view» منتقل کنید (مثلا یک دکمه علاقهمندیها).
- انیمیشنهای جابهجایی که ساختهاید را بهتر کنید. نرمافزار «Newsstand» اندروید را بررسی کنید و ببینید چگونه از یک جدول به صفحه جزئیات جابهجا میشود. شما تمامی کدهایی که برای ساخت آن نیاز دارید را در این مقاله دارید.
- سعی کنید تمام انیمیشنهای مورف را که اینجا ساختید با استفاده از «animated-vectors» بازسازی کنید.
و در نهایت، از این دانشی که کسب کردید در اپلیکیشنهای خودتان استفاده کنید تا آنها هم به همین زیبایی شوند. فایل نهایی پروژه را نیز میتوانید از این لینک دریافت کنید. اگر مطلب بالا برای شما مفید بوده است و تمایل دارید در رابطه با مباحث آن مطالعه بیشتری داشته باشید، شاید آموزشهایی که در زیر آمدهاند برایتان مفید باشند.
- کار با فرگمنتها در برنامهنویسی اندروید
- آموزش نصب اندروید استودیو (Android Studio)
- آموزش برنامه نویسی اندروید (Android)
#