[关闭]
@linux1s1s 2017-01-22T16:26:33.000000Z 字数 12230 阅读 4599

Android 属性动画 (Property Animation)

AndroidAnimation 2016-06


为什么会有属性动画?因为前面补间动画和帧动画不能胜任复杂动画,所以属性动画应运而生。

了解属性动画前需要对下面这些属性门清

接下来分别介绍属性动画的相关类,我们从简单的开始

ObjectAnimator

这个类比较简单,我们直接看如何使用即可

  1. @TargetApi(VERSION_CODES.HONEYCOMB)
  2. private void rotate(View view)
  3. {
  4. ObjectAnimator.ofFloat(view, "rotationX", 0.0f, 360.0f)
  5. .setDuration(100)
  6. .start();
  7. }

如果我们设置一个背景图的点击事件,然后调用这个rotate()方法,那么这个背景图就会沿着X轴旋转360度,哪里定义非得沿着X轴旋转了呢?我们先来看看ofFloat()这个静态方法

  1. public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
  2. ObjectAnimator anim = new ObjectAnimator(target, propertyName);
  3. anim.setFloatValues(values);
  4. return anim;
  5. }
  6. private ObjectAnimator(Object target, String propertyName) {
  7. setTarget(target);
  8. setPropertyName(propertyName);
  9. }
  10. public void setPropertyName(@NonNull String propertyName) {
  11. // mValues could be null if this is being constructed piecemeal. Just record the
  12. // propertyName to be used later when setValues() is called if so.
  13. if (mValues != null) {
  14. PropertyValuesHolder valuesHolder = mValues[0];
  15. String oldName = valuesHolder.getPropertyName();
  16. valuesHolder.setPropertyName(propertyName);
  17. mValuesMap.remove(oldName);
  18. mValuesMap.put(propertyName, valuesHolder);
  19. }
  20. mPropertyName = propertyName;
  21. // New property/values/target should cause re-initialization prior to starting
  22. mInitialized = false;
  23. }

然后就进一步跟踪到PropertyValuesHolder这个类中的setPropertyName方法

  1. /**
  2. * Sets the name of the property that will be animated. This name is used to derive
  3. * a setter function that will be called to set animated values.
  4. * For example, a property name of <code>foo</code> will result
  5. * in a call to the function <code>setFoo()</code> on the target object. If either
  6. * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
  7. * also be derived and called.
  8. *
  9. * <p>Note that the setter function derived from this property name
  10. * must take the same parameter type as the
  11. * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
  12. * the setter function will fail.</p>
  13. *
  14. * @param propertyName The name of the property being animated.
  15. */
  16. public void setPropertyName(String propertyName) {
  17. mPropertyName = propertyName;
  18. }

从上面一大段的英文注释可以看到,这里方法保存了propertyName并在合适的时机通过反射调用setxxx方法,如下

  1. String methodName = getMethodName("set", mPropertyName);

这个给出可以操作的属性:scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY

接下来解释一下这个静态方法的参数定义
提供了ofInt、ofFloat、ofObject,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。
当对于属性值,只设置一个的时候,会认为当然对象该属性的值为开始(getPropName反射获取),然后设置的值为终点。如果设置两个,则一个为开始、一个为结束~~~
动画更新的过程中,会不断调用setPropName更新元素的属性,所有使用ObjectAnimator更新某个属性,必须得有getter(设置一个属性值的时候)和setter方法~

上面这个例子很单一,因为仅仅涉及一个参数的变化,当我们有多个参数涉及变化,怎么办?

  1. @TargetApi(VERSION_CODES.HONEYCOMB)
  2. private void rotate(final View view)
  3. {
  4. ObjectAnimator animator = ObjectAnimator.ofFloat(view, "custom", 1.0f, 0.0f)
  5. .setDuration(100);
  6. animator.start();
  7. animator.addUpdateListener(new AnimatorUpdateListener()
  8. {
  9. @Override
  10. public void onAnimationUpdate(ValueAnimator animation)
  11. {
  12. float val = (float) animation.getAnimatedValue();
  13. view.setAlpha(val);
  14. view.setScaleX(val);
  15. view.setScaleY(val);
  16. }
  17. });
  18. }

注意这里propertyName参数传进来的值并没有出现上面给出的几个属性值scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY,而是一个随便定义的值custom,监听一个不存在的属性的原因就是,我们只需要动画的变化值,通过这个值,我们自己来实现要修改的效果,实际上,更直接的方法,就是使用ValueAnimator来实现,其实ObjectAnimator就是ValueAnimator的子类,这个在下面会具体讲到。

上面是通过代码来实现ObjectAnimator能不能直接通过xml来定义一个标签 objectAnimator实现相同的效果呢?
当然可以,举个例子:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:duration="1000"
  4. android:propertyName="scaleX"
  5. android:valueFrom="1.0"
  6. android:valueTo="2.0"
  7. android:valueType="floatType" >
  8. </objectAnimator>

然后在代码中加载进来即可,如下所示:

  1. public void scaleX(View view)
  2. {
  3. Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
  4. anim.setTarget(view);
  5. anim.start();
  6. }

这个仅仅是当个方向的动画,如果需要多个方法的动画该肿么整?

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:ordering="together" >
  4. <objectAnimator
  5. android:duration="1000"
  6. android:propertyName="scaleX"
  7. android:valueFrom="1"
  8. android:valueTo="0.5" >
  9. </objectAnimator>
  10. <objectAnimator
  11. android:duration="1000"
  12. android:propertyName="scaleY"
  13. android:valueFrom="1"
  14. android:valueTo="0.5" >
  15. </objectAnimator>
  16. </set>

同样是通过XML来设置初始化值。然后通过代码实现动画

  1. Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
  2. mMv.setPivotX(0);
  3. mMv.setPivotY(0);
  4. mMv.invalidate();
  5. anim.setTarget(view);
  6. anim.start();

ValueAnimator

说简单点,ValueAnimator就是一个数值产生器,他本身不作用于任何一个对象,但是可以对产生的值进行动画处理。先看个小球的例子

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:id="@+id/id_container"
  6. >
  7. <ImageView
  8. android:id="@+id/id_ball"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:src="@drawable/bol_blue" />
  12. <LinearLayout
  13. android:layout_width="fill_parent"
  14. android:layout_height="wrap_content"
  15. android:layout_alignParentBottom="true"
  16. android:orientation="horizontal" >
  17. <Button
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:onClick="verticalRun"
  21. android:text="垂直" />
  22. <Button
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:onClick="paowuxian"
  26. android:text="抛物线" />
  27. </LinearLayout>
  28. </RelativeLayout>

左上角一个小球,底部两个按钮~我们先看一个自由落体的代码:

  1. public void verticalRun( View view)
  2. {
  3. ValueAnimator animator = ValueAnimator.ofFloat(0, mScreenHeight
  4. - mBlueBall.getHeight());
  5. animator.setTarget(mBlueBall);
  6. animator.setDuration(1000).start();
  7. // animator.setInterpolator(value)
  8. animator.addUpdateListener(new AnimatorUpdateListener()
  9. {
  10. @Override
  11. public void onAnimationUpdate(ValueAnimator animation)
  12. {
  13. mBlueBall.setTranslationY((Float) animation.getAnimatedValue());
  14. }
  15. });
  16. }
  • 通过这个动画我们可以发现,和我们在上面提供的使用ObjectAnimator的方法很像,的确,我前面说这个才是专业的写法,就是这个原因,动画生成的原理就是通过差值器计算出来的一定规律变化的数值作用到对象上来实现对象效果的变化,因此我们可以使用ObjectAnimator来生成这些数,然后在动画重绘的监听中,完成自己的效果。
  • ValueAnimator是计算动画过程中变化的值,包含动画的开始值,结束值,持续时间等属性。但并没有把这些计算出来的值应用到具体的对象上面,所以也不会有什么的动画显示出来。要把计算出来的值应用到对象上,必须为ValueAnimator注册一个监听器ValueAnimator.AnimatorUpdateListener,该监听器负责更新对象的属性值。在实现这个监听器的时候,可以通过getAnimatedValue()的方法来获取当前帧的值。
  • ValueAnimator封装了一个TimeInterpolator,TimeInterpolator定义了属性值在开始值与结束值之间的插值方法。ValueAnimator还封装了一个TypeAnimator,根据开始、结束值与TimeIniterpolator计算得到的值计算出属性值。ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子,最后TypeAnimator通过这个因子计算出属性值,例如在10ms时(total 40ms):
    首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25
    经插值计算(inteplator)后的插值因子:大约为0.15,如果使用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):
    (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    最后根据TypeEvaluator计算出在10ms时的属性值:0.15*(40-0)=6pixel。如果使用TypeEvaluator为FloatEvaluator,计算方法为 :
  1. public Float evaluate(float fraction, Number startValue, Number endValue)
  2. {
  3. float startFloat = startValue.floatValue();
  4. return startFloat + fraction * (endValue.floatValue() - startFloat);
  5. }

我们这里把监听重新拿出来说一下:
对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们需要知道动画如何结束。

所以我们可以添加一个动画的监听:

  1. public void fadeOut(View view)
  2. {
  3. ObjectAnimator anim = ObjectAnimator.ofFloat(mBlueBall, "alpha", 0.5f);
  4. anim.addListener(new AnimatorListener()
  5. {
  6. @Override
  7. public void onAnimationStart(Animator animation)
  8. {
  9. Log.e(TAG, "onAnimationStart");
  10. }
  11. @Override
  12. public void onAnimationRepeat(Animator animation)
  13. {
  14. // TODO Auto-generated method stub
  15. Log.e(TAG, "onAnimationRepeat");
  16. }
  17. @Override
  18. public void onAnimationEnd(Animator animation)
  19. {
  20. Log.e(TAG, "onAnimationEnd");
  21. ViewGroup parent = (ViewGroup) mBlueBall.getParent();
  22. if (parent != null)
  23. parent.removeView(mBlueBall);
  24. }
  25. @Override
  26. public void onAnimationCancel(Animator animation)
  27. {
  28. // TODO Auto-generated method stub
  29. Log.e(TAG, "onAnimationCancel");
  30. }
  31. });
  32. anim.start();
  33. }

AnimatorSet

AnimatorSet用于实现多个动画的协同作用。AnimatorSet中有一系列的顺序控制方法:playTogether、playSequentially、animSet.play().with()、defore()、after()等。用来实现多个动画的协同工作方式。

Layout Animations

布局动画,主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果。
我们来看看比较常用的方法如下:

  1. LayoutTransition transition = new LayoutTransition();
  2. transition.setAnimator(LayoutTransition.CHANGE_APPEARING,
  3. transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
  4. transition.setAnimator(LayoutTransition.APPEARING,
  5. null);
  6. transition.setAnimator(LayoutTransition.DISAPPEARING,
  7. null);
  8. transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
  9. null);
  10. mGridLayout.setLayoutTransition(transition);

然后我们重点看一下LayoutTransition定义的几个属性:
过渡的类型一共有四种:

注意动画到底设置在谁身上,此View还是其他View。

关于上面这四个参数的范例可以看看下面这个Demo
先上Layout部分

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/id_container"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical" >
  7. <Button
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:onClick="addBtn"
  11. android:text="addBtns" />
  12. <CheckBox
  13. android:id="@+id/id_appear"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:checked="true"
  17. android:text="APPEARING" />
  18. <CheckBox
  19. android:id="@+id/id_change_appear"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:checked="true"
  23. android:text="CHANGE_APPEARING" />
  24. <CheckBox
  25. android:id="@+id/id_disappear"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:checked="true"
  29. android:text="DISAPPEARING" />
  30. <CheckBox
  31. android:id="@+id/id_change_disappear"
  32. android:layout_width="wrap_content"
  33. android:layout_height="wrap_content"
  34. android:checked="true"
  35. android:text="CHANGE_DISAPPEARING " />
  36. </LinearLayout>

接下来看看Code

  1. package com.example.zhy_property_animation;
  2. import android.animation.LayoutTransition;
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.view.ViewGroup;
  8. import android.widget.Button;
  9. import android.widget.CheckBox;
  10. import android.widget.CompoundButton;
  11. import android.widget.CompoundButton.OnCheckedChangeListener;
  12. import android.widget.GridLayout;
  13. public class LayoutAnimaActivity extends Activity implements
  14. OnCheckedChangeListener
  15. {
  16. private ViewGroup viewGroup;
  17. private GridLayout mGridLayout;
  18. private int mVal;
  19. private LayoutTransition mTransition;
  20. private CheckBox mAppear, mChangeAppear, mDisAppear, mChangeDisAppear;
  21. @Override
  22. public void onCreate(Bundle savedInstanceState)
  23. {
  24. super.onCreate(savedInstanceState);
  25. setContentView(R.layout.layout_animator);
  26. viewGroup = (ViewGroup) findViewById(R.id.id_container);
  27. mAppear = (CheckBox) findViewById(R.id.id_appear);
  28. mChangeAppear = (CheckBox) findViewById(R.id.id_change_appear);
  29. mDisAppear = (CheckBox) findViewById(R.id.id_disappear);
  30. mChangeDisAppear = (CheckBox) findViewById(R.id.id_change_disappear);
  31. mAppear.setOnCheckedChangeListener(this);
  32. mChangeAppear.setOnCheckedChangeListener(this);
  33. mDisAppear.setOnCheckedChangeListener(this);
  34. mChangeDisAppear.setOnCheckedChangeListener(this);
  35. mGridLayout = new GridLayout(this);
  36. mGridLayout.setColumnCount(5);
  37. viewGroup.addView(mGridLayout);
  38. mTransition = new LayoutTransition();
  39. mGridLayout.setLayoutTransition(mTransition);
  40. }
  41. public void addBtn(View view)
  42. {
  43. final Button button = new Button(this);
  44. button.setText((++mVal) + "");
  45. mGridLayout.addView(button, Math.min(1, mGridLayout.getChildCount()));
  46. button.setOnClickListener(new OnClickListener()
  47. {
  48. @Override
  49. public void onClick(View v)
  50. {
  51. mGridLayout.removeView(button);
  52. }
  53. });
  54. }
  55. @Override
  56. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
  57. {
  58. mTransition = new LayoutTransition();
  59. mTransition.setAnimator(
  60. LayoutTransition.APPEARING,
  61. (mAppear.isChecked() ? mTransition
  62. .getAnimator(LayoutTransition.APPEARING) : null));
  63. mTransition
  64. .setAnimator(
  65. LayoutTransition.CHANGE_APPEARING,
  66. (mChangeAppear.isChecked() ? mTransition
  67. .getAnimator(LayoutTransition.CHANGE_APPEARING)
  68. : null));
  69. mTransition.setAnimator(
  70. LayoutTransition.DISAPPEARING,
  71. (mDisAppear.isChecked() ? mTransition
  72. .getAnimator(LayoutTransition.DISAPPEARING) : null));
  73. mTransition.setAnimator(
  74. LayoutTransition.CHANGE_DISAPPEARING,
  75. (mChangeDisAppear.isChecked() ? mTransition
  76. .getAnimator(LayoutTransition.CHANGE_DISAPPEARING)
  77. : null));
  78. mGridLayout.setLayoutTransition(mTransition);
  79. }
  80. }

此处输入图片的描述

关于属性动画就介绍到这里,其他更深入的介绍后续会更新

参考:http://blog.csdn.net/lmj623565791/article/details/38067475

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注