UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用
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"
展示效果:彈出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
* @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>