DrawerLayout 源碼分析

簡介

DrawerLayout充當窗口內(nèi)容的頂層容器,允許"抽屜"式的控件可以從窗口的一邊或者兩邊垂直邊緣拉出

使用

抽屜的位置或者布局可以通過android:layout_gravity子view的屬性控制從那邊拉出根穷,left/start代表從左邊拉出,right/end代表從右側(cè)拉出浪漠,需要注意的是只能有一個抽屜控件從窗口的垂直邊緣奏属,如果布局中每個垂直窗口有多于一個抽屜控件木柬,將會拋出異常

根布局使用DrawerLayout作為第一個主內(nèi)容布局间学,主內(nèi)容布局寬高設置為match_parent不用設置layout_gravity,然后在主內(nèi)容布局上添加子控件拒担,并且設置layout_gravity

 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"  
     xmlns:tools="http://schemas.android.com/tools"  
     android:id="@+id/drawerlayout"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent" > 
      
     <FrameLayout  
         android:id="@+id/fragment_layout"  
         android:layout_width="match_parent"  
         android:layout_height="match_parent" >  
     </FrameLayout>  
   
     <RelativeLayout  
         android:id="@+id/left"  
         android:layout_width="200dp"  
         android:layout_height="match_parent"  
         android:layout_gravity="left"   
         android:background="@android:color/white">  
    
         <ListView  
             android:id="@+id/left_listview"  
             android:layout_width="match_parent"  
             android:layout_height="match_parent" >  
         </ListView>  
     </RelativeLayout>  
    
     <RelativeLayout  
         android:id="@+id/right"  
         android:layout_width="260dp"  
         android:layout_height="match_parent"  
         android:layout_gravity="right"   
         android:background="@android:color/holo_green_light">  
   
         <TextView  
             android:id="@+id/right_textview"  
             android:layout_width="match_parent"  
             android:layout_height="match_parent"  
             android:text="個人登陸頁面" />  
     </RelativeLayout>  
     
 </android.support.v4.widget.DrawerLayout> 

詳細代碼請參考 http://blog.csdn.net/elinavampire/article/details/41477525

源碼分析

構造函數(shù)

public DrawerLayout(Context context) {
    this(context, null);
}
public DrawerLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);    
 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    final float density = getResources().getDisplayMetrics().density;
    mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
    final float minVel = MIN_FLING_VELOCITY * density;
    mLeftCallback = new ViewDragCallback(Gravity.LEFT);
    mRightCallback = new ViewDragCallback(Gravity.RIGHT);
    mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    mLeftDragger.setMinVelocity(minVel);
    mLeftCallback.setDragger(mLeftDragger);
    mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
    mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    mRightDragger.setMinVelocity(minVel);
    mRightCallback.setDragger(mRightDragger);
    // So that we can catch the back button
    setFocusableInTouchMode(true);
    ViewCompat.setImportantForAccessibility(this,            ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); 
   ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
    if (ViewCompat.getFitsSystemWindows(this)) {
        IMPL.configureApplyInsets(this);
        mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
    }
    mDrawerElevation = DRAWER_ELEVATION * density;
    mNonDrawerViews = new ArrayList<View>();
}

構造函數(shù)中嘹屯,設置view group的初始焦點,根據(jù)手機密度計算出Drawermargin值从撼,初始化從左側(cè)邊緣拉出來的布局的回掉監(jiān)聽和從右側(cè)邊緣拉出來的布局的回掉監(jiān)聽州弟,其中,在DrawerLayout的源碼是的滑動部分使用的是ViewDragHelper,所以要初始化左側(cè)的滑動和右側(cè)的滑動,設置觸摸時的焦點婆翔,初始化view的List

ViewDragHelper的回調(diào)ViewDragCallback

其中初始化的過程中有個很重要的方法拯杠,就是ViewDragHelper的回掉,下面我們就來看一下ViewDragCallback

private class ViewDragCallback extends ViewDragHelper.Callback {
    private final int mAbsGravity;
    private ViewDragHelper mDragger;
    private final Runnable mPeekRunable = new Runnable() {
        @Override public void run() {
            peekDrawer();
        }
    };
   // 注明拖拽的方向
    public ViewDragCallback(int gravity) {
        mAbsGravity = gravity;
    }
    public void setDragger(ViewDragHelper dragger) {
        mDragger = dragger;
    }
   // 移除方法回掉
    public void removeCallbacks() {
        DrawerLayout.this.removeCallbacks(mPeekRunnable);
    }
   // 當前child是拖拽的view啃奴,并且當前拖拽是當前設置的方向潭陪,并且當前的child可以拖拽
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        // Only capture views where the gravity matches what we're looking for.
        // This lets us use two ViewDragHelpers, one for each side drawer. 
       return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)                && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
    }
   // 當前拖拽的view的狀態(tài)發(fā)生變化時,更新拖拽狀態(tài)
    @Override
    public void onViewDragStateChanged(int state) {
        updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
    }
   // 當view的位置發(fā)生變化時最蕾,重新布局
    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        float offset;
        final int childWidth = changedView.getWidth();
        // This reverses the positioning shown in onLayout.
        if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
            offset = (float) (childWidth + left) / childWidth;
        } else {
            final int width = getWidth();
            offset = (float) (width - left) / childWidth;
        }
        setDrawerViewOffset(changedView, offset);
        changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
        invalidate();
    }
   // view開始被拖拽
    @Override
    public void onViewCaptured(View capturedChild, int activePointerId) {
        final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
        lp.isPeeking = false;
        closeOtherDrawer();
    }
   // 確認當前拖拽的方向依溯,關閉掉其他方向的拖拽
    private void closeOtherDrawer() {
        final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
        final View toClose = findDrawerWithGravity(otherGrav);
        if (toClose != null) {
            closeDrawer(toClose);
        }
    }
   // 被拖拽的被回掉時調(diào)用,先獲得子view的寬瘟则,然后計算出左邊距黎炉,滑動到指定位置
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        // Offset is how open the drawer is, therefore left/right values
        // are reversed from one another.
        final float offset = getDrawerViewOffset(releasedChild);
        final int childWidth = releasedChild.getWidth();
        int left;
        if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
            left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
        } else {
            final int width = getWidth();
            left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width;
        } 
       mDragger.settleCapturedViewAt(left, releasedChild.getTop());
        invalidate();
    }
   //觸摸到邊緣時回掉函數(shù)
    @Override
    public void onEdgeTouched(int edgeFlags, int pointerId) {
        postDelayed(mPeekRunnable, PEEK_DELAY);
    }
  // 根據(jù)拖拽的方向計算出view的左側(cè)位置,判斷是哪個方向滑動壹粟,如果是單側(cè)劃定關閉另一側(cè)的view拜隧,取消另一側(cè)的滑動
    private void peekDrawer() {
        final View toCapture;
        final int childLeft;
        final int peekDistance = mDragger.getEdgeSize();
        final boolean leftEdge = mAbsGravity == Gravity.LEFT;
        if (leftEdge) {
            toCapture = findDrawerWithGravity(Gravity.LEFT); 
           childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
        } else { 
            toCapture = findDrawerWithGravity(Gravity.RIGHT);
            childLeft = getWidth() - peekDistance;
        }
        // Only peek if it would mean making the drawer more visible and the drawer isn't locked
        if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) ||                (!leftEdge && toCapture.getLeft() > childLeft)) &&                getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
            final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
            mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
            lp.isPeeking = true;
            invalidate();
            closeOtherDrawer();
            cancelChildViewTouch();
        }
    }
   // 是否鎖定邊緣,如果鎖定邊緣趁仙,view不為空并且view不能拖拽洪添,關閉view的抽屜
    @Override
    public boolean onEdgeLock(int edgeFlags) {
        if (ALLOW_EDGE_LOCK) {
            final View drawer = findDrawerWithGravity(mAbsGravity);
            if (drawer != null && !isDrawerOpen(drawer)) {
                closeDrawer(drawer);
            }
            return true;
        }
        return false;
    }
   // 觸摸邊緣開始時調(diào)用此方法,先根據(jù)滑動方向獲得當前view雀费,如果當前view可以拖拽干奢,捕獲view的操作
    @Override
    public void onEdgeDragStarted(int edgeFlags, int pointerId) {
        final View toCapture;
        if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
            toCapture = findDrawerWithGravity(Gravity.LEFT);
        } else {
            toCapture = findDrawerWithGravity(Gravity.RIGHT);
        }
        if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
            mDragger.captureChildView(toCapture, pointerId);
        }
    }
   // 獲取拖拽view的水平方向的范圍
    @Override
    public int getViewHorizontalDragRange(View child) {
        return isDrawerView(child) ? child.getWidth() : 0;
    }
   // 捕獲水平方向的view被拖拽到的位置
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
            return Math.max(-child.getWidth(), Math.min(left, 0));
        } else {
            final int width = getWidth();
            return Math.max(width - child.getWidth(), Math.min(left, width));
        }
    }
   // 垂直方向view移動的位置
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return child.getTop();
    }
}

ViewDragHelper使用了Scroller,最后滑動的computeScroll()

@Override
public void computeScroll() {
    final int childCount = getChildCount();
    float scrimOpacity = 0;
    for (int i = 0; i < childCount; i++) {
        final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
        scrimOpacity = Math.max(scrimOpacity, onscreen);
    }
    mScrimOpacity = scrimOpacity;
    // "|" used on purpose; both need to run.
    if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

看過ViewDragHelper的人應該都知道上面這個方法中的含義盏袄,這里簡單在代碼中注釋,詳見ViewDragHelper 源碼分析

onInterceptTouchEvent方法

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    // "|" used deliberately here; both methods should be invoked.
    final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |            mRightDragger.shouldInterceptTouchEvent(ev);
    boolean interceptForTap = false;
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final float x = ev.getX();
            final float y = ev.getY();
            mInitialMotionX = x;
            mInitialMotionY = y;
            if (mScrimOpacity > 0) {
                final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
                if (child != null && isContentView(child)) {
                    interceptForTap = true;
                }
            }
            mDisallowInterceptRequested = false;
            mChildrenCanceledTouch = false;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            // If we cross the touch slop, don't perform the delayed peek for an edge touch.
            if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
                mLeftCallback.removeCallbacks();
                mRightCallback.removeCallbacks();
            }
            break;
        }
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP: {
            closeDrawers(true);
            mDisallowInterceptRequested = false;
            mChildrenCanceledTouch = false;
        }
    }
    return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
}

在使用ViewDragHelper時都知道要攔截事件交給ViewDragHelper忿峻,還有幾種情況也要攔截,如果左側(cè)拖轉(zhuǎn)的view不為空辕羽,并且gravity == Gravity.NO_GRAVITY也攔截該事件逛尚,在DownUp也攔截該事件

private boolean hasPeekingDrawer() {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
        if (lp.isPeeking) {
            return true;
        }
    }
    return false;
}

如果當前的子view是拖拽的view,也攔截該事件

onMeasure方法

由于DrawerLayout是繼承自ViewGroup刁愿,所以onMeasure方法主要是計算本身的寬高和子view的寬高绰寞,處理設置wrap_content的情況

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
        if (isInEditMode()) {
            // Don't crash the layout editor. Consume all of the space if specified
            // or pick a magic number from thin air otherwise.
            // TODO Better communication with tools of this bogus state.
            // It will crash on a real device.
            if (widthMode == MeasureSpec.AT_MOST) {
                widthMode = MeasureSpec.EXACTLY;
            } else if (widthMode == MeasureSpec.UNSPECIFIED) {
                widthMode = MeasureSpec.EXACTLY;
                widthSize = 300;
            }
            if (heightMode == MeasureSpec.AT_MOST) {
                heightMode = MeasureSpec.EXACTLY;
            }
            else if (heightMode == MeasureSpec.UNSPECIFIED) {
                heightMode = MeasureSpec.EXACTLY;
                heightSize = 300;
            }
        } else {
            throw new IllegalArgumentException(                    "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
        }
    }
    setMeasuredDimension(widthSize, heightSize);
    final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    // Only one drawer is permitted along each vertical edge (left / right). These two booleans
    // are tracking the presence of the edge drawers.
    boolean hasDrawerOnLeftEdge = false;
    boolean hasDrawerOnRightEdge = false;
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        } 
       final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (applyInsets) {
            final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
            if (ViewCompat.getFitsSystemWindows(child)) {
                IMPL.dispatchChildInsets(child, mLastInsets, cgrav);
            } else {
                IMPL.applyMarginInsets(lp, mLastInsets, cgrav);
            }
        }
        if (isContentView(child)) {
            // Content views get measured at exactly the layout's size.
            final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                    widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
            final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                    heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
            child.measure(contentWidthSpec, contentHeightSpec);
        } else if (isDrawerView(child)) {
            if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
                if (ViewCompat.getElevation(child) != mDrawerElevation) {
                    ViewCompat.setElevation(child, mDrawerElevation);
                }
            }
            final @EdgeGravity int childGravity =                    getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
            // Note that the isDrawerView check guarantees that childGravity here is either
            // LEFT or RIGHT
            boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT);
            if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge) ||                    (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) {
                throw new IllegalStateException("Child drawer has absolute gravity " +                        gravityToString(childGravity) + " but this " + TAG + " already has a " +                        "drawer view along that edge");
            }
            if (isLeftEdgeDrawer) {
                hasDrawerOnLeftEdge = true;
            } else {
                hasDrawerOnRightEdge = true;
            }
            final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,                    mMinDrawerMargin + lp.leftMargin + lp.rightMargin,                    lp.width);
            final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,                    lp.topMargin + lp.bottomMargin,                    lp.height);
            child.measure(drawerWidthSpec, drawerHeightSpec);
        } else {
            throw new IllegalStateException("Child " + child + " at index " + i +                    " does not have a valid layout_gravity - must be Gravity.LEFT, " +                    "Gravity.RIGHT or Gravity.NO_GRAVITY");
        }
    }
}

如果寬或者高不是MeasureSpec.EXACTLY時,如果widthMode等于MeasureSpec.AT_MOST,則widthMode等于MeasureSpec.EXACTLY,如果widthMode等于MeasureSpec.UNSPECIFIED則寬默認等于300,高同理铣口,然后遍歷子view

boolean isContentView(View child) {
    return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}

如果子view沒有設置gravity屬性的話滤钱,給子view設置寬高以及mode

boolean isDrawerView(View child) {
    final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
    final int absGravity = GravityCompat.getAbsoluteGravity(gravity,            ViewCompat.getLayoutDirection(child));
    if ((absGravity & Gravity.LEFT) != 0) {
        // This child is a left-edge drawer
        return true;
    }
    if ((absGravity & Gravity.RIGHT) != 0) {
        // This child is a right-edge drawer
        return true;
    }
    return false;
}

判斷gravity屬性是left or right,然后通過child.measure(drawerWidthSpec, drawerHeightSpec);給子view設置寬高Spec

onLayout方法

onLayout方法主要是給子view布局

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mInLayout = true;
    final int width = r - l;
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (isContentView(child)) {
            child.layout(lp.leftMargin, lp.topMargin,                    lp.leftMargin + child.getMeasuredWidth(),                    lp.topMargin + child.getMeasuredHeight());
        } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            int childLeft;
            final float newOffset;
            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
                childLeft = -childWidth + (int) (childWidth * lp.onScreen);
                newOffset = (float) (childWidth + childLeft) / childWidth;
            } else { // Right; onMeasure checked for us.
                childLeft = width - (int) (childWidth * lp.onScreen);
                newOffset = (float) (width - childLeft) / childWidth;
            }
            final boolean changeOffset = newOffset != lp.onScreen;
            final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
            switch (vgrav) {
                default:
                case Gravity.TOP: {
                    child.layout(childLeft, lp.topMargin, childLeft + childWidth,                            lp.topMargin + childHeight);
                    break;
                }
                case Gravity.BOTTOM: {
                    final int height = b - t;
                    child.layout(childLeft,                            height - lp.bottomMargin - child.getMeasuredHeight(),                            childLeft + childWidth,                            height - lp.bottomMargin);
                    break;
                }
                case Gravity.CENTER_VERTICAL: {
                    final int height = b - t;
                    int childTop = (height - childHeight) / 2;
                    // Offset for margins. If things don't fit right because of
                    // bad measurement before, oh well.
                    if (childTop < lp.topMargin) {
                        childTop = lp.topMargin;
                    } else if (childTop + childHeight > height - lp.bottomMargin) {
                        childTop = height - lp.bottomMargin - childHeight;
                    }
                    child.layout(childLeft, childTop, childLeft + childWidth,                            childTop + childHeight);
                    break;
                }
            }
            if (changeOffset) {
                setDrawerViewOffset(child, newOffset);
            }
            final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
            if (child.getVisibility() != newVisibility) {
                child.setVisibility(newVisibility);
            }
        }
    }
    mInLayout = false;
    mFirstLayout = false;
}

遍歷子view脑题,如果子view設置了gravity,根據(jù)子view的gravity屬性計算childLeftnewOffset件缸,如果子view是垂直方向的,根據(jù)gravity屬性計算topandbottom

drawChild方法

@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    final int height = getHeight();
    final boolean drawingContent = isContentView(child);
    int clipLeft = 0, clipRight = getWidth();
    final int restoreCount = canvas.save();
    if (drawingContent) {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View v = getChildAt(i);
            if (v == child || v.getVisibility() != VISIBLE ||                    !hasOpaqueBackground(v) || !isDrawerView(v) ||                    v.getHeight() < height) {
                continue;
            }
            if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
                final int vright = v.getRight();
                if (vright > clipLeft) clipLeft = vright;
            } else {
                final int vleft = v.getLeft();
                if (vleft < clipRight) clipRight = vleft;
            }
        }
        canvas.clipRect(clipLeft, 0, clipRight, getHeight());
    }
    final boolean result = super.drawChild(canvas, child, drawingTime);
    canvas.restoreToCount(restoreCount);
    if (mScrimOpacity > 0 && drawingContent) {
        final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
        final int imag = (int) (baseAlpha * mScrimOpacity);
        final int color = imag << 24 | (mScrimColor & 0xffffff);
        mScrimPaint.setColor(color);
        canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
    } else if (mShadowLeftResolved != null            &&  checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
        final int shadowWidth = mShadowLeftResolved.getIntrinsicWidth();
        final int childRight = child.getRight();
        final int drawerPeekDistance = mLeftDragger.getEdgeSize();
        final float alpha =                Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
        mShadowLeftResolved.setBounds(childRight, child.getTop(),                childRight + shadowWidth, child.getBottom());
        mShadowLeftResolved.setAlpha((int) (0xff * alpha));
        mShadowLeftResolved.draw(canvas);
    } else if (mShadowRightResolved != null            &&  checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
        final int shadowWidth = mShadowRightResolved.getIntrinsicWidth();
        final int childLeft = child.getLeft();
        final int showing = getWidth() - childLeft;
        final int drawerPeekDistance = mRightDragger.getEdgeSize();
        final float alpha =                Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
        mShadowRightResolved.setBounds(childLeft - shadowWidth, child.getTop(),                childLeft, child.getBottom());
        mShadowRightResolved.setAlpha((int) (0xff * alpha));
        mShadowRightResolved.draw(canvas);
    }
    return result;
}

判斷當前的view是否設置gravity屬性值叔遂,如果沒有設置gravity,計算clipLeftclipRight值,如果mScrimOpacity > 0畫一個矩形他炊,如果view的gravity值為Gravity.LEFT,畫右側(cè)的view陰影部分争剿,如果view的gravity值為Gravity.RIGHT畫左側(cè)的view陰影部分

DrawerLayout的主要功能就是滑動,源碼中使用了ViewDragHelper實現(xiàn)了滑動佑稠,具體不了解的地方可以去看ViewDragHelper源碼秒梅,DrawerLayout繼承自ViewGroup,所以要去計算自身以及子view的寬高舌胶,以及實現(xiàn)子view在DrawerLayout的布局捆蜀,本文主要描述主要的幾個方法,想了解其他內(nèi)容幔嫂,自行查看源碼

以上源碼來自api 23辆它,如果有不正確的地方,歡迎指正

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末履恩,一起剝皮案震驚了整個濱河市锰茉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌切心,老刑警劉巖飒筑,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绽昏,居然都是意外死亡协屡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門全谤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肤晓,“玉大人,你說我怎么就攤上這事认然〔购叮” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵卷员,是天一觀的道長盈匾。 經(jīng)常有香客問我,道長毕骡,這世上最難降的妖魔是什么威酒? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮挺峡,結果婚禮上,老公的妹妹穿的比我還像新娘担钮。我一直安慰自己橱赠,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布箫津。 她就那樣靜靜地躺著狭姨,像睡著了一般宰啦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饼拍,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天赡模,我揣著相機與錄音,去河邊找鬼师抄。 笑死漓柑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的叨吮。 我是一名探鬼主播辆布,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼茶鉴!你這毒婦竟也來了锋玲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤涵叮,失蹤者是張志新(化名)和其女友劉穎惭蹂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體割粮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盾碗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了穆刻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片置尔。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖氢伟,靈堂內(nèi)的尸體忽然破棺而出榜轿,到底是詐尸還是另有隱情,我是刑警寧澤朵锣,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布谬盐,位于F島的核電站,受9級特大地震影響诚些,放射性物質(zhì)發(fā)生泄漏飞傀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一诬烹、第九天 我趴在偏房一處隱蔽的房頂上張望砸烦。 院中可真熱鬧,春花似錦绞吁、人聲如沸幢痘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颜说。三九已至购岗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間门粪,已是汗流浹背喊积。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玄妈,地道東北人乾吻。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像措近,于是被迫代替她去往敵國和親溶弟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容