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方法

@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,如果有不正确的地方,欢迎指正

时间: 2024-10-10 04:29:57

DrawerLayout 源码分析的相关文章

《极简笔记》源码分析(二)

0. 介绍 此文将对Github上lguipeng大神所开发的 极简笔记 v2.0 (点我下载源码)代码进行分析学习. 通过此文你将学到: 应用源码的研读方法 MVP架构模式 Application的应用 Degger2依赖注入框架 搜索控件的使用 ButterKnife库的使用 Material主题 RecyclerView等新控件的用法 Lambda表达式 Java自定义注解 aFinal框架 RxJava框架 EventBus消息框架 布局文件常用技巧 PreferenceFragment

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

zg手册 之 python2.7.7源码分析(4)-- pyc字节码文件

什么是字节码 python解释器在执行python脚本文件时,对文件中的python源代码进行编译,编译的结果就是byte code(字节码) python虚拟机执行编译好的字节码,完成程序的运行 python会为导入的模块创建字节码文件 字节码文件的创建过程 当a.py依赖b.py时,如在a.py中import b python先检查是否有b.pyc文件(字节码文件),如果有,并且修改时间比b.py晚,就直接调用b.pyc 否则编译b.py生成b.pyc,然后加载新生成的字节码文件 字节码对象

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实