CoordinatorLayout的使用

使用CoordinatorLayout打造各种炫酷的效果

自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示

NestedScrolling 机制深入解析

一步步带你读懂 CoordinatorLayout 源码

自定义 Behavior -仿新浪微博发现页的实现

前段时间写了一篇博客使用CoordinatorLayout打造各种炫酷的效果,主要介绍了APPBarLayout和CollapsingToolbarLayout的基本用法,AppBarLayout主要用来实现在滚动的时候ToolBar的 隐藏于展示,CollapsingToolbarLayout主要用来实现parallax和pin等效果。如果你对CoordinatorLayout还不了解的话,请先阅读这篇文章。

写作思路

  • CoordinatorLayout Behavior 简介
  • 怎样自定义 Behavior
  • 仿知乎效果 自定义 Behavior 的实现
  • 自定义 Behavior 两种方法的 对比
  • FloatActionButton 自定义 Behavior 效果的实现
  • 题外话

今天就来讲解怎样通过自定义behavior来实现各种炫酷的效果 ,效果图如下

下面让我们一起来看一下怎样实现仿知乎的效果

老规矩,先看代码

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/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"
    >

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


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways">


            <ImageView
                android:id="@+id/search"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:src="@drawable/search"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:layout_toRightOf="@id/search"
                android:text="搜索话题、问题或人"
                android:textSize="16sp"/>

        </RelativeLayout>


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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    </android.support.v7.widget.RecyclerView>


    <!--使用RadioGroup来实现tab的切换-->
    <RadioGroup
        android:id="@+id/rg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@color/bg_tab"
        android:orientation="horizontal"
        app:layout_behavior="@string/behavior_footer"
        >

        <RadioButton
            android:id="@+id/rb_home"
            style="@style/bottom_tab"
            android:drawableTop="@drawable/sel_home"
            android:text="Home"/>

        <RadioButton
            android:id="@+id/rb_course"
            style="@style/bottom_tab"
            android:drawableTop="@drawable/sel_course"
            android:text="course"/>

        <RadioButton
            android:id="@+id/rb_direct_seeding"
            style="@style/bottom_tab"
            android:drawableTop="@drawable/sel_direct_seeding"
            android:text="direct"/>

        <RadioButton
            android:id="@+id/rb_me"
            style="@style/bottom_tab"
            android:drawableTop="@drawable/sel_me"
            android:text="me"/>

    </RadioGroup>


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

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
<style name="bottom_tab">
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">60dp</item>
    <item name="android:layout_weight">1</item>
    <item name="android:text">0dp</item>
    <item name="android:gravity">center</item>
    <item name="android:textColor">@drawable/sel_bottom_tab_text</item>
    <item name="android:padding">10dp</item>
    <item name="android:button">@null</item>
</style>
<style name="bottom_tab">
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">60dp</item>
    <item name="android:layout_weight">1</item>
    <item name="android:text">0dp</item>
    <item name="android:gravity">center</item>
    <item name="android:textColor">@drawable/sel_bottom_tab_text</item>
    <item name="android:padding">10dp</item>
    <item name="android:button">@null</item>
</style>


   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

思路分析

根据动态如可以看到,主要有两个效果

  • 上面的AppBarLayout 向上滑动的时候会隐藏,向下滑动的时候会展示,说白了就是给APPLayout的子View Relativelayout 设置 app:layout_scrollFlags=”scroll|enterAlways”,核心代码如下
<android.support.design.widget.AppBarLayout
    android:id="@+id/index_app_bar"
    theme="@style/AppTheme.AppBarOverlay"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways">


      ----

    </RelativeLayout>
 </android.support.design.widget.AppBarLayout>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 下面的 RadioGroup ,我们可以看到,向上 滑动的时候会隐藏,向下滑动的时候会显示,其实我们只是给其设置了 behavior 而已 app:layout_behavior=”@string/behavior_footer”,那这个behavior_footer是什么东西,别急 ,下面就是介绍了
<string name="behavior_footer">com.xujun.contralayout.behavior.FooterBehavior</string>
   
   
  • 1

Behavior简介

Behavior是CoordinatorLayout里面的一个内部类,通过它我们可以与 CoordinatorLayout的一个或者多个子View进行交互,包括 drag,swipes, flings等手势动作。

今天 我们主要着重介绍里面的几个方法

方法解释
boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)确定child View 是否有一个特定的兄弟View作为布局的依赖(即dependency)
boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)当child View 的 dependent view 发生变化的时候,这个方法会调用
boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)当CoordinatorLayout 的直接或者非直接子View开始准备嵌套滑动的时候会调用
void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)当嵌套滑动的 时候,target尝试滑动或者正在滑动的 时候会调用

关于更多方法,请参考官网文档说明

怎样自定义Behavior

前面已经说到,今天主要介绍四个方法,这里我们把它分为两组。

第一组

// 决定child 依赖于把一个 dependency
boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

// 当 dependency View 改变的时候 child 要做出怎样的响应
boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

第二组

// 当CoordinatorLayout的直接或者非直接子View开始嵌套滑动的时候,会调用这个方法
boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

// 当嵌套滑动的时候,target 尝试滑动或者正在滑动会调用这个方法
onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

首先我们先看第一组是怎样实现的?

/**
 * 知乎效果底部behavior 依赖于 AppBarLayout
 *
 * @author xujun  on 2016/11/30.
 * @email gdutxiaoxu@163.com
 */

public class FooterBehaviorDependAppBar extends CoordinatorLayout.Behavior<View> {

    public static final String TAG = "xujun";

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

    //当 dependency instanceof AppBarLayout 返回TRUE,将会调用onDependentViewChanged()方法
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return   dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        //根据dependency top值的变化改变 child 的 translationY
        float translationY = Math.abs(dependency.getTop());
        child.setTranslationY(translationY);
        Log.i(TAG, "onDependentViewChanged: " + translationY);
        return true;

    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

思路分析

这里我们要分清两个概念,child 和 dependency ,child 是我们要改变的坐标的view,而 dependency 是child 的 附属 ,即child 会随着 dependency 坐标的改变而改变。

比如上面的例子:当我们把 app:layout_behavior=”com.xujun.contralayout.behavior.FooterBehaviorDependAppBar” 设置给 RadioGroup 的时候,这时候 child 就是 RadioGroup ,而 dependency 就是 APPBarLayout ,因为我们在 layoutDependsOn 方法里面 ,返回 dependency instanceof AppBarLayout ,即当 dependency 是 AppBarLayout 或者 AppBarLayout的子类的时候返回TRUE。

//当 dependency instanceof AppBarLayout 返回TRUE,将会调用onDependentViewChanged()方法
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return   dependency instanceof AppBarLayout;
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

而之所以 RadioGroup 在向上滑动的时候会隐藏,向下滑动的时候会显示,是因为我们在 onDependentViewChanged 方法的时候 动态地根据 dependency 的 top 值改变 RadioGroup 的 translationY 值,核心 代码如下

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        //根据dependency top值的变化改变 child 的 translationY
        float translationY = Math.abs(dependency.getTop());
        child.setTranslationY(translationY);
        Log.i(TAG, "onDependentViewChanged: " + translationY);
        return true;

    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

到此第一种思路分析为止

第二种思路

主要是根据 onStartNestedScroll() 和 onNestedPreScroll()方法 来实现的,

  • 当我们开始滑动的时候,我们判断是否是垂直滑动,如果是返回TRUE,否则返回 FALSE,返回TRUE,会接着调用onNestedPreScroll()等一系列方法。
    //1.判断滑动的方向 我们需要垂直滑动
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 在 onNestedPreScroll() 方法里面,我们根据我们的逻辑来决定是否显示 target , 在这里我们是向上上滑动的时候,如果我们滑动的距离超过 target 的高度 并且 当前是可见的状态下,我们执行动画,隐藏 target,当我们向下滑动的时候,并且 View 是不可见的情况下,我们执行动画 ,显示target
  //2.根据滑动的距离显示和隐藏footer view
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child,
                                  View target, int dx, int dy, int[] consumed) {
        if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {
            child.animate().cancel();
            sinceDirectionChange = 0;
        }
        sinceDirectionChange += dy;
        int visibility = child.getVisibility();
        if (sinceDirectionChange > child.getHeight() && visibility == View.VISIBLE) {
            hide(child);
        } else {
            if (sinceDirectionChange < 0 && (visibility == View.GONE || visibility == View
                    .INVISIBLE)) {
                show(child);
            }
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

全部代码如下

/**
 * 知乎效果底部 behavior
 *
 * @author xujun  on 2016/11/30.
 * @email gdutxiaoxu@163.com
 */

public class FooterBehavior extends CoordinatorLayout.Behavior<View> {

    private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

    private int sinceDirectionChange;

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

    //1.判断滑动的方向 我们需要垂直滑动
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    //2.根据滑动的距离显示和隐藏footer view
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child,
                                  View target, int dx, int dy, int[] consumed) {
        if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {
            child.animate().cancel();
            sinceDirectionChange = 0;
        }
        sinceDirectionChange += dy;
        int visibility = child.getVisibility();
        if (sinceDirectionChange > child.getHeight() && visibility == View.VISIBLE) {
            hide(child);
        } else {
            if (sinceDirectionChange < 0 && (visibility == View.GONE || visibility == View
                    .INVISIBLE)) {
                show(child);
            }
        }
    }

    private void hide(final View view) {
        ViewPropertyAnimator animator = view.animate().translationY(view.getHeight()).
                setInterpolator(INTERPOLATOR).setDuration(200);
        animator.setListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                view.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                show(view);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        animator.start();
    }

    private void show(final View view) {
        ViewPropertyAnimator animator = view.animate().translationY(0).
                setInterpolator(INTERPOLATOR).
                setDuration(200);
        animator.setListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                view.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                hide(view);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        animator.start();

    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

两种实现方法的对比和总结

  • 我们知道第一种方法我们主要是重写layoutDependsOn 和 onDependentViewChanged 这两个方法,这个方法在 layoutDependsOn 判断 dependency 是否是 APpBarLayout 的实现类,所以 会导致 child 依赖于 AppBarLayout,灵活性不是太强

  • 而第二种方法,我们主要是重写 onStartNestedScroll 和 onNestedPreScroll 这两个方法,判断是否是垂直滑动,是的话就进行处理,灵活性大大增强,推荐使用这一种方法

  • 需要注意的是不管是第一种方法,还是第二种方法,我们都需要重写带两个构造方法的函数,因为底层机制会采用反射的形式获得该对象

public FooterBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
}
   
   
  • 1
  • 2
  • 3

自定义 Behavior 实现 FloatingActionButton 的显示与隐藏

效果图如下

缩放隐藏的

向上向下隐藏的

布局代码

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/activity_floating_action_button"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.xujun.contralayout.UI.FloatingActionButtonActivity">


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


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways">


            <ImageView
                android:id="@+id/search"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:src="@drawable/search"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:layout_toRightOf="@id/search"
                android:text="搜索话题、问题或人"
                android:textSize="16sp"/>

        </RelativeLayout>


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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    </android.support.v7.widget.RecyclerView>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right|end"
        android:layout_marginBottom="40dp"
        android:layout_marginRight="25dp"
        android:background="@android:color/holo_green_light"
        android:src="@drawable/add"
        app:layout_behavior="@string/behavior_my_fab_scale"/>

</android.support.design.widget.CoordinatorLayout>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

如果想使用不同的效果,只需要给 FloatingActionButton 制定不同的 bevaior 即可

 app:layout_behavior="com.xujun.contralayout.behavior.MyFabBehavior"
   
   
  • 1

自定义behavior 代码

/**
 *  FloatingActionButton behavior 向上向下隐藏的
 * @author xujun  on 2016/12/1.
 * @email gdutxiaoxu@163.com
 */

public class MyFabBehavior extends CoordinatorLayout.Behavior<View> {

    private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

    private float viewY;//控件距离coordinatorLayout底部距离
    private boolean isAnimate;//动画是否在进行

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

    //在嵌套滑动开始前回调
    @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) {
        //dy大于0是向上滚动 小于0是向下滚动

        if (dy >=0&&!isAnimate&&child.getVisibility()==View.VISIBLE) {
            hide(child);
        } else if (dy <0&&!isAnimate&&child.getVisibility()==View.GONE) {
            show(child);
        }
    }

    //隐藏时的动画
    private void hide(final View view) {
        ViewPropertyAnimator animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(200);

        animator.setListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                isAnimate=true;
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                view.setVisibility(View.GONE);
                isAnimate=false;
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                show(view);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        animator.start();
    }

    //显示时的动画
    private void show(final View view) {
        ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);
        animator.setListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                view.setVisibility(View.VISIBLE);
                isAnimate=true;
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                isAnimate=false;
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                hide(view);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        animator.start();
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
/**
 * <p>下拉时显示FAB,上拉隐藏,留出更多位置给用户。</p>
 * Created on 2016/12/1.
 *
 * @author xujun
 */
public class ScaleDownShowBehavior extends FloatingActionButton.Behavior {
    /**
     * 退出动画是否正在执行。
     */
    private boolean isAnimatingOut = false;

    private OnStateChangedListener mOnStateChangedListener;

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

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut && child.getVisibility() == View.VISIBLE) {//往下滑
            AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener);
            if (mOnStateChangedListener != null) {
                mOnStateChangedListener.onChanged(false);
            }
        } else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE) {
            AnimatorUtil.scaleShow(child, null);
            if (mOnStateChangedListener != null) {
                mOnStateChangedListener.onChanged(true);
            }
        }
    }

    public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {
        this.mOnStateChangedListener = mOnStateChangedListener;
    }

    // 外部监听显示和隐藏。
    public interface OnStateChangedListener {
        void onChanged(boolean isShow);
    }

    public static <V extends View> ScaleDownShowBehavior from(V view) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if (!(params instanceof CoordinatorLayout.LayoutParams)) {
            throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
        }
        CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior();
        if (!(behavior instanceof ScaleDownShowBehavior)) {
            throw new IllegalArgumentException("The view is not associated with ScaleDownShowBehavior");
        }
        return (ScaleDownShowBehavior) behavior;
    }

    private ViewPropertyAnimatorListener viewPropertyAnimatorListener = new ViewPropertyAnimatorListener() {

        @Override
        public void onAnimationStart(View view) {
            isAnimatingOut = true;
        }

        @Override
        public void onAnimationEnd(View view) {
            isAnimatingOut = false;
            view.setVisibility(View.GONE);
        }

        @Override
        public void onAnimationCancel(View arg0) {
            isAnimatingOut = false;
        }
    };
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

思路这里就不详细展开了,因为前面在讲解 仿知乎效果的时候已经讲过了,大概就是根据不同的滑动行为执行不同的动画 而已


题外话

  • 通过这篇博客,熟悉 CoordinatorLayout 的 各种用法,同时也初步理解了自定义Behavior的思路
  • 同时复习了动画的相关知识
  • 如果你觉得效果还不错,欢迎到我的github上面star,github地址

文章首发地址CSDN:http://blog.csdn.net/gdutxiaoxu/article/details/53453958

源码下载地址:https://github.com/gdutxiaoxu/CoordinatorLayoutExample.git

最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

                                <link href="http://csdnimg.cn/release/phoenix/production/markdown_views-993eb3f29c.css" rel="stylesheet">
                                </div>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CoordinatorLayout 是 Android Design Support Library 提供的一个特殊的布局容器,它可以协调其内部的子 View 之间的交互行为,实现各种复杂的交互效果,比如 AppBarLayout 和 FloatingActionButton 的联动效果。 CoordinatorLayout 的主要作用是让子 View 之间可以通过 Behavior 进行交互。Behavior 是指子 View 在 CoordinatorLayout 中的交互行为的定义,可以让子 View 之间实现联动效果,如 FloatingActionButton 随着 Snackbar 的出现和消失而改变位置,子 View 之间的交互行为通过 Behavior 实现。 使用 CoordinatorLayout 需要注意以下几点: 1. CoordinatorLayout 必须作为根布局。 2. 子 View 需要设置 app:layout_behavior 属性,指定其交互行为的 Behavior。 3. 子 View 的交互行为需要在 Behavior 中定义。 下面是一个简单的 CoordinatorLayout 的示例: ``` <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.appbar.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="200dp" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/image"/> <androidx.appcompat.widget.Toolbar android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> ``` 在上面的示例中,AppBarLayout 和 CollapsingToolbarLayout 实现了一个可折叠的 Toolbar,RecyclerView 使用了 appbar_scrolling_view_behavior Behavior,实现了和可折叠的 Toolbar 的联动效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值