Android动画实践

市面上的大多数应用,多多少少都会通过动画,让应用多一些灵动性和趣味性,并且在视图之间的切换会显得更加自然。例如许多应用都定制了自己的下拉刷新中的动画,让应用增色不少。Android动画有三种:

  • View 动画
  • 属性动画
  • 帧动画

View动画####

View动画是Android里面常用的动画方式。View动画直接作用于View上面。View动画有且只有四种:平移、缩放、旋转、透明度。平移就是左右上下位置移动,缩放就是大小的变换,旋转就是视图的2D旋转,透明度就不用多说了,由于View动画采用xml方式实现较多,所以这里我们着重介绍下xml方式下的View动画标签。

动画 标签
平移动画 TranslateAnimation
缩放动画 ScaleAnimation
旋转动画 RotateAnimation
透明度动画 AlphaAnimation
动画集合 AnimationSet
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <!--
		fromAlpha 起始透明度 
		toAlpha 结束透明度
		1.0完全不透明 0.0完全透明
	 -->
    <alpha
        android:fromAlpha="float" 
        android:toAlpha="float" />   
    <!--
		fromXScale X坐标缩放起始值
		toXScale X坐标缩放结束值
		fromYScale Y坐标缩放起始值 
		toYScale Y坐标结束值
		1.0则表示原大小
		privotX&privotY 缩放中心点,有三种模式 %,%p,固定值,其中%是相对于本身的一个比例,
		如果privotX&privotY都是50%,那么就是视图中心点,%p则是相对于父容器的一个比例。
	 -->
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
   <!--
		fromXDelta X轴相对于当前位置的起始值
		toXDelta X轴相对于当前位置的结束值
		fromYDelta Y轴相对于当前位置的起始值 
		toYDelta  Y轴相对于当前位置的结束值%p则是相对于父容器长宽的一个比例。
	 -->
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
      <!--
		fromDegrees 起始角度
		toDegrees  结束角度
		pivotX&pivotY 旋转中心点,有三种模式 %,%p,固定值,其中%是相对于本身的一个比例,
		如果privotX&privotY都是50%,那么就是视图中心点,%p则是相对于父容器的一个比例。
	 -->
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>
插值器#####

我们可以看到这里有一个 android:interpolator 属性。这个在动画中十分重要,interpolator 插值器,控制动画是以一定的速度播放还是先快后慢等动画播放效果。android内置了丰富的插值器,不同的插值器对整个动画过程会产生不同的影响,默认的插值器是常量速度插值器,我们可以通过插值器让动画执行过程由慢到快的变换。下面是Android系统提供的部分常见插值器:

名称 效果
AccelerateInterpolator 动画由慢向快加速
DecelerateInterpolator 动画由快到慢减速
AccelerateDecelerateInterpolator 动画中间快两端慢
AnticipateInterpolator 动画反效果后再按照效果执行,例如动画是向右平移100px,那么会先向左平移一段事件再向右平移到终点
BounceInterpolator 动画结束的时候会有一个弹跳的效果,自行脑补弹珠在地上弹起的效果
LinearInterpolator 常量速率执行动画,默认
OvershootInterpolator 动画结束时候,会反效果执行一段时间,例如向右平移100px,向右平移到终点,会反方向平移一定距离
CycleInterpolator 动画按照一定形式循环播放一定的次数,例如向右平移100px,这时候会向右平移100px后,会反方向,向左平移100px,这样重复一定次数
AnticipateOvershootInterpolator 组合了AnticipateInterpolator和OvershootInterpolator的效果
View动画实现#####

View动画有两种实现方式,一种是硬编码,一种是通过XML文件,建议使用XML方式。如果采用XML文件的话,那么需要在/res文件夹下面新建anim文件夹,并且将XML动画文件放在文件夹下面。

XML文件形式######
<!--放置在res/anmi文件夹下面, 实现由小到大,并且从透明到可见的动画-->
<!-- res/anim/alpha_scale.xml -->
<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"/>
    <scale
        android:fromXScale="0"
        android:toXScale="1"
        android:fromYScale="0"
        android:toYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        />
</set>
//加载XML动画并且播放
public void loadAnimation(){
   //加载动画
   Animation animation = AnimationUtils.loadAnimation(this,R.anim.alpha_scale);
  //设定动画播放时长
  animation.setDuration(3000);
  //通过View.startAnimation(Animation)执行动画
  findViewById(R.id.iv).startAnimation(animation);
}
纯硬编码方式######
 //纯硬编码方式
 public void loadAnimation2(){
		 //动画效果由透明动画与缩放动画组合成,所以需要用AnimationSet将他们装起来
        AnimationSet animationSet = new AnimationSet(true);
        //创建透明度动画
        AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
        //创建缩放动画
        ScaleAnimation scaleAnimation = new ScaleAnimation(0f, 1f, 0f, 1f, 
         Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);
        animationSet.addAnimation(alphaAnimation);
        animationSet.addAnimation(scaleAnimation);
        animationSet.setDuration(3000);
		//通过View.startAnimation(Animation)播放动画
        findViewById(R.id.iv).startAnimation(animationSet);
    }
View动画常用方法#####:
方法 描述
AnimationUtils.loadAnimation(int) 加载xml动画
Animation.setDuration(long) 设置动画播放时间
Animation.setRepeatCount(int) 设置动画重复次数
Animation.setFillAfter(boolean) 设置动画播放后是否停留在当前位置
Animation.start() 播放动画
Animation.cancel() 停止动画
View.startAnimation(Animation) 播放动画

需要特别注意,View动画的交互事件只在动画前的位置有效。例如我向右平移了100px,并停留在该位置,但是在该位置无法触发触摸等事件,因为View动画的交互事件只在原来的位置有效!


属性动画####

属性动画是Android 从API 11(Android3.0)开始支持的动画类型。虽然官方只支持API11以上(包括),但是我们依旧可以使用NineOldAndroids在API11下面采用属性动画的用法(部分动画效果实质还是View动画)。属性动画十分强大,几乎可以在任何对象上产生动画效果。属性动画的原理是:在一定的动画时长内,在不同的时间点(帧)下,不断改变对象的属性值,从而产生动画效果。属性动画由三部分构成:动画控制器、插值器、估值器。

属性动画控制器#####

属性动画控制器,控制着动画的播放&停止,动画的时间等等,并提供一系列的动画回调。控制器是我们使用属性动画的入口。我们经常使用的是ObjectAnimator来实现动画效果,如果需要更加灵活的控制动画,那么可以通过ValueAnimator实现。

标签 描述
ValueAnimator 属性动画的基础类,本身并不实现任何动画效果,主要充当属性动画的控制器,控制播放、暂停、插值器的应用、时长设置、属性动画的回调方法等,如果我们需要通过它实现动画效果的话,那么需要配合回调方法手动更新对象的属性值,下面将会给出例子
ObjectAnimator 继承于ValueAnimator,实现属性动画的常用类。需要动画目标对象提供Setter封装方法。ObjectAnimator的实现原理是:当ValueAnimator根据插值器和估值器计算出下一个时间点需要的属性值之后,ObjectAnimator会通过Java的反射机制,自动调用设置对象属性的方法,以此达到动画的效果。
AnimatorSet 属性动画集,将多个属性动画聚集起来,可以根据需求控制动画的播放顺序
属性动画插值器#####

这里的插值器与上面View动画的插值器是一致的,属性动画的原理是不停的改变属性值,来实现动画效果,这个属性值的计算是由插值器与估值器共同计算出来的,而插值器是主要的影响因素。例如上面有一AccelerateInterpolator(动画由慢到快)插值器,插值的增长速率会慢慢变快。以此才能达到由慢到快的动画效果。下图是AccelerateInterpolator插值器每两个时间点的差值。
AccelerateInterpolator插值器
可以很清晰的看到,越往下,每两个时间点的差值越大,也就是插值随着时间加速增长,因此,才能让动画由慢到快。不同的插值器,在动画开始到结束过程中,产生的插值数值也不一样,需要注意的是,这里的插值数值是随着动画时间变化的一个比例值,不是具体应该被设置的属性值。

属性动画估值器#####

上面我们提到动画播放过程中的属性值由插值器和估值器共同计算,由于插值器只得出了随着时间变化而改变的一个插值比例值,所以我们还需要通过动画估值器来计算出该时间点最终的属性值。Android系统提供的默认估值器有:

估值器 描述
IntEvaluator 当作用属性值是Int类型时候,可以使用该估值器计算最终属性值
FloatEvaluator 当作用属性值是Float类型时候,可以使用该估值器计算最终属性值
ArgbEvaluator 当作用属性值是颜色类型时候,可以使用该估值器计算最终属性值
TypeEvaluator 如果当作用的属性值系统并未提供相应的估值器,那么就需要通过TypeEvaluator自定义估值器
ObjectAnimator 属性动画实例#####

我们比较常用ObjectAnimator实现属性动画,使用ObjectAnimator的时候,有一个前提条件,那就是需要目标对象提供作用属性的Setter封装方法。下面是Android 11(Android 3.0)之后。我们常用且View基类中提供了Setter方法的属性。

属性名称 描述
alpha 透明度
scaleX X轴缩放值
scaleY Y轴缩放值
rotation 平面旋转值
rotationX X轴3D旋转
rotationY Y轴3D旋转
pivotX 旋转/缩放中心X轴坐标
pivotY 旋转/缩放中心Y轴坐标
x X坐标
y Y坐标
translationX X轴 平移偏移量
translationY Y轴 平移偏移量
scrollX X轴 滚动偏移量
scrollX Y轴 滚动偏移量

利用ObjectAnimator实现上面View动画从0到1透明并且逐渐放大的效果:

  private void playObjectPropertyAnimation() {
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        final AnimatorSet animatorSet = new AnimatorSet();
        //作用在ImageView的alpha、scaleX、scaleY属性上
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(iv, "alpha", 0, 1);
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(iv, "scaleX", 0, 1);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(iv, "scaleY", 0, 1);
        //这里同一时间播放上面三个动画
        animatorSet.playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator);
        animatorSet.setDuration(3000);
        animatorSet.start();
   }

如果目标对象的作用属性没有提供Setter方法,那么我们需要创建包装类,提供相应的Setter方法。我们如果要将动画作用于宽度&高度上,但是View对象并没有提供宽度&高度的Setter方法,那么我们需要创建相应的包装类。

//View宽度包装类
public class ViewWidthWrapper {

    private View v;
    public ViewWidthWrapper(View v){
        this.v = v;
    }
	//宽度Setter方法
    public void setWidth(int width){
        ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
        layoutParams.width = width;
        v.setLayoutParams(layoutParams);
        v.invalidate();
    }
    public int getWidth(){
        return v.getWidth();
    }
}
//播放宽度属性
 private void playCustomPropertyAnimation() {
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        //创建View宽度包装类
        final ViewWidthWrapper widthWrapper = new ViewWidthWrapper(iv);
        iv.post(new Runnable() {
            @Override
            public void run() {
		        //播放宽度属性动画
                final ObjectAnimator widthAnimator = ObjectAnimator.ofInt(widthWrapper,"width",widthWrapper.getWidth(),widthWrapper.getWidth()+100);
                widthAnimator.setDuration(3000);
                widthAnimator.start();
            }
        });

    }
ValueAnimator 属性动画实例#####

我们也可以利用ValueAnimator更加灵活的实现动画,但是相对于ObjectAnimator会相对复杂一点。我们需要联合ValueAnimator.AnimatorUpdateListener回调来实现属性动画。下面是用ValueAnimator实现上面ObjectAnimator实现的动画效果。

public void loadValueAnimation(){
        final ImageView iv =  (ImageView)findViewById(R.id.iv);
        //估值器
        final FloatEvaluator floatEvaluator = new FloatEvaluator();
        //透明度动画
        ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0,1);
        alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //这里我们需要自己计算最终属性值
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                //自己更新作用属性值
                iv.setAlpha(value);
            }
        });
        //X轴缩放动画
        ValueAnimator scaleXAnimator = ValueAnimator.ofFloat(0,1);
        scaleXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                iv.setScaleX(value);
            }
        });
        //Y轴缩放值
        ValueAnimator scaleYAnimator = ValueAnimator.ofFloat(0,1);
        scaleXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                iv.setScaleY(value);
            }
        });
        final AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(alphaAnimator,scaleXAnimator,scaleYAnimator);
        animatorSet.setDuration(3000);
        iv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animatorSet.start();
            }
        });

    }

通过代码我们可以看到,ValueAnimator只是帮我们计算了插值并提供了一个插值变化的回调方法,我们需要自己手动在回调方法里面,根据估值器计算最终属性值,并且自己更新View的属性值,最终才能实现动画效果。相比较ObjectAnimator来说,实现同一个效果会相对麻烦,但是灵活性增高了。例如ValueAnimator不需要我们提供Setter方法。

属性动画常用方法#####
方法名称 描述
ObjectAnimator.ofXX 属性动画的主要创建方法,根据作用属性值来创建相应的属性动画,有ofInt,ofFloat等方法
ObjectAnimator.setDuration(int) 设置动画播放时长
ObjectAnimator.start() 播放动画
ObjectAnimator.cancel() 停止播放动画,会暂停在当前这一帧
ObjectAnimator.end() 停止播放动画集,直接跳到动画最后一帧
ObjectAnimator.setRepeatCount(int) 设置动画重复次数,ValueAnimator.INFINITE表示无限次数
ObjectAnimator.setRepeatMode(int) 设置动画重复模式,有两种,一种是ValueAnimator.RESTART[重新播放],一直是ValueAnimator.REVERSE[反序播放]
ObjectAnimator.setInterpolator(Interpolator) 设置插值器
ObjectAnimator.setEvaluator(TypeEvaluator) 设置估值器
AnimatorInflater.loadAnimator(Context,int) 加载Xml文件形式的属性动画
AnimatorSet.play(Animator) 将某个属性动画添加进入属性动画集,这个方法会返回一个Builder对象,通过这个Builder对象,我们可以简单控制它与其他属性动画的播放顺序,可以通过Builder.after(Animator),Builder.before(Animator),Builder.with(Animator)这三个方法来控制它与其他属性动画的播放顺序
AnimatorSet.playSequentially(Animator...) 按照添加的顺序播放属性动画集
AnimatorSet.playTogether(Animator...) 同一时间播放动画集
AnimatorSet.setInterpolator(Interpolator) 设置插值器
AnimatorSet.setDuration(int) 设置动画集的播放时长,动画集设置的时长会覆盖掉单个动画设置的时长
AnimatorSet.start() 播放动画集
AnimatorSet.cancel() 停止播放动画集,会停止在当前这一帧
AnimatorSet.end() 停止播放动画集,会跳到动画最后一帧

帧动画####

帧动画是直接由一副一副图片组成,并且按照一定的顺序和间隔进行播放,这就构成了帧动画。Android帧动画的实现十分简单,通过AnimationDrawable就可以很简单的实现帧动画:

<!--帧动画的XML文件,放置在res/drawable下面 -->
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
	<!--这里是一副一副帧画面,duration是每幅图停留的时间 -->
    <item android:drawable="@drawable/a1" android:duration="150"/>
    <item android:drawable="@drawable/a2" android:duration="150"/>
</animation-list>
  private  void playFrameAnimation(){
        ImageView iv = (ImageView) findViewById(R.id.iv);
        //这里跟我们设置其他图片资源是一样的
        iv.setImageResource(R.drawable.ani);
        //将ImageView图片资源转换成AnimationDrawable
        AnimationDrawable animationDrawable = (AnimationDrawable) iv.getDrawable();
        //播放帧动画
        animationDrawable.start();
    }

其他方式实现动画效果####

除了用以上几种Android提供的方式实现动画之外,还可以通过自定义View实现动画效果。
我们可以在onDraw()方法里面,不停调用invaildate()来产生动画效果,有一些动画效果就是通过这种方式实现的。例如下面就是通过自定义View方式粗糙的仿荔枝FM加载稍等提示框的效果。
动画效果

public class LizhiLoadingView extends View {

    private float lineOneStartY,lineOneEndY;
    private float lineSecondStartY,lineSecondEndY;
    private float lineThirdStartY,lineThirdEndY;
    private boolean onePlus;
    private Paint mPaint;

    public LizhiLoadingView(Context context) {
        super(context);
        initPaint();
    }

    public LizhiLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public LizhiLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }


    private void initPaint(){
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(20);
        mPaint.setStyle(Paint.Style.FILL);
        lineOneStartY = 0;
        lineOneEndY  = 100;
        lineSecondStartY = 10;
        lineSecondEndY = 80;
        lineThirdStartY = 0;
        lineThirdEndY = 100;
        onePlus = true;


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       canvas.drawLine(80,lineOneStartY,80,lineOneEndY,mPaint);
       canvas.drawLine(150,lineSecondStartY,150,lineSecondEndY,mPaint);
        canvas.drawLine(220,lineThirdStartY,220,lineThirdEndY,mPaint);
        //下面改变绘画参数
        if(onePlus){
            lineOneStartY++;
            lineThirdStartY++;
            lineSecondStartY --;
            lineOneEndY--;
            lineThirdEndY--;
            lineSecondEndY++;
        }else{
            lineOneStartY--;
            lineThirdStartY--;
            lineSecondStartY ++;
            lineOneEndY++;
            lineThirdEndY++;
            lineSecondEndY--;
        }
        if(lineOneStartY==25){
            onePlus = false;
        }else if(lineOneStartY ==0){
            onePlus = true;
        }
        //这里导致onDraw()被不停调用
        invalidate();


    }


}

posted @ 2017-12-15 11:10  Zhouztashin  阅读(200)  评论(0编辑  收藏  举报