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();
}
}
}
呵呵,是不是非常的简单?