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 group
的初始焦点,根据手机密度计算出Drawer
的margin
值,初始化从左侧边缘拉出来的布局的回掉监听和从右侧边缘拉出来的布局的回掉监听,其中,在DrawerLayout
的源码是的滑动部分使用的是ViewDragHelper
,所以要初始化左侧的滑动和右侧的滑动,设置触摸时的焦点,初始化view的List
其中初始化的过程中有个很重要的方法,就是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 源码分析
@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
也拦截该事件,在Down
和Up
也拦截该事件
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,也拦截该事件
由于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
@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
属性计算childLeft
和newOffset
,如果子view是垂直方向的,根据gravity
属性计算top
andbottom
@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
,计算clipLeft
和clipRight
值,如果mScrimOpacity > 0
画一个矩形,如果view的gravity
值为Gravity.LEFT
,画右侧的view阴影部分,如果view的gravity
值为Gravity.RIGHT
画左侧的view阴影部分
DrawerLayout
的主要功能就是滑动,源码中使用了ViewDragHelper
实现了滑动,具体不了解的地方可以去看ViewDragHelper
源码,DrawerLayout
继承自ViewGroup
,所以要去计算自身以及子view的宽高,以及实现子view在DrawerLayout
的布局,本文主要描述主要的几个方法,想了解其他内容,自行查看源码
以上源码来自api 23,如果有不正确的地方,欢迎指正