前言
- 动画时Android开发中使用频率比较高的功能。
- 对Android提供的补间动画,帧动画以及属性动画做出归纳总结。
目录
1. 帧动画
帧动画总体实现比较简单,其实现本身是实现一个图片集的连续播放,从而达到动画的效果。
实现帧动画就必须将大量图片资源加入到APK当中,从而增加APK的大小,但是却可以实现比较复杂的动画效果。
帧动画使用比较简单的,具体操作过程如下
- 将图片集导入到对应目录下
- 在drawable文件夹下新建文件anim_chat. xml,的代码实现如下
<?xml version="1.0" encoding="UTF-8"?>
<animation-list android:oneshot="false"
xmlns:android="http://schemas.android.com/apk/res/android">
//duraction字段可以用来设置该图片播放时长,drawable用来设置要显示的图片
<item android:duration="230" android:drawable="@drawable/ic_chat_recording1" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording2" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording3" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording4" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording5" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording6" />
</animation-list>
复制代码
- 在布局文件activity_main.xml当中添加组件
<Button
android:id="@+id/frame_animation_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="帧动画测试" />
<ImageView
android:id="@+id/frame_animation_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_chat_recording1"
android:layout_marginTop="20dp"/>
复制代码
- 在activity当中加入java代码实现
public class MainActivity extends AppCompatActivity {
private Button frameButton;
private ImageView frameImage;
private AnimationDrawable frameAnimation;
boolean isStart = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFrame();
}
private void initFrame() {
//初始化控件
frameButton = (Button) findViewById(R.id.frame_animation_test);
frameImage = (ImageView) findViewById(R.id.frame_animation_img);
//给ImageView设置drawable
frameImage.setImageResource(R.drawable.anim_chat_recording);
//给动画资源赋值
frameAnimation = (AnimationDrawable) frameImage.getDrawable();
//给按钮添加点击事件用来控制动画
frameButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//当isStart=false时表示动画没有在播放,点击按钮开始播放
if (!isStart) {
frameAnimation.start();
isStart = true;
} else {//当isStart=true时表示动画正在播放,点击按钮停止播放
frameAnimation.stop();
isStart = false;
}
}
});
}
}
复制代码
运行代码进行测试
帧动画也存在使用纯java代码的实现方式,但是在应用当中并不多见,有兴趣可以了解一下,这里不做介绍。
2. 补间动画
与按帧播放的帧动画不同,补间动画只需要定义初始和结束时的状态,中间的动画过程将由系统自动补齐。
-
特点:
- 补间动画作用于View,可以实现视觉上的动画效果,但是并没有真正对视图做出改变。
- 使用简单,可以使用非常简单的方式实现动画效果。
-
实现方式可以有jave代码实现和XML代码实现两种。
-
分类:补间动画可分为四类
jave | xml | 效果 ---|--- | -- AlphaAnimation | alph | 渐变透明度动画效果 ScaleAnimation | scale | 渐变尺寸伸缩动画效果 TranslateAnimation1 | translate | 画面转换位置移动动画效果 RotateAnimation | rotate | 画面转移旋转动画效果
后文将对四种补间动画效果做具体说明。
2.1 alph动画
特有属性:
- android:fromAlpha:动画开始时的透明度。
- android:toAlpha:动画结束时的透明度。
Java代码实现
直接上代码
- 在activity_main.xml当中定义布局资源
<ImageView
android:id="@+id/alph_animation_img"
android:layout_width="150dp"
android:layout_height="200dp"
android:src="@drawable/animation_test1"/>
复制代码
后续内容的动画效果基本针对图片涉及到的xml布局都基本类似,将不再进行说明。
- 在activity当中进行实现
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAlphJava();
}
private void initAlphJava() {
alphImage = (ImageView) findViewById(R.id.alph_animation_img);
alphImage.setVisibility(View.VISIBLE);
//构造参数中,第一个参数为动画开始时候透明度,第二个参数toAlpha为 动画结束时候透明度
//对于fromAlpha和toAlpha,1.0代表完全不透明状态,0.0代表完全透明状态
AlphaAnimation mAlpha = new AlphaAnimation(1.0f,0.0f);
//设置动画播放时间,2000MS=2S
mAlpha.setDuration(2000);
//设置动画循环次数,-1为一直循环
mAlpha.setRepeatCount(-1);
//设置动画循环方式Animation.REVERSE为倒叙播放,Animation.RESTART为重复播放
mAlpha.setRepeatMode(Animation.REVERSE);
//alphImage开始播放动画
alphImage.startAnimation(mAlpha);
}
复制代码
动画效果为:由原图显示渐变为隐藏状态。
XML实现
- 在res目录中新建anim文件夹。
- 在anim目录中新建一个alph_anim.xml文件(注意文件名小写)。
- 在alph_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!--透明度控制动画效果 alpha-->
<!--fromAlpha 属性为动画起始时透明度 0.0表示完全透明 1.0表示完全不透明-->
<!--toAlpha 属性为动画结束时透明度-->
<!--duration 属性为动画持续时间-->
<alpha
android:duration="2000" //播放时间为2秒
android:fromAlpha="1.0" //初始透明度为完全显示
android:toAlpha="0.0" //结束透明度为完全透明
android:repeatCount= "-1" /> //重复播放次数为无限循环
</set>
复制代码
fromAlpha和toAlpha为alph动画的特有属性,1.0代表完全不透明状态,0.0代表完全透明状态
- 在activity当中对动画资源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAlphXml();
}
private void initAlphXml() {
alphImage = (ImageView) findViewById(R.id.alph_animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.alph_anim);
alphImage.startAnimation(animation);
}
复制代码
运行程序,动画效果与上述java实现相同。 补间动画中有一些公共属性,其说明如下:
android:duration: 动画执行的时间,以毫秒为单位
android:fillEnabled:true|false 动画结束时还原到开始动画前的状态
android:fillBefore:true|false 动画结束后视图会停留在动画开始的状态,如果fillEnabled的值为true,它的值才有意义,否则没有意义。默认值是true。
android:fillAfter:true|false 动画结束后是否保留这个动画的最后一帧的效果,它的设置不受fillEnabled的影响
android:repeatMode:reverse|restart 重复类型,reverse:表示倒序回放,restart:表示重新放一遍,这个属性必须与repeatCount联合使用,因为它的前提是重复,即重复播放时的播放类型。
android:repeatCount:动画重复的次数(注意是重复的次数),设定具体数值,也是可以是infinite,表示无限循环
android:interpolator:设定的插值器,它主要用来为动画设置一些特殊的效果,比方说:加速运动、减速运动等等。
2.2 scale动画
特有属性:
- android:fromXScale起始的X方向上相对自身的缩放比例,类型float
- android:toXScale:结尾的X方向上相对自身的缩放比例,类型float
- android:fromYScale:起始的Y方向上相对自身的缩放比例,类型float
- android:toYScale:结尾的Y方向上相对自身的缩放比例,类型float
- android:pivotX: 缩放起点X轴坐标,可以是数值、百分数、百分数p。
- android:pivotY: 缩放起点Y轴坐标,同pivotX。
java代码实现
- 在activity_main.xml当中定义布局资源
- 在java代码当中的代码实现如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initScaleJava();
}
private void initScaleJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
ScaleAnimation scaleAnimation = new ScaleAnimation(1,2,1,2,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
// fromX:动画起始X轴缩放倍数
// toX:动画结束时X轴缩放倍数
// fromY:动画起始Y轴缩放倍数
// toY:动画结束时Y轴缩放倍数
// pivotXType:X轴缩放原点参数类型
// pivotXValue:X轴缩放原点参数
// pivotYType:Y轴缩放原点参数类型
// pivotYValue:Y轴缩放原点参数
scaleAnimation.setDuration(3000);
scaleAnimation.setRepeatMode(Animation.REVERSE);
//Animation.INFINITE与-1取值相同,为无限重播
scaleAnimation.setRepeatCount(Animation.INFINITE);
mImage.startAnimation(scaleAnimation);
}
复制代码
动画实现效果:以自身中心点为原点,缩放为原大小的两倍。
构造方法
ScaleAnimation(float fromX, float toX, float fromY, float toY,
int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
复制代码
参数取值介绍
对于前四个参数:0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 pivotXType存在三种取值 pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 + pivotXValue数值的点(y方向同理)
pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 + 自身宽度乘上pivotXValue数值的值(y方向同理)
pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 + 父控件宽度乘上pivotXValue数值的值 (y方向同理)
例子中所选参数为从原图大小X轴和Y轴缩放到原大小的两倍,缩放参照点为以自身宽高比例的50%处,也就是中心点。
XML实现
- 在res目录中新建anim文件夹。
- 在anim目录中新建一个scale_anim.xml文件(注意文件名小写)。
- 在scale_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="3000" //设置播放时长为3秒
android:fillAfter="false" //设置不保存播放完毕之后的画面
android:fromXScale="1.0" //起始画面X轴缩放倍数
android:fromYScale="1.0" //起始画面Y轴缩放倍数
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
//使用accelerate_decelerate_interpolator加速--减速差值器
android:pivotX="50%" //X轴缩放原点为自身宽度的50%
android:pivotY="50%" //Y轴缩放原点为自身宽度的50%
android:toXScale="2.0" //结束画面X轴缩放倍数
android:toYScale="2.0" //结束画面Y轴缩放倍数
android:repeatCount= "-1" //动画循环次数为无限循环
android:repeatMode="reverse"/> //循环模式为倒播
</set>
复制代码
pivotX与pivotY相同,有三种取值方式:
- 取值为数值:当为数值时,表示在当前View的左上角,加上参数值即原点处,做为旋转点X坐标,单位为px。
- 取值为百分数:如果是50%表示在当前控件的左上角加上自己宽度的50%做为旋转点X坐标。
- 取值为百分数p:如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为旋转点X坐标。
- 在activity当中对动画资源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initScaleXML();
}
private void initAlphXml() {
private void initScaleXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.scale_anim);
mImage.startAnimation(animation);
}
}
复制代码
运行程序,动画效果与上述java实现相同。
2.3 translate动画
特有属性:
- android:fromXDelta:起始点X轴坐标。
- android:fromYDelta:起始点Y坐标。
- android:toXDelta:结束点X坐标
- android:toYDelta:结束点Y坐标
java代码实现
- 在activity_main.xml当中定义布局资源
- 在java代码当中的代码实现如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initTranslateJava();
}
private void initTranslateJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
TranslateAnimation translateAnimation = new TranslateAnimation(0,300,0,300);
//fromXDelta:起始点X轴的坐标
//toXDelta:终止点X轴的坐标
//fromYDelta:起始点Y轴的坐标
//toYDelta:终止点Y轴的坐标
translateAnimation.setDuration(2000);
translateAnimation.setRepeatMode(Animation.REVERSE);
//Animation.INFINITE与-1取值相同,为无限重播
translateAnimation.setRepeatCount(Animation.INFINITE);
mImage.startAnimation(translateAnimation);
}
复制代码
上述代码实现效果:View向右下角45°移动,最终坐标为原左上角坐标的X轴正方向300,Y轴正方向300. 动画效果如下
对于构造方法
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
int fromYType, float fromYValue, int toYType, float toYValue) {
复制代码
参数类型可大体分为两种: value和type
Type参数取值介绍
fromXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理) 默认为这种方式。
fromXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
fromXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
Value取值介绍:
有三种取值方式:
- 取值为数值:当为数值时,表示在当前View的左上角,即原点处加上参数值,做为旋转点X坐标,单位为px。
- 取值为百分数:如果是50%表示在当前控件的左上角加上自己宽度的50%做为原点X坐标。
- 取值为百分数p:如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为原点X坐标。
XML实现
- 在res目录中新建anim文件夹。
- 在anim目录中新建一个translate_anim.xml文件(注意文件名小写)。
- 在translate_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="300"
android:toYDelta="300" />
</set>
复制代码
参数值类型介绍
整型值:
fromXDelta 属性为动画起始时 X坐标上的位置
toXDelta 属性为动画结束时 X坐标上的位置
fromYDelta 属性为动画起始时 Y坐标上的位置
toYDelta 属性为动画结束时 Y坐标上的位置
注意:
没有指定fromXType toXType fromYType toYType 时候, 默认是以自己为相对参照物
长整型值:
duration 属性为动画持续时间
说明: 时间以毫秒为单位
- 在activity当中对动画资源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initTranslateXML();
}
private void initTranslateXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.translate_anim);
mImage.startAnimation(animation);
}
复制代码
运行程序,动画效果与上述java实现相同。
2.4 Rotate动画
特有属性:
- android:fromDegrees:动画开始时旋转的角度位置,float类型,正值代表顺时针方向度数,负值代码逆时针方向度数
- android:toDegrees: 动画结束时旋转到的角度位置,float类型,正值代表顺时针方向度数,负值代码逆时针方向度数
- android:pivotX:旋转点X轴坐标,float类型,可以是数值、百分数、百分数p三种样式。
- android:pivotY:旋转点Y轴坐标,取值及意义跟android:pivotX一样。
Java实现
- 在activity_main.xml当中定义布局资源
- 在java代码当中的代码实现如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRotateJava();
}
private void initRotateJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
RotateAnimation rotateAnimation = new RotateAnimation(0,90,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
// 参数说明:
// fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
// toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
// pivotXType:旋转轴点的x坐标的模式
// pivotXValue:旋转轴点x坐标的相对值
// pivotYType:旋转轴点的y坐标的模式
// pivotYValue:旋转轴点y坐标的相对值
rotateAnimation.setRepeatMode(Animation.REVERSE);
// 视频循环模式
rotateAnimation.setDuration(2000);
// 视频播放时长
rotateAnimation.setRepeatCount(Animation.INFINITE);
// 视频循环次数
mImage.startAnimation(rotateAnimation);
复制代码
上述代码实现效果:以自身中心点为坐标,顺时针旋转90度。
参数取值介绍
fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
XML实现
- 在res目录中新建anim文件夹。
- 在anim目录中新建一个set_anim.xml文件(注意文件名小写)。
- 在set_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:duration="3000"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="90" />
</set>
<!--
rotate 旋转动画效果
属性:interpolator 指定一个动画的插入器
在我试验过程中,使用android.res.anim中的资源时候发现
有三种动画插入器:
accelerate_decelerate_interpolator 加速-减速 动画插入器
accelerate_interpolator 加速-动画插入器
decelerate_interpolator 减速- 动画插入器
其他的属于特定的动画效果
浮点数型值:
fromDegrees 属性为动画起始时物件的角度
toDegrees 属性为动画结束时物件旋转的角度 可以大于360度
说明:
当角度为负数——表示逆时针旋转
当角度为正数——表示顺时针旋转
(负数from——to正数:顺时针旋转)
(负数from——to负数:逆时针旋转)
(正数from——to正数:顺时针旋转)
(正数from——to负数:逆时针旋转)
pivotX 属性为动画相对于物件的X坐标的开始位置
pivotY 属性为动画相对于物件的Y坐标的开始位置
说明: 以上两个属性值 从0%-100%中取值
50%为物件的X或Y方向坐标上的中点位置
长整型值:
duration 属性为动画持续时间
说明: 时间以毫秒为单位
-->
复制代码
- 在activity当中对动画资源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRotateXML();
}
private void initRotateXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
mImage.startAnimation(animation);
}
复制代码
运行程序,动画效果与上述java实现相同。
2.5 组合动画(set标签)
使用set标签可以做到多种动画类型的组合效果具体步骤如下
- 在res目录中新建anim文件夹。
- 在anim目录中新建一个set_anim.xml文件(注意文件名小写)。
- 在set_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha= "0.0"
android:toAlpha= "1.0"
android:duration= "3000" />
//alph动画 实现效果:从透明状态渐变到显示状态
<scale
android:fromXScale= "0.0"
android:toXScale= "1.0"
android:fromYScale= "0.0"
android:toYScale= "1.0"
android:pivotX= "50%"
android:pivotY= "50%"
android:duration= "3000" />
//以自身原点为坐标从0缩放原图大小
<rotate
android:fromDegrees= "0"
android:toDegrees= "720"
android:pivotX= "50%"
android:pivotY= "50%"
android:duration= "3000"/>
//旋转720°
<translate
android:startOffset= "3000"
android:fromXDelta= "0"
android:fromYDelta= "0"
android:toXDelta= "85"
android:toYDelta= "0"
android:duration= "1000" />
//3000毫秒以后,水平方向移动85距离
<alpha
android:startOffset= "4000"
android:fromAlpha= "1.0"
android:toAlpha= "0.0"
android:duration= "1000" />
//4000毫秒以后图片渐变消失
</set>
复制代码
- 在==activity==当中对动画资源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initSet();
}
private void initSet() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.set_anim);
animation.setRepeatCount(-1);
mImage.startAnimation(animation);
}
复制代码
动画效果如下:
补间动画使用方便,但是其针对view进行操作的特性,以及只能实现视图上的动画而不能改变实际的view位置大小等缺点使其功能受到限制。下面将介绍功能更加强大的属性动画。3.属性动画
- 优势:
- 在Android3.0推出了属性动画。相比于属性动画,View动画一个非常大的缺陷就是其不具备交互性。当某个元素发生View动画之后,其相应事件的位置依然在进行前的地方,所以View动画只能做到普通的动画效果,尽量避免交互操作。View动画的优势也十分明显:效率比较高,使用也方便。
- View动画只能实现比较单一的View的移动缩放等效果,效果比较单一,属性动画则是通过属性值变化来重新布局View,理论上可以实现任何动画效果。
- 问题:
- 在使用上不如View动画方便。
3.1 原理解析
属性动画有两个非常重要的类:ValueAnimator 类 和 ObjectAnimator 类
- ValueAnimator:通过不断控制值的变化,再不断手动赋给对象的属性,从而实现动画效果。
- ObjectAnimator:直接对对象的属性值进行改变操作,通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。其实现继承了ValueAnimator。
3.2 差值器和估值器
- 插值器(Interpolator)前面我们在介绍补间动画时就有提到过,它主要用来为动画设置一些特殊的效果,比方说:加速运动、减速运动、动画结束的时候弹等等。主要有
- accelerate_decelerate_interpolator 加速-减速 动画插入器
- accelerate_interpolator 加速-动画插入器
- decelerate_interpolator 减速- 动画插入器
- 估值器(TypeEvaluator)决定值的具体变化数值
我们先来看一下系统提供的估值器:
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>float</code> or
* <code>Float</code>
* @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
<!--fraction 动画的完成度-->
<!--startValue 动画的初始值-->
<!--endValue 动画的结束值-->
float startFloat = startValue.floatValue();
<!--计算返回结果的公式为result = x0 + t * (v1 - v0)-->
<!--x0=startValue-->
<!--x1 = endValue-->
<!--t = fraction-->
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
复制代码
这是由系统提供的操作float类型数据的估值器,可以根据我们提供的fraction、startValue以及endValue不断地产float数值对象返回给我们,我们可以根据返回的数据对我们的View进行更改。
但是很多情况下我们需要操作的不只是float或者int类型等基本数据类型,所以我们就需要自定义==ObjectEvaluator==。
模仿==FloatEvaluator==,自定义==ObjectEvaluator==如下:
// 实现TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator<Object>{
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
... // 写入对象动画过渡的逻辑
// 返回对象动画过渡的逻辑计算后的值
return value;
}
复制代码
后面会进行实例说明。再回过头来看我们的主角:ValueAnimator和ObjectAnimator类
3.3 ObjectAnimator类
本质原理: 通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。 先来看一个简单的例子:
private void initObjectAnimationJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mImage, "alpha", 0f, 1f);
alpha.setDuration(2000);//设置动画时间
alpha.setInterpolator(new DecelerateInterpolator());//设置动画插入器,减速
alpha.setRepeatCount(-1);//设置动画重复次数,这里-1代表无限
alpha.setRepeatMode(ValueAnimator.REVERSE);//设置动画循环模式,注意这里是ValueAnimator.REVERSE而不是Animator.REVERSE
alpha.start();//启动动画。
}
复制代码
实现效果:图片由隐藏渐渐显示。
动画对象定义完后的操作与View动画基本一致,最主要的区别在于构造方法,这里我使用了ObjectAnimator.ofFloat,ofFloat()方法主要有两个作用:设置参数以及返回ObjectAnimator对象,其方法如下:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
//1. 需要操作的对象,注意这里与补间动画不同,不止限于View对象,而可以是任意操作对象。
//2. propertyName需要操作的属性,这个属性必须要有get和set方法才可以。
//3. 这是一个可变参数,为后续的变化趋势。如设置两个参数v1,v2,则属性由v1变化到v2,如果设置的是v1,v2,v3,则由v1变化到v2再变化到v3以此类推。
复制代码
-
透明度:
ObjectAnimator alpha = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f);
复制代码
透明度由0.0变为1.0。
-
伸缩X轴:
ObjectAnimator scaleX = ObjectAnimator.ofFloat(text, "scaleX", 0f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(text, "scaleY", 0f, 1f);
复制代码
X轴(Y轴)方向由0缩放到原图大小。
-
移动:
ObjectAnimator translationUp = ObjectAnimator.ofFloat(text, "Y",0f,300f, 0f);
复制代码
Y轴方向:0f-> -300f,向左;-300f-> 0f,向右。
-
旋转
ObjectAnimator animator = ObjectAnimator.ofFloat(text, "rotation", 0f, 360f, 0f);
复制代码
旋转动画:0f -> 360f ,顺时针; 360f -> 0f,逆时针。 可以操作的动画属性propertyName的取值可以为:
属性 | 作用 | 数值类型 |
---|---|---|
Alpha | 控制View的透明度 | float |
TranslationX | 控制X方向的位移 | float |
TranslationY | 控制Y方向的位移 | float |
ScaleX | 控制X方向的缩放倍数 | float |
ScaleY | 控制Y方向的缩放倍数 | float |
Rotation | 控制以屏幕方向为轴的旋转度数 | float |
RotationX | 控制以X轴为轴的旋转度数 | float |
RotationY | 控制以Y轴为轴的旋转度数 | float |
ObjectAnimation组合动画
Java代码
使用AnimatorSet完成组合动画播放。
- anim1.with(anim2):anim1与anim2同时播放。
- anim1.befor(anim2):在anim2之前播放anim1。
- anim1.after(anim2):在anim2之后播放anim1。
- anim1.after(long delay):delay时间之后播放anim1。
具体使用代码如下:
private void initObjectAnimationSetJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
AnimatorSet set = new AnimatorSet() ;
//X轴旋转180°
ObjectAnimator rotationX = ObjectAnimator .ofFloat(mImage, "rotationX", 0f, 180f);
rotationX.setDuration(2000);
//Y轴旋转180°
ObjectAnimator rotationY = ObjectAnimator .ofFloat(mImage, "rotationY", 0f, 180f);
rotationY.setDuration(2000);
//X轴方向缩放到原图大小
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImage, "scaleX", 0f, 1f);
scaleX.setDuration(2000);
//Y轴方向缩放到原图大小
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImage, "scaleY", 1f, 0f);
scaleY.setDuration(2000);
set.play(rotationX).with(scaleX); //X轴旋转与X轴缩放动画同时进行
set.play(rotationX).before(rotationY); //X轴旋转动画完成以后开始Y轴旋转
set.play(rotationY).with(scaleY);//Y轴旋转动画与Y轴缩放同时开始
set.start(); //开始播放动画
}
复制代码
动画效果:X轴旋转与X轴缩放动画同时进行,结束之后Y轴旋转动画与Y轴缩放同时开始。实际效果如下:
XML实现
set中的属==性android:ordering==:规定了这个set中的动画的执行顺序,包括:
- together(默认):set中的动画同时执行
- sequentially:set中的动画按顺序执行
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<set android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="rotationX"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:repeatMode="reverse"
android:valueFrom="0.0"
android:valueTo="1.0" />
</set>
<set android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="rotationY"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="2000"
android:propertyName="scaleY"
android:repeatMode="reverse"
android:valueFrom="1.0"
android:valueTo="0.0" />
</set>
</set>
复制代码
java代码:
private void initObjectAnimationSetXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.object_set_anim);
animator.setTarget(mImage);
animator.start();
}
复制代码
实现效果与java代码一致。
3.4 ValueAnimator类
ValueAnimator类本质上是一种值得操作机制,想要实现动画,是需要开发者手动将这些值 赋给对象的属性值。 ValueAnimator动画同样存在java和xml两种使用方式。建议使用java代码的方式,这里只介绍java方式,xml有兴趣的可以自己了解一下。
ValueAnimator类常用的方法与ObjectAnimation类似,主要方法有
- public static ValueAnimator ofInt(int... values)
- public static ValueAnimator ofArgb(int... values)
- public static ValueAnimator ofFloat(float... values)
- public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
前三种方法使用类似,后面的参数都表示变化趋势,即:设置两个参数v1,v2,则属性由v1变化到v2,如果设置的是v1,v2,v3,则由v1变化到v2再变化到v3以此类推。
以ofFloat为例,使用方法如下
private void initValueAnimationJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
//步骤1. 定义ValueAnimator方法
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f,1.0f);
//获取原控件宽高
final int width = mImage.getLayoutParams().width;
final int height = mImage.getLayoutParams().height;
//设置动画属性
valueAnimator.setDuration(2000);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
//步骤2. 添加AnimatorUpdateListener方法永联监听值得变化趋势
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float currentValue = (Float) animator.getAnimatedValue();
// 获得每次变化后的属性值
// 步骤3:每次值变化时,将值手动赋值给对象的属性
// 即将每次变化后的值 赋 给按钮的宽度,这样就实现了按钮宽度属性的动态变化
mImage.getLayoutParams().width = (int) (width*currentValue);
mImage.getLayoutParams().height = (int) (height*currentValue);
// 步骤4:刷新视图,即重新绘制,从而实现动画效果
mImage.requestLayout();
}
});
valueAnimator.start();
}
复制代码
实际动画效果如下:
如果我们想实现一些比较负载的动画效果,而系统提供的估值器满足不来我们时怎么办,这时候我们之前提到的估值器TypeEvaluator就该登场了,通过方法public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values),设置估值器以后将对数据按照估值器进行计算并返回值,通过点赞动画例子了解实战应用。
4. 属性动画实战--点赞动画
不多说,直接上代码
- 首先是点赞动画的自定义==PraiseLayout==
public class PraiseLayout extends RelativeLayout {
//底部点赞按钮宽高
private int dHeight;
private int dWidth;
//整个View空间宽高
private int mHeight;
private int mWidth;
private LayoutParams layoutParams;
private Random random = new Random();
//图片资源数组
private Drawable[] drawables;
public PraiseLayout(Context context) {
super(context);
init();
}
public PraiseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PraiseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化图片资源
drawables = new Drawable[4];
Drawable red = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_red);
Drawable blue = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_blue);
Drawable orange = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_orange);
Drawable pink = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_pink);
drawables[0] = red;
drawables[1] = blue;
drawables[2] = orange;
drawables[3] = pink;
dHeight = red.getIntrinsicHeight();
dWidth = red.getIntrinsicWidth();
layoutParams = new LayoutParams(dWidth, dHeight);
//点赞动画开始于布局底部
layoutParams.addRule(CENTER_HORIZONTAL, TRUE);
layoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE);
}
//获取组件大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
}
//点击动画事件
public void addFavorWithoutBiz() {
ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(4)]);
imageView.setLayoutParams(layoutParams);
addView(imageView);
if (mWidth > 0 && dHeight > 0) {
Animator set = getBezierValueAnimator(imageView);
set.addListener(new AnimEndListener(imageView));
set.start();
}
}
//添加点赞动画
private ValueAnimator getBezierValueAnimator(View target) {
//点赞动画的估值器,参数为贝塞尔曲线两个中间点
BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));
//参数1:估值器对象 参数2、3:三重贝塞尔曲线的起点和终止点。
ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight - 20), new PointF(random.nextInt(getWidth()), 0));
animator.addUpdateListener(new BezierListenr(target));
animator.setTarget(target);
animator.setDuration(3000);
return animator;
}
private PointF getPointF(int scale) {
PointF pointF = new PointF();
pointF.x = random.nextInt((mWidth - 50));//减去50 是为了控制 x轴活动范围,看效果 随意~~
//再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些 也可以用其他方法
pointF.y = random.nextInt((mHeight - 150)) / scale;
return pointF;
}
//终止动画监听
class AnimEndListener extends AnimatorListenerAdapter {
private View target;
public AnimEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView((target));
}
}
//属性动画的监听类,当数值更新时触发
class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
private View target;
public BezierListenr(View target) {
this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
PointF pointF = (PointF) animation.getAnimatedValue();
target.setX(pointF.x);
target.setY(pointF.y);
// alpha动画
target.setAlpha(1 - animation.getAnimatedFraction());
}
}
}
复制代码
- 估值器==BezierEvaluator==的实现,这里使用三重贝塞尔曲线的公式计算。
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF pointF1;
private PointF pointF2;
public BezierEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float time, PointF startValue, PointF endValue) {
float timeLeft = 1.0f - time;
PointF point = new PointF();//结果
point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
+ 3 * timeLeft * timeLeft * time * (pointF1.x)
+ 3 * timeLeft * time * time * (pointF2.x)
+ time * time * time * (endValue.x);
point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
+ 3 * timeLeft * timeLeft * time * (pointF1.y)
+ 3 * timeLeft * time * time * (pointF2.y)
+ time * time * time * (endValue.y);
return point;
}
}
复制代码
- 在xml布局文件中引入自定义布局
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.liubohua.myapplication.PraiseLayout
android:id="@+id/animation_praise"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.liubohua.myapplication.PraiseLayout>
<ImageView
android:id="@+id/animation_praise_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/nemo_heart_blue"
android:layout_gravity="center|bottom"/>
</FrameLayout>
复制代码
- 在java代码实现
private void initPraise() {
animation_praise_img = (ImageView) findViewById(R.id.animation_praise_img);
animation_praise = (PraiseLayout) findViewById(R.id.animation_praise);
animation_praise_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animation_praise.addFavorWithoutBiz();
}
});
}
复制代码
实现效果图:
尾声
差不多Android常见的动画操作也就这些,通过简单的学习再加上庞大的脑洞就可以实现非常炫酷的效果,赶紧尝试一下吧。