CoordinatorLayout 是一种功能更强大的FrameLayout
主要用于:
1.作为window的顶层布局 decor
2.作为父容器调度协调子布局,通过设置子View的 Behavior来调度子View的布局实现手势影响布局的形式产生动画效果
3.Behavior是CoordinatorLayout中的一个抽象类,用来协助CoordinatorLayout的Child Views之间的交互,Library中提供了
AppbarLayout.Behavior\AppBarLayout.ScrollingViewBehavior\FloatingActionButton.Behavior\SwipeDismissBehavior等实现子类,也可以自行定义实现子类。
引入依赖
compile 'com.android.support:design:22.2.1'
CoordinatorLayout与 FloatingActionButton合用
// 在布局中加入coordinatorlayout和FloatingActionButton的控件
//代码 public class MainActivity extends AppCompatActivity { private FloatingActionButton fab; private CoordinatorLayout coor_layout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { fab = findViewById(R.id.fab_main); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //Snackbar 和toast的用法相似,snackbar是从屏幕底滑出,通过setAction()方法可能给snackbar添加一个按钮 Snackbar.make(view, "hello, I am smackbar!", Snackbar.LENGTH_LONG).setAction("UNDO", new View.OnClickListener() { @Override public void onClick(View view) { //点击SnackBar上胡按钮后需要做的操作 fab.setImageResource(R.drawable.ic_action_cancel); } }).show(); } }); } }
展示效果:弹出SnackBar 时,FloatingActionButton布局会向上移动
FloatingActionButton是默认使用FloatingActionButton.Behavior
Coordinatorlayout与AppbarLayout合用
AppBarLayout :
继承自LinearLayout,子控件默认为竖直方向显示,可以用它实现Material Design 的Toolbar;它支持滑动手势;它的子控件可以通过在代码里调用setScrollFlags(int)或者在XML里app:layout_scrollFlags来设置它的滑动手势。当然实现这些的前提是它的根布局必须是 CoordinatorLayout。这里的滑动手势可以理解为:当某个可滚动View的滚动手势发生变化时,AppBarLayout内部的子View实现某种动作。AppBarLayout的子控件不仅仅可以设置为Toolbar,也可以包含其他的View,AppBarLayout必须作为Toolbar的父布局容器。app:layout_scrollFlags="[scroll | enterAlways |
enterAlwaysCollapsed | exitUntilCollapsed]"
scroll
:可以滚动出屏幕的Flag,如没有设置则view会停留在屏幕顶部enterAlways
:任意向下滚动都会使该view变为可见,启用快速”返回模式”。enterAlwaysCollapsed
: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。exitUntilCollapsed
: 滚动退出屏幕,最后折叠在顶端
//布局
<!--viewpager中必须设置layout_behavior属性-->
布局要点:1.CoordinatorLayout必须是整个布局的父布局.
2.CoordinatorLayout需要有两个直接的子view(AppbarLayout,和一个能滚动的view)
3.需要滚动出屏幕的view必须在AppbarLayout布局中,且设置属性app:layout_scrollFlag="scroll|enteralways"
4.CoordinatorLayout主布局中要有一个能够滚动的view,可以是NestedScrollView或者RecyclerView(或
者是在viewPager里面直接内嵌一个RecyclerView,貌似不能通过fragment来嵌套recyclerView),能滚
动的view必须设置属性 app:layout_behavior="@string/appbar_scrolling_view_behavior"
CollapsingToolbarLayout:
————————————继承于FragmentLayout,主要作用是提供一个可以折叠的Toolbar,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。
特殊xml属性:
app: titleEnabled
true则title会跟着放大折叠,默认为true
app:title
标题,当titleEnabled为true时会跟着缩放
app:toolbarId
用于折叠展开的toolbar的id
app:collapsedTitleGravity
折叠状态时title的位置
app:collapsedTitleTextAppearance
折叠状态时标题文字的style
app:contentScrim
折叠状态时toolbar的背景
app:expandedTitleGravity
展开状态时标题的位置
app:expandedTitleMargin
展开状态时的标题边距
app:expandedTitleTextAppearance
展开状态时标题文字的style
app:scrimAnimationDuration
app:scrimVisibleHeightTrigger
当处于什么高度时出现设置的背景
app:statusbarScrim
折叠状态时设置状态栏的背景
//布局
布局要点:
1.CoordinatorLayout必须是全局父容器,且必须有一个AppLayout和一个可滑动的view作为其直接子view,可滑动的子
view必须设置app:layout_behavior="@string/appbar_scrolling_view_behavior";来告诉
coordinatorLayout什么时候触发滚动以及behavior类型
2.CollapsingToolbarLayout必须是AppLayout的直接子view且必须设置滑动标志
app:layout_scrollFlags="scroll|exitUntilCollapsed"
3.CollapsingToolbarLayout的子view Toolbar和imageView必须设置折叠的方式app:collapseMode="[pin|parallax]";
parallax:视图将会随着滚动一起伸缩;pin:固定在顶部,不随着一起变化大小
4.CoordinatorLayout 还提供了一个 layout_anchor 的属性,连同 layout_anchorGravity 一起,可以用来放置
与其他视图关联在一起的悬浮视图(如 FloatingActionButton)app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|right|end"
CoordinatorLayout通过behavior来控制子view。前面写到FloatingActionButton.Behavior,AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior。 AppBarLayout中有两个Behavior,一个是拿来给它自己用的,另一个是拿来给同级别的可滑动view用的。
Q:为什么有些CoordinatorLayout的child Views 需要在xml中设置layout_behavior,有些不用设置如(FloatingActionBar) |
Behavior有两个构造方法
public static abstract class Behavior {
/**
* Default constructor for instantiating Behaviors.
*/
public Behavior() {
}
/**
* Default constructor for inflating Behaviors from layout. The Behavior will have
* the opportunity to parse specially defined layout parameters. These parameters will
* appear on the child view tag.
*
* @param context
* @param attrs
*/
public Behavior(Context context, AttributeSet attrs) {
}
在构造方法的注释可以知道,第二种有参构造法会解析布局的特殊构造属性,调用parseBehavior方法通过反射来获取对应的Behavior
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// 获得类的全称
fullName = context.getPackageName() + name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
? (WIDGET_PACKAGE_NAME + '.' + name)
: name;
}
try {
Map> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor c = constructors.get(fullName);
if (c == null) {
//通过反射来得到Behavior
final Class clazz = (Class) 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);
}
}
由此可以看出给Child Views设置Behavior有两种方法:
Q:如何自定义实现自己的Behavior |
自定义的Behavior可以分为两类:dependent机制和nested机制来对应不同的场景
dependent机制
这种机制描述的是两个Child Views之间的绑定依赖关系,设置Behavior属性的Child View跟随依赖对象Dependency View的大小位置改变而发生变化,对应需要实现的方法常见有两个:
//决定是否产生依赖行为,返回true则child布局依赖于dependency
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
//依赖的控件发生大小或者位置变化时产生回调
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
Demo: 依赖Snackbar的布局
public class MyCustomBehavior extends CoordinatorLayout.Behavior {
public MyCustomBehavior() {
}
public MyCustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = getViewbTranslationYFromSnackbar(parent, child);
child.setRotation(90 * translationY / dependency.getHeight());
child.setTranslationX(child.getWidth() * translationY / dependency.getHeight());
child.setTranslationY(translationY);
return false;
}
//计算child需要在Y方向上移动的距离
private float getViewTranslationYFromSnackbar(CoordinatorLayout parent, View child) {
float minOffset = 0f;
//通过CoordinatorLayout获得child滚动所依赖的Views
List list = parent.getDependencies(child);
for (int i = 0; i < list.size(); i++) {
View view = list.get(i);
//coordinatorLayout.doViewOverlap(child,view)判断两个view的位置是否重叠
if ((view instanceof Snackbar.SnackbarLayout) && parent.doViewsOverlap(child, view)) {
minOffset = Math.min(minOffset, view.getTranslationY() - view.getHeight());
}
}
return minOffset;
}
}
//在xml中
Demo 2: 依赖Toolbar的AppBarLayout
public class MySearchBehavior extends CoordinatorLayout.Behavior {
public MySearchBehavior() {
}
public MySearchBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float dy = dependency.getTop();
child.setTranslationY(-dy);
return true;
}
}
//在xml布局中
//将自定义的Behavior设置给子布局
nested机制
Nested机制要求CoordinatorLayout包含了一个实现了NestedScrollingChild接口的滚动视图控件(如RecyclerView),设置Behavior属性的Child Views会随着这个控件的滚动而发生布局变化,需要重写以下方法
//返回值为true时才能让自定义的Behavior接受滑动事件
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes, @NestedScrollType int type) {
}
//滚动视图控件滑动时调用,dyConsumed为Y轴上滑动的距离
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
}
Demo: nested滚动视图的滚动
public class MyCustomBehavior extends CoordinatorLayout.Behavior {
public MyCustomBehavior() {
}
public MyCustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull
View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//当滑动为竖直方向时返回true,接受滑动事件
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View
child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0){
child.animate().translationY(child.getHeight() * 4).setInterpolator(new AccelerateInterpolator(2)).start();//向下滑动时,将child向下移除屏幕
}else{
child.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start(); //向上滑动时,将child移动回原来的位置
}
}
}
;//在xml布局中
;//将自定义的Behavior设置给CoordinatorLayout的直接child view