欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android动画机制及其使用

程序员文章站 2022-07-10 10:50:23
作为交互的一部分,开发Android应用的时候时常会用到动画,这样可以使应用看起来不那么死板。某些较为特定的点击可以使用有趣的动画引起注意,进而可以获取更多的点击量。随着And...

逐帧动画(Frame Animation)

逐帧动画也称之为Drawable Animation,是通过一系列的图片按顺序播放使其连成动画,这里每一帧都有对应的图片。例如我需要将名字为“droidman01”到“droidman16”的16张图片连成帧动画动画,那么实现方式如下
XML中:
将每一帧的图片放入res的drawable中然后再res的anim中(没有则新建一个Directory命名为anim)或是Android Studio的drawable中新建一个动画XML文件,使用标签来定义各个帧动图片的顺序,通过标签来排列。



    
    
...
imageView.setImageResource(R.drawable.frameanim);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();

这里的oneshot表示是否只执行一次,为false则执行一次就结束动画,为true当然就是循环播放。android:duration则是该帧图片播放的持续时间。
java代码中:

AnimationDrawable animationDrawable = new AnimationDrawable();
for (int i = 1;i<16;i++){
    int id;
    if (i<10){
        id = getResources().getIdentifier("droidman0"+i,"drawable",getPackageName());
    }else{
        id = getResources().getIdentifier("droidman"+i,"drawable",getPackageName());
    }
    Drawable drawable = getResources().getDrawable(id);
    animationDrawable.addFrame(drawable,150);
}
imageView.setImageDrawable(animationDrawable);
animationDrawable.setOneShot(false);
animationDrawable.start();

在这里会看到一个方法getIdentifier(),这个方法是用来获取资源id的。第一个参数是资源名,第二个参数是资源类型可以为null,第三个参数是包名,也可以为空。这个方法对于获取资源名称类似的很有用,很方便。这里通过addFrame方法传入每帧的图片,并设置播放时间,同样代码中可通过setOneShot方法设置是否循环播放,最后通过start()方法开始播放动画。除此之外AnimationDrawable还提供其他方法如:
stop():停止播放动画
isRunning():放回boolean类型表示动画是否正在播放中
run():继续从下一帧开始播放
getNumberOfFrames():获取总帧数
getFrame(int index):获取指定帧图片
getDuration(int i):获取指定帧播放时间
isOneShot():是否是一次播放
上面动画的效果:
Android动画机制及其使用

补间动画(Twwen Animation)

补间动画无需定义每一帧,只需定义开始和结束的关键两帧,中间的变化可设置插值器(Interpolator)来平滑过渡。补间动画提供了四个基本类型的动画:AlphaAnimation、RotateAnimation、TranslateAnimation和ScaleAnimation。其实这些动画使用起来相对不难,只要知道各个参数的含义即可。一下就一一说明。

AlphaAnimation

// 参数分别为开始和结束时的透明度,参数的取值区间[0,1]的float类型,即全透明到完全不透明。
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);

RotateAnimation

// 两参数分别为动画开始时的旋转角度和动画结束时的旋转角度,以点(0,0)为中心旋转
RotateAnimation rotateAnimation1 = new RotateAnimation(0,360);
// 旋转角度从0度到360度,旋转中心为点(100,100)
RotateAnimation rotateAnimation2 = new RotateAnimation(0,360,100,100);
// 旋转角度从0到360度。X轴方向上的旋转参考类型,相对参考类型的X坐标位置,Y轴方向上旋转的参考类型,相对参考类型的Y坐标位置,
// 此处参考类型有RotateAnimation.RELATIVE_TO_SELF自身,RotateAnimation.ABSOLUTE绝对位置,RotateAnimation.RELATIVE_TO_PARENT父控件,
// 如该写法为以自身的中心位置旋转
RotateAnimation rotateAnimation3 = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);

TranslateAnimation

// 参数分别为动画开始X坐标,动画结束X坐标,动画开始Y坐标,动画结束X坐标。即动画效果是从(0,0)平移到(200,300)位置
TranslateAnimation translateAnimation1 = new TranslateAnimation(0,200,0,300);
// 和旋转的一样,有着参考类型,该效果为从该控件的(0,0)开始到控件宽高的2倍位置
TranslateAnimation translateAnimation2 = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_SELF,0F,TranslateAnimation.RELATIVE_TO_SELF,0F,
    TranslateAnimation.RELATIVE_TO_SELF,2F,TranslateAnimation.RELATIVE_TO_SELF,2F);

ScaleAnimation

// 参数分别为动画开始时的X轴坐标伸缩尺寸,动画结束时X轴伸缩尺寸,动画开始时Y轴伸缩尺寸,动画结束时Y轴伸缩尺寸。其中尺寸数值为该空间原始大小的倍数。
// 该效果为以点(0,0)开始向外放大2倍
ScaleAnimation scaleAnimation1 = new ScaleAnimation(0f,2f,0f,2f);
// 前四个参数与上一样,后两个为缩放中心
ScaleAnimation scaleAnimation2 = new ScaleAnimation(0f,2f,0f,2f,100,200);
// 前四个参数与上一样,后四个为参考类型和参考相对位置,如此效果为以自身中心X,Y轴都伸展为原来的2倍
ScaleAnimation scaleAnimation3 = new ScaleAnimation(0f,2f,0f,2f,ScaleAnimation.RELATIVE_TO_SELF,0.5F,ScaleAnimation.RELATIVE_TO_SELF,0.5F);

使用时只需设置动画时间即可开启动画,以透明动画为例,其他用法类似

//动画持续时间
alphaAnimation.setDuration(1000);
//动画结束时保留状态
alphaAnimation.setFillAfter(true);
//设置插值器
alphaAnimation.setInterpolator(new LinearInterpolator();
//开启动画
imageView.startAnimation(alphaAnimation);

动画叠加AnimationSet

AnimationSet animationSet = new AnimationSet(true);
animationSet.setDuration(2000);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation1);
animationSet.addAnimation(translateAnimation1);
animationSet.addAnimation(scaleAnimation1);
imageView.startAnimation(animationSet);

通过AnimationSet的addAnimation方法可将各动画添加进来,然后设置时间即可。

动画监听

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 动画开始时
    }

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

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

我们常常会在动画执行的开始或者结束时做一些相应的操作,此时就会需要监听动画的执行过程。如一个动画之后执行另一个动画等。

属性动画(Property Animation)

Android 3.0之后引进了属性动画,相较于之前的动画,属性动画真实改变了View的属性,而之前的那两种动画则只是视图上的改变,其触发位置仍旧没有变化,因此属性动画更适合做一些需要交互较强的动画。

ObjectAnimator

属性动画常用到的一个类就是ObjectAnimator,它是属性动画中最重要的一个执行类,继承自属性动画中最重要的类ValueAnimator。ObjectAnimator使用起来也相对较方便,只要记得第二个参数所要执行的动画即可,因为这里所填的是第一个参数对象的属性,且这个属性必须有get、set方法。我们以一个简单的用法来说明

ObjectAnimator objectAnimator =ObjectAnimator.ofFloat(imageView,"translationX",-300,300);
objectAnimator.setDuration(2000);
// 设置播放次数。如果为ValueAnimator.INFINITE表示无限播放下去
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 设置播放重复模式。RESTART表示重新开始动画,REVERSE表示动画反过来播放。
objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
objectAnimator.start();

通过ObjectAnimator的工厂方法创建对象,其中第一个参数是要操作的View,第二个参数是要操作的属性,第三个参数是对应的属性变化,其是一个可变数组,即后面可继续添加参数,如这个效果就是将imageView控件在X轴方向的位移。第三个参数及其之后的参数为关键转折点。重点说下第二个参数,我们对View的动画操作常用的有(即第二个参数的值):
pivotX和pivotY:设置View的支点位置,默认为View的中心点
translationX和translationY:View的X轴和Y轴的偏移量,或者说位移距离。
rotation、rotationX和rotationY:View围绕支点,分别以Z轴、X轴和Y轴旋转,其中Z轴垂直屏幕。
scaleX和scaleY:View以支点做缩放。
x和y:直接设置View的位置,相当于原位置移动x和y距离。
alpha:设置View的透明度,1不透明,0全透明。
那么之前所说的set和get和这些值有什么关系呢?我们会有这些值,是因为内部通过java反射机制来调用set函数修改对象的属性值,我们打开源码ImageView继承View,而View中有一系列的函数,即方法。我们可以发现这些可以改变View属性的方法,如setTranslationX/getTranslationX、setRotationX/getRotationX等,这才使得属性动画能够找到这些方法并对其属性值做更改。

PropertyValuesHolder

PropertyValuesHolder使用起来和ObjectAnimator一样,只是它可以通过ObjectAnimator的ofPropertyValuesHolder方法将各个效果叠加起来,和AnimationSet类似。其使用方法如下,效果是在移动同时进行缩放操作。

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",200f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0f,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0f,1f);
ObjectAnimator.ofPropertyValuesHolder(imageView,pvh1,pvh2,pvh3).setDuration(2000).start();

ValueAnimator

ValueAnimator是属性动画中最重要的一个类,继承自Animator。它定义了属性动画中大部分的功能,如计算各帧的属性值、处理更新事件、根据属性值得类型知道操作的属性对象而做相应的计算规则的控制。也就是说ValueAnimator其实本身不负责动画的执行,更重要的是动画执行过程中数据的获取。可以根据需要设置动画持续的时间、插值方式、重复次数等,然后启动动画。使用时一般需要注册AnimatorUpdateListener监听器。然后再监听器中获取实时数值,之后将得到的数值设置到需要的地方。用法大致如下:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,100);
valueAnimator.setTarget(imageView);
valueAnimator.setDuration(2000);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Float value = (Float) animation.getAnimatedValue();
        // TODO 使用获取来的值
    }
});

AnimatorSet

视图动画有同感AnimationSet将各个动画叠加,属性动画当然也有,那就是AnimatorSet,相较于之前的PropertyValuesHolder,AnimatorSet可以更好地控制各个动画的顺序,如用AnimatorSet实现如上面PropertyValuesHolder一样的动画效果

ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView,"translationX",200f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView,"scaleX",1f,0f,1f);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView,"scaleY",1f,0f,1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3);
animatorSet.start();

在此AnimatorSet还提供不同的方法来调整各个属性动画的顺序。如:

// 三个动画同时播放
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3);
// 三个动画按顺序播放
animatorSet.playSequentially(objectAnimator1,objectAnimator2,objectAnimator3);
// objectAnimator1动画与objectAnimator2动画同时播放
animatorSet.play(objectAnimator1).with(objectAnimator2);
// objectAnimator1动画在objectAnimator2动画播放之前进行,即先播放1动画再播放2动画
animatorSet.play(objectAnimator1).before(objectAnimator2);
// objectAnimator1动画在objectAnimator2动画播放之后进行,即先播放2动画再播放1动画
animatorSet.play(objectAnimator1).after(objectAnimator2);

animate方法给View设置动画

在系统3.0之后,如果动画只执行一次,则可以考虑使用animate()方法来完成动画。即View调用animate()。其简单使用例子如下。

imageView.animate()
        .translationX(200f)
        .scaleX(2f).scaleY(2f)
        .setDuration(2000)
        .withStartAction(new Runnable() {
    @Override
    public void run() {
        // 动画开始
    }
}).withEndAction(new Runnable() {
    @Override
    public void run() {
        // 动画结束
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

            }
        });
    }
});

这里调用animate()方法实际是活的一个ViewPropertyAnimator对象,其实际也是属性动画。但这里的属性不是可变数组,且不能设置重复播放。因此用于简单动画叠加或者一次性播放的还是挺方便的。

动画事件的监听

在属性动画执行的过程中,如果需要在某个执行过程做其他操作,则需添加监听事件

objectAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        // 动画开始
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        // 动画结束
    }

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

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

如果只想单独监听某个事件则注册监听AnimatorListenerAdapter即可选择需要的事件对其进行监听。

objectAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationCancel(Animator animation) {
        super.onAnimationCancel(animation);
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        super.onAnimationRepeat(animation);
    }

    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
    }

    @Override
    public void onAnimationPause(Animator animation) {
        super.onAnimationPause(animation);
    }

    @Override
    public void onAnimationResume(Animator animation) {
        super.onAnimationResume(animation);
    }
});

XML中定义属性动画

在res目录下创建anim或是animator文件夹,并新建一个根标签为objectAnimator的xml文件



在代码中使用

Animator animator = AnimatorInflater.loadAnimator(this,R.animator.objectanim);
animator.setTarget(imageView);
animator.start();