开源项目之Android undergarment

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

开源项目之Android undergarment


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

开源项目之Android undergarment


开源项目之Android undergarment


不知道效果能不能得到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();

		}



	}

}

 

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

 

项目下载

 

你可能感兴趣的:(android)