Android开源AndroidSideMenu实现抽屉和侧滑菜单
短信预约 -IT技能 免费直播动态提醒
AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。
核心类如下:
package com.agimind.widget;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
public class SlideHolder extends FrameLayout {
public final static int DIRECTION_LEFT = 1;
public final static int DIRECTION_RIGHT = -1;
protected final static int MODE_READY = 0;
protected final static int MODE_SLIDE = 1;
protected final static int MODE_FINISHED = 2;
private Bitmap mCachedBitmap;
private Canvas mCachedCanvas;
private Paint mCachedPaint;
private View mMenuView;
private int mMode = MODE_READY;
private int mDirection = DIRECTION_LEFT;
private int mOffset = 0;
private int mStartOffset;
private int mEndOffset;
private boolean mEnabled = true;
private boolean mInterceptTouch = true;
private boolean mAlwaysOpened = false;
private boolean mDispatchWhenOpened = false;
private Queue<Runnable> mWhenReady = new LinkedList<Runnable>();
private OnSlideListener mListener;
public SlideHolder(Context context) {
super(context);
initView();
}
public SlideHolder(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SlideHolder(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mCachedPaint = new Paint(
Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG
| Paint.DITHER_FLAG
);
}
@Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mEnabled;
}
public void setDirection(int direction) {
closeImmediately();
mDirection = direction;
}
public void setAllowInterceptTouch(boolean allow) {
mInterceptTouch = allow;
}
public boolean isAllowedInterceptTouch() {
return mInterceptTouch;
}
public void setDispatchTouchWhenOpened(boolean dispatch) {
mDispatchWhenOpened = dispatch;
}
public boolean isDispatchTouchWhenOpened() {
return mDispatchWhenOpened;
}
public void setAlwaysOpened(boolean opened) {
mAlwaysOpened = opened;
requestLayout();
}
public int getMenuOffset() {
return mOffset;
}
public void setOnSlideListener(OnSlideListener lis) {
mListener = lis;
}
public boolean isOpened() {
return mAlwaysOpened || mMode == MODE_FINISHED;
}
public void toggle(boolean immediately) {
if(immediately) {
toggleImmediately();
} else {
toggle();
}
}
public void toggle() {
if(isOpened()) {
close();
} else {
open();
}
}
public void toggleImmediately() {
if(isOpened()) {
closeImmediately();
} else {
openImmediately();
}
}
public boolean open() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
open();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean openImmediately() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
openImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.VISIBLE);
mMode = MODE_FINISHED;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(true);
}
return true;
}
public boolean close() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
close();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean closeImmediately() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
closeImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.GONE);
mMode = MODE_READY;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(false);
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = 0;
final int parentTop = 0;
final int parentRight = r - l;
final int parentBottom = b - t;
View menu = getChildAt(0);
int menuWidth = menu.getMeasuredWidth();
if(mDirection == DIRECTION_LEFT) {
menu.layout(parentLeft, parentTop, parentLeft+menuWidth, parentBottom);
} else {
menu.layout(parentRight-menuWidth, parentTop, parentRight, parentBottom);
}
if(mAlwaysOpened) {
if(mDirection == DIRECTION_LEFT) {
mOffset = menuWidth;
} else {
mOffset = 0;
}
} else if(mMode == MODE_FINISHED) {
mOffset = mDirection*menuWidth;
} else if(mMode == MODE_READY) {
mOffset = 0;
}
View main = getChildAt(1);
main.layout(
parentLeft + mOffset,
parentTop,
parentLeft + mOffset + main.getMeasuredWidth(),
parentBottom
);
invalidate();
Runnable rn;
while((rn = mWhenReady.poll()) != null) {
rn.run();
}
}
private boolean isReadyForSlide() {
return (getWidth() > 0 && getHeight() > 0);
}
@Override
protected void onMeasure(int wSp, int hSp) {
mMenuView = getChildAt(0);
if(mAlwaysOpened) {
View main = getChildAt(1);
if(mMenuView != null && main != null) {
measureChild(mMenuView, wSp, hSp);
LayoutParams lp = (LayoutParams) main.getLayoutParams();
if(mDirection == DIRECTION_LEFT) {
lp.leftMargin = mMenuView.getMeasuredWidth();
} else {
lp.rightMargin = mMenuView.getMeasuredWidth();
}
}
}
super.onMeasure(wSp, hSp);
}
private byte mFrame = 0;
@Override
protected void dispatchDraw(Canvas canvas) {
try {
if(mMode == MODE_SLIDE) {
View main = getChildAt(1);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if(main.isDirty()) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
} else {
if(++mFrame % 5 == 0) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
}
View menu = getChildAt(0);
final int scrollX = menu.getScrollX();
final int scrollY = menu.getScrollY();
canvas.save();
if(mDirection == DIRECTION_LEFT) {
canvas.clipRect(0, 0, mOffset, menu.getHeight(), Op.REPLACE);
} else {
int menuWidth = menu.getWidth();
int menuLeft = menu.getLeft();
canvas.clipRect(menuLeft+menuWidth+mOffset, 0, menuLeft+menuWidth, menu.getHeight());
}
canvas.translate(menu.getLeft(), menu.getTop());
canvas.translate(-scrollX, -scrollY);
menu.draw(canvas);
canvas.restore();
canvas.drawBitmap(mCachedBitmap, mOffset, 0, mCachedPaint);
} else {
if(!mAlwaysOpened && mMode == MODE_READY) {
mMenuView.setVisibility(View.GONE);
}
super.dispatchDraw(canvas);
}
} catch(IndexOutOfBoundsException e) {
}
}
private int mHistoricalX = 0;
private boolean mCloseOnRelease = false;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(((!mEnabled || !mInterceptTouch) && mMode == MODE_READY) || mAlwaysOpened) {
return super.dispatchTouchEvent(ev);
}
if(mMode != MODE_FINISHED) {
onTouchEvent(ev);
if(mMode != MODE_SLIDE) {
super.dispatchTouchEvent(ev);
} else {
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(cancelEvent);
cancelEvent.recycle();
}
return true;
} else {
final int action = ev.getAction();
Rect rect = new Rect();
View menu = getChildAt(0);
menu.getHitRect(rect);
if(!rect.contains((int) ev.getX(), (int) ev.getY())) {
if (action == MotionEvent.ACTION_UP && mCloseOnRelease && !mDispatchWhenOpened) {
close();
mCloseOnRelease = false;
} else {
if(action == MotionEvent.ACTION_DOWN && !mDispatchWhenOpened) {
mCloseOnRelease = true;
}
onTouchEvent(ev);
}
if(mDispatchWhenOpened) {
super.dispatchTouchEvent(ev);
}
return true;
} else {
onTouchEvent(ev);
ev.offsetLocation(-menu.getLeft(), -menu.getTop());
menu.dispatchTouchEvent(ev);
return true;
}
}
}
private boolean handleTouchEvent(MotionEvent ev) {
if(!mEnabled) {
return false;
}
float x = ev.getX();
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
mHistoricalX = (int) x;
return true;
}
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
float diff = x - mHistoricalX;
if((mDirection*diff > 50 && mMode == MODE_READY) || (mDirection*diff < -50 && mMode == MODE_FINISHED)) {
mHistoricalX = (int) x;
initSlideMode();
} else if(mMode == MODE_SLIDE) {
mOffset += diff;
mHistoricalX = (int) x;
if(!isSlideAllowed()) {
finishSlide();
}
} else {
return false;
}
}
if(ev.getAction() == MotionEvent.ACTION_UP) {
if(mMode == MODE_SLIDE) {
finishSlide();
}
mCloseOnRelease = false;
return false;
}
return mMode == MODE_SLIDE;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = handleTouchEvent(ev);
invalidate();
return handled;
}
private void initSlideMode() {
mCloseOnRelease = false;
View v = getChildAt(1);
if(mMode == MODE_READY) {
mStartOffset = 0;
mEndOffset = mDirection*getChildAt(0).getWidth();
} else {
mStartOffset = mDirection*getChildAt(0).getWidth();
mEndOffset = 0;
}
mOffset = mStartOffset;
if(mCachedBitmap == null || mCachedBitmap.isRecycled() || mCachedBitmap.getWidth() != v.getWidth()) {
mCachedBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
mCachedCanvas = new Canvas(mCachedBitmap);
} else {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
}
v.setVisibility(View.VISIBLE);
mCachedCanvas.translate(-v.getScrollX(), -v.getScrollY());
v.draw(mCachedCanvas);
mMode = MODE_SLIDE;
mMenuView.setVisibility(View.VISIBLE);
}
private boolean isSlideAllowed() {
return (mDirection*mEndOffset > 0 && mDirection*mOffset < mDirection*mEndOffset && mDirection*mOffset >= mDirection*mStartOffset)
|| (mEndOffset == 0 && mDirection*mOffset > mDirection*mEndOffset && mDirection*mOffset <= mDirection*mStartOffset);
}
private void completeOpening() {
mOffset = mDirection*mMenuView.getWidth();
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_FINISHED;
mMenuView.setVisibility(View.VISIBLE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(true);
}
}
private Animation.AnimationListener mOpenListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeOpening();
}
};
private void completeClosing() {
mOffset = 0;
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_READY;
mMenuView.setVisibility(View.GONE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(false);
}
}
private Animation.AnimationListener mCloseListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeClosing();
}
};
private void finishSlide() {
if(mDirection*mEndOffset > 0) {
if(mDirection*mOffset > mDirection*mEndOffset/2) {
if(mDirection*mOffset > mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
} else {
if(mDirection*mOffset < mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
}
} else {
if(mDirection*mOffset < mDirection*mStartOffset/2) {
if(mDirection*mOffset < mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
} else {
if(mDirection*mOffset > mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
}
}
}
private class SlideAnimation extends Animation {
private static final float SPEED = 0.6f;
private float mStart;
private float mEnd;
public SlideAnimation(float fromX, float toX) {
mStart = fromX;
mEnd = toX;
setInterpolator(new DecelerateInterpolator());
float duration = Math.abs(mEnd - mStart) / SPEED;
setDuration((long) duration);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float offset = (mEnd - mStart) * interpolatedTime + mStart;
mOffset = (int) offset;
postInvalidate();
}
}
public static interface OnSlideListener {
public void onSlideCompleted(boolean opened);
}
}
使用:
package com.agimind.sidemenuexample;
import com.agimind.widget.SlideHolder;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.app.ActionBar;
import android.app.Activity;
public class MainActivity extends Activity {
private SlideHolder mSlideHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSlideHolder = (SlideHolder) findViewById(R.id.slideHolder);
// mSlideHolder.setAllowInterceptTouch(false);
// mSlideHolder.setAlwaysOpened(true);
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setHomeButtonEnabled(true);
View toggleView = findViewById(R.id.textView);
toggleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSlideHolder.toggle();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
mSlideHolder.toggle();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
布局如下:
<com.agimind.widget.SlideHolder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/slideHolder"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<ScrollView
android:layout_width="200dp"
android:layout_height="fill_parent"
android:background="@android:color/black" >
<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
</LinearLayout>
</ScrollView>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/swipe"
android:textSize="25sp" />
</RelativeLayout>
</com.agimind.widget.SlideHolder>
下载:AndroidSideMenu
您可能感兴趣的文章:Android DrawerLayout实现抽屉效果实例代码Android 抽屉效果的导航菜单实现代码实例Android实现右边抽屉Drawerlayout效果Android抽屉导航Navigation Drawer实例解析Android实现自定义滑动式抽屉效果菜单Android App中DrawerLayout抽屉效果的菜单编写实例Android组件之DrawerLayout实现抽屉菜单Android提高之多方向抽屉实现方法Android控件之SlidingDrawer(滑动式抽屉)详解与实例分享Android自定义控件仿QQ抽屉效果
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341