Android CoordinatorLayout使用總結(jié)

設(shè)計支持庫

設(shè)計軟件包提供的 API 支持向應(yīng)用中添加 Material Design 組件和模式磺芭。設(shè)計支持庫添加了對應(yīng)用開發(fā)者依賴的各種 Material Design 組件和模式的支持,例如抽屜式導航欄析苫、浮動操作按鈕 (FAB)、快捷信息欄和標簽頁渺氧。

support design 官方地址

使用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:

  1. As a top-level application decor or chrome layout
  2. As a container for a specific interaction with one or more child views
  1. 用作頂層布局
  2. 作為一個容器與一個或者多個子View進行交互

參考:使用CoordinatorLayout打造各種炫酷的效果
android-[譯]掌握CoordinatorLayout

Behavior

CoordinatorLayout中子view進行交互就是通過Behavior來完成的。
指定Behavior有三種方式:

  1. 通過構(gòu)造方法實例,并在java代碼中設(shè)置到LayoutParamas里
android.support.design.widget.CoordinatorLayout.LayoutParams#setBehavior
  1. 在layout xml里指定:
<View
        xmlns:app="http://schemas.android.com/apk/res-auto"
        ...
        app:layout_behavior="com.xujun.contralayout.behavior.NoNestedBehavior"/>
  1. 通過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的幾個值
  1. scroll:子View 添加layout_scrollFlags屬性 的值scroll 時摩桶,這個View將會隨著可滾動View(如:ScrollView,以下都會用ScrollView 來代替可滾動的View )一起滾動,就好像子View 是屬于ScrollView的一部分一樣帽揪。
  2. enterAlways:子View 添加layout_scrollFlags屬性 的值有enterAlways 時, 當ScrollView 向下滑動時硝清,子View 將直接向下滑動,而不管ScrollView 是否在滑動转晰。注意:要與scroll 搭配使用芦拿,否者是不能滑動的。
  3. enterAlwaysCollapsed:enterAlwaysCollapsed 是對enterAlways 的補充查邢,當ScrollView 向下滑動的時候蔗崎,滑動View(也就是設(shè)置了enterAlwaysCollapsed 的View)下滑至折疊的高度,當ScrollView 到達滑動范圍的結(jié)束值的時候扰藕,滑動View剩下的部分開始滑動缓苛。這個折疊的高度是通過View的minimum height (最小高度)指定的。
  4. exitUntilCollapsed:當ScrollView 滑出屏幕時(也就時向上滑動時)邓深,滑動View先響應(yīng)滑動事件未桥,滑動至折疊高度,也就是通過minimum height 設(shè)置的最小高度后芥备,就固定不動了冬耿,再把滑動事件交給 scrollview 繼續(xù)滑動
  5. 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等將不能正常使用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坛吁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铐尚,更是在濱河造成了極大的恐慌拨脉,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣增,死亡現(xiàn)場離奇詭異玫膀,居然都是意外死亡,警方通過查閱死者的電腦和手機爹脾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門帖旨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灵妨,你說我怎么就攤上這事解阅。” “怎么了泌霍?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵瓮钥,是天一觀的道長。 經(jīng)常有香客問我烹吵,道長,這世上最難降的妖魔是什么桨武? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任肋拔,我火速辦了婚禮,結(jié)果婚禮上呀酸,老公的妹妹穿的比我還像新娘凉蜂。我一直安慰自己,他們只是感情好性誉,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布窿吩。 她就那樣靜靜地躺著,像睡著了一般错览。 火紅的嫁衣襯著肌膚如雪纫雁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天倾哺,我揣著相機與錄音轧邪,去河邊找鬼刽脖。 笑死,一個胖子當著我的面吹牛忌愚,可吹牛的內(nèi)容都是我干的曲管。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼硕糊,長吁一口氣:“原來是場噩夢啊……” “哼院水!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起简十,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤檬某,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后勺远,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體橙喘,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年胶逢,在試婚紗的時候發(fā)現(xiàn)自己被綠了厅瞎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡初坠,死狀恐怖和簸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碟刺,我是刑警寧澤锁保,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站半沽,受9級特大地震影響爽柒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜者填,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一浩村、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧占哟,春花似錦心墅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜜暑,卻和暖如春铐姚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背史煎。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工谦屑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驳糯,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓氢橙,卻偏偏與公主長得像酝枢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悍手,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容