掌握 Coordinator Layout

jopen 9年前發布 | 47K 次閱讀 Android開發 移動開發 CoordinatorLayout

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

CoordinatorLayout

顧名思義,這個控件的目的就是協調它里面View的行為。

請看下面的圖片:


</div>

在這個例子中我們可以看到View之間是如何相互配合的,View會根據其他View的變動做相應的變化。

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

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout xmlns:android="

<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></pre>

我們看一下這個layout結構,CoordinatorLayout包含3個子控件:
AppbarLayout, scrolleable view 和anchoredFloatingActionBar。

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

AppBarLayout

AppBarLayout 是繼承LinerLayout實現的一個ViewGroup容器組件,
默認的AppBarLayout是垂直方向的, 可以管理其中的控件在內容滾動時的行為。

這聽起來可能有點令人困惑,我想一張圖片可以勝過千言萬語,特別時GIF圖片:


</div>

AppBarLayout在這個例子中時藍色的View,在其下放置了一個可以縮放的圖片,其中包含一個Toolbar,
一個LinearLayout(包含標題和副標題),以及一個TabLayout。

我們可以通過設置layout_scrollFlags參數,來控制AppBarLayout中的控件行為。
在我們的這個例子中,大部分View的layout_scrollFlags都設置為scroll,如果沒有設置的話,
當可滾動的View進行滾動時,這些沒設置為scroll的View位置會保持不變;

layout_scrollFlags設置上snap值則可以避免進入動畫中間狀態( mid-animation-states),
這意味著動畫會一直持續到View完全顯示或完全隱藏為止。

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

TabLayout會一直是可見的,因為我們沒有在TabLayout上設置任何flag。

正如你所見,AppbarLayout的強大管理能力是通過在View上設置不同scroll flags實現的。

<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></pre>

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

AppbarLayout flags

SCROLL_FLAG_ENTER_ALWAYS: 當任何向下滾動事件發生時, View都會移入 , 不管scrolling view 是否正在滾動。

SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED: 'enterAlways'的附加標識,它使得returning view恢復到指定的最小高度后才開始顯示,然后再慢慢展開。

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

SCROLL_FLAG_SCROLL: View 會根據滾動事件進行移動。

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

CoordinatorLayout Behaviors

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


</div>

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

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

/*

  • 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();
        }
    }
    

    }

    ... }</pre>

    負責縮放動畫的是design library新引入的元素叫做Behavior, 在這里是CoordinatorLayout.Behavior<FloatingAcctionButton>, 它根據一些滾動條件,判斷是否顯示FAB。

    SwipeDismissBehavior

    深入design support library的代碼,我們會發現一個新的類:SwipeDismissBehavior,使用這個Behavior,
    我們可以很容易的使用CoordinatorLayout實現滑動刪除功能:


    </div>
    @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);
    

    }</pre>

    Custom Behaviors

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


    </div>

    Childs and dependencies

    child 是指需要應用behavior的View ,dependency 擔任觸發behavior的角色,并與child進行互動。
    在這個例子中, child 是ImageView, dependency 是Toolbar,如果Toolbar發生移動,ImageView也會做相應的移動。


    </div>

    現在我們已經知道概念了,接著我們看看怎么實現,
    第一步我們需要繼承CoordinatorLayout.Behavior<T>,T是指某一個View,
    在我們的例子中是ImageView, 繼承后,我們必須實現以下2個方法:

    • layoutDependsOn
    • onDependentViewChanged

    layoutDependsOn方法在每次layout發生變化時都會調用,我們需要在dependency控件發生變化時返回True,在我們的例子中是用戶在屏幕上滑動時(因為Toolbar發生了移動),然后我們需要讓child做出相應的反應。

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

    return dependency instanceof Toolbar; }</pre>

    一旦layoutDependsOn返回了True,第二個方法onDependentViewChanged就會被調用,
    在這個方法里我們需要實現動畫,轉場等效果。

    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);
    

    }</pre>

    整合后的代碼:

    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);
    

    }
    }</pre>

    Resources

    譯者: 陽春面
    原文地址:http://saulmm.github.io/mastering-coordinator/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!