首页 > 代码库 > Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

1、原理分析

首先对比一下我们上篇的实现距离QQ的效果还有多远:

差距还是蛮大的

区别1、QQ的内容区域会伴随菜单的出现而缩小

区别2、QQ的侧滑菜单给人的感觉是隐藏在内容的后面,而不是拖出来的感觉

区别3、QQ的侧滑菜单有一个缩放以及透明度的效果~

 

那么我们如何能做到呢:

对于区别1:这个好办,我们可以在滑动的时候,不断的改变内容区域的大小;如何改变呢?我们在菜单出现的整个过程中,不断记录菜单显示的宽度与其总宽度的比值,是个从0到1的过程,然后把0~1转化为1~0.7(假设内容区域缩小至0.7);不断去缩小内容区域;

对于区别3:也比较好办,上面已经可以得到0到1的这个值了,那么缩放和透明度的动画就不在话下了;

对 于区别2:我们使用的HorizontalScrollView,然后水平放置了菜单和内容,如何让菜单可以隐藏到内容的后面呢?其实也比较简单,在菜单 出现的过程中,不断设置菜单的x方向的偏移量;0的时候完全隐藏,0.3的时候,隐藏x方向偏移量为0.7个宽度,类推~~~

 

好了,分析完毕,那么对于这些动画用什么实现最好呢?

想都不用想,属性动画哈,如果你对属性动画不了解,可以参:Android 属性动画(Property Animation) 完全解析 (上)和Android 属性动画(Property Animation) 完全解析 (下)

2、实现

 

1、初步的代码

布局文件神马的,都和上一篇一模一样,这里就不重复贴代码了,不了解的,先看下上一篇;

先看一下上一篇我们已经实现的完整代码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package com.example.zhy_slidingmenu;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.util.AttributeSet;  
  6. import android.util.TypedValue;  
  7. import android.view.MotionEvent;  
  8. import android.view.ViewGroup;  
  9. import android.widget.HorizontalScrollView;  
  10. import android.widget.LinearLayout;  
  11.   
  12. import com.zhy.utils.ScreenUtils;  
  13.   
  14. public class SlidingMenu extends HorizontalScrollView  
  15. {  
  16.     /** 
  17.      * 屏幕宽度 
  18.      */  
  19.     private int mScreenWidth;  
  20.     /** 
  21.      * dp 
  22.      */  
  23.     private int mMenuRightPadding;  
  24.     /** 
  25.      * 菜单的宽度 
  26.      */  
  27.     private int mMenuWidth;  
  28.     private int mHalfMenuWidth;  
  29.   
  30.     private boolean isOpen;  
  31.   
  32.     private boolean once;  
  33.   
  34.     public SlidingMenu(Context context, AttributeSet attrs)  
  35.     {  
  36.         this(context, attrs, 0);  
  37.   
  38.     }  
  39.   
  40.     public SlidingMenu(Context context, AttributeSet attrs, int defStyle)  
  41.     {  
  42.         super(context, attrs, defStyle);  
  43.         mScreenWidth = ScreenUtils.getScreenWidth(context);  
  44.   
  45.         TypedArray a = context.getTheme().obtainStyledAttributes(attrs,  
  46.                 R.styleable.SlidingMenu, defStyle, 0);  
  47.         int n = a.getIndexCount();  
  48.         for (int i = 0; i < n; i++)  
  49.         {  
  50.             int attr = a.getIndex(i);  
  51.             switch (attr)  
  52.             {  
  53.             case R.styleable.SlidingMenu_rightPadding:  
  54.                 // 默认50  
  55.                 mMenuRightPadding = a.getDimensionPixelSize(attr,  
  56.                         (int) TypedValue.applyDimension(  
  57.                                 TypedValue.COMPLEX_UNIT_DIP, 50f,  
  58.                                 getResources().getDisplayMetrics()));// 默认为10DP  
  59.                 break;  
  60.             }  
  61.         }  
  62.         a.recycle();  
  63.     }  
  64.   
  65.     public SlidingMenu(Context context)  
  66.     {  
  67.         this(context, null, 0);  
  68.     }  
  69.   
  70.     @Override  
  71.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  72.     {  
  73.         /** 
  74.          * 显示的设置一个宽度 
  75.          */  
  76.         if (!once)  
  77.         {  
  78.             LinearLayout wrapper = (LinearLayout) getChildAt(0);  
  79.             ViewGroup menu = (ViewGroup) wrapper.getChildAt(0);  
  80.             ViewGroup content = (ViewGroup) wrapper.getChildAt(1);  
  81.   
  82.             mMenuWidth = mScreenWidth - mMenuRightPadding;  
  83.             mHalfMenuWidth = mMenuWidth / 2;  
  84.             menu.getLayoutParams().width = mMenuWidth;  
  85.             content.getLayoutParams().width = mScreenWidth;  
  86.   
  87.         }  
  88.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  89.   
  90.     }  
  91.   
  92.     @Override  
  93.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  94.     {  
  95.         super.onLayout(changed, l, t, r, b);  
  96.         if (changed)  
  97.         {  
  98.             // 将菜单隐藏  
  99.             this.scrollTo(mMenuWidth, 0);  
  100.             once = true;  
  101.         }  
  102.     }  
  103.   
  104.     @Override  
  105.     public boolean onTouchEvent(MotionEvent ev)  
  106.     {  
  107.         int action = ev.getAction();  
  108.         switch (action)  
  109.         {  
  110.         // Up时,进行判断,如果显示区域大于菜单宽度一半则完全显示,否则隐藏  
  111.         case MotionEvent.ACTION_UP:  
  112.             int scrollX = getScrollX();  
  113.             if (scrollX > mHalfMenuWidth)  
  114.             {  
  115.                 this.smoothScrollTo(mMenuWidth, 0);  
  116.                 isOpen = false;  
  117.             } else  
  118.             {  
  119.                 this.smoothScrollTo(0, 0);  
  120.                 isOpen = true;  
  121.             }  
  122.             return true;  
  123.         }  
  124.         return super.onTouchEvent(ev);  
  125.     }  
  126.   
  127.     /** 
  128.      * 打开菜单 
  129.      */  
  130.     public void openMenu()  
  131.     {  
  132.         if (isOpen)  
  133.             return;  
  134.         this.smoothScrollTo(0, 0);  
  135.         isOpen = true;  
  136.     }  
  137.   
  138.     /** 
  139.      * 关闭菜单 
  140.      */  
  141.     public void closeMenu()  
  142.     {  
  143.         if (isOpen)  
  144.         {  
  145.             this.smoothScrollTo(mMenuWidth, 0);  
  146.             isOpen = false;  
  147.         }  
  148.     }  
  149.   
  150.     /** 
  151.      * 切换菜单状态 
  152.      */  
  153.     public void toggle()  
  154.     {  
  155.         if (isOpen)  
  156.         {  
  157.             closeMenu();  
  158.         } else  
  159.         {  
  160.             openMenu();  
  161.         }  
  162.     }  
  163.       
  164.       
  165.   
  166. }  


利用HorizontalScrollView,监听了ACTION_UP的事件,当用户抬起手指时,根据当前菜单显示的宽度值,判断是缩回还是完全展开;给用户提供了一个rightPadding属性,用于设置菜单离右屏幕的距离;以及对外提供了打开,关闭,切换的几个方法;具体的讲解看下上篇博客了;

2、实现的思路

现在我们开始解决那3个区别,已经选择了使用属性动画,现在决定动画效果应该加在哪儿?

不用说,我用大腿想一想都应该是在ACTION_MOVE中,是的,ACTION_MOVE中的确可以,不断获取当前的getScrollX / mMenuWidth,不断改变菜单的透明度,缩放,X方向的偏移量;不断改变内容区域的宽度和高度;

说一下,起初我也是在MOVE中这么做的,但是呢,出现两个问题:

1、动画效果并不是很流畅,特别是菜单,有抖动的效果;

2、用户抬起后,还需要在UP里面,继续未完成的动画,就是说,你的透明度、缩放神马的,当用户抬起以后就需要自动变化了;

于是乎,我就开始换了个方向,既然是SrollView,肯定有一个ScrollChanged方法,功夫不负有心人,真心这么个方法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onScrollChanged(int l, int t, int oldl, int oldt)  
  3.     {  
  4.           
  5.     }  

这个方法只要scrollChanged就会触发,l就是我们需要的scrollX,太赞了~~~

3、动画比例的计算

我们在onScrollChanged里面,拿到 l 也就是个getScrollX,即菜单已经显示的宽度值;

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. float scale = l * 1.0f / mMenuWidth;  


与菜单的宽度做除法运算,在菜单隐藏到显示整个过程,会得到1.0~0.0这么个变化的区间;

有了这个区间,就可以根据这个区间设置动画了;

1、首先是内容区域的缩放比例计算:

我们准备让在菜单出现的过程中,让内容区域从1.0~0.8进行变化~~

那么怎么把1.0~0.0转化为1.0~0.8呢,其实很简单了:

float rightScale = 0.8f + scale * 0.2f; (scale 从1到0 ),是不是哦了~

接下来还有3个动画:

2、菜单的缩放比例计算

仔细观察了下QQ,菜单大概缩放变化是0.7~1.0

float leftScale = 1 - 0.3f * scale;

3、菜单的透明度比例:

我们设置为0.6~1.0;即:0.6f + 0.4f * (1 - scale)

4、菜单的x方向偏移量:

看一下QQ,并非完全从被内容区域覆盖,还是有一点拖出的感觉,所以我们的偏移量这么设置:

tranlateX = mMenuWidth * scale * 0.6f ;刚开始还是让它隐藏一点点~~~

4、完整的实现

说了这么多,其实到上一篇史上最简单的侧滑,到QQ5.0的效果的转变,只需要几行代码~~

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onScrollChanged(int l, int t, int oldl, int oldt)  
  3.     {  
  4.         super.onScrollChanged(l, t, oldl, oldt);  
  5.         float scale = l * 1.0f / mMenuWidth;  
  6.         float leftScale = 1 - 0.3f * scale;  
  7.         float rightScale = 0.8f + scale * 0.2f;  
  8.           
  9.         ViewHelper.setScaleX(mMenu, leftScale);  
  10.         ViewHelper.setScaleY(mMenu, leftScale);  
  11.         ViewHelper.setAlpha(mMenu, 0.6f + 0.4f * (1 - scale));  
  12.         ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.6f);  
  13.   
  14.         ViewHelper.setPivotX(mContent, 0);  
  15.         ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);  
  16.         ViewHelper.setScaleX(mContent, rightScale);  
  17.         ViewHelper.setScaleY(mContent, rightScale);  
  18.   
  19.     }  


就这么几行。这里属性动画用的nineoldandroids为了保持向下的兼容;主要就是设置了各种动画,上面都详细说了~~~
然后,记得把我们的菜单和内容的布局,单独声明出来为我们的mMenu ,mContent ~没了,就改了这么几行~

3、效果图

是骡子是马,拉出来溜溜

菜单栏需要ListView的拖动也是不会冲突了,上篇已经测试了;

关于动画属性的范围:上面介绍的特别清楚,比如内容我们是最小显示0.8,你要是喜欢0.6,自己去修改一下;包括偏移量,透明度等范围;

因为上一篇已经写了如何把属性抽取成自定义的属性;所以这里就没有抽取了,不然总觉得是在重复~

 

嗯,最近还有写APP的侧滑,是这样的,就是菜单栏完全隐藏在内容区域下面,如果需要这样需求的:

其实我还满喜欢这样效果的。

实现呢,注释几行代码就实现了:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onScrollChanged(int l, int t, int oldl, int oldt)  
  3.     {  
  4.         super.onScrollChanged(l, t, oldl, oldt);  
  5.         float scale = l * 1.0f / mMenuWidth;  
  6. //      float leftScale = 1 - 0.3f * scale;  
  7. //      float rightScale = 0.8f + scale * 0.2f;  
  8. //        
  9. //      ViewHelper.setScaleX(mMenu, leftScale);  
  10. //      ViewHelper.setScaleY(mMenu, leftScale);  
  11. //      ViewHelper.setAlpha(mMenu, 0.6f + 0.4f * (1 - scale));  
  12.         ViewHelper.setTranslationX(mMenu, mMenuWidth * scale );  
  13.   
  14. //      ViewHelper.setPivotX(mContent, 0);  
  15. //      ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);  
  16. //      ViewHelper.setScaleX(mContent, rightScale);  
  17. //      ViewHelper.setScaleY(mContent, rightScale);  
  18.   

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭