coordinatorlayout和behavior解析

CoordinatorLayout

CoordinateLayout已经出来有一段时间了,但是一直没有使用,最近项目开发中遇到一个效果,使用CoordinatorLayout来实现的,瞬间感觉到了他的强大之处,所以趁着空闲时间,自己深入了解总结了一下,不到之处,请多多包涵:

官方API:

https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html

CoordinatorLayout:协调布局,是一个更加强大的FrameLayout 继承于ViewGroup,主要被设计来应用于两种场景:

1.作为一个顶层布局
2.作为与一个或多个子视图进行特定交互的容器

注:CoordinatorLayout不一定要作为最外层的根节点!

通过设置CoordinatorLayout子视图的Behavior来为不同的子视图之间提供交互。

同时CoordinatorLayout的子视图可以设置anchor(锚点:即为该子视图指定一个参照物)
该父容器视图必须是CoordinatorLayout或其子类锁包含的子视图。使用id进行绑定,类似于RelativeLayout设置相对位置时。
子View和锚点View必须在同一CoordinateLayout视图内。

官方API

Behaviorhttps://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.Behavior.html

anchorhttps://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.LayoutParams.html#setAnchorId(int)

1.使用CoordinatorLayout作为一个普通的容器(类似于FrameLayout)

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    tools:context="com.example.lbjfan.coordinatorlayoutview.MainActivity">

    <TextView
        android:id="@+id/lbj_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:text="Hello World1!" />

    <TextView
        android:id="@+id/lbj_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="Hello2!" />

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

运行效果:
这里写图片描述

2.使用anchor来设置子视图相对位置

layout_anchorGravity:描述子视图相对于锚点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"
    tools:context="com.example.lbjfan.coordinatorlayoutview.MainActivity">

    <TextView
        android:id="@+id/lbj_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:text="Hello World1!" />

    <TextView
        android:id="@+id/lbj_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="Hello2!"
        app:layout_anchor="@+id/lbj_text1"
        app:layout_anchorGravity="bottom" />

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

运行效果:
这里写图片描述
疑惑:为什么没有直接在lbj_text1的下方,还有一半高度覆盖了!(有知道的朋友解释一下),
当然,我们也可以通过设置margin来控制两个View之间的距离

layout_anchorGravity的其他取值:right,left,center,clip_vertical等。

layout_anchorGravity属性设置为center,效果如下:
这里写图片描述

3.自定义Behavior来进行CoordinatorLayout子视图之间的交互

BehaviorCoordinatorLayout的一个静态内部抽象类(静态内部类不持有外部类的引用)

public static abstract class Behavior<V extends View> {

}

需要注意的是 layout_behaviorCoordinatorLayout的layoutParams属性, 因此只有它的直接子View才能设置。

 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * A {@link Behavior} that the child view should obey.
         */
        Behavior mBehavior;
}

CoordinatorLayoutBehavior的初始化

LayoutParams(Context context, AttributeSet attrs) {
    super(context, attrs);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.CoordinatorLayout_LayoutParams);
    ...
    mBehaviorResolved = a.hasValue(
            R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
    if (mBehaviorResolved) {
        mBehavior = parseBehavior(context, attrs, a.getString(
                R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
    }

    a.recycle();
}

LayoutParams的构造方法中,首先是去检查了是不是有layout_behavior,然后调用了parseBehavior方法,返回了Behavior的实例

parseBehavior函数

static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
    if (TextUtils.isEmpty(name)) {
        return null;
    }

    // 所关联behavior的完整路径
    final String fullName;
    // 如果是".MyBehavior",则在前面加上程序的包名
    if (name.startsWith(".")) {
        fullName = context.getPackageName() + name;
    } else if (name.indexOf('.') >= 0) {
        // 指定了全名
        // Fully qualified package name.
        fullName = name;
    } else {
        fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                ? (WIDGET_PACKAGE_NAME + '.' + name)
                : name;
    }

    try {
        Map<String, Constructor<Behavior>> constructors = sConstructors.get();
        if (constructors == null) {
            constructors = new HashMap<>();
            sConstructors.set(constructors);
        }
        Constructor<Behavior> c = constructors.get(fullName);
        // 这里利用反射去实例化了指定的Behavior
        // 并且值得注意到是,这里指定了构造的参数类型
        // 也就是说我们在自定义Behavior的时候,必须要有这种类型的构造方法
        if (c == null) {
            final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
                    context.getClassLoader());
            c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
            c.setAccessible(true);
            constructors.put(fullName, c);
        }
        return c.newInstance(context, attrs);
    } catch (Exception e) {
        throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
    }
}

CONSTRUCTOR_PARAMS:

static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
        Context.class,
        AttributeSet.class
};

因此自定义Behavior时必须重载带有参数的构造函数,因为在CoordinatorLayout里利用反射去获取这个Behavior的时候就是使用这个构造函数。

几个重要的方法

onDependentViewChanged

* <p>This method is called whenever a dependent view changes in size or position outside
 * of the standard layout flow. A Behavior may use this method to appropriately update
 * the child view in response.</p>
 * <p>If the Behavior changes the child view's size or position, it should return true.
 * The default implementation returns false.</p>
 public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            return false;
  }

意思是说:当一个标准布局中所关联View的大小或位置发生改变时,Behavior可以用这个方法去更新响应这个变化的View的状态。如果Behavior改变了这个响应View的大小或位置,应返回true,默认实现返回false。

layoutDependsOn

  * Determine whether the supplied child view has another specific sibling view as a layout dependency.
  This method will be called at least once in response to a layout request
**/
 public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
}

判断是否向设置Behavior的View提供了一个依赖View,至少会调用一次,加载布局的时候。(dependencyView大小或位置发生变化的时候,也会调用)

onStartNestedScroll

/**
     * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
     * to this event and return true to indicate that the CoordinatorLayout should act as
     * a nested scrolling parent for this scroll. Only Behaviors that return true from
     * this method will receive subsequent nested scroll events.</p>
     * 个人理解:CoordinatorLayout中设置Behavior的子View响应这个事件并且返回true标志着CoordinatorLayout作为
     * 嵌套滚动的Parent
     *
     * @param coordinatorLayout:设置Behavior的View对应的CoordinatorLayout
     * @param child:设置Behavior的View
     * @param directTargetChild:
     * @param target:
     * @param nestedScrollAxes:滚动方向:SCROLL_AXIS_HORIZONTAL和SCROLL_AXIS_VERTICAL
     * @return:默认返回false
     */
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        Log.i("ScrollBehavior:", "onStartNestedScroll" + nestedScrollAxes);
        //竖直方向就消耗滚动事件
        if (nestedScrollAxes == SCROLL_AXIS_VERTICAL) {
            return true;
        } else {
            return false;
        }
    }

onNestedScroll

 /**
     * 滑动时调用
     *
     * @param coordinatorLayout
     * @param child:设置Behavior的子View
     * @param target:触发嵌套滚动的子View
     * @param dxConsumed:横向滑动距离
     * @param dyConsumed:纵向滑动距离
     */
 public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        Log.i("ScrollBehavior:", "onNestedScroll:" + dyConsumed);
}

onNestedPreFling

  public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        Log.i("ScrollBehavior:", "onNestedPreFling");
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

onNestedFling

public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
        Log.i("ScrollBehavior:", "onNestedFling");
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }
简单自定义一个竖直方向位移的Behavior

让一个View移动时,它下方的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"
    tools:context="com.example.lbjfan.coordinatorlayoutview.MainActivity">

    <TextView
        android:id="@+id/lbj_text1"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="Dependency View" />

    <TextView
        android:id="@+id/lbj_text2"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="50dp"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="ChildView"
        android:textSize="15sp"
        app:layout_behavior="com.example.lbjfan.coordinatorlayoutview.TranslationBehavior" />

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

TranslationBehavior

/**
 * 移动的Behavior
 * Created by fanxudong on 2017/10/16.
 */

public class TranslationBehavior extends CoordinatorLayout.Behavior<TextView> {

    public TranslationBehavior() {

    }

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


    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        Log.i("Behavior:", "layoutDependsOn");
        return dependency.getId() == R.id.lbj_text1;
    }

    /**
     * @param parent:最外层的CoordinatorLayout或者其子类视图
     * @param child:设置Behavior的View
     * @param dependency:依赖的View
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        Log.i("Behavior:", "onDependentViewChanged=====" + (dependency.getId() == R.id.lbj_text1));
        if (dependency.getId() == R.id.lbj_text1) {
//            CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
//            params.topMargin = dependency.getHeight() + dependency.getTop();
//            child.setLayoutParams(params);
            child.setTranslationY(dependency.getTranslationY());
        }
        return true;
    }
实现类似于AppBarLayout的Behavior

我们知道CoordinatorLayout实现了NestedScrollingParent接口, 而要监听其子View的滑动事件,那么该子View就必须实现NestedScrollingChild接口,这样CoordinatorLayout才能收到这个子类的滑动事件,V4包里面的嵌套滑动实现了NestedScrollingChild接口的View包括:

  • HorizontalGridView
  • NestedScrollView
  • RecyclerView
  • SwipeRefreshLayout
  • VerticalGridView

因此,ListViewScrollView是不支持的。

运行效果:
这里写图片描述
布局文件:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/lbj_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="Child View"
        android:textColor="@android:color/white"
        android:textSize="15sp"
        app:layout_behavior=".ScrollBehavior" />

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

ScrollBehavior

/**
 * 滑动的Behavior
 * Created by fanxudong on 2017/10/17.
 */

public class ScrollBehavior extends CoordinatorLayout.Behavior<View> {

    public ScrollBehavior() {
    }

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

    /**
     * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
     * to this event and return true to indicate that the CoordinatorLayout should act as
     * a nested scrolling parent for this scroll. Only Behaviors that return true from
     * this method will receive subsequent nested scroll events.</p>
     * 译文:CoordinatorLayout中设置Behavior的子View响应这个事件并且返回true标志着CoordinatorLayout作为
     * 嵌套滚动的Parent
     *
     * @param coordinatorLayout:设置Behavior的View对应的CoordinatorLayout
     * @param child:设置Behavior的View
     * @param directTargetChild:
     * @param target:
     * @param nestedScrollAxes:滚动方向:SCROLL_AXIS_HORIZONTAL和SCROLL_AXIS_VERTICAL
     * @return:默认返回false
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        Log.i("ScrollBehavior:", "onStartNestedScroll" + nestedScrollAxes);
        //竖直方向就消耗滚动事件
        if (nestedScrollAxes == SCROLL_AXIS_VERTICAL) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 当一个嵌套的滑动子View触发Fling时调用
     */
    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        Log.i("ScrollBehavior:", "onNestedPreFling");
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

    /**
     * 当一个嵌套的滑动子View Fling时调用
     */
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
        Log.i("ScrollBehavior:", "onNestedFling");
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    /**
     * 滑动时调用
     *
     * @param coordinatorLayout
     * @param child:设置Behavior的子View
     * @param target:触发嵌套滚动的子View
     * @param dxConsumed:横向滑动距离
     * @param dyConsumed:纵向滑动距离
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        Log.i("ScrollBehavior:", "onNestedScroll:" + dyConsumed);
        if (dyConsumed > 0) {
            // 手势从下向上滑动(列表往下滚动), 隐藏
            if (dyConsumed >= child.getHeight() / 5 && child.getAlpha() != 0f) {
                hideChild(child);
            }
        } else if (dyConsumed < 0) {
            // 手势从上向下滑动(列表往上滚动), 显示
            if (child.getAlpha() != 1.0f) {
                showChild(child);
            }
        }
    }

    /**
     * 隐藏Child
     */
    private void hideChild(final View child) {
        ObjectAnimator.ofFloat(child, "alpha", child.getAlpha(), 0f).setDuration(500).start();
        ObjectAnimator.ofFloat(child, "translationY", child.getTranslationY(), -child.getHeight()).setDuration(500).start();
    }

    /**
     * 显示Child
     */
    private void showChild(final View child) {
        ObjectAnimator.ofFloat(child, "alpha", child.getAlpha(), 1f).setDuration(500).start();
        ObjectAnimator.ofFloat(child, "translationY", child.getTranslationY(), 0).setDuration(500).start();
    }

    /**
     * 滑动结束后调用
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        Log.i("ScrollBehavior:", "onStopNestedScroll");
        super.onStopNestedScroll(coordinatorLayout, child, target);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值