开源项目:AndroidSideMenu(轻量级侧边栏)
(2014-08-27 10:43:16)
标签:
androidandroidsidemenu |
分类: Android |
/
* Copyright
http://blog.sina.com.cn/dalong798091984
*
* 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;
/
**
* 轻量级侧边栏:分为菜单栏和内容栏。
*
仅支持从左边或右边开启侧边栏菜单,不支持同时,且滑开菜单栏的动画不能修改;
*
实现方法:滑动的是内容栏,菜单栏不动。这个是使用Canvas.translate()配合动画来完成内容栏滑动效果的。
*
不过我们自己写的话,大多数会采用scrollTo()函数来完成。
/
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; //
标记侧边栏是否是持续打开的,用于大屏幕的Pad
private boolean
mDispatchWhenOpened = false; // 标记当侧边栏打开时,是否分发触摸手势
private
Queue<</span>Runnable>
mWhenReady = new
LinkedList<</span>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 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;
}
/
* 设置侧边栏总是打开,用于大屏幕的Pad设备
* @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();
}
/
**
* 获取侧边栏菜单的偏移量
* @return
*
/
public int
getMenuOffset()
{
return
mOffset;
}
public void
setOnSlideListener(OnSlideListener
lis) {
mListener
= lis;
}
/
**
* 菜单栏是否已经打开
* @return
*
/
public boolean
isOpened()
{
return
mAlwaysOpened || mMode
== MODE_FINISHED;
}
/
**
* 菜单栏的开关
* @param immediately
*
/
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();
}
}
/
**
* 开启菜单栏
* @return
*
/
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;
}
/
**
* 立即打开侧边栏菜单
* @return
*
/
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;
}
/
**
* 关闭菜单栏
* @return
*
/
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;
}
/
**
* 快速关闭菜单栏
* @return
*
/
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);
//
直接把菜单栏设为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();
}
}
/
**
*
是否准备好打开菜单栏或关闭菜单栏,即是否可以滑动??
* @return
返回false的唯一情况是:当前正在滑动菜单栏中...
*
/
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;
}
}
}
/
**
* 处理触摸手势事件
* @param ev
* @return
*
/
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 <</span>
-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;
}
/
* 初始化侧边栏菜单的模式:打开和关闭菜单栏的时候都需要重新设置
侧边栏参数,
*
这个函数在配合onLayout()函数,就实现了侧边栏动画。
*
/
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
<</span> 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 <</span>
mDirection *
mStartOffset) { // 防止滑过界
mOffset =
mStartOffset;
}
// 关闭菜单栏动画
Animation
anim = new
SlideAnimation(mOffset,
mStartOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
}
} else
{ //
菜单栏在右侧
if
(mDirection *
mOffset <</span>
mDirection * mStartOffset
/ 2)
{
if
(mDirection *
mOffset <</span>
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);
}
}

加载中…