我是靠谱客的博主 仁爱自行车,这篇文章主要介绍Android之SlidingMenu源码分析1.SlidingMenu是什么2.SlidingMenu使用及源码分析3.SlidingMenu下实现滑动显示左右菜单栏源码分析4.CustomViewBehind,现在分享给大家,希望可以做个参考。

目录

 

1.SlidingMenu是什么

1.1SlidingMenu类结构

2.SlidingMenu使用及源码分析

2.1新建SlidingMenu对象

2.2slidingMenu基础参数设置

2.3将Activity视图添加到SlidingMenu下的上层视图

2.3设置SlidingView菜单栏视图

2.4显示SlidingView内容

3.SlidingMenu下实现滑动显示左右菜单栏源码分析

3.1按下事件监听

3.2滑动事件监听

3.3手势抬起

3.4事件的分发和拦截

3.5当前页面点击返回按钮时关闭菜单栏

4.CustomViewBehind

4.1菜单栏的滚动显示/隐藏

4.1.1只分析LEFT的情况(请他情况同理)

4.1.2在CustomViewAbove重写dispatchDraw方法,调用CustomViewAbove绘制阴影,淡入和选择器方法实现绘制


1.SlidingMenu是什么

SlidingMenu是一个开源的Android库,支持左右菜单栏,它允许开发者使用滑动菜单轻松创建应用程序,就像谷歌+、YouTube和Facebook应用程序中流行的那些滑动菜单一样。你可以在你的Android应用程序中自由使用它;

1.1SlidingMenu类结构

SlidingMenu库设计思路三个主要的ViewGroup(SlidingMenu,CustomViewAbove,CustomViewBehind), SlidingMenu包含两个盖在一起的ViewGroup(CustomViewAbove,CustomViewBehind),CustomViewAbove做为上层视图(主要将Activity中setContentView设置的视图添加到上层视图CustomViewAbove),CustomViewBehind做为下层视图(主要将左右菜单栏视图加添加到下层视图);
在CustomViewAbove上滑动时显示下层菜单视图CustomViewBehind;
主ViewGroup(SlidingMenu)作为一个自定义控件,里面的内容和菜单利用自定义属性设置;

复制代码
1
2
3
4
public class SlidingMenu extends RelativeLayout { private CustomViewAbove mViewAbove; private CustomViewBehind mViewBehind; }

SlidingMenu(继承RelativeLayout),就是这个主ViewGroup,其中又包含两个Activity内容CustomViewAbove和菜单CustomViewBehind(都继承ViewGroup),SlidingMenu提供两个自定义属性,供使用者传入两个布局id,对应主体内容和菜单布局,此外还有其他一些属性供开发者设置菜单效果;

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SlidingMenu extends RelativeLayout { public void setContent(int res) { this.setContent(LayoutInflater.from(this.getContext()).inflate(res, (ViewGroup)null)); } //设置Activity主视图 public void setContent(View view) { this.mViewAbove.setContent(view); this.showContent(); } public void setMenu(int res) { this.setMenu(LayoutInflater.from(this.getContext()).inflate(res, (ViewGroup)null)); } //设置左侧菜单栏视图 public void setMenu(View v) { this.mViewBehind.setContent(v); } public void setSecondaryMenu(int res) { this.setSecondaryMenu(LayoutInflater.from(this.getContext()).inflate(res, (ViewGroup)null)); } //设置右侧菜单栏视图 public void setSecondaryMenu(View v) { this.mViewBehind.setSecondaryContent(v); } }

2.SlidingMenu使用及源码分析

SlidingMenu类提供API接口调用,同时提供相应属性参数设置;

2.1新建SlidingMenu对象

复制代码
1
slidingMenu = new SlidingMenu(this);
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.mActionbarOverlay = false; LayoutParams behindParams = new LayoutParams(-1, -1); //底层视图主要添加左右菜单栏使用 this.mViewBehind = new CustomViewBehind(context); this.addView(this.mViewBehind, behindParams); LayoutParams aboveParams = new LayoutParams(-1, -1); //上层视图主要添加主视图(当前Activity的视图) this.mViewAbove = new CustomViewAbove(context); this.addView(this.mViewAbove, aboveParams); this.mViewAbove.setCustomViewBehind(this.mViewBehind); this.mViewBehind.setCustomViewAbove(this.mViewAbove); //设置监听菜单栏打开或者关闭接口 this.mViewAbove.setOnPageChangeListener(new OnPageChangeListener() { public static final int POSITION_OPEN = 0; public static final int POSITION_CLOSE = 1; public static final int POSITION_SECONDARY_OPEN = 2; public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } public void onPageSelected(int position) { if(position == 0 && SlidingMenu.this.mOpenListener != null) { SlidingMenu.this.mOpenListener.onOpen(); } else if(position == 1 && SlidingMenu.this.mCloseListener != null) { SlidingMenu.this.mCloseListener.onClose(); } else if(position == 2 && SlidingMenu.this.mSecondaryOpenListner != null) { SlidingMenu.this.mSecondaryOpenListner.onOpen(); } } }); //设置菜单栏模式,左侧,右侧,左右同时同存在 //public static final int LEFT = 0; //public static final int RIGHT = 1; //public static final int LEFT_RIGHT = 2; TypedArray ta = context.obtainStyledAttributes(attrs, styleable.SlidingMenu); int mode = ta.getInt(styleable.SlidingMenu_mode, 0); this.setMode(mode); int viewAbove = ta.getResourceId(styleable.SlidingMenu_viewAbove, -1); //设置空的主视图 if(viewAbove != -1) { this.setContent(viewAbove); } else { this.setContent(new FrameLayout(context)); } //设置空的菜单栏视图 int viewBehind = ta.getResourceId(styleable.SlidingMenu_viewBehind, -1); if(viewBehind != -1) { this.setMenu(viewBehind); } else { this.setMenu(new FrameLayout(context)); } //设置显示菜单栏显示操作模式 //public static final int TOUCHMODE_MARGIN = 0; // public static final int TOUCHMODE_FULLSCREEN = 1; //public static final int TOUCHMODE_NONE = 2; int touchModeAbove = ta.getInt(styleable.SlidingMenu_touchModeAbove, 0); this.setTouchModeAbove(touchModeAbove); int touchModeBehind = ta.getInt(styleable.SlidingMenu_touchModeBehind, 0); this.setTouchModeBehind(touchModeBehind); //设置视图显示样式 int offsetBehind = (int)ta.getDimension(styleable.SlidingMenu_behindOffset, -1.0F); int widthBehind = (int)ta.getDimension(styleable.SlidingMenu_behindWidth, -1.0F); if(offsetBehind != -1 && widthBehind != -1) { throw new IllegalStateException("Cannot set both behindOffset and behindWidth for a SlidingMenu"); } else { if(offsetBehind != -1) { this.setBehindOffset(offsetBehind); } else if(widthBehind != -1) { this.setBehindWidth(widthBehind); } else { this.setBehindOffset(0); } float scrollOffsetBehind = ta.getFloat(styleable.SlidingMenu_behindScrollScale, 0.33F); this.setBehindScrollScale(scrollOffsetBehind); int shadowRes = ta.getResourceId(styleable.SlidingMenu_shadowDrawable, -1); if(shadowRes != -1) { this.setShadowDrawable(shadowRes); } //设置阴影效果 int shadowWidth = (int)ta.getDimension(styleable.SlidingMenu_shadowWidth, 0.0F); this.setShadowWidth(shadowWidth); boolean fadeEnabled = ta.getBoolean(styleable.SlidingMenu_fadeEnabled, true); this.setFadeEnabled(fadeEnabled); float fadeDeg = ta.getFloat(styleable.SlidingMenu_fadeDegree, 0.33F); this.setFadeDegree(fadeDeg); boolean selectorEnabled = ta.getBoolean(styleable.SlidingMenu_selectorEnabled, false); this.setSelectorEnabled(selectorEnabled); int selectorRes = ta.getResourceId(styleable.SlidingMenu_selectorDrawable, -1); if(selectorRes != -1) { this.setSelectorDrawable(selectorRes); } ta.recycle(); } }

2.2slidingMenu基础参数设置

复制代码
1
2
3
4
5
6
slidingMenu.setMode(SlidingMenu.LEFT_RIGHT); // 设置触摸屏幕的模式 slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); // 设置渐入渐出效果的值 slidingMenu.setFadeDegree(0.35f); slidingMenu.setFadeEnabled(true);

a.设置菜单栏模式

slidingMenu.setMode(SlidingMenu.LEFT_RIGHT);

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
/** Constant value for use with setMode(). Puts the menu to the left of the content. */ /**方法setMode()使用的常量.把菜单放在内容的左边*/ public static final int LEFT = 0; /** Constant value for use with setMode(). Puts the menu to the right of the content. */ /**方法setMode()使用的常量.把菜单放在内容的右边*/ public static final int RIGHT = 1; /** Constant value for use with setMode(). Puts menus to the left and right of the content. */ /**方法setMode()使用的常量.把菜单放在内容的左边和右边*/

b.设置触摸屏幕模式
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe * gesture on the screen's margin */ /**使用setTouchModeAbove()的常量.允许SlidingMenu在屏幕的边缘通过一个猛击的手势打开. */ public static final int TOUCHMODE_MARGIN = 0; /** Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe * gesture anywhere on the screen */ /**使用setTouchModeAbove()的常量.允许SlidingMenu在屏幕的任何地方通过一个猛击的手势打开. */ public static final int TOUCHMODE_FULLSCREEN = 1; /** Constant value for use with setTouchModeAbove(). Denies the SlidingMenu to be opened with a swipe * gesture */ /**使用setTouchModeAbove()的常量.不允许SlidingMenu通过一个猛击的手势打开. */ public static final int TOUCHMODE_NONE = 2;

c.设置显示菜单栏动画效果

// 设置渐入渐出效果的值
        slidingMenu.setFadeDegree(0.35f);
        slidingMenu.setFadeEnabled(true);

2.3将Activity视图添加到SlidingMenu下的上层视图

复制代码
1
2
//使SlidingMenu附加在Activity上 slidingMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void attachToActivity(Activity activity, int slideStyle, boolean actionbarOverlay) { if(slideStyle != 0 && slideStyle != 1) { throw new IllegalArgumentException("slideStyle must be either SLIDING_WINDOW or SLIDING_CONTENT"); } else if(this.getParent() != null) { throw new IllegalStateException("This SlidingMenu appears to already be attached"); } else { TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{16842836}); int background = a.getResourceId(0, 0); a.recycle(); switch(slideStyle) { case 0: this.mActionbarOverlay = false; ViewGroup decor = (ViewGroup)activity.getWindow().getDecorView(); ViewGroup decorChild = (ViewGroup)decor.getChildAt(0); decorChild.setBackgroundResource(background); decor.removeView(decorChild); decor.addView(this); this.setContent(decorChild); break; case 1: this.mActionbarOverlay = actionbarOverlay; ViewGroup contentParent = (ViewGroup)activity.findViewById(16908290);//获取Activity根视图 View content = contentParent.getChildAt(0); //获取Activity根视图下的第一个视图(setContentView添加的) contentParent.removeView(content);//Activity根视图下的第一个视图(setContentView添加的) contentParent.addView(this);将SlidingMenu视图添加到根视图 this.setContent(content);//将Activity根视图下的第一个视图(setContentView添加的)添加到SlidingMenu下的上层视图 if(content.getBackground() == null) { content.setBackgroundResource(background); } } } }

2.3设置SlidingView菜单栏视图

复制代码
1
2
3
4
5
6
slidingMenu.setMenu(R.layout.left_menu); leftMenu = new LeftMenuFragment(); getSupportFragmentManager().beginTransaction().add(R.id.left_menu, leftMenu).commit(); slidingMenu.setSecondaryMenu(R.layout.right_menu); rightMenu = new RightMenuFragment(); getSupportFragmentManager().beginTransaction().add(R.id.right_menu, rightMenu).commit();

在SlidingView将菜单栏视图添加到底层视图

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
SlidingView public void setMenu(int res) { this.setMenu(LayoutInflater.from(this.getContext()).inflate(res, (ViewGroup)null)); } public void setMenu(View v) { this.mViewBehind.setContent(v); } public void setSecondaryMenu(int res) { this.setSecondaryMenu(LayoutInflater.from(this.getContext()).inflate(res, (ViewGroup)null)); } public void setSecondaryMenu(View v) { this.mViewBehind.setSecondaryContent(v); }

2.4显示SlidingView内容

复制代码
1
slidingMenu.showContent();

3.SlidingMenu下实现滑动显示左右菜单栏源码分析

CustomViewAbove.java

主要处理界面的touch事件,解决滑动冲突,控制着整个控件当前的滑动状态,大部分方法属性也传递到slidingmenu.java暴露给了用户;

CustomViewBehind.java

主要处理界面的一些基本属性及状态,如滑动时视差滚动,透明度渐变,对画布的操作,阴影的绘制等,大部分方法也通过slidingmenu.java暴露给了用户,Behind View 中的touch事件也延用了Above View 的touch 事件,相当于统一交予Above View来处理;

CustomViewAbove同时处理CustomViewAbove和CustomViewBehind触摸事件;CustomViewAbove重点主要实现onInterceptTouchEvent()和onTouchEvent()处理滑动事件,onInterceptTouchEvent()主要决定是否进行事件拦截,onTouchEvent()实际处理触摸事件,监听执行视图滑动;

在CustomViewAbove处理事件主要处理按下,滑动,抬起;

复制代码
1
2
3
4
5
6
7
public static final int ACTION_DOWN = 0; //按下 public static final int ACTION_UP = 1; //抬起 public static final int ACTION_MOVE = 2; //滑动

3.1按下事件监听

复制代码
1
2
3
4
5
6
7
case MotionEvent.ACTION_DOWN: this.completeScroll(); int index = MotionEventCompat.getActionIndex(ev); this.mActivePointerId = MotionEventCompat.getPointerId(ev, index); this.mLastMotionX = this.mInitialMotionX = ev.getX(); break;

this.completeScroll();

如果视图还在滚动,则停止;

this.mActivePointerId = MotionEventCompat.getPointerId(ev, index);

获取第一个按下的触控点(手势)ID,mActivePointerId是记录多点触碰的activity第一个点的id,总之是用来处理多点除控时拖动的稳定性;
this.mLastMotionX = this.mInitialMotionX = ev.getX();

只需要实现水平滚动,记录按下时x轴坐标;

3.2滑动事件监听

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
case MotionEvent.ACTION_MOVE: if(!this.mIsBeingDragged) { this.determineDrag(ev); if(this.mIsUnableToDrag) { return false; } } if(this.mIsBeingDragged) { activePointerIndex = this.getPointerIndex(ev, this.mActivePointerId); if(this.mActivePointerId != -1) { float x = MotionEventCompat.getX(ev, activePointerIndex); float deltaX = this.mLastMotionX - x; this.mLastMotionX = x; oldScrollX = (float)this.getScrollX(); float scrollX = oldScrollX + deltaX; leftBound = (float)this.getLeftBound(); float rightBound = (float)this.getRightBound(); if(scrollX < leftBound) { scrollX = leftBound; } else if(scrollX > rightBound) { scrollX = rightBound; } this.mLastMotionX += scrollX - (float)((int)scrollX); this.scrollTo((int)scrollX, this.getScrollY()); this.pageScrolled((int)scrollX); } } break; case 3:

1)this.determineDrag(ev);

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void determineDrag(MotionEvent ev) { //多点触碰的activity第一个点的id int activePointerId = this.mActivePointerId; //多点触碰的activity第一个点的id对应的索引,代表每个手指的索引值 int pointerIndex = this.getPointerIndex(ev, activePointerId); if(activePointerId != -1 && pointerIndex != -1) { float x = MotionEventCompat.getX(ev, pointerIndex); float dx = x - this.mLastMotionX; float xDiff = Math.abs(dx); float y = MotionEventCompat.getY(ev, pointerIndex); float dy = y - this.mLastMotionY; float yDiff = Math.abs(dy); if(xDiff > (float)(this.isMenuOpen()?this.mTouchSlop / 2:this.mTouchSlop) && xDiff > yDiff && this.thisSlideAllowed(dx)) { this.startDrag(); this.mLastMotionX = x; this.mLastMotionY = y; this.setScrollingCacheEnabled(true); } else if(xDiff > (float)this.mTouchSlop) { this.mIsUnableToDrag = true; } } }

if(xDiff > (float)(this.isMenuOpen()?this.mTouchSlop / 2:this.mTouchSlop) && xDiff > yDiff && this.thisSlideAllowed(dx))

xDiff/yDiff代表水平和垂直方向移动的距离;

mTouchSlop是系统对于拖动的最低长度判断,即至少移动多少距离,才算是一个拖动的动作;

判断拖动条件:

水平移动距离xDiff大于拖动的最低长度(若菜单栏打开,xDiff大于拖动的最低长度/2),水平方向xDiff移动距离大于垂直方向yDiff移动距离,this.thisSlideAllowed(dx)判断是否允许滑动显示菜单栏(x轴移动的距离(正:向右滑动,负:向左滑动));

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//CustomViewAbove private boolean thisSlideAllowed(float dx) { boolean allowed = false; if(this.isMenuOpen()) { allowed = this.mViewBehind.menuOpenSlideAllowed(dx); } else { allowed = this.mViewBehind.menuClosedSlideAllowed(dx); } return allowed; } //CustomViewBehind public boolean menuClosedSlideAllowed(float dx) { return this.mMode == 0?dx > 0.0F:(this.mMode == 1?dx < 0.0F:this.mMode == 2); } public boolean menuOpenSlideAllowed(float dx) { return this.mMode == 0?dx < 0.0F:(this.mMode == 1?dx > 0.0F:this.mMode == 2); }

根据滑动方向确认是否需要允许滑动显示菜单栏,例如菜单栏关闭状态判断this.mMode == 0(仅有左侧菜单栏)?dx > 0.0F,dx为正表示向右滑动,允许显示左侧菜单栏;

在x水平移动距离大于y垂直移动距离,同时移动的方向有设置了相应的菜单栏则允许滑动(判断是否允许拖动操作),则判断为触摸事件;

private void determineDrag(MotionEvent ev){

this.startDrag(); //主要设置mIsBeingDragged变量为true,表示滑动准备显示菜单栏
this.mLastMotionX = x;//记录滑动的x轴坐标
this.mLastMotionY = y;//记录滑动的y轴坐标

}

2)this.mIsBeingDragged==true

a.this.scrollTo((int)scrollX, this.getScrollY());

随着手指滑动动态计算滑动位置,执行视图滚动;

复制代码
1
2
3
4
5
6
public void scrollTo(int x, int y) { super.scrollTo(x, y); this.mScrollX = (float)x; this.mViewBehind.scrollBehindTo(this.mContent, x, y); ((SlidingMenu)this.getParent()).manageLayers(this.getPercentOpen()); }

super.scrollTo(x, y);执行视图滚动,

this.mViewBehind.scrollBehindTo(this.mContent, x, y);调用菜单栏视图执行滚动;

manageLayers是提高性能用的,不深入研究了;

b.this.pageScrolled((int)scrollX);

在SlidingMenu类中会设置监听器,在pageScrolled会调用监听器回调,回传滚动变化;

3.3手势抬起

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case MotionEvent.ACTION_UP: if(this.mIsBeingDragged) { VelocityTracker velocityTracker = this.mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, (float)this.mMaximumVelocity); int initialVelocity = (int)VelocityTrackerCompat.getXVelocity(velocityTracker, this.mActivePointerId); int scrollX = this.getScrollX(); oldScrollX = (float)(scrollX - this.getDestScrollX(this.mCurItem)) / (float)this.getBehindWidth(); int activePointerIndex = this.getPointerIndex(ev, this.mActivePointerId); if(this.mActivePointerId != -1) { leftBound = MotionEventCompat.getX(ev, activePointerIndex); int totalDelta = (int)(leftBound - this.mInitialMotionX); int nextPage = this.determineTargetPage(oldScrollX, initialVelocity, totalDelta); this.setCurrentItemInternal(nextPage, true, true, initialVelocity); } else { this.setCurrentItemInternal(this.mCurItem, true, true, initialVelocity); } this.mActivePointerId = -1; this.endDrag(); } else if(this.mQuickReturn && this.mViewBehind.menuTouchInQuickReturn(this.mContent, this.mCurItem, ev.getX() + this.mScrollX)) { this.setCurrentItem(1); this.endDrag(); } break;

手势抬起时检测是否是显示菜单栏滑动操作;

主要包含两部分实现,一个是计算滑动以后要显示页号(0:表示左菜单,1:表示当前主页,2:表示右侧菜单),另一部分是执行页面滚动,完成目标页号的显示;

a.int nextPage = this.determineTargetPage(oldScrollX, initialVelocity, totalDelta);

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private int determineTargetPage(float pageOffset, int velocity, int deltaX) { int targetPage = this.mCurItem; if(Math.abs(deltaX) > this.mFlingDistance && Math.abs(velocity) > this.mMinimumVelocity) { if(velocity > 0 && deltaX > 0) { --targetPage; } else if(velocity < 0 && deltaX < 0) { ++targetPage; } } else { targetPage = Math.round((float)this.mCurItem + pageOffset); } return targetPage; }

pageOffset:表示页面偏移比例(范围:-1-1),负值表示表示向右滑动,正值表示向左滑动;

velocity:表示滑动速度;

deltaX:总的滑动距离;

条件判断根据距离和速度判断,若移动的那个的距离大于this.mFlingDistance(float density = context.getResources().getDisplayMetrics().density;
this.mFlingDistance = (int)(25.0F * density);)同时滑动速度大于最小滑动速度,则根据滑动速度和具体计算目标页号,同时为正,显示左侧,同理相反;

否则采用四舍五入方式,例如pageOffset=-0.7;则1+(-0.7)=0.3,目标页号为0;

b.this.setCurrentItemInternal(nextPage, true, true, initialVelocity);

实际执行滚动显示目标页面;

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { if(!always && this.mCurItem == item) { this.setScrollingCacheEnabled(false); } else { item = this.mViewBehind.getMenuPage(item); boolean dispatchSelected = this.mCurItem != item; this.mCurItem = item; int destX = this.getDestScrollX(this.mCurItem); if(dispatchSelected && this.mOnPageChangeListener != null) { this.mOnPageChangeListener.onPageSelected(item); } if(dispatchSelected && this.mInternalPageChangeListener != null) { this.mInternalPageChangeListener.onPageSelected(item); } if(smoothScroll) { this.smoothScrollTo(destX, 0, velocity); } else { this.completeScroll(); this.scrollTo(destX, 0); } } }

a.页面变化监听;

b.滚动到目标页面

this.smoothScrollTo(destX, 0, velocity);

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void smoothScrollTo(int x, int y, int velocity) { if(this.getChildCount() == 0) { this.setScrollingCacheEnabled(false); } else { int sx = this.getScrollX(); int sy = this.getScrollY(); int dx = x - sx; int dy = y - sy; if(dx == 0 && dy == 0) { this.completeScroll(); if(this.isMenuOpen()) { if(this.mOpenedListener != null) { this.mOpenedListener.onOpened(); } } else if(this.mClosedListener != null) { this.mClosedListener.onClosed(); } } else { this.setScrollingCacheEnabled(true); this.mScrolling = true; int width = this.getBehindWidth(); int halfWidth = width / 2; float distanceRatio = Math.min(1.0F, 1.0F * (float)Math.abs(dx) / (float)width); float distance = (float)halfWidth + (float)halfWidth * this.distanceInfluenceForSnapDuration(distanceRatio); int duration = false; velocity = Math.abs(velocity); int duration; if(velocity > 0) { duration = 4 * Math.round(1000.0F * Math.abs(distance / (float)velocity)); } else { float pageDelta = (float)Math.abs(dx) / (float)width; duration = (int)((pageDelta + 1.0F) * 100.0F); duration = 600; } duration = Math.min(duration, 600); this.mScroller.startScroll(sx, sy, dx, dy, duration); this.invalidate(); } } }

判断条件若当前需要滚动的距离为0,则直接通知菜单栏打开或者额关闭;

否则根据duration执行滚动:

this.mScroller.startScroll(sx, sy, dx, dy, duration);
this.invalidate();

3.4事件的分发和拦截

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public boolean onInterceptTouchEvent(MotionEvent ev) { if(!this.mEnabled) { return false; } else { int action = ev.getAction() & 255; if(action != 3 && action != 1 && (action == 0 || !this.mIsUnableToDrag)) { switch(action) { case 0: int index = MotionEventCompat.getActionIndex(ev); this.mActivePointerId = MotionEventCompat.getPointerId(ev, index); if(this.mActivePointerId != -1) { this.mLastMotionX = this.mInitialMotionX = MotionEventCompat.getX(ev, index); this.mLastMotionY = MotionEventCompat.getY(ev, index); if(this.thisTouchAllowed(ev)) { this.mIsBeingDragged = false; this.mIsUnableToDrag = false; if(this.isMenuOpen() && this.mViewBehind.menuTouchInQuickReturn(this.mContent, this.mCurItem, ev.getX() + this.mScrollX)) { this.mQuickReturn = true; } } else { this.mIsUnableToDrag = true; } } break; case 2: this.determineDrag(ev); break; case 6: this.onSecondaryPointerUp(ev); } if(!this.mIsBeingDragged) { if(this.mVelocityTracker == null) { this.mVelocityTracker = VelocityTracker.obtain(); } this.mVelocityTracker.addMovement(ev); } return this.mIsBeingDragged || this.mQuickReturn; } else { this.endDrag(); return false; } } }

onInterceptTouchEvent返回true表示当前视图onTouchEvent处理触摸事件,false表示继续向下传递;

在菜单打开的时候要进行判断:

 if(this.isMenuOpen() && this.mViewBehind.menuTouchInQuickReturn(this.mContent, this.mCurItem, ev.getX() + this.mScrollX)) {
                                this.mQuickReturn = true;
                            }
如果点击在主体部分,则自动收回菜单,即让主体部分消费这个touch事件;
如果此时点击在菜单部分,那above中应该不消费此事件,要将其传给behind部分,然后让behind中的listview或者button等自定义设置的控件去获取处理touch事件;

3.5当前页面点击返回按钮时关闭菜单栏

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override public void onBackPressed() { if (slidingMenu.isMenuShowing() || slidingMenu.isSecondaryMenuShowing()) { slidingMenu.showContent(); } else { if (System.currentTimeMillis() - mLastClickTime <= 2000L) { super.onBackPressed(); } else { mLastClickTime = System.currentTimeMillis(); Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); } } }

4.CustomViewBehind

CustomViewBehind主要实现菜单栏的滚动显示/隐藏和菜单栏的阴影绘制等;

4.1菜单栏的滚动显示/隐藏

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void scrollBehindTo(View content, int x, int y) { int vis = 0; if(this.mMode == 0) { if(x >= content.getLeft()) { vis = 4; } this.scrollTo((int)((float)(x + this.getBehindWidth()) * this.mScrollScale), y); } else if(this.mMode == 1) { if(x <= content.getLeft()) { vis = 4; } this.scrollTo((int)((float)(this.getBehindWidth() - this.getWidth()) + (float)(x - this.getBehindWidth()) * this.mScrollScale), y); } else if(this.mMode == 2) { this.mContent.setVisibility(x >= content.getLeft()?4:0); this.mSecondaryContent.setVisibility(x <= content.getLeft()?4:0); vis = x == 0?4:0; if(x <= content.getLeft()) { this.scrollTo((int)((float)(x + this.getBehindWidth()) * this.mScrollScale), y); } else { this.scrollTo((int)((float)(this.getBehindWidth() - this.getWidth()) + (float)(x - this.getBehindWidth()) * this.mScrollScale), y); } } if(vis == 4) { Log.v("CustomViewBehind", "behind INVISIBLE"); } this.setVisibility(vis); }

4.1.1只分析LEFT的情况(请他情况同理)

检测滚动的x坐标大于主视图CustomViewAbove左侧则显示菜单栏视图;

复制代码
1
2
3
if(x >= content.getLeft()) { vis = 4; }

实现菜单栏内滚动

this.scrollTo((int)((float)(x + this.getBehindWidth()) * this.mScrollScale), y);
比如菜单的width即getBehindWidth的宽度为300
那从菜单关闭状态一直滚到菜单完全打开状态,above主体内容x轴上的scroll变化就是0 ~ -300
此时如果mScrollScale即滚动比例为0.3
那按照上面scrollBehindTo中的算法, behind菜单部分x轴上的scroll变化就是
(0+300)*0.3 ~ (-300+300)*0.3即100~0

如果比例换成0.6,那behind的x变化就是180~0

极端情况下
1.mScrollScale=0, behind的x变化为 0~0, 此时会发现behind菜单部分在打开关闭的过程中无任何滚动
2.mScrollScale=1,behind的换标为300~0, 此时的效果就是主体和菜单紧挨着以同一个速度滚动

注意,这里的x为滚动的偏移量,即scrollTo使用的参数,
比如scrollView中,scrollTo(0, 100)会往下滚动到y偏移量100的位置,
此时scrollView里面的内容视觉上看其实是向上移了~
这里同理
虽然朝右拖动的时候view看上去是朝右运动,但x轴上滚动偏移量是减少的

4.1.2在CustomViewAbove重写dispatchDraw方法,调用CustomViewAbove绘制阴影,淡入和选择器方法实现绘制

CustomViewAbove

复制代码
1
2
3
4
5
6
protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); this.mViewBehind.drawShadow(this.mContent, canvas); this.mViewBehind.drawFade(this.mContent, canvas, this.getPercentOpen()); this.mViewBehind.drawSelector(this.mContent, canvas, this.getPercentOpen()); }

CustomViewAbove

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void drawShadow(View content, Canvas canvas) { if(this.mShadowDrawable != null && this.mShadowWidth > 0) { int left = 0; if(this.mMode == 0) { left = content.getLeft() - this.mShadowWidth; } else if(this.mMode == 1) { left = content.getRight(); } else if(this.mMode == 2) { if(this.mSecondaryShadowDrawable != null) { left = content.getRight(); this.mSecondaryShadowDrawable.setBounds(left, 0, left + this.mShadowWidth, this.getHeight()); this.mSecondaryShadowDrawable.draw(canvas); } left = content.getLeft() - this.mShadowWidth; } this.mShadowDrawable.setBounds(left, 0, left + this.mShadowWidth, this.getHeight()); this.mShadowDrawable.draw(canvas); } }

this.mShadowDrawable.draw(canvas);将阴影Drawable绘制到画布上;

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void drawFade(View content, Canvas canvas, float openPercent) { if(this.mFadeEnabled) { int alpha = (int)(this.mFadeDegree * 255.0F * Math.abs(1.0F - openPercent)); this.mFadePaint.setColor(Color.argb(alpha, 0, 0, 0)); int left = 0; int right = 0; if(this.mMode == 0) { left = content.getLeft() - this.getBehindWidth(); right = content.getLeft(); } else if(this.mMode == 1) { left = content.getRight(); right = content.getRight() + this.getBehindWidth(); } else if(this.mMode == 2) { left = content.getLeft() - this.getBehindWidth(); right = content.getLeft(); canvas.drawRect((float)left, 0.0F, (float)right, (float)this.getHeight(), this.mFadePaint); left = content.getRight(); right = content.getRight() + this.getBehindWidth(); } canvas.drawRect((float)left, 0.0F, (float)right, (float)this.getHeight(), this.mFadePaint); } }

在画布上绘制阴影效果;

参考:

https://blog.csdn.net/u011494050/article/details/43531087

https://github.com/jfeinstein10/SlidingMenu

 

 

最后

以上就是仁爱自行车最近收集整理的关于Android之SlidingMenu源码分析1.SlidingMenu是什么2.SlidingMenu使用及源码分析3.SlidingMenu下实现滑动显示左右菜单栏源码分析4.CustomViewBehind的全部内容,更多相关Android之SlidingMenu源码分析1内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(73)

评论列表共有 0 条评论

立即
投稿
返回
顶部