DrawerLayout 源码分析

简介

DrawerLayout充当窗口内容的顶层容器,允许"抽屉"式的控件可以从窗口的一边或者两边垂直边缘拉出

使用

抽屉的位置或者布局可以通过android:layout_gravity子view的属性控制从那边拉出,left/start代表从左边拉出,right/end代表从右侧拉出,需要注意的是只能有一个抽屉控件从窗口的垂直边缘,如果布局中每个垂直窗口有多于一个抽屉控件,将会抛出异常

根布局使用DrawerLayout作为第一个主内容布局,主内容布局宽高设置为match_parent不用设置layout_gravity,然后在主内容布局上添加子控件,并且设置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

源码分析

构造函数

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>();
}

构造函数中,设置view group的初始焦点,根据手机密度计算出Drawermargin值,初始化从左侧边缘拉出来的布局的回掉监听和从右侧边缘拉出来的布局的回掉监听,其中,在DrawerLayout的源码是的滑动部分使用的是ViewDragHelper,所以要初始化左侧的滑动和右侧的滑动,设置触摸时的焦点,初始化view的List

ViewDragHelper的回调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的状态发生变化时,更新拖拽状态
    @Override
    public void onViewDragStateChanged(int state) {
        updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
    }
   // 当view的位置发生变化时,重新布局
    @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);
        }
    }
   // 被拖拽的被回掉时调用,先获得子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();
    }
   //触摸到边缘时回掉函数
    @Override
    public void onEdgeTouched(int edgeFlags, int pointerId) {
        postDelayed(mPeekRunnable, PEEK_DELAY);
    }
  // 根据拖拽的方向计算出view的左侧位置,判断是哪个方向滑动,如果是单侧划定关闭另一侧的view,取消另一侧的滑动
    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;
    }
   // 触摸边缘开始时调用此方法,先根据滑动方向获得当前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,还有几种情况也要拦截,如果左侧拖转的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,根据子view的gravity属性计算childLeftnewOffset,如果子view是垂直方向的,根据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,画右侧的view阴影部分,如果view的gravity值为Gravity.RIGHT画左侧的view阴影部分

DrawerLayout的主要功能就是滑动,源码中使用了ViewDragHelper实现了滑动,具体不了解的地方可以去看ViewDragHelper源码,DrawerLayout继承自ViewGroup,所以要去计算自身以及子view的宽高,以及实现子view在DrawerLayout的布局,本文主要描述主要的几个方法,想了解其他内容,自行查看源码

以上源码来自api 23,如果有不正确的地方,欢迎指正

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容