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也不會被呼叫了
好了,就說這麼多吧,如果你想知道滑動事件具體的分發過程就期待我的下篇部落格吧