一、Behavior是什么?为什么要用Behavior?
CoordinatorLayout是android support design推出的新布局,主要用于作为视图根布局以及协调子控件的行为,而Behavior就是用于直接子控件来协调自身CoordinatorLayout以及和其他子控件的关系,使用Behavior的控件必须是直接从属于CoordinatorLayout。
在传统的事件分发流程中,在子控件处理事件过程中,父控件是可以进行拦截的,但一旦父控件进行拦截,那么这次事件只能由父控件处理,而不能再由子控件处理了。
在android5.0之后新的嵌套滑动机制中,引入了:NestScrollChild和NestedScrollingParent两个接口,用于协调子父控件滑动状态,而CoordinatorLayout实现了NestedScrollingParent接口,在实现了NestScrollChild这个接口的子控件在滑动时会调用NestedScrollingParent接口的相关方法,将事件发给父控件,由父控件决定是否消费当前事件,在CoordinatorLayout实现的NestedScrollingParent相关方法中会调用Behavior内部的方法。
我们实现Behavior的方法,就可以嵌入整个CoordinatorLayout所构造的嵌套滑动机制中,可以获取到两个方面的内容:
1、某个view监听另一个view的状态变化,例如大小、位置、显示状态等需要重写layoutDependsOn和onDependentViewChanged方法
2、某个view监听CoordinatorLayout内NestedScrollingChild的接口实现类的滑动状态需要重写onStartNestedScroll和onNestedPreScroll方法。注意:是监听实现了NestedScrollingChild的接口实现类的滑动状态,这就可以解释为什么不能用ScrollView而用NestScrollView来滑动了。本文的实现效果就是通过重写onStartNestedScroll和onNestedPreScroll方法。
二、如何使用Behavior实现悬浮控件动画?
前面已经说了,如果要监听另一个view的状态变化,需要重写layoutDependsOn和onDependentViewChanged方法;如果要监听实现了NestedScrollingChild的接口实现类的滑动状态(如NestedScrollView、RecyclerView等),需要重写onStartNestedScroll和onNestedPreScroll方法。因此,要实现本文效果,采用后者实现。
效果如下:
布局xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/behavior_demo_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/behavior_demo_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="72dp"
android:layout_marginRight="16dp"
android:src="@android:drawable/ic_dialog_email"
app:layout_behavior="com.example.ltcoordinatorlayout.MyFabBehavior"/>
<ImageView
android:layout_gravity="bottom|left"
android:layout_marginBottom="72dp"
android:layout_marginLeft="16dp"
android:src="@mipmap/ic_launcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="com.example.ltcoordinatorlayout.MyFabBehavior"/>
</android.support.design.widget.CoordinatorLayout>
给需要动画的控件添加behavior
app:layout_behavior=”com.example.ltcoordinatorlayout.MyFabBehavior”
MyFabBehavior.java:
public class MyFabBehavior extends CoordinatorLayout.Behavior<View>
{
private float viewY;//控件距离coordinatorLayout底部距离
private boolean isHideAnimate;//隐藏动画是否在进行
private boolean isShowAnimate;//显示动画是否在进行
private boolean isChildTop = true;//判断控件是否回到原始位置
private ViewPropertyAnimator animator;
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
public MyFabBehavior(Context context, AttributeSet attrs)
{
super(context, attrs);
}
/**
* 滑动前指定Behavior关注的滑动方向
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes)
{
if (child.getVisibility() == View.VISIBLE && viewY == 0)
{
//获取控件距离父布局(coordinatorLayout)底部距离
viewY = coordinatorLayout.getHeight() - child.getY();
}
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//判断是否竖直滚动
}
/**
* 用来监听滑动状态,对象消费滚动距离前回调
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed)
{
//速度大于40,则执行动画(速度判断)
if (Math.abs(dy) > 40)
{
//dy大于0是向上滚动 dy小于0是向下滚动
if (dy >= 0 && !isHideAnimate && child.getVisibility() == View.VISIBLE)//如果向上滚动,隐藏动画已结束并且控件可见,则启动隐藏动画
{
hide(child);
} else if (dy < 0 && !isShowAnimate && !isChildTop)//如果向下滚动,显示动画已结束并且控件未回到原始位置,则启动显示动画
{
show(child);
} else if (dy >= 0 && isShowAnimate)//如果向上滚动,并且显示动画未结束,则先取消显示动画再启动隐藏动画
{
animator.cancel();
} else if (dy < 0 && isHideAnimate)//如果向下滚动,并且显示隐藏未结束,则先取消隐藏动画再启动显示动画
{
animator.cancel();
}
}
}
/**
* 隐藏时的动画
*/
private void hide(final View view)
{
animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(300);
animator.setListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animator)
{
isHideAnimate = true;
}
//每次cancel也会走onAnimationEnd方法
@Override
public void onAnimationEnd(Animator animator)
{
//如果隐藏动画正常结束,而非取消后结束,则将布局隐藏
if (isHideAnimate)
{
// view.setVisibility(View.GONE);
isHideAnimate = false;
}
isChildTop = false;
}
@Override
public void onAnimationCancel(Animator animator)
{
isHideAnimate = false;
}
@Override
public void onAnimationRepeat(Animator animator)
{
}
});
animator.start();
}
/**
* 显示时的动画
*/
private void show(final View view)
{
animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(300);
animator.setListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animator)
{
view.setVisibility(View.VISIBLE);
isShowAnimate = true;
}
//每次cancel也会走onAnimationEnd方法
@Override
public void onAnimationEnd(Animator animator)
{
//如果显示动画正常结束,而非取消后结束,则isChildTop = true,表示控件回到原来位置
if (isShowAnimate)
{
isShowAnimate = false;
isChildTop = true;
}
}
@Override
public void onAnimationCancel(Animator animator)
{
isShowAnimate = false;
}
@Override
public void onAnimationRepeat(Animator animator)
{
}
});
animator.start();
}
}
需要注意的是滚动控件必须实现NestedScrollingChild接口,而没有实现该接口且不调用 dispatchNestedScroll相关接口的滚动控件如ScrollView、WebView、ListView是没有作用的。
源码地址:github