概述
Google官方对它的概述如下:
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 是一个增强版的FrameLayout。(继承自ViewGroup)
主要有两个用途:
1、作为应用的顶层视图
2、作为一个可以指定子views之间相互作用的容器,通过给CoordinatorLayout的子View指定CoordinatorLayout.Behavior来提供子view之间不同的相互作用,也就是说可以通过自定义CoordinatorLayout.Behavior来定义子views之间的相互作用。
CoordinatorLayout核心就在于协调子View之间的相互作用,而子View之间的相互作用是通过CoordinatorLayout.Behavior来定义的,Google实现了几个继承自CoordinatorLayout.Behavior的类:
注意:上面白底的是我自己自定义的Behavior,大家不必关心这两个。
CoordinatorLayout处理子View的layout_behavior属性的源码分析
通过layout_behavior的名称就可以知道,这个属性肯定是CoordinatorLayout.LayoutParams解析的,那我们就看一下CoordinatorLayout.LayoutParams构造方法:
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);
}
}
从上面的代码中可以得知,会先判断CoordinatorLayout的子View是否设置CoordinatorLayout_Layout_layout_behavior(即在布局文件中设置的layout_behavior属性)属性,如果在子View的布局中设置了layout_behavior属性,就会调用CoordinatorLayout类的parseBehavior方法,在该方法中会通过反射技术实例化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> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor c = constructors.get(fullName);
if (c == null) {
final Class clazz = (Class) 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);
}
}
由上面的代码可知在实例化Behavior时,会调用Behavior的参数类型为Context和AttributeSet的构造函数,这也是为什么在自定义Behavior是必现要实现这个构造函数的原因。
CoordinatorLayout如何管理自己的子View
由于CoordinatorLayout中的子View之间是具有依赖关系的,所以将CoordinatorLayout中的子View按照依赖关系保存到Directed Acyclic Graph(定向无环图)中,首先通过下图直观的看一下DAG:
DAG 沒有环,不走回头路、永远不回头、不断向前进。 DAG 可以重新绘制,让所有边朝着同一个方向延伸拓展、让所有点有着先后次序。
CoordinatorLayout的onMeasure方法中会调用CoordinatorLayout的prepareChildren方法,prepareChildren就是用来将CoordinatorLayout中的子View按照依赖关系保存到DAG 中并且进行拓扑排序:
private final List mDependencySortedChildren = new ArrayList<>();
private final DirectedAcyclicGraph 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按照依赖关系保存到DAG中,具体的算法细节有兴趣的同学可以自己研究一下,这里就不在讲解了,最终DAG的结果可以通过上图进行理解,将上图的数字原点想象成View,那箭头的方向就是被依赖的关系。
然后通过基于dfs算法的拓扑排序对CoordinatorLayout子View的DAG图进行排序,下面通过一张图直观的看一下拓扑排序:
最终得到拓扑排序后的列表中View之间的依赖顺序一定和列表中的View的顺序相同,例如上图中的依赖9节点的节点一定在节点9的后面。
自定义CoordinatorLayout.Behavior
View之间的相互作用就是一个View监听另一个View的变化从而做出响应,View的变化可以概括的分为两类:
- View的大小、在父布局中位置、显示状态等发生改变
- View自身的内容发生改变(比如内容发生移动)
注意:通过下面的源码分析可知,CoordinatorLayout是通过监听视图树的绘制来监听子View的第一类变化,如果子View发生了第一类的变化并且绘制区域发生了改变就会通知依赖该子View的兄弟View。因此子View显示状态的变化只有从VISIBLE --> GONE变化时才会导致依赖该子View的兄弟View得到通知,而INVISIBLE --> GONE和VISIBLE --> INVISIBLE 不会导致依赖该子View的兄弟View得到通知。
1. View的第一类变化
CoordinatorLayout的子View的第一类变化肯定会导致CoordinatorLayout的子View的绘图区域发生改变,从而被重绘,因此在CoordinatorLayout的onAttachedToWindow方法中监听了视图树的绘制,代码如下:
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
resetTouchBehaviors();
if (mNeedsPreDrawListener) {
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
//添加视图树即将被绘制的监听器
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;
}
当视图树即将被重绘时,OnPreDrawListener类的onPreDraw方法会被调用,代码如下:
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;
}
接着CoordinatorLayout类的onChildViewsChanged方法被调用,部分代码如下:
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设置了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首先会按照拓扑排序后的顺序遍历CoordinatorLayout子View,然后判断子View的绘制区域是否发生了改变,如果发生了改变,接着会通过该子View的兄弟View的Behavior实例的layoutDependsOn方法判断兄弟View是否依赖于该子View,如果依赖,则会调用兄弟View的Behavior实例的onDependentViewChanged方法。
到此CoordinatorLayout 处理子View第一类变化的过程的源码分析完毕,举例如下:
这里我们实现一个简单的效果,让一个View根据另一个View上下左右移动。
1> 首先我们来自定义一个继承自CoordinatorLayout.Behavior的类DependentBehavior,如下所示:
public class DependentBehavior extends CoordinatorLayout.Behavior {
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) {
//当dependency发生移动时,计算出child应该偏移的距离,然后让child进行偏移
int offsetX = (dependency.getLeft() - child.getLeft()) - initDisX;
int offsetY = dependency.getTop() - child.getTop();
child.offsetLeftAndRight(offsetX);
child.offsetTopAndBottom(offsetY);
return true;
}
}
2> 下面是应用的布局文件:
3> 下面是上面布局文件对应的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:
// 记录触摸点坐标
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
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);
}
}
最后的运行结果如下:
上图中左边ImageView的运动是通过监听触摸事件实现的,由于左边ImageView的运动会导致视图树重绘并且左边ImageView的绘制区域发生了改变,因此根据源码分析自定义的DependentBehavior 类的layoutDependsOn会被调用,由于左边ImageView的类型是ImageView类型,所以layoutDependsOn会返回true,layoutDependsOn返回true导致右边ImageView会依赖于左边ImageView,接着onDependentViewChanged方法会被调用,就可以在onDependentViewChanged方法中让右边ImageView对左边ImageView的运动做出响应,这样就会实现左右两张图片的联动效果。
layoutDependsOn和onDependentViewChanged方法的参数相同,第一个参数是CoordinatorLayout实例,第二个参数是我们设置了Behavior的View,第三个参数就是第二个参数依赖的View。
2. View的第二类变化
第二类的变化不会导致CoordinatorLayout子View的绘制区域发生改变,而是会导致CoordinatorLayout子View的内容发生移动,由于绘制区域没有发生改变,所以View的第一类变化的监听方法无法监听View的第二类变化。那么我们就已RecyclerView为例,因为RecyclerView作为CoordinatorLayout的子View时可以实现联动效果并且滑动的是其内容,我们首先通过一张时序图直观的理解RecyclerView实现嵌套滑动的这个过程:
如上图所示,1、9、17步的操作都会触发RecyclerView的onTouchEvent方法的执行,
RecyclerView的部分相关源码如下:
@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步中,当用户触碰到RecyclerView所在的屏幕区域时会触发MotionEvent.ACTION_DOWN事件,此时会调用startNestedScroll方法(第2步),传给startNestedScroll方法的参数与
RecyclerView的滑动方向有关(通常在为RecyclerView设置LayoutManager时设置RecyclerView的滑动方向);在第9步中,当用户在RecyclerView所在的屏幕区域上滑动时会触发MotionEvent.ACTION_MOVE事件,此时会调用dispatchNestedPreScroll方法(第10步),传给dispatchNestedPreScroll方法的前两个参数是RecyclerView的内容在水平和垂直方向上偏移的距离;在第17步中,当用户手指离开RecyclerView所在的屏幕区域时会触发MotionEvent.ACTION_UP事件,此时会调用resetTouch方法(第18步),resetTouch方法中会调用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();
}
上面的三个方法会调用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;
}
}
上面的三个方法会调用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();
}
}
由于我的手机是L的手机,所以上面的三个方法会调用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个方法会调用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);
}
}
因为CoordinatorLayout实现了ViewParent 接口,所以
上面的三个方法会调用父布局(即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;
}
接着上面的三个方法会就会调用实现CoordinatorLayout.Behavior子类的onStartNestedScroll(第8步)、onNestedPreScroll(第16步)和onStopNestedScroll方法(第25步)。
注意:
在第3步到第8步的过程中,会遍历RecyclerView的所有祖先View,寻找第一个实现了ViewParent接口的onStartNestedScroll方法的祖先View并且该祖先View至少有一个设置了layout_behavior和behavior onStartNestedScroll方法的返回值为true的子View,如果找到符合条件的祖先View,第3步的方法就会返回true并且将该祖先View保存到NestedScrollingChildHelper实例的mNestedScrollingParent变量中,否者返回false。
在1中,判断祖先View至少有一个设置了layout_behavior和behavior onStartNestedScroll方法的返回值为true的子View的过程是在第8步中完成的,当某个子View的behavior onStartNestedScroll方法的返回值为true时,就会调用该子View的LayoutParams acceptNestedScroll方法(参数为true),将该子View的LayoutParams中的mDidAcceptNestedScroll属性设置为true。
在第11步中,会直接将使用1中保存的mNestedScrollingParent当做参数传递;在第15步中就会通过mNestedScrollingParent调用onNestedPreScroll方法。
在第16步中,会遍历mNestedScrollingParent的所有子View,如果子View的LayoutParams的mDidAcceptNestedScroll属性为true并且设置了layout_behavior属性,就会执行该子View的behavior的onNestedPreScroll方法。这也就证明了只有当子View的behavior的onStartNestedScroll方法返回了true,子View的behavior的onNestedPreScroll方法才有可能被执行。
到此CoordinatorLayout 处理子View第二类变化的过程的源码分析完毕,举例如下:
这里我们实现一个简单的效果,让一个RecyclerView根据另一个RecyclerView上下滑动而上下滑动。
1> 首先我们来自定义一个继承自CoordinatorLayout.Behavior的类ScrollBehavior,如下所示:
public class ScrollBehavior extends CoordinatorLayout.Behavior {
public ScrollBehavior() {
}
public ScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
// 第一个参数就是CoordinatorLayout 实例,也就是当前ScrollBehavior 实例对应的View的祖先View
// 第二个参数就是当前ScrollBehavior 实例对应的View
// 第三个参数就是直接目标View,比如第一个参数CoordinatorLayout 实例包含嵌套两层的RecyclerView,那这个参数就是最外层的RecyclerView。
// 第四个参数就是目标View,比如第一个参数CoordinatorLayout 实例包含嵌套两层的RecyclerView,那这个参数就是手指触屏区域对应的最内层的RecyclerView。
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
// 前三个参数与上面的相同,第4和第5个参数代表在水平和垂直方向上的偏移量
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>下面是应用的布局文件:
3> 下面是上面布局文件对应的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);
}
}
}
最后的运行结果如下: