1. 程式人生 > >Kotlin實現仿知乎底部導航欄顯示隱藏效果Behavior

Kotlin實現仿知乎底部導航欄顯示隱藏效果Behavior

        最開始遇見這個問題我的第一想法是給recyclerview新增滑動監聽,然後再給底部導航新增顯示隱藏動畫,可是這麼做很不優雅,一旦recyclerview不止一個就需要給每個都新增一遍監聽(雖然同樣的程式碼cv就行了),這絕不是一個優秀程式設計師的追求。所以就出現了第二種方法,利用系統提供的Behavior。

       許多人應該不陌生,利用CoordinatorLayout和自定義的Behavior我們可以實現很多炫酷的效果,AndroidStudio提供的模板Activity中的ScrollingActivity就是用的這種方式。

        言歸正傳,下面說一說如何實現仿知乎的顯示隱藏效果如何實現,在這裡我就不做具體講解了,網上講解的部落格已經很多了,相信也比我三言兩句講解的清楚,下面我推薦幾篇我在實現的時候參考過的博文

接下來是我參考過的自定義Behavior的博文

廢話不多說,先貼出程式碼

package com.yking.kotlinfuture.widgets

import android.animation.Animator
import android.content.Context
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.CoordinatorLayout.Behavior
import android.support.v4.view.ViewCompat
import android.support.v4.view.animation.FastOutSlowInInterpolator
import android.util.AttributeSet
import android.view.View


/**
 * Created by  on 2018/9/18.YaoKai
 */
class FooterBehavior @JvmOverloads constructor(context: Context, attrs: AttributeSet) : Behavior<View>(context, attrs) {

    private val INTER_POLATOR by lazy { FastOutSlowInInterpolator() }

    private var viewY: Float = 0.toFloat()//控制元件距離coordinatorLayout底部距離
    private var MOVE_LENGTH: Float = 0.toFloat()//移動多大距離開始顯示或隱藏
    private var isAnimate: Boolean = false//動畫是否在進行


    //在巢狀滑動開始前回調
    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: View, directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean {

        if (child.visibility == View.VISIBLE && viewY == 0f) {
            //獲取控制元件距離父佈局(coordinatorLayout)底部距離
            viewY = coordinatorLayout.height - child.y
            MOVE_LENGTH = viewY / 5
        }
        return nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0//判斷是否豎直滾動
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: View, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        //dy大於0是手指向上滾動 小於0是向下滾動

        if (dy >= MOVE_LENGTH && !isAnimate && child.visibility == View.VISIBLE) {
            hide(child)
        } else if (dy < -MOVE_LENGTH && !isAnimate && child.visibility == View.INVISIBLE) {
            show(child)
        }
    }


    //隱藏時的動畫
    private fun hide(view: View) {
        val animator = view.animate().translationY(viewY).setInterpolator(INTER_POLATOR).setDuration(200)

        animator.setListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animator: Animator) {
                isAnimate = true
            }

            override fun onAnimationEnd(animator: Animator) {
                view.visibility = View.INVISIBLE
                isAnimate = false
            }

            override fun onAnimationCancel(animator: Animator) {
                show(view)
            }

            override fun onAnimationRepeat(animator: Animator) {}
        })
        animator.start()
    }

    //顯示時的動畫
    private fun show(view: View) {
        val animator = view.animate().translationY(0f).setInterpolator(INTER_POLATOR).setDuration(200)
        animator.setListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animator: Animator) {
                view.visibility = View.VISIBLE
                isAnimate = true
            }

            override fun onAnimationEnd(animator: Animator) {
                isAnimate = false
            }

            override fun onAnimationCancel(animator: Animator) {
                hide(view)
            }

            override fun onAnimationRepeat(animator: Animator) {}
        })
        animator.start()
    }
}

其實實現很簡單,本文主要用到了

onStartNestedScroll
onNestedPreScroll

兩個方法

        第一個方法在巢狀滑動開始前呼叫,它返回的Boolean值決定了是否繼續呼叫之後的方法,我們在這個方法中獲取了控制元件距離父佈局(CoordinatorLayout)底部距離

        第二個方法裡我們做了一個判斷:dy大於0是手指向上滑動,向上滑動時開啟隱藏底部控制元件動畫,並把控制元件置為INVISIBLE;小於0就是手指向下滑動,向下滑動時就開啟顯示底部控制元件動畫,並還原控制元件可見性

怎麼樣,是不是很簡單,但這裡我要提示大家有一個坑:千萬不要把控制元件直接GONE掉,因為

@Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }
      CoordinatorLayout中的onStartNestedScroll方法會進行view.getVisibility() == View.GONE判斷,如果你把控制元件直接GONE掉了會執行不到
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target, axes, type);

這一步,然後我們自定義Behavior的onNestedPreScroll也不會被呼叫了

好了,就說這麼多吧,如果你想知道滑動事件具體的分發過程就期待我的下篇部落格吧