Android实现侧滑抽屉菜单,Android开源AndroidSideMenu实现抽屉和侧滑菜单

AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。

3547a28a9e12b04a03a247b69bd41d00.png

核心类如下:

/*

* Copyright dmitry.zaicew@gmail.com Dmitry Zaitsev

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

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 mWhenReady = new LinkedList();

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;

}

/**

*

* @param direction - direction in which SlideHolder opens. Can be: DIRECTION_LEFT, DIRECTION_RIGHT

*/

public void setDirection(int direction) {

closeImmediately();

mDirection = direction;

}

/**

*

* @param allow - if false, SlideHolder won't react to swiping gestures (but still will be able to work by manually invoking mathods)

*/

public void setAllowInterceptTouch(boolean allow) {

mInterceptTouch = allow;

}

public boolean isAllowedInterceptTouch() {

return mInterceptTouch;

}

/**

*

* @param dispatch - if true, in open state SlideHolder will dispatch touch events to main layout (in other words - it will be clickable)

*/

public void setDispatchTouchWhenOpened(boolean dispatch) {

mDispatchWhenOpened = dispatch;

}

public boolean isDispatchTouchWhenOpened() {

return mDispatchWhenOpened;

}

/**

*

* @param opened - if true, SlideHolder will always be in opened state (which means that swiping won't work)

*/

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) {

/*

* On new versions we redrawing main layout only

* if it's marked as dirty

*/

if(main.isDirty()) {

mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

main.draw(mCachedCanvas);

}

} else {

/*

* On older versions we just redrawing our cache

* every 5th frame

*/

if(++mFrame % 5 == 0) {

mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

main.draw(mCachedCanvas);

}

}

/*

* Draw only visible part of menu

*/

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) {

/*

* Possibility of crashes on some devices (especially on Samsung).

* Usually, when ListView is empty.

*/

}

}

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);

/*

* toggleView can actually be any view you want. Here, for simplicity,

* we're using TextView, but you can easily replace it with button.

*

* Note, when menu opens our textView will become invisible, so it quite

* pointless to assign toggle-event to it. In real app consider using UP

* button instead. In our case toggle() can be replaced with open().

*/

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);

}

}

布局如下:

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/slideHolder"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

tools:context=".MainActivity" >

android:layout_width="200dp"

android:layout_height="fill_parent"

android:background="@android:color/black" >

android:layout_width="200dp"

android:layout_height="wrap_content"

android:orientation="vertical" >

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/menu_settings" />

android:layout_width="fill_parent"

android:layout_height="fill_parent" >

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" />

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值