Behaviour源碼分析

Behaviour如何進行實例化

簡單介紹下CoordinatorLayout的LayoutParams每次被子View觸發后,就會走到Behaviour的實例化方法,具體看下圖。
實例化過程如圖所示:


實例化過程
  1. 關鍵代碼
static Behavior parseBehavior(Context context, AttributeSet attrs, String 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);
        }
    }

layoutDependsOn方法

  1. 在CoordinatorLayout初始化時候,會在prepareChildren方法里面遍歷子View,其中layoutDependsOn的返回值會作為是否添加依賴關系的判斷。
private void prepareChildren() {
            ……
            // 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);
                //dependsOn方法里會有layoutDependsOn方法作為判斷條件之一
                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);
                }
            }
        }
  1. 在每次子View發生變化時,CoordinatorLayout都會遍歷子View,給其對應的Behaviour回調一次layoutDependsOn方法,確保有依賴關系的View能進行相應的變化。
final void onChildViewsChanged(@DispatchChangeEvent final int type) {  
        ………………………………
        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;
            }
            ………………………………
            // 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:
                            //告知依賴checkChild,被依賴的child有可能發生變化了
                            // 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);
                    }
                }
            }
        }
        ……
    }

2-1. onChildViewsChanged所有的回調地方如圖所示


回調時機

onDependentViewChanged方法

  1. CoordinatorLayout在onChildViewsChanged根據type的狀態來決定有可能會調用到。
    其中type的值有:EVENT_PRE_DRAW = 0 ; EVENT_NESTED_SCROLL = 1; EVENT_VIEW_REMOVED = 2;
    由上面的代碼可以看出,除了REMOVED狀態的以外,其他的都會走onDependentViewChanged的回調。

  2. 調用者可以通過dispatchDependentViewsChanged手動觸發View對應的Behaviour的onDependentViewChanged方法。
    :還有部分調用我沒講,大家有興趣可以自己去源碼看下。

onLayoutChild方法

  1. 這個方法明眼人一看就明白是擺放子View位置的,所以這個方法的回調也依賴于CoordinatorLayout的onLayout方法,當onLayout方法執行時候,會遍歷自身所有的子View,獲取他們對應的behaviour值,然后回調behaviour的onLayoutChild方法。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }

總結

  1. 關于Behaviour必須依賴在CoordinatorLayout布局里面才會生效,原因從源碼分析可知:Behaviour是作為CoordinatorLayout的layoutParams一部分進行實例化的。
  2. 自定義Behaviour的demo教程自定義Behaviour
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。