徹底搞懂 CoordinatorLayout

lkim6487 8年前發布 | 15K 次閱讀 安卓開發 Android開發 移動開發

本系列文章會從官方文檔出發,從基本使用姿勢到工作原理,試圖把CoordinatorLayout、AppBarLayout等一系列Material Desgin風格控件徹底講明白。本篇文章主要介紹CoordinatorLayout的基本概念,是后續篇章的基礎。

從是什么開始

首先我們先來看看CoordinatorLayout究竟是個什么東東,它究竟是用來做什么的。官方文檔對CoordinatorLayout是這樣描述的:

CoordinatorLayout是一個“加強版”FrameLayout,它主要有兩個用途:

  1. 用作應用的頂層布局管理器,也就是作為用戶界面中所有UI控件的容器

  2. 用作相互之間具有特定交互行為的UI控件的容器

通過為CoordinatorLayout的子View指定Behavior,就可以實現它們之間的交互行為。 Behavior可以用來實現一系列的交互行為和布局變化,比如說側滑菜單、可滑動刪除的UI元素,以及跟隨著其他UI控件移動的按鈕等。

上面的描述可能有些抽象,現在我們只需要知道CoordinatorLayout是一個布局管理器,主要用來實現它的子View間的交互行為。那么什么是交互行為呢?我們來看一個簡單的例子:

在上圖中,我們拖動按鈕,可以看到,一個TextView會跟著按鈕一起移動。這就是交互行為的一個簡單地例子。也就是說首先需要Button的位置發生變化,然后TextView對Button的位置變化做出響應,這個例子中,TextView做出響應的方式就是跟隨著Button一起移動。那么這個交互行為時如何實現的呢?我們接著往下看。

如何實現交互行為

上面我們看到了一個簡單的交互行為的例子,下面我們通過分析這個例子來介紹一下實現交互行為的一般性步驟。

上面的例子實現的是Button和TextView的交互行為:Button發生變化時,TextView要對這個變化做出響應。那么首先我們要讓TextView知道Button產生了變化,還要指明TextView對Button的變化做出什么反應。

這里實際上是一個觀察者模式的運用:TextView是觀察者,Button是被觀察者。TextView需要向系統注冊一個回調,告知系統在Button發生變化時通知它,這樣當Button發生變化時,TextView會得到關于這個變化的通知,而后就可以對這個變化做出響應。如此一來我們便實現了TextView和Button的交互行為。下面我們來介紹如何通過給TextView設置一個Behavior來實現上面的交互行為。

XML布局文件

首先我們先來看一下用戶界面的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">
  <TextView
    app:layout_behavior=".FollowBehavior"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="觀察者" />
  <Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="被觀察者" />
</android.support.design.widget.CoordinatorLayout>

我們為TextView指定了一個layout_behavior屬性,這樣就給它設置了一個Behavior。我們可以看到layout_behavior屬性的值為".FollowBehavior",指的是當前Module中一個名為FollowBehavior的類,它實際上是CoordinatorLayout.Behavior類的子類,我們來看一下FollowBehavior類的實現。

自定義交互行為類

FollowBehavior類的代碼如下:

public class FollowBehavior extends CoordinatorLayout.Behavior<TextView> {
  public FollowBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
  @Override
  public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) { 
    return dependency instanceof  Button;
  }
  @Override
  public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
        View dependency) {
    child.setX(dependency.getX() + 150);
    child.setY(dependency.getY() + 150);
    return true;
  }
}

在上面的代碼中我們重寫了父類的兩個方法:layoutDependsOn()方法和onDependentViewChanged()方法。在介紹這兩個方法的作用前,我們先來介紹一下dependent view。在一個交互行為中,dependent view的變化決定了另一個相關View的行為。在這個例子中,Button就是dependent view,因為TextView跟隨著它。實際上dependent view就相當于我們前面介紹的被觀察者。知道了這個概念,讓我們看看重寫的兩個方法的作用:

  • layoutDependsOn():這個方法在對界面進行布局時至少會調用一次,用來確定本次交互行為中的dependent view,在上面的代碼中,當dependency是Button類的實例時返回true,就可以讓系統知道布局文件中的Button就是本次交互行為中的dependent view。

  • onDependentViewChanged():當dependent view發生變化時,這個方法會被調用,參數中的child相當于本次交互行為中的觀察者,觀察者可以在這個方法中對被觀察者的變化做出響應,從而完成一次交互行為。

現在我們已經定義好了一個交互行為,但是Button還不會跟著我們的手指移動,接下來我們讓它動起來。

讓Button動起來

我們只需讓Button在屏幕上的位置隨我們手指移動,從而讓TextView跟著它移動,相關代碼如下:

public class CoorDemoActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.follow);
    findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {        
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
          v.setX(event.getRawX()-v.getWidth()/2);
          v.setY(event.getRawY()-v.getHeight()/2);
        }
        return true;
      }
    });
  }
}

這樣一來,我們就完成了為TextView和Button設置跟隨移動這個交互行為。很簡單有木有,其實為CoordinatorLayout的子View設置交互行為只需三步:

  • 自定義一個繼承自Behavior類的交互行為類;

  • 把觀察者的layout_behavior屬性設置為自定義行為類的類名;

  • 重寫Behavior類的相關方法來實現我們想要的交互行為。

值得注意的是,有些時候,并不需要我們自己來定義一個Behavior類,因為系統為我們預定義了不少Behavior類。在接下來的篇章中,我們會做出進一步的介紹。

更進一步

現在我們已經知道了怎么通過給CoordinatorLayout的子View設置Behavior來實現交互行為。現在,讓我們更進一步地挖掘下CoordinatorLayout,深入了解一下隱藏在表象背后的神秘細節。

實際上,CoordinatorLayout本身并沒有做過多工作,實現交互行為的主要幕后推手是CoordinatorLayout的內部類——Behavior。通過為CoordinatorLayout的 直接子View 綁定一個Behavior,這個Behavior就會攔截發生在這個View上的Touch事件、嵌套滾動等。不僅如此,Behavior還能攔截對與它綁定的View的測量及布局。關于嵌套滾動,我們會在后續文章中進行詳細介紹。下面我們來深入了解一下Behavior是如何做到這一切的。

深入理解Behavior

攔截Touch事件

當我們為一個CoordinatorLayout的直接子View設置了Behavior時,這個Behavior就能攔截發生在這個View上的Touch事件,那么它是如何做到的呢?實際上,CoordinatorLayout重寫了onInterceptTouchEvent()方法,并在其中給Behavior開了個后門,讓它能夠先于View本身處理Touch事件。具體來說,CoordinatorLayout的onInterceptTouchEvent()方法中會遍歷所有直接子View,對于綁定了Behavior的直接子View調用Behavior的onInterceptTouchEvent()方法,若這個方法返回true,那么后續本該由相應子View處理的Touch事件都會交由Behavior處理,而View本身表示懵逼,完全不知道發生了什么。

攔截測量及布局

了解了Behavior是怎養攔截Touch事件的,想必大家已經猜出來了它攔截測量及布局事件的方式——CoordinatorLayout重寫了測量及布局相關的方法并為Behavior開了個后門。沒錯,真相就是如此。

CoordinatorLayout在onMeasure()方法中,會遍歷所有直接子View,若該子View綁定了一個Behavior,就會調用相應Behavior的onMeasureChild()方法,若此方法返回true,那么CoordinatorLayout對該子View的測量就不會進行。這樣一來,Behavior就成功接管了對View的測量。

同樣,CoordinatorLayout在onLayout()方法中也做了與onMeasure()方法中相似的事,讓Behavior能夠接管對相關子View的布局。

View的依賴關系的確定

現在,我們在探究一下交互行為中的兩個View之間的依賴關系是怎么確定的。我們稱child為交互行為中根據另一個View的變化做出響應的那個個體,而dependent view為child所依賴的View。實際上,確立child和dependent view的依賴關系有兩種方式:

  • 顯式依賴:為child綁定一個Behavior,并在Behavior類的layoutDependsOn()方法中做手腳。即當傳入的dependency為dependent view時返回true,這樣就建立了child和dependent view之間的依賴關系。

  • 隱式依賴:通過我們最開始提到的錨(anchor)來確立。具體做法可以這樣:在XML布局文件中,把child的layout_anchor屬性設為dependent view的id,然后child的layout_anchorGravity屬性用來描述為它想對dependent view的變化做出什么樣的響應。關于這個我們會在后續篇章給出具體示例。

無論是隱式依賴還是顯式依賴,在dependent view發生變化時,相應Behavior類的onDependentViewChanged()方法都會被調用,在這個方法中,我們可以讓child做出改變以響應dependent view的變化。

 

 

來自:http://mp.weixin.qq.com/s?__biz=MzIzMjE1Njg4Mw==&mid=2650117781&idx=1&sn=187bcdbbbfa0610e131c03a0e8a0fbf5&chksm=f0980d29c7ef843f8e5614cb2907277913022f0376363310fe206b1bf413ffec025f593866af#rd

 

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