تنها با ده خط کد، BottomNavigationView خود را Scroll کنید

پنج شنبه ۰۶ اردیبهشت ۹۷ توسط فاطمه بهاروند

مانند بسیاری از موارد طراحی کلی که گوگل معرفی کرده است نوار راهنمای پایین صفحه (Navigation Bottom Bars) در اندروید استودیو را نیز با کتابخانه ی به خصوصی طراحی نموده اما در ارائه ی نوار لغزنده برای آن به عنوان یک مولفه ی کلیدی قصور کرده است. ویژگی های طراحی اصلی سبب می شود نوار پایین(Bottom bar) با اسکرول و پیمایش مخفی شود و این موضوع ناامید کننده است.

در حالی که کتابخانه هایی که توسط اشخاص ثالث توسعه یافته اند شرایط خوبی را برای سفارشی سازی نوار پایین(Bottom bar) فراهم آورده است من تصمیم دارم راهی برای اضافه کردن این مورد به ظاهر ساده در ویو پایین صفحه ی نرمال (BottomNavigationView) بیابم. من برای پیاده سازی این ویژگی از برنامه ی کابردی(app) گوگل پلاس (Google+) الهام گرفته ام. راه حل آن این است که از همان ابتدا نوار پایین(Bottom bar) را به عنوان AppBarLayout تلقی کنید و در پایین صفحه مشاهده می کنید که هر دو با یک سرعت و در یک جهت حرکت می کنند. من در پی آن بودم این رفتار(behavior) را پیاده سازی کنم.

مخفی شدن نوار راهنمای پایین صفحه در برنامه گوگل پلاس

 

یک رفتار سفارشی آسانی حیرت انگیز

من می دانستم که پیاده سازی شامل نوشتن رفتاری سفارشی در CoordinatorLayout است که این موضوع در همان ابتدا کار من را آسان کرد. هر زمانی که من در پی یافتن نمونه کدهایی برای سفارشی رفتارها بودم آن ها به شدت پیچیده به نظر می رسیدند. اما اکنون در این نمونه مشخص شد راه سختی در پیش نداریم:

class BottomNavigationBehavior(context: Context, attrs: AttributeSet) :
    CoordinatorLayout.Behavior(context, attrs) {

    override fun onStartNestedScroll(
        coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int
    ): Boolean {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onNestedPreScroll(
        coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int
    ) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
        child.translationY = max(0f, min(child.height.toFloat(), child.translationY + dy))
    }
}

نتیجه ی آن؟؟ بفرمایید ببینید:

من می خواهم آن را با توجه آنچه که مد نظرمان است فراخوانی کنیم

مهمترین قسمت کد متد onStartNestedScroll است که در آن CoordinatorLayout را قرار داده و رویدادهای اسکرول عمودی را نیز تنظیم می کنیم. پس از آن از متد onNestedPreScroll استفاده می کنیم. این متد همان متدی است که رفتار AppBarLayout را برای مخفی کردن خود از بین می برد. عملکرد PreScroll بدین صورت است که ما رویداد اسکرول را پیش از دریافت آن توسط RecyclerView (که کانتینر (container) حقیقی عمل اسکرول در این مورد است) به دست می آوریم. تنها بخش حائز اهمیت برای ما (dy) است که در اصل تغییرات اسکرول است. تمام کاری که ما باید انجام دهیم تنظیم ویژگی(property) translationY در ویو خودمان است. کد زیر یک تابع اصلی است.

((max(0f, min(child.height.toFloat(), child.translationY + dy

مقدار translationY حکم یک چارچوب محدود کننده را دارد و محدوده ی آن از صفر تا ارتفاع ویو (view) است، زیرا ما نمی خواهیم نوار پایین صفحه (bottom bar) را پایین تر از ارتفاع صفحه و بالاتر از موقعیت شروع قرار دهیم.

اعمال رفتار(behavior) به ویو(view) شما

اگر شما هرگز از رفتار سفارشی CoordinatorLayout استفاده نکرده اید بدین روش برای الصاق آن به ویو خود عمل کنید، تنها در کلاس خود layout-bahavior را قرار دهید.

android.support.design.widget.BottomNavigationView
    android:id="@+id/navigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    app:layout_behavior="com.mypackage.BottomNavigationBehavior"
    app:menu="@menu/my_menu"/>

اما در مورد snackbars ؟

آنچه اکنون داریم عالی است اما ناقص است. Snackbars در CoordinatorLayout وجود ندارد. اگر رخدادی پیش آید در پشت ویو راهنمای پایین(BottomNavogationView) ظاهر می شود که این امر اشتباه است. روشن است که علت این امر چیست:

  Snackbar باید در بالای نوار پایین صفحه ظاهر شود

هنگامی که یک snackbar ظاهر می شود باید در بالای نوار پایین (bottom bar) قرار گیرد و همگام با آن حرکت کند. خب اینطور که معلوم است انجام این کار خیلی پیچیده نیست (توجه داشته باشید یک الگو در اینجا وجود دارد)

class BottomNavigationBehavior(context: Context, attrs: AttributeSet) :
    CoordinatorLayout.Behavior(context, attrs) {
        
    // Rest of the code is the same    

    override fun layoutDependsOn(parent: CoordinatorLayout?, child: V, dependency: View?): Boolean {
        if (dependency is Snackbar.SnackbarLayout) {
            updateSnackbar(child, dependency)
        }
        return super.layoutDependsOn(parent, child, dependency)
    }

    private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
        if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) {
            val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams

            params.anchorId = child.id
            params.anchorGravity = Gravity.TOP
            params.gravity = Gravity.TOP
            snackbarLayout.layoutParams = params
        }
    }
}

اکنون ما از پشتیبانی Snsckbar بهره مندیم:

راهکار این است که ما آن را داخل متد LayoutDependsOn قرار دهیم تا زمانی که رخداد لایه ی جدید روی می دهد به عنوان نتیجه Snackbar اضافه شود، از آنجایی که می دانیم Snackbar به CoordinatorLayout اضافه شده است می توانیم از آن استفاده کنیم و پارامترهای لایه آن را تغییر دهیم. با استفاده از پارامترهای آماده مانند anchor، anchorGravity و gravity ما Sncackbar را به لبه ی بالایی نوار پایین صفحه متصل می کنیم(به یاد داشته باشید که child در این کد ویو راهنمای پایین (BottomNavigationView) است که رفتار(behavior) به آن وابسته است) و باید از gravitytop به منظور ظاهر شدن snackbar در بالای راهنمای پایین صفحه استفاده شود.

این کد یک کد فوق العاده برای تعامل پیمایشی(scroll interaction) نیست. از آنجایی که این کد بسیار ساده است من قصد ندارم آن را به عنوان یک کتابخانه در نظر بگیریم اما آن را در این در گیت هاب بارگذاری کردم تا در صورت تمایل از آن استفاده کنید.


کلیدواژه: راهنمای پایین صفحه باتم نویگیشن نویگیشن باتم بار Bottom Navigation View Bottom Navigation Navigation Bottom Bars Material Design متریال دیزاین

منابع: android.jlelse.eu

ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.