转载出处:http://blog.csdn.net/lmj623565791/article/details/39257409

上一篇博客带大家实现了:Android 自定义控件打造史上最简单的侧滑菜单 ,有兄弟看了以后说,你这滑动菜单过时了呀~QQ5.0的效果还不错嗯,的确,上一篇也承诺过,稍微修改上一篇的代码,实现QQ5.0侧滑菜单好了,下面就开始为大家展示写一个类QQ的侧滑有多easy ~!

1、原理分析

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

差距还是蛮大的

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

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

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

那么我们如何能做到呢:

对于区别1:这个好办,我们可以在滑动的时候,不断的改变内容区域的大小;如何改变呢?我们在菜单出现的整个过程中,不断记录菜单显示的宽度与其总宽度的比值,是个从0到1的过程,然后把01转化为10.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 plain](http://blog.csdn.net/lmj623565791/article/details/39257409#)[copy](http://blog.csdn.net/lmj623565791/article/details/39257409#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/468874)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/468874/fork)
  <div>
    <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.zhy_slidingmenu;

- 
- <span class="keyword">import</span> android.content.Context;

- <span class="keyword">import</span> android.content.res.TypedArray;

- <span class="keyword">import</span> android.util.AttributeSet;

- <span class="keyword">import</span> android.util.TypedValue;

- <span class="keyword">import</span> android.view.MotionEvent;

- <span class="keyword">import</span> android.view.ViewGroup;

- <span class="keyword">import</span> android.widget.HorizontalScrollView;

- <span class="keyword">import</span> android.widget.LinearLayout;

- 
- <span class="keyword">import</span> com.zhy.utils.ScreenUtils;

- 
- <span class="keyword">public</span> <span class="keyword">class</span> SlidingMenu <span class="keyword">extends</span> HorizontalScrollView

- {

- <span class="comment">/**</span>

- <span class="comment">     * 屏幕宽度</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">int</span> mScreenWidth;

- <span class="comment">/**</span>

- <span class="comment">     * dp</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">int</span> mMenuRightPadding;

- <span class="comment">/**</span>

- <span class="comment">     * 菜单的宽度</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">int</span> mMenuWidth;

- <span class="keyword">private</span> <span class="keyword">int</span> mHalfMenuWidth;

- 
- <span class="keyword">private</span> <span class="keyword">boolean</span> isOpen;

- 
- <span class="keyword">private</span> <span class="keyword">boolean</span> once;

- 
- <span class="keyword">public</span> SlidingMenu(Context context, AttributeSet attrs)

- {

- <span class="keyword">this</span>(context, attrs, <span class="number"></span>);

- 
- }

- 
- <span class="keyword">public</span> SlidingMenu(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle)

- {

- <span class="keyword">super</span>(context, attrs, defStyle);

- mScreenWidth = ScreenUtils.getScreenWidth(context);

- 
- TypedArray a = context.getTheme().obtainStyledAttributes(attrs,

- R.styleable.SlidingMenu, defStyle, <span class="number"></span>);

- <span class="keyword">int</span> n = a.getIndexCount();

- <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < n; i++)

- {

- <span class="keyword">int</span> attr = a.getIndex(i);

- <span class="keyword">switch</span> (attr)

- {

- <span class="keyword">case</span> R.styleable.SlidingMenu_rightPadding:

- <span class="comment">// 默认50</span>

- mMenuRightPadding = a.getDimensionPixelSize(attr,

- (<span class="keyword">int</span>) TypedValue.applyDimension(

- TypedValue.COMPLEX_UNIT_DIP, 50f,

- getResources().getDisplayMetrics()));<span class="comment">// 默认为10DP</span>

- <span class="keyword">break</span>;

- }

- }

- a.recycle();

- }

- 
- <span class="keyword">public</span> SlidingMenu(Context context)

- {

- <span class="keyword">this</span>(context, <span class="keyword">null</span>, <span class="number"></span>);

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec)

- {

- <span class="comment">/**</span>

- <span class="comment">         * 显示的设置一个宽度</span>

- <span class="comment">         */</span>

- <span class="keyword">if</span> (!once)

- {

- LinearLayout wrapper = (LinearLayout) getChildAt(<span class="number"></span>);

- ViewGroup menu = (ViewGroup) wrapper.getChildAt(<span class="number"></span>);

- ViewGroup content = (ViewGroup) wrapper.getChildAt(<span class="number">1</span>);

- 
- mMenuWidth = mScreenWidth &#8211; mMenuRightPadding;

- mHalfMenuWidth = mMenuWidth / <span class="number">2</span>;

- menu.getLayoutParams().width = mMenuWidth;

- content.getLayoutParams().width = mScreenWidth;

- 
- }

- <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec);

- 
- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> r, <span class="keyword">int</span> b)

- {

- <span class="keyword">super</span>.onLayout(changed, l, t, r, b);

- <span class="keyword">if</span> (changed)

- {

- <span class="comment">// 将菜单隐藏</span>

- <span class="keyword">this</span>.scrollTo(mMenuWidth, <span class="number"></span>);

- once = <span class="keyword">true</span>;

- }

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">boolean</span> onTouchEvent(MotionEvent ev)

- {

- <span class="keyword">int</span> action = ev.getAction();

- <span class="keyword">switch</span> (action)

- {

- <span class="comment">// Up时,进行判断,如果显示区域大于菜单宽度一半则完全显示,否则隐藏</span>

- <span class="keyword">case</span> MotionEvent.ACTION_UP:

- <span class="keyword">int</span> scrollX = getScrollX();

- <span class="keyword">if</span> (scrollX > mHalfMenuWidth)

- {

- <span class="keyword">this</span>.smoothScrollTo(mMenuWidth, <span class="number"></span>);

- isOpen = <span class="keyword">false</span>;

- } <span class="keyword">else</span>

- {

- <span class="keyword">this</span>.smoothScrollTo(<span class="number"></span>, <span class="number"></span>);

- isOpen = <span class="keyword">true</span>;

- }

- <span class="keyword">return</span> <span class="keyword">true</span>;

- }

- <span class="keyword">return</span> <span class="keyword">super</span>.onTouchEvent(ev);

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 打开菜单</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> openMenu()

- {

- <span class="keyword">if</span> (isOpen)

- <span class="keyword">return</span>;

- <span class="keyword">this</span>.smoothScrollTo(<span class="number"></span>, <span class="number"></span>);

- isOpen = <span class="keyword">true</span>;

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 关闭菜单</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> closeMenu()

- {

- <span class="keyword">if</span> (isOpen)

- {

- <span class="keyword">this</span>.smoothScrollTo(mMenuWidth, <span class="number"></span>);

- isOpen = <span class="keyword">false</span>;

- }

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 切换菜单状态</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> toggle()

- {

- <span class="keyword">if</span> (isOpen)

- {

- closeMenu();

- } <span class="keyword">else</span>

- {

- openMenu();

- }

- }

- 
- 
- 
- }

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

2、实现的思路

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

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

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

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

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

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

**[java]** [view plain](http://blog.csdn.net/lmj623565791/article/details/39257409#)[copy](http://blog.csdn.net/lmj623565791/article/details/39257409#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/468874)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/468874/fork)
  <div>
    <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2">
    </embed>
  </div>
</div>
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onScrollChanged(<span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> oldl, <span class="keyword">int</span> oldt)

- {

- 
- }

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

3、动画比例的计算

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

**[html]** [view plain](http://blog.csdn.net/lmj623565791/article/details/39257409#)[copy](http://blog.csdn.net/lmj623565791/article/details/39257409#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/468874)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/468874/fork)
  <div>
    <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3">
    </embed>
  </div>
</div>
- float <span class="attribute">scale</span> = <span class="attribute-value">l</span> * 1.0f / mMenuWidth;

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

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

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

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

那么怎么把1.00.0转化为1.00.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 plain](http://blog.csdn.net/lmj623565791/article/details/39257409#)[copy](http://blog.csdn.net/lmj623565791/article/details/39257409#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/468874)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/468874/fork)
  <div>
    <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4">
    </embed>
  </div>
</div>
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onScrollChanged(<span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> oldl, <span class="keyword">int</span> oldt)

- {

- <span class="keyword">super</span>.onScrollChanged(l, t, oldl, oldt);

- <span class="keyword">float</span> scale = l * <span class="number">1</span>.0f / mMenuWidth;

- <span class="keyword">float</span> leftScale = <span class="number">1</span> &#8211; <span class="number"></span>.3f * scale;

- <span class="keyword">float</span> rightScale = <span class="number"></span>.8f + scale * <span class="number"></span>.2f;

- 
- ViewHelper.setScaleX(mMenu, leftScale);

- ViewHelper.setScaleY(mMenu, leftScale);

- ViewHelper.setAlpha(mMenu, <span class="number"></span>.6f + <span class="number"></span>.4f * (<span class="number">1</span> &#8211; scale));

- ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * <span class="number"></span>.6f);

- 
- ViewHelper.setPivotX(mContent, <span class="number"></span>);

- ViewHelper.setPivotY(mContent, mContent.getHeight() / <span class="number">2</span>);

- ViewHelper.setScaleX(mContent, rightScale);

- ViewHelper.setScaleY(mContent, rightScale);

- 
- }

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

3、效果图

是骡子是马,拉出来溜溜

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

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

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

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

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

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

**[java]** [view plain](http://blog.csdn.net/lmj623565791/article/details/39257409#)[copy](http://blog.csdn.net/lmj623565791/article/details/39257409#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/468874)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/468874/fork)
  <div>
    <embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_5">
    </embed>
  </div>
</div>
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onScrollChanged(<span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> oldl, <span class="keyword">int</span> oldt)

- {

- <span class="keyword">super</span>.onScrollChanged(l, t, oldl, oldt);

- <span class="keyword">float</span> scale = l * <span class="number">1</span>.0f / mMenuWidth;

- <span class="comment">//      float leftScale = 1 &#8211; 0.3f * scale;</span>

- <span class="comment">//      float rightScale = 0.8f + scale * 0.2f;</span>

- <span class="comment">//      </span>

- <span class="comment">//      ViewHelper.setScaleX(mMenu, leftScale);</span>

- <span class="comment">//      ViewHelper.setScaleY(mMenu, leftScale);</span>

- <span class="comment">//      ViewHelper.setAlpha(mMenu, 0.6f + 0.4f * (1 &#8211; scale));</span>

- ViewHelper.setTranslationX(mMenu, mMenuWidth * scale );

- 
- <span class="comment">//      ViewHelper.setPivotX(mContent, 0);</span>

- <span class="comment">//      ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);</span>

- <span class="comment">//      ViewHelper.setScaleX(mContent, rightScale);</span>

- <span class="comment">//      ViewHelper.setScaleY(mContent, rightScale);</span>

- 
- }

 

好了,虽说最终的实现看起来还是很简单的,看起来,嗯但是从无到有的这个过程还是不容易的各种尝试,我能说我连蹲坑都在滑QQ的菜单观察么哈,见笑了;博客中也写出了过程中失败的尝试,希望能够更好的让大家在里面学到些有用的东西~~YEAH!! 就到这,没事就留个言~~~再不留言,我就来个源码请留下邮箱,嘿嘿,开个玩笑

源码点击下载

最后,我建了一个QQ群,方便大家交流。群号:55032675

—————————————————————————————————————————————

本博客内容视频版,已经上线,如果你不喜欢枯燥的文本,请猛戳:

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0侧滑

3、Android智能机器人“小慕”的实现

4、Android自定义控件 打造Android流式布局和热门标签

 

💬 评论