第三十二章 属性动画
有关视图的属性值
属性动画顾名思义就是依靠View的属性来实现的,因此我们要知道一个View组件有哪些属性。除了上一章的触摸事件的属性之外,还有布局属性和坐标体系,参考资料:
1.布局属性:
属性 | 作用 |
---|---|
alpha | 视图的透明度,值在0~1之间,0为完全透明,1为完全不透明 |
background | 视图背景(可以设置颜色,也可以设置图片) |
clickable | 视图是否可以点击 |
ID | 定义ID(便于查找) |
tag | 设置tag值(便于查找视图) |
isScrollContainer | 设置view为滚动效应 |
keepScreenOn | 视图在前台运行时是否保持亮屏 |
paddingBottom | 下边距 |
paddingRight(paddingEnd) | 右边距 |
paddingTop | 上边距 |
paddingLeft(paddingStart) | 左边距 |
rotationX | 水平方向旋转度数 |
rotationY | 竖直方向旋转度数 |
scaleX | 水平方向缩放比例 |
scaleY | 竖直方向缩放比例 |
textDirection | 文本的显示方向 |
translationX | 水平方向移动距离 |
translationY | 竖直方向移动距离 |
2.View的坐标体系:
属性 | 作用 |
---|---|
getHeight | View自身的高度 |
getWidth | View自身的宽度 |
getX | View左上角距离父View左边的距离 |
getY | View左上角距离父View顶部的距离 |
getTop | View顶部距离父View顶部的距离 |
getLeft | View左部距离父View左部的距离 |
getBottom | View底部距离父View顶部的距离 |
getRight | View右部距离父View左部的距离 |
这些属性都自带有继承的getter和setter,属性动画就是依靠改变这些属性而实现的。
有关ObjectAnimator对象
ObjectAnimator对象是属性动画的实现方式,其原理见此。以一个例子为例:
ObjectAnimator heightAnimator=ObjectAnimator
.ofFloat(mSunView,"y",sunYStart,sunYEnd)
.setDuration(3000);
这个ObjectAnimator对象的ofFloat表示修改的属性的类型,里面的参数的含义为:第一个View是要修改的View,第二个是需要修改的View的属性名,剩下的是修改的数值,根据不同的属性名的数值的个数和含义都不相同,例如这里是将y从sunYStart变化到sunYEnd。
整个动画的过程,实际上就是ObjectAnimator对象在不听地调用set方法来改变视图属性。
但是我不知道为什么这本书的作者推荐用setX和setY的方法,这两种方法似乎不能突破父视图的坐标轴范围,导致动画的效果不能正确显示(我在挑战练习里面想要将一个视图慢慢移动到屏幕外,但是只能移动到内切与屏幕的位置。用setTranslationX就可以实现了)。
有关加速特效
需要使用ObjectAnimator的setInterpolator方法,传入一个TimeInterpoLator对象实现变速移动的效果,如下:
ObjectAnimator heightAnimator=ObjectAnimator
.ofFloat(mSunView,"y",sunYStart,sunYEnd)
.setDuration(3000);
heightAnimator.setInterpolator(new AccelerateInterpolator());
TimeInterpolator似乎可以翻译为插值器。AccelerateInterpolator是加速运动的插值器,DecelerateInterpolator是减速运动的插值器,LinearInterpolator是匀速运动的差值器。系统默认的插值器AccelerateDecelerateInterpolator(在动画开始与结束的地方速率改变比较慢,在中间的时候加速)。资料
有关色彩渐变
实现色彩的变化也是通过set视图属性background来实现的。但是稍有常识的人都会知道,颜色的值并不是一个随着色彩的连续变化而变化的数值,实际上颜色的分布不是线性的,而是一个四维的立体。这时候需要调用ObjectAnimator的setEvaluator来添加一个TypeEvaluator的子类argbEvaluator来计算得到颜色渐变时的数值,如下:
ObjectAnimator sunsetSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mBlueSkyColor,mSunsetColor)
.setDuration(3000);
sunsetSkyAnimator.setEvaluator(new ArgbEvaluator());
有关播放多个动画
以上的这些动画,调用start时是同时播放的。如果需要按照一定的先后循序来播放,需要将它们都加入到一个AnimatorSet中。
AnimatorSet加入动画有四个方式:play()表示以这个动画为基准区分先后;with()是与基准动画同时开始播放的,可以写多个;before()是在基准动画之后开始播放的,可以写多个;after()是在基准动画之前播放的,可以写多个。如下:
AnimatorSetr animatorSet=new AnimatorSet(); animatorSet.play(heightAnimator) .with(reflectionAnimator) .with(sunsetSkyAnimator) .with(zoomXAnimator) .with(zoomYAnimator) .before(widthAnimator) .before(nightSkyAnimator);
同样的,AnimatorSet也需要start来开始播放:
animatorSet.start();
有关挑战练习
我觉得这一章的挑战练习才是重头戏,这次的挑战练习需要我去实现很多花样。但是再写挑战练习之前,我觉得晚上的布局未免也太过单调了,就给晚上的天加了一个月亮(效果图是完成了挑战练习之后录的,设计剧透内容):
写月亮时遇到一个问题:在日落期间时没有月亮的,如果要将布局中的月亮View设置为invisible,如何在太阳刚好下山时再将月亮设置为visible是一个问题:可见性并不是一个可以修改的属性。
我的解决方法是现在布局中将其设置为不可见,再动画开始之前先将月亮可见,再移到视图外面去:
mMoonView.setX(mSkyView.getWidth());
让日落可逆
首先要写一个日出方法,过程和日出相反。然后需要标志当前点击应当日出还是日落:
private int mMoveState=1;//-1为落下,-2为正在落下,1为升起,2为正在升起
但是出现了一个问题是,在动画还没有结束时再点击屏幕,就会开始另一个动画,所以需要在动画正在播放时使得点击无效,播放完毕之后在修改状态,需要用到AnimatorSet的监听器:
animatorSet=new AnimatorSet(); animatorSet.play(heightAnimator) .with(reflectionAnimator) .with(sunsetSkyAnimator) .with(zoomXAnimator) .with(zoomYAnimator) .before(widthAnimator) .before(nightSkyAnimator); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { mMoveState=-2; } @Override public void onAnimationEnd(Animator animator) { mMoveState=-1; } @Override public void onAnimationCancel(Animator animator) { ... } @Override public void onAnimationRepeat(Animator animator) { } }); animatorSet.start();
有一个细节是监听器需要写在start前面,否则onAnimationStart会监听不到。
完整代码我在最后放吧,因为这个挑战练习实在是太繁琐了。
添加太阳动画特效
我看到题目就直接像setScale了,根本没看括号里面的使用setRepeatCount方法,不过我的方法也是可行的:
ObjectAnimator zoomXAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleX",1f,1.2f,1f)
.setDuration(3000);
ObjectAnimator zoomYAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleY",1f,1.2f,1f)
.setDuration(3000);
添加倒影
作者说的很轻巧,“海上要是有太阳的倒影就更真实了”给我加了多少工作量!!!
这里就是我上面说的直接setY实现不了的,太阳的倒影会一直靠近到内切于海平面的地方。其实我觉得用translation才是真正的浪漫,这种直来直去的设置反而容易把握。
日落:
ObjectAnimator reflectionAnimator=ObjectAnimator
.ofFloat(mReflectionView,"translationY",-mReflectionView.getBottom(),0)
.setDuration(3000);
widthAnimator.setInterpolator(new AccelerateInterpolator());
时间倒流
意思是太阳落下时点击就让它再升起来,在升起来的时候点击就让它落下去。我觉得这个要求太无聊的,而且这个练习确实有难度。
首先要先实现点击时让现在播放的动画停止,这个简单,直接cancel就行了:
mSceneView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMoveState==1)
{
startAnimationForSunset();
}
else if (mMoveState==-1)
{
startAnimationForSunrise();
}
else if (mMoveState==-2)
{
animatorSet.cancel();
mMoveState=1;//写在cancel中好像没用
}
else if (mMoveState==2)
{
animatorSet.cancel();
mMoveState=-1;
}
}
});
之后就是怎么去让动画倒着放了。书上说AnimatorSet没有倒放,我查了,是有的,但是是安卓9的新功能,网上也没有使用说明,我自己试了亿下也不知道是什么牛鬼蛇神。所以就暂时忽视这个reverse方法。
我的思路是在AnimatorSet的监听器的onAnimationCancel中调用一个返回到原来状态的动画的方法(新方法+2),这两个方法还需要根据已经停止的动画播放到什么阶段了而去设置自己的倒放的动画(例如日落,有日正在落和月亮来了这两个阶段),而且我不知道怎么去获取那个动画播放了多久,起码AnimatorSet我是没找到获取的方法,如果要自己设置一个计时器就当我没说,总之我在时间倒流的两个方法里面时间设置的都是固定值,所以看上去效果可能会有些慢。
一个监听器:
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
mMoveState=-2;
}
@Override
public void onAnimationEnd(Animator animator) {
mMoveState=-1;
}
@Override
public void onAnimationCancel(Animator animator) {
goBackToSky();
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
有一个缺陷是我不知道怎么去获取当前的背景颜色值,反正View的所有和color或background相关的方法我都试过了。所以每次回放颜色都是从刺眼的白色开始进行渐变。不过我觉得这样效果还挺好的,这是对你们企图倒退时间的惩罚1
需要注意的是,由于时间倒退了,最后的状态也会复原,我们不应该在onAnimationCancel中修改mMoveState,因为似乎是先onAnimationCancel再onAnimationEnd。修改状态值应该在AnimatorSet的cancel之后进行:
mSceneView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMoveState==1)
{
startAnimationForSunset();
}
else if (mMoveState==-1)
{
startAnimationForSunrise();
}
else if (mMoveState==-2)
{
animatorSet.cancel();
mMoveState=1;//写在cancel中好像没用
}
else if (mMoveState==2)
{
animatorSet.cancel();
mMoveState=-1;
}
}
});
完整代码:
public class SunsetFragmnet extends Fragment {
private View mSceneView;
private View mSunView;
private View mMoonView;
private View mReflectionView;
private View mSkyView;
private int mBlueSkyColor;
private int mSunsetColor;
private int mNightSkyColor;
private int mMoveState=1;//-1为落下,0为运动中,1为升起
AnimatorSet animatorSet;
public static SunsetFragmnet newInstance()
{
return new SunsetFragmnet();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_sunset,container,false);
mSceneView =view;
mSunView=view.findViewById(R.id.sun);
mMoonView=view.findViewById(R.id.moon);
mReflectionView=view.findViewById(R.id.reflection_sun);
mSkyView=view.findViewById(R.id.sky);
Resources resources=getResources();
mBlueSkyColor=resources.getColor(R.color.blue_sky);
mSunsetColor=resources.getColor(R.color.sunset_sky);
mNightSkyColor=resources.getColor(R.color.night_sky);
mSceneView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMoveState==1)
{
startAnimationForSunset();
}
else if (mMoveState==-1)
{
startAnimationForSunrise();
}
else if (mMoveState==-2)
{
animatorSet.cancel();
mMoveState=1;//写在cancel中好像没用
}
else if (mMoveState==2)
{
animatorSet.cancel();
mMoveState=-1;
}
}
});
return view;
}
private void startAnimation()
{
float sunYStart=mSunView.getTop();
float sunYEnd=mSkyView.getHeight();
ObjectAnimator heightAnimator=ObjectAnimator
.ofFloat(mSunView,"y",sunYStart,sunYEnd)
.setDuration(3000);
heightAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator sunsetSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mBlueSkyColor,mSunsetColor)
.setDuration(3000);
sunsetSkyAnimator.setEvaluator(new ArgbEvaluator());
ObjectAnimator nightSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mSunsetColor,mNightSkyColor)
.setDuration(1500);
nightSkyAnimator.setEvaluator(new ArgbEvaluator());
AnimatorSet animatorSet=new AnimatorSet();
animatorSet.play(heightAnimator)
.with(sunsetSkyAnimator)
.before(nightSkyAnimator);
animatorSet.start();
}
private void startAnimationForSunset()
{
float sunYStart=mSunView.getTop();
float sunYEnd=mSkyView.getHeight();
float moonXStart=mSkyView.getWidth();
float moonXEnd=mMoonView.getLeft();
mMoonView.setX(mSkyView.getWidth());
ObjectAnimator heightAnimator=ObjectAnimator
.ofFloat(mSunView,"y",sunYStart,sunYEnd)
.setDuration(3000);
heightAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator zoomXAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleX",1f,1.2f,1f)
.setDuration(3000);
ObjectAnimator zoomYAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleY",1f,1.2f,1f)
.setDuration(3000);
ObjectAnimator widthAnimator=ObjectAnimator
.ofFloat(mMoonView,"x",moonXStart,moonXEnd)
.setDuration(1500);
widthAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator reflectionAnimator=ObjectAnimator
.ofFloat(mReflectionView,"translationY",0,-mReflectionView.getBottom())
.setDuration(3000);
widthAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator sunsetSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mBlueSkyColor,mSunsetColor)
.setDuration(3000);
sunsetSkyAnimator.setEvaluator(new ArgbEvaluator());
ObjectAnimator nightSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mSunsetColor,mNightSkyColor)
.setDuration(1500);
nightSkyAnimator.setEvaluator(new ArgbEvaluator());
mMoonView.setVisibility(View.VISIBLE);
animatorSet=new AnimatorSet();
animatorSet.play(heightAnimator)
.with(reflectionAnimator)
.with(sunsetSkyAnimator)
.with(zoomXAnimator)
.with(zoomYAnimator)
.before(widthAnimator)
.before(nightSkyAnimator);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
mMoveState=-2;
}
@Override
public void onAnimationEnd(Animator animator) {
mMoveState=-1;
}
@Override
public void onAnimationCancel(Animator animator) {
goBackToSky();
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animatorSet.start();
}
private void startAnimationForSunrise()
{
float sunYStart=mSkyView.getHeight();
float sunYEnd=mSunView.getTop();
float moonXStart=mMoonView.getLeft();
float moonXEnd=mSkyView.getWidth();
ObjectAnimator heightAnimator=ObjectAnimator
.ofFloat(mSunView,"y",sunYStart,sunYEnd)
.setDuration(3000);
heightAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator zoomXAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleX",1f,1.2f,1f)
.setDuration(3000);
ObjectAnimator zoomYAnimator=ObjectAnimator
.ofFloat(mSunView,"scaleY",1f,1.2f,1f)
.setDuration(3000);
ObjectAnimator widthAnimator=ObjectAnimator
.ofFloat(mMoonView,"x",moonXStart,moonXEnd)
.setDuration(1500);
widthAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator reflectionAnimator=ObjectAnimator
.ofFloat(mReflectionView,"translationY",-mReflectionView.getBottom(),0)
.setDuration(3000);
widthAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator sunsetSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mSunsetColor,mBlueSkyColor)
.setDuration(3000);
sunsetSkyAnimator.setEvaluator(new ArgbEvaluator());
ObjectAnimator nightSkyAnimator=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mNightSkyColor,mSunsetColor)
.setDuration(1500);
nightSkyAnimator.setEvaluator(new ArgbEvaluator());
animatorSet=new AnimatorSet();
animatorSet.play(nightSkyAnimator)
.with(widthAnimator)
.before(heightAnimator)
.before(sunsetSkyAnimator)
.before(zoomXAnimator)
.before(zoomYAnimator)
.before(reflectionAnimator);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
mMoveState=2;
}
@Override
public void onAnimationEnd(Animator animator) {
mMoveState=1;
}
@Override
public void onAnimationCancel(Animator animator) {
goBackUnderGround();
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animatorSet.start();
}
private void goBackToSky()
{
int sunsetColor=mSkyView.getSolidColor();
float moonPosition=mMoonView.getTranslationX();
ObjectAnimator SunGoBackAnimator=ObjectAnimator
.ofFloat(mSunView,"translationY",mSunView.getTranslationY(),0)
.setDuration(3000);
SunGoBackAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator ReflectionGoBackAnimator=ObjectAnimator
.ofFloat(mReflectionView,"translationY",mReflectionView.getTranslationY(),0)
.setDuration(3000);
ReflectionGoBackAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator sunsetSkyGoBackAnimatorOne=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",sunsetColor,mBlueSkyColor)
.setDuration(3000);
sunsetSkyGoBackAnimatorOne.setEvaluator(new ArgbEvaluator());
ObjectAnimator sunsetSkyGoBackAnimatorTwo=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mSunsetColor,mBlueSkyColor)
.setDuration(3000);
sunsetSkyGoBackAnimatorTwo.setEvaluator(new ArgbEvaluator());
ObjectAnimator sunsetSkyGoBackAnimatorThree=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",sunsetColor,mSunsetColor)
.setDuration(1500);
sunsetSkyGoBackAnimatorThree.setEvaluator(new ArgbEvaluator());
ObjectAnimator moonGoBackAnimator=ObjectAnimator
.ofFloat(mMoonView,"translationX",mMoonView.getTranslationX(),-mMoonView.getLeft()+mSkyView.getWidth())
.setDuration(1500);
moonGoBackAnimator.setInterpolator(new AccelerateInterpolator());
AnimatorSet animatorSet=new AnimatorSet();
//月亮还没出来
if (moonPosition==-mMoonView.getLeft()+mSkyView.getWidth())
{
animatorSet.play(SunGoBackAnimator)
.with(ReflectionGoBackAnimator)
.with(sunsetSkyGoBackAnimatorOne);
}
//月亮出来了
else
{
animatorSet.play(SunGoBackAnimator)
.with(ReflectionGoBackAnimator)
.with(sunsetSkyGoBackAnimatorTwo)
.after(sunsetSkyGoBackAnimatorThree)
.after(moonGoBackAnimator);
}
animatorSet.start();
}
private void goBackUnderGround()
{
int sunsetColor=mSkyView.getSolidColor();
float sunPosition=mSunView.getTranslationY();
ObjectAnimator SunGoBackAnimator=ObjectAnimator
.ofFloat(mSunView,"translationY",mSunView.getTranslationY(),mSkyView.getHeight()-mSunView.getTop())
.setDuration(3000);
SunGoBackAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator ReflectionGoBackAnimator=ObjectAnimator
.ofFloat(mReflectionView,"translationY",mReflectionView.getTranslationY(),-mReflectionView.getBottom())
.setDuration(3000);
ReflectionGoBackAnimator.setInterpolator(new AccelerateInterpolator());
ObjectAnimator sunsetSkyGoBackAnimatorOne=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",sunsetColor,mSunsetColor)
.setDuration(3000);
sunsetSkyGoBackAnimatorOne.setEvaluator(new ArgbEvaluator());
ObjectAnimator sunsetSkyGoBackAnimatorTwo=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",mSunsetColor,mNightSkyColor)
.setDuration(1500);
sunsetSkyGoBackAnimatorTwo.setEvaluator(new ArgbEvaluator());
ObjectAnimator sunsetSkyGoBackAnimatorThree=ObjectAnimator
.ofInt(mSkyView,"backgroundColor",sunsetColor,mNightSkyColor)
.setDuration(1500);
sunsetSkyGoBackAnimatorThree.setEvaluator(new ArgbEvaluator());
ObjectAnimator moonGoBackAnimator=ObjectAnimator
.ofFloat(mMoonView,"translationX",mMoonView.getTranslationX(),0)
.setDuration(1500);
moonGoBackAnimator.setInterpolator(new AccelerateInterpolator());
AnimatorSet animatorSet=new AnimatorSet();
//太阳还没出来
if (sunPosition==mSkyView.getHeight()-mSunView.getTop())
{
animatorSet.play(moonGoBackAnimator)
.with(sunsetSkyGoBackAnimatorThree);
}
//太阳出来了
else
{
animatorSet.play(moonGoBackAnimator)
.with(sunsetSkyGoBackAnimatorTwo)
.after(sunsetSkyGoBackAnimatorOne)
.after(SunGoBackAnimator)
.after(ReflectionGoBackAnimator);
}
animatorSet.start();
}
}
效果图: