CoordinatorLayout,是Material风格的重要组件, 作为布局的顶层控件,协调(Coordinate)其他组件, 实现联动。在我们新建Activity的时候,可以通过ScrollingActivity模板,创建一个标准的CoordinatorLayout布局的Activity,相信大家一定见识过,网上介绍CoordinatorLayout使用方法的文章也有很多,这里我就不再说了。今天想介绍的是CoordinatorLayout的高级用法,自定义Behavior。
还记得那一串字符串吗?
app:layout_behavior="@string/appbar_scrolling_view_behavior"
其实它并不是一个字符串资源,而是代表了一个类,就是一个Behavior。
认识Behavior
Behavior是CoordinatorLayout的一个抽象内部类
public abstract static class Behavior<V extends View> {
public Behavior() {
}
public Behavior(Context context, AttributeSet attrs) {
}
}
有一个泛型是指定的我们应用这个Behavior的View的类型,例如上面的appbar_scrolling_view_behavior对应的字符串其实是Android.support.design.widget.AppBarLayout$ScrollingViewBehavior。
在Behavior中有两个概念,dependency和child,很好理解,dependency在这里指的是被依赖的View,child是指需要依赖的View,也就是说,child依赖于dependency,当dependency做出变化时,child可以做出相应的反应。
如何自定义
public class MyBehavior extends CoordinatorLayout.Behavior<View>{
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
一定要重写这个构造函数。因为CoordinatorLayout源码中parseBehavior()函数中直接反射调用这个构造函数。可以看到这里指定了一个泛型,这个泛型其实指定的就是child的泛型,因此,如果没有特殊需求,直接指定view为View就行了。
在任意View中添加:
app:layout_behavior=“完整类名”然后CoordinatorLayout就会反射生成你的Behavior。
另外一种方法如果你的自定义View默认使用一个Behavior。
在你的自定义View类上添加@DefaultBehavior(你的Behavior.class)这句注解。
你的View就默认使用这个Behavior。就像AppBarLayout一样。
@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
生成Behavior后第一件事就是确定依赖关系。重写Behavior的这个方法来确定你依赖哪个View。
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == R.id.first;
我们可以按照两种目的来实现自己的Behavior,当然也可以两种都实现
1.child监听dependency的状态变化,例如大小、位置、显示状态等
2.child监听实现了NestedScrollingChild的接口的dependency的滑动状态
第一种情况需要重写layoutDependsOn和onDependentViewChanged方法
第二种情况需要重写onStartNestedScroll和onNestedPreScroll系列方法
先来说第一种情况:
很简单,顶部、底部分别有一个导航栏,右下角一个FloatingActionButton,底部导航栏和FloatingActionButton随着顶部导航栏的变化而变化。
XML文件
<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.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="0dp">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="我是TopBar"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
app:behavior_overlapTop="30dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="8dp"
app:contentPadding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="8dp"
android:text="@string/content"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="8dp"
app:contentPadding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="8dp"
android:text="@string/content"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="8dp"
app:contentPadding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="8dp"
android:text="@string/content"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="我是BottomBar"
app:layout_behavior=".BottomBehavior" />
<android.support.design.widget.FloatingActionButton
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="bottom|right"
android:layout_marginBottom="70dp"
android:layout_marginRight="20dp"
android:src="@mipmap/ic_launcher_round"
app:backgroundTint="#ffffff"
app:elevation="5dp"
app:layout_behavior=".FloatingBehavior" />
</android.support.design.widget.CoordinatorLayout>
分别给FloatingActionButton和底部导航栏设置了
app:layout_behavior=”.FloatingBehavior”和app:layout_behavior=”.BottomBehavior”
AppBarLayout的滑动隐藏显示就不说了
下面来看自定义Behavior的实现
BottomHehavior
public class BottomBehavior extends CoordinatorLayout.Behavior<View> {
public BottomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//设置依赖View为AppBarLayout(如果dependency的类型在布局文件中有多个,就不能这样写,而是通过id来判断)
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float scale = Math.abs(dependency.getY()) / dependency.getHeight();
child.setTranslationY(child.getHeight() * scale );
return true;
}
}
实现也很简单,在onDependentViewChanged中根据dependency.getY的变化改变child的TranslationY。FloatingBehavior和这个一样,通过scale 这个值来设置child的scaleX和scaleY即可。
滑动的情况,就留到下次再讲,嗯,就这样。
ps:自定义Behavior是可以使用自定义属性的,就和自定义控件中的用法一样。