3.1.3_CoordinatorLayout原理

給子View設置Behavior的原理

如果我們在給CoordinatorLayout設置app:layout_behavior屬性

app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" 

CoordinatorLayout會去獲取layout_behavior屬性

this.mBehaviorResolved = a.hasValue(styleable.CoordinatorLayout_Layout_layout_behavior);
if (this.mBehaviorResolved) {
    this.mBehavior = CoordinatorLayout.parseBehavior(context, attrs, a.getString(styleable.CoordinatorLayout_Layout_layout_behavior));
}  

如果layout_behavior屬性存在,則會進行解析

static CoordinatorLayout.Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
       String fullName;
       if (name.startsWith(".")) { //如果沒有加報名,自動添加報名
           fullName = context.getPackageName() + name;
       } else if (name.indexOf(46) >= 0) {
           fullName = name;
       } else {
           fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME) ? WIDGET_PACKAGE_NAME + '.' + name : name;
       }

       try {
           Map<String, Constructor<CoordinatorLayout.Behavior>> constructors = (Map)sConstructors.get();
           if (constructors == null) {
               constructors = new HashMap();
               sConstructors.set(constructors);
           }

           Constructor<CoordinatorLayout.Behavior> c = (Constructor)((Map)constructors).get(fullName);
           if (c == null) {
               //反射加載Behavior
               Class<CoordinatorLayout.Behavior> clazz = context.getClassLoader().loadClass(fullName);
               c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
               c.setAccessible(true);
               ((Map)constructors).put(fullName, c);
           }

           return (CoordinatorLayout.Behavior)c.newInstance(context, attrs);
       } catch (Exception var7) {
           throw new RuntimeException("Could not inflate Behavior subclass " + fullName, var7);
       }
   }

可以看到,會通過反射來加載layout_behavior,並存儲到constructors這個Map中

CoordinatorLayout如何結合Behavior實現對子View的事件響應

父View(CoordinatorLayout)實現NestedScrollingParent2接口,子View(Recyclerview)實現NestedScrollingChild2接口

然後這兩個接口的實現會交由NestedScrollingChildHelper來實現

來看下Recyclerview,就有如下代碼

private NestedScrollingChildHelper getScrollingChildHelper() {
    if (this.mScrollingChildHelper == null) {
        this.mScrollingChildHelper = new NestedScrollingChildHelper(this);
    }

    return this.mScrollingChildHelper;
}  

public boolean startNestedScroll(int axes) {
    return this.getScrollingChildHelper().startNestedScroll(axes);
}

再來看NestedScrollingChildHelper

public boolean startNestedScroll(int axes, int type) {
    if (this.hasNestedScrollingParent(type)) {
        return true;
    } else {
        if (this.isNestedScrollingEnabled()) {
            ViewParent p = this.mView.getParent();

            for(View child = this.mView; p != null; p = p.getParent()) {
            	//調用父類的onStartNestedScroll -> 比如CoordinatorLayout 會遍歷子View,獲得子View的Behavior,調用Behavior的onStartNestedScroll方法
                if (ViewParentCompat.onStartNestedScroll(p, child, this.mView, axes, type)) {
                    this.setNestedScrollingParentForType(type, p);
                    //調用父類onNestedScrollAccepted
                    ViewParentCompat.onNestedScrollAccepted(p, child, this.mView, axes, type);
                    return true;
                }

                if (p instanceof View) {
                    child = (View)p;
                }
            }
        }

        return false;
    }
}

可以看到,最終會遍歷子View,獲得子View的Behavior,調用Behavior的onStartNestedScroll和onNestedScrollAccepted方法

同理,Recyclerview的dispatchNestedScroll通過NestedScrollingChildHelper也會調用onNestedScroll方法

來看NestedScrollingChildHelper#dispatchNestedScroll

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
    if (this.isNestedScrollingEnabled()) {
        ViewParent parent = this.getNestedScrollingParentForType(type);
        if (parent == null) {
            return false;
        }

        if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
            int startX = 0;
            int startY = 0;
            if (offsetInWindow != null) {
                this.mView.getLocationInWindow(offsetInWindow);
                startX = offsetInWindow[0];
                startY = offsetInWindow[1];
            }

            ViewParentCompat.onNestedScroll(parent, this.mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
            if (offsetInWindow != null) {
                this.mView.getLocationInWindow(offsetInWindow);
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }

            return true;
        }

        if (offsetInWindow != null) {
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }

    return false;
}

以一個自定義的Behavior爲例,可以看到調用的對應方法

public class ScaleBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {

    private FastOutLinearInInterpolator mFastOutLinearInInterpolator = new FastOutLinearInInterpolator();
    private LinearOutSlowInInterpolator mLinearOutSlowInInterpolator = new LinearOutSlowInInterpolator();

    public ScaleBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL; //垂直滾動
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
        if (dyConsumed > 0 && !isRunning && child.getVisibility() == View.VISIBLE){//向下滑動,縮放隱藏控件
            scaleHide(child);
        }else if (dyConsumed < 0 && !isRunning && child.getVisibility() == View.INVISIBLE){ //向上滑動,縮放顯示控件
            scaleShow(child);
        }
    }

    private boolean isRunning;

    private void scaleShow(V child) {
        child.setVisibility(View.VISIBLE);
        ViewCompat.animate(child)
                .scaleX(1)
                .scaleY(1)
                .setDuration(500)
                .setInterpolator(mLinearOutSlowInInterpolator)
                .setListener(new ViewPropertyAnimatorListenerAdapter(){
                    @Override
                    public void onAnimationStart(View view) {
                        super.onAnimationStart(view);
                        isRunning = true;
                    }

                    @Override
                    public void onAnimationEnd(View view) {
                        super.onAnimationEnd(view);
                        isRunning = false;
                    }

                    @Override
                    public void onAnimationCancel(View view) {
                        super.onAnimationCancel(view);
                        isRunning = false;
                    }
                });
    }

    private void scaleHide(final V child) {
        ViewCompat.animate(child)
                .scaleX(0)
                .scaleY(0)
                .setDuration(500)
                .setInterpolator(mFastOutLinearInInterpolator)
                .setListener(new ViewPropertyAnimatorListenerAdapter(){
                    @Override
                    public void onAnimationStart(View view) {
                        super.onAnimationStart(view);
                        isRunning = true;
                    }

                    @Override
                    public void onAnimationEnd(View view) {
                        super.onAnimationEnd(view);
                        isRunning = false;
                        child.setVisibility(View.INVISIBLE);
                    }

                    @Override
                    public void onAnimationCancel(View view) {
                        super.onAnimationCancel(view);
                        isRunning = false;
                    }
                });
    }
    
	@Override
    public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes, type);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章