1CoordinatorLayout与behavior入门

前言

闲来无事,掏出AS熟练的new一个project,发现默认出来的activity点击之后有如下动画

fab 和snackbar

感觉很神奇,于是分析以下,这是怎么做到的?
这个activity的style是@style/AppTheme.NoActionBar,布局如下所示,这是目前android比较推崇的做法,基本理念就是脱离actionbar,在自己的布局里写toolbar,这样会提高更大的自由度,toolbar就变成了一个普通的view,我想放哪里放哪里,想怎么放怎么放(其实默认actionbar也是toolbar实现的,view层次结构可以参考AppCompatActivity的View树)。

<?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"
    android:fitsSystemWindows="true"
    tools:context="com.fish.a2.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/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

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

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

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

这一个xml中,CoordinatorLayout、FloatingActionButton、AppBarLayout都是陌生的。FloatingActionButton就是右下角的带阴影圆形按钮,可以简单看做一个ImageButton,如果对此控件有兴趣,可以阅读http://blog.csdn.net/lmj623565791/article/details/46678867,此文内鸿洋大神还自己实现了阴影效果。
我们再来看自动生成的部分代码,FloatingActionButton(后边简称fab)的点击就是弹出一个Snackbar,又出来一个新玩意,Snackbar可以看做是Toast的升级版本,没什么高深的,下边代码的意思就是点击fab弹出一个Snackbar,Snackbar像toast一样几秒后就消失。

      FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show();
            }
        });

这些看起来都很简单,但是Snackbar弹出的时候,为何fab为何为往上跑呢?然后过了几秒钟,Snackbar消失了,fab又自动往下滑了,这2个动画是怎么做到的?从代码里看不出任何端倪.

CoordinatorLayout

先简单介绍下CoordinatorLayout,看官方文档
CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout is intended for two primary use cases:

1.As a top-level application decor or chrome layout
2.As a container for a specific interaction with one or more child views

By specifying Behaviors for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using the DefaultBehavior annotation.

Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.

CoordinatorLayout是个超级强大的FrameLayout(其实他并不继承FrameLayout,只是里面的view布局和FrameLayout一样,一层层叠上去)

CoordinatorLayout主要有2种用法
1.作为app的rootview(contentview的child)
2.作为一个容器,包含几个view,这些view有特殊行为,互相作用

给CoordinatorLayout的子view指定behavior可以让他们互相作用互相依赖

behavior

概述

Behavior是一种新的view关系描述,CoordinatorLayout的一个子view可以依赖于另一个子view做出响应,比如有子view A和B,A依赖于B,一旦B发生变化(比如大小位置可见性改变),A就可以知道并且响应,这其实就是观察者模式。一个子view可以依赖于多个子view,比如A可以依赖于B,C这个依赖关系会在一个类内写明,这个类是CoordinatorLayout.Behavior的子类,可以自定义。

例子解释

还是有点难以理解,看上文的实际例子吧。
fab会因为Snackbar的弹出而上移,随着Snackbar的消失而下移,是因为fab依赖于Snackbar。 这种依赖关系由FloatingActionButton内部的Behavior类决定的。
Behavior内有如下代码,看layoutDependsOn代码,dependency是SnackbarLayout就返回true(SNACKBAR_BEHAVIOR_ENABLED前提下),这就说明了fab依赖于SnackbarLayout,一旦SnackbarLayout发生变化,fab就会立刻知道并作出反应。

    @Override
        public boolean layoutDependsOn(CoordinatorLayout parent,
                FloatingActionButton child, View dependency) {
            // We're dependent on all SnackbarLayouts (if enabled)
            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
        }

做出什么反应呢?来看onDependentViewChanged的代码,主要调用updateFabTranslationForSnackbar,注意updateFabTranslationForSnackbar 的L34、L35,这里触发了平移动画。这就是为什么snackbar出现或消失的时候,fab会有平移动画。


       @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                updateFabTranslationForSnackbar(parent, child, dependency);
            } 
            return false;
        }
       private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
                final FloatingActionButton fab, View snackbar) {
            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
            if (mFabTranslationY == targetTransY) {
                // We're already at (or currently animating to) the target value, return...
                return;
            }

            final float currentTransY = ViewCompat.getTranslationY(fab);

            // Make sure that any current animation is cancelled
            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
                mFabTranslationYAnimator.cancel();
            }

            if (fab.isShown()
                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
                // If the FAB will be travelling by more than 2/3 of it's height, let's animate
                // it instead
                if (mFabTranslationYAnimator == null) {
                    mFabTranslationYAnimator = ViewUtils.createAnimator();
                    mFabTranslationYAnimator.setInterpolator(
                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                    mFabTranslationYAnimator.setUpdateListener(
                            new ValueAnimatorCompat.AnimatorUpdateListener() {
                                @Override
                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
                                    ViewCompat.setTranslationY(fab,
                                            animator.getAnimatedFloatValue());
                                }
                            });
                }
                //这里制造动画来完成上移或者下移
                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
                mFabTranslationYAnimator.start();
            } else {
                // Now update the translation Y
                ViewCompat.setTranslationY(fab, targetTransY);
            }

            mFabTranslationY = targetTransY;
        }

我们再具体分析下,为何snackbar出现的时候,fab上移,而snackbar消失的时候fab下移呢?
在snackbar出现的时候,会调用onDependentViewChanged()然后调用updateFabTranslationForSnackbar(),这里重点是L34代码,意思就是从currentTransY平移到targetTransY,关注下这2个变量。currentTransY怎么来的,看L9就是fab本身的TranslationY,那targetTransY呢?看L3,由getFabTranslationYForSnackbar得到。

所以看一下getFabTranslationYForSnackbar的代码,L5获取CoordinatorLayout内部被依赖的子view,在这个CoordinatorLayout内只有fab依赖于Snackbar.SnackbarLayout,所以Snackbar.SnackbarLayout是被依赖的子view,也只有这一个。看L10,此时view就是Snackbar.SnackbarLayout ,minOffset为0,ViewCompat.getTranslationY(view) 为0,所以最终得到的minOffset就是- view.getHeight(),其实就是snackbar的高度的相反数。我们假设-200好了。再回到updateFabTranslationForSnackbar内,此时targetTransY为-200,而currentTransY为0,那会发生什么?从0上移到-200,所以snackbar出现的时候,fab做了一个上移动画。

       private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
                FloatingActionButton fab) {
            float minOffset = 0;
            //获取被依赖的子view
            final List<View> dependencies = parent.getDependencies(fab);
            for (int i = 0, z = dependencies.size(); i < z; i++) {
                final View view = dependencies.get(i);
                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
                //重点是这句代码
                    minOffset = Math.min(minOffset,
                            ViewCompat.getTranslationY(view) - view.getHeight());
                }
            }

            return minOffset;
        }

下面我们再可以分析snackbar消失的时候的场景,注意这里稍有不同,snackbar 出现调用了onDependentViewChanged,但是snackbar消失调用的是onDependentViewRemoved, 但最终调的还是updateFabTranslationForSnackbar ,殊途同归啊,此时getFabTranslationYForSnackbar内dependencies为空(因为snackbar 不见了),所以最终返回0,所以targetTransY为0,而currentTransY位-200,所以就从-200下移到了0,下移就是这么产生的。

    @Override
        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                updateFabTranslationForSnackbar(parent, child, dependency);
            }
        }

snackbar出现回调到onDependentViewChanged,然后调用updateFabTranslationForSnackbar完成上移。
snackbar消失回调到onDependentViewRemoved,然后调用updateFabTranslationForSnackbar完成上移。

总结

简单的说CoordinatorLayout是容器,behavior是核心。behavior封装了CoordinatorLayout的子view随着其他子view的变化而变化的规则,主要是通过layoutDependsOn和onDependentViewChanged这2个方法。再看看CoordinatorLayout的名字,就是协调布局的意思,协调子view之间的关系,被协调的2个view必须是CoordinatorLayout的直接子view

dependency概念

fab依赖于Snackbar.SnackbarLayout,那我们就可以说fab的dependency是Snackbar.SnackbarLayout。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值