Android动画(二)-属性动画

我们在上一篇博客中,讨论了视图动画与帧动画。那么这节课则要讨论更复杂,更强大的Property animation(属性动画)。

视图动画使用简单,但是功能也简单。(只有那四种功能)。并且也不改变View的真实布局属性值。帧动画主要是依靠ui设计师切图。而android3.0之后,推出了属性动画,则是功能强大,可以能让我们自由发挥想象力。

Android3.0以下版本也有支持库 NineOldAndroids,不过其作者,大名鼎鼎的JakeWharton大神已经表示该库已经不再维护更新了。不过Android3.0的android机在市场上应该早就消失殆尽了。

在具体讨论之前,我们先来熟悉两个概念:插值器估值器

插值器和估值器

TimeInterpolator中文翻译为时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等;TypeEvaluator的中文翻译为类型估值算法,也叫作估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点型属性)和ArgbEvaluator(针对Color属性)。

光说枯燥无味的概念肯定是羞涩难懂。所以借用google官方给的实例来理解。

下图是一个匀速动画,采用线性插值器和整型估值算法。在40ms内,view的X属性从0到40变换。

插值器工作原理.png-4.4kB

动画默认刷新频率是10ms/帧,所以该动画要分为5帧来进行,我们来考虑第三帧,(也就是最中间一帧)。此时t=20ms,所以时间流逝的百分比是0.5(20/40)。也就是时间插值器得出的百分比是0.5。那么x等于多少呢。因为匀速,所以此时x=20,所以估值器计算得出的值就是20。

再说一个更加通俗易懂的例子。要求运动员60分钟跑完一万米,他可以采用各种不同的跑步策略,比如匀速跑,也可以先慢慢跑节省体力,然后最后阶段再加速冲刺, 或者先快跑后慢跑,总之,他怎么跑都行,只要最后能完成目标,在第60分钟时跑到一万米的终点就好了。在该过程的某个节点上,时间插值器的作用是估算运动员已使用的时间占总时间(就是60分钟)的百分比,而估值器则是估算出该运动员目前跑了多少米。

TimeInterpolatorTypeEvaluator都是interface,所以也可以直接继承它们,定义符合我们特殊需求的插值器和估值器。这在很多模拟自然物理特性的动画中要用到。比如动画模拟一个小球下落的轨迹,这是一个重力加速过程,肯定不是匀速动画。

Property Animation

属性动画,顾名思义,就是对对象的属性进行改变。(并不局限于View),默认帧率为10ms/帧,其实就是说:在指定的时间间隔内(时间插值器决定),将该对象的属性值改变多少(估值器决定)。比如,可以将对象的透明度在1s之内从0变化为1.这就是一个改变属性值的过程。并且是在每间隔10ms,重新调用setAlpha方法重新进行绘制。但其实这不就是 AlphaAnimation做的事情嘛。所以说属性动画更强大,并且可以替代视图动画。(当然视图动画简单易用,这是优点)。

既然说白了,属性动画就是通过每间隔10ms,就调用View的属性值设置方法进行改变的过程,那么常用的View属性有哪些呢。

  • translationXtranslationY:控制View的位置,值是相对于View容器左上角坐标的偏移。
  • rotation,rotationXrotationY:控制View对象围绕支点进行旋转。
  • scaleX,scaleY:围绕支点进行2D缩放。
  • pivotX, pivotY:这两个属性控制着View对象的支点位置,旋转缩放等操作的中心点就是该支点,默认该支点位置就是View对象的中心点。
  • xy:控制View在容器中的位置,即左上角坐标加上translationX和translationY的值。
  • alpha:控制View对象的alpha透明度值。默认1(不透明),0代表完全透明。

关于属性动画,我们最常用的类型有以下几种,ValueAnimator,ObjectAnimatorAnimatorSet
在xml中定义属性动画,文件路径如下:res/animator/fileName.xml,具体定义形式如下:

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

set相关属性:

xml属性 解释
android:ordering 控制子动画启动方式是先后有序的还是同时进行。sequentially:动画按照先后顺序;together(默认):动画同时启动;

objectAnimatoranimator 相关属性:

xml属性 解释
android:propertyName String类型,必须要设置的节点属性,代表要执行动画的属性(通过名字引用),辟如你可以指定了一个View的”alpha” 或者 “backgroundColor” ,这个objectAnimator元素没有对外说明target属性,所以你不能在XML中设置执行这个动画,必须通过调用loadAnimator()方法加载你的XML动画资源,然后调用setTarget()应用到具备这个属性的目标对象上(譬如TextView)。
android:valueTo float、int或者color类型,必须要设置的节点属性,表明动画结束的点;如果是颜色的话,由6位十六进制的数字表示。
android:valueFrom 相对应valueTo,动画的起始点,如果没有指定,系统会通过属性的get方法获取,颜色也是6位十六进制的数字表示。
android:duration 动画的时长,int类型,以毫秒为单位,默认为300毫秒。
android:startOffset 动画延迟的时间,从调用start方法后开始计算,int型,毫秒为单位。
android:repeatCount 一个动画的重复次数,int型,”-1“表示无限循环,”1“表示动画在第一次执行完成后重复执行一次,也就是两次,默认为0,不重复执行。
android:repeatMode 重复模式:int型,当一个动画执行完的时候应该如何处理。该值必须是正数或者是-1,“reverse”会使得按照动画向相反的方向执行,可实现类似钟摆效果。“repeat”会使得动画每次都从头开始循环。
android:valueType 关键参数,如果该value是一个颜色,那么就不需要指定,因为动画框架会自动的处理颜色值。有intType和floatType(默认)两种:分别说明动画值为int和float型。

而在代码中,我们这样来使用我们设置的xml文件。

//@author www.yaoxiaowen.com
//本文地址 :http://www.cnblogs.com/yaoxiaowen/p/7501202.html
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animtor.fileName);
set.setTarget(myObject);
set.start();

ObjectAnimator

ObjectAnimator extends ValueAnimator ,它允许指定某个对象以及该对象的一个属性。该类会根据计算得到的新值自动更新这个对象的属性。ObjectAnimator其实算是对ValueAnimator的包装,它使我们的处理过程变的简单,因为ValueAnimator还要自己手动去写更新逻辑。所以大部分情况下,我们使用ObjectAnimator就足够了。

使用方式:

//@author www.yaoxiaowen.com
//本文地址 :http://www.cnblogs.com/yaoxiaowen/p/7501202.html
AnimatorSet set = new AnimatorSet();
set.playTogether(
		ObjectAnimator.ofFloat(mView, "rotationX", 0, 360),
		ObjectAnimator.ofFloat(mView, "rotationY", 0, 180),
		ObjectAnimator.ofFloat(mView, "rotation", 0, 90),
		ObjectAnimator.ofFloat(mView, "translationX", 0, 90),
		ObjectAnimator.ofFloat(mView, "translationY", 0, 90),
		ObjectAnimator.ofFloat(mView, "scaleX", 1, 1.5f),
		ObjectAnimator.ofFloat(mView, "scaleY", 1, 1.5f),
		ObjectAnimator.ofFloat(mView, "alpha", 1, 1.5f, 1)
);
set.setDuration(5000).start();
  • ObjectAnimator 提供了 ofIntofFloatofObject这三个默认方法来设置动画作用的元素,属性,动画开始,结束以及中间的任意属性值。
  • 而对于 from和to一对属性值,如果只设置了一个值,则认为From的值就是该View的当前值,设置的值就是To终点值,当然如果设置了两个,则一个是From值,一个是To值。
  • 如果操作View的某种属性方法里面,比如setRotationX内部没有调用View的重绘。或者设置的的object不是View,那么就需要我们自己执行相关刷新操作了。(否则你使用了ObjectAnimator也没任何效果)。
anim.addUpdateListener(new AnimatorUpdateListener(){  
	@Override  
	public void onAnimationUpdate(ValueAnimator animation)  
	{  
//              view.postInvalidate();  
//              view.invalidate();  
//             或者 其他操作


	}  
}); 
  • 我们前面就强调过,属性动画其实实质上就是不断的调用 setXXX来更新元素的属性罢了。所以既然使用了 ObjectAnimator那么这个属性就必须有对应的set,get方法。可如果某些属性没有这个相应的set,get方法咋整呢(比如View元素的宽高),其实Android也提供了对应的解决方案。使用一个包装类进行包装,间接提供set,get方法就可以了。
private void performAnimate() {  
    ViewWrapper wrapper = new ViewWrapper(mButton);  
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();  
}  
  
//包装类
private static class ViewWrapper {  
    private View mTarget;  
  
    public ViewWrapper(View target) {  
        mTarget = target;  
    }  
  
    public int getWidth() {  
        return mTarget.getLayoutParams().width;  
    }  
  
    public void setWidth(int width) {  
        mTarget.getLayoutParams().width = width;  
        mTarget.requestLayout();  
    }  
}  

ValueAnimator

ValueAnimator其实才是属性动画的核心,它是比ObjectAnimator更加抽象的动画类,它本身不产生什么具体动画效果。但它设置了作用间隔等,进行相应的驱动。说的通俗点,它更像一个数值发生器,产生具有一定规律的数字,通过监听函数,从而让调用者自己去控制究竟实现什么功能。(所以它自然也不要求操作对用有set,get方法)。

它的常用使用方式如下:

ValueAnimator animator = ValueAnimator.ofFloat(0, 100);  
animator.setTarget(view);   //设置作用目标
animator.setDuration(5000).start();
animator.addUpdateListener(new AnimatorUpdateListener() {
	//每更新一帧,都会进行回调
    @Override
    public void onAnimationUpdate(ValueAnimator animation){
        float value = (float) animation.getAnimatedValue();
        // 做一些我们的自定义操作

//@author www.yaoxiaowen.com
//本文地址 :http://www.cnblogs.com/yaoxiaowen/p/7501202.html
    }
});

动画事件的监听

一个完整的动画具有Start,Repeat,End,Cancel四个过程,我们可以通过监听事件来监听对应的事件。

ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha",0.5f);
anim.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) {
	}
});

当然,四个监听方法虽全,但是太繁琐了,很多时候,我们只关心其中的某一个事件,所以Android也提供了对应的简化版本api。使用AnimatorListenerAdapter监听对象,AnimatorListenerAdapter implements Animator.AnimatorListener ,它是类而不是接口,我们可以自由选择实现一个或多个监听方法。

除此之外,还有很重要的 ValueAnimator.AnimatorUpdateListener接口,我们在前面也已经使用过,每帧发生变化时,都会调用该接口。

anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //一般此时需要进行强制类型转化
            Object obj = animation.getAnimatedValue();
        }
    });

PropertyValuesHolder

类似视图动画中的AnimationSet,针对同一个对象的多个属性,同时作用于多种动画.
使用方式如下:

PropertyValuesHolder a1 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);  
PropertyValuesHolder a2 = PropertyValuesHolder.ofFloat("translationY", 0, viewWidth);  
......
ObjectAnimator.ofPropertyValuesHolder(view, a1, a2, ......).setDuration(1000).start();

AnimatorSet

和前面的 PropertyValuesHolder类似,但是 AnimatorSet 除了有同时作用于多种动画的作用之外,它还能实现更为精准的顺序控制,比如同时播放,顺序播放,延迟播放等。

ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
......
AnimatorSet animSet = new AnimatorSet();  
animSet.setDuration(5000);  
animSet.setInterpolator(new LinearInterpolator());   
//animSet.playTogether(a1, a2, ...); //两个动画同时执行  
animSet.play(a1).after(a2); //先后执行
......//其他组合方式
animSet.start();  


//@author www.yaoxiaowen.com
//本文地址 :http://www.cnblogs.com/yaoxiaowen/p/7501202.html

其他问题。

结合自己写demo时的犯下的小错误,有以下几点要注意一下:

  • 属性动画类的拼写是Animator,不是视图动画的Animation,两者的继承关系也不一样,不要拼写错了。像那些具体的属性值之类的,也要注意不要拼写错误了。
  • 在具体设置某种属性的时候,要注意是 int 还是float类型。不要想当然,我写了个demo,设置 rotation,我觉的是int,试了几遍没反应,后来才发现是 float类型,记不清楚的去查看api文档。

作者: www.yaoxiaowen.com

博客地址: www.cnblogs.com/yaoxiaowen/

github: https://github.com/yaowen369

欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系

<p >
		 欢迎转载,转载请注明出处.谢谢
</p>


<script type="text/javascript">
 function    Curgo()   
 {   
     window.open(window.location.href);
 }   
</script>
posted @ 2017-09-10 15:52  eleven_yw  阅读(1151)  评论(0编辑  收藏  举报