設(shè)計支持庫
設(shè)計軟件包提供的 API 支持向應(yīng)用中添加 Material Design 組件和模式磺芭。設(shè)計支持庫添加了對應(yīng)用開發(fā)者依賴的各種 Material Design 組件和模式的支持,例如抽屜式導航欄析苫、浮動操作按鈕 (FAB)、快捷信息欄和標簽頁渺氧。
使用gradle引入:
compile 'com.android.support:design:25.2.0'
CoordinatorLayout
CoordinatorLayout可以做什么
CoordinatorLayout is a super-powered FrameLayout
CoordinatorLayout is intended for two primary use cases:
- As a top-level application decor or chrome layout
- As a container for a specific interaction with one or more child views
- 用作頂層布局
- 作為一個容器與一個或者多個子View進行交互
參考:使用CoordinatorLayout打造各種炫酷的效果
android-[譯]掌握CoordinatorLayout
Behavior
CoordinatorLayout中子view進行交互就是通過Behavior來完成的。
指定Behavior有三種方式:
- 通過構(gòu)造方法實例,并在java代碼中設(shè)置到LayoutParamas里
android.support.design.widget.CoordinatorLayout.LayoutParams#setBehavior
- 在layout xml里指定:
<View
xmlns:app="http://schemas.android.com/apk/res-auto"
...
app:layout_behavior="com.xujun.contralayout.behavior.NoNestedBehavior"/>
- 通過DefaultBehavior注解指定
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
...
}
Behavior幾個重要方法作用
/**
* Determine whether the supplied child view has another specific sibling view as a
* layout dependency.
*
* <p>This method will be called at least once in response to a layout request. If it
* returns true for a given child and dependency view pair, the parent CoordinatorLayout
* will:</p>
* <ol>
* <li>Always lay out this child after the dependent child is laid out, regardless
* of child order.</li>
* <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
* position changes.</li>
* </ol>
*
* @param parent the parent view of the given child
* @param child the child view to test
* @param dependency the proposed dependency of child
* @return true if child's layout depends on the proposed dependency's layout,
* false otherwise
*
* @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
*/
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
/**
* Respond to a change in a child's dependent view
*
* <p>This method is called whenever a dependent view changes in size or position outside
* of the standard layout flow. A Behavior may use this method to appropriately update
* the child view in response.</p>
*
* <p>A view's dependency is determined by
* {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
* if {@code child} has set another view as it's anchor.</p>
*
* <p>Note that if a Behavior changes the layout of a child via this method, it should
* also be able to reconstruct the correct position in
* {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}.
* <code>onDependentViewChanged</code> will not be called during normal layout since
* the layout of each child view will always happen in dependency order.</p>
*
* <p>If the Behavior changes the child view's size or position, it should return true.
* The default implementation returns false.</p>
*
* @param parent the parent view of the given child
* @param child the child view to manipulate
* @param dependency the dependent view that changed
* @return true if the Behavior changed the child view's size or position, false otherwise
*/
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)
layoutDependsOn方法用來確定依賴關(guān)系套媚,返回true表示依賴關(guān)系確定,即child依賴dependency磁椒。dependency對child是無感知的堤瘤,當dependency的大小或位置變化時,會回調(diào)所有依賴dependency的child對應(yīng)behavior的onDependentViewChanged方法浆熔,onDependentViewChanged返回true表示對應(yīng)child的位置或大小變化了本辐。
依賴關(guān)系會改變子View onMeasure onLayout的順序,被依賴的dependency在之前執(zhí)行医增。
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
這兩個方法分別在CoordinatorLayout中的onInterceptTouchEvent慎皱、onTouchEvent中被調(diào)用。只要有一個behavior的onInterceptTouchEvent返回true時叶骨,所有子View的touch事件都會被攔截茫多。
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed)
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
這兩個方法分別在CoordinatorLayout中的onMeasure、onLayout中調(diào)用邓萨,當返回true時地梨,表示測量、布局完成缔恳,則該child默認的onMeasure、onLayout不會執(zhí)行洁闰,被攔截了歉甚。
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes)
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
View directTargetChild, View target, int nestedScrollAxes)
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dx, int dy, int[] consumed)
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY, boolean consumed)
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY)
CoordinatorLayout實現(xiàn)了NestedScrollingParent接口,這幾個方法分別對應(yīng)NestedScrollingParent接口中的方法扑眉,當CoordinatorLayout中方法回調(diào)后纸泄,調(diào)用所有Behavior中對應(yīng)方法,主要用于嵌套滑動腰素,必須配合NestedScrollingChild使用聘裁。
參考:
sidhu眼中的CoordinatorLayout.Behavior(一)
sidhu眼中的CoordinatorLayout.Behavior(二)
sidhu眼中的CoordinatorLayout.Behavior(三)
案例分析
layout布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<xxx.xxx.PtrLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:ptr_ratio_of_header_height_to_refresh="1.0"
>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00000000">
<FrameLayout
android:id="@+id/frag_head_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/xxx"
app:layout_collapseMode="parallax"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/colorAccent"
app:tabIndicatorHeight="4dp"
app:tabSelectedTextColor="#000"
app:tabTextColor="#fff">
</android.support.design.widget.TabLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
</xxx.xxx.PtrLayout>
<LinearLayout
android:id="@+id/title_bar_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/status_bar_holder"
android:layout_width="match_parent"
android:layout_height="0dp" />
<xxx.xxx.TitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
CoordinatorLayout的直接子View兩個,分別是AppBarLayout弓千、ViewPager衡便,Behavior只能設(shè)置給CoordinatorLayout的直接子View,因為Behavior是android.support.design.widget.CoordinatorLayout.LayoutParams的一個屬性洋访。
AppBarLayout的Behavior是通過注解的方式指定的
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
...
}
ViewPager的Behavior是在xml布局中指定的镣陕,由app:layout_behavior指定為
android.support.design.widget.AppBarLayout.ScrollingViewBehavior
AppBarLayout.Behavior主要重寫了以下方法
//這兩個方法用來處理觸摸在AppBarLayout上的手勢,使AppBarLayout進行上下滑動
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
//這幾個方法是用來處理手勢不在AppBarLayout上時姻政,比如在ViewPager中的RecycleView上滑動時呆抑,使AppBarLayout響應(yīng)嵌套滾動事件
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes)
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed)
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed)
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl,
View target)
public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
final AppBarLayout child, View target, float velocityX, float velocityY,
boolean consumed)
AppBarLayout.ScrollingViewBehavior主要重寫的方法
//這兩個方法主要作用是讓child(即ViewPager)一直跟隨在AppBarLayout在下面
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency){
return dependency instanceof AppBarLayout;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency){
offsetChildAsNeeded(parent, child, dependency);
return false;
}
//用來確定child(即ViewPager)的高度
public boolean onMeasureChild(CoordinatorLayout parent, View child,
int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
int heightUsed){
...
//header即AppBarLayout
final View header = findFirstDependency(dependencies);
...
int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
...
//getScrollRange(header)是指AppBarLayout可滑動的高度
final int height = availableHeight - header.getMeasuredHeight()
+ getScrollRange(header);
}
可以看到ScrollingViewBehavior并沒有重寫任何的滑動事件,如果ViewPager里沒有可滑動的控件汁展,則手勢在ViewPager上時將不可以上下滑動鹊碍,如果可滑動的是ListView這種沒有實現(xiàn)NestedScrollingChild接口的View厌殉,則AppBarLayout將也不能跟著滑動,只有實現(xiàn)NestedScrollingChild時侈咕,AppBarLayout才能配合實現(xiàn)嵌套滾動公罕。
AppBarLayout要可滾動,需要設(shè)置幾個參數(shù)乎完,可滾動的子View必須要設(shè)置下面這個屬性熏兄,并且第一個直接子View一定要有這個屬性
app:layout_scrollFlags="scroll"
加上exitUntilCollapsed并且設(shè)置minHeight可以減少可滾動高度,不設(shè)置默認是整個子View的高度树姨。
layout_scrollFlags的幾個值
- scroll:子View 添加layout_scrollFlags屬性 的值scroll 時摩桶,這個View將會隨著可滾動View(如:ScrollView,以下都會用ScrollView 來代替可滾動的View )一起滾動,就好像子View 是屬于ScrollView的一部分一樣帽揪。
- enterAlways:子View 添加layout_scrollFlags屬性 的值有enterAlways 時, 當ScrollView 向下滑動時硝清,子View 將直接向下滑動,而不管ScrollView 是否在滑動转晰。注意:要與scroll 搭配使用芦拿,否者是不能滑動的。
- enterAlwaysCollapsed:enterAlwaysCollapsed 是對enterAlways 的補充查邢,當ScrollView 向下滑動的時候蔗崎,滑動View(也就是設(shè)置了enterAlwaysCollapsed 的View)下滑至折疊的高度,當ScrollView 到達滑動范圍的結(jié)束值的時候扰藕,滑動View剩下的部分開始滑動缓苛。這個折疊的高度是通過View的minimum height (最小高度)指定的。
- exitUntilCollapsed:當ScrollView 滑出屏幕時(也就時向上滑動時)邓深,滑動View先響應(yīng)滑動事件未桥,滑動至折疊高度,也就是通過minimum height 設(shè)置的最小高度后芥备,就固定不動了冬耿,再把滑動事件交給 scrollview 繼續(xù)滑動
- snap:在滾動結(jié)束后,如果view只是部分可見萌壳,它將滑動到最近的邊界亦镶。
AppBarLayout參考:
Material Design之 AppbarLayout 開發(fā)實踐總結(jié)
一個bug記錄
AppBarLayout遮擋布局在其上面View問題:
解決1、setCollapsedState設(shè)置為true就會遮擋讶凉,設(shè)置為false則不會
解決2染乌、mAppBarLayout.setStateListAnimator(null);
解決3、設(shè)置要顯示在前面View的elevation大于appbarlayout的值(4dp)懂讯。
原因:design_appbar_state_list_animator.xml中在狀態(tài)state_collapsed為true時設(shè)elevation為4dp了
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:state_enabled="true" app:state_collapsed="false" app:state_collapsible="true">
<objectAnimator android:duration="@integer/app_bar_elevation_anim_duration"
android:propertyName="elevation"
android:valueTo="0dp"
android:valueType="floatType"/>
</item>
<item android:state_enabled="true">
<objectAnimator android:duration="@integer/app_bar_elevation_anim_duration"
android:propertyName="elevation"
android:valueTo="@dimen/design_appbar_elevation"
android:valueType="floatType"/>
</item>
<item>
<objectAnimator android:duration="0"
android:propertyName="elevation"
android:valueTo="0"
android:valueType="floatType"/>
</item>
</selector>
總結(jié)
CoordinatorLayout通過對子View設(shè)置Behavior來完成各種復(fù)雜交互荷憋,Behavior可以攔截touch事件、測量褐望、布局勒庄、嵌套滾動串前、指定依賴及響應(yīng)依賴位置大小等變化。
優(yōu)點:
- 對View無侵入式实蔽,用組合替代繼承來攔截一切荡碾。
缺點:
- 嵌套滾動View必須實現(xiàn)NestedScrollingChild接口,ListView局装、ScrollView等將不能正常使用