安卓动画
Android 中的动画可以分为以下几类:
- 逐帧动画
- 补间动画
- 属性动画
1、逐帧动画
逐帧动画的原理就是让一系列的静态图片依次播放,利用人眼“视觉暂留”的原理,实现动画。
步骤:
1、res/drawable 下新建 xml 文件,这里定义动画的每一帧,素材图片放到 drawable 下。
frame_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/frame01" android:duration="100"/>
<item android:drawable="@drawable/frame02" android:duration="100"/>
<item android:drawable="@drawable/frame03" android:duration="100"/>
<item android:drawable="@drawable/frame04" android:duration="100"/>
<item android:drawable="@drawable/frame05" android:duration="100"/>
<item android:drawable="@drawable/frame06" android:duration="100"/>
<item android:drawable="@drawable/frame07" android:duration="100"/>
</animation-list>
2、布局中将 AnimationDrawable 对象直接作为背景
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_marginTop="50dp"
android:layout_centerHorizontal="true"
android:id="@+id/frame_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@drawable/frame_animation"
/>
</RelativeLayout>
3、Activity 中控制播放
public class FrameAnimation extends AppCompatActivity {
ImageView frame_image;
AnimationDrawable animationDrawable;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_frame_animation);
frame_image = findViewById(R.id.frame_image);
// 获取 AnimationDrawable 对象
animationDrawable = (AnimationDrawable) frame_image.getBackground();
// 开始播放
animationDrawable.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
//停止播放
animationDrawable.stop();
}
}
利用 Java 代码实现逐帧动画
public class FrameAnimation extends Activity {
ImageView frame_image;
AnimationDrawable animationDrawable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_frame_animation);
frame_image = findViewById(R.id.frame_image);
animationDrawable = new AnimationDrawable();
for (int i = 1; i < 10; i++) {
int id = getResources().getIdentifier("frame0" + i, "drawable", getPackageName());
Drawable drawable = getResources().getDrawable(id);
animationDrawable.addFrame(drawable, 100);
}
animationDrawable.setOneShot(true);
frame_image.setImageDrawable(animationDrawable);
animationDrawable.stop();
// 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
animationDrawable.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
animationDrawable.setOneShot(true);
frame_image.setImageDrawable(animationDrawable);
animationDrawable.stop();
}
}
2、补间动画
补间动画就是指开发者指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。文件一般存放在res/anim文件夹下,访问时采用R.anim.XXX.xml的方式。
补间动画有四种:
- 淡入淡出: alpha
- 位移:translate
- 缩放:scale
- 旋转: rotate
scale标签——调节尺寸
android:fromXScale:起始的X方向上相对自身的缩放比例,浮点值,比如1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍;
android:toXScale:结尾的X方向上相对自身的缩放比例,浮点值;
android:fromYScale:起始的Y方向上相对自身的缩放比例,浮点值,
android:toYScale:结尾的Y方向上相对自身的缩放比例,浮点值;
android:pivotX :缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,当为数值时,表示在当前View的左上角,即原点处加上50px,做为起始缩放点;如果是50%,表示在当前控件的左上角加上自己宽度的50%做为起始点;如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为起始点x轴坐标。
android:pivotY:缩放起点Y轴坐标,取值及意义跟android:pivotX一样。
从Animation类继承的属性
android:duration:动画持续时间,以毫秒为单位
android:fillAfter:如果设置为true,控件动画结束时,将保持动画最后时的状态
android:fillBefore:如果设置为true,控件动画结束时,还原到开始动画前的状态
android:fillEnabled:与android:fillBefore 效果相同,都是在动画结束时,将控件还原到初始化状态
android:repeatCount:重复次数
android:repeatMode:重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。因为这里的意义是重复的类型,即回放时的动作。
android:interpolator:设定插值器,其实就是指定的动作效果,比如弹跳效果等,不在这小节中讲解,后面会单独列出一单讲解。
android:duration:动画的持续时长,以毫秒为单位
alpha标签——调节透明度
android:fromAlpha 动画开始的透明度,从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
android:toAlpha 动画结束时的透明度,也是从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
rotate标签——旋转
android:fromDegrees:开始旋转的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:toDegrees:结束时旋转到的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:pivotX:缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:pivotY:缩放起点Y轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p
translate标签 —— 平移
android:fromXDelta:起始点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:fromYDelta:起始点Y轴从标,可以是数值、百分数、百分数p 三种样式;
android:toXDelta:结束点X轴坐标
android:toYDelta:结束点Y轴坐标
示例:
1、定义动画资源:
res\anim\tween_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
>
<scale
android:duration="3000"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0"/>
<alpha
android:duration="3000"
android:fromAlpha="1.0"
android:toAlpha="0.5" />
<rotate
android:fromDegrees="0"
android:toDegrees="720"
android:pivotX = "50%"
android:pivotY="50%"
android:duration = "3000"
/>
<translate
android:fromXDelta="0"
android:toXDelta="100"
android:fromYDelta="0"
android:toYDelta="100" />
</set>
2、Animation 控制图片播放动画
public class tweenAnimation extends Activity {
// tween_image;
Button tween_start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tween_animation);
final ImageView tween_image = findViewById(R.id.tween_image);
tween_start = findViewById(R.id.tween_start);
// 加载动画资源
final Animation anim = AnimationUtils.loadAnimation(this,R.anim.tween_anim);
//设置动画结束后保留结束状态
anim.setFillAfter(true);
tween_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tween_image.startAnimation(anim);
}
});
}
}
这几个动画可以组合在一起使用,同时完成缩放、透明的、旋转或者位移等的变化。
3、属性动画
工作原理 指定时间内,修改属性(对象中对应的字段)的值,以此实现该对象在属性上的动画效果。
属性动画的使用:
ObjectAnimator animator = ObjectAnimator.ofFloat(button, "ScaleX", 1f, 2f, 3f, 1f);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(3000);
animator.start();
动画的本质:
动画实际上是改变View在某一时间点的样式属性,比如在0.1s的时候View的坐标是100px,在0.2s的时候改变成了200px,用户感觉View在向右移动
实际上是通过一个线程每隔一段时间通过调用View.setX()改变属性值,也能产生动画效果
为什么要将动画分解成不同的关键帧:
动画是需要时间开销才能够完成的,如果不给出关键帧动画,动画的过程将无法控制。在不同的时间点,控件的状态也不一样。
什么时候开始绘制呢?
在Choreographer类里面存在FrameDisplayEventReceiver对象,用于接收Vsync信号,接收到信号之后就会回调ViewRootImpl里面TraversalRunnable的run方法。
自定义动画框架的实现:
实现属性动画实际是通过反射获取View中的方法,在不同的时间改变对象的属性值
//每隔16ms执行一次
@Override
public boolean doAnimationFrame(long currentTime) {
//后续的效果渲染
//动画的总帧数
float total = mDuration / 16;
//拿到执行百分比 (index)/total
float fraction = (index++) / total;
//通过插值器去改变对应的执行百分比
if (interpolator != null) {
fraction = interpolator.getInterpolator(fraction);
}
//循环 repeat
if (index >= total) {
index = 0;
}
//交给mFloatPropertyValuesHolder,改变对应的属性值
mFloatPropertyValuesHolder.setAnimatedValue(mTarget.get(), fraction);
return false;
}
public class MyFloatPropertyValuesHolder {
//属性名
String mPropertyName;
//属性类型 float
Class mValueType;
//反射
Method mSetter = null;
//关键帧管理类
MyKeyframeSet mKeyframeSet;
public MyFloatPropertyValuesHolder(String propertyName, float... values) {
this.mPropertyName = propertyName;
mValueType = float.class;
//交给关键帧管理初始化
mKeyframeSet = MyKeyframeSet.ofFloat(values);
}
//通过反射获取控件对应的方法
public void setupSetter() {
char firstLetter = Character.toUpperCase(mPropertyName.charAt(0));
String theRest = mPropertyName.substring(1);
//setScaleX
String methodName = "set" + firstLetter + theRest;
try {
mSetter = View.class.getMethod(methodName, float.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//给控件设置相应的属性值
public void setAnimatedValue(View view, float fraction) {
Object value = mKeyframeSet.getValue(fraction);
try {
mSetter.invoke(view, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}