怎么自定义behavior?
自定义behavior的几个重载方法的参数有何意义(何为消耗)?
什么是嵌套滑动?Behavior里有dependency这个依赖和嵌套滑动有关系
么?
CoordinatorLayout内一定要有appbarlayout?亦或是CollapsingToolbarLayout?
这几个问题相信很多人都觉得似懂非懂,如果你对事件分发,view的机制冥然于心的话,那分析出coordinatorLayout自然这几个问题也就引刃而解了,首先我们从CoordinatorLayout的onMeasure开始说起(基于android-27)的源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
...
...}
我们可以看到首先调用了这两个很重要的方法,首先看prepareChildren
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);
if (lp.dependsOn(this, view, other)) {
if (!mChildDag.contains(other)) {
// Make sure that the other node is added
mChildDag.addNode(other);
}
// Now add the dependency to the graph
mChildDag.addEdge(other, view);
}
}
}
// 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);
}
很明显关键方法是
LayoutParams getResolvedLayoutParams(View child) {
final LayoutParams result = (LayoutParams) child.getLayoutParams();
if (!result.mBehaviorResolved) {
if (child instanceof AttachedBehavior) {
Behavior attachedBehavior = ((AttachedBehavior) child).getBehavior();
if (attachedBehavior == null) {
Log.e(TAG, "Attached behavior class is null");
}
result.setBehavior(attachedBehavior);
result.mBehaviorResolved = true;
} else {
// The deprecated path that looks up the attached behavior based on annotation
Class<?> childClass = child.getClass();
DefaultBehavior defaultBehavior = null;
while (childClass != null
&& (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class))
== null) {
childClass = childClass.getSuperclass();
}
if (defaultBehavior != null) {
try {
result.setBehavior(
defaultBehavior.value().getDeclaredConstructor().newInstance());
} catch (Exception e) {
Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName()
+ " could not be instantiated. Did you forget"
+ " a default constructor?", e);
}
}
result.mBehaviorResolved = true;
}
}
return result;
}
这个是啥意思呢,很明显就是在onMeasure时通过注解获取view的对应的behavior,前提是mBehaviorResolved为空,引出了第一个概念
behavior,其实啊如果看过你必须了解的LayoutParams的那些事儿就知道在addview的时候
就会创建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);
}
}
截取部分代码看到是通过一个parseBehavior方法把一个String类型的
layout_behavior,变成了一个类,parseBehavior如下所示:
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>) context.getClassLoader()
.loadClass(fullName);
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);
}
}
我们可以看到它反射了构造器来创建对象,这里说一下,这个构造器
的参数必须是2个参数。
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[]{
Context.class,
AttributeSet.class
};
所以自定义behavior必须要两个参数的构造
那我们知道了创建behavior第一种是在xml里,第二种在onMeasure通过注解,而且xml的优先级显然比注解的优先级高。
那我们在回到一开始的prepareChildren方法,我们可以看到下面又出现了一个 if (lp.dependsOn(this, view, other))这个方法.
behavior里的依赖关系
这里我们举个最简单的例子
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/app_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
这个布局相信大家很熟悉,我们可以看到NestedScrollView有个app:layout_behavior是appBarLayout的ScrollingViewBehavior,AppBarLayout也有behavior(用注解表示)是
public static class Behavior extends HeaderBehavior<AppBarLayout> {
private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
private static final int INVALID_POSITION = -1;
...
}
我们在ScrollingViewBehavior发现了依赖的代码
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
// We depend on any AppBarLayouts
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}
由此我们知道layoutDependsOn的参数child是nestScrolledView
dependency如果是appBarLayout此方法就返回true,当然在这个
prepareChildren里面调动是为了做排序让被依赖的放在在这个列表的前面,比如nestScrolledView依赖于appBarLayout,那么appBarLayout肯定在nestScrolledView的前边。也就是说布局文件里
你把nestScrolledView写在appBarLayout上面依然是appBarLayout在第一个view.
接下来看第二个方法ensurePreDrawListener
void ensurePreDrawListener() {
boolean hasDependencies = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (hasDependencies(child)) {
hasDependencies = true;
break;
}
}
if (hasDependencies != mNeedsPreDrawListener) {
if (hasDependencies) {
addPreDrawListener();
} else {
removePreDrawListener();
}
}
}
这个方法最终会调用onChildViewsChanged()这里的参数是EVENT_PRE_DRAW同时会注册一个preDraw的监听,具体那里调用可以看下浅谈ondraw的前世今身
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
final Rect inset = acquireTempRect();
final Rect drawRect = acquireTempRect();
final Rect lastDrawRect = acquireTempRect();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
// Do not try to update GONE child views in pre draw updates.
continue;
}
// Check child views before for anchor
for (int j = 0; j < i; j++) {
final View checkChild = mDependencySortedChildren.get(j);
if (lp.mAnchorDirectChild == checkChild) {
offsetChildToAnchor(child, layoutDirection);
}
}
// Get the current draw rect of the view
getChildRect(child, true, drawRect);
// Accumulate inset sizes
if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
final int absInsetEdge = GravityCompat.getAbsoluteGravity(
lp.insetEdge, layoutDirection);
switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
inset.top = Math.max(inset.top, drawRect.bottom);
break;
case Gravity.BOTTOM:
inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
break;
}
switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
inset.left = Math.max(inset.left, drawRect.right);
break;
case Gravity.RIGHT:
inset.right = Math.max(inset.right, getWidth() - drawRect.left);
break;
}
}
// Dodge inset edges if necessary
if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
offsetChildByInset(child, inset, layoutDirection);
}
if (type != EVENT_VIEW_REMOVED) {
// Did it change? if not continue
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();
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);
}
}
}
}
releaseTempRect(inset);
releaseTempRect(drawRect);
releaseTempRect(lastDrawRect);
}
这段代码首先新建了三个rect,然后对应的在每个view上画出rect的大小,然后每次改变都会改变rect的大小,从后文的for循环里知道j=i+1开始,举个例子有a,b,c三个view,a在第一个,b和c都依赖于它,那当在onMeasure时就会调用b,c的onDependentViewChanged方法,因为被依赖的永远在后面。
而
总结:
了解了behavior的创建过程。
知道了onDependentViewChanged第一个调用的地方。
依赖的产生原理。