Android Design Support Library系列之七:CoordinatorLayout

CoordinatorLayout------协调者布局.
首先来看一下CoordinatorLayout的继承结构:


Android Design Support Library系列之七:CoordinatorLayout_第1张图片

CoordinatorLayout其本质是一个超级 FrameLayout,其功能主要有2个:
1)作为顶层布局
2)协调其直接子View进行交互

官方文档
使用前添加依赖:

compile 'com.android.support:design:25.3.1'

一、Behavior

CoordinatorLayout的神奇之处就在于Behavior对象,CoordinatorLayout通过Behavior对象来处理其子View的下列事件:
1)布局事件
2)触摸事件
3)View状态变化事件
4)嵌套滑动事件
Behavior就是CoordinatorLayout处理事件的媒介,在Behavior中定义了 CoordinatorLayout 中直接子 View 的行为规范,决定了当收到不同事件时,应该做怎样的处理。

Behavior继承结构
Android Design Support Library系列之七:CoordinatorLayout_第2张图片

Behavior是一个定义在CoordinatorLayout中的抽象内部类.

public static abstract class Behavior {

     public Behavior() {
     }

     public Behavior(Context context, AttributeSet attrs) {
     }
     ......
}
下面我们通过自定义Behavior来简单了解:

3)View状态变化事件
某个View监听另一个View的状态变化,例如大小、位置、显示状态等
此时,我们需要关心Behavior的下面两个方法:

layoutDependsOn()
onDependentViewChanged()

4)嵌套滑动事件
某个View监听另一个View(实现NestedScrollingChild接口)的滑动状态
此时,我们需要关心Behavior中与NestedScrolling相关的方法.

二、自定义Behavior之一

某个View监听另一个View的状态变化

我们首先来看一下效果图:
1)水平拖动dependency时,child朝着与dependency相反方向移动
2)竖直拖动dependency时,child在相同方向上同步移动

Android Design Support Library系列之七:CoordinatorLayout_第3张图片

这里我们要理解两个概念: childdependency.
1) child是CoordinatorLayout的直接子View,也就是要执行动作的View
2) dependency是指child依赖的View,也就是child要监听的View

在上面的效果图中:child的动作依赖于dependency,当dependency这个View发生了变化,那么child这个View就发生相应变化。
child具体变化的动作就定义在Behavior中:
我们定义一个类,继承CoordinatorLayout.Behavior,其中,泛型参数T是我们要执行动作的View类,也就是Child,然后实现Behavior的两个方法:

layoutDependsOn()
onDependentViewChanged()
public class DependencyBehavior extends CoordinatorLayout.Behavior

这里的DragTextView是一个自定义的TextView

/**
 * 随着手指移动的TextView
 */
public class DragTextView extends TextView {
    private int lastX;
    private int lastY;

    public DragTextView(Context context) {
        super(context);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) getLayoutParams();
                int left = layoutParams.leftMargin + x - lastX;
                int top = layoutParams.topMargin + y - lastY;

                layoutParams.leftMargin = left;
                layoutParams.topMargin = top;
                setLayoutParams(layoutParams);
                requestLayout();
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
        }
        lastX = x;
        lastY = y;
        return true;
    }
}

Behavior的使用方式:

  1. 在布局文件中通过app:layout_behavior=" "引用
  2. 使用注解添加,系统的控件一般使用这种方式,例AppBarLayout:
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
  ......
  public static class Behavior extends HeaderBehavior {
    ......
  }
  ......
}
  1. 在代码中添加
DependencyBehavior mBehavior = new DependencyBehavior(this,null);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) childView.getLayoutParams();
params.setBehavior(mBehavior);

这里我们使用第一种方式:




    

    

这里需要注意的是:
1)一个child可以同时依赖多个dependency
2)dependency也有可能依赖一个或者多个另外的dependency
3)如果你添加了一个依赖,不管child的顺序如何,你的child将总是在所依赖的View放置之后才会被放置
ok,简单的自定义Behavior已经完成了,你对于CoordinatorLayout使用Behavior协调子View之间的交互是否有所了解了?

三、自定义Behavior之二

某个View监听另一个View(实现NestedScrollingChild接口)的滑动状态

我们首先来看一下效果图:


1)向上滑动时,右下角FloatingActionButton隐藏
2)向下滑动时,右下角FloatingActionButton显示
3)点击FloatingActionButton时,弹出Snackbar,同时FloatingActionButton自动上移.

其中效果(3)是FloatingActionButton中自带的Behavior的效果,相信看了上面你也大概了解这个Behavior中FloatingActionButton一定是依赖Snackbar的了吧。

所以这里我们是直接继承FloatingActionButton.Behavior:

public class ScrollBehavior extends FloatingActionButton.Behavior {

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

    /**
     * 嵌套滑动事件开始
     *
     * @return  根据返回值确定我们关心哪个方向的滑动(x轴/y轴),这里我们关心的是y轴方向
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }


    /**
     *  嵌套滑动正在进行中
     *  参数有点多,由于这里我们只关心y轴方向的滑动,所以简单测试了dyConsumed、dyUnconsumed
     *      dyConsumed > 0 && dyUnconsumed == 0 上滑中
     *      dyConsumed == 0 && dyUnconsumed > 0 到边界了还在上滑
     *
     *      dyConsumed < 0 && dyUnconsumed == 0 下滑中
     *      dyConsumed == 0 && dyUnconsumed < 0 到边界了还在下滑
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        if (((dyConsumed > 0 && dyUnconsumed == 0) 
                || (dyConsumed == 0 && dyUnconsumed > 0))
                && child.getVisibility() != View.INVISIBLE) {// 上滑隐藏
            child.setVisibility(View.INVISIBLE);
        } else if (((dyConsumed < 0 && dyUnconsumed == 0) 
                || (dyConsumed == 0 && dyUnconsumed < 0)) 
                && child.getVisibility() != View.VISIBLE ) {//下滑显示
            child.setVisibility(View.VISIBLE);
        }
    }
}

这里我们采用一种高大上的方式来使用Behavior ,也是我们使用系统定义好Behavior 的常用方式:
res/values/strings.xml中:


    CoordinatorLayout
    
    com.my.ScrollBehavior
    

    \n
                    从前现在过去了再不来\n\n
                    红红落叶长埋尘土内\n\n
                    开始终结总是没变改\n\n
                    天边的你飘泊白云外\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n\n
                    情人别后永远再不来(消散的情缘)\n\n
                    无言独坐放眼尘世外(愿来日再续)\n\n
                    鲜花虽会凋谢(只愿)\n\n
                    但会再开(为你)\n\n
                    一生所爱隐约(守候)\n\n
                    在白云外(期待)\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n
                    ----------------\n
                    从前现在过去了再不来\n\n
                    红红落叶长埋尘土内\n\n
                    开始终结总是没变改\n\n
                    天边的你飘泊白云外\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n\n
                    情人别后永远再不来(消散的情缘)\n\n
                    无言独坐放眼尘世外(愿来日再续)\n\n
                    鲜花虽会凋谢(只愿)\n\n
                    但会再开(为你)\n\n
                    一生所爱隐约(守候)\n\n
                    在白云外(期待)\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n\n
                    苦海翻起爱恨\n\n
                    在世间难逃避命运\n\n
                    相亲竟不可接近\n\n
                    或我应该相信是缘份\n
    




    

        

    


    



Activiy中:

public class MainActivity extends AppCompatActivity {

    private FloatingActionButton mFAB;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scroll);

        mFAB = (FloatingActionButton) findViewById(R.id.fab);
        mFAB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(mFAB,"Snackbar",Snackbar.LENGTH_SHORT).show();
            }
        });
    }
}

这里需要注意的是:
1)你不需要在嵌套滑动的Behavior中定义依赖,CoordinatorLayout的每个child都有机会接收到嵌套滑动事件,这里继承的FloatingActionButton.Behavior中存在依赖是因为要和Snackbar实现联动.
2)虽然我叫它嵌套滑动,但其实它包含滚动(scrolling)和滑动(flinging)两种
3)监听的滑动View必须实现NestedScrollingChild的接口,这是因为CoordinatorLayout中一个View想向外界传递滑动事件,即通知 NestedScrollingParent(CoordinatorLayout实现了此接口),就必须实现此接口.而 Child 与 Parent 的具体交互逻辑, NestedScrollingChildHelper 辅助类基本已经帮我们封装好了,所以我们只需要调用对应的方法即可。
这就可以解释为什么不能用ScrollView、ListView而用NestScrollView来滑动了,当然,如果你要自己自定义一个View实现NestedScrollingChild接口也是可以的,不过那样太麻烦了。像NestScrollView、RecyclerView、SwipeRefreshLayout中都实现了NestedScrollingChild接口.

如果你想了解关于嵌套滑动机制更多的详情,你可以去看一看下面几个类:
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper

本文参考:
CoordinatorLayout的使用如此简单
Intercepting everything with CoordinatorLayout Behaviors

你可能感兴趣的:(Android Design Support Library系列之七:CoordinatorLayout)