自定义Behavior实现底部View的平移动画

本文介绍了如何在Android 5.0中利用CoordinatorLayout和自定义Behavior为底部View添加平移动画,实现类似Toolbar的伸缩效果。通过实现NestedScrollingParent和NestedScrollingChild接口,结合RecyclerView的滚动,创建了一个可以随着内容滚动隐藏和显示的底部布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义Behavior实现底部View的平移动画

最新在研究一些5.0的新控件,看着新控件的酷炫效果真是手痒痒恨不得一下子全吃肚子里了,奈何是个小菜,只得慢慢来了。写这个全当是做做笔记记录一下吧,方便以后使用。
说道5.0.我觉得不能不提CoordinatorLayout,通过实现NestedScrollingParent接口,配合子View实现NestedScrollingChild接口可以实现各种酷炫效果,比如说今天我们说的Toolbar的伸缩效果,我们都知道CoordinatorLayout嵌套Toolbar之后给Toolbar设置app:layout_scrollFlags=”scroll|enterAlways”,并给可以滚动的视图设置app:layout_behavior=”@string/appbar_scrolling_view_behavior”之后,Toolbar在视图滚动的时候有一个上下平移动画,如下图
这里写图片描述
现在我们的App一般都有底部导航的,既然头部可以伸缩,那如何给我们底部布局也加上一个伸缩的效果呢(平移动画),这就需要我们自定义behavior了。如图,图上底部布局是一个线性布局,需求是实现列表RecyclerView滑动的时候底部布局自动隐藏和显示
这里写图片描述

主界面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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.mteeyu.behaviordemo.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/id_toolbar"
            android:layout_width="match_parent"
            android:minHeight="?attr/actionBarSize"
            android:layout_height="wrap_content"
            android:paddingTop="@dimen/toolbar_padding_top"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|enterAlways"
            />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main"/>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_gravity="bottom"
        android:background="?attr/colorPrimary"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        app:layout_behavior="com.mteeyu.behaviordemo.FootBehavior"
        >

        <android.support.v7.widget.AppCompatImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginLeft="10dp"
            android:background="@drawable/ic_mood"/>

        <android.support.v7.widget.AppCompatEditText
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="#FFF"/>

    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

为了协调Toolbar的动画效果content_main中添加适配代码,这是content_main的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/id_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never"/>
</LinearLayout>

可以看到,为了实现底部布局的滑动效果,我加上了

app:layout_behavior="com.mteeyu.behaviordemo.FootBehavior"

这就是我们自定义的Behavior了。贴出Behavior的代码,这份代码差不多是照搬了BottomNavigationBar源码中Behavior的实现,因为自己写的是在是没他写的好,而且兼容性也很好,动画效果也是杠杠的。用到了泛型所以理论上支持所有控件。

public class FootBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {

    private int mTotalDyUnconsumed = 0;
    private int mTotalDyConsumed = 0;
    private int mTotalDy = 0;

    private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator ();
    private int mFootViewHeight;
    private int mDefaultOffset;

    private ViewPropertyAnimatorCompat mTranslationAnimator;
    private boolean hidden = false;

    @ScrollDirection
    private int mScrollDirection = ScrollDirection.SCROLL_NONE;
    @ScrollDirection
    private int mPreScrollDirection = ScrollDirection.SCROLL_NONE;
    @ScrollDirection
    private int mConsumedScrollDirection = ScrollDirection.SCROLL_NONE;

    public FootBehavior () {
        super ();
    }

    public FootBehavior (Context context, AttributeSet attrs) {
        super (context, attrs);
    }

    @Retention (RetentionPolicy.SOURCE)
    @IntDef ({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN})
    public @interface ScrollDirection {
        int SCROLL_DIRECTION_UP = 1;
        int SCROLL_DIRECTION_DOWN = -1;
        int SCROLL_NONE = 0;
    }


    @ScrollDirection
    public int getScrollDirection () {
        return mScrollDirection;
    }


    @ScrollDirection
    public int getConsumedScrollDirection () {
        return mConsumedScrollDirection;
    }


    @ScrollDirection
    public int getPreScrollDirection () {
        return mPreScrollDirection;
    }


    @Override
    public boolean onLayoutChild (CoordinatorLayout parent, final V child, int layoutDirection) {
        parent.onLayoutChild (child, layoutDirection);

        child.post (new Runnable () {
            @Override
            public void run () {
                mFootViewHeight = child.getHeight ();
            }
        });

        mDefaultOffset = 0;

        return super.onLayoutChild (parent, child, layoutDirection);
    }

    //判断是否接受后续滑动事件
    @Override
    public boolean onStartNestedScroll (CoordinatorLayout coordinatorLayout, V child,
                                        View directTargetChild, View target, int nestedScrollAxes) {

        return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
    }


    //进行滑动事件处理
    @Override
    public void onNestedScroll (CoordinatorLayout coordinatorLayout, V child, View target,
                                int dxConsumed, int dyConsumed, int dxUnconsumed,
                                int dyUnconsumed) {
        super.onNestedScroll (coordinatorLayout, child, target, dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed);
        if (dyUnconsumed > 0 && mTotalDyUnconsumed < 0) {
            mTotalDyUnconsumed = 0;
            mScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
            onNestedVerticalScrollUnconsumed (coordinatorLayout, child, mScrollDirection,
                    dyConsumed, mTotalDyUnconsumed);
        } else if (dyUnconsumed < 0 && mTotalDyUnconsumed > 0) {
            mTotalDyUnconsumed = 0;
            mScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
            onNestedVerticalScrollUnconsumed (coordinatorLayout, child, mScrollDirection,
                    dyConsumed, mTotalDyUnconsumed);
        }
        mTotalDyUnconsumed += dyUnconsumed;

        if (dyConsumed > 0 && mTotalDyConsumed < 0) {
            mTotalDyConsumed = 0;
            mConsumedScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
            onNestedVerticalScrollConsumed (coordinatorLayout, child, mConsumedScrollDirection,
                    dyConsumed, mTotalDyConsumed);
        } else if (dyConsumed < 0 && mTotalDyConsumed > 0) {
            mTotalDyConsumed = 0;
            mConsumedScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
            onNestedVerticalScrollConsumed (coordinatorLayout, child, mConsumedScrollDirection,
                    dyConsumed, mTotalDyConsumed);
        }
        mTotalDyConsumed += dyConsumed;
    }

    @Override
    public void onNestedPreScroll (CoordinatorLayout coordinatorLayout, V child, View target,
                                   int dx, int dy, int[] consumed) {
        super.onNestedPreScroll (coordinatorLayout, child, target, dx, dy, consumed);
        if (dy > 0 && mTotalDy < 0) {
            mTotalDy = 0;
            mPreScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
            onNestedVerticalPreScroll (coordinatorLayout, child, target, dx, dy, consumed,
                    mPreScrollDirection);
        } else if (dy < 0 && mTotalDy > 0) {
            mTotalDy = 0;
            mPreScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
            onNestedVerticalPreScroll (coordinatorLayout, child, target, dx, dy, consumed,
                    mPreScrollDirection);
        }
        mTotalDy += dy;
    }


    //当进行快速滑动
    @Override
    public boolean onNestedFling (CoordinatorLayout coordinatorLayout, V child, View target,
                                  float velocityX, float velocityY, boolean consumed) {
        super.onNestedFling (coordinatorLayout, child, target, velocityX, velocityY, consumed);
        return onNestedDirectionFling (coordinatorLayout, child, target, velocityX, velocityY,
                consumed,
                velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN);
    }


    public void onNestedVerticalScrollUnconsumed (CoordinatorLayout coordinatorLayout, V child,
                                                  @ScrollDirection int scrollDirection,
                                                  int currentOverScroll, int totalScroll) {

    }


    public void onNestedVerticalScrollConsumed (CoordinatorLayout coordinatorLayout, V child,
                                                @ScrollDirection int scrollDirection,
                                                int currentOverScroll, int totalConsumedScroll) {
        handleDirection (child, scrollDirection);
    }

    private void handleDirection (V child, int scrollDirection) {
        if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) {
            hidden = false;
            animateOffset (child, mDefaultOffset);
        } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) {
            hidden = true;
            animateOffset (child, mFootViewHeight + mDefaultOffset);
        }
    }

    private void animateOffset (V child, int offset) {
        ensureOrCancelAnimator (child);
        mTranslationAnimator.translationY (offset).start ();
    }

    private void ensureOrCancelAnimator (V child) {
        if (mTranslationAnimator == null) {
            mTranslationAnimator = ViewCompat.animate (child);
            mTranslationAnimator.setDuration (400);
            mTranslationAnimator.setInterpolator (INTERPOLATOR);
        } else {
            mTranslationAnimator.cancel ();
        }
    }


    public void onNestedVerticalPreScroll (CoordinatorLayout coordinatorLayout, V child,
                                           View target, int dx, int dy, int[] consumed,
                                           @ScrollDirection int scrollDirection) {

    }


    protected boolean onNestedDirectionFling (CoordinatorLayout coordinatorLayout, V child,
                                              View target, float velocityX, float velocityY,
                                              boolean consumed,
                                              @ScrollDirection int scrollDirection) {
        if (consumed) {
            handleDirection (child, scrollDirection);
        }
        return consumed;
    }


}

需要注意的是,FootBehavior继承的是CoordinatorLayout.Behavior这个类,所以需要使用的时候,布局文件最外层就必须是CoordinatorLayout。值得一提的是,CoordinatorLayout中填充RecyclerView或者NestedScrollingView可以触发动画,但是像我们很熟悉的ListView,GridView等是触发不了的,解决办法是有,比如说嵌套NestedScrollingView,但是这样又有ScrollingView嵌套问题,目前完美解决办法我还是没有找到,毕竟是个菜鸟,如果有朋友找到了还请告诉我一下啊,谢谢。

最后附上最终实现效果
这里写图片描述

项目地址
https://github.com/MteeYu/BehaviorDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值