Android仿QQ5.0侧滑菜单ResideMenu源码分析

AndroidResideMenu

github:https://github.com/SpecialCyCi/AndroidResideMenu csdn:http://download.csdn.net/detail/cym492224103/7887801

\

先看看如何使用:

把项目源码下载下来导入工程,可以看到

\

ResideMenu为引用工程,再看看如何使用这个引用工程来构建出ResideMenu,

1.先new一个ResideMenu对象

 

1. resideMenu = new ResideMenu(this);
2.设置它的背景图片

 

 

1. resideMenu.setBackground(R.drawable.menu_background);
3.绑定当前Activity

 

 

1. resideMenu.attachToActivity(this);
4.设置监听

 

 

1. resideMenu.setMenuListener(menuListener);
可以监听菜单打开和关闭状态

 

 

01. private ResideMenu.OnMenuListener menuListener = new ResideMenu.OnMenuListener() {
02. @Override
03. public void openMenu() {
04. Toast.makeText(mContext, "Menu is opened!", Toast.LENGTH_SHORT).show();
05. }
06.  
07. @Override
08. public void closeMenu() {
09. Toast.makeText(mContext, "Menu is closed!", Toast.LENGTH_SHORT).show();
10. }
11. };
5.设置内容缩放比例(0.1~1f)

 

 

1. //valid scale factor is between 0.0f and 1.0f. leftmenu'width is 150dip.
2. resideMenu.setScaleValue(0.6f);
6.创建子菜单

 

 

1. // create menu items;
2. itemHome     = new ResideMenuItem(this, R.drawable.icon_home,     "Home");
3. itemProfile  = new ResideMenuItem(this, R.drawable.icon_profile,  "Profile");
4. itemCalendar = new ResideMenuItem(this, R.drawable.icon_calendar, "Calendar");
5. itemSettings = new ResideMenuItem(this, R.drawable.icon_settings, "Settings");
7.设置点击事件及将刚创建的子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)

 

 

01. itemHome.setOnClickListener(this);
02. itemProfile.setOnClickListener(this);
03. itemCalendar.setOnClickListener(this);
04. itemSettings.setOnClickListener(this);
05.  
06. resideMenu.addMenuItem(itemHome, ResideMenu.DIRECTION_LEFT);
07. resideMenu.addMenuItem(itemProfile, ResideMenu.DIRECTION_LEFT);
08. resideMenu.addMenuItem(itemCalendar, ResideMenu.DIRECTION_RIGHT);
09. resideMenu.addMenuItem(itemSettings, ResideMenu.DIRECTION_RIGHT);
8.设置title按钮的点击事件,设置左右菜单的开关

 

 

01. // You can disable a direction by setting ->
02. // resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
03.  
04. findViewById(R.id.title_bar_left_menu).setOnClickListener(new View.OnClickListener() {
05. @Override
06. public void onClick(View view) {
07. resideMenu.openMenu(ResideMenu.DIRECTION_LEFT);
08. }
09. });
10. findViewById(R.id.title_bar_right_menu).setOnClickListener(new View.OnClickListener() {
11. @Override
12. public void onClick(View view) {
13. resideMenu.openMenu(ResideMenu.DIRECTION_RIGHT);
14. }
15. });
9.还重写了dispatchTouchEvent

 

 

1. @Override
2. public boolean dispatchTouchEvent(MotionEvent ev) {
3. return resideMenu.dispatchTouchEvent(ev);
4. }
10.菜单关闭方法

 

 

1. resideMenu.closeMenu();

 

11.屏蔽菜单方法

 

1. // You can disable a direction by setting ->
2. // resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);

 

使用方法已经说完了,接下来,看看它的源码,先看看源码的项目结构。

\

 

很多人初学者都曾纠结,看源码,如何从何看起,我个人建议从上面使用的顺序看起,并且在看的时候要带个问题去看去思考,这样更容易理解。

上面的第一步是,创建ResideMenu对象,我们就看看ResideMenu的构造。

 

1. public ResideMenu(Context context) {
2. super(context);
3. initViews(context);
4. }
从上面代码,看到构造里面就一个初始化view,思考问题:如何初始化view及初始化了什么view。

 

 

01. private void initViews(Context context){
02. LayoutInflater inflater = (LayoutInflater)
03. context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
04. inflater.inflate(R.layout.residemenu, this);
05. scrollViewLeftMenu = (ScrollView) findViewById(R.id.sv_left_menu);
06. scrollViewRightMenu = (ScrollView) findViewById(R.id.sv_right_menu);
07. imageViewShadow = (ImageView) findViewById(R.id.iv_shadow);
08. layoutLeftMenu = (LinearLayout) findViewById(R.id.layout_left_menu);
09. layoutRightMenu = (LinearLayout) findViewById(R.id.layout_right_menu);
10. imageViewBackground = (ImageView) findViewById(R.id.iv_background);
11. }
原理分析:从上面的代码可以看到,加载了一个residemenu的布局,先看布局

 

 

01. <?xml version="1.0" encoding="utf-8"?>
02.  
03. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
04. android:layout_width="match_parent"
05. android:layout_height="match_parent">
06. <ImageView
07. android:id="@+id/iv_background"
08. android:adjustViewBounds="true"
09. android:scaleType="centerCrop"
10. android:layout_width="match_parent"
11. android:layout_height="match_parent"/>
12.  
13. <ImageView
14. android:id="@+id/iv_shadow"
15. android:background="@drawable/shadow"
16. android:layout_width="fill_parent"
17. android:layout_height="fill_parent"
18. android:scaleType="fitXY"/>
19.  
20. <ScrollView
21. android:id="@+id/sv_left_menu"
22. android:scrollbars="none"
23. android:paddingLeft="30dp"
24. android:layout_width="150dp"
25. android:layout_height="fill_parent">
26. <LinearLayout
27. android:id="@+id/layout_left_menu"
28. android:orientation="vertical"
29. android:layout_gravity="center_vertical"
30. android:layout_width="wrap_content"
31. android:layout_height="wrap_content">
32.  
33. </LinearLayout>
34. </ScrollView>
35.  
36. <ScrollView
37. android:id="@+id/sv_right_menu"
38. android:scrollbars="none"
39. android:paddingRight="30dp"
40. android:layout_width="150dp"
41. android:layout_height="fill_parent"
42. android:layout_gravity="right">
43. <LinearLayout
44. android:id="@+id/layout_right_menu"
45. android:orientation="vertical"
46. android:layout_gravity="center_vertical"
47. android:layout_width="wrap_content"
48. android:layout_height="wrap_content"
49. android:gravity="right">
50.  
51. </LinearLayout>
52. </ScrollView>
53.  
54. </FrameLayout>
布局显示效果

 

\
从布局文件,以及显示效果我们可以看到,它是一个帧布局,第一个ImageView是背景,第二个ImageView是.9的阴影效果的图片(看下面的图),

两个(ScrollView包裹着一个LinerLayout),可以从上面图看到结构分别是左菜单和右菜单

 

1.初始化布局以及布局文件分析完毕,2.接下来是设置背景图,初始化view的时候就已经拿到了背景控件,所以设置背景图也是非常好实现的事情了。

 

1. public void setBackground(int imageResrouce){
2. imageViewBackground.setImageResource(imageResrouce);
3. }
3.绑定activity,思考问题:它做了什么?

 

 

 

01. /**
02. * use the method to set up the activity which residemenu need to show;
03. *
04. * @param activity
05. */
06. public void attachToActivity(Activity activity){
07. initValue(activity);
08. setShadowAdjustScaleXByOrientation();
09. viewDecor.addView(this0);
10. setViewPadding();
11. }
原理分析:绑定activity做了4件事情,分别是:

 

1.初始化参数:

 

01. private void initValue(Activity activity){
02. this.activity   = activity;
03. leftMenuItems   = new ArrayList<ResideMenuItem>();
04. rightMenuItems  = new ArrayList<ResideMenuItem>();
05. ignoredViews    = new ArrayList<View>();
06. viewDecor = (ViewGroup) activity.getWindow().getDecorView();
07. viewActivity = new TouchDisableView(this.activity);
08.  
09. View mContent   = viewDecor.getChildAt(0);
10. viewDecor.removeViewAt(0);
11. viewActivity.setContent(mContent);
12. addView(viewActivity);
13.  
14. ViewGroup parent = (ViewGroup) scrollViewLeftMenu.getParent();
15. parent.removeView(scrollViewLeftMenu);
16. parent.removeView(scrollViewRightMenu);
17. }

 

2.正对横竖屏缩放比例进行调整

 

01. private void setShadowAdjustScaleXByOrientation(){
02. int orientation = getResources().getConfiguration().orientation;
03. if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
04. shadowAdjustScaleX = 0.034f;
05. shadowAdjustScaleY = 0.12f;
06. else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
07. shadowAdjustScaleX = 0.06f;
08. shadowAdjustScaleY = 0.07f;
09. }
10. }

 

3.添加当前view

 

1. viewDecor.addView(this0);

 

4.设置view边距

 

01. /**
02. * we need the call the method before the menu show, because the
03. * padding of activity can't get at the moment of onCreateView();
04. */
05. private void setViewPadding(){
06. this.setPadding(viewActivity.getPaddingLeft(),
07. viewActivity.getPaddingTop(),
08. viewActivity.getPaddingRight(),
09. viewActivity.getPaddingBottom());
10. }
4.设置监听,思考问题:它什么时候调用监听,原理分析:动画监听开始执行动画掉哦那个openMenu动画结束调用closeMenu,从此我们可以想到,但它调用openMenu(int direction)和closeMenu()都会设置这个监听。

 

 

01. private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {
02. @Override
03. public void onAnimationStart(Animator animation) {
04. if (isOpened()){
05. showScrollViewMenu();
06. if (menuListener != null)
07. menuListener.openMenu();
08. }
09. }
10.  
11. @Override
12. public void onAnimationEnd(Animator animation) {
13. // reset the view;
14. if(isOpened()){
15. viewActivity.setTouchDisable(true);
16. viewActivity.setOnClickListener(viewActivityOnClickListener);
17. }else{
18. viewActivity.setTouchDisable(false);
19. viewActivity.setOnClickListener(null);
20. hideScrollViewMenu();
21. if (menuListener != null)
22. menuListener.closeMenu();
23. }
24. }
25.  
26. @Override
27. public void onAnimationCancel(Animator animation) {
28.  
29. }
30.  
31. @Override
32. public void onAnimationRepeat(Animator animation) {
33.  
34. }
35. };
5.设置内容缩放比例(0.1~1f),细心的同学会发现在当缩完成后还可以在往里面拉到更小,有种弹性的感觉,挺有趣的。但是有些人的需求不想要有这种弹性效果,我们可以通过修改源码修改这个弹性效果,找到getTargetScale这个方法,修改下面0.5这个数值。使用时设置了0.6的缩放比例,默认下面的弹性参数是0.5所以我们当缩完成后还可以在往里面拉0.1的比例。

 

 

01. private float getTargetScale(float currentRawX){
02. float scaleFloatX = ((currentRawX - lastRawX) / getScreenWidth()) * 0.75f;
03. scaleFloatX = scaleDirection == DIRECTION_RIGHT ? - scaleFloatX : scaleFloatX;
04.  
05. float targetScale = ViewHelper.getScaleX(viewActivity) - scaleFloatX;
06. targetScale = targetScale > 1.0f ? 1.0f : targetScale;
07. targetScale = targetScale < 0.5f ? 0.5f : targetScale;
08. return targetScale;
09. }

 

默认缩放比例:

 

1. //valid scale factor is between 0.0f and 1.0f.
2. private float mScaleValue = 0.5f;
1. AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
01. /**
02. * a helper method to build scale down animation;
03. *
04. * @param target
05. * @param targetScaleX
06. * @param targetScaleY
07. * @return
08. */
09. private AnimatorSet buildScaleDownAnimation(View target,float targetScaleX,float targetScaleY){
10.  
11. AnimatorSet scaleDown = new AnimatorSet();
12. scaleDown.playTogether(
13. ObjectAnimator.ofFloat(target, "scaleX", targetScaleX),
14. ObjectAnimator.ofFloat(target, "scaleY", targetScaleY)
15. );
16.  
17. scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity,
18. android.R.anim.decelerate_interpolator));
19. scaleDown.setDuration(250);
20. return scaleDown;
21. }
6.创建子菜单,看下子菜单的构造,我们通过上面的学习,原理分析:我们可以猜测到,无非就是加载布局设置内容

 

 

01. public ResideMenuItem(Context context, int icon, String title) {
02. super(context);
03. initViews(context);
04. iv_icon.setImageResource(icon);
05. tv_title.setText(title);
06. }
07.  
08. private void initViews(Context context){
09. LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
10. inflater.inflate(R.layout.residemenu_item, this);
11. iv_icon = (ImageView) findViewById(R.id.iv_icon);
12. tv_title = (TextView) findViewById(R.id.tv_title);
13. }
布局文件:

 

 

01. <?xml version="1.0" encoding="utf-8"?>
02.  
03. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
04. android:orientation="horizontal"
05. android:layout_width="match_parent"
06. android:layout_height="wrap_content"
07. android:gravity="center_vertical"
08. android:paddingTop="30dp">
09.  
10. <ImageView
11. android:layout_width="30dp"
12. android:layout_height="30dp"
13. android:scaleType="centerCrop"
14. android:id="@+id/iv_icon"/>
15.  
16. <TextView
17. android:layout_width="match_parent"
18. android:layout_height="wrap_content"
19. android:textColor="@android:color/white"
20. android:textSize="18sp"
21. android:layout_marginLeft="10dp"
22. android:id="@+id/tv_title"/>
23.  
24. </LinearLayout>

 

显示效果图:

\

7.子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)原理分析:根据不同的常量来区分添加不同菜单的子菜单

 

01. /**
02. * add a single items;
03. *
04. * @param menuItem
05. * @param direction
06. */
07. public void addMenuItem(ResideMenuItem menuItem, int direction){
08. if (direction == DIRECTION_LEFT){
09. this.leftMenuItems.add(menuItem);
10. layoutLeftMenu.addView(menuItem);
11. }else{
12. this.rightMenuItems.add(menuItem);
13. layoutRightMenu.addView(menuItem);
14. }
15. }
8.设置title按钮的点击事件,设置左右菜单的开关,原理分析:先设置了缩放方向然后在设置动画,正如我们上面想的一样还设置了动画监听。

 

 

01. /**
02. * show the reside menu;
03. */
04. public void openMenu(int direction){
05.  
06. setScaleDirection(direction);
07.  
08. isOpened = true;
09. AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
10. AnimatorSet scaleDown_shadow = buildScaleDownAnimation(imageViewShadow,
11. mScaleValue + shadowAdjustScaleX, mScaleValue + shadowAdjustScaleY);
12. AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 1.0f);
13. scaleDown_shadow.addListener(animationListener);
14. scaleDown_activity.playTogether(scaleDown_shadow);
15. scaleDown_activity.playTogether(alpha_menu);
16. scaleDown_activity.start();
17. }
设置缩放方向及计算x,y轴位置。

 

 

01. private void setScaleDirection(int direction){
02.  
03. int screenWidth = getScreenWidth();
04. float pivotX;
05. float pivotY = getScreenHeight() * 0.5f;
06.  
07. if (direction == DIRECTION_LEFT){
08. scrollViewMenu = scrollViewLeftMenu;
09. pivotX  = screenWidth * 1.5f;
10. }else{
11. scrollViewMenu = scrollViewRightMenu;
12. pivotX  = screenWidth * -0.5f;
13. }
14.  
15. ViewHelper.setPivotX(viewActivity, pivotX);
16. ViewHelper.setPivotY(viewActivity, pivotY);
17. ViewHelper.setPivotX(imageViewShadow, pivotX);
18. ViewHelper.setPivotY(imageViewShadow, pivotY);
19. scaleDirection = direction;
20. }
9.重写dispatchTouchEvent,问题思考:如何到根据手指滑动自动缩放

 

如果还不了解,dispatchTouchEvent这个函数如何调用?什么时候调用?请先看看http://blog.csdn.net/cym492224103/article/details/39179311

 

01. @Override
02. public boolean dispatchTouchEvent(MotionEvent ev) {
03. float currentActivityScaleX = ViewHelper.getScaleX(viewActivity);
04. if (currentActivityScaleX == 1.0f)
05. setScaleDirectionByRawX(ev.getRawX());
06.  
07. switch (ev.getAction()){
08. case MotionEvent.ACTION_DOWN:
09. lastActionDownX = ev.getX();
10. lastActionDownY = ev.getY();
11. isInIgnoredView = isInIgnoredView(ev) && !isOpened();
12. pressedState    = PRESSED_DOWN;
13. break;
14.  
15. case MotionEvent.ACTION_MOVE:
16. if (isInIgnoredView || isInDisableDirection(scaleDirection))
17. break;
18.  
19. if(pressedState != PRESSED_DOWN &&
20. pressedState != PRESSED_MOVE_HORIZANTAL)
21. break;
22.  
23. int xOffset = (int) (ev.getX() - lastActionDownX);
24. int yOffset = (int) (ev.getY() - lastActionDownY);
25.  
26. if(pressedState == PRESSED_DOWN) {
27. if(yOffset > 25 || yOffset < -25) {
28. pressedState = PRESSED_MOVE_VERTICAL;
29. break;
30. }
31. if(xOffset < -50 || xOffset > 50) {
32. pressedState = PRESSED_MOVE_HORIZANTAL;
33. ev.setAction(MotionEvent.ACTION_CANCEL);
34. }
35. else if(pressedState == PRESSED_MOVE_HORIZANTAL) {
36. if (currentActivityScaleX < 0.95)
37. showScrollViewMenu();
38.  
39. float targetScale = getTargetScale(ev.getRawX());
40. ViewHelper.setScaleX(viewActivity, targetScale);
41. ViewHelper.setScaleY(viewActivity, targetScale);
42. ViewHelper.setScaleX(imageViewShadow, targetScale + shadowAdjustScaleX);
43. ViewHelper.setScaleY(imageViewShadow, targetScale + shadowAdjustScaleY);
44. ViewHelper.setAlpha(scrollViewMenu, (1 - targetScale) * 2.0f);
45.  
46. lastRawX = ev.getRawX();
47. return true;
48. }
49.  
50. break;
51.  
52. case MotionEvent.ACTION_UP:
53.  
54. if (isInIgnoredView) break;
55. if (pressedState != PRESSED_MOVE_HORIZANTAL) break;
56.  
57. pressedState = PRESSED_DONE;
58. if (isOpened()){
59. if (currentActivityScaleX > 0.56f)
60. closeMenu();
61. else
62. openMenu(scaleDirection);
63. }else{
64. if (currentActivityScaleX < 0.94f){
65. openMenu(scaleDirection);
66. }else{
67. closeMenu();
68. }
69. }
70.  
71. break;
72.  
73. }
74. lastRawX = ev.getRawX();
75. return super.dispatchTouchEvent(ev);
76. }
上面代码量有点多,看上去有点晕,接下来我们来分别从按下、移动、放开、来原理分析:

 

 

MotionEvent.ACTION_DOWN:

记录了X,Y轴的坐标点,判断是否打开,设置了按下的状态为PRESSED_DOWN

 

MotionEvent.ACTION_MOVE:

拿到当前X,Y减去DOWN下记录下来的X,Y,这样得到了移动的X,Y,

然后判断如果如果移动的X,Y大于25或者小于-25就改变按下状态为PRESSED_MOVE_VERTICAL

如果移动的X,Y大于50或者小于-50就改变状态为PRESSED_MOVE_HORIZANTAL

状态为PRESSED_MOVE_HORIZANTAL就改变菜单主视图内容以及阴影图片大小,在改变的同时还设置了当前菜单的透明度。

 

MotionEvent.ACTION_UP:

判断是否菜单是否打开状态,在获取当前缩放的X比例,

判断比例小于0.56f,则关闭菜单,反正开启菜单。

看完后,我们在回去看看代码,就会发现其实也不过如此~!

10.菜单关闭方法,同样也设置了动画监听之前的想法也是成立的。

 

01. /**
02. * close the reslide menu;
03. */
04. public void closeMenu(){
05.  
06. isOpened = false;
07. AnimatorSet scaleUp_activity = buildScaleUpAnimation(viewActivity, 1.0f, 1.0f);
08. AnimatorSet scaleUp_shadow = buildScaleUpAnimation(imageViewShadow, 1.0f, 1.0f);
09. AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 0.0f);
10. scaleUp_activity.addListener(animationListener);
11. scaleUp_activity.playTogether(scaleUp_shadow);
12. scaleUp_activity.playTogether(alpha_menu);
13. scaleUp_activity.start();
14. }
11.屏蔽菜单方法

 

 

1. public void setSwipeDirectionDisable(int direction){
2. disabledSwipeDirection.add(direction);
3. }
1. private boolean isInDisableDirection(int direction){
2. return disabledSwipeDirection.contains(direction);
3. }
原理分析:在重写dispatchTouchEvent的时候,细心的同学应该会看到,ACTION_MOVE下面有个判断

 

 

1. if (isInIgnoredView || isInDisableDirection(scaleDirection))
如果这个方向的菜单被屏蔽了,就滑不出来了。

 

最后我们会发现我们一直都没说到TouchDisableView,其实initValue的时候就初始化了,它就是viewActivity,是我们的内容视图。

\

我们来看看它做了什么?

 

01. @Override
02. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
03.  
04. int width = getDefaultSize(0, widthMeasureSpec);
05. int height = getDefaultSize(0, heightMeasureSpec);
06. setMeasuredDimension(width, height);
07.  
08. final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
09. final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
10. mContent.measure(contentWidth, contentHeight);
11. }
12.  
13. @Override
14. protected void onLayout(boolean changed, int l, int t, int r, int b) {
15. final int width = r - l;
16. final int height = b - t;
17. mContent.layout(00, width, height);
18. }
19.  
20. @Override
21. public boolean onInterceptTouchEvent(MotionEvent ev) {
22. return mTouchDisabled;
23. }
24.  
25. void setTouchDisable(boolean disableTouch) {
26. mTouchDisabled = disableTouch;
27. }
28.  
29. boolean isTouchDisabled() {
30. return mTouchDisabled;
31. }原文:http://www.it165.net/pro/html/201409/21764.html
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值