Android动画

Android 中的动画可以分为以下几类:

  • 帧动画
  • 补间动画
  • 属性动画

一、帧动画

逐帧动画的原理就是让一系列的静态图片依次播放,利用人眼“视觉暂留”的原理,实现动画。

利用 xml 实现逐帧动画

逐帧动画通常是采用 XML 资源进行定义的,需要在 <animation-list .../> 标签下使用 <item .../> 子元素标签定义动画的全部帧,并指定各帧的持续时间。
定义逐帧动画的语法格式如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
      android:oneshot="true|false">
      <item android:drawable="" android:duration=""/>
</animation-list>

其中android:oneshot控制该动画是否循环播放。如果为true,动画将不会循环播放, 否则该动画将会循环播放

步骤:

1、res/drawable 下新建 xml 文件,这里定义动画的每一帧,素材图片放到 drawable 下。

frame_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/frame01" android:duration="100"/>
    <item android:drawable="@drawable/frame02" android:duration="100"/>
    <item android:drawable="@drawable/frame03" android:duration="100"/>
    <item android:drawable="@drawable/frame04" android:duration="100"/>
    <item android:drawable="@drawable/frame05" android:duration="100"/>
    <item android:drawable="@drawable/frame06" android:duration="100"/>
    <item android:drawable="@drawable/frame07" android:duration="100"/>
    <item android:drawable="@drawable/frame08" android:duration="100"/>
    <item android:drawable="@drawable/frame09" android:duration="100"/>
    <item android:drawable="@drawable/frame10" android:duration="100"/>
    <item android:drawable="@drawable/frame11" android:duration="100"/>
    <item android:drawable="@drawable/frame12" android:duration="100"/>
    <item android:drawable="@drawable/frame13" android:duration="100"/>
    <item android:drawable="@drawable/frame14" android:duration="100"/>
    <item android:drawable="@drawable/frame15" android:duration="100"/>
    <item android:drawable="@drawable/frame16" android:duration="100"/>
    <item android:drawable="@drawable/frame17" android:duration="100"/>
    <item android:drawable="@drawable/frame18" android:duration="100"/>

</animation-list>

2、布局中将 AnimationDrawable 对象直接作为背景

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_marginTop="50dp"
        android:layout_centerHorizontal="true"
        android:id="@+id/frame_image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@drawable/frame_animation"
        />

    <LinearLayout
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_margin="5dp"
        android:layout_marginBottom="20dp"
        >
    <Button
        android:id="@+id/frame_start"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="start"
        android:text="start"
        />

        <Button
            android:id="@+id/frame_stop"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="stop"
            android:text="stop"
            />
    </LinearLayout>
</RelativeLayout>

3、Activity 中控制播放与停止

public class FrameAnimation extends AppCompatActivity {
    ImageView frame_image;
    AnimationDrawable animationDrawable;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        frame_image = findViewById(R.id.frame_image);
        // 获取 AnimationDrawable 对象
         animationDrawable = (AnimationDrawable) frame_image.getBackground();
    }

    public void start(View view){
        // 开始播放
        animationDrawable.start();
    }

    public void stop(View view){
        //停止播放
        animationDrawable.stop();
    }
}

利用 Java 代码实现逐帧动画

通过代码实现逐帧动画,就不用在布局中设置 backgroud。

1、首先要创建 AnimationDrawable 对象

    animationDrawable = new AnimationDrawable();

        for (int i = 1; i < 10; i ++ ){
            int id = getResources().getIdentifier("frame0" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }

        for (int i = 10; i < 19; i ++){
            int id = getResources().getIdentifier("frame" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }

2、控制播放和停止

    public void start(View view){
        // 开始播放
       // animationDrawable.start();

        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        // 获取资源对象
        animationDrawable.stop();
        // 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
        animationDrawable.start();
        // 启动动画

    }

    public void stop(View view){
        //停止播放
       // animationDrawable.stop();
        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        animationDrawable.stop();
    }

完整代码:

public class FrameAnimation extends AppCompatActivity {
    ImageView frame_image;
    AnimationDrawable animationDrawable;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        frame_image = findViewById(R.id.frame_image);
        // 获取 AnimationDrawable 对象
        // animationDrawable = (AnimationDrawable) frame_image.getBackground();

        animationDrawable = new AnimationDrawable();

        for (int i = 1; i < 10; i ++ ){
            int id = getResources().getIdentifier("frame0" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }

        for (int i = 10; i < 19; i ++){
            int id = getResources().getIdentifier("frame" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }


    }


    public void start(View view){
        // 开始播放
       // animationDrawable.start();

        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        // 获取资源对象
        animationDrawable.stop();
        // 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
        animationDrawable.start();
        // 启动动画

    }

    public void stop(View view){
        //停止播放
       // animationDrawable.stop();
        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        animationDrawable.stop();
    }
}

效果:

二、补间动画

补间动画就是指开发者指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。
补间动画有四种:

  • 透明度: alpha
  • 位移:translate
  • 缩放:scale
  • 旋转: rotate

1、 XML 形式补间动画

补间动画一般也是通过 xml 来实现,对于 xml 形式补间动画的定义,也是需要在 res/anim/ 文件夹下定义动画资源,如:
alpha_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:toAlpha="0.0" />

interpolator 代表插值器,主要作用是可以控制动画的变化速率,可以通过 @android:anim 来选择不同的插值器。

scale_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0"/>

translate_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
      android:fromDegree="0"
      android:toDegree="1800"
      android:pivotX = "50%"
      android:pivotY="50%"
      android:duration = "3000"
/>

pivot 这个属性主要是在translate 和 scale 动画中,这两种动画都牵扯到view 的“物理位置“发生变化,所以需要一个参考点。而pivotX和pivotY就共同决定了这个点;它的值可以是float或者是百分比数值。
以 pivotX 为例,说明其取不同的值的含义:
10:距离动画所在view自身左边缘10像素
10% :距离动画所在view自身左边缘 的距离是整个view宽度的10%
10%p:距离动画所在view父控件左边缘的距离是整个view宽度的10%

rotate_anim.xml

<?xml version="1.0" encoding="utf-8"?>
 <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="50%"
        android:pivotY="50%" />

定义好了动画资源之后,就可以利用 AnimationUtils 工具类来加载指定动画资源,加载成功后返回一个 Animation,该对象可以控制图片或者视图播放动画。

示例:
一、定义动画资源:
res\anim\tween_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    >
    <scale
        android:duration="3000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.0"
        android:toYScale="1.0"/>
    <alpha
        android:duration="3000"
        android:fromAlpha="1.0"
        android:toAlpha="0.5" />
    <rotate
        android:fromDegrees="0"
        android:toDegrees="720"
        android:pivotX = "50%"
        android:pivotY="50%"
        android:duration = "3000"
        />
    <translate
        android:fromXDelta="0"
        android:toXDelta="100"
        android:fromYDelta="0"
        android:toYDelta="100" />
</set>

二、Animation 控制图片播放动画

public class tweenAnimation extends AppCompatActivity {
    // tween_image;
    Button tween_start;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tween_animation);

        final ImageView  tween_image = findViewById(R.id.tween_image);
        tween_start = findViewById(R.id.tween_start);
        
        // 加载动画资源
        final Animation anim = AnimationUtils.loadAnimation(this,R.anim.tween_anim);
        //设置动画结束后保留结束状态
        anim.setFillAfter(true);
        
        tween_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                tween_image.startAnimation(anim);
            }
        });
        
    }
}

这几个动画可以组合在一起使用,同时完成缩放、透明的、旋转或者位移等的变化。

2、Java 代码实现补间动画

  • Translate
 Animation translateAnimation = new TranslateAnimation(0,500,0,500);
        // 创建平移动画的对象:平移动画对应的Animation子类为TranslateAnimation
        // 参数分别是:
        // 1. fromXDelta :视图在水平方向x 移动的起始值
        // 2. toXDelta :视图在水平方向x 移动的结束值
        // 3. fromYDelta :视图在竖直方向y 移动的起始值
        // 4. toYDelta:视图在竖直方向y 移动的结束值
        translateAnimation.setDuration(3000);
        // 播放动画直接 startAnimation(translateAnimation)
        //如:
        mButton.startAnimation(translateAnimation);
  • Scale:
        Animation scaleAnimation= new ScaleAnimation(0,2,0,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        // 1. fromX :动画在水平方向X的结束缩放倍数
        // 2. toX :动画在水平方向X的结束缩放倍数
        // 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
        // 4. toY:动画在竖直方向Y的结束缩放倍数
        // 5. pivotXType:缩放轴点的x坐标的模式
        // 6. pivotXValue:缩放轴点x坐标的相对值
        // 7. pivotYType:缩放轴点的y坐标的模式
        // 8. pivotYValue:缩放轴点y坐标的相对值
        // pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 =  View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
        // pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
        // pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
        scaleAnimation.setDuration(3000);
        // 使用
        mButton.startAnimation(scaleAnimation);
  • Rotate:
   Animation rotateAnimation = new RotateAnimation(0,270,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        // 1. fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
        // 2. toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
        // 3. pivotXType:旋转轴点的x坐标的模式
        // 4. pivotXValue:旋转轴点x坐标的相对值
        // 5. pivotYType:旋转轴点的y坐标的模式
        // 6. pivotYValue:旋转轴点y坐标的相对值
        // pivotXType = Animation.ABSOLUTE:旋转轴点的x坐标 =  View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
        // pivotXType = Animation.RELATIVE_TO_SELF:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
        // pivotXType = Animation.RELATIVE_TO_PARENT:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
        rotateAnimation.setDuration(3000);
        mButton.startAnimation(rotateAnimation);
  • Alpha
 Animation alphaAnimation = new AlphaAnimation(1,0);
        // 1. fromAlpha:动画开始时视图的透明度(取值范围: -1 ~ 1)
        // 2. toAlpha:动画结束时视图的透明度(取值范围: -1 ~ 1)
        alphaAnimation.setDuration(3000);
        mButton.startAnimation(alphaAnimation);
  • 动画组合
        // 组合动画设置
        AnimationSet setAnimation = new AnimationSet(true);

        // 特别说明以下情况
        // 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
        // 所以动画不会结束,而是无限循环
        // 所以组合动画的下面两行设置是无效的
        setAnimation.setRepeatMode(Animation.RESTART);
        setAnimation.setRepeatCount(1);// 设置了循环一次,但无效

        // 旋转动画
        Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        rotate.setDuration(1000);
        rotate.setRepeatMode(Animation.RESTART);
        rotate.setRepeatCount(Animation.INFINITE);

        // 平移动画
        Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
                TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
                TranslateAnimation.RELATIVE_TO_SELF,0
                ,TranslateAnimation.RELATIVE_TO_SELF,0);
        translate.setDuration(10000);

        // 透明度动画
        Animation alpha = new AlphaAnimation(1,0);
        alpha.setDuration(3000);
        alpha.setStartOffset(7000);

        // 缩放动画
        Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        scale1.setDuration(1000);
        scale1.setStartOffset(4000);

        // 将创建的子动画添加到组合动画里
        setAnimation.addAnimation(alpha);
        setAnimation.addAnimation(rotate);
        setAnimation.addAnimation(translate);
        setAnimation.addAnimation(scale1);
        // 使用
        mButton.startAnimation(setAnimation);

3、动画监听

为了实现一些需求,如动画结束后开始另一个动画或者页面跳转,这时候就需要监听动画。

 Animation.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //动画开始时执行
          }

           @Override
          public void onAnimationRepeat(Animation animation) {
              //动画重复时执行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //动画取消时执行
          }

          @Override
          public void onAnimationEnd(Animation animation) {
              //动画结束时执行
          }
      });

4、自定义补间动画

Android 提供了 Animation 作为补间动画抽象基类,而且为该抽象基类提供了 AlphaAnimation、RotationAnimation、ScaleAnimation、TranslateAnimation 四个实现类,这四个实现类只是补间动画的基本形式:透明度、旋转、缩放、位移。但是要实现复杂的动画,就需要继承 Animation。继承 Animation 类关键是要重写一个方法:

applyTransformation(float interpolatedTime,Transformation t)
interploatedTime: 代表了动画的时间进行比。不管动画实际的持续时间如何,当动画播放时,该参数总是从 0 到 1。Transformation t:该参数代表了补间动画在不同时刻对图形或组件的变形程度。

在实现自定义动画的关键就是重写 applyTransformation 方法时根据 interpolatedTime 时间来动态地计算动画对图片或视图的变形程度。

Transformation 代表了对图片或者视图的变形,该对象封装了一个 Matrix 对象,对它所包装了 Matrix 进行位移、倾斜、旋转等变换时,Transformation 将会控制对应的图片或视图进行相应的变换。

为了控制图片或者 View 进行三维空间的变换,还需要借助于 Android 提供的一个 Camera,这个 Camera 并非代表手机摄像头,而是空间变换工具。作用类似于 Matrix,其常用方法如下:
getMatrix(Matrix matrix):将 Camera 所做的变换应用到指定的 matrix 上。
rotateX(float deg):将组件沿 X 轴旋转。
rotateY(float deg):将组件沿 Y 轴旋转。
rotateZ(float deg):将组件沿 Z 轴旋转。
translate(float x,float y,float z):目标组件在三维空间里变换。
applyToCanvas(Canvas canvas):把 Camera 所做的变换应用到 Canvas 上。

手机屏幕三维空间

下面示例利用 Camera 自定义三维空间动画。
自定义 Animation

public class CustomAnimation extends Animation {
    private float centerX;
    private float centerY;
    // 定义动画的持续事件
    private int duration;
    private Camera camera = new Camera();
    public CustomAnimation(float x,float y,int duration)
    {
        this.centerX = x;
        this.centerY = y;
        this.duration = duration;
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        //设置动画的持续时间
        setDuration(duration);
        //设置动画结束后保留效果
        setFillAfter(true);
        setInterpolator(new LinearInterpolator());
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        //super.applyTransformation(interpolatedTime, t);
        camera.save();
        // 根据 interpolatedTime 时间来控制X,Y,Z 上偏移
        camera.translate(100.0f - 100.f * interpolatedTime,150.0f * interpolatedTime - 150,80.0f - 80.0f * interpolatedTime);
        // 根据 interploatedTime 设置在 X 轴 和 Y 轴旋转
        camera.rotateX(360 * interpolatedTime);
        camera.rotateY(360 * interpolatedTime);
        // 获取 Transformation 参数的 Matrix 对象
        Matrix matrix = t.getMatrix();
        camera.getMatrix(matrix);
        matrix.preTranslate(-centerX,-centerY);
        matrix.postTranslate(centerX,centerY);
        camera.restore();
    }
}

上面自定义了动画,主要是设置了旋转。
使用:

  linearLayout.startAnimation(new CustomAnimation(metrics.xdpi/2,metrics.ydpi/2,3500));

三、属性动画

属性动画可以看作是增强版的补间动画,与补间动画的不同之处体现在:

  • 补间动画只能定义两个关键帧在透明、旋转、位移和缩放这四个属性的变换,但是属性动画可以定义任何属性的变化。
  • 补间动画只能对 UI 组件执行动画,但属性动画可以对任何对象执行动画。

与补间动画类似的是,属性动画也需要定义几个方面的属性:

  • 动画持续时间。默认为 300ms,可以通过 android:duration 属性指定。
  • 动画插值方式。通过 android:interploator 指定。
  • 动画重复次数。通过 android:repeatCount 指定。
  • 重复行为。通过 android:repeatMode 指定。
  • 动画集。在属性资源文件中通过 <set .../> 来组合。
  • 帧刷新率。指定多长时间播放一帧。默认为 10 ms。

属性动画 API

  • Animator: 提供创建属性动画的基类,基本不会直接使用这个类。
  • ValueAnimator:属性动画用到的主要的时间引擎,负责计算各个帧的属性值。
  • ObjectAnimator: ValueAnimator 的子类,对指定对象的属性执行动画。
  • AnimatorSet:Animator 的子类,用于组合多个 Animator。

除了这些 API,属性动画还提供了一个 Evaluator ,用来控制属性动画如何计算属性值。

  • IntEvaluator:计算 int 类型属性值的计算器。
  • FloatEvaluator: 用于计算 float 类型属性值的计算器。
  • ArgbEvaluator: 用于计算十六进制形式表示的颜色值的计算器。
  • TypeEvaluator: 可以自定义计算器。

使用 ValueAnimator 创建动画的步骤:

  • 调用 ValueAnimator 的 ofInt()、ofFloat() 或者 ofObject() 静态方法创建 ValueAnimator 实例。
  • 调用 ValueAnimator 的 setXxx() 等方法设置持续时间,插值方式、重复次数等。
  • 调用 ValueAnimator 的 start() 方法启动动画。
  • 为 ValueAnimator 注册 AnimatorUpdateListener 监听器,在该监听器中可以监听 ValueAnimator 计算出来的值改变,并将这些值应用到指定对象上。

属性动画的一般使用:

定义属性动画和补间动画等类似,有两种方式:

  • 使用 ValueAnimator 或者 ObjectAnimator 的静态工厂方法创建动画。
  • 使用资源文件来定义动画。

属性动画的使用:

  • 创建 ValueAnimator 或 ObjectAnimator 对象 —— 即可以从 XML 资源文件加载该动画也可以直接调用 ValueAnimator 或者 ObjectAnimator 的静态工厂方法创建动画。
  • 根据需要为 Animator 对象设置属性。
  • 如果需要监听 Animator 的动画开始事件,动画结束事件、动画重复事件、动画值改变事件,并根据事件提供响应处理代码,需要为Animator 对象设置监听器。
  • 如果有多个动画需要同时播放,需要使用 AnimatorSet 组合这些动画。
  • 调用 Animator 对象的 start 启动动画。

属性动画示例:
1、布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/imageView_b"
        android:src="@drawable/img"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/imageView_c"
        android:src="@drawable/img"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/imageView_d"
        android:src="@drawable/img"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/imageView_e"
        android:src="@drawable/img"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/imageView_a"
        android:src="@drawable/img"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

2、使用

public class PropertyAnimator  extends Activity implements View.OnClickListener {

    private int[] mRes = {R.id.imageView_a, R.id.imageView_b, R.id.imageView_c,
            R.id.imageView_d, R.id.imageView_e};
    private List<ImageView> mImageViews = new ArrayList<>();
    private boolean mFlag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.property_animator);

        int sum = mRes.length;
        for (int i = 0; i < sum; i++) {
            ImageView imageView = (ImageView) findViewById(mRes[i]);
            imageView.setOnClickListener(this);
            mImageViews.add(imageView);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imageView_a:
                if (mFlag) {
                    startAnim();
                } else {
                    closeAnim();
                }
                break;
            case R.id.imageView_b:
                Toast.makeText(PropertyAnimator.this, "b", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_c:
                Toast.makeText(PropertyAnimator.this, "c", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_d:
                Toast.makeText(PropertyAnimator.this, "d", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_e:
                Toast.makeText(PropertyAnimator.this, "e", Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void closeAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),
                "alpha", 0.5F, 1F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),
                "translationY", 200F, 0);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),
                "translationX", 200F, 0);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),
                "translationY", -200F, 0);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),
                "translationX", -200F, 0);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(animator0, animator1, animator2, animator3, animator4);
        set.start();
        mFlag = true;
    }

    private void startAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(
                mImageViews.get(0),
                "alpha",
                1F,
                0.5F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(
                mImageViews.get(1),
                "translationY",
                200F);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(
                mImageViews.get(2),
                "translationX",
                200F);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(
                mImageViews.get(3),
                "translationY",
                -200F);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(
                mImageViews.get(4),
                "translationX",
                -200F);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(
                animator0,
                animator1,
                animator2,
                animator3,
                animator4);
        set.start();
        mFlag = false;
    }
}

效果:

四、使用 SurfaceView 实现动画

实现动画还可以通过自定义 View 的方式,但是自定义 View 有如下缺陷:

  • View 缺乏双缓冲机制。
  • 当程序需要更新 View 上的图像时,程序必须重绘 View 上显示的整张图片。
  • 新线程无法直接更新 View 组件。

因此,自定义 View 实现绘图不是很好的选择,尤其是游戏绘图时,性能不是很好。因此,Android 提供了一个 SurfaceView 来代替 View。

SurfaceView 一般使用:

首先继承SurfaceView,并实现SurfaceHolder.Callback接口,实现它的三个方法:

  • surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。
  • surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。
  • surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。

SurfaceView 一般与 SurfaceHolder 结合使用,SurfaceHolder 用于向与之关联的 SurfaceView 上绘图,调用 SurfaceView 的 getHolder() 方法就可以获取 SurfaceView 关联的 SurfaceHolder。

SurfaceHolder 提供了如下方法获取 Canvas 对象。

  • Canvas lockCanvas(): 锁定整个 SurfaceView 对象,获取该 Surface 上的 Canvas。
  • Canvas locakCanvas(Rect dirty): 锁定 SurfaceView 上的 Rect 划分的区域,获取该 Surface 上的 Canvas,这样可以提高画面的跟新速度。

释放 Canvas 并提交绘制:

  • unlockCanvasAndPost(canvas)

SurfaceView 与普通 View 还有一个重要区别: View 的绘图必须在 UI 线程中进行,而 SurfaceView 由 SurfaceHolder 来完成,SurfaceHodler 会开启新的线程去绘制,不会阻塞 UI 线程。

SurfaceView 实现动画示例:

  • 定义一个动画 Bean 类:
public class SurfaceBean {

    public Point point;
    /**
     * 移动动画
     */
    private ValueAnimator moveAnim;
    /**
     * 放大动画
     */
    private ValueAnimator zoomAnim;
    /**
     * 透明度
     */
    public int alpha = 255;//
 
    private Bitmap bitmap;
    /**
     * 绘制bitmap的矩阵  用来做缩放和移动的
     */
    private Matrix matrix = new Matrix();
    /**
     * 缩放系数
     */
    private float sf = 0;
    /**
     * 产生随机数
     */
    private Random random;
    public boolean isEnd = false;//是否结束

    public SurfaceBean(Context context, int resId, SurfaceAnimView surfaceAnimView) {
        random = new Random();
        bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
        init(new Point(surfaceAnimView.getWidth() / 2, surfaceAnimView.getHeight()- bitmap.getHeight() / 2), new Point((random.nextInt(surfaceAnimView.getWidth())), 0));
    }


    public SurfaceBean(Bitmap bitmap, SurfaceAnimView surfaceAnimView) {
        random = new Random();
        this.bitmap = bitmap;
        //为了让在起始坐标点时显示完整 需要减去bitmap.getHeight()/2
        init(new Point(surfaceAnimView.getWidth() / 2, surfaceAnimView.getHeight() - bitmap.getHeight() / 2), new Point((random.nextInt(surfaceAnimView.getWidth())), 0));
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void init(final Point startPoint, Point endPoint) {
        moveAnim = ValueAnimator.ofObject(new BezierEvaluator(new Point(random.nextInt(startPoint.x * 2), Math.abs(endPoint.y - startPoint.y) / 2)), startPoint, endPoint);
        moveAnim.setDuration(1500);
        moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                point = (Point) animation.getAnimatedValue();
                alpha = (int) ((float) point.y / (float) startPoint.y * 255);
            }
        });
        moveAnim.start();
        zoomAnim = ValueAnimator.ofFloat(0, 1f).setDuration(700);
        zoomAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float f = (Float) animation.getAnimatedValue();
                sf = f.floatValue();
            }
        });
        zoomAnim.start();
    }


    public void stop() {
        if (moveAnim != null) {
            moveAnim.cancel();
            moveAnim = null;
        }
        if (zoomAnim != null) {
            zoomAnim.cancel();
            zoomAnim = null;
        }
    }

    /**
     * 主要绘制函数
     */
    public void draw(Canvas canvas, Paint p) {
        if (bitmap != null && alpha > 0) {
            p.setAlpha(alpha);
            matrix.setScale(sf, sf, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
            matrix.postTranslate(point.x - bitmap.getWidth() / 2, point.y - bitmap.getHeight() / 2);
            canvas.drawBitmap(bitmap, matrix, p);
        } else {
            isEnd = true;
        }
    }

    /**
     * 二次贝塞尔曲线
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private class BezierEvaluator implements TypeEvaluator<Point> {

        private Point centerPoint;

        public BezierEvaluator(Point centerPoint) {
            this.centerPoint = centerPoint;
        }

        @Override
        public Point evaluate(float t, Point startValue, Point endValue) {
            int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * centerPoint.x + t * t * endValue.x);
            int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * centerPoint.y + t * t * endValue.y);
            return new Point(x, y);
        }
    }
}
  • 定义绘图类
public class SurfaceAnimView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder surfaceHolder;

    private ArrayList<SurfaceBean> surfaceBeans = new ArrayList<>();
    private Paint p;
    /**
     * 负责绘制的工作线程
     */
    private DrawThread drawThread;

    public SurfaceAnimView(Context context) {
        this(context, null);
    }

    public SurfaceAnimView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SurfaceAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setZOrderOnTop(true);
        /**设置画布  背景透明*/
        this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        p = new Paint();
        p.setAntiAlias(true);
        drawThread = new DrawThread();
    }


    public void addBean(SurfaceBean surfaceBean){
        surfaceBeans.add(surfaceBean);
        if (surfaceBeans.size() > 40) {
            surfaceBeans.remove(0);
        }
        start();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (drawThread == null) {
            drawThread = new DrawThread();
        }
        drawThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (drawThread != null) {
            drawThread.isRun = false;
            drawThread = null;
        }
    }

    class DrawThread extends Thread {
        boolean isRun = true;

        @Override
        public void run() {
            super.run();
            /**绘制的线程 死循环 不断的跑动*/
            while (isRun) {
                Canvas canvas = null;
                try {
                    synchronized (surfaceHolder) {
                        canvas = surfaceHolder.lockCanvas();
                        /**清除画面*/
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        boolean isEnd = true;

                        for (int i = 0; i < surfaceBeans.size(); i++) {
                            isEnd = surfaceBeans.get(i).isEnd;
                            surfaceBeans.get(i).draw(canvas, p);
                        }
                        /**这里做一个性能优化的动作,由于线程是死循环的 在没有心需要的绘制的时候会结束线程*/
                        if (isEnd) {
                            isRun = false;
                            drawThread = null;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (canvas != null) {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
                try {
                    /**用于控制绘制帧率*/
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void stop() {
        if (drawThread != null) {

            for (int i = 0; i < surfaceBeans.size(); i++) {
                surfaceBeans.get(i).stop();
            }

            drawThread.isRun = false;
            drawThread = null;
        }

    }

    public void start() {
        if (drawThread == null) {
            drawThread = new DrawThread();
            drawThread.start();
        }
    }
}

  • 使用
public class SurfaceViewTest extends AppCompatActivity {
    SurfaceAnimView surface_anim_view;
    Button button;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.surfaceview_anim);
        surface_anim_view = findViewById(R.id.surface_anim_view);
        button = findViewById(R.id.add);

        surface_anim_view.start();
    }

    public void add(View view) {
        SurfaceBean surfaceBean = new SurfaceBean(BitmapFactory.decodeResource(getResources(), R.drawable.img_t), surface_anim_view);
        surface_anim_view.addBean(surfaceBean);
    }
}

  • 效果

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值