动画与图形——安卓中的属性动画

之前提到Android提供了三种动画类型:

  1. View Animation
  2. Drawable Animation
  3. Property Animation

本文介绍第三种

Property Animation(属性动画)

概述

  • 属性动画是在Android 3.0的(API级别11)引入的。
  • 属性动画系统可以制作动画的任何对象的属性,它允许你动画几乎所有的东西。例如一个对象在屏幕中的位置,要动画多久,和动画之间的距值。
  • 属性动画系统是首选的动画使用方法,因为它更灵活,并提供更多功能(特性)。

偷一张图,出处:http://www.runoob.com/w3cnote/android-tutorial-valueanimator.html

顾名思义,属性动画操作若干属性,通常我们要操作的属性为:

  • rotationX、rotationY——旋转
  • scaleX、scaleY——缩放
  • translationX、translationY——平移
  • X、Y——坐标
  • alpha——透明度

属性动画又可以分为两种:

  1. ObjectAnimator
  2. ValueAnimator

ObjectAnimator

引言

ObjectAnimator继承自ValueAnimator,所以ValueAnimator的方法在ObjectAnimator都能用,可以考虑跳过这部分先看下面的ValueAnimator。前文中提到的“属性动画系统可以制作动画的任何对象的属性,它允许你动画几乎所有的东西”,实际上应该是描述ObjectAnimator才对,因为ValueAnimator主要操作“值的过渡”,而后者更接近本质。


旋转动画案例

  • 布局,添加一个Imageview,并添加点击事件,保险起见设置了clickable="true"
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/timg"
        android:onClick="click"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:clickable="true"/>
    public void click(View v) {
        //第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
        ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
        //rotation x 0.0->360旋转三百六十度
}
  • 效果如? 


组合多个动画

  • 主要通过PropertyValuesHolder来添加独立动画,然后通过ofPropertyValuesHolder进行调用,步骤非常简单。
  • 完整代码如?,布局文件还是和?一样 
package com.example.a4_22animations;

import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

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

    public void click(View v) {
        //第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
        //ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
        //rotation x 0.0->360旋转三百六十度


        //组合多个动画 相当于缩放效果
        PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
        PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
        PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
        ObjectAnimator.ofPropertyValuesHolder(v, p1, p2, p3).setDuration(3000).start();

    }
}
  • 效果如?


ValueAnimator

步骤

  1. 调用ValueAnimator的ofInt(),ofFloat()或ofObject()静态方法创建ValueAnimator实例
  2. 调用实例的setXxx方法设置动画持续时间,插值方式,重复次数等
  3. 调用实例的addUpdateListener添加AnimatorUpdateListener监听器,在该监听器中 可以获得ValueAnimator计算出来的值,你可以值应用到指定对象上~
  4. 调用实例的start()方法开启动画! 另外我们可以看到ofInt和ofFloat都有个这样的参数:float/int... values代表可以多个值!

(?出处也是最开始偷的图片的出处  orz)

自由落体动画案例

  • 布局还是没改,接着在原来点击事件代码上写。 (去掉v.getY()参数将不会再返回)
    public void click(View v) {

        /**************************ObjectAnimator**************************/
        //第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
        //ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
        //rotation x 0.0->360旋转三百六十度

        //组合多个动画 相当于缩放效果
        /*
        PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
        PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
        PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
        ObjectAnimator.ofPropertyValuesHolder(v, p1, p2, p3).setDuration(3000).start();
        */

        /**************************ValueAnimator**************************/
        //自由落体实例
        final View view = v;
        //拿到屏幕高度
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        //定义一个动画  ofFloat返回一个ValueAnimator
        ValueAnimator va = ValueAnimator.ofFloat(v.getY(), dm.heightPixels, v.getY()).setDuration(500);
        //监听动画的每个动作
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                view.setTranslationY((Float) animation.getAnimatedValue());
            }
        });
        va.start();
    }
  • 效果如?


监听动画事件

  • 实际上在?的案例已经用了一个监听,相当于监听图片运动情况,然后调用方法。
  • 而这里是监听动画事件主要指的是监听动画的状态(比方说动画结束)
    public void click(View v) {
        final View view = v;
        //监听动画事件
        ObjectAnimator oa = ObjectAnimator.ofFloat(v, "alpha", 1f, 0f).setDuration(1000);
        oa.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                //动画开始时调用
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //动画结束时调用
                ViewGroup viewGroup = (ViewGroup) view.getParent();
                if (viewGroup != null) {
                    viewGroup.removeView(view);
                    System.out.println("图片已删除");
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                //取消动画时调用
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                //重复动画时调用
            }

        });
        oa.start();
    }
  • 效果如下?


  • ?面的方法不免产生一些冗余代码,所以Android其实提供了更加简洁的办法?
  • addListener参数可以直接new一个AnimatorListenerAdapter,其他完全不变,但是可以选择自己需要的方法仅需重写,而不必全部都重写
    public void click(View v) {
        final View view = v;
        //监听动画事件,图片移除,写法2
        ObjectAnimator oa = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).setDuration(1000);
        oa.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //动画结束
                ViewGroup viewGroup = (ViewGroup) view.getParent();
                if (viewGroup != null) {
                    viewGroup.removeView(view);
                    System.out.println("图片已删除");
                }
            }
        });
        oa.start();
    }

AnimatorSet

引言

之前介绍了一种组合动画的方式——PropertyValuesHolder。实际上通过AnimatorSet也能实现组合动画的效果。它的定制性更强,PropertyValuesHolder组合的动画基本上就一起播放了,而AnimatorSetw完全可以定制组合动画中每个独立动画的播放次序。

案例

    public void click(View v) {
        ObjectAnimator a1 = ObjectAnimator.ofFloat(v, "translationX", 0f, 200f);
        ObjectAnimator a2 = ObjectAnimator.ofFloat(v, "translationY", 0f, 200f);
        ObjectAnimator a3 = ObjectAnimator.ofFloat(v, "rotation", 0f, 360f);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(1000);
        set.playTogether(a1, a2, a3);//三个动画同时执行
        // set.setStartDelay(300);//延迟执行
        // set.playSequentially(a1,a2,a3);//顺序执行

        //自定义顺序写法参考,先a1和a2一起执行,最后a3
        // set.play(a1).with(a2);
        // set.play(a3).after(a2);

        set.start();

    }
  • 效果如? 

注意:
animSet.play().with();

虽然支持链式编程,但是:

animSet.play(a1).with(a2).before(a3).before(a5);

?这样是不行的,系统不会根据你写的这一长串来决定先后。

 


Interpolators

发现一篇博文,可以参考下

https://blog.51cto.com/androidigging/1427128

即插值器,简单理解就是提供在动画中进行的插补操作,在本文提到的几种动画类型中皆可适用。

例如:

set.setInterpolator(new BounceInterpolator());

 

下面是支持的lnterpolator

  • AccelerateDeceleratelnterpolator:插补, 其变化率慢慢开始和结束,但通过中间加速。
  • Accelerateinterpolator:插补, 其变化率开始缓慢,然后加快。
  • Anticipatelnterpolator:内插的变化 开始落后,然后向前甩。
  • AnticipateOvershootinterpolator:内插的变化, 开始落后,甩向前过冲目标值,然后终于可以追溯到最终值。
  • Bouncelnterpolator:插补, 其变化在最后反弹。
  • Cyclelnterpolator:内插动画 重复指定的周期数。
  • Decelerateinterpolator:插补, 其变化的速度开始很快,然后减速。
  • Linearlnterpolator:插补, 其变化率是恒定的
  • Overshootinterpolator:内插的变化甩向前和过冲的最后一个值,然后回来。
  • Timelnterpolator:一个接口,使您实现自己的插补。
     

用XML文件创建属性动画

  • 在res资源文件下创建animator资源文件夹

  • 然后就可以创建动画文件了,例如建一个test.xml,插值器也是可以用的。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator android:duration="3000"
        android:propertyName="alpha"
        android:valueFrom="1.0"
        android:valueTo="0.0"/>
</set>
  • 最后使用也非常简单
    public void click(View v) {
        //使用XML定义的动画
        Animator a = AnimatorInflater.loadAnimator(this, R.animator.test);
        a.setTarget(v);
        a.start();
    }
  • 效果

  • XML文件里进行多个动画组合也是可以的,只需要修改XML文件

  • 加载(设置了一下相对位置)


动画菜单

新建一个MenuActivity,修改其布局文件,考虑到菜单选项一开始应该是摞在一起的,所以选择FrameLayout

然后素材自备...

布局如?

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MenuActivity">
    <ImageView
        android:id="@+id/imageView8"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img8"
        android:tag="imageView8"/>
    <ImageView
        android:id="@+id/imageView7"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img7"
        android:tag="imageView7"/>
    <ImageView
        android:id="@+id/imageView6"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img6"
        android:tag="imageView6"/>
    <ImageView
        android:id="@+id/imageView5"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img5"
        android:tag="imageView5"/>
    <ImageView
        android:id="@+id/imageView4"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img4"
        android:tag="imageView4"/>
    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img3"
        android:tag="imageView3"/>
    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img2"
        android:tag="imageView2" />
    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|top"
        android:src="@mipmap/img1"
        android:tag="imageView1"/>
</FrameLayout>

完整代码如?

package com.example.a4_22animations;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.ArrayList;

//监听事件
public class MenuActivity extends AppCompatActivity implements View.OnClickListener {
    private int[] res = {
            R.id.imageView1,
            R.id.imageView2,
            R.id.imageView3,
            R.id.imageView4,
            R.id.imageView5,
            R.id.imageView6,
            R.id.imageView7,
            R.id.imageView8
    };
    private ArrayList<ImageView> list = new ArrayList<>();
    //菜单展开状态标志
    private boolean isOpen = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);
        //遍历图片资源数组
        for (int i = 0; i < res.length; i++) {
            ImageView imageView = findViewById(res[i]);
            imageView.setOnClickListener(this);
            //添加到list里
            list.add(imageView);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imageView1:
                if (isOpen) {
                    closeMenu();
                    isOpen = false;
                } else {
                    openMenu();
                    isOpen = true;
                }
                break;

            default:
                //点击其他的就显示tag
                Toast.makeText(this, v.getTag().toString(), Toast.LENGTH_SHORT).show();
                break;
        }
    }

    //展开菜单
    private void openMenu() {
        for (int i = 1; i < res.length; i++) {
            ObjectAnimator a1 = ObjectAnimator.ofFloat(list.get(i), "translationX", 0, 100 * i);
            a1.setInterpolator(new BounceInterpolator());
            ObjectAnimator a2 = ObjectAnimator.ofFloat(list.get(i), "translationY", 0, 100 * i);
            a2.setInterpolator(new BounceInterpolator());
            AnimatorSet set = new AnimatorSet();
            set.setDuration(300);
            set.playTogether(a1, a2);
            set.start();

        }
    }

    //收回菜单
    private void closeMenu() {
        for (int i = 1; i < res.length; i++) {

            ObjectAnimator a1 = ObjectAnimator.ofFloat(list.get(i), "translationX", 100 * i, 0);
            a1.setInterpolator(new BounceInterpolator());
            ObjectAnimator a2 = ObjectAnimator.ofFloat(list.get(i), "translationY", 100 * i, 0);
            a2.setInterpolator(new BounceInterpolator());
            AnimatorSet set = new AnimatorSet();
            set.setDuration(300);
            set.playTogether(a1, a2);
            set.start();

        }
    }


}

测试效果如?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云无心鸟知还

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值