开源项目之Android undergarment

undergarment 是 Android 上实现滑盖导航(抽屉)效果的 UI 组件。 项目封装成了静态库,其它程序直接引用,项目如图:



界面效果很给力,58同城 android版主界面采用了类似的效果,只不过起点和方向不一样,笔者已经实现了该效果,有机会贴出源码来,看效果:





不知道效果能不能得到58同城的认可,呵呵!看源码

 

public class DrawerGarment extends FrameLayout { // FrameLayout称为层布局,将组件显示在屏幕的左上角,后面的组件覆盖前面的组

	public static final int SLIDE_TARGET_CONTENT = 0; //

	public static final int SLIDE_TARGET_WINDOW = 1;

	private static final int SCROLL_DURATION = 400; // 滑动时间间距

	private static final float TOUCH_TARGET_WIDTH_DIP = 48.0f;

	private boolean mAdded = false; // 是否添加

	private boolean mDrawerEnabled = true; // 是否自绘

	private boolean mDrawerOpened = false; // 是否开打

	private boolean mDrawerMoving = false; // 是否移动

	private boolean mGestureStarted = false; // 是否支持手势

	private int mDecorContentBackgroundColor = Color.TRANSPARENT; // 背景色

	private int mDecorOffsetX = 0; //

	private int mDrawerMaxWidth = WRAP_CONTENT;

	private int mDrawerWidth;

	private int mGestureStartX;

	private int mGestureCurrentX;

	private int mGestureStartY;

	private int mGestureCurrentY;

	private int mSlideTarget;

	private int mTouchTargetWidth;

	private Drawable mShadowDrawable;

	private Handler mScrollerHandler;

	private Scroller mScroller; // 滚动条

	private ViewGroup mDecorView;

	private ViewGroup mContentTarget;

	private ViewGroup mContentTargetParent;

	private ViewGroup mWindowTarget;

	private ViewGroup mWindowTargetParent;

	private ViewGroup mDecorContent;

	private ViewGroup mDecorContentParent;

	private ViewGroup mDrawerContent;

	private Runnable mDrawOpenRunnable, mDrawCloseRunnable;

	private VelocityTracker mVelocityTracker;

	private IDrawerCallbacks mDrawerCallbacks;

	public static interface IDrawerCallbacks { // 回调打开/关闭

		public void onDrawerOpened();

		public void onDrawerClosed();
	}

	// interpolator被用来修饰动画效果,定义动画的变化率,可以使存在的动画效果可以accelerated(加速),decelerated(减速),repeated(重复),bounced(弹跳)等。
	public static class SmoothInterpolator implements Interpolator {

		@Override
		public float getInterpolation(float v) {
			return (float) (Math.pow((double) v - 1.0, 5.0) + 1.0f);
		}
	}

	public void reconfigureViewHierarchy() { // 重新配置视图层次
		final DisplayMetrics dm = getResources().getDisplayMetrics();
		final int widthPixels = dm.widthPixels;

		if (mDecorView == null) {
			return;
		}
		if (mDrawerContent != null) {
			removeView(mDrawerContent);
		}
		if (mDecorContent != null) {
			// 添加窗口/内容
			removeView(mDecorContent);
			mDecorContentParent.addView(mDecorContent);

			// 取消单击监听 背景透明
			mDecorContent.setOnClickListener(null);
			mDecorContent.setBackgroundColor(Color.TRANSPARENT);
		}
		if (mAdded) {
			mDecorContentParent.removeView(this);
		}
		if (mSlideTarget == SLIDE_TARGET_CONTENT) {
			mDecorContent = mContentTarget;
			mDecorContentParent = mContentTargetParent;
		} else if (mSlideTarget == SLIDE_TARGET_WINDOW) {
			mDecorContent = mWindowTarget;
			mDecorContentParent = mWindowTargetParent;
		} else {
			throw new IllegalArgumentException(
					"Slide target must be one of SLIDE_TARGET_CONTENT or SLIDE_TARGET_WINDOW.");
		}
		((ViewGroup) mDecorContent.getParent()).removeView(mDecorContent);
		addView(mDrawerContent, new ViewGroup.LayoutParams(mDrawerMaxWidth,
				MATCH_PARENT));
		addView(mDecorContent, new ViewGroup.LayoutParams(MATCH_PARENT,
				MATCH_PARENT));
		mDecorContentParent.addView(this);
		mAdded = true;

		mDecorContent.setBackgroundColor(mDecorContentBackgroundColor); // 设置背景

		mShadowDrawable
				.setBounds(-mTouchTargetWidth / 6, 0, 0, dm.heightPixels); // 重设大小

		// 防止关闭
		mDecorContent.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View view) {
			}
		});
	}

	public DrawerGarment(Activity activity, int drawerLayout) {
		super(activity);

		final DisplayMetrics dm = activity.getResources().getDisplayMetrics();

		mTouchTargetWidth = Math.round(TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, TOUCH_TARGET_WIDTH_DIP, dm));

		mShadowDrawable = getResources().getDrawable(R.drawable.decor_shadow);

		mScrollerHandler = new Handler();
		mScroller = new Scroller(activity, new SmoothInterpolator());

		// 默认针对整个窗口
		mSlideTarget = SLIDE_TARGET_WINDOW;

		mDecorView = (ViewGroup) activity.getWindow().getDecorView();
		mWindowTarget = (ViewGroup) mDecorView.getChildAt(0);
		mWindowTargetParent = (ViewGroup) mWindowTarget.getParent();
		mContentTarget = (ViewGroup) mDecorView
				.findViewById(android.R.id.content);
		mContentTargetParent = (ViewGroup) mContentTarget.getParent();
		mDrawerContent = (ViewGroup) LayoutInflater.from(activity).inflate(
				drawerLayout, null);

		mDrawerContent.setVisibility(INVISIBLE);

		// 重新配置视图层次
		reconfigureViewHierarchy();

		/*
		 * This currently causes lock-ups on 10" tablets (e.g., Xoom &
		 * Transformer), should probably look into why this is happening.
		 * 
		 * if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
		 * setLayerType(LAYER_TYPE_HARDWARE, null); }
		 */
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) { // 用于指定所有子视图的位置
		Rect windowRect = new Rect();
		mDecorView.getWindowVisibleDisplayFrame(windowRect);

		if (mSlideTarget == SLIDE_TARGET_WINDOW) {
			mDrawerContent.layout(left, top + windowRect.top, right, bottom);
			mDecorContent.layout(mDecorContent.getLeft(),
					mDecorContent.getTop(), mDecorContent.getLeft() + right,
					bottom);
		} else {
			mDrawerContent.layout(left, 0, right, bottom);
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				mDecorContent.layout(mDecorContent.getLeft(), 0,
						mDecorContent.getLeft() + right, bottom);
			} else {
				mDecorContent.layout(mDecorContent.getLeft(), top,
						mDecorContent.getLeft() + right, bottom);
			}
		}

		mDrawerWidth = mDrawerContent.getMeasuredWidth();
		if (mDrawerWidth > right - mTouchTargetWidth) {
			mDrawerContent.setPadding(0, 0, mTouchTargetWidth, 0);
			mDrawerWidth -= mTouchTargetWidth;
		}
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) { //触碰响应
		final ViewConfiguration vc = ViewConfiguration.get(getContext());
		final float touchThreshold = TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, 30.0f, getResources()
						.getDisplayMetrics());
		final int widthPixels = getResources().getDisplayMetrics().widthPixels;

		final double hypo;
		final boolean overcameSlop;

		/* Immediately bomb out if the drawer is disabled */
		if (!mDrawerEnabled) {
			return false;
		}

		/*
		 * ...otherwise, handle the various types of input events.
		 */
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			/*
			 * Record the starting X and Y positions for the possible gesture.
			 */
			mGestureStartX = mGestureCurrentX = (int) (ev.getX() + 0.5f);
			mGestureStartY = mGestureCurrentY = (int) (ev.getY() + 0.5f);

			/*
			 * If the starting X position is within the touch threshold of 30dp
			 * inside the screen's left edge, set mGestureStared to true so that
			 * future ACTION_MOVE events will continue being handled here.
			 */

			if (mGestureStartX < touchThreshold && !mDrawerOpened) {
				mGestureStarted = true;
			}

			if (mGestureStartX > mDrawerWidth && mDrawerOpened) {
				mGestureStarted = true;
			}

			if (mDrawerMoving && mGestureStartX > mDecorOffsetX) {
				return true;
			}

			/*
			 * We still want to return false here since we aren't positive we've
			 * got a gesture we want just yet.
			 */
			return false;
		case MotionEvent.ACTION_MOVE:

			/*
			 * Make sure the gesture was started within 30dp of the screen's
			 * left edge.
			 */
			if (!mGestureStarted) {
				return false;
			}

			/*
			 * Make sure we're not going backwards, but only if the drawer isn't
			 * open yet
			 */
			if (!mDrawerOpened
					&& (ev.getX() < mGestureCurrentX || ev.getX() < mGestureStartX)) {
				return (mGestureStarted = false);
			}

			/*
			 * Update the current X and Y positions for the gesture.
			 */
			mGestureCurrentX = (int) (ev.getX() + 0.5f);
			mGestureCurrentY = (int) (ev.getY() + 0.5f);

			/*
			 * Decide whether there is enough movement to do anything real.
			 */
			hypo = Math.hypot(mGestureCurrentX - mGestureStartX,
					mGestureCurrentY - mGestureStartY);
			overcameSlop = hypo > vc.getScaledTouchSlop();

			/*
			 * If the last check is true, we'll start handling events in
			 * DrawerGarment's onTouchEvent(MotionEvent) method from now on.
			 */
			return overcameSlop;
		case MotionEvent.ACTION_UP:

			mGestureStarted = false;

			/*
			 * If we just tapped the right edge with the drawer open, close the
			 * drawer.
			 */
			if (mGestureStartX > mDrawerWidth && mDrawerOpened) {
				closeDrawer();
				mGestureStartX = mGestureCurrentX = -1;
				mGestureStartY = mGestureCurrentY = -1;
				return true;
			} else {
				mGestureStartX = mGestureCurrentX = -1;
				mGestureStartY = mGestureCurrentY = -1;
				return false;
			}
		}

		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) { //子视图响应完触发

		final ViewConfiguration vc = ViewConfiguration.get(getContext());
		final int widthPixels = getResources().getDisplayMetrics().widthPixels;

		final int deltaX = (int) (event.getX() + 0.5f) - mGestureCurrentX;
		final int deltaY = (int) (event.getY() + 0.5f) - mGestureCurrentY;

		/*
		 * Obtain a new VelocityTracker if we don't already have one. Also add
		 * this MotionEvent to the new/existing VelocityTracker so we can
		 * determine flings later on.
		 */
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);

		/*
		 * Update the current X and Y positions for the ongoing gesture.
		 */
		mGestureCurrentX = (int) (event.getX() + 0.5f);
		mGestureCurrentY = (int) (event.getY() + 0.5f);

		switch (event.getAction()) {
		case MotionEvent.ACTION_MOVE:
			mDrawerContent.setVisibility(VISIBLE);
			mDrawerMoving = true;

			if (mDecorOffsetX + deltaX > mDrawerWidth) {
				if (mDecorOffsetX != mDrawerWidth) {
					mDrawerOpened = true;
					mDecorContent.offsetLeftAndRight(mDrawerWidth
							- mDecorOffsetX);
					mDecorOffsetX = mDrawerWidth;
					invalidate();
				}
			} else if (mDecorOffsetX + deltaX < 0) {
				if (mDecorOffsetX != 0) {
					mDrawerOpened = false;
					mDecorContent.offsetLeftAndRight(0 - mDecorContent
							.getLeft());
					mDecorOffsetX = 0;
					invalidate();
				}
			} else {
				mDecorContent.offsetLeftAndRight(deltaX);
				mDecorOffsetX += deltaX;
				invalidate();
			}

			return true;
		case MotionEvent.ACTION_UP:
			mGestureStarted = false;
			mDrawerMoving = false;

			/*
			 * Determine if the user performed a fling based on the final
			 * velocity of the gesture.
			 */
			mVelocityTracker.computeCurrentVelocity(1000);
			if (Math.abs(mVelocityTracker.getXVelocity()) > vc
					.getScaledMinimumFlingVelocity()) {
				/*
				 * Okay, the user did a fling, so determine the direction in
				 * which the fling was flung so we know which way to toggle the
				 * drawer state.
				 */
				if (mVelocityTracker.getXVelocity() > 0) {
					mDrawerOpened = false;
					openDrawer();
				} else {
					mDrawerOpened = true;
					closeDrawer();
				}
			} else {
				/*
				 * No sizable fling has been flung, so fling the drawer towards
				 * whichever side we're closest to being flung at.
				 */
				if (mDecorOffsetX > (widthPixels / 2.0)) {
					mDrawerOpened = false;
					openDrawer();
				} else {
					mDrawerOpened = true;
					closeDrawer();
				}
			}
			return true;
		}
		return false;
	}

	@Override
	protected void dispatchDraw(Canvas canvas) { //绘制子视图
		super.dispatchDraw(canvas);

		if (mDrawerOpened || mDrawerMoving) {
			canvas.save();
			canvas.translate(mDecorOffsetX, 0);
			mShadowDrawable.draw(canvas);
			canvas.restore();
		}
	}

	//设置颜色
	public void setDecorContentBackgroundColor(final int color) {
		mDecorContentBackgroundColor = color;
	}

	public int getDecorContentBackgroundColor() {
		return mDecorContentBackgroundColor;
	}

	//设置目标宽度
	public void setTouchTargetWidth(final int width) {
		mTouchTargetWidth = width;
	}

	public int getTouchTargetWidth() {
		return mTouchTargetWidth;
	}

	/**
	 * Sets the maximum width in pixels the drawer will open to. Default is
	 * WRAP_CONTENT. Can also be MATCH_PARENT or another value in pixels.
	 * 
	 * @param maxWidth
	 */
	public void setDrawerMaxWidth(final int maxWidth) {
		mDrawerMaxWidth = maxWidth;
	}

	public int getDrawerMaxWidth() {
		return mDrawerMaxWidth;
	}

	public void setDrawerEnabled(final boolean enabled) {
		mDrawerEnabled = enabled;
	}

	public boolean isDrawerEnabled() {
		return mDrawerEnabled;
	}

	public void toggleDrawer(final boolean animate) {
		if (!mDrawerOpened) {
			openDrawer(animate);
		} else {
			closeDrawer(animate);
		}
	}

	public void toggleDrawer() {
		toggleDrawer(true);
	}

	public void openDrawer(final boolean animate) {
		if (mDrawerMoving) {
			mScrollerHandler.removeCallbacks(mDrawCloseRunnable);
			mScrollerHandler.removeCallbacks(mDrawOpenRunnable);
		}

		if (mDrawerOpened) {
			return;
		}

		mDrawerContent.setVisibility(VISIBLE);
		mDrawerMoving = true;

		final int widthPixels = getResources().getDisplayMetrics().widthPixels;
		if (mDrawerWidth > widthPixels - mTouchTargetWidth) {
			mScroller.startScroll(mDecorOffsetX, 0,
					(widthPixels - mTouchTargetWidth) - mDecorOffsetX, 0,
					animate ? SCROLL_DURATION : 0);
		} else {
			mScroller.startScroll(mDecorOffsetX, 0, mDrawerWidth
					- mDecorOffsetX, 0, animate ? SCROLL_DURATION : 0);
		}

		mDrawOpenRunnable = new Runnable() {
			@Override
			public void run() {
				final boolean scrolling = mScroller.computeScrollOffset();
				mDecorContent.offsetLeftAndRight(mScroller.getCurrX()
						- mDecorOffsetX);
				mDecorOffsetX = mScroller.getCurrX();
				postInvalidate();

				if (!scrolling) {
					mDrawerMoving = false;
					mDrawerOpened = true;
					if (mDrawerCallbacks != null) {
						mScrollerHandler.post(new Runnable() {
							@Override
							public void run() {
								mDrawerCallbacks.onDrawerOpened();
							}
						});
					}
				} else {
					mScrollerHandler.post(this);
				}
			}
		};
		mScrollerHandler.post(mDrawOpenRunnable);
	}

	public void openDrawer() {
		openDrawer(true);
	}

	public void closeDrawer(final boolean animate) {
		if (mDrawerMoving) {
			mScrollerHandler.removeCallbacks(mDrawCloseRunnable);
			mScrollerHandler.removeCallbacks(mDrawOpenRunnable);
		} else if (!mDrawerOpened) {
			return;
		}

		mDrawerMoving = true;

		final int widthPixels = getResources().getDisplayMetrics().widthPixels;
		mScroller.startScroll(mDecorOffsetX, 0, -mDecorOffsetX, 0,
				animate ? SCROLL_DURATION : 0);

		mDrawCloseRunnable = new Runnable() {
			@Override
			public void run() {
				final boolean scrolling = mScroller.computeScrollOffset();
				mDecorContent.offsetLeftAndRight(mScroller.getCurrX()
						- mDecorOffsetX);
				mDecorOffsetX = mScroller.getCurrX();
				postInvalidate();

				if (!scrolling) {
					mDrawerMoving = false;
					mDrawerOpened = false;
					mDrawerContent.setVisibility(INVISIBLE);
					if (mDrawerCallbacks != null) {
						mScrollerHandler.post(new Runnable() {
							@Override
							public void run() {
								mDrawerCallbacks.onDrawerClosed();
							}
						});
					}
				} else {
					mScrollerHandler.post(this);
				}
			}
		};
		mScrollerHandler.post(mDrawCloseRunnable);
	}

	public void closeDrawer() {
		closeDrawer(true);
	}

	public boolean isDrawerOpened() {
		return mDrawerOpened;
	}

	public boolean isDrawerMoving() {
		return mDrawerMoving;
	}

	public void setDrawerCallbacks(final IDrawerCallbacks callbacks) {
		mDrawerCallbacks = callbacks;
	}

	public IDrawerCallbacks getDrawerCallbacks() {
		return mDrawerCallbacks;
	}

	public int getSlideTarget() {
		return mSlideTarget;
	}

	public void setSlideTarget(final int slideTarget) {
		if (mSlideTarget != slideTarget) {
			mSlideTarget = slideTarget;
			reconfigureViewHierarchy();
		}

	}
}

 

呵呵,是不是非常的简单?

 

项目下载

 

posted @ 2013-06-15 14:30  爱生活,爱编程  阅读(321)  评论(0编辑  收藏  举报