文章大纲

  • 引言
  • 一、CoordinatorLayout概述
  • 二、CoordinatorLayout.LayoutParams概述
  • 三、CoordinatorLayout.Behavior
    • 1、Behavior概述
    • 2、CoordinatorLayout.Behavior核心方法
      • 2.1、layoutDependsOn方法
      • 2.2、onDependentViewChanged方法
      • 2.3、onDependentViewRemoved方法
      • 2.4、onInterceptTouchEvent方法设置是否拦截触摸事件
      • 2.5、onTouchEvent方法处理触摸事件
      • 2.6、onMeasureChild方法测量使用Behavior的View尺寸
      • 2.7、onLayoutChild方法重新布局使用Behavior的View
  • 三、CoordinatorLayout的简单使用
    • 1、引入CoordinatorLayout 依赖
    • 2、继承CoordinatorLayout.Behavior实现自己的Behavior
    • 3、使用CoordinatorLayout作为顶层布局,同时给对应的View配置Behavior使之变成主题View
    • 4、在MainActivity中触发dependency View状态改变进行测试
  • 四、CoordinatorLayout的核心流程解析

引言

前面系列文章总结了Material Design 兼容库提供大部分新控件的使用,Android L之前,如果希望一个ViewGroup里的独立的控件互相关联和交互,比如说侧滑菜单、可滑动删除的UI元素等效果,需要自己去实现交互逻辑,而在引入Material Design 兼容库之后就十分简单了,CoodinatorLayout就提供交互逻辑,系列文章链接:

  • Android进阶——Material Design新控件之初识TabLayout(一)
  • Android进阶——Material Design新控件之TabLayout制作可滚动的Tabs页面(二)
  • Android进阶——Material Design新控件之Snackbar(三)
  • Android进阶——Material Design新控件之TextInputLayout(四)
  • Android进阶——Material Design新控件之FloatingActionButton(五)
  • Android进阶——Material Design新控件之NavigationView(六)
  • Android进阶——Material Design新控件之利用CoordinatorLayout协同多控件交互(七)

一、CoordinatorLayout概述

CoordinatorLayout继承自ViewGroup,可以看成是一个加强版的“FrameLayout”,在Android开发中提供的核心功能主要有:

  • 布局文件当中作为最顶层的布局容器控件
  • CoordinatorLayout中的直接子控件提供特定的交互功能,相当于是给CoordinatorLayout里的直接子控件建立依赖关系,使得原本相对独立的控件,产生“依赖”联系,可以实现一个目标控件改变时,另一个也随着改变。

简而言之,CoordinatorLayout可以使得其直接子控件之间产生“依赖”联系,具体是通过变形的“观察者模式”实现的

  • 主题(被观察者角色)——配置了Behavior的子View(在布局文件中使用Behavior的全类名字符串(也可以@string/R.string.xxx形式引入)来配置View的app:layout_behavior属性
  • 观察者角色—— 在绑定的Behavior的layoutDependsOn方法返回true时的dependency view

观察者View(位置、大小)改变时,就会触发Behavior的onDependentViewChanged方法(只有Dependency View 改变时才会触发)。

无论是主题还是观察者View,都必须是CoordinatorLayout内的直接子View

二、CoordinatorLayout.LayoutParams概述

CoordinatorLayout.LayoutParams是CoordinatorLayout的内部类,和其他ViewGroup功能类似,在CoordinatorLayout的generateLayoutParams方法中直接调用构造方法进行初始化且在CoordinatorLayout.LayoutParams构造方法内部调用CoordinatorLayout的parseBehavior根据配置的Behavior的类名反射创建Behavior并赋值到mBehavior字段,然后再通过Behavior的onAttachedToLayoutParams方法Called when the Behavior has been attached to a LayoutParams instance.,所以除了保存CoordinatorLayout内的子控件的布局信息之外,还保存着对应的Behavior对象引用 mBehavior

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {   ...   public static class LayoutParams extends MarginLayoutParams {   ...        Behavior mBehavior;        LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) {    super(context, attrs);    final TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CoordinatorLayout_Layout);    this.gravity = a.getInteger(R.styleable.CoordinatorLayout_Layout_android_layout_gravity,Gravity.NO_GRAVITY);    mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,View.NO_ID);    this.anchorGravity = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,Gravity.NO_GRAVITY);    this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,-1);    insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);    dodgeInsetEdges = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);    mBehaviorResolved = a.hasValue(R.styleable.CoordinatorLayout_Layout_layout_behavior);    if (mBehaviorResolved) {        mBehavior = parseBehavior(context, attrs, a.getString(R.styleable.CoordinatorLayout_Layout_layout_behavior));    }    a.recycle();    if (mBehavior != null) {        // If we have a Behavior, dispatch that it has been attached        mBehavior.onAttachedToLayoutParams(this);    }}}...}

三、CoordinatorLayout.Behavior

1、Behavior概述

CoordinatorLayout.Behavior是CoordinatorLayout的抽象泛型内部类,Behvaior 本身并不具备具体的业务功能,本质上就只是为了进行解耦的而封装的一个交互接口集合类,而CoordinatorLayout可以借助Behavior使得独立的子View可以产生交互,是因为CoordinatorLayout内部把事件分发至Behavior,让Behavior具有可以控制其他子View的效果了,也是CoordinatorLayout中核心的设计,也正是因为这个CoordinatorLayout.Behavior使得CoordinatorLayout中的直接子控件间可以产生联系,CoordinatorLayout.Behavior可以理解为事件分发的传送渠道(并不负责具体的任务),只是负责调用对应子View的相关方法parseBehavior方法根据配置的Behavior的类名反射创建Behavior并赋值到mBehavior字段,这是继承Behavior时必须要重写两个参数的构造方法的原因。通俗来说,Behavior 设置在谁身上,就可以通过Behavior来改变它对应的状态,观察者改变时,主题也跟着改变

2、CoordinatorLayout.Behavior核心方法

CoordinatorLayout.Behavior中最核心的方法只有三个:layoutDependsOn方法、onDependentViewChanged方法和onDependentViewRemoved方法,通过这三个方法就可以实现直接子View之间的交互,至于其他方法是处理到其他业务情况的时候,比如说嵌套滑动、重新布局等等。

2.1、layoutDependsOn方法

当进行Layout请求的时候就会触发执行,给CoordinatorLayout中的直接子控件设置了对应的Behavior之后,绘制时至少会执行一次,表示是否给配置了Behavior 的CoordinatorLayout直接子View 指定一个作为观察者角色的子View,返回true则表示主题角色child view的观察者是dependency view, 当观察者角色View状态(大小、位置)发生变化时,不管被观察View 的顺序怎样,被观察的View也可监听到并回调对应的方法;反之则两者之间没有建立联系。简而言之,这个方法的作用是配置了Behavior的主题子控件被符合哪些条件逻辑的子控件观察的(即作为主题的观察者之一)(Determine whether the supplied child view has another specific sibling view as a layout dependency)。

/** * 用于给配置了Behavior的View(主题) 指定一个观察者角色的View,返回true则dependency 为主题的观察者 * @param parent child和dependency view的外层父布局 * @param child 绑定behavior 的View   (观察者) * @param dependency   被观察者的view (主题) * @return 如果child 是观察者观察的View 返回true,否则返回false */@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {    Log.e("------>","layoutDependsOn:child= "+child.getClass().getName()            +"  dependency= "+dependency.getClass().getName());    //依次判断dependency 是否符合条件,    if(dependency instanceof Button){        return true;    }    return super.layoutDependsOn(parent, child, dependency);}

比如在CoordinatorLayout里的某一个子View配置了Behavior之后,CoordinatorLayout在布局时会循环去查找其所有的直接子View,逐一去判断这个子View是否可以作为主题的观察者,所以当CoordinatorLayout里有2个直接子控件时layoutDependsOn方法会触发3次(触发时机是处理layout request时,包含第一次绘制时和View改变时触发),如果打印dependency view的名称,你会看到CoordinatorLayout里所有的直接子View都会打印一遍(除了主题View)。

2.2、onDependentViewChanged方法

当且仅当Dependency View 状态(位置、大小等)改变时就会触发,返回true则表示Behavior改变了主题的状态,可能会执行多次,当然第一次绘制到布局上也算是状态改变时,所以自然也会触发,至于当监听到改变之后,如何去实现什么样的效果则由我们自己去开发实现。

/** * 当被观察者的View 状态(如:位置、大小)发生变化时就会触发执行 * @return true if the Behavior changed the child view's size or position, false otherwise */@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {    Log.e("------>","onDependentViewChanged:child= "+child.getClass().getName()            +"  dependency= "+dependency.getClass().getName());    return super.onDependentViewChanged(parent, child, dependency);}

可以在View改变时及时得知,以下为部分运行日志:

2.3、onDependentViewRemoved方法

当依赖的Dependency View被移除时触发回调(Respond to a child’s dependent view being removed.)

/** * Respond to a child's dependent view being removed. * @param parent the parent view of the given child * @param child the child view to manipulate * @param dependency the dependent view that has been removed */public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child,        @NonNull View dependency) {}

2.4、onInterceptTouchEvent方法设置是否拦截触摸事件

设置是否拦截触摸事件,返回true则表示当前Behavior会拦截触摸事件,不会分发到CoordinatorLayout内的子View下了。(Respond to CoordinatorLayout touch events before they are dispatched to child views.)

public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,       @NonNull MotionEvent ev) {   return false;}

2.5、onTouchEvent方法处理触摸事件

public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,       @NonNull MotionEvent ev) {   return false;}

2.6、onMeasureChild方法测量使用Behavior的View尺寸

/** * Called when the parent CoordinatorLayout is about to measure the given child view. * @param child the child to measure * @return true if the Behavior measured the child view, false if the CoordinatorLayout *         should perform its default measurement */public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child,        int parentWidthMeasureSpec, int widthUsed,        int parentHeightMeasureSpec, int heightUsed) {    return false;}

2.7、onLayoutChild方法重新布局使用Behavior的View

/** * Called when the parent CoordinatorLayout is about the lay out the given child view. * @return true if the Behavior performed layout of the child view, false to request default layout behavior */public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child,        int layoutDirection) {    return false;}

三、CoordinatorLayout的简单使用

1、引入CoordinatorLayout 依赖

dependencies {    def coordinatorlayout_version = "1.0.0"    implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayout_version"}

2、继承CoordinatorLayout.Behavior实现自己的Behavior

  • 必须重写形参列表为(Context context, AttributeSet attrs)的构造方法
  • 重写layoutDependsOn方法
  • 重写onDependentViewChanged方法
/** * 此Behavior 将要被绑定到TextView上,用于改变TextView的UI效果 * @author : Crazy.Mo */public class TextColorBehavior extends CoordinatorLayout.Behavior<TextView> {//避免因为CoordinatorLayout的onLayout执行时,一开始就调用了Behavior的onLayout造成,还未开始交互就一直执行了onDependentViewChanged    private boolean isFirst=true;    /**     * 必须重写,否则肯定报错     * @param context     * @param attrs     */    public TextColorBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    /**     * 这个方法是响应layout请求的,将至少调用一次,返回true时,child和dependency建立了依赖联系     * 即child是依赖dependency的     * @param parent     * @param child     * @param dependency     * @return     */    @Override    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull TextView child, @NonNull View dependency) {        //确定什么样的dependency 才有资格做child的观察者,逻辑由你自己决定        if(dependency instanceof Button){            return true;        }        return false;    }    /**     * 当dependency 的状态改变时就会触发     * @param parent     * @param child     * @param dependency     * @return     */    @Override    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull TextView child, @NonNull View dependency) {        ///当dependency 的状态改变时,就会触发这个方法执行,在这个方法你可以拿到主题View和观察者View,所以想要实现什么样的效果,完全由你决定,彻彻底底地解耦        if(!isFirst){            if(child.getId()==R.id.txt_demo2) {                child.setX(dependency.getX() + 20);                child.setY(dependency.getY() + 150);                child.setTextColor(Color.GREEN);            }else if(child.getId()==R.id.txt_demo){                child.setX(dependency.getX() + 20);                child.setY(dependency.getY() + 350);                child.setTextColor(Color.BLUE);            }//当然你可以同时改变dependency的状态//            dependency.setX(child.getX()+20);//            dependency.setY(child.getY()+150);           /// dependency.setBackgroundColor(Color.RED);        }        isFirst=false;        return true;    }}

在CoordinatorLayout中一般对于一个主题View来说一次只能设置绑定一个Behavior(且绑定的Behavior并不是被独享,其他主题View也可以绑定同一个Behavior),但可以被多个观察者所监听,而一个观察者可以同时监听多个主题View

package com.crazymo.coordinator;import android.content.Context;import android.graphics.Color;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.TextView;import androidx.annotation.NonNull;import androidx.coordinatorlayout.widget.CoordinatorLayout;/** * @author : Crazy.Mo */public class TextBehavior extends CoordinatorLayout.Behavior<TextView> {    private boolean isFirst=true;    /**     * 必须重写,否则肯定报错     * @param context     * @param attrs     */    public TextBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    /**     * 这个方法是响应layout请求的,将至少调用一次,返回true时,child和dependency建立了依赖联系     * 即child是依赖dependency的     * @param parent     * @param child     * @param dependency     * @return     */    @Override    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull TextView child, @NonNull View dependency) {        if(dependency instanceof Button){            return true;        }        return false;    }    @Override    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull TextView child, @NonNull View dependency) {        if(!isFirst){            child.setX(dependency.getX()+20);            child.setY(dependency.getY()+250);            child.setTextColor(Color.RED);        }        isFirst=false;        return true;    }}

上面定义了两个Behavior。

3、使用CoordinatorLayout作为顶层布局,同时给对应的View配置Behavior使之变成主题View

使用CoordinatorLayout一定要给对应的主题View配置app:layout_behavior属性,否则就没有必要使用CoordinatorLayout。

<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.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=".MainActivity">    <TextView        android:id="@+id/txt_demo"        app:layout_behavior=".TextColorBehavior"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="top"        android:text="我是主题View(被观察者)" />    <TextView        android:id="@+id/txt_demo2"        app:layout_behavior=".TextColorBehavior"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="left|center"        android:text="我是主题View2(被观察者)" />    <TextView        android:id="@+id/txt_demo3"        app:layout_behavior=".TextBehavior"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="right|center_horizontal"        android:text="我是主题View3(被观察者)" />    <Button        android:id="@+id/btn_demo"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="bottom"        android:text="观察者" />androidx.coordinatorlayout.widget.CoordinatorLayout>

4、在MainActivity中触发dependency View状态改变进行测试

此处模拟的是当dependency View状态改变时,其他主题View跟随者改变的简单效果,当观察者移动时主题TextView紧跟其下方移动。

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.btn_demo).setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                if(event.getAction()==MotionEvent.ACTION_MOVE){                    v.setX(event.getRawX()-v.getWidth()/2);                    v.setY(event.getRawY()-v.getHeight()/2);                }                return true;            }        });    }}

如下图所示当我按着观察者Button移动时候,三个主题TextView都显示在Button的下方且跟着移动:

通俗总结就是:CoordinatorLayout里的任何直接子View具有随时监听到对方的状态改变的能力

四、CoordinatorLayout的核心流程解析

未完待续,篇幅问题见下篇文章。

更多相关文章

  1. Android换肤白天/夜间模式的框架
  2. Android动态部署五:如何从插件apk中启动Service
  3. Android(安卓)layout xml总结
  4. Android状态栏总结
  5. 【Android】Android(安卓)Parcelable 源码解析
  6. 类加载机制系列2——深入理解Android中的类加载器
  7. Android的一些小问题处理
  8. Android(安卓)对话框(Dialog)大全 建立你自己的对话框
  9. android中的TextView滾動條的設置

随机推荐

  1. Android知识点总结(二十)Android中的ANR
  2. Android(安卓)Studio 配置JNI快速生成头
  3. 亲测源码分享
  4. android 实现录像时拍照
  5. android volley 调用webService
  6. android 数据库 sqlite数据类型
  7. Android(安卓)framework analysis
  8. 一步步教你实现Android(安卓)HotFix热更
  9. 整理 酷炫 Android(安卓)开源UI框架 FAB
  10. Android(安卓)URl网络获取图片