自定义CoordinatorLayout.Behavior 实现悬浮控件动画

来源:互联网 发布:c语言中的error 编辑:程序博客网 时间:2024/06/07 09:49

一、Behavior是什么?为什么要用Behavior?

CoordinatorLayout是android support design推出的新布局,主要用于作为视图根布局以及协调子控件的行为,而Behavior就是用于直接子控件来协调自身CoordinatorLayout以及和其他子控件的关系,使用Behavior的控件必须是直接从属于CoordinatorLayout

在传统的事件分发流程中,在子控件处理事件过程中,父控件是可以进行拦截的,但一旦父控件进行拦截,那么这次事件只能由父控件处理,而不能再由子控件处理了。

在android5.0之后新的嵌套滑动机制中,引入了:NestScrollChildNestedScrollingParent两个接口,用于协调子父控件滑动状态,而CoordinatorLayout实现了NestedScrollingParent接口,在实现了NestScrollChild这个接口的子控件在滑动时会调用NestedScrollingParent接口的相关方法,将事件发给父控件,由父控件决定是否消费当前事件,在CoordinatorLayout实现的NestedScrollingParent相关方法中会调用Behavior内部的方法。

我们实现Behavior的方法,就可以嵌入整个CoordinatorLayout所构造的嵌套滑动机制中,可以获取到两个方面的内容:

1、某个view监听另一个view的状态变化,例如大小、位置、显示状态等需要重写layoutDependsOnonDependentViewChanged方法

2、某个view监听CoordinatorLayout内NestedScrollingChild的接口实现类的滑动状态需要重写onStartNestedScrollonNestedPreScroll方法。注意:是监听实现了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)    {        Log.e("LT", "===============================>>>>>>>>dy: " + dy);        //速度判断        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)    {        Log.e("LT", "===============================>>>>>>>>溜了溜了 ");        animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(1000);        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)    {        Log.e("LT", "===============================>>>>>>>>回来啦回来啦 ");        animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(1000);        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是没有作用的。

参考文章:深入理解CoordinatorLayout.Behavior

阅读全文
0 0
原创粉丝点击