Android属性动画的基本用法

本篇博客记录一下Android属性动画的基本用法。


1、ValueAnimator

ValueAnimator是属性动画中最重要的一个类,它继承自Animator类,
定义了属性动画中大部分的核心功能,包括计算各个帧的属性值、
处理更新事件、按照属性值的类型控制计算规则等。

一个完整的属性动画由两部分组成:
1、计算动画各个帧的相关属性值。
2、将这些属性值设置给指定的对象。

ValueAnimator为开发者实现了第一部分功能,
第二部分功能由开发者自行设置。

ValueAnimator的构造函数是空实现,一般都是使用类似如下的
静态工厂方法来进行实例化:

    .............
    public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }

    public static ValueAnimator ofArgb(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        anim.setEvaluator(ArgbEvaluator.getInstance());
        return anim;
    }
    .............

获取到ValueAnimator的实例后,需要设置动画的持续时间、插值方式、重复次数等属性值,
还需要为ValueAnimator注册AnimationUpdateListener监听器,并在这个监听器的onAnimationUpdate
方法中将计算出来的属性值设置给指定对象。
具体的使用方法类似于:

        final ImageView imageView = findViewById(R.id.test_view);

        ValueAnimator colorAnimation = ValueAnimator.ofObject(new FloatEvaluator(),
                1, 0.8f, 0.5f, 0);
        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                imageView.setAlpha((float)valueAnimator.getAnimatedValue());
            }
        });
        colorAnimation.setDuration(6000);
        colorAnimation.start();

2、ObjectAnimator:单属性变化

ObjectAnimator是ValueAnimator的子类,封装了将属性值设置给指定的对象的动作。
因此在实际开发中使用的最多的就是ObjectAnimator,它在构造实例时就会指定动画作用
的具体对象和对象的属性名,而且一般不需要注册AnimationUpdateListener监听器。

Android提供的属性动画机制,可以很简单地调整视图的属性值,形成动画效果。例如:

.............
//在3s时间内,将mSunView沿着y轴从start移动到end的位置
//其实现的原理是,不断调用mSunView.setY接口,设置其纵坐标
//纵坐标的值,逐渐从start变化到end
ObjectAnimator heightAnimator = ObjectAnimator
        .ofFloat(mSunView, "y", start, end)
        .setDuration(3000);

//也可以设置插值器,例如逐渐加速等
heightAnimator.setInterpolator(new AccelerateInterpolator());

//动画开始
heightAnimator.start();
............

ObjectAnimator将根据插值器的规则,将属性值从start逐渐变化到end。

不过有的属性值并不适合逐渐变化,例如颜色。
我们知道颜色是用类似于#fcfcb716这种16进制的数字表示的,如果逐渐增加数字,反而会带来剧烈的色彩变化。
此时ObjectAnimator需要借助TypeEvalutor的子类,精确地计算开始到结束间的递增指。例如:

.............
ObjectAnimator skyAnimator = ObjectAnimator
        .ofInt(mSkyView, "backgroundColor", start, end)
        .setDuration(duration);
//借助于ArgbEvaluator,精确调整色彩变化的递进值
skyAnimator.setEvaluator(new ArgbEvaluator());
skyAnimator.start();
.............

与补间动画类似,属性动画也可以在XML文件中定义,XML文件可放置在res/animator目录下。


<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together" >

    <objectAnimator android:duration="5000"
        android:propertyName="scaleX"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>

    <objectAnimator android:duration="5000"
        android:propertyName="scaleY"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
set>

在Java中使用XML定义的属性动画的代码类似于:

final ImageView imageView = findViewById(R.id.test_view);

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

3、PropertyValuesHolder:多属性同时变化

当需要同时调整多个属性时,可以使用PropertyValuesHolder,例如:

................
//沿着x轴缩放,缩放比例从startend
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("scaleX", start, end);

//沿着y轴缩放,缩放比例从startend
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleY", start, end);

//利用ofPropertyValuesHolder接口,可以传入View对象及多个PropertyValuesHolder
//当该动画开始时,mSunView多个的将同时沿着x、y轴缩放
ObjectAnimator.ofPropertyValuesHolder(mSunView, pvhX, pvhY)
        .setDuration(3000)
        .start();
..........

为了达到同样的效果,也可以使用AnimatorUpdateListener,例如:

...............
ObjectAnimator scaleAnimator = ObjectAnimator
        //使用一个不存在的属性,mSunView并不会发生实际的改变
        //但生成的值会逐渐从start变化到end
        .ofFloat(mSunView, "whatever", start, end)
        .setDuration(3000);

//增加AnimatorUpdateListener,生成的值变化时,就会回调onAnimationUpdate接口
scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //得到变化的值
        float val = (float)animation.getAnimatedValue();

        //调整属性
        mSunView.setScaleX(val);
        mSunView.setScaleY(val);
    }
});

scaleAnimator.start();
..............

4、AnimatorSet: 多属性先后变化

当需要多个属性先后变化时,就可以AnimatorSet了。
AnimatorSet就是可以放在一起执行的动画集,其使用方法类似于:

...................
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void startAnimation(boolean isSunSet) {
    //创建动画集
    mAnimatorSet = new AnimatorSet();

    //heightAnimator将和initialSkyAnimator、scaleAnimator、rotateAnimator同时播放
    mAnimatorSet.play(getHeightAnimator(isSunSet))
            .with(getInitialSkyAnimator(isSunSet))
            .with(getScaleAnimator(isSunSet))
            .with(getRotateAnimator())
            //先于laterSkyAnimator
            .before(getLaterSkyAnimator(isSunSet));

    mAnimatorSet.start();
}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private ObjectAnimator getHeightAnimator(boolean isSunset) {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight() + mSunView.getBottom() - mSunView.getTop();

    final float start = isSunset ? sunYStart : sunYEnd;
    final float end = isSunset ? sunYEnd : sunYStart;

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", start, end)
            .setDuration(3000);
    heightAnimator.setInterpolator(new AccelerateInterpolator());
    return heightAnimator;
}

private ObjectAnimator getInitialSkyAnimator(boolean isSunset) {
    int start = isSunset ? mBlueSkyColor : mNightSkyColor;
    int end = mSunsetSkyColor;

    return createSkyAnimator(start, end, 3000);
}

private ObjectAnimator getLaterSkyAnimator(boolean isSunset) {
    int start = mSunsetSkyColor;
    int end = isSunset ? mNightSkyColor : mBlueSkyColor;

    return createSkyAnimator(start, end, 1500);
}

private ObjectAnimator createSkyAnimator(int start, int end, int duration) {
    ObjectAnimator skyAnimator = ObjectAnimator
            .ofInt(mSkyView, "backgroundColor", start, end)
            .setDuration(duration);
    skyAnimator.setEvaluator(new ArgbEvaluator());

    return skyAnimator;
}

private ObjectAnimator getScaleAnimator(boolean isSunSet) {
    float start = (float) (isSunSet ? 1 : 1.5);
    float end = (float) (isSunSet ? 1.5 : 1);

    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("scaleX", start, end);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleY", start, end);
    return ObjectAnimator.ofPropertyValuesHolder(mSunView, pvhX, pvhY)
            .setDuration(3000);

    return scaleAnimator;
}

private ObjectAnimator getRotateAnimator() {
    ObjectAnimator objectAnimator = ObjectAnimator
            .ofFloat(mSunView, "rotation", 0, 360)
            .setDuration(1000);
    objectAnimator.setRepeatCount(4);
    return objectAnimator;
}
.................

5、状态判断和回调接口

AnimatorSet继承Animator,具有同样的接口判断动画执行的状态,例如:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_sunset, container, false);
    ..............
    v.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (Build.VERSION.SDK_INT >= 19) {
                //isStarted判断动画是否已经开始(动画暂停时,isStarted返回的也是true)
                if (mAnimatorSet == null || !mAnimatorSet.isStarted()) {
                    startAnimation();
                } else {
                    //isPaused判断动画是否暂停
                    if (mAnimatorSet.isPaused()) {
                        //resume继续播放动画
                        mAnimatorSet.resume();
                    } else {
                        //pause暂停播放动画
                        mAnimatorSet.pause();
                    }
                }
            }
        }
    });

    return v;
}

与上述状态变化接口对应,Animator及其子类均可以利用AnimatorListener监听动画的状态:

..............
mAnimatorSet.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) {
        ..............
    }
});

Animator运行到不同的状态时,将回调AnimatorListener相应的接口。
例如,利用AnimatorListener也可以做到一个动画结束时,启动下一个动画。
只要在AnimatorListener.onAnimationEnd中启动下一个动画即可。


6、逆向播放动画示例

假设现在有个需求,需要点击屏幕后,逆向播放已经放过的动画。
即一个View从高度A下降到B时,点击屏幕,View从B上升到A。

显然动画包含的属性越是复杂,完全逆向就越困难。
不过实现的思路大概是,保存初始和暂停的状态,然后构造逆向的ObjectAnimator,示例如下:

................
private long mCurrentPlayTime = 0;
private float mCurrentHeight = 0;

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private ObjectAnimator getHeightAnimator(boolean isSunset) {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight() + mSunView.getBottom() - mSunView.getTop();

    final float start = isSunset ? sunYStart : sunYEnd;
    final float end = isSunset ? sunYEnd : sunYStart;

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", start, end)
            .setDuration(3000);
    heightAnimator.setInterpolator(new AccelerateInterpolator());

    //利用AnimatorUpdateListener记录动画执行时的中间状态
    heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mCurrentPlayTime = animation.getCurrentPlayTime();
            mCurrentHeight = (float)animation.getAnimatedValue();
        }
    });

    heightAnimator.addPauseListener(new Animator.AnimatorPauseListener() {
        @Override
        public void onAnimationPause(Animator animation) {
            heightAnimator = ObjectAnimator
                //从当前位置变化到初始位置
                .ofFloat(mSunView, "y", mCurrentHeight, start)
                //执行时间为已经播放的时间
                .setDuration(mCurrentPlayTime);
            //逐渐减速
            heightAnimator.setInterpolator(new DecelerateInterpolator());

            //取消之前的动画
            //这里只是示范一下,如果整个AnimatorSet逆向
            //则因该在AnimatorSet的AnimatorPauseListener中重构整个AnimatorSet
            mAnimatorSet.cancel();

            //开始新的动画
            heightAnimator.start();
        }

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

    return heightAnimator;
}
.........

以上就是属性动画的一些基本用法,以后遇到新的知识再作进一步补充。

P.S. :
最后补充一下,视图中各个属性的基本含义:
Android属性动画的基本用法_第1张图片

Android属性动画的基本用法_第2张图片

Android属性动画的基本用法_第3张图片

你可能感兴趣的:(Android开发)