文章目录
三个要做的动画
- 正确转向和转向过度(由于都是一个圆周运动的一部分,所以放在一起)
这个比较简单,作为一个单一动画的例子
- 转向过度,甩尾(多个运动的叠加)
多个动画叠加,作为一个进阶的例子
理论知识
安卓动画分为三类:
- View Animation (视图动画)
- Drawable Animation (帧动画)
- Property Animation(属性动画)
这里我将用 View Animation作为例子来讲解如何实现
视图动画的作用对象是View,支持四种动画效果,分别是平移动画,缩放动画,旋转动画,透明度动画。譬如,我们可以对安卓组件设置其的移动,旋转,缩放,透明。
动画 | 调用 |
---|---|
平移动画 | translation |
缩放动画 | scale |
旋转 | rotate |
透明度动画 | Alpha |
每一个动画都可以由两种方式进行设置
1. XML文件设置
首先在res文件夹下建立anim文件夹
然后在其中加入xml文件来编写动画文本
xml代码:
其中包括了4种动画的定义,这里大家可以任意选择使用某几种动画,并且设置其中的参数来达到自己想要的效果
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="20"
android:fromYDelta="0"
android:toYDelta="20"
android:duration="4000"/>
<scale
android:fromXScale="1.0"
android:toXScale="0.2"
android:fromYScale="1.0"
android:toYScale="0.2"
android:pivotX="50%"
android:pivotY="50%"
android:duration="4000"/>
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="4000"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.2"
android:duration="4000"/>
</set>
xml文件编写完毕之后,需要在java代码中调用一下才能使动画运行
Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.test);
raceCar.startAnimation(animation);
2.JAVA代码设置
这里为了让大家看看清楚最基本的一个动画如何建立,我就直接截了Android studio上的图,这样可以方便大家能看清楚每一个参数是干什么的(其实自己打代码的话也是能看到的)
可以选择设置一个持续时间,调用.setDuration(long time)方法
然后调用.start()即可
相对来说JAVA代码设置会简单一些
这里再提供一个类的方法:
我这里用的是ObjectAnimator,因为这个进行动画集合的AnimatorSet比较好用,并且可以无限套娃
AnimatorSet()不仅可以使用playTogether()(同时播放动画),还可以让动画顺序播放,调用playSequentially():
同样的,还可以设置延时播放(不过这样不太好用,我用另外一种实现方式做出来了)
下面的第一个语句就是backX这个动画在backY之后1000毫秒后播放
backAnimatorSet.play(backX).after(backY).after(1000);
backAnimatorSet.play(backX).before(backY);
backAnimatorSet.play(backX).with(backY);
具体实例实现(JAVA代码方式)
这里我要做在博文开始提到的3个动画
动画一(赛车的外内外走线)
首先,让赛车的转向圆与赛道完美切合,我们可以先话一个圆,然后在圆上画赛道
这样子圆运动就比较好规划了,让赛车的起点在赛道左下角,然后圆心定在图片右下角,转90度即可
如果大家不知道如何确定圆心位置,Android Studio上有一个叫做GuidLine的东西,可以用来找到位置
然后就可以编写一个完美过弯的代码了
这里重点在于new RotateAnimation()里面的几个参数,前两个为从0°顺时针旋转到90°,后面两个为圆心的X,Y坐标,这里坐标是以动画主体(我的是一个图片)的左上角为原点的。
//编写动画
final Animation perfectTurning = new RotateAnimation(0,90,1100,488-432);
perfectTurning.setDuration(2000);
//编写按钮触发该动画
perfectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
backAnimatorSet.start();
yuanli.setVisibility(View.INVISIBLE);
raceCar.startAnimation(perfectTurning);
desc.setText("正确的入弯时机\n正确的外内外走线\n正确的出弯时机");
}
});
动画二(转向不足的动画)
这个跟上面的大同小异,只是不再是完美走线,而是一个半径更大的圆,所以我更改了pivotX的数值,其他保持不变,就完成了这个动画
为了更加清楚的解释赛车的动态,我还加了一个轮胎来解释这个动画
final Animation notEnough = new RotateAnimation(0,75,1600,488-432);
notEnough.setDuration(1500);
notEnoughBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
backAnimatorSet.start();
clickTime++;
if (clickTime % 2 == 1) {
raceCar.setVisibility(View.VISIBLE);
yuanli.setVisibility(View.VISIBLE);
raceCar.startAnimation(notEnough);
desc.setText("车速过快,\n导致前轮胎摩擦力无法提供向心力\n而出现推头现象");
}
if (clickTime % 2 == 0) {
raceCar.setVisibility(View.INVISIBLE);
yuanli.setVisibility(View.VISIBLE);
tier.setVisibility(View.VISIBLE);
tier.startAnimation(notEnough);
tier.setVisibility(View.INVISIBLE);
desc.setText("车速过快,\n导致前轮胎摩擦力无法提供向心力\n而出现推头现象");
}
}
});
动画三(转向过度的动画)(复杂的叠加动画)
这里写的每一个单一动画我都用了另外一个实现方式(ObjectAnimator)
情景说明
这个相对来讲比较复杂,我的想的场景是这样的,赛车转向动作开始晚了一些(这里需要一个向前的动画),然后突然进行转弯(叠加一个向右的动画,此时的向前动画是还没停止的)。这里转弯不能只有平移,还要有旋转才能让这个转向更加逼真。由于这里是一个转向过度造成甩尾的情景,所以在平移结束后我的旋转动画实际上还没有结束(这里延时多设置了100毫秒)
动画代码编写
首先先把上述几个动画写好:
goStraight = ObjectAnimator.ofFloat(raceCar,"translationY",-980);
goStraight.setDuration(1600);
turningX = ObjectAnimator.ofFloat(raceCar,"translationX",500);
turningX.setDuration(1200);
turningStep2 = ObjectAnimator.ofFloat(raceCar,"rotation",0,175);
turningStep2.setDuration(1300);
由于这种动画方式不会自动恢复动画物体位置,我自己只好手写了一个恢复位置动画(其实是一个持续时间为0的动画,也就是一瞬间完成),正好也用到了AnimationSet这个东西:
final ObjectAnimator backX = ObjectAnimator.ofFloat(raceCar,"translationX",0);
final ObjectAnimator backY = ObjectAnimator.ofFloat(raceCar,"translationY",0);
final ObjectAnimator backRotate = ObjectAnimator.ofFloat(raceCar,"rotation",0,0);
backAnimatorSet = new AnimatorSet();
backAnimatorSet.playTogether(backX,backY,backRotate);
backAnimatorSet.setDuration(0);
加入时间线来执行这些动画
这几个动画执行的顺序是这样的:
本来可以使用上面讲到的after来执行,但是我觉得是在不太好控制
我这里又建了一个专门用来计时的线程,还有一个相应的Handler。
到了某个时间我就发message给Handler,然后在Handler中执行动画即可。
class MyTimer extends Thread{
@Override
public void run() {
time=0;
while(true) {
time+=10;
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
}
if (time == 10 ){
Message message = handler.obtainMessage();
message.what=3;
handler.sendMessage(message);
}
if (time == 700){
Message message = handler.obtainMessage();
message.what=1;
handler.sendMessage(message);
}
if (time >=2000){
break;
}
}
}
}
上面的time是以毫秒为单位,每10ms过去time就更新一次
(严格山来说不是严格的10ms,因为执行这些其他的语句还要时间)
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 1){
turningX.start();
turningStep2.start();
}
else if (msg.what==3){
goStraight.start();
}
}
};
然后就是动画的执行了,我在一开始就执行了前进,然后在一定时间之后同时开始了转向动画的平移和旋转动画。由于前面设置的持续时间不一样,视觉上看起来就是车辆不再平移后还旋转了一小段。
最后开启一下Thread即可
tooMuchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
backAnimatorSet.start();
time = 0;
MyTimer timer = new MyTimer();
timer.start();
yuanli.setVisibility(View.INVISIBLE);
desc.setText("入弯过晚->转向过快\n导致后轮胎摩擦力无法提供向心力\n从而出现甩尾现象");
}
});
工程文件:
AndroidAnimation