v7包下的組件類似CoordinatorLayout推出也有一段時間了,大家使用的時候應該會體會到其中很多的便利,今天這篇文章帶大家來了解一個比較重要的ui組件——Behavior。從字面意思上就可以看出它的作用,就是用來規定某些組件的行為的,那它到底是什么,又該怎么用呢?看完這篇文章希望大家會有自己的收獲~
前言
寫這篇文章的起因是因為我無意中在GitHub上發現了Jake Wharton大神新建了一個Repo,內容是JakeWharton/DrawerBehavior。有興趣的同學可以去看看,其實就是通過Behavior去構造一個類似於DrawerLayout的布局。想了想已經挺長時間沒有搞ui方面的代碼了,所以趁着這個機會復習了一下,順便寫一篇文章鞏固,也給想要了解這方面內容的同學一個平台吧。
Behavior是什么
在文章的開始,我們先要了解什么是Behavior。
1 |
/** |
它是CoordinatorLayout的內部類,從它的注釋和其中的方法可以看出來,它其實就是給CoordinatorLayout的子View提供了一些交互的方法,用來規范它們的交互行為,比如上面出現的onTouchEvent可以用來規范子View的觸摸事件,onLayoutChild可以用來規范子View的布局。
說到這里,大家可能會有一個問題,CoordinatorLayout又是個什么東西?
1 |
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent { |
可以看出,它其實就是一個ViewGroup,實現了NestedScrollingParent用來執行嵌套滑動。至於嵌套滑動的機制大家可以看我博客的第一篇文章,這不是我們這篇文章的重點。
既然CoordinatorLayout僅僅只是一個ViewGroup,它又為什么能展示出它在xml布局中展示的威力呢?其中的秘密就是在Behavior中。我們可以這么說,CoordinatorLayout利用了Behavior作為一個代理,去控制管理其下的子View做到各種布局和動畫效果。那為什么要使用Behavior呢?我想原因大概就是解耦吧,如果把所有的邏輯都寫死在CoordinatorLayout中,一來不利於維護,二來我們就沒有做一些自定義的事情,會顯得非常的笨重。
為什么要用Behavior
這里我們舉一個非常簡單的例子。首先來看看我們的布局文件。
1 |
<?xml version="1.0" encoding="utf-8"?> |
非常簡單有木有,CoordinatorLayout作為根布局,里面一個AppBarLayout一個RecyclerView。讓我們看看界面是怎么樣的。
可以看到顯示是正確的。但是如果我把xml里RecyclerView的那行layout_behavior刪掉呢?就像這樣。
1 |
<?xml version="1.0" encoding="utf-8"?> |
最終界面的展示就像這樣,RecyclerView把AppBarLayout給覆蓋了。這里其實很好理解,如剛才的代碼所示,CoordinatorLayout其實只是一個ViewGroup,它不像LinearLayout那樣具有特定的布局特點,甚至可以說它內部的邏輯和FrameLayout是沒什么差別的,所以如果你不設置對應的Behavior的話,布局就會有問題。從這里也可以反映出Behavior的作用,就是規范子View的顯示和交互。
原理&系統是怎么用Behavior的
說完了Behavior的作用,那該怎么用它呢?這一小節讓我們來講講Behavior的原理以及系統是如何使用它的。
首先先看原理。我們知道Behavior是用來幫助CoordinatorLayout的,所以我們要從CoordinatorLayout中尋找答案。首先,我們可以看到CoordinatorLayout中有一個LayoutParams,它的子View的LayoutParams都是這個,其中它的構造函數如下。
1 |
LayoutParams(Context context, AttributeSet attrs) { |
可以看到它通過parseBehavior去得到了對應子View的Behavior。大家可以試試用RecyclerView的getLayoutParams方法去獲取LayoutParams並且調用getBehavior方法,可以得到的就是我們在xml文件中設置的那個Behavior。
知道了如何將Behavior設置進去,那它是如何發揮作用的呢?讓我們來看看onLayout函數。
1 |
|
可以看到的是其中會先調用behavior.onLayoutChild(this, child, layoutDirection)。也就是說,Behavior的邏輯要優先於CoordinatorLayout自己的邏輯。其實不止是onLayout,我們還可以看看onTouchEvent這個函數。
1 |
public boolean onTouchEvent(MotionEvent ev) { |
可以看到也是調用了Behavior的onTouchEvent,我們可以下判斷說Behavior中的那些方法在CoordinatorLayout中都會在合適的時機去調用。這也證明了我們剛才的那句話:[Behavior就是CoordinatorLayout的代理,幫助它去管理子View]。
我們做一個總結,Behavior可以代理哪些行為呢?
1.Measure和Layout的布局行為。
2.onTouchEvent和onInterceptTouchEvent的觸摸行為。比如design包中的SwipeDismissBehavior就是通過這樣的方式完成的。
3.嵌套滑動行為(NestedScrollingParent和NestedScrollingChild中的邏輯)。
4.子View間的依賴行為。
對於第四點我們這里可以細說一下,什么叫子View的依賴行為呢?這里我們舉個例子,我們都知道如果在CoordinatorLayout中使用了FAB並且點擊展示SnackbarLayout的話,FAB會在Snackbar顯示的時候對應的上移,這是因為FAB依賴了SnackbarLayout。
1 |
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { |
這是FAB中的Behavior,可以看到它重寫了layoutDependsOn和onDependentViewChanged,里面的邏輯很簡單的就可以看明白。這里我們[將代碼翻譯成語言]就是說FAB要依賴的組件是SnackbarLayout,所以在之后的操作里當DependentView(SnackbarLayout)發生了改變,自己(FAB)也會相應的做出改變。
值得一提的是,onDependentViewChanged這個函數的調用時機並不是在onLayout之前,而是在onPreDraw中,具體代碼如下:
1 |
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener { |
如此簡單的處理View間的依賴,可見Behavior配合CoordinatorLayout是有多強大。下面我們可以再舉一個例子來講講Behavior的作用。還記得我們上面說的嗎?RecyclerView設置了一個Behavior它就可以和AppBarLayout很好的展示出來。這個Behavior的名字是:
1 |
app:layout_behavior="@string/appbar_scrolling_view_behavior" |
可以看到它是AppBarLayout里的一個內部類,讓我們看看它做了什么。
1 |
|
我們知道,如果不設置這個Behavior的話,RecyclerView會覆蓋AppBarLayout。而上面這段代碼里的邏輯就可以很好的解釋這個原因了。值得一提的是,在offsetChildAsNeeded方法中有這么一段:
1 |
final CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior(); |
這里dependency就是AppBarLayout,所以我們可以知道,AppBarLayout中有兩個Behavior,一個是我們前面提到的ScrollingViewBehavior,用來處理它和其他滑動View的關系,另外一個就是Behavior,用來處理自己的邏輯,比如Layout。通過這種巧妙的方式,我們就可以做到非常簡便的控制View本身和View之間的邏輯。
如何自定義Behavior
本來想寫個demo給大家看一看的,不過感覺還是不要重復造輪子了,還是沒用的輪子。推薦大家看SwipeDismissBehavior用法及實現原理這篇文章和一開始提到的Jake大神的新作DrawerBehavior。如果你把這兩個東西搞懂,那么Behavior你可以說已經完全沒問題了~
后記
最近一段時間都在搞hotPatch和插件化相關的東西,看了很多Framework層的源碼,要做的東西也做的七七八八,希望快點解決最后的幾個bug並且之后能開源和大家見面吧