安卓behavior詳解3--自定義behavior詳解
一.前言
官方定義:
A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.
中文:
一個Behavior實現了一個或多個使用者可以採取的互動檢視。這些相互作用可能包括拖動,滑動,快速滑動,或任何其他的手勢。
二.類似觀察者概念
其實Behavior就是一個應用於View的觀察者模式,一個View跟隨者另一個View的變化而變化,或者說一個View監聽另一個View。
在Behavior中,被觀察的View 也就是事件源被稱為denpendcy,而觀察View,則被稱為child
Behavior內部的方法當在CoordinatorLayout上發生觸控事件的時候,CoordinatorLayout會處理觸控事件,並回調Behavior中的部分方法,我們可以通過處理這些回撥方法來實現需求
三.Behavior中的幾個重要方法
Behavior 是一個頂層抽象類,其他的一些具體行為的Behavior 都是繼承自這個類。它提供了幾個重要的方法:
layoutDependsOn
onDependentViewChanged
onStartNestedScroll
onNestedPreScroll
onNestedScroll
onStopNestedScroll
onNestedScrollAccepted
onNestedPreFling
onLayoutChild
/**
* 表示是否給應用了Behavior 的View 指定一個依賴的佈局,通常,當依賴的View 佈局發生變化時,不管被被依賴View 的順序怎樣,被依賴的View也會重新佈局
* @param parent
* @param child 繫結behavior 的View
* @param dependency 依賴的view
* @return 如果child 是依賴的指定的View 返回true,否則返回false
*/
@Override
public boolean layoutDependsOn (CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 當被依賴的View 狀態(位置、大小等)發生變化時,這個方法被呼叫
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 當coordinatorLayout 的子View試圖開始巢狀滑動的時候被呼叫。
* 當返回值為true的時候表明coordinatorLayout 充當nested scroll parent 處理這次滑動,需要注意的是隻有當返回值為true的時候,Behavior 才能收到後面的一些nested scroll 事件回撥(如:onNestedPreScroll、onNestedScroll等)這個方法有個重要的引數 nestedScrollAxes,表明處理的滑動的方向。
* @param coordinatorLayout 和Behavior 繫結的View的父CoordinatorLayout
* @param child 和Behavior 繫結的View
* @param directTargetChild
* @param target
* @param nestedScrollAxes
巢狀滑動 應用的滑動方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
{@link ViewCompat#SCROLL_AXIS_VERTICAL}
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 巢狀滾動發生之前被呼叫
在nested scroll child 消費掉自己的滾動距離之前,巢狀滾動每次被nested scroll child
更新都會呼叫onNestedPreScroll。注意有個重要的引數consumed,可以修改這個數 組表示你消費了多少距離。假設使用者滑動了100px,child 做了90px 的位移,你需要 把 consumed[1]的值改成90,
* 這樣coordinatorLayout就能知道只處理剩下的10px的滾動。
* @param coordinatorLayout
* @param child
* @param target
* @param dx 使用者水平方向的滾動距離
* @param dy 使用者豎直方向的滾動距離
* @param consumed
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
/**
* 進行巢狀滾動時被呼叫
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已經消費的x方向的距離
* @param dyConsumed target 已經消費的y方向的距離
* @param dxUnconsumed x 方向剩下的滾動距離
* @param dyUnconsumed y 方向剩下的滾動距離
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
/**
* 巢狀滾動結束時被呼叫,這是一個清除滾動狀態等的好時機。
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
/**
* onStartNestedScroll返回true才會觸發這個方法,接受滾動處理後回撥,可以在這個
方法裡做一些準備工作,如一些狀態的重置等。
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
*/
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 使用者鬆開手指並且會發生慣性動作之前呼叫,引數提供了速度資訊,可以根據這些 速度資訊決定最終狀態,比如滾動Header,是讓Header處於展開狀態還是摺疊狀 態。返回true 表示消費了fling.
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX x方向的速度
* @param velocityY y方向的速度
* @return
*/
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
/*
* 可以重寫這個方法對子View 進行重新佈局
* @param coordinatorLayout
* @param child
* @param layoutDirection 佈局的方向
*/
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
四.巢狀滾動原理和CoordinatorLayout
NestedScrolling提供了一套父View和子View滑動互動機制。要完成這樣的互動,父View需要實現NestedScrollingParent介面,而子View需要實現NestedScrollingChild介面,系統提供的NestedScrollView控制元件就實現了這兩個介面,這兩個介面雖然有很多抽象方法,但均有NestedScrolling[Parent,Children]Helper輔助類來幫助處理大部分邏輯
NestedScroll的機制的簡版是這樣的,當子View在處理滑動事件之前,先告訴自己的父View是否需要先處理這次滑動事件,父View處理完之後,告訴子View它處理的多少滑動距離,剩下的還是交給子View自己來處理
CoordinatorLayout的事件傳遞
CoordinatorLayout並不會直接處理觸控事件,而是儘可能地先交由子View的Behavior來處理,它的onInterceptTouchEvent和onTouchEvent兩個方法最終都是呼叫performIntercept方法,用來分發不同的事件型別分發給對應的子View的Behavior處理
//處理攔截或者自己的觸控事件
private boolean performIntercept(MotionEvent ev, final int type) {
boolean intercepted = false;
boolean newBlock = false;
MotionEvent cancelEvent = null;
final int action = MotionEventCompat.getActionMasked(ev);
final List<View> topmostChildList = mTempList1;
getTopSortedChildren(topmostChildList); //在5.0以上,按照z屬性來排序,以下,則是按照新增順序或者自定義的繪製順序來排列
// Let topmost child views inspect first
final int childCount = topmostChildList.size();
for (int i = 0; i < childCount; i++) {
final View child = topmostChildList.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
// 如果有一個behavior對事件進行了攔截,就傳送Cancel事件給後續的所有Behavior。假設之前還沒有Intercept發生,那麼所有的事件都平等地對所有含有behavior的view進行分發,現在intercept忽然出現,那麼相應的我們就要對除了Intercept的view發出Cancel
if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
// Cancel all behaviors beneath the one that intercepted.
// If the event is "down" then we don't have anything to cancel yet.
if (b != null) {
if (cancelEvent == null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
}
switch (type) {
case TYPE_ON_INTERCEPT:
b.onInterceptTouchEvent(this, child, cancelEvent);
break;
case TYPE_ON_TOUCH:
b.onTouchEvent(this, child, cancelEvent);
break;
}
}
continue;
}
if (!intercepted && b != null) {
switch (type) {
case TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(this, child, ev);
break;
case TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(this, child, ev);
break;
}
if (intercepted) {
mBehaviorTouchView = child; //記錄當前需要處理事件的View
}
}
// Don't keep going if we're not allowing interaction below this.
// Setting newBlock will make sure we cancel the rest of the behaviors.
final boolean wasBlocking = lp.didBlockInteraction();
final boolean isBlocking = lp.isBlockingInteractionBelow(this, child); //behaviors是否攔截事件
newBlock = isBlocking && !wasBlocking;
if (isBlocking && !newBlock) {
// Stop here since we don't have anything more to cancel - we already did
// when the behavior first started blocking things below this point.
break;
}
}
topmostChildList.clear();
return intercepted;
}