CoordinatorLayout中Behavior介绍与简单使用

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/litengit/article/details/53007276

​ 我们之前简单介绍了AppBarLayout,CollapsingToolbarLayout的使用,他们的都是作为CoordinatorLayout的子View 使用的。如果没有CoordinatorLayout作为父View,它们是没有任何效果的。今天我们介绍一下CoordinatorLayout中的Behavior。在CoorindatorLayout中其实没有做太多的事情,它就是一个ViewGroup,类似FrameLayout;它的核心就在于Behavior类。我们给CoordinatorLayout的子View设置一个Behavior,就可以接管该View自身的一些事件与其他的View之间的交互事件。包括 touch,measure,layout等事件。

​ 我们使用的AppBarLayout有属于自己的Behavior,它是在AppBarLayout中声明的内部类,有兴趣的去看看AppBarLayout的代码就很容易发现。

简单定义Behavior

​ 我们没有办法直接使用Behavior,需要我们自己定义自己的Behavior。如何定义自己的内部类呢?只需要继承Behavior就OK了。就像下面这样:

public class MyBehavior extends CoordinatorLayout.Behavior<View> {
    //用来在代码里实例化Behavior的构造
    public MyBehavior() {
    }

    //如果要在布局中使用Behavior,这个构造是必须的
    public MyBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

​ 这里有个注意地方,Behavior只能作用在View上的,所以泛型上用的是View 。我们都知道View中 用setTag,getTag用来保存临时数据,也有onSaveInstanceState,onRestoreInstanceState来保存相关实例的状态,Behavior也有,用法跟View相当类似,这里不介绍了,一般用不到。

使用自定义的Behavior

​ 第一种方式:我们自定义的可以通过在布局中使用app:layout_behavior的来指定,例如,我们在NestedScrollView设定的Android提供的Behavior,app:layout_behavior="@string/appbar_scrolling_view_behavior">,其中的string是Behavior的绝对路径。

​ 第二种方式:如果你要用到自定义View/ViewGroup中,可以通过@CoordinatorLayout.DefaultBehavior()注解指定Behavior;这样我们的View就会默认使用这个Behavior。比如在AppBarLayout的使用方式:

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
    //省略具体实现代码
}

​ 第三种方式:通过代码设定Behavior,前提是你的指定的View必须是CoordinatorLayout的子View,如下:

MyBehavior myBehavior = new MyBehavior();
//我们的View必须是CoordinatorLayout的子View,否则我们获取不到CoordinatorLayout.LayoutParams
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) 我们的View.getLayoutParams();
params.setBehavior(myBehavior);

Behavior中的依赖

​ Behavior可以设置使用Behavior的View依赖的View。比如我们的View A 使用了我们自定义的Behavior MyBehavior,我们可以通过使用MyBehavior设定我们的 View A依赖于某个View B,这样我们可以获取到View B的各种状态,根据View B的状态去设定我们的View A 。

​ 我们通过 覆写BehaviorlayoutDependsOn去指定依赖的View。例如像这样:

/**
 * 指定依赖的View,在这里指定依赖的View之后,
 * 我们去{@linkplain #onDependentViewChanged(CoordinatorLayout, View, View)}里去处理这两个依赖的View之间的关系
 *
 * @param parent
 * @param child      使用该Behavior的View
 * @param dependency 依赖的View
 * @return 当指定的View是我们需要的View时,返回true
 */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency instanceof AppBarLayout;
}

也可以像这样指定某个View,通过id去验证是不是我们依赖的View :

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency.getId() == R.id.dependency_view_id;
}

​ 指定完依赖的View 之后,我们去覆写onDependentViewChanged方法去获取我们依赖的View的各个状态,并且设置我们自己的View状态,例如:

/**
 * 当我们指定依赖的View 有变化时,调用这个方法
 * @param parent
 * @param child 使用该Behavior的View
 * @param dependency 依赖的View
 * @return
 */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    final View finalChild = child;
    AppBarLayout finalDependency = (AppBarLayout) dependency;
    finalDependency.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            float tranlationY = Math.abs(verticalOffset / 3);
            finalChild.setTranslationY(tranlationY);
        }
    });
    return true;
}

这里我利用了 AppBarLayout CollapsingToolbarLayout 的进一步使用中的示例,如果兴趣的同学可以看一下。那么整个Behavior就是如下这样了:

public class MyBehavior extends CoordinatorLayout.Behavior<View> {

    //用来在代码里实例化我们自定义的Behavior的构造
    public MyBehavior() {
    }

    //如果要在布局中使用Behavior,这个构造是必须的
    public MyBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 指定依赖的View,在这里指定依赖的View之后,
     * 我们去{@linkplain #onDependentViewChanged(CoordinatorLayout, View, View)}里去处理这两个依赖的View之间的关系
     *
     * @param parent
     * @param child      使用该Behavior的View
     * @param dependency 依赖的View
     * @return 当指定的View是我们需要的View时,返回true
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    /**
     * 当我们指定依赖的View 有变化时,调用这个方法
     * @param parent
     * @param child 使用该Behavior的View
     * @param dependency 依赖的View
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        final View finalChild = child;
        AppBarLayout finalDependency = (AppBarLayout) dependency;
        finalDependency.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                float tranlationY = Math.abs(verticalOffset / 3);
                finalChild.setTranslationY(tranlationY);
            }
        });
        return true;
    }
}

我们设定了依赖的View 是AppBarLayout,我们监听AppBarLayout的上下折叠的偏移,根据它的偏移来设置我们的View的状态。效果如下:

我们可以看到我们的下面粉红色的View,会随着我们的AppBarLayout的折叠展开会滑出滑进屏幕。根据这个效果结合我们AppBarLayout的app:layout_scrollFlags 就可以做出类似知乎首页的那种功能了。

监听滑动

​ 如果我们需要监听一下CoordinatorLayout中子View的滑动,而不使用依赖View,那么怎么办呢?

要处理这个问题我们至少需要了解两个方法onStartNestedScroll,onNestedPreScroll这两个方法:

/**
 * 这里返回true,才会接收到后续滑动事件
 *
 * @param coordinatorLayout
 * @param child             使用该Behavior的View
 * @param directTargetChild Coordinator的子View,它可能包含一些滑动的操作
 * @param target            初始化滑动动作的View
 * @param nestedScrollAxes  滑动的轴,根据这个值来判断是纵向滑动还是横向滑动;
 *                          {@linkplain ViewCompat#SCROLL_AXIS_VERTICAL} 竖向;
 *                          {@linkplain ViewCompat#SCROLL_AXIS_HORIZONTAL} 横向
 * @return
 */
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
    return  super.onStartNestedScroll(coordinatorLayout,child,directTargetChild,target,nestedScrollAxes);
}

/**
 * 这里可以获取到CoordinatorLayout的滑动
 *
 * @param coordinatorLayout
 * @param child             使用该Behavior的View
 * @param target            执行滑动动作的View
 * @param dx                横向滑动的距离,以像素为单位
 * @param dy                纵向滑动的距离,以像素为单位
 * @param consumed
 */
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}

如果现在我们有一个Button想让它随着一个ScrollView 的滑动而滑动,怎么办呢?通过上面的两个方法就可以实现。我们自己定义一个ScrollBehavior,覆写上面的方法就OK了:

public class ScrollBehavior extends CoordinatorLayout.Behavior<View>{

    public ScrollBehavior() {
    }

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

    /**
     * 这里返回true,才会接收到后续滑动事件
     *
     * @param coordinatorLayout
     * @param child             使用该Behavior的View
     * @param directTargetChild Coordinator的子View,它可能包含一些滑动的操作
     * @param target            初始化滑动动作的View
     * @param nestedScrollAxes  滑动的轴,根据这个值来判断是纵向滑动还是横向滑动;
     *                          {@linkplain ViewCompat#SCROLL_AXIS_VERTICAL} 竖向;
     *                          {@linkplain ViewCompat#SCROLL_AXIS_HORIZONTAL} 横向
     * @return
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    /**
     * 这里可以获取到CoordinatorLayout的滑动
     *
     * @param coordinatorLayout
     * @param child             使用该Behavior的View
     * @param target            执行滑动动作的View
     * @param dx                横向滑动的距离,以像素为单位
     * @param dy                纵向滑动的距离,以像素为单位
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        final View finalChild = child;
        if (target instanceof NestedScrollView){
            NestedScrollView view = (NestedScrollView) target;
            view.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                @Override
                public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    finalChild.setTranslationY(scrollY/5);
                }
            });
        }
    }
}

这里我们在onNestedPreScroll 里对我们的ScrollView进行的了滑动监听,这样就避免去拦截它的onNestedFling了,onNestedPreScroll中的 target 表示的是CoordinatorLayout子View中执行滑动操作的View。

具体效果如下:

其实还有其它的几个方法还是比较重要的,比如:onNestedFlingonNestedPreFling,等等,有兴趣的朋友可以去研究一下。

展开阅读全文

没有更多推荐了,返回首页