本篇博客记录一下Android属性动画的基本用法。
Android提供的属性动画机制,可以很简单地调整视图的属性值,形成动画效果。例如:
.............//在3s时间内,将mSunView沿着y轴从start移动到end的位置//其实现的原理是,不断调用mSunView.setY接口,设置其纵坐标//纵坐标的值,逐渐从start变化到endObjectAnimator 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();.............当需要同时调整多个属性时,可以使用PRopertyValuesHolder,例如:
................//沿着x轴缩放,缩放比例从start到endPropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("scaleX", start, end);//沿着y轴缩放,缩放比例从start到endPropertyValuesHolder 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,具有同样的接口判断动画执行的状态,例如:
@Overridepublic 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;}.........以上就是属性动画的一些基本用法,以后遇到新的知识再作进一步补充。
P.S. : 最后补充一下,视图中各个属性的基本含义:
新闻热点
疑难解答