怎么自定義behavior?
自定義behavior的幾個重載方法的參數(shù)有何意義(何為消耗)?
什么是嵌套滑動雁芙?Behavior里有dependency這個依賴和嵌套滑動有關(guān)系
么?
CoordinatorLayout內(nèi)一定要有appbarlayout?亦或是CollapsingToolbarLayout钞螟?
這幾個問題相信很多人都覺得似懂非懂,如果你對事件分發(fā)兔甘,view的機(jī)制冥然于心的話,那分析出coordinatorLayout自然這幾個問題也就引刃而解了鳞滨,首先我們從CoordinatorLayout的onMeasure開始說起(基于android-27)的源碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
...
...}
我們可以看到首先調(diào)用了這兩個很重要的方法,首先看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);
}
很明顯關(guān)鍵方法是
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的對應(yīng)的behavior,前提是mBehaviorResolved為空拯啦,引出了第一個概念
behavior,其實(shí)啊如果看過你必須了解的LayoutParams的那些事兒就知道在addview的時候
就會創(chuàng)建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);
}
}
我們可以看到它反射了構(gòu)造器來創(chuàng)建對象褒链,這里說一下唁情,這個構(gòu)造器
的參數(shù)必須是2個參數(shù)。
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[]{
Context.class,
AttributeSet.class
};
所以自定義behavior必須要兩個參數(shù)的構(gòu)造
那我們知道了創(chuàng)建behavior第一種是在xml里甫匹,第二種在onMeasure通過注解甸鸟,而且xml的優(yōu)先級顯然比注解的優(yōu)先級高。
那我們在回到一開始的prepareChildren方法兵迅,我們可以看到下面又出現(xiàn)了一個 if (lp.dependsOn(this, view, other))這個方法.
behavior里的依賴關(guān)系
這里我們舉個最簡單的例子
<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發(fā)現(xiàn)了依賴的代碼
@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的參數(shù)child是nestScrolledView
dependency如果是appBarLayout此方法就返回true,當(dāng)然在這個
prepareChildren里面調(diào)動是為了做排序讓被依賴的放在在這個列表的前面,比如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();
}
}
}
這個方法最終會調(diào)用onChildViewsChanged()這里的參數(shù)是EVENT_PRE_DRAW同時會注冊一個preDraw的監(jiān)聽,具體那里調(diào)用可以看下淺談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扯夭,然后對應(yīng)的在每個view上畫出rect的大小鳍贾,然后每次改變都會改變rect的大小,從后文的for循環(huán)里知道j=i+1開始勉抓,舉個例子有a,b,c三個view贾漏,a在第一個,b和c都依賴于它藕筋,那當(dāng)在onMeasure時就會調(diào)用b,c的onDependentViewChanged方法纵散,因?yàn)楸灰蕾嚨挠肋h(yuǎn)在后面。
而
總結(jié):
了解了behavior的創(chuàng)建過程隐圾。
知道了onDependentViewChanged第一個調(diào)用的地方伍掀。
依賴的產(chǎn)生原理。