概述
Google官方對(duì)它的概述如下:
CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}.
CoordinatorLayout is intended for two primary use cases:
As a top-level application decor or chrome layout
As a container for a specific interaction with one or more child views
By specifying {@link CoordinatorLayout.Behavior Behaviors} for child views of a
CoordinatorLayout you can provide many different interactions within a single parent and those
views can also interact with one another. View classes can specify a default behavior when
used as a child of a CoordinatorLayout using the
{@link CoordinatorLayout.DefaultBehavior DefaultBehavior} annotation.
Behaviors may be used to implement a variety of interactions and additional layout
modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons
that stick to other elements as they move and animate.
Children of a CoordinatorLayout may have an
{@link CoordinatorLayout.LayoutParams#setAnchorId(int) anchor}. This view id must correspond
to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself
or a descendant of the anchored child. This can be used to place floating views relative to
other arbitrary content panes.
Children can specify {@link CoordinatorLayout.LayoutParams#insetEdge} to describe how the
view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by
{@link CoordinatorLayout.LayoutParams#dodgeInsetEdges} will be moved appropriately so that the
views do not overlap.
大概的意思也就是說:
CoordinatorLayout 是一個(gè)增強(qiáng)版的FrameLayout。(繼承自ViewGroup)
主要有兩個(gè)用途:
1雌隅、作為應(yīng)用的頂層視圖
2溪掀、作為一個(gè)可以指定子views之間相互作用的容器睹簇,通過給CoordinatorLayout的子View指定CoordinatorLayout.Behavior來提供子view之間不同的相互作用疆虚,也就是說可以通過自定義CoordinatorLayout.Behavior來定義子views之間的相互作用朗若。
CoordinatorLayout核心就在于協(xié)調(diào)子View之間的相互作用,而子View之間的相互作用是通過CoordinatorLayout.Behavior來定義的蓝角,Google實(shí)現(xiàn)了幾個(gè)繼承自CoordinatorLayout.Behavior的類:
注意:上面白底的是我自己自定義的Behavior阱穗,大家不必關(guān)心這兩個(gè)。
CoordinatorLayout處理子View的layout_behavior屬性的源碼分析
通過layout_behavior的名稱就可以知道使鹅,這個(gè)屬性肯定是CoordinatorLayout.LayoutParams解析的揪阶,那我們就看一下CoordinatorLayout.LayoutParams構(gòu)造方法:
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CoordinatorLayout_Layout);
this.gravity = a.getInteger(
R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
Gravity.NO_GRAVITY);
mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
View.NO_ID);
this.anchorGravity = a.getInteger(
R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
Gravity.NO_GRAVITY);
this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
-1);
insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
dodgeInsetEdges = a.getInt(
R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_Layout_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
a.recycle();
if (mBehavior != null) {
// If we have a Behavior, dispatch that it has been attached
mBehavior.onAttachedToLayoutParams(this);
}
}
從上面的代碼中可以得知,會(huì)先判斷CoordinatorLayout的子View是否設(shè)置CoordinatorLayout_Layout_layout_behavior(即在布局文件中設(shè)置的layout_behavior屬性)屬性患朱,如果在子View的布局中設(shè)置了layout_behavior屬性鲁僚,就會(huì)調(diào)用CoordinatorLayout類的parseBehavior方法,在該方法中會(huì)通過反射技術(shù)實(shí)例化Behavior:
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// Relative to the app package. Prepend the app package name.
fullName = context.getPackageName() + name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
? (WIDGET_PACKAGE_NAME + '.' + name)
: name;
}
try {
Map<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
}
return c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
}
}
由上面的代碼可知在實(shí)例化Behavior時(shí)裁厅,會(huì)調(diào)用Behavior的參數(shù)類型為Context和AttributeSet的構(gòu)造函數(shù)冰沙,這也是為什么在自定義Behavior是必現(xiàn)要實(shí)現(xiàn)這個(gè)構(gòu)造函數(shù)的原因。
CoordinatorLayout如何管理自己的子View
由于CoordinatorLayout中的子View之間是具有依賴關(guān)系的执虹,所以將CoordinatorLayout中的子View按照依賴關(guān)系保存到Directed Acyclic Graph(定向無環(huán)圖)中拓挥,首先通過下圖直觀的看一下DAG:
DAG 沒有環(huán),不走回頭路袋励、永遠(yuǎn)不回頭侥啤、不斷向前進(jìn)当叭。 DAG 可以重新繪制,讓所有邊朝著同一個(gè)方向延伸拓展愿棋、讓所有點(diǎn)有著先后次序科展。
CoordinatorLayout的onMeasure方法中會(huì)調(diào)用CoordinatorLayout的prepareChildren方法,prepareChildren就是用來將CoordinatorLayout中的子View按照依賴關(guān)系保存到DAG 中并且進(jìn)行拓?fù)渑判颍?/p>
private final List<View> mDependencySortedChildren = new ArrayList<>();
private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
private void prepareChildren() {
mDependencySortedChildren.clear();
mChildDag.clear();
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);
mChildDag.addNode(view);
// Now iterate again over the other children, adding any dependencies to the graph
for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
final View other = getChildAt(j);
final LayoutParams otherLp = getResolvedLayoutParams(other);
if (otherLp.dependsOn(this, other, view)) {
if (!mChildDag.contains(other)) {
// Make sure that the other node is added
mChildDag.addNode(other);
}
// Now add the dependency to the graph
mChildDag.addEdge(view, other);
}
}
}
// Finally add the sorted graph list to our list
mDependencySortedChildren.addAll(mChildDag.getSortedList());
// We also need to reverse the result since we want the start of the list to contain
// Views which have no dependencies, then dependent views after that
Collections.reverse(mDependencySortedChildren);
}
上面的代碼將CoordinatorLayout的子View按照依賴關(guān)系保存到DAG中糠雨,具體的算法細(xì)節(jié)有興趣的同學(xué)可以自己研究一下才睹,這里就不在講解了,最終DAG的結(jié)果可以通過上圖進(jìn)行理解甘邀,將上圖的數(shù)字原點(diǎn)想象成View琅攘,那箭頭的方向就是被依賴的關(guān)系。
然后通過基于dfs算法的拓?fù)渑判?/a>對(duì)CoordinatorLayout子View的DAG圖進(jìn)行排序松邪,下面通過一張圖直觀的看一下拓?fù)渑判颍?br>
最終得到拓?fù)渑判蚝蟮牧斜碇蠽iew之間的依賴順序一定和列表中的View的順序相同坞琴,例如上圖中的依賴9節(jié)點(diǎn)的節(jié)點(diǎn)一定在節(jié)點(diǎn)9的后面。
自定義CoordinatorLayout.Behavior
View之間的相互作用就是一個(gè)View監(jiān)聽另一個(gè)View的變化從而做出響應(yīng)逗抑,View的變化可以概括的分為兩類:
- View的大小剧辐、在父布局中位置、顯示狀態(tài)等發(fā)生改變
- View自身的內(nèi)容發(fā)生改變(比如內(nèi)容發(fā)生移動(dòng))
注意:通過下面的源碼分析可知邮府,CoordinatorLayout是通過監(jiān)聽視圖樹的繪制來監(jiān)聽子View的第一類變化荧关,如果子View發(fā)生了第一類的變化并且繪制區(qū)域發(fā)生了改變就會(huì)通知依賴該子View的兄弟View。因此子View顯示狀態(tài)的變化只有從VISIBLE --> GONE變化時(shí)才會(huì)導(dǎo)致依賴該子View的兄弟View得到通知褂傀,而INVISIBLE --> GONE和VISIBLE --> INVISIBLE 不會(huì)導(dǎo)致依賴該子View的兄弟View得到通知忍啤。
1. View的第一類變化
CoordinatorLayout的子View的第一類變化肯定會(huì)導(dǎo)致CoordinatorLayout的子View的繪圖區(qū)域發(fā)生改變,從而被重繪仙辟,因此在CoordinatorLayout的onAttachedToWindow方法中監(jiān)聽了視圖樹的繪制同波,代碼如下:
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
resetTouchBehaviors();
if (mNeedsPreDrawListener) {
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
//添加視圖樹即將被繪制的監(jiān)聽器
vto.addOnPreDrawListener(mOnPreDrawListener);
}
if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
// We're set to fitSystemWindows but we haven't had any insets yet...
// We should request a new dispatch of window insets
ViewCompat.requestApplyInsets(this);
}
mIsAttachedToWindow = true;
}
當(dāng)視圖樹即將被重繪時(shí),OnPreDrawListener類的onPreDraw方法會(huì)被調(diào)用叠国,代碼如下:
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;
}
接著CoordinatorLayout類的onChildViewsChanged方法被調(diào)用未檩,部分代碼如下:
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
final Rect inset = mTempRect4;
inset.setEmpty();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
......
// Get the current draw rect of the view
final Rect drawRect = mTempRect1;
getChildRect(child, true, drawRect);
......
if (type == EVENT_PRE_DRAW) {
// Did it change? if not continue
final Rect lastDrawRect = mTempRect2;
getLastChildRect(child, lastDrawRect);
if (lastDrawRect.equals(drawRect)) {
continue;
}
recordLastChildRect(child, drawRect);
}
// Update any behavior-dependent views for the change
for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
//如果checkChild設(shè)置了layout_ behavior屬性且checkChild依賴于child
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
// If this is from a pre-draw and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch onDependentViewChanged()
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}
if (type == EVENT_NESTED_SCROLL) {
// If this is from a nested scroll, set the flag so that we may skip
// any resulting onPreDraw dispatch (if needed)
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
}
}
onChildViewsChanged首先會(huì)按照拓?fù)渑判蚝蟮捻樞虮闅vCoordinatorLayout子View,然后判斷子View的繪制區(qū)域是否發(fā)生了改變粟焊,如果發(fā)生了改變讹挎,接著會(huì)通過該子View的兄弟View的Behavior實(shí)例的layoutDependsOn方法判斷兄弟View是否依賴于該子View,如果依賴吆玖,則會(huì)調(diào)用兄弟View的Behavior實(shí)例的onDependentViewChanged方法筒溃。
到此CoordinatorLayout 處理子View第一類變化的過程的源碼分析完畢,舉例如下:
這里我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的效果沾乘,讓一個(gè)View根據(jù)另一個(gè)View上下左右移動(dòng)怜奖。
1> 首先我們來自定義一個(gè)繼承自CoordinatorLayout.Behavior的類DependentBehavior,如下所示:
public class DependentBehavior extends CoordinatorLayout.Behavior<View> {
private int initDisX = 0;
public DependentBehavior() {
}
public DependentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setInitDisX(int initDisX) {
this.initDisX = initDisX;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//如果dependency的類型是ImageView翅阵,則就可以被child依賴
return dependency instanceof ImageView;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//當(dāng)dependency發(fā)生移動(dòng)時(shí)歪玲,計(jì)算出child應(yīng)該偏移的距離迁央,然后讓child進(jìn)行偏移
int offsetX = (dependency.getLeft() - child.getLeft()) - initDisX;
int offsetY = dependency.getTop() - child.getTop();
child.offsetLeftAndRight(offsetX);
child.offsetTopAndBottom(offsetY);
return true;
}
}
2> 下面是應(yīng)用的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/iv_dependency"
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/second_pic"
android:scaleType="centerCrop"
android:layout_gravity="left|top"/>
<ImageView
android:id="@+id/iv_child"
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/third_pic"
android:scaleType="centerCrop"
android:layout_gravity="top|right"
android:layout_marginRight="64dp"
custom:layout_behavior="com.cytmxk.test.testmaterialdesign.DependentBehavior"/>
</android.support.design.widget.CoordinatorLayout>
3> 下面是上面布局文件對(duì)應(yīng)的fragment的源碼:
public class BehaviorFragment extends BaseFragment {
private View root;
private ImageView ivDependency;
private ImageView ivChild;
@Override
protected int getLayoutResId() {
return R.layout.fragment_behavior;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = super.onCreateView(inflater, container, savedInstanceState);
initView();
return root;
}
private int mLastX;
private int mLastY;
private void initView() {
ivDependency = (ImageView) root.findViewById(R.id.iv_dependency);
ivDependency.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int x = (int) motionEvent.getX();
int y = (int) motionEvent.getY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點(diǎn)坐標(biāo)
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 計(jì)算偏移量
int offsetX = x - mLastX;
int offsetY = y - mLastY;
ivDependency.offsetLeftAndRight(offsetX);
ivDependency.offsetTopAndBottom(offsetY);
break;
default:
break;
}
return true;
}
});
ivChild = (ImageView) root.findViewById(R.id.iv_child);
ivChild.postDelayed(new Runnable() {
@Override
public void run() {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams)ivChild.getLayoutParams();
final DependentBehavior dependentBehavior = (DependentBehavior) layoutParams.getBehavior();
dependentBehavior.setInitDisX(ivDependency.getLeft() - ivChild.getLeft());
}
}, 100);
}
}
最后的運(yùn)行結(jié)果如下:
上圖中左邊ImageView的運(yùn)動(dòng)是通過監(jiān)聽觸摸事件實(shí)現(xiàn)的,由于左邊ImageView的運(yùn)動(dòng)會(huì)導(dǎo)致視圖樹重繪并且左邊ImageView的繪制區(qū)域發(fā)生了改變滥崩,因此根據(jù)源碼分析自定義的DependentBehavior 類的layoutDependsOn會(huì)被調(diào)用岖圈,由于左邊ImageView的類型是ImageView類型,所以layoutDependsOn會(huì)返回true钙皮,layoutDependsOn返回true導(dǎo)致右邊ImageView會(huì)依賴于左邊ImageView蜂科,接著onDependentViewChanged方法會(huì)被調(diào)用,就可以在onDependentViewChanged方法中讓右邊ImageView對(duì)左邊ImageView的運(yùn)動(dòng)做出響應(yīng)短条,這樣就會(huì)實(shí)現(xiàn)左右兩張圖片的聯(lián)動(dòng)效果导匣。
layoutDependsOn和onDependentViewChanged方法的參數(shù)相同,第一個(gè)參數(shù)是CoordinatorLayout實(shí)例茸时,第二個(gè)參數(shù)是我們?cè)O(shè)置了Behavior的View贡定,第三個(gè)參數(shù)就是第二個(gè)參數(shù)依賴的View。
2. View的第二類變化
第二類的變化不會(huì)導(dǎo)致CoordinatorLayout子View的繪制區(qū)域發(fā)生改變可都,而是會(huì)導(dǎo)致CoordinatorLayout子View的內(nèi)容發(fā)生移動(dòng)缓待,由于繪制區(qū)域沒有發(fā)生改變,所以View的第一類變化的監(jiān)聽方法無法監(jiān)聽View的第二類變化渠牲。那么我們就已RecyclerView為例旋炒,因?yàn)镽ecyclerView作為CoordinatorLayout的子View時(shí)可以實(shí)現(xiàn)聯(lián)動(dòng)效果并且滑動(dòng)的是其內(nèi)容,我們首先通過一張時(shí)序圖直觀的理解RecyclerView實(shí)現(xiàn)嵌套滑動(dòng)的這個(gè)過程:
如上圖所示嘱兼,1、9贤徒、17步的操作都會(huì)觸發(fā)RecyclerView的onTouchEvent方法的執(zhí)行,
RecyclerView的部分相關(guān)源碼如下:
@Override
public boolean onTouchEvent(MotionEvent e) {
......
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
} break;
......
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
......
} break;
......
case MotionEvent.ACTION_UP: {
......
resetTouch();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
}
if (!eventAddedToVelocityTracker) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
private void resetTouch() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
stopNestedScroll();
releaseGlows();
}
private void cancelTouch() {
resetTouch();
setScrollState(SCROLL_STATE_IDLE);
}
在第1步中芹壕,當(dāng)用戶觸碰到RecyclerView所在的屏幕區(qū)域時(shí)會(huì)觸發(fā)MotionEvent.ACTION_DOWN事件,此時(shí)會(huì)調(diào)用startNestedScroll方法(第2步)接奈,傳給startNestedScroll方法的參數(shù)與
RecyclerView的滑動(dòng)方向有關(guān)(通常在為RecyclerView設(shè)置LayoutManager時(shí)設(shè)置RecyclerView的滑動(dòng)方向)踢涌;在第9步中,當(dāng)用戶在RecyclerView所在的屏幕區(qū)域上滑動(dòng)時(shí)會(huì)觸發(fā)MotionEvent.ACTION_MOVE事件序宦,此時(shí)會(huì)調(diào)用dispatchNestedPreScroll方法(第10步)睁壁,傳給dispatchNestedPreScroll方法的前兩個(gè)參數(shù)是RecyclerView的內(nèi)容在水平和垂直方向上偏移的距離;在第17步中互捌,當(dāng)用戶手指離開RecyclerView所在的屏幕區(qū)域時(shí)會(huì)觸發(fā)MotionEvent.ACTION_UP事件潘明,此時(shí)會(huì)調(diào)用resetTouch方法(第18步),resetTouch方法中會(huì)調(diào)用stopNestedScroll方法(第19步)秕噪。
下面我們來看一下上面提到的RecyclerView類中的startNestedScroll钳降、dispatchNestedPreScroll和stopNestedScroll方法的源碼:
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}
上面的三個(gè)方法會(huì)調(diào)用NestedScrollingChildHelper類的startNestedScroll(第3步)、dispatchNestedPreScroll(第11步)腌巾、stopNestedScroll(第20步)方法:
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dx != 0 || dy != 0) {
int startX = 0;
int startY = 0;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
if (consumed == null) {
if (mTempNestedScrollConsumed == null) {
mTempNestedScrollConsumed = new int[2];
}
consumed = mTempNestedScrollConsumed;
}
consumed[0] = 0;
consumed[1] = 0;
ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return consumed[0] != 0 || consumed[1] != 0;
} else if (offsetInWindow != null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
mNestedScrollingParent = null;
}
}
上面的三個(gè)方法會(huì)調(diào)用ViewParentCompat的onStartNestedScroll(第4步)遂填、onNestedPreScroll(第12步)铲觉、和onStopNestedScroll方法(第21步):
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}
public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
int[] consumed) {
IMPL.onNestedPreScroll(parent, target, dx, dy, consumed);
}
public static void onNestedScroll(ViewParent parent, View target, int dxConsumed,
int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
IMPL.onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
下面我們看一下ViewParentCompat中的如下一段代碼:
static final ViewParentCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new ViewParentCompatLollipopImpl();
} else if (version >= 19) {
IMPL = new ViewParentCompatKitKatImpl();
} else if (version >= 14) {
IMPL = new ViewParentCompatICSImpl();
} else {
IMPL = new ViewParentCompatStubImpl();
}
}
由于我的手機(jī)是L的手機(jī),所以上面的三個(gè)方法會(huì)調(diào)用ViewParentCompatLollipopImpl的onStartNestedScroll(第5步)吓坚、onNestedPreScroll(第13步)撵幽、和onStopNestedScroll方法(第22步):
public boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return ViewParentCompatLollipop.onStartNestedScroll(parent, child, target,
nestedScrollAxes);
}
@Override
public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
int[] consumed) {
ViewParentCompatLollipop.onNestedPreScroll(parent, target, dx, dy, consumed);
}
@Override
public void onStopNestedScroll(ViewParent parent, View target) {
ViewParentCompatLollipop.onStopNestedScroll(parent, target);
}
上面的3個(gè)方法會(huì)調(diào)用ViewParentCompatLollipop的onStartNestedScroll(第6步)、onNestedPreScroll(第14步)和onStopNestedScroll方法(第23步):
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
try {
return parent.onStartNestedScroll(child, target, nestedScrollAxes);
} catch (AbstractMethodError e) {
Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
"method onStartNestedScroll", e);
return false;
}
}
public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
int[] consumed) {
try {
parent.onNestedPreScroll(target, dx, dy, consumed);
} catch (AbstractMethodError e) {
Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
"method onNestedPreScroll", e);
}
}
public static void onStopNestedScroll(ViewParent parent, View target) {
try {
parent.onStopNestedScroll(target);
} catch (AbstractMethodError e) {
Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
"method onStopNestedScroll", e);
}
}
因?yàn)镃oordinatorLayout實(shí)現(xiàn)了ViewParent 接口礁击,所以
上面的三個(gè)方法會(huì)調(diào)用父布局(即CoordinatorLayout類)的startNestedScroll(第7步)盐杂、onNestedPreScroll(第15步)和onStopNestedScroll方法(第24步):
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
int xConsumed = 0;
int yConsumed = 0;
boolean accepted = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
mTempIntPair[0] = mTempIntPair[1] = 0;
viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
: Math.min(xConsumed, mTempIntPair[0]);
yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
: Math.min(yConsumed, mTempIntPair[1]);
accepted = true;
}
}
consumed[0] = xConsumed;
consumed[1] = yConsumed;
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public void onStopNestedScroll(View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onStopNestedScroll(this, view, target);
}
lp.resetNestedScroll();
lp.resetChangedAfterNestedScroll();
}
mNestedScrollingDirectChild = null;
mNestedScrollingTarget = null;
}
接著上面的三個(gè)方法會(huì)就會(huì)調(diào)用實(shí)現(xiàn)CoordinatorLayout.Behavior子類的onStartNestedScroll(第8步)、onNestedPreScroll(第16步)和onStopNestedScroll方法(第25步)客税。
注意:
在第3步到第8步的過程中况褪,會(huì)遍歷RecyclerView的所有祖先View,尋找第一個(gè)實(shí)現(xiàn)了ViewParent接口的onStartNestedScroll方法的祖先View并且該祖先View至少有一個(gè)設(shè)置了layout_behavior和behavior onStartNestedScroll方法的返回值為true的子View更耻,如果找到符合條件的祖先View测垛,第3步的方法就會(huì)返回true并且將該祖先View保存到NestedScrollingChildHelper實(shí)例的mNestedScrollingParent變量中,否者返回false秧均。
在1中食侮,判斷祖先View至少有一個(gè)設(shè)置了layout_behavior和behavior onStartNestedScroll方法的返回值為true的子View的過程是在第8步中完成的,當(dāng)某個(gè)子View的behavior onStartNestedScroll方法的返回值為true時(shí)目胡,就會(huì)調(diào)用該子View的LayoutParams acceptNestedScroll方法(參數(shù)為true)锯七,將該子View的LayoutParams中的mDidAcceptNestedScroll屬性設(shè)置為true。
在第11步中誉己,會(huì)直接將使用1中保存的mNestedScrollingParent當(dāng)做參數(shù)傳遞眉尸;在第15步中就會(huì)通過mNestedScrollingParent調(diào)用onNestedPreScroll方法。
在第16步中巨双,會(huì)遍歷mNestedScrollingParent的所有子View噪猾,如果子View的LayoutParams的mDidAcceptNestedScroll屬性為true并且設(shè)置了layout_behavior屬性,就會(huì)執(zhí)行該子View的behavior的onNestedPreScroll方法筑累。這也就證明了只有當(dāng)子View的behavior的onStartNestedScroll方法返回了true袱蜡,子View的behavior的onNestedPreScroll方法才有可能被執(zhí)行。
到此CoordinatorLayout 處理子View第二類變化的過程的源碼分析完畢慢宗,舉例如下:
這里我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的效果坪蚁,讓一個(gè)RecyclerView根據(jù)另一個(gè)RecyclerView上下滑動(dòng)而上下滑動(dòng)。
1> 首先我們來自定義一個(gè)繼承自CoordinatorLayout.Behavior的類ScrollBehavior镜沽,如下所示:
public class ScrollBehavior extends CoordinatorLayout.Behavior<View> {
public ScrollBehavior() {
}
public ScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
// 第一個(gè)參數(shù)就是CoordinatorLayout 實(shí)例敏晤,也就是當(dāng)前ScrollBehavior 實(shí)例對(duì)應(yīng)的View的祖先View
// 第二個(gè)參數(shù)就是當(dāng)前ScrollBehavior 實(shí)例對(duì)應(yīng)的View
// 第三個(gè)參數(shù)就是直接目標(biāo)View,比如第一個(gè)參數(shù)CoordinatorLayout 實(shí)例包含嵌套兩層的RecyclerView缅茉,那這個(gè)參數(shù)就是最外層的RecyclerView茵典。
// 第四個(gè)參數(shù)就是目標(biāo)View,比如第一個(gè)參數(shù)CoordinatorLayout 實(shí)例包含嵌套兩層的RecyclerView宾舅,那這個(gè)參數(shù)就是手指觸屏區(qū)域?qū)?yīng)的最內(nèi)層的RecyclerView统阿。
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
// 前三個(gè)參數(shù)與上面的相同彩倚,第4和第5個(gè)參數(shù)代表在水平和垂直方向上的偏移量
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
child.scrollBy(dx, dy);
}
}
2>下面是應(yīng)用的布局文件:
<?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:fitsSystemWindows="true"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview_dependency"
android:layout_gravity="left"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview_child"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="com.cytmxk.test.testmaterialdesign.ScrollBehavior" />
</android.support.design.widget.CoordinatorLayout>
3> 下面是上面布局文件對(duì)應(yīng)的fragment的源碼:
public class ScrollBehaviorFragment extends BaseFragment {
private View root;
private RecyclerView dependencyRV;
private RecyclerView childRV;
@Override
protected int getLayoutResId() {
return R.layout.fragment_scroll_behavior;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = super.onCreateView(inflater, container, savedInstanceState);
initView();
return root;
}
private void initView() {
dependencyRV = (RecyclerView) root.findViewById(R.id.recyclerview_dependency);
LinearLayoutManager layoutManager1 = new LinearLayoutManager(getActivity());
MyAdapter adapter1 = new MyAdapter();
dependencyRV.setLayoutManager(layoutManager1);
dependencyRV.setAdapter(adapter1);
childRV = (RecyclerView) root.findViewById(R.id.recyclerview_child);
LinearLayoutManager layoutManager2 = new LinearLayoutManager(getActivity());
MyAdapter adapter2 = new MyAdapter();
childRV.setLayoutManager(layoutManager2);
childRV.setAdapter(adapter2);
}
public class MyAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.my_view_holder_item, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder)holder).updateView("position : " + position);
}
@Override
public int getItemCount() {
return 100;
}
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textview);
}
void updateView(String text) {
textView.setText(text);
}
}
}
最后的運(yùn)行結(jié)果如下: