android.support.design 學(xué)習(xí)筆記 1

dim.red
在appcompat 22 的時候,google帶來了Support Design,成為實現(xiàn)MD的利器,最近因為要開始使用這個庫,稍微過了下庫的內(nèi)容.

這次主要通過講解當(dāng)前界面是怎么實現(xiàn)的.來學(xué)習(xí)這個庫.
布局

布局

看看這個界面的實現(xiàn),我們主要通過3個方面來了解,

  1. 子控件的寬高的測量
  • 子控件的位置擺放
  • 子控件的事件傳遞

1 測量:

因為它們的根控件是CoordinatorLayout .所以我們重點是放在
CoordinatorLayout 的onMeasure方法里面:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    /**
     * 省略N多代碼
     */
        final Behavior b = lp.getBehavior();
        if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                childHeightMeasureSpec, 0)) {
            onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0);
        }

    /**
     * 省略N多代碼
     */
         
    setMeasuredDimension(width, height);
}

子控件的測量交給他們的Behavior,Behavior 不處理,交給CoordinatorLayout處理 ,Behavior 可以在attr中指定. 可以看出ViewPager的Behavior 是AppBarLayout$ScrollingViewBehavior
,我們進(jìn)入ScrollingViewBehavior 中的onMeasureChild方法中



@Override
public boolean onMeasureChild(CoordinatorLayout parent, View child,
        int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
        int heightUsed) {
    final int childLpHeight = child.getLayoutParams().height;
    if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
            || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
     /**
     * 省略N多代碼
     */
 

    if (availableHeight == 0) {
       // If the measure spec doesn't specify a size, use the         current height
         availableHeight = parent.getHeight();
     }
     final int height = availableHeight - header.getMeasuredHeight()
                    + getScrollRange(header);
      final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
                    childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
                            ? View.MeasureSpec.EXACTLY
                            : View.MeasureSpec.AT_MOST);

            // Now measure the scrolling menu with the correct height
      parent.onMeasureChild(child, parentWidthMeasureSpec,
                    widthUsed, heightMeasureSpec, heightUsed);

            return true;
        }
    }
    return false;

}

可以看出來當(dāng)你的ViewPager的高度不設(shè)置固定的值得話,他的高度會被ScrollingViewBehavior重新賦值,高度為CoordinatorLayout的高度減去AppBarLayout的可滑動范圍.(既getTotalScrollRange())

可以看出:當(dāng)前的ViewPager 的高度比我們當(dāng)前屏幕上看的要高一點.

AppBarLayout 里面有3個范圍比較有意思.
getTotalScrollRange():表示總共可以滑動的范圍
它是計算所有l(wèi)ayout_scrollFlags標(biāo)有scroll 的View 的高度減去所有同時標(biāo)有scroll 和 exitUntilCollapsed 的 View 的最小高度.

getDownNestedPreScrollRange():表示當(dāng)向下滑動可以滑動的范圍.
它計算了所有l(wèi)ayout_scrollFlags同時標(biāo)記scroll 和 enterAlways 同時不標(biāo)記 enterAlwaysCollapsed的View 的高度 加上既標(biāo)記了scroll 和 enterAlways又標(biāo)記了enterAlwaysCollapsed 的最小高度.
產(chǎn)生的效果是:在下滑的過程中AppBarLayout殘留在屏幕上的最小高度為 AppBarLayout本身的高度減去getDownNestedPreScrollRange()的高度.

getUpNestedPreScrollRange():表示當(dāng)向上滑動可以滑動的范圍.
這里返回的是getTotalScrollRange().
產(chǎn)生的效果是:在上滑的過程中AppBarLayout殘留在屏幕上的最小高度為 AppBarLayout本身的高度減去getUpNestedPreScrollRange()的高.

而這三種范圍構(gòu)成了 AppBarLayout 在 RecyclerView 滑動事件的滑動效果.

主意點:
  1. exitUntilCollapsed只有和scroll一起組合才會有效果;
  • enterAlwaysCollapsed 要和scroll 和enterAlways一起使用才有效果.
  • 官方說要把帶有scroll flag的view放在前面,這樣收回的view才能讓正常退出欺冀,而固定的view繼續(xù)留在頂部树绩。
    那是因為AppBarLayout 是一個 LinearLayout 布局.最后留在屏幕上的東西是 AppBarLayout 的底部,所以需要把要固定的 View 放在最后.
  • 這里所有的 View 都是 AppBarLayout 的一級 View.二級不太考慮當(dāng)中,

下面放出幾個例子來加深大家對layout_scrollFlags和3中范圍的理解.
第一中 正常情況(scroll):

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#f00"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:layout_scrollFlags="scroll" />

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

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

第2種(minHeight +scroll +exitUntilCollapsed)

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#f00"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:minHeight="20dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed" />

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

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

第3種(minHeight +scroll +enterAlways+enterAlwaysCollapsed)


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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#f00"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:minHeight="20dp"
        app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" />

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

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

2 位置擺放

同樣進(jìn)入CoordinatorLayout 的onLayout方法


@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        final View child = mDependencySortedChildren.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior behavior = lp.getBehavior();

        if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
            onLayoutChild(child, layoutDirection);
        }
    }
}

同樣可以看到它也是先讓Behavior處理.不處理才是CoordinatorLayout自身去處理.
同樣我們?yōu)榱瞬榭碫iewPager 的擺放,我們進(jìn)入ScrollingViewBehavior 中的onLayoutChild方法中.

@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
    // First lay out the child as normal
    super.onLayoutChild(parent, child, layoutDirection);

    // Now offset us correctly to be in the correct position. This is important for things
    // like activity transitions which rely on accurate positioning after the first layout.
    final List<View> dependencies = parent.getDependencies(child);
    for (int i = 0, z = dependencies.size(); i < z; i++) {
        if (updateOffset(parent, child, dependencies.get(i))) {
            // If we updated the offset, break out of the loop now
            break;
        }
    }
    return true;
}

先調(diào)用的父類的onLayoutChild 的方法.然后根據(jù)dependencies (其實就是AppBarLayout)的getTopBottomOffsetForScrollingSibling(),其實就是把ViewPager放在AppBarLayout的下方.

3 事件傳遞

Touch事件的話

CoordinatorLayout是會在onInterceptTouchEvent 對所有的攜帶Behavior的第一級View 發(fā)送通知.如果被哪一個Behavior的onInterceptTouchEvent 的攔截,所以的后續(xù)的 Touch動作都分發(fā)給這個Behavior.

7BE0A9A6-CA47-4FD4-9CFF-6BE1790B86B6.png
注意點:

能接受到事件只有第一級的并且攜帶Behavior的控件.
同時這個事件是通知給所有的攜帶Behavior的控件,也就是說當(dāng)你的點擊事件不在這個 View 的上方,只要這個View 有攜帶 Behavior 都會收到通知,就是說不管你是點擊屏幕上的1還是2,AppBarLayout 都會收到onInterceptTouchEvent事件,所以在復(fù)寫 Behavior 的onInterceptTouchEvent 要特別注意到這個情況.

比如說界面一開始往上滑動. 這個時候點擊事件是被AppBarLayout的Behavior 攔截的. AppBarLayout的Behavior事件會設(shè)置AppBarLayout的setTopAndBottomOffset ,使AppBarLayout產(chǎn)生了往上偏移,所以你可以看到AppBarLayout 往上偏移,那么ViewPager 為啥也向上偏移.因為ViewPager的ScrollingViewBehavior 中

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    // We depend on any AppBarLayouts
    return dependency instanceof AppBarLayout;
}

對AppBarLayout 進(jìn)行關(guān)聯(lián),當(dāng)AppBarLayout 有變化的時候會通知給
ScrollingViewBehavior 的onDependentViewChanged 方法中.
通過在這個方法中進(jìn)行對ViewPager的位置也進(jìn)行偏移.使他們一起往上偏移.所以看起來想兩個一起往上偏移,這個也是酷酷的.

Scroll 事件

當(dāng)Touch 事件在ViewPager中. 因為ViewPager中的使用的RecyclerView控件,而RecyclerView 是使用Nest來和其他控件一起處理Scroll事件.RecyclerView 的Nest的事件會一層一層的上傳Scroll 事件,被最近的NestedScrollingParent 接受,這里是CoordinatorLayout ,CoordinatorLayout是一個協(xié)調(diào)者的角色,他將Nest的事件分發(fā)給子控件的View的Behavior處理.
在這里都會被AppBarLayout的Behavior接受.它會根據(jù)getTotalScrollRange,getDownNestedPreScrollRange,getUpNestedPreScrollRange來進(jìn)行想對應(yīng)的偏移. 效果在上面已經(jīng)講了.

關(guān)于Nest 來處理 Scroll 事件:

當(dāng) NestedScrollingChild(下面用Child代替) 要開始滑動的時候會發(fā)送 onStartNestedScroll 請求給最近的NestedScrollingParent(下面用Parent代替). 當(dāng)onStartNestedScroll 返回 true 表示同意一起處理 Scroll 事件的時候時候Child會發(fā)送onNestedScrollAccepted 通知 讓Parent去做一些準(zhǔn)備動作,當(dāng)Child 要開始滑動的時候,會先發(fā)送onNestedPreScroll 請求給Parent ,告訴它我現(xiàn)在要滑動多少米了,你覺得行不行,這時候Parent 根據(jù)實際情況告訴Child 現(xiàn)在只允許你滑動多少.然后 Child 根據(jù) onNestedPreScroll 中傳遞回來的信息對滑動距離做相對應(yīng)的調(diào)整.在滑動的過程中 Child 會發(fā)送onNestedScroll通知告知Parent 當(dāng)前 Child 的滑動情況. 當(dāng)要進(jìn)行滑行的時候,會先發(fā)送onNestedFling 請求給Parent,告訴它 我現(xiàn)在要滑行了,你說行不行, 這時候Parent會根據(jù)情況告訴 Child 你是否可以滑行. Child 通過onNestedFling 返回的 Boolean 值來覺得是否進(jìn)行滑行.如果要滑行的話,會在滑行的時候發(fā)送onNestedFling 通知告知 Parent 滑行情況.當(dāng)滑動事件結(jié)束就會發(fā)送onStopNestedScroll 通知 Parent 去做相關(guān)操作.

主意點:
  1. Parent 告知 Child 現(xiàn)在允許你滑動多少是通過
    onNestedPreScroll中的數(shù)組int[] consumed ,consumed[0]表示 Parent 在 X 軸消耗的量, 所以 Child 滑動距離是請求X軸的滑動距離上面減少consumed[0],consumed[1]表示 Y軸上面的消耗.
    因為consumed是數(shù)組,所以Child可以完成可以拿到數(shù)據(jù),而不需要onNestedPreScroll 的返回值.
  • 重點注意講解中的請求和通知.

尾巴

詳情界面我也大概看了一遍..機(jī)制差不多,其實就是多了CollapsingToolbarLayout這個的好玩的控件.所以這個學(xué)習(xí)筆記不一定有2.呵呵

同時我會在最近的寫一些有意思的 Behavior 出來.

歡迎大家關(guān)注 我的github,我的微博.

github
我的新浪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市隐轩,隨后出現(xiàn)的幾起案子饺饭,更是在濱河造成了極大的恐慌,老刑警劉巖职车,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘫俊,死亡現(xiàn)場離奇詭異,居然都是意外死亡悴灵,警方通過查閱死者的電腦和手機(jī)扛芽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來积瞒,“玉大人川尖,你說我怎么就攤上這事∶?祝” “怎么了空厌?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵庐船,是天一觀的道長银酬。 經(jīng)常有香客問我嘲更,道長,這世上最難降的妖魔是什么揩瞪? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任赋朦,我火速辦了婚禮,結(jié)果婚禮上李破,老公的妹妹穿的比我還像新娘宠哄。我一直安慰自己,他們只是感情好嗤攻,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布毛嫉。 她就那樣靜靜地躺著,像睡著了一般妇菱。 火紅的嫁衣襯著肌膚如雪承粤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天闯团,我揣著相機(jī)與錄音辛臊,去河邊找鬼涂炎。 笑死觉鼻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诫咱。 我是一名探鬼主播候味,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刃唤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了白群?” 一聲冷哼從身側(cè)響起尚胞,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎川抡,沒想到半個月后辐真,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡崖堤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年侍咱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片密幔。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡楔脯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胯甩,到底是詐尸還是另有隱情昧廷,我是刑警寧澤堪嫂,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站木柬,受9級特大地震影響皆串,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眉枕,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一恶复、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧速挑,春花似錦谤牡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腊满,卻和暖如春套么,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背糜烹。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工违诗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疮蹦。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓诸迟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親愕乎。 傳聞我的和親對象是個殘疾皇子阵苇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

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