CoordinatorLayout補齊資料篇

CoordinatorLayout補齊資料篇

前言

CoordinatorLayout 是在 Google I/O 2015 提出來的 Material Design 組件,一併發佈的有 AppbarLayout ,  CollapsingToolbarLayout  NestedScrollView
 等一系列靈活處理動態交互的 Materail Design 控件。
很早就想對 Coordinator  做一次全面的分析,因爲懶,一直都沒動手,以至於這一篇拖了將近兩年纔開始寫。

組件

工欲善其事,必先利其器。先從官網入手,熟悉一下這一控件的大致功能。

CoordinatorLayout

官網介紹:

這裏寫圖片描述
大致意思就是:

CoordinatorLayout 是一個加強版的 FrameLayout。

主要有兩個用途:

  1. 作爲頂層應用的裝飾或者 chrome 佈局
  2. 作爲容器來協調一個或多個子 view 的特定交互

爲子View指定 Behaviors 可以實現子 view 與父 view 或者是子 view 相互間的交互。

CoordinatorLayout 的子 view 還可以通過 anchor 來制定位置基準對象。

系統給一些控件提供了默認的 Behavior,處理了相關的聯動動態效果,我們也可以自定義 Behavior,註解 DefaultBehavior。關於如何自定義 Behavior,在後面我會着重提到。

AppBarLayout

再來看看官網對AppBarLayout的介紹:

這裏寫圖片描述

大致意思就是:

AppBarLayout是一個豎直的 LinearLayout,而且實現了很多 Material Design 特性並且能夠響應滑動事件。

  1. AppBarLayout 最好是 CoordinatorLayout 的直接子 view,如果是在其他 ViewGroup 中,可能會喪失很多特效。

  2. AppBarLayout 的子 view 需要設置 app:layout_scrollFlags,或者是在代碼中調用 setScrollFlags(int) 設置這個屬性。

  3. AppBarLayout的兄弟節點最好是可以滾動的,需要指定 behavior 屬性爲 AppBarLayout.ScrollingViewBehavior (可以使用一個內置的 string 表示這個默認的實例@string/appbar_scrolling_view_behavior)。

看來 AppBarLayout 內容比較多啊,來看看 Google 給出的講解性僞代碼。

這裏寫圖片描述

好了,一下子都看清楚了。

CollapsingToolbarLayout

還是來看官方 Api:

這裏寫圖片描述

意思是:

CollapsingToolbarLayout 是實現了摺疊工具欄效果 Toolbar 的包裝器,被作爲 AppBarLayout 的直接子 View。

  1. 摺疊標題 CollapsingTitle:

當佈局完全顯示是,標題會變大;當佈局上滑滑出屏幕,標題會逐漸變小。可以通過 setTitle()來設置標題,通過 collapsedTextAppearance() 和 expandedTextAppearance() 方法來調整外觀屬性。

  1. 沉浸式內容 Content scrim

ToolBar被摺疊到頂部固定時候的背景,你可以調用setContentScrim(Drawable)方法改變背景或者 在屬性中使用 app:contentScrim=”?attr/colorPrimary” 來改變背景。

  1. 沉浸式狀態欄 Status bar scrim

當滾動到一定值時顯示或隱藏狀態欄沉浸式效果,通過 setStatusBarScrim() 方法來設置,僅在 Android5.0 以上並且設置了適應窗口 (fitsSystemWindows=”true”) 時有用。

  1. 視差滾動 Parallax scrolling children

CollapsingToolbarLayout 可以選擇視差滾動的方式來滾動,滾動方式有 parallax 和 pin。設置屬性 layout_collapseParallaxMultiplier 改變,視差係數在 0-1 之間。

  1. 頂部懸浮 CollapseMode

子試圖的摺疊模式,parallax 和 pin。

pin:固定模式,滑動時也固定大小,直到定位在最終位置

parallax:視差模式,在摺疊的時候有視差摺疊效果,滑動時伸縮

Behavior

CoordinatorLayout 協調子 View 的響應,響應的核心是 Behavior,來看一下介紹:

這裏寫圖片描述

一個 Behavior 實現了用戶的一個或者多個交互行爲,它們可能包括拖拽、滑動、快滑或者其他一些手勢。

Behavior 是一個頂層抽象類,其他的一些具體行爲的Behavior 都是繼承自這個類。它提供了幾個重要的方法:

  • layoutDependsOn
  • onDependentViewChanged
  • onStartNestedScroll
  • onNestedPreScroll
  • onNestedScroll
  • onStopNestedScroll
  • onNestedScrollAccepted
  • onNestedPreFling
  • onStartNestedScroll
  • onLayoutChild

解釋一下上面幾個方法和它們的調用時機:

    /**
     * 表示是否給應用了 Behavior 的 View 指定一個依賴的佈局,通常,當被依賴的 View (參照物)佈局發生變化時
     * CoordinatorLayout都會協調依賴的 View(跟隨物)立即變化,聯動效果不會被受 CoordinatorLayout 子 View 的順序影響。
     * @param parent
     * @param child 綁定behavior 的View
     * @param dependency   依賴的view
     * @return 如果child 是依賴的指定的View 返回true,否則返回false
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return super.layoutDependsOn(parent, child, dependency);
    }

    /**
     * 當被依賴的View 狀態(如:位置、大小)發生變化時,這個方法被調用
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        return super.onDependentViewChanged(parent, child, dependency);
    }

    /**
     *  當coordinatorLayout 的子View試圖開始嵌套滑動的時候被調用。當返回值爲true的時候表明
     *  coordinatorLayout 充當nested scroll parent 處理這次滑動,需要注意的是隻有當返回值爲true
     *  的時候,Behavior 才能收到後面的一些nested scroll 事件回調(如:onNestedPreScroll、onNestedScroll等)
     *  這個方法有個重要的參數nestedScrollAxes,表明處理的滑動的方向。
     *
     * @param coordinatorLayout 和Behavior 綁定的View的父CoordinatorLayout
     * @param child  和Behavior 綁定的View
     * @param directTargetChild
     * @param target
     * @param nestedScrollAxes 嵌套滑動 應用的滑動方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
     * @return
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    /**
     * 嵌套滾動發生之前被調用
     * 在nested scroll child 消費掉自己的滾動距離之前,嵌套滾動每次被nested scroll child
     * 更新都會調用onNestedPreScroll。注意有個重要的參數consumed,可以修改這個數組表示你消費
     * 了多少距離。假設用戶滑動了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,
     * 這樣coordinatorLayout就能知道只處理剩下的10px的滾動。
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dx  用戶水平方向的滾動距離
     * @param dy  用戶豎直方向的滾動距離
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }

    /**
     * 進行嵌套滾動時被調用
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dxConsumed target 已經消費的x方向的距離
     * @param dyConsumed target 已經消費的y方向的距離
     * @param dxUnconsumed x 方向剩下的滾動距離
     * @param dyUnconsumed y 方向剩下的滾動距離
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    /**
     *  嵌套滾動結束時被調用,這是一個清除滾動狀態等的好時機。
     * @param coordinatorLayout
     * @param child
     * @param target
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        super.onStopNestedScroll(coordinatorLayout, child, target);
    }

    /**
     * onStartNestedScroll返回true纔會觸發這個方法,接受滾動處理後回調,可以在這個
     * 方法裏做一些準備工作,如一些狀態的重置等。
     * @param coordinatorLayout
     * @param child
     * @param directTargetChild
     * @param target
     * @param nestedScrollAxes
     */
    @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    /**
     * 用戶鬆開手指並且會發生慣性動作之前調用,參數提供了速度信息,可以根據這些速度信息
     * 決定最終狀態,比如滾動Header,是讓Header處於展開狀態還是摺疊狀態。返回true 表
     * 示消費了fling.
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param velocityX x 方向的速度
     * @param velocityY y 方向的速度
     * @return
     */
    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

    //可以重寫這個方法對子View 進行重新佈局
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        return super.onLayoutChild(parent, child, layoutDirection);
    }

基於上面講的一些  Behavior 的不同回調,Google封裝了一些組件使用的 Behavior,比如 AppbarLayout 內部的 Behavior 專門用來協調 AppbarLayout 與可滾動的 View(NestedScrollView,RecyclerView)的,FloatActionButton 內部的 Behavior 協調和 Snackbar 的關係,保證 Snackbar 彈出的時候不被 FAB 遮擋。知道了這些,我們還可以自定義 Behavior。

Behavior 作用於CoordinatorLayout 子 View 的順序無關性是指根據依賴關係, CoordinatorLayout 中維護了一個mDependencySortedChildren 列表,裏面含有所有的子 View,按依賴關係排序,被依賴者排在前面,會在每次測量前重新排序,確保處理的順序是 被依賴的 View 會先被 measure 和 layout

自定義 Behavior 的方式主要有兩種:

  • 第一種是通過監聽一個 View 的狀態,如位置、大小的變化,來改變其他 View 的行爲,這種只需要重寫兩個方法就可以了,分別是 layoutDependsOn 和 onDependentViewChanged,layoutDependsOn 方法判斷是指定依賴的 View 時,返回 true。在 onDependentViewChanged 裏,被依賴的 View需要作出行爲動作來響應。

  • 第二種就是重寫 onStartNestedScroll、onNestedPreScrol、onNestedscroll 等一系列方法。

通過依賴的監聽,回調了上述方法。如果 CoordinatorLayout 內的 A 依賴 B,在 B 的大小位置等狀態改變的時候,A 可以監聽到,並作出響應。

CoordinatorLayout 本身註冊了兩種監聽器,ViewTreeObserver.OnPreDrawListener 和OnHierarchyChangeListener,一種是在繪製的之前進行回調,一種是在子 View 的層級結構發生變化的時候回調,有這兩種監聽就可以在接受到被依賴的 View 的變化了。

CoordinatorLayout 雖然繼承自 ViewGroup,但其更像是 FrameLayout,不能用 RelativeLayout 的layout_below等屬性來控制相對位置,所以控件確定上下的位置需要使用其他方式。

  • 一種是:在控件高度不變的情況下,利用margin來定位。比如頭部 Header 高度恆定爲40dp,那麼想要一個控件放在 Header 下方,就設置這個控件的 marginTop 爲 40dp 就好了。這種處理方式比較侷限。

  • 另一種是:利用 Behavior 提供的 onLayoutChild 方法重寫控件的 layout 過程。

未完待續,會寫個例子補充

參考資料:

Md系列3、CoordinatorLayout 裏 Toobar和TabLayout等發生的一系列故事
自定義Behavior的藝術探索-仿UC瀏覽器主頁

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章