本篇博客记录一下Android属性动画的基本用法。
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();
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();
当需要同时调整多个属性时,可以使用PropertyValuesHolder,例如:
................
//沿着x轴缩放,缩放比例从start到end
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("scaleX", start, end);
//沿着y轴缩放,缩放比例从start到end
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();
..............
当需要多个属性先后变化时,就可以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;
}
.................
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中启动下一个动画即可。
假设现在有个需求,需要点击屏幕后,逆向播放已经放过的动画。
即一个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;
}
.........
以上就是属性动画的一些基本用法,以后遇到新的知识再作进一步补充。