属性动画


属性动画实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。属性动画机制已经不只是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。所以我们仍然可以将一个View进行移动或者缩放,但同时也可以对自定义View中的Point对象进行动画操作了。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去完成了。


  • ValueAnimator
    ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
    示例:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);  
anim.start()

setStartDelay()方法来设置动画延迟播放的时间
setRepeatCount()设置动画循环播放的次数
setRepeatMode()方法设置循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。


  • ObjectAnimator
    ObjectAnimator是经常接触到的类,它是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的。提供了ofInt、ofFloat、ofObject。
    示例:
float curTranslationX = textview.getTranslationX(); 
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX); 
animator.setDuration(5000); 
animator.start();

调用了TextView的getTranslationX()方法来获取到当前TextView的translationX的位置,然后ofFloat()方法的第二个参数传入"translationX",紧接着后面三个参数用于告诉系统TextView应该怎么移动。ofFloat()方法的第二个参数可以是

  • alpha:表示View对象的透明度
  • rotation、rotationX、rotationY:控制View对象围绕支点进行2D和3D旋转
  • translationX、translationY:作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。
  • x、y:描述了View对象在它的容器中的最终位置,是最初的左上角坐标和translationX和translationY值得累加
  • scaleY、scaleX:控制View对象围绕它的支点进行2D缩放
    ObjectAnimator内部的工作机制并不是直接对传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,这两个方法是由View对象提供的,因此alpha属性所对应的get和set方法应该就是:
public void setAlpha(float value);  
public float getAlpha();

  • 组合动画
    实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
    • after(Animator anim) 将现有动画插入到传入的动画之后执行
    • after(long delay) 将现有动画延迟指定毫秒后执行
    • before(Animator anim) 将现有动画插入到传入的动画之前执行
    • with(Animator anim) 将现有动画和传入的动画同时执行

示例:
TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作。

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textviwe, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();

  • Animator监听器
    Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。
    ValueAnimator、ObjectAnimator、AnimatorSet都是继承自Animator的,都可以使用addListener()这个方法。
    添加一个监听器的代码如下:
anim.addListener(new AnimatorListener(){
    @Override
    public void onAnimationStart(Animator animator){ //在动画开始的时候调用
   
    }

     @Override
    public void onAnimationRepeat(Animator animator){ //动画重复执行的时候调用
    }

     @Override
    public void onAnimationEnd(Animator animator){ //在动画结束的时候调用
        //删除动画
        Log.e(TAG, "onAnimationEnd");  
        ViewGroup parent = (ViewGroup) mBlueBall.getParent();  
        if (parent != null)  
            parent.removeView(mBlueBall);                 
    }

       @Override
    public void onAnimationCancel(Animator animator){ //在动画被取消的时候调用
    }
});

有时并不用将这四个接口都实现,Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:

anim.addListener(new AnimatorListenerAdapter(){
});

向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。因此,如果想监听动画结束这个事件,就只需要单独重写这一个方法就可以了,如下所示:

anim.addListener(new AnimatorListenerAdapter(){
    @Override
    public void onAnimationEnd(Animator animator){ //在动画结束的时候调用
    }
});

  • 使用XML编写动画
    通过XML来编写动画在重用方面将会变得非常轻松,比如某个通用的动画编写到XML里面,可以在各个界面当中轻松去重用它。
    如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中我们一共可以使用如下三种标签:
<animator>  对应代码中的ValueAnimator
<objectAnimator>  对应代码中的ObjectAnimator
<set>  对应代码中的AnimatorSet

示例:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"  **
    android:valueTo="0"  **
    android:valueType="floatType"  **
    android:propertyName="alpha"/>**

使用XML来完成复杂的组合动画操作:

<set xmlns:android="http://schemas.android.com/apk/res/android"
        android:ordering="sequentially">
        <objectAnimator
            andorid:duration="2000"
            android:propertyName="translationX"
            android:valueFrom="-500"
            andorid:valueTo="0"
            android:valueType="floatType"
        />
        <set android:ordering="together"
            <objectAnimator
                andorid:duration="2000"
                android:propertyName="rotation"
                android:valueFrom="0"
                andorid:valueTo="360"
                android:valueType="floatType"
            />
        <set android:ordering="sequentially">
            <objectAnimator
                andorid:duration="2000"
                android:propertyName="alpha"
                android:valueFrom="1"
                andorid:valueTo="0"
                android:valueType="floatType"
            />
            <objectAnimator
                andorid:duration="2000"
                android:propertyName="alpha"
                android:valueFrom="0"
                andorid:valueTo="1"
                android:valueType="floatType"
            />
        </set>
    </set>
</set>

使用set标签,orderring属性设置为together,sequentially表示一个接一个执行
在代码中把文件加载进来并将动画启动:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file); 
animator.setTarget(view);  
animator.start();

调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用start()方法启动动画。

  • View的animate()方法
    animate()方法是属性动画的一种简写方式
view.animate()
            .alpha(0)
            .y(300)
            .setDuration(300)
            .withStartAction(new Runnable() {
                @Override
                 public void run(){
                 } 
            })
            .withEndAction(new Runnable() {
                  @Override
                  public void run(){
                      runOnUIThread(new Runnable(){
                            @Override
                            public void run(){
                            }
                      });      
                  }
            }).start();

  • ValueAnimator的高级用法
    如果有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。
    TypeEvaluator的作用就是告诉动画系统如何从初始值过度到结束值。ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,就是因为系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:
public class FloatEvaluator implements TypeEvaluator{
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction*(((Number) endValue).floatValue() - startFloat);
    }
}

loatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction用于表示动画的完成度的,根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。

  • 对象的动画操作
    因为系统完全无法知道如何从初始对象过度到结束对象,需要实现一个自己的TypeEvaluator来告知系统如何进行过度。
public class Point {
    private float x;
    private float y;

    public Point(float x, float y){
        this.x = x;
        this.y = y;
    }
    public float getX(){
        return x;
    }
    public float getY(){
        reutrn y;
    }
}

Point类只有x和y两个变量用于记录坐标的位置,接下来定义PointEvaluator:

public valss PointEvaluator implements TypeEvaluator{
    @Overrride
    public Object evaluate(float fraction, Object startValue, Object endValue){
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        float x = startPoint + fraction*(endPoint.getX() - startPoint.getX());
        float y = startPoint + fraction*(endPoint.getY() - startPoint.getY());
        Point point = new Point(x, y);
        reutrn point;
    }
}

PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate()方法。在evaluate()方法中的先将startValue和endValue强制转为Point对象,然后根据fraction来计算当前动画的x和y的值,然后封装到一个新的Point对象返回。
对Point对象进行动画操作:
新建一个MyAninView继承自View:

public class MyAnimView extends View{
    public static final float RADIUS = 50f;
    private Point currentPoint;
    private Paint mPaint;

    public MyAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//用于绘制时消除锯齿
        mPaint.setColor(Color.BULE);
    }

    @Override
    protected void OnDraw(Canvas canvas){
        if (currentPoint == null){
            currentPoint = new Point(RADIUS , RADIUS );
            drawCircle(canvas);
            startAnimation();
        } else{
            drawCircle(canvas);
        }
    }
    private void drawCircle(Canvas canvas){
        float x = currentPoint.getX();
        float y = currentPoint.getY();
        canvas.drawCircle(x, y, RADIUS, mPaint);
    }
    private void startAnimation() {
        Point startPoint = new Point(RADIUS, RADIUS);
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
        valueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator animation){
                currentPoint = (Point) animation.getAnimateValue();
                invalidate(); //请求重新draw(),但只会绘制调用者本身
            }
        });
        anim.setDuration(5000);
        anim.start();
    }
}

首先在自定义View的构造方法当中初始化了一个Paint对象作为画笔,并将画笔颜色设置为蓝色,接着在onDraw()方法当中进行绘制。这里我们绘制的逻辑是由currentPoint这个对象控制的,如果currentPoint对象不等于空,那么就调用drawCircle()方法在currentPoint的坐标位置画出一个半径为50的圆,如果currentPoint对象是空,那么就调用startAnimation()方法来启动动画。


  • 布局动画
    布局动画是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过度效果。
    最简单的布局动画是在IViewGroup的XML中,当ViewGroup添加View时,子View会呈现逐渐显示Android默认的过渡效果,:
android:animateLayoutChanges="true"

可以通过LayoutAnimationController类来自定义一个子View的过渡效果:

Linearlayout ll = (LinearLayout) findViewById(R.id.ll);
//设置过渡动画
ScaleAnimation sa  = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
//设置布局动画的显示属性
LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
//为ViewGroup设置布局动画
ll.setLayoutAnimation(lac);

通过以上代码,给LinearLayout增加了一个视图动画,让子View在出现的时候,有一个缩放的动画效果。
LayoutAnimationController构造函数的第一个参数是需要作用的动画,第二个参数是每个子View显示的delay时间。当delay不为0时,可以设置子View的顺序:

  • LayoutAnimationController.ORDER_NORMAL
  • LayoutAnimationController.ORDER_RANDOM 随机
  • LayoutAnimationController.ORDER_REVERSE 反序

  • Interpolator插值器
    这个方法主要是用来控制android动画的执行速率,可以使存在的动画效果:
  • AccelerateInterpolator ——在动画开始的地方速率改变比较慢,然后开始加速
  • AnticipateInterpolator ——开始的时候向后然后向前甩
  • AnticipateOvershootInterpolator ——开始的时候向后然后向前甩一定值后返回最后的值
  • BounceInterpolator ——开始时弹出,动画结束的时候弹起
  • CycleInterpolator ——动画循环播放特定的次数,速率改变沿着正弦曲线
  • DecelerateInterpolator—— 在动画开始的地方快然后慢
  • LinearInterpolator ——以常量速率改变
  • OvershootInterpolator ——向前甩一定值后再回到原来位置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容