(譯)掌握 Coordinator Layout

在今年的 Google I/O 15上Google 發(fā)布了 新的支持庫 迟赃,其中有好幾個組件與Material Design設(shè)計(jì)密切相關(guān),
在這些新組件中,你可以找到有幾個類似于ViewGroup 的控件菜枷,如 AppbarLayout,CollapsingToolbarLayout 和 CoordinatorLayout.
這些ViewGroups 控件提供了非常強(qiáng)大的功能,我決定寫一篇文章來介紹相關(guān)的配置和技巧叁丧。

CoordinatorLayout

顧名思義啤誊,這個控件的目的就是協(xié)調(diào)它里面View的行為。

請看下面的圖片:

在這個例子中我們可以看到View之間是如何相互配合的拥娄,View會根據(jù)其他View的變動做相應(yīng)的變化蚊锹。

以下是CoordinatorLayout的簡單使用例子:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_light"
    android:fitsSystemWindows="true"
    >

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

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/main.collapsing"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp"
            >

            <ImageView
                android:id="@+id/main.backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                android:src="@drawable/material_flat"
                app:layout_collapseMode="parallax"
                />

            <android.support.v7.widget.Toolbar
                android:id="@+id/main.toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin"
                />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:lineSpacingExtra="8dp"
            android:text="@string/lorem"
            android:padding="@dimen/activity_horizontal_margin"
            />
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_margin="@dimen/activity_horizontal_margin"
        android:src="@drawable/ic_comment_24dp"
        app:layout_anchor="@id/main.appbar"
        app:layout_anchorGravity="bottom|right|end"
        />
</android.support.design.widget.CoordinatorLayout>

我們看一下這個layout結(jié)構(gòu),CoordinatorLayout包含3個子控件:
AppbarLayout条舔, scrolleable view 和 anchoredFloatingActionBar枫耳。

<CoordinatorLayout>
    <AppbarLayout/>
    <scrollableView/>
    <FloatingActionButton/>
</CoordinatorLayout>

AppBarLayout

AppBarLayout 是繼承LinerLayout實(shí)現(xiàn)的一個ViewGroup容器組件,
默認(rèn)的AppBarLayout是垂直方向的, 可以管理其中的控件在內(nèi)容滾動時的行為。

這聽起來可能有點(diǎn)令人困惑孟抗,我想一張圖片可以勝過千言萬語迁杨,特別時GIF圖片:


AppBarLayout在這個例子中時藍(lán)色的View,在其下放置了一個可以縮放的圖片凄硼,其中包含一個Toolbar铅协,
一個LinearLayout(包含標(biāo)題和副標(biāo)題),以及一個TabLayout摊沉。

我們可以通過設(shè)置layout_scrollFlags參數(shù)狐史,來控制AppBarLayout中的控件行為。
在我們的這個例子中说墨,大部分View的layout_scrollFlags都設(shè)置為scroll骏全,如果沒有設(shè)置的話,
當(dāng)可滾動的View進(jìn)行滾動時尼斧,這些沒設(shè)置為scroll的View位置會保持不變姜贡;

layout_scrollFlags設(shè)置上snap值則可以避免進(jìn)入動畫中間狀態(tài)( mid-animation-states),
這意味著動畫會一直持續(xù)到View完全顯示或完全隱藏為止棺棵。

LinearLayout其中包含了一個標(biāo)題和一個副標(biāo)題楼咳,當(dāng)用戶向上移動時LinearLayout是一直顯示的,直到移出屏幕(enterAlways);

TabLayout會一直是可見的烛恤,因?yàn)槲覀儧]有在TabLayout上設(shè)置任何flag母怜。

正如你所見,AppbarLayout的強(qiáng)大管理能力是通過在View上設(shè)置不同scroll flags實(shí)現(xiàn)的缚柏。

<AppBarLayout>
    <CollapsingToolbarLayout
        app:layout_scrollFlags="scroll|snap"
        />

    <Toolbar
        app:layout_scrollFlags="scroll|snap"
        />

    <LinearLayout
        android:id="+id/title_container"
        app:layout_scrollFlags="scroll|enterAlways"
        />

    <TabLayout /> <!-- no flags -->
</AppBarLayout>

這些參數(shù)的設(shè)置請參考 Google Developers docs苹熏。
不過我建議還是通過代碼練習(xí)來掌握它。我在文章的末尾提供了幾個Github上的例子。

AppbarLayout flags

SCROLL_FLAG_ENTER_ALWAYS: 當(dāng)任何向下滾動事件發(fā)生時, View都會移入 , 不管scrolling view 是否正在滾動柜裸。

SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED: 'enterAlways'的附加標(biāo)識缕陕,它使得returning view恢復(fù)到指定的最小高度后才開始顯示粱锐,然后再慢慢展開疙挺。

SCROLL_FLAG_EXIT_UNTIL_COLLAPSED: 但向上移出屏幕時,View會一直收縮到最小高度后怜浅,再移出屏幕铐然。

SCROLL_FLAG_SCROLL: View 會根據(jù)滾動事件進(jìn)行移動。

SCROLL_FLAG_SNAP: 但滾動結(jié)束時恶座,如果View只有部分可見搀暑,它將會自動滑動到最近的邊界(完全可見或完全隱藏)

CoordinatorLayout Behaviors

讓我們做一些測試,打開Android Studio(>= 1.4)跨琳,根據(jù)模板Scrolling Activity創(chuàng)建一個項(xiàng)目自点,
不需要修改任何代碼,以下就是運(yùn)行后的界面:

如果我們查看生成的代碼脉让,不管layouts或java類中我們都不能找到Fab在滾動時變化的動畫桂敛,為什么呢?

答案在FloatingActionButton的源代碼里溅潜,自動 Android Studio v1.2 加入了java反編譯功能术唬,
我們使用ctrl/cmd + click可以查看源碼,看看到底發(fā)生了什么:

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 *  Floating action buttons are used for a
 *  special type of promoted action. 
 *  They are distinguished by a circled icon 
 *  floating above the UI and have special motion behaviors 
 *  related to morphing, launching, and the transferring anchor point.
 * 
 *  blah.. blah.. 
 */
@CoordinatorLayout.DefaultBehavior(
    FloatingActionButton.Behavior.class)
public class FloatingActionButton extends ImageButton {
    ...

    public static class Behavior 
        extends CoordinatorLayout.Behavior<FloatingActionButton> {

        private boolean updateFabVisibility(
           CoordinatorLayout parent, AppBarLayout appBarLayout, 
           FloatingActionButton child {

           if (a long condition) {
                // If the anchor's bottom is below the seam, 
                // we'll animate our FAB out
                child.hide();
            } else {
                // Else, we'll animate our FAB back in
                child.show();
            }
        }
    }

    ...
}

負(fù)責(zé)縮放動畫的是design library新引入的元素叫做Behavior, 在這里是CoordinatorLayout.Behavior<FloatingAcctionButton>, 它根據(jù)一些滾動條件滚澜,判斷是否顯示FAB粗仓。

SwipeDismissBehavior

深入design support library的代碼,我們會發(fā)現(xiàn)一個新的類:SwipeDismissBehavior设捐,使用這個Behavior借浊,
我們可以很容易的使用CoordinatorLayout實(shí)現(xiàn)滑動刪除功能:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_swipe_behavior);
    mCardView = (CardView) findViewById(R.id.swype_card);

    final SwipeDismissBehavior<CardView> swipe 
        = new SwipeDismissBehavior();

        swipe.setSwipeDirection(
            SwipeDismissBehavior.SWIPE_DIRECTION_ANY);

        swipe.setListener(
            new SwipeDismissBehavior.OnDismissListener() {
            @Override public void onDismiss(View view) {
                Toast.makeText(SwipeBehaviorExampleActivity.this,
                    "Card swiped !!", Toast.LENGTH_SHORT).show();
            }

            @Override 
            public void onDragStateChanged(int state) {}
        });

        LayoutParams coordinatorParams = 
            (LayoutParams) mCardView.getLayoutParams();

        coordinatorParams.setBehavior(swipe);
    }

Custom Behaviors

創(chuàng)建自定義Behaviors,并沒有想象的那么難萝招,首先我們得搞清楚兩個核心元素 childdependency.

Childs and dependencies

child 是指需要應(yīng)用behavior的View 蚂斤,dependency 擔(dān)任觸發(fā)behavior的角色,并與child進(jìn)行互動即寒。
在這個例子中橡淆, child 是ImageView, dependency 是Toolbar母赵,如果Toolbar發(fā)生移動逸爵,ImageView也會做相應(yīng)的移動。

現(xiàn)在我們已經(jīng)知道概念了凹嘲,接著我們看看怎么實(shí)現(xiàn)师倔,
第一步我們需要繼承CoordinatorLayout.Behavior<T>,T是指某一個View周蹭,
在我們的例子中是ImageView趋艘, 繼承后疲恢,我們必須實(shí)現(xiàn)以下2個方法:

  • layoutDependsOn
  • onDependentViewChanged

layoutDependsOn方法在每次layout發(fā)生變化時都會調(diào)用,我們需要在dependency控件發(fā)生變化時返回True瓷胧,在我們的例子中是用戶在屏幕上滑動時(因?yàn)門oolbar發(fā)生了移動)显拳,然后我們需要讓child做出相應(yīng)的反應(yīng)。

 @Override
   public boolean layoutDependsOn(
      CoordinatorLayout parent, 
      CircleImageView, child, 
      View dependency) {

      return dependency instanceof Toolbar; 
  } 

一旦layoutDependsOn返回了True搓萧,第二個方法onDependentViewChanged就會被調(diào)用杂数,
在這個方法里我們需要實(shí)現(xiàn)動畫,轉(zhuǎn)場等效果瘸洛。

public boolean onDependentViewChanged(
      CoordinatorLayout parent, 
      CircleImageView avatar, 
      View dependency) {?

      modifyAvatarDependingDependencyState(avatar, dependency);
   }

   private void modifyAvatarDependingDependencyState(
    CircleImageView avatar, View dependency) {
        //  avatar.setY(dependency.getY());
        //  avatar.setBlahBlat(dependency.blah / blah);
    } 

整合后的代碼:

public static class AvatarImageBehavior 
   extends CoordinatorLayout.Behavior<CircleImageView> {

   @Override
   public boolean layoutDependsOn(
    CoordinatorLayout parent, 
    CircleImageView, child, 
    View dependency) {

       return dependency instanceof Toolbar; 
  } 

  public boolean onDependentViewChanged( 
    CoordinatorLayout parent, 
    CircleImageView avatar, 
    View dependency) {
      modifyAvatarDependingDependencyState(avatar, dependency);
   }

  private void modifyAvatarDependingDependencyState(
    CircleImageView avatar, View dependency) {
        //  avatar.setY(dependency.getY());
        //  avatar.setBlahBlah(dependency.blah / blah);
    }    
}

Resources

譯者: 陽春面
我的博客:http://www.aswifter.com/2015/11/12/mastering-coordinator/
原文地址:http://saulmm.github.io/mastering-coordinator/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揍移,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子反肋,更是在濱河造成了極大的恐慌那伐,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件石蔗,死亡現(xiàn)場離奇詭異罕邀,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抓督,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門燃少,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铃在,你說我怎么就攤上這事阵具。” “怎么了定铜?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵阳液,是天一觀的道長。 經(jīng)常有香客問我揣炕,道長帘皿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任畸陡,我火速辦了婚禮鹰溜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丁恭。我一直安慰自己曹动,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布牲览。 她就那樣靜靜地躺著墓陈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贡必,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天兔港,我揣著相機(jī)與錄音,去河邊找鬼仔拟。 笑死衫樊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的理逊。 我是一名探鬼主播橡伞,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晋被!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刚盈,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羡洛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后藕漱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欲侮,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年肋联,在試婚紗的時候發(fā)現(xiàn)自己被綠了威蕉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡橄仍,死狀恐怖韧涨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侮繁,我是刑警寧澤虑粥,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站宪哩,受9級特大地震影響娩贷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锁孟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一彬祖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧品抽,春花似錦储笑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春赘方,著一層夾襖步出監(jiān)牢的瞬間烧颖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工窄陡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炕淮,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓跳夭,卻偏偏與公主長得像涂圆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子币叹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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