最近真的是忙死了,做了很久的这个菜单动画特效,都没有时间写博客,今天在机场等飞机终于有了空闲时间。
上图先:
那么下面开始吧~
首先,将整个菜单动画分解开来。
1. 一级菜单按钮的旋转动画2个,十字和叉叉状态的转换。
2. 二级菜单按钮的平移动画2个,弹簧效果的in和out。
3. 二级菜单按钮的点击效果,放大消失,其他未点击按钮缩小消失。
4. 一级菜单按钮的恢复效果,放大出现。
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <rotate |
3 | xmlns:android="http://schemas.android.com/apk/res/android" |
4 | android:interpolator="@android:anim/linear_interpolator" |
5 | android:duration="150" |
6 | android:fromDegrees="0.0" |
7 | android:toDegrees="-225.0" |
8 | android:pivotX="50.0%" |
9 | android:pivotY="50.0%" |
10 | android:fillAfter="true" |
11 | android:fillEnabled="true" |
12 | /> |
rotate_story_add_button_out.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <rotate |
3 | xmlns:android="http://schemas.android.com/apk/res/android" |
4 | android:interpolator="@android:anim/linear_interpolator" |
5 | android:duration="150" |
6 | android:fromDegrees="-225.0" |
7 | android:toDegrees="0.0" |
8 | android:pivotX="50.0%" |
9 | android:pivotY="50.0%" |
10 | android:fillAfter="true" |
11 | android:fillEnabled="true" |
12 | /> |
这2段没什么好说的,定义好角度即可。
接下来是需要我们在代码中定义的动画部分,这几个动画的部分需要定义一个基类,作为统一的调用接口,这个基类被称作InOutAnimation,继承自AnimationSet,这个基类的主要工作是为view提供in和out两种不同的状态时的动画效果。其子类需要实现2个方法:
好的 逐一去实现:
首先是一级菜单按钮的旋转动画,这2个动画可以直接在xml中定义,然后load到代码中来,具体代码如下:
rotate_story_add_button_in.xml
1 | protected abstract void addInAnimation(View aview[]); |
2 | protected abstract void addOutAnimation(View aview[]); |
从而进行view的入场和离场动画。
下面是InOutAnimation的代码部分:
1 | public abstract class InOutAnimation extends AnimationSet { |
2 | |
3 | public Direction direction; |
4 | |
5 | public enum Direction { |
6 | IN, OUT; |
7 | } |
8 | |
9 | public InOutAnimation(Direction direction, long l, View[] aview) { |
10 | super(true); |
11 | this.direction = direction; |
12 | switch (this.direction) { |
13 | case IN: |
14 | addInAnimation(aview); |
15 | break; |
16 | case OUT: |
17 | addOutAnimation(aview); |
18 | |
19 | break; |
20 | } |
21 | setDuration(l); |
22 | } |
23 | |
24 | protected abstract void addInAnimation(View aview[]); |
25 | |
26 | protected abstract void addOutAnimation(View aview[]); |
27 | |
28 | } |
接下来就是重头戏啦,二级菜单按钮的平移动画。
这部分动画看起来可能会比较复杂和神秘,其实不然,当把整个动画过程分解开来的时候,都是最最简单的平移而已,我们要做的只是定义一下平移的起点和终点、开始动画的顺序以及插值(Interpolator),让整个过程看起来很炫。
先说动画的起点和终点吧,起点很简单,就是整个view的左下角,即0,0点,为了效果漂亮一些,我们稍微的将左下角位置定义的有一些偏移,经验上的值是16,-13,这个点的位置看你心情而定咯~ 好 终点就是你想让他在的点上就好了,终点我们将定义到layout中去,为这个2级菜单指定一个margin的值就好。
还是上代码比较直观:
动画如下:
1 | 收缩部分:TranslateAnimation(xOffset + -mlp.leftMargin, 0F,yOffset + mlp.bottomMargin, 0F) |
2 | 扩张部分:TranslateAnimation(0F, xOffset + -mlp.leftMargin, 0F,yOffset + mlp.bottomMargin) |
位置定义部分:
例如:
android:layout_marginBottom="142dp" android:layout_marginLeft="10.667dp"
这个位置大家可以直观的在布局文件中看到,详细的布局文件也将在下面展示。
以上是单独的每一个二级按钮的动画,而组合的动画就是指定了一下开始的时间差以及插值:
这个就是奥妙所在了,OvershootInterpolator AnticipateInterpolator 这2个插值器提供了弹力效果。
整段的代码如下:
1 | public class ComposerButtonAnimation extends InOutAnimation { |
2 | |
3 | public static final int DURATION = 500; |
4 | private static final int xOffset = 16; |
5 | private static final int yOffset = -13; |
6 | |
7 | public ComposerButtonAnimation(Direction direction, long l, View view) { |
8 | super(direction, l, new View[] { view }); |
9 | } |
10 | |
11 | public static void startAnimations(ViewGroup viewgroup, |
12 | InOutAnimation.Direction direction) { |
13 | switch (direction) { |
14 | case IN: |
15 | startAnimationsIn(viewgroup); |
16 | break; |
17 | case OUT: |
18 | startAnimationsOut(viewgroup); |
19 | break; |
20 | } |
21 | } |
22 | |
23 | private static void startAnimationsIn(ViewGroup viewgroup) { |
24 | for (int i = 0; i < viewgroup.getChildCount(); i++) { |
25 | if (viewgroup.getChildAt(i) instanceof InOutImageButton) { |
26 | InOutImageButton inoutimagebutton = (InOutImageButton) viewgroup |
27 | .getChildAt(i); |
28 | ComposerButtonAnimation animation = new ComposerButtonAnimation( |
29 | InOutAnimation.Direction.IN, DURATION, inoutimagebutton); |
30 | animation.setStartOffset((i * 100) |
31 | / (-1 + viewgroup.getChildCount())); |
32 | animation.setInterpolator(new OvershootInterpolator(2F)); |
33 | inoutimagebutton.startAnimation(animation); |
34 | } |
35 | } |
36 | } |
37 | |
38 | private static void startAnimationsOut(ViewGroup viewgroup) { |
39 | for (int i = 0; i < viewgroup.getChildCount(); i++) { |
40 | if (viewgroup.getChildAt(i) instanceof InOutImageButton) { |
41 | InOutImageButton inoutimagebutton = (InOutImageButton) viewgroup |
42 | .getChildAt(i); |
43 | ComposerButtonAnimation animation = new ComposerButtonAnimation( |
44 | InOutAnimation.Direction.OUT, DURATION, |
45 | inoutimagebutton); |
46 | animation.setStartOffset((100 * ((-1 + viewgroup |
47 | .getChildCount()) - i)) |
48 | / (-1 + viewgroup.getChildCount())); |
49 | animation.setInterpolator(new AnticipateInterpolator(2F)); |
50 | inoutimagebutton.startAnimation(animation); |
51 | } |
52 | } |
53 | } |
54 | |
55 | @Override |
56 | protected void addInAnimation(View[] aview) { |
57 | MarginLayoutParams mlp = (MarginLayoutParams) aview[0] |
58 | .getLayoutParams(); |
59 | addAnimation(new TranslateAnimation(xOffset + -mlp.leftMargin, 0F, |
60 | yOffset + mlp.bottomMargin, 0F)); |
61 | } |
62 | |
63 | @Override |
64 | protected void addOutAnimation(View[] aview) { |
65 | MarginLayoutParams mlp = (MarginLayoutParams) aview[0] |
66 | .getLayoutParams(); |
67 | addAnimation(new TranslateAnimation(0F, xOffset + -mlp.leftMargin, 0F, |
68 | yOffset + mlp.bottomMargin)); |
69 | } |
70 | } |
剩下的增大出现、增大消失及缩小消失都是scale和alpha的组合动画。
例如增大出现为:
ComposerButtonAnimation.java
1 | addAnimation(new ScaleAnimation(0F, 1F, 0F, 1F, 1, 0.5F, 1, 0.5F)); |
2 | addAnimation(new AlphaAnimation(0F, 1F)); |
1 | public class ComposerButtonGrowAnimationIn extends InOutAnimation { |
2 | |
3 | public ComposerButtonGrowAnimationIn(int i) { |
4 | super(InOutAnimation.Direction.IN, i, new View[0]); |
5 | } |
6 | |
7 | @Override |
8 | protected void addInAnimation(View[] aview) { |
9 | addAnimation(new ScaleAnimation(0F, 1F, 0F, 1F, 1, 0.5F, 1, 0.5F)); |
10 | addAnimation(new AlphaAnimation(0F, 1F)); |
11 | |
12 | } |
13 | |
14 | @Override |
15 | protected void addOutAnimation(View[] aview) {} |
16 | |
17 | } |
18 | |
19 | public class ComposerButtonGrowAnimationOut extends InOutAnimation { |
20 | |
21 | public ComposerButtonGrowAnimationOut(int i) { |
22 | super(InOutAnimation.Direction.OUT, i, new View[0]); |
23 | } |
24 | |
25 | @Override |
26 | protected void addInAnimation(View[] aview) {} |
27 | |
28 | @Override |
29 | protected void addOutAnimation(View[] aview) { |
30 | addAnimation(new ScaleAnimation(1F, 5F, 1F, 5F, 1, 0.5F, 1, 0.5F)); |
31 | addAnimation(new AlphaAnimation(1F, 0F)); |
32 | } |
33 | |
34 | } public class ComposerButtonShrinkAnimationOut extends InOutAnimation { |
35 | |
36 | public ComposerButtonShrinkAnimationOut(int i) { |
37 | super(InOutAnimation.Direction.OUT, i, new View[0]); |
38 | } |
39 | |
40 | @Override |
41 | protected void addInAnimation(View[] aview) { |
42 | |
43 | } |
44 | |
45 | @Override |
46 | protected void addOutAnimation(View[] aview) { |
47 | addAnimation(new ScaleAnimation(1F, 0F, 1F, 0F, 1, 0.5F, 1, 0.5F)); |
48 | addAnimation(new AlphaAnimation(1F, 0F)); |
49 | } |
50 | } |
接下来我们需要为这些控件做一下扩展,以便其可以再动画完成后显示或消失。
很简单:
1 | public class InOutImageButton extends ImageButton { |
2 | |
3 | private Animation animation; |
4 | |
5 | public InOutImageButton(Context context, AttributeSet attrs, int defStyle) { |
6 | super(context, attrs, defStyle); |
7 | } |
8 | |
9 | public InOutImageButton(Context context, AttributeSet attrs) { |
10 | super(context, attrs); |
11 | } |
12 | |
13 | public InOutImageButton(Context context) { |
14 | super(context); |
15 | } |
16 | |
17 | @Override |
18 | protected void onAnimationEnd() { |
19 | super.onAnimationEnd(); |
20 | if ((this.animation instanceof InOutAnimation)) { |
21 | setVisibility(((InOutAnimation) this.animation).direction != InOutAnimation.Direction.OUT ? View.VISIBLE |
22 | : View.GONE); |
23 | } |
24 | } |
25 | |
26 | @Override |
27 | protected void onAnimationStart() { |
28 | super.onAnimationStart(); |
29 | if ((this.animation instanceof InOutAnimation)) |
30 | setVisibility(View.VISIBLE); |
31 | } |
32 | |
33 | @Override |
34 | public void startAnimation(Animation animation) { |
35 | super.startAnimation(animation); |
36 | this.animation = animation; |
37 | getRootView().postInvalidate(); |
38 | } |
39 | } |
那么到这里基本上就已经搞定了所有的事情了,剩下点没做的事就是把这些动画效果设置给对应的控件了,这里就不详细描述了。
源码下载:http://files.cnblogs.com/mudoot/PureComposerDemo.rar。