`
lexsain
  • 浏览: 17638 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

MultiSlidingDrawer多方向抽屉导航

阅读更多

项目需求很复杂,各种要求。官方的drawerlayout和开源的slidingmenu已经不能满足我们的需求了,所以MultiSlidingDrawer就诞生了,支持左、右、上、下。甚至是在其它容器里面。
源码:

package com.lee.ademo.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.RotateAnimation;

import com.lee.ademo.activity.R;

public class MultiSlidingDrawer extends ViewGroup {

	public static final int ORIENTATION_RTL = 0;
	public static final int ORIENTATION_BTT = 1;
	public static final int ORIENTATION_LTR = 2;
	public static final int ORIENTATION_TTB = 3;

	private static final int TAP_THRESHOLD = 6;
	private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
	private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
	private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
	private static final float MAXIMUM_ACCELERATION = 2000.0f;
	private static final int VELOCITY_UNITS = 1000;
	private static final int MSG_ANIMATE = 1000;
	private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

	private static final int EXPANDED_FULL_OPEN = -10001;
	private static final int COLLAPSED_FULL_CLOSED = -10002;

	private final int mHandleId;
	private final int mContentId;

	private View mHandle;
	private View mContent;

	private final Rect mFrame = new Rect();
	private final Rect mInvalidate = new Rect();
	private boolean mTracking;
	private boolean mLocked;

	private VelocityTracker mVelocityTracker;

	private boolean mInvert;
	private boolean mVertical;
	private boolean mExpanded;
	private int mBottomOffset;
	private int mTopOffset;
	private int mHandleHeight;
	private int mHandleWidth;

	private OnDrawerOpenListener mOnDrawerOpenListener;
	private OnDrawerCloseListener mOnDrawerCloseListener;
	private OnDrawerScrollListener mOnDrawerScrollListener;

	private final Handler mHandler = new SlidingHandler();
	private float mAnimatedAcceleration;
	private float mAnimatedVelocity;
	private float mAnimationPosition;
	private long mAnimationLastTime;
	private long mCurrentAnimationTime;
	private int mTouchDelta;
	private boolean mAnimating;
	private boolean mAllowSingleTap;
	private boolean mAnimateOnClick;

	private final int mTapThreshold;
	private final int mMaximumTapVelocity;
	private int mMaximumMinorVelocity;
	private int mMaximumMajorVelocity;
	private int mMaximumAcceleration;
	private final int mVelocityUnits;

	public static interface OnDrawerOpenListener {
		public void onDrawerOpened();
	}

	public static interface OnDrawerCloseListener {
		public void onDrawerClosed();
	}

	public static interface OnDrawerScrollListener {
		public void onScrollStarted();

		public void onScrollEnded();
	}

	protected RotateAnimation openRotateAnimation;
	protected RotateAnimation closeRotateAnimation;

	public MultiSlidingDrawer(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public MultiSlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.MultiSlidingDrawer, defStyle, 0);

		int orientation = a.getInt(R.styleable.MultiSlidingDrawer_direction,
				ORIENTATION_BTT);
		mVertical = (orientation == ORIENTATION_BTT || orientation == ORIENTATION_TTB);
		mBottomOffset = (int) a.getDimension(
				R.styleable.MultiSlidingDrawer_bottomOffset, 0.0f);
		mTopOffset = (int) a.getDimension(
				R.styleable.MultiSlidingDrawer_topOffset, 0.0f);
		mAllowSingleTap = a.getBoolean(
				R.styleable.MultiSlidingDrawer_allowSingleTap, true);
		mAnimateOnClick = a.getBoolean(
				R.styleable.MultiSlidingDrawer_animateOnClick, true);
		mInvert = (orientation == ORIENTATION_TTB || orientation == ORIENTATION_LTR);

		int handleId = a
				.getResourceId(R.styleable.MultiSlidingDrawer_handle, 0);
		if (handleId == 0) {
			throw new IllegalArgumentException(
					"The handle attribute is required and must refer "
							+ "to a valid child.");
		}

		int contentId = a.getResourceId(R.styleable.MultiSlidingDrawer_content,
				0);
		if (contentId == 0) {
			throw new IllegalArgumentException(
					"The content attribute is required and must refer "
							+ "to a valid child.");
		}

		if (handleId == contentId) {
			throw new IllegalArgumentException(
					"The content and handle attributes must refer "
							+ "to different children.");
		}
		mHandleId = handleId;
		mContentId = contentId;

		final float density = getResources().getDisplayMetrics().density;
		mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
		mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
		mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
		mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
		mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
		mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
		if (mInvert) {
			mMaximumAcceleration = -mMaximumAcceleration;
			mMaximumMajorVelocity = -mMaximumMajorVelocity;
			mMaximumMinorVelocity = -mMaximumMinorVelocity;
		}
		a.recycle();
		setAlwaysDrawnWithCacheEnabled(false);
		if (openRotateAnimation == null) {
			openRotateAnimation = new RotateAnimation(0, -180,
					RotateAnimation.RELATIVE_TO_SELF, 0.5f,
					RotateAnimation.RELATIVE_TO_SELF, 0.5f);
			openRotateAnimation.setFillAfter(true);
		}
		if (closeRotateAnimation == null) {
			closeRotateAnimation = new RotateAnimation(0, 0,
					RotateAnimation.RELATIVE_TO_SELF, 0.5f,
					RotateAnimation.RELATIVE_TO_SELF, 0.5f);
			closeRotateAnimation.setFillAfter(true);
		}
	}

	@Override
	protected void onFinishInflate() {
		mHandle = findViewById(mHandleId);
		if (mHandle == null) {
			throw new IllegalArgumentException(
					"The handle attribute is must refer to an"
							+ " existing child.");
		}
		mHandle.setOnClickListener(new DrawerToggler());

		mContent = findViewById(mContentId);
		if (mContent == null) {
			throw new IllegalArgumentException(
					"The content attribute is must refer to an"
							+ " existing child.");
		}
		mContent.setVisibility(View.GONE);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

		int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

		if (widthSpecMode == MeasureSpec.UNSPECIFIED
				|| heightSpecMode == MeasureSpec.UNSPECIFIED) {
			throw new RuntimeException(
					"SlidingDrawer cannot have UNSPECIFIED dimensions");
		}

		final View handle = mHandle;
		measureChild(handle, widthMeasureSpec, heightMeasureSpec);

		if (mVertical) {
			int height = heightSpecSize - handle.getMeasuredHeight()
					- mTopOffset;
			mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize,
					MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
					MeasureSpec.EXACTLY));
		} else {
			int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
			mContent.measure(MeasureSpec.makeMeasureSpec(width,
					MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
					heightSpecSize, MeasureSpec.EXACTLY));
		}

		setMeasuredDimension(widthSpecSize, heightSpecSize);
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		final long drawingTime = getDrawingTime();
		final View handle = mHandle;
		final boolean isVertical = mVertical;

		drawChild(canvas, handle, drawingTime);

		if (mTracking || mAnimating) {
			final Bitmap cache = mContent.getDrawingCache();
			if (cache != null) {
				if (isVertical) {
					if (mInvert) {
						canvas.drawBitmap(cache, 0, handle.getTop()
								- (getBottom() - getTop()) + mHandleHeight,
								null);
					} else {
						canvas.drawBitmap(cache, 0, handle.getBottom(), null);
					}
				} else {
					canvas.drawBitmap(cache,
							mInvert ? handle.getLeft() - cache.getWidth()
									: handle.getRight(), 0, null);
				}
			} else {
				canvas.save();
				if (mInvert) {
					canvas.translate(isVertical ? 0 : handle.getLeft()
							- mTopOffset - mContent.getMeasuredWidth(),
							isVertical ? handle.getTop() - mTopOffset
									- mContent.getMeasuredHeight() : 0);
				} else {
					canvas.translate(isVertical ? 0 : handle.getLeft()
							- mTopOffset, isVertical ? handle.getTop()
							- mTopOffset : 0);
				}
				drawChild(canvas, mContent, drawingTime);
				canvas.restore();
			}
			invalidate();
		} else if (mExpanded) {
			drawChild(canvas, mContent, drawingTime);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (mTracking) {
			return;
		}

		final int width = r - l;
		final int height = b - t;

		final View handle = mHandle;

		int handleWidth = handle.getMeasuredWidth();
		int handleHeight = handle.getMeasuredHeight();

		int handleLeft;
		int handleTop;

		final View content = mContent;

		if (mVertical) {
			handleLeft = (width - handleWidth) / 2;
			if (mInvert) {
				handleTop = mExpanded ? height - mBottomOffset - handleHeight
						: mTopOffset;
				content.layout(0, mTopOffset, content.getMeasuredWidth(),
						mTopOffset + content.getMeasuredHeight());
			} else {
				handleTop = mExpanded ? mTopOffset : height - handleHeight
						+ mBottomOffset;
				content.layout(0, mTopOffset + handleHeight,
						content.getMeasuredWidth(), mTopOffset + handleHeight
								+ content.getMeasuredHeight());
			}
		} else {
			handleTop = (height - handleHeight) / 2;
			if (mInvert) {
				handleLeft = mExpanded ? width - mBottomOffset - handleWidth
						: mTopOffset;
				content.layout(mTopOffset, 0,
						mTopOffset + content.getMeasuredWidth(),
						content.getMeasuredHeight());
			} else {
				handleLeft = mExpanded ? mTopOffset : width - handleWidth
						+ mBottomOffset;
				content.layout(mTopOffset + handleWidth, 0, mTopOffset
						+ handleWidth + content.getMeasuredWidth(),
						content.getMeasuredHeight());
			}
		}

		handle.layout(handleLeft, handleTop, handleLeft + handleWidth,
				handleTop + handleHeight);
		mHandleHeight = handle.getHeight();
		mHandleWidth = handle.getWidth();
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		if (mLocked) {
			return false;
		}

		final int action = event.getAction();

		float x = event.getX();
		float y = event.getY();

		final Rect frame = mFrame;
		final View handle = mHandle;

		handle.getHitRect(frame);
		if (!mTracking && !frame.contains((int) x, (int) y)) {
			return false;
		}

		if (action == MotionEvent.ACTION_DOWN) {
			// 设置无背景
			mTracking = true;
			handle.setPressed(true);
			// Must be called before prepareTracking()
			prepareContent();

			// Must be called after prepareContent()
			if (mOnDrawerScrollListener != null) {
				mOnDrawerScrollListener.onScrollStarted();
			}

			if (mVertical) {
				final int top = mHandle.getTop();
				mTouchDelta = (int) y - top;
				prepareTracking(top);
			} else {
				final int left = mHandle.getLeft();
				mTouchDelta = (int) x - left;
				prepareTracking(left);
			}
			mVelocityTracker.addMovement(event);
		}

		return true;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (mLocked) {
			return true;
		}

		if (mTracking) {
			mVelocityTracker.addMovement(event);
			final int action = event.getAction();
			switch (action) {
			case MotionEvent.ACTION_DOWN:
				break;
			case MotionEvent.ACTION_MOVE:
				moveHandle((int) (mVertical ? event.getY() : event.getX())
						- mTouchDelta);
				break;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL: {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(mVelocityUnits);

				float yVelocity = velocityTracker.getYVelocity();
				float xVelocity = velocityTracker.getXVelocity();
				boolean negative;

				final boolean vertical = mVertical;
				if (vertical) {
					negative = yVelocity < 0;
					if (xVelocity < 0) {
						xVelocity = -xVelocity;
					}
					// fix by Maciej Ciemięga.
					if ((!mInvert && xVelocity > mMaximumMinorVelocity)
							|| (mInvert && xVelocity < mMaximumMinorVelocity)) {
						xVelocity = mMaximumMinorVelocity;
					}
				} else {
					negative = xVelocity < 0;
					if (yVelocity < 0) {
						yVelocity = -yVelocity;
					}
					// fix by Maciej Ciemięga.
					if ((!mInvert && yVelocity > mMaximumMinorVelocity)
							|| (mInvert && yVelocity < mMaximumMinorVelocity)) {
						yVelocity = mMaximumMinorVelocity;
					}
				}

				float velocity = (float) Math.hypot(xVelocity, yVelocity);
				if (negative) {
					velocity = -velocity;
				}

				final int handleTop = mHandle.getTop();
				final int handleLeft = mHandle.getLeft();
				final int handleBottom = mHandle.getBottom();
				final int handleRight = mHandle.getRight();

				if (Math.abs(velocity) < mMaximumTapVelocity) {
					boolean c1;
					boolean c2;
					boolean c3;
					boolean c4;

					if (mInvert) {
						c1 = (mExpanded && (getBottom() - handleBottom) < mTapThreshold
								+ mBottomOffset);
						c2 = (!mExpanded && handleTop < mTopOffset
								+ mHandleHeight - mTapThreshold);
						c3 = (mExpanded && (getRight() - handleRight) < mTapThreshold
								+ mBottomOffset);
						c4 = (!mExpanded && handleLeft > mTopOffset
								+ mHandleWidth + mTapThreshold);
					} else {
						c1 = (mExpanded && handleTop < mTapThreshold
								+ mTopOffset);
						c2 = (!mExpanded && handleTop > mBottomOffset
								+ getBottom() - getTop() - mHandleHeight
								- mTapThreshold);
						c3 = (mExpanded && handleLeft < mTapThreshold
								+ mTopOffset);
						c4 = (!mExpanded && handleLeft > mBottomOffset
								+ getRight() - getLeft() - mHandleWidth
								- mTapThreshold);
					}

					if (vertical ? c1 || c2 : c3 || c4) {

						if (mAllowSingleTap) {
							playSoundEffect(SoundEffectConstants.CLICK);

							if (mExpanded) {
								animateClose(vertical ? handleTop : handleLeft);
							} else {
								animateOpen(vertical ? handleTop : handleLeft);
							}
						} else {
							performFling(vertical ? handleTop : handleLeft,
									velocity, false);
						}
					} else {
						performFling(vertical ? handleTop : handleLeft,
								velocity, false);
					}
				} else {
					performFling(vertical ? handleTop : handleLeft, velocity,
							false);
				}
			}
				break;
			}
		}
		return mTracking || mAnimating || super.onTouchEvent(event);
	}

	private void animateClose(int position) {
		prepareTracking(position);
		performFling(position, mMaximumAcceleration, true);
	}

	private void animateOpen(int position) {
		prepareTracking(position);
		performFling(position, -mMaximumAcceleration, true);
	}

	private void performFling(int position, float velocity, boolean always) {
		mAnimationPosition = position;
		mAnimatedVelocity = velocity;

		boolean c1;
		boolean c2;
		boolean c3;

		if (mExpanded) {
			int bottom = mVertical ? getBottom() : getRight();
			int handleHeight = mVertical ? mHandleHeight : mHandleWidth;

			c1 = mInvert ? velocity < mMaximumMajorVelocity
					: velocity > mMaximumMajorVelocity;
			c2 = mInvert ? (bottom - (position + handleHeight)) + mBottomOffset > handleHeight
					: position > mTopOffset
							+ (mVertical ? mHandleHeight : mHandleWidth);
			c3 = mInvert ? velocity < -mMaximumMajorVelocity
					: velocity > -mMaximumMajorVelocity;
			if (always || (c1 || (c2 && c3))) {
				// We are expanded, So animate to CLOSE!
				mAnimatedAcceleration = mMaximumAcceleration;
				if (mInvert) {
					if (velocity > 0) {
						mAnimatedVelocity = 0;
					}
				} else {
					if (velocity < 0) {
						mAnimatedVelocity = 0;
					}
				}
			} else {
				// We are expanded, but they didn't move sufficiently to cause
				// us to retract. Animate back to the expanded position. so
				// animate BACK to expanded!
				mAnimatedAcceleration = -mMaximumAcceleration;

				if (mInvert) {
					if (velocity < 0) {
						mAnimatedVelocity = 0;
					}
				} else {
					if (velocity > 0) {
						mAnimatedVelocity = 0;
					}
				}
			}
		} else {

			// WE'RE COLLAPSED

			c1 = mInvert ? velocity < mMaximumMajorVelocity
					: velocity > mMaximumMajorVelocity;
			c2 = mInvert ? (position < (mVertical ? getHeight() : getWidth()) / 2)
					: (position > (mVertical ? getHeight() : getWidth()) / 2);
			c3 = mInvert ? velocity < -mMaximumMajorVelocity
					: velocity > -mMaximumMajorVelocity;

			if (!always && (c1 || (c2 && c3))) {
				mAnimatedAcceleration = mMaximumAcceleration;

				if (mInvert) {
					if (velocity > 0) {
						mAnimatedVelocity = 0;
					}
				} else {
					if (velocity < 0) {
						mAnimatedVelocity = 0;
					}
				}
			} else {
				mAnimatedAcceleration = -mMaximumAcceleration;

				if (mInvert) {
					if (velocity < 0) {
						mAnimatedVelocity = 0;
					}
				} else {
					if (velocity > 0) {
						mAnimatedVelocity = 0;
					}
				}
			}
		}

		long now = SystemClock.uptimeMillis();
		mAnimationLastTime = now;
		mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
		mAnimating = true;
		mHandler.removeMessages(MSG_ANIMATE);
		mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
				mCurrentAnimationTime);
		stopTracking();
	}

	private void prepareTracking(int position) {
		mTracking = true;
		mVelocityTracker = VelocityTracker.obtain();
		boolean opening = !mExpanded;

		if (opening) {
			mAnimatedAcceleration = mMaximumAcceleration;
			mAnimatedVelocity = mMaximumMajorVelocity;
			if (mInvert)
				mAnimationPosition = mTopOffset;
			else
				mAnimationPosition = mBottomOffset
						+ (mVertical ? getHeight() - mHandleHeight : getWidth()
								- mHandleWidth);
			moveHandle((int) mAnimationPosition);
			mAnimating = true;
			mHandler.removeMessages(MSG_ANIMATE);
			long now = SystemClock.uptimeMillis();
			mAnimationLastTime = now;
			mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
			mAnimating = true;
		} else {
			if (mAnimating) {
				mAnimating = false;
				mHandler.removeMessages(MSG_ANIMATE);
			}
			moveHandle(position);
		}
	}

	private void moveHandle(int position) {
		final View handle = mHandle;

		if (mVertical) {
			if (position == EXPANDED_FULL_OPEN) {
				if (mInvert)
					handle.offsetTopAndBottom(mBottomOffset + getBottom()
							- getTop() - mHandleHeight);
				else
					handle.offsetTopAndBottom(mTopOffset - handle.getTop());
				invalidate();
			} else if (position == COLLAPSED_FULL_CLOSED) {
				if (mInvert) {
					handle.offsetTopAndBottom(mTopOffset - handle.getTop());
				} else {
					handle.offsetTopAndBottom(mBottomOffset + getBottom()
							- getTop() - mHandleHeight - handle.getTop());
				}
				invalidate();
			} else {
				final int top = handle.getTop();
				int deltaY = position - top;
				if (position < mTopOffset) {
					deltaY = mTopOffset - top;
				} else if (deltaY > mBottomOffset + getBottom() - getTop()
						- mHandleHeight - top) {
					deltaY = mBottomOffset + getBottom() - getTop()
							- mHandleHeight - top;
				}

				handle.offsetTopAndBottom(deltaY);

				final Rect frame = mFrame;
				final Rect region = mInvalidate;

				handle.getHitRect(frame);
				region.set(frame);

				region.union(frame.left, frame.top - deltaY, frame.right,
						frame.bottom - deltaY);
				region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom
						- deltaY + mContent.getHeight());

				invalidate(region);
			}
		} else {
			if (position == EXPANDED_FULL_OPEN) {
				if (mInvert)
					handle.offsetLeftAndRight(mBottomOffset + getRight()
							- getLeft() - mHandleWidth);
				else
					handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
				invalidate();
			} else if (position == COLLAPSED_FULL_CLOSED) {
				if (mInvert)
					handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
				else
					handle.offsetLeftAndRight(mBottomOffset + getRight()
							- getLeft() - mHandleWidth - handle.getLeft());
				invalidate();
			} else {
				final int left = handle.getLeft();
				int deltaX = position - left;
				if (position < mTopOffset) {
					deltaX = mTopOffset - left;
				} else if (deltaX > mBottomOffset + getRight() - getLeft()
						- mHandleWidth - left) {
					deltaX = mBottomOffset + getRight() - getLeft()
							- mHandleWidth - left;
				}
				handle.offsetLeftAndRight(deltaX);

				final Rect frame = mFrame;
				final Rect region = mInvalidate;

				handle.getHitRect(frame);
				region.set(frame);

				region.union(frame.left - deltaX, frame.top, frame.right
						- deltaX, frame.bottom);
				region.union(frame.right - deltaX, 0, frame.right - deltaX
						+ mContent.getWidth(), getHeight());

				invalidate(region);
			}
		}
	}

	private void prepareContent() {
		if (mAnimating) {
			return;
		}

		// Something changed in the content, we need to honor the layout request
		// before creating the cached bitmap
		final View content = mContent;
		if (content.isLayoutRequested()) {

			if (mVertical) {
				final int handleHeight = mHandleHeight;
				int height = getBottom() - getTop() - handleHeight - mTopOffset;
				content.measure(MeasureSpec.makeMeasureSpec(getRight()
						- getLeft(), MeasureSpec.EXACTLY), MeasureSpec
						.makeMeasureSpec(height, MeasureSpec.EXACTLY));

				if (mInvert)
					content.layout(0, mTopOffset, content.getMeasuredWidth(),
							mTopOffset + content.getMeasuredHeight());
				else
					content.layout(
							0,
							mTopOffset + handleHeight,
							content.getMeasuredWidth(),
							mTopOffset + handleHeight
									+ content.getMeasuredHeight());

			} else {

				final int handleWidth = mHandle.getWidth();
				int width = getRight() - getLeft() - handleWidth - mTopOffset;
				content.measure(MeasureSpec.makeMeasureSpec(width,
						MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
						getBottom() - getTop(), MeasureSpec.EXACTLY));

				if (mInvert)
					content.layout(mTopOffset, 0,
							mTopOffset + content.getMeasuredWidth(),
							content.getMeasuredHeight());
				else
					content.layout(handleWidth + mTopOffset, 0, mTopOffset
							+ handleWidth + content.getMeasuredWidth(),
							content.getMeasuredHeight());
			}
		}
		// Try only once... we should really loop but it's not a big deal
		// if the draw was cancelled, it will only be temporary anyway
		content.getViewTreeObserver().dispatchOnPreDraw();
		content.buildDrawingCache();

		content.setVisibility(View.GONE);
	}

	private void stopTracking() {
		mHandle.setPressed(false);
		mTracking = false;

		if (mOnDrawerScrollListener != null) {
			mOnDrawerScrollListener.onScrollEnded();
		}

		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}

	private void doAnimation() {
		if (mAnimating) {
			incrementAnimation();

			if (mInvert) {
				if (mAnimationPosition < mTopOffset) {
					mAnimating = false;
					closeDrawer();
				} else if (mAnimationPosition >= mTopOffset
						+ (mVertical ? getHeight() : getWidth()) - 1) {
					mAnimating = false;
					openDrawer();
				} else {
					moveHandle((int) mAnimationPosition);
					mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
					mHandler.sendMessageAtTime(
							mHandler.obtainMessage(MSG_ANIMATE),
							mCurrentAnimationTime);
				}
			} else {
				if (mAnimationPosition >= mBottomOffset
						+ (mVertical ? getHeight() : getWidth()) - 1) {
					mAnimating = false;
					closeDrawer();
				} else if (mAnimationPosition < mTopOffset) {
					mAnimating = false;
					openDrawer();
				} else {
					moveHandle((int) mAnimationPosition);
					mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
					mHandler.sendMessageAtTime(
							mHandler.obtainMessage(MSG_ANIMATE),
							mCurrentAnimationTime);
				}
			}
		}
	}

	private void incrementAnimation() {
		long now = SystemClock.uptimeMillis();
		float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
		final float position = mAnimationPosition;
		final float v = mAnimatedVelocity; // px/s
		final float a = mInvert ? mAnimatedAcceleration : mAnimatedAcceleration; // px/s/s
		mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px
		mAnimatedVelocity = v + (a * t); // px/s
		mAnimationLastTime = now; // ms
	}

	public void toggle() {
		if (!mExpanded) {
			openDrawer();
		} else {
			closeDrawer();
		}
		invalidate();
		requestLayout();
	}

	public void animateToggle() {
		if (!mExpanded) {
			animateOpen();
		} else {
			animateClose();
		}
	}

	public void open() {
		openDrawer();
		invalidate();
		requestLayout();

		sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
	}

	public void close() {
		closeDrawer();
		invalidate();
		requestLayout();
	}

	public void animateClose() {
		prepareContent();
		final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
		if (scrollListener != null) {
			scrollListener.onScrollStarted();
		}
		animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());

		if (scrollListener != null) {
			scrollListener.onScrollEnded();
		}
	}

	public void animateOpen() {
		prepareContent();
		final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
		if (scrollListener != null) {
			scrollListener.onScrollStarted();
		}
		animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());

		sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);

		if (scrollListener != null) {
			scrollListener.onScrollEnded();
		}
	}

	private void closeDrawer() {
		moveHandle(COLLAPSED_FULL_CLOSED);
		mContent.setVisibility(View.GONE);
		mContent.destroyDrawingCache();

		if (!mExpanded) {
			return;
		}

		mExpanded = false;
		if (mOnDrawerCloseListener != null) {
			mOnDrawerCloseListener.onDrawerClosed();
		}
		mHandle.startAnimation(closeRotateAnimation);
	}

	private void openDrawer() {
		moveHandle(EXPANDED_FULL_OPEN);
		mContent.setVisibility(View.VISIBLE);

		if (mExpanded) {
			return;
		}

		mExpanded = true;

		if (mOnDrawerOpenListener != null) {
			mOnDrawerOpenListener.onDrawerOpened();
		}
		mHandle.startAnimation(openRotateAnimation);
	}

	public void setOnDrawerOpenListener(
			OnDrawerOpenListener onDrawerOpenListener) {
		mOnDrawerOpenListener = onDrawerOpenListener;
	}

	public void setOnDrawerCloseListener(
			OnDrawerCloseListener onDrawerCloseListener) {
		mOnDrawerCloseListener = onDrawerCloseListener;
	}

	public void setOnDrawerScrollListener(
			OnDrawerScrollListener onDrawerScrollListener) {
		mOnDrawerScrollListener = onDrawerScrollListener;
	}

	public View getHandle() {
		return mHandle;
	}

	public View getContent() {
		return mContent;
	}

	public void unlock() {
		mLocked = false;
	}

	public void lock() {
		mLocked = true;
	}

	public boolean isOpened() {
		return mExpanded;
	}

	public boolean isMoving() {
		return mTracking || mAnimating;
	}

	private class DrawerToggler implements OnClickListener {

		public void onClick(View v) {
			if (mLocked) {
				return;
			}
			// mAllowSingleTap isn't relevant here; you're *always*
			// allowed to open/close the drawer by clicking with the
			// trackball.
			if (mAnimateOnClick) {
				animateToggle();
			} else {
				toggle();
			}
		}
	}

	@SuppressLint("HandlerLeak")
	private class SlidingHandler extends Handler {

		public void handleMessage(Message m) {
			switch (m.what) {
			case MSG_ANIMATE:
				doAnimation();
				break;
			}
		}
	}
}

 

分享到:
评论

相关推荐

    android 多方向抽屉

    在Android开发中,"多方向抽屉"是一种常见的交互设计,允许用户从屏幕的各个边缘滑动以显示或隐藏不同的内容区域。这个功能通常被称为Drawer,借鉴了现实生活中抽屉打开和关闭的动作,使得用户可以方便地访问隐藏的...

    用VC做的功能强大的抽屉导航类

    6. **多平台支持**:虽然VC主要用于Windows开发,但如果你想让你的抽屉导航类也适用于其他平台,可能需要引入跨平台的库,如Qt或wxWidgets,或者使用C++的现代框架如C++/WinRT,来实现跨平台兼容性。 抽屉导航类在...

    类QQ抽屉导航工具代码

    抽屉导航,通常被称为侧滑菜单或抽屉式菜单,源自于物理抽屉的开合动作,它隐藏在主屏幕的一侧,当用户触发特定操作(如点击汉堡菜单图标)时会滑出,显示更多的选项。这种设计模式在有限的屏幕空间下尤其有用,例如...

    多方向抽屉代码

    "多方向抽屉代码" 提供了一种实现这一功能的技术方案,允许抽屉从多个方向(如上、下、左、右)拉出,增强了用户体验的多样性。下面我们将深入探讨这一主题。 1. **抽屉概念**: 抽屉式导航通常用于提供二级或三级...

    android studio开发的抽屉导航

    抽屉导航在不同屏幕尺寸和设备方向上应保持一致的用户体验。适当地调整布局和内容,确保在手机和平板等不同设备上都能良好工作。 9. **测试和调试**: 在开发过程中,使用Android Studio的模拟器或连接的真实设备...

    多方向 抽屉

    标题中的“多方向 抽屉”可能是指在软件开发中常见的滑动抽屉效果,这种效果通常用于导航菜单、设置面板等,可以在多个方向(如上、下、左、右)进行滑动展开和收起。抽屉效果是移动应用和现代网页设计中的常见元素...

    Axure夜话之Axure手机原型视频教程之侧滑菜单 抽屉导航 rar

    1. 抽屉导航的概念:与侧滑菜单类似,但抽屉导航通常用于应用的主界面,从屏幕底部或侧面滑出,显示更多的导航选项或设置。 2. 设计要点:抽屉导航需要考虑与主要内容的层级关系,以及打开、关闭的过渡动画。 3. 在...

    html+css侧边抽屉式导航栏

    在网页设计中,侧边抽屉式导航栏是一种常见的交互元素,它通常用于节省空间,尤其是在移动设备上。这种导航栏在页面加载时隐藏,当用户点击特定按钮(如汉堡菜单)时,会从页面侧面滑动弹出。在HTML和CSS中实现这样...

    Android 抽屉导航

    - 在处理抽屉打开和关闭的逻辑时,需要考虑不同设备尺寸和方向的影响。 - 为无障碍功能考虑,确保抽屉导航也能通过触摸屏幕边缘或其他辅助工具访问。 通过上述知识点,开发者可以构建出一个符合Material Design...

    抽屉式导航 简单上手

    抽屉式导航,也称为侧滑菜单或者抽屉布局,是iOS应用设计中常见的导航模式,尤其适用于内容较多、层级较深的应用。这种导航方式可以让用户通过从屏幕边缘向内滑动来展示隐藏的菜单选项,从而保持主屏幕的简洁性。在...

    android,Path动画,多方向抽屉,旋转动画

    Path动画允许开发者通过自定义路径来实现复杂的动画效果,而多方向抽屉则常用于实现类似侧滑菜单的功能,为用户提供导航或设置选项。 首先,让我们深入了解Path动画。Path动画是Android SDK中的一个高级特性,它...

    C# winform左侧导航抽屉菜单

    - 在抽屉菜单上放置多个Button或MenuStrip控件代表不同的功能模块,为每个按钮添加对应的事件处理程序,实现点击后的功能跳转或操作。 7. **优化体验**: - 考虑触摸屏支持,确保手势操作如滑动也能触发菜单的开...

    (续)Python3.x+Pyqt5实现主窗体里QToolBox导航栏抽屉效果和树形导航,以及QSplitter用QToolBox导航栏抽屉效果动态载入子窗体

    QToolBox是PyQt5提供的一种用户界面元素,它允许用户通过下拉菜单或抽屉效果在多个页面之间切换,而QTreeWidget则用于呈现层次结构的数据,常用于导航菜单。 首先,我们需要了解PyQt5的基础知识。PyQt5是Python中的...

    实现了抽屉式的导航效果

    在iOS中,我们可以使用PanGestureRecognizer来检测用户的滑动操作,然后根据手势的方向和位移来计算抽屉视图应该移动的距离。同时,利用UIView的animate(withDuration:animations:)方法,我们可以优雅地执行打开和...

    抽屉式导航

    抽屉式导航是一种常见的移动应用设计模式,尤其在Android和iOS平台上广泛使用。它通常用于实现主内容区域与侧边栏菜单之间的交互,允许用户在不遮挡主要内容的情况下访问额外的功能或选项。这种设计模式提供了简洁且...

    C# 抽屉菜单实例(winform左侧导航菜单).rar

    在本文中,我们将深入探讨如何在C# WinForm应用程序中实现一个抽屉式的左侧导航菜单。抽屉菜单是一种常见的UI设计模式,它允许用户通过滑动或点击图标从屏幕边缘拉出一个包含导航选项的菜单。这个"C# 抽屉菜单实例...

    仿知乎ActionBar Fragment侧滑抽屉菜单导航栏

    这种模式只能在一项任务的的根activity 中使用,即任何界面都能通过Actionbar触发出drawer式的抽屉导航栏 4.2 item选择 5.不建议使用右侧抽屉来导航,一般用于具体的点击事件。这是遵循导航栏以及触发他的Actionbar...

    android类似facebooke抽屉式导航菜单源码

    在Android应用开发中,抽屉式导航菜单(Drawer Menu)是一种常见的设计模式,它模仿了Facebook应用程序的侧滑导航方式,使用户能够轻松地在不同的功能模块之间切换。抽屉式菜单通常隐藏在屏幕左侧,当用户向右滑动...

Global site tag (gtag.js) - Google Analytics