您的位置:首页 > 移动开发

UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用

2017-08-22 16:42 525 查看

1.CoordinatorLayout 是什么

CoordinatorLayout 是一种功能更强大的FrameLayout

主要用于:

1.作为window的顶层布局 decor

2.作为父容器调度协调子布局,通过设置子View的 Behavior来调度子View的布局实现手势影响布局的形式产生动画效果

3.Behavior是CoordinatorLayout中的一个抽象类,用来协助CoordinatorLayout的Child Views之间的交互,Library中提供了

AppbarLayout.Behavior\AppBarLayout.ScrollingViewBehavior\FloatingActionButton.Behavior\SwipeDismissBehavior等实现子类,也可以自行定义实现子类。

2.CoordinatorLayout的使用

引入依赖

compile 'com.android.support:design:22.2.1'


CoordinatorLayout与 FloatingActionButton合用

// 在布局中加入coordinatorlayout和FloatingActionButton的控件
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coor_main"
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.wendy.coordinatorlayout.MainActivity">

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:elevation="4dp"
android:src="@drawable/ic_action_accept"
app:fabSize="normal"/>
</android.support.design.widget.CoordinatorLayout>

//代码
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
: 滚动退出屏幕,最后折叠在顶端

//布局
<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout
android:id="@+id/coor_main"
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.wendy.coordinatorlayout.MainActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/appbar_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_main"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:titleTextColor="#ffffff"/>

<com.astuetz.PagerSlidingTabStrip
android:id="@+id/title_main"
android:layout_width="match_parent"
android:layout_height="48dip"
app:pstsDividerColor="#0e0e0e"
app:pstsDividerPadding="15dp"
app:pstsDividerWidth="1dp"
app:pstsIndicatorColor="#ffffff"
app:pstsIndicatorHeight="2dp"
app:pstsShouldExpand="true"
app:pstsTabBackground="@color/colorPrimaryLight"/>

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

<!--viewpager中必须设置layout_behavior属性-->
<android.support.v4.view.ViewPager
android:id="@+id/viewPager_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

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

布局要点: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"






CoordinatorLayout与AppBarLayout嵌套CollapsingToolbarLayout

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
折叠状态时设置状态栏的背景

//布局
<?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.wendy.coordinatorlayout.CollapsingActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/appbar_coll"
android:layout_width="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_height="wrap_content">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_coll"
android:layout_width="match_parent"
app:title="@string/app_name"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:layout_height="256dp">

<ImageView
android:id="@+id/image_coll"
app:layout_collapseMode="parallax"
android:layout_width="match_parent"
android:src="@drawable/grass"
android:scaleType="fitXY"
android:layout_height="match_parent"/>

<android.support.v7.widget.Toolbar
app:titleTextColor="@color/colorWhite"
android:id="@+id/toolbar_coll"
android:layout_width="match_parent"
app:layout_collapseMode="pin"
android:layout_height="?android:attr/actionBarSize"/>

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

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

<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll_coll"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_margin="10dp"
app:cardCornerRadius="4dp"
android:elevation="4dp"
android:layout_width="match_parent"
android:layout_height="100dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="CardView"
android:layout_margin="10dp"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="#000000"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="@string/appbar_scrolling_view_behavior"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textColor="#000000"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
app:fabSize="normal"
android:src="@drawable/ic_done_black_24dp"
android:layout_width="wrap_content"
app:layout_anchor="@id/appbar_coll"
app:layout_anchorGravity="end|bottom"
android:layout_marginRight="10dp"
android:layout_height="wrap_content"/>
</android.support.design.widget.CoordinatorLayout>
布局要点:
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"






3.关于behavior

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<V extends View> {

/**
* 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
*
102fd
@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<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
//通过反射来得到Behavior
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);
}
}


由此可以看出给Child Views设置Behavior有两种方法:

在xml中通过设置 app:layout_behavior=”.MyBehavior”

在Child Views控件定义源码中,使用@CoordinatorLayout.DefaultBehavior类注解,通过然后通过反射机制来得到Behaivor,系统的AppBarLayout、FloatingActionButton都采用了这种方式,所以无需在布局中重复设置。(这种方法需要自定义的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<View> {

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<View> 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中
<?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.wendy.coordinatorlayout.ThirdActivity">
<Button
android:id="@+id/bt_third"
android:text="弹出Snackbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageButton
android:src="@drawable/icon3"
android:layout_width="80dp"
android:scaleType="centerCrop"
android:background="@drawable/icon3"
android:layout_gravity="bottom|right"
android:layout_margin="10dp"
android:layout_height="80dp"
app:layout_behavior="com.example.wendy.coordinatorlayout.MyCustomBehavior"//将自定义的behavior应用上>

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




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布局中

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coor_main"
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.wendy.coordinatorlayouttest.MainActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/abl_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
android:id="@+id/tb_main"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>

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

<android.support.v4.view.ViewPager
android:id="@+id/vp_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

</android.support.v4.view.ViewPager>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:background="@color/colorPrimaryDark"
android:padding="10dp"
android:weightSum="5"

app:layout_behavior=".MySearchBehavior">//将自定义的Behavior设置给子布局

<EditText
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:background="#ffffff"
android:hint="Please input zhe Comment"
android:textSize="16sp"/>

<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:background="#ffffff"
android:text="Ok"/>

</LinearLayout>

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




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布局中

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coor_main"
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.wendy.coordinatorlayouttest.MainActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/abl_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
android:id="@+id/tb_main"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize">
</android.support.v7.widget.Toolbar>

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

<android.support.v4.view.ViewPager
android:id="@+id/vp_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

</android.support.v4.view.ViewPager>

<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="20dp"
android:src="@android:drawable/ic_media_play"
app:backgroundTint="@color/colorAccent"
app:fabSize="mini"
app:layout_behavior=".MyCustomBehavior"/>  ;//将自定义的Behavior设置给CoordinatorLayout的直接child view

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


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐