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