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

一、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)
    {
        //速度大于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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值