动画在Android开发中比较常用,主要有:帧动画、补间动画、属性动画(3.0增加),下面开始学习:
Frame Animation,逐帧动画是一种常见的动画形式,其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。
简单理解:
连续播放准备好的关键帧图片,形成动画
先创建帧动画文件
// res/drawable/anim_frame.xml
…… 其余item省略
设置动画
// 2. 将动画设置给 iv,二选一
// 2.1 设置成图片资源
mIv.setImageResource(R.drawable.anim_frame);
// 2.2 设置成背景资源,如果单纯想显示动画,就把src图片去掉,否则就是图片会盖在动图上面,因为是背景
// mIv.setBackgroundResource(R.drawable.anim_frame);
// 3. 获取动画,根据设置动画方式,二选一
// 3.1 设置为图片资源的获取方式
mAnimDrawable = (AnimationDrawable) mIv.getDrawable();
// 3.2 设置为背景资源的获取方式
// mAnimDrawable2 = (AnimationDrawable) mIv.getBackground();
// 4. 也可在代码中设置 是否播放一次
mAnimDrawable.setOneShot(false);
/**
* 开始动画
* @param view
*/
public void startAnim(View view) {
mAnimDrawable.start();
}
/**
* 停止动画
* @param view
*/
public void stopAnim(View view) {
mAnimDrawable.stop();
}
// 1. 创建 AnimationDrawable 对象
mAnimDrawable = new AnimationDrawable();
// 2. 设置是否播放一次
mAnimDrawable.setOneShot(false);
// 3. 添加帧
for (int i = 1; i < 23; i++) {
int id = getResources().getIdentifier("smoke" + i, "drawable", getPackageName());
Drawable drawable = ContextCompat.getDrawable(this, id);
// 添加帧图片,并设置每帧持续时间
mAnimDrawable.addFrame(drawable, 200);
}
// 4. iv 设置动画对象
// 4.1 设置为图片资源
mIv.setImageDrawable(mAnimDrawable);
// 4.2 设置为背景资源
// mIv.setBackgroundDrawable(mAnimDrawable);
/**
* 开始动画
* @param view
*/
public void startAnim(View view) {
mAnimDrawable.start();
}
/**
* 停止动画
* @param view
*/
public void stopAnim(View view) {
mAnimDrawable.stop();
}
效果图:
- 帧动画主要使用AnimationDrawable对象
你可能需要的:在线GIF分解
Tween Animation,补间动画指的是做flash动画时,在两个关键帧中间需要做“补间动画”,才能实现图画的运动;插入补间动画两个关键帧后,之间的插补帧是由计算机自动运算而得到的。
简单理解:
需要定义开始与结束两个关键帧,指定好运动的方式与时间,系统就会自动计算加入中间帧进行播放,形成动画
分类:
xml 标签 | java 对象 | 功能 |
---|---|---|
alph | AlphaAnimation | 透明度动画 |
rotate | RotateAnimation | 旋转动画 |
translate | TranslateAnimation | 位移动画 |
scale | ScaleAnimation | 缩放动画 |
set | AnimationSet | 补间动画集合 |
属性:
xml 属性 | 功能 | 可用于动画 |
---|---|---|
duration | 动画持续时间 | ALL |
startOffset | 动画开始延迟时间 | ALL |
fillBefore | 动画完成后,保持动画前状态,默认true | ALL |
fillAfter | 动画完成后,保持动画后状态,默认false | ALL |
fillEnabled | 是否使用fillBefore值,对fillAfter值无影响,默认为true | ALL |
repeatMode | 重放,restart正序重放,reverse倒序回放,默认restart | ALL |
repeatCount | 重放次数,播放次数=重放次数+1,infinite为无限播放 | ALL(set使用无效) |
interpolator | 插值器 | ALL |
fromAlpha | 开始时透明图(0.0~1.0) | Alpha |
toAlpha | 结束时透明图(0.0~1.0) | Alpha |
fromDegrees | 开始时角度 | Rotate |
toDegrees | 结束时角度 | Rotate |
fromXDelta | 动画起始x轴位置 | Translate |
toXDelta | 动画结束x轴位置 | Translate |
fromYDelta | 动画起始y轴位置 | Translate |
toYDelta | 动画结束y轴位置 | Translate |
fromXScale | 开始x轴方向缩放值 | Scale |
toXScale | 结束时x轴方向缩放值 | Scale |
fromYScale | 开始y轴方向缩放值 | Scale |
toYScale | 开始y轴方向缩放值 | Scale |
pivotX | 旋转、缩放中心点x坐标 | Rotate、Scale |
pivotY | 旋转、缩放中心点y坐标 | Rotate、Scale |
shareInterpolator | 共享插值器 | Set |
注意:
通过Xml创建动画时
1、位置变化相关属性
取值类型为:float, fraction 的属性,需要注意:
设置为数字时(如50,0.5),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE
。
设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF
。
设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数Animation.RELATIVE_TO_PARENT
。
2、缩放相关属性
取值类型为dimension, float, fraction的属性 ,需要注意:
dimension:如50dp,表示view的x轴(宽)或y轴(高)为50dp
float、百分比:如0.5、50%,同等效果,表示View当前宽高为原大小的50%
百分比p:如50%p,表示View当前宽高为父容器的50%
查看属性取值类型:点击属性使属性获取焦点,鼠标悬浮在属性上等待一会即可。
如果没有,可参考Android studio 3.1 基础设置 > 快速文档设置
透明度动画
// 1. 创建动画文件
// 2. 加载动画
Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha_sample);
// 3. 设置动画作用对象并开始动画播放
mView.startAnimation(animation);
// 1. 创建动画,并设置相关属性
AlphaAnimation animation = new AlphaAnimation(1.0f,0.2f);
animation.setDuration(500);
animation.setStartOffset(500);
animation.setFillAfter(false);
animation.setRepeatCount(1);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(new AccelerateDecelerateInterpolator());
// 2. 设置动画作用对象并开始动画播放
mView.startAnimation(animation);
旋转动画
位移动画
缩放动画
动画集合
注意:
- Set种各个动画创建的顺序,会影响最后的效果。经过反复测试,
rotate
和scale
都要在translate
之前repeatCount
给Set
设置不起作用,需要单独各个设置
Java代码创建动画的方式,只示例了AlphaAnimation,其它的都类似,只有AnimationSet特殊一点,如下:
AnimationSet set = new AnimationSet(true);
// 需要将一个个子动画添加进来
set.addAnimation(anim1);
set.addAnimation(anim2);
set.addAnimation(anim3);
// ...... 其他类似
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.test);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.e("补间动画","动画开始了");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.e("补间动画","动画结束了");
}
@Override
public void onAnimationRepeat(Animation animation) {
Log.e("补间动画","动画重复播放了");
}
});
mView.setAnimation(animation);
效果
本篇暂只介绍属性动画的基本使用,实现补间动画的效果,高级使用方法后续再学习
属性动画,3.0之前只有帧动画和补间动画,但是由于这两种动画的动画效果只能作用于View,效果简单并且只改变了View的视觉效果,实际上属性并没有改变,所以在3.0引入了属性动画。
属性动画的工作原理:
在动画持续时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果,可以是任意对象的任意属性
主要类:
该对象的使用是通过Android 3.1系统上新增的一个方法View#animate()方法获取实例。
特点
属性
View的方法 | 功能 | ViewPropertyAnimator的方法 |
---|---|---|
setTranslationX() | 设置x轴偏移 | translationX() translationXBy() |
setTranslationY() | 设置y轴偏移 | translationY() translationYBy() |
setTranslationZ() | 设置z轴偏移 | translationZ() translationZBy() |
setX() | 设置x轴绝对位置 | x() xBy() |
setY() | 设置y轴绝对位置 | y() yBy() |
setZ() | 设置z轴绝对位置 | z() zBy() |
setRotation() | 设置平面旋转 | rotation() rotationBy() |
setRotationX() | 设置绕x轴旋转 | rotationX() rotationXBy() |
setRotationY() | 设置绕y轴旋转 | rotationY() rotationYBy() |
setScaleX() | 设置横向缩放 | scaleX() scaleXBy() |
setScaleY() | 设置纵向缩放 | scaleY() scaleYBy() |
setAlpha() | 设置透明度 | alpha() alphaBy() |
说明:
使用
/**
* button点击事件
*/
public void viewPropertyAnimator(View view) {
// 因为监听事件需要API 21,为了链式调用,直接在这里进行判断了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
iv.animate() // 获取ViewPropertyAnimator
.translationXBy(50) // 每次点击时x轴递增平移;如果是translationX(50)再次点击不会有效果
.rotationBy(90) // 平面旋转,同上
.setDuration(500) // 动画时长
.setStartDelay(1000) // 开始延迟
.setInterpolator(new LinearInterpolator()) // 设置插值器,默认AccelerateDecelerateInterpolator
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
// 动画更新监听事件,可获取计算出的【完成度】
Log.e("完成度",animatedValue+" translationX:"+(50*animatedValue) + " rotation:"+(90*animatedValue));
// 属性动画,(left,top,right,bottom)是不变的,变化的是(x,y,translationX,translationY) , x = left + translationX
Log.d("属性值变化", "getLeft=" + iv.getLeft() + " getX=" + iv.getX() +" getTranslationX=" + iv.getTranslationX());
}
});
}
}
效果
上面Log中间处省略。ViewPropertyAnimator大概使用如上,其他的动画效果实现也一样,API很简单,练习一下就OK了
该对象的特点就完全对应上面第2点所说属性动画的全部特点了,可以作用任意java对象,不过要有相应的setter/getter方法
;通过ObjectAnimator.ofxxx()
获取ObjectAnimator对象;该对象会根据动画时长自动计算某一时刻的属性值,自动赋值
;需要手动调用start()
方法播放动画
java实现
public void objectAnimator(View view) {
// 获取ObjectAnimator
// 参数1:作用对象
// 参数2:作用属性,例:setTranslationX -> "translationX" ; setRotationX -> "rotationX",我们的ofFloat也是根据该属性的setter方法参数类型使用的,如果是int,那么我们需要使用ofInt
// 参数3:可变长参数,-150为开始值 ,0为转接点值 ,150为结束值
ObjectAnimator animator = ObjectAnimator.ofFloat(iv,"translationX",-150,0,150);
// 时长
animator.setDuration(1000);
// 延迟
animator.setStartDelay(1000);
// 重复次数,播放次数 = 重复次数 + 1
animator.setRepeatCount(0);
// 重复模式,RESTART:重新开始; REVERSE:反转回来
animator.setRepeatMode(ValueAnimator.RESTART);
// 插值器
animator.setInterpolator(new LinearInterpolator());
// 动画更新监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 可获取计算出【当前时刻的属性值】
float animatedValue = (float) animation.getAnimatedValue();
Log.e("当前属性值",animatedValue+"");
// 属性动画,(left,top,right,bottom)是不变的,变化的是(x,y,translationX,translationY) , x = left + translationX
Log.d("属性值变化", "getLeft=" + iv.getLeft() + " getX=" + iv.getX() +" getTranslationX=" + iv.getTranslationX());
}
});
// 开始动画
animator.start();
}
xml实现
public void objectAnimator(View view) {
// 加载动画
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.translation);
// 作用对象
animator.setTarget(iv);
// 开始动画
animator.start();
}
上图Log,中间有省略
其他动画效果实现方式类似
组合动画: 借助PropertyValuesHolder实现
public void objectAnimator(View view) {
// PropertyValuesHolder此类包含有关属性的信息以及该属性在动画期间应采用的值。
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.2f, 1);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.2f, 1);
PropertyValuesHolder rotation = PropertyValuesHolder.ofFloat("rotation", 0, 360);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0.2f, 1);
// 获取ObjectAnimator
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(iv, scaleX, scaleY, rotation, alpha);
// 开始动画
animator.setDuration(2000)
.start();
}
效果
属性动画中最核心的一个类,此类提供了一个简单的计时引擎,用于运行动画时计算动画值,使用这个类,需要我们手动赋值属性值
。
xml实现
// res/animator目录下创建
// 加载动画
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.valueanimator);
java实现
// 获取对象,参数为可变长参数,指定开始值,转接点值,结束值
ValueAnimator animator = ValueAnimator.ofFloat(0, 90, 180, 360);
// 时长
animator.setDuration(2000);
// 延迟
animator.setStartDelay(1000);
// 重复次数
// animator.setRepeatCount(0);
// 重复模式,RESTART:重新开始; REVERSE:反转回来
// animator.setRepeatMode(ValueAnimator.RESTART);
// 插值器
animator.setInterpolator(new CycleInterpolator(1f));
设置属性值
// 动画更新监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 可获取计算出【当前时刻的属性值】
float animatedValue = (float) animation.getAnimatedValue();
Log.e("当前属性值", animatedValue + "");
iv.setRotation(animatedValue);
iv.setTranslationX(animatedValue);
// 属性动画,(left,top,right,bottom)是不变的,变化的是(x,y,translationX,translationY) , x = left + translationX
Log.d("属性值变化", "getLeft=" + iv.getLeft() + " getX=" + iv.getX() + " getTranslationX=" + iv.getTranslationX());
// 刷新视图
iv.requestLayout();
}
});
// 开始动画
animator.start();
效果
上图Log,中间有省略
组合动画一:自己实现
上面的动画组合算是凑巧,下面用另一种方式实现
public void valueAnimator(View view) {
// 这里我们设置为 0~1
ValueAnimator animator = ValueAnimator.ofFloat(0,1);
// 时长
animator.setDuration(2000);
// 延迟
animator.setStartDelay(1000);
// 插值器
animator.setInterpolator(new CycleInterpolator(1f));
// 动画更新监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 可获取计算出【当前时刻的属性值】,我们当做比例来用
float animatedValue = (float) animation.getAnimatedValue();
// 2s内,平面旋转0~360
iv.setRotation(animatedValue * 360);
// 2s内,x轴位移,translationX为0~360
iv.setTranslationX(animatedValue * 360);
// 2s内,横向缩放 0.2~1.2
iv.setScaleX((float) (animatedValue+0.2));
// 2s内,纵向缩放 0.2~1.2
iv.setScaleY((float) (animatedValue+0.2));
// 刷新视图
iv.requestLayout();
}
});
// 开始动画
animator.start();
}
组合动画二:借助PropertyValuesHolder实现
public void valueAnimator(View view) {
// PropertyValuesHolder此类包含有关属性的信息以及该属性在动画期间应采用的值。
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.2f, 1.2f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.2f, 1.2f);
PropertyValuesHolder rotation = PropertyValuesHolder.ofFloat("rotation", 0, 360);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("translationX", 0, 360);
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleX, scaleY, rotation, alpha);
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new CycleInterpolator(1f));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PropertyValuesHolder[] values = animation.getValues();
for (int i = 0; i < values.length; i++) {
// 通过PropertyValuesHolder#getPropertyName() 可获得设置的属性
// 通过ValueAnimator#getAnimatedValue("属性") 可获得计算出的当前时刻属性值
Log.e("holder", values[i].getPropertyName() + " " + animation.getAnimatedValue(values[i].getPropertyName()));
iv.setScaleX((float)animation.getAnimatedValue("scaleX"));
iv.setScaleY((float)animation.getAnimatedValue("scaleY"));
iv.setRotation((float)animation.getAnimatedValue("rotation"));
iv.setTranslationX((float)animation.getAnimatedValue("translationX"));
}
}
});
valueAnimator.start();
}
通过上面的学习我们了解到:ObjectAnimator只能改变一个属性,不能直接实现组合动画,需要借助PropertyValuesHolder才行;ValueAnimator也可以直接实现组合动画,也借助PropertyValuesHolder实现;ViewPropertyAnimator可以很方便的实现组合动画;但是它们都有一个缺点:都只能是几种动画效果同时播放
;下面我们学习组合动画的正确姿势AnimatorSet
API
AnimatorSet
play(Animator anim)
创建AnimatiorSet.Builder对象
playSequentially(Animator… items)
创建AnimatiorSet.Builder对象,并设置每个动画在上个动画执行完后执行,顺序执行
playSequentially(List items)
同上,只不过是集合形式
playTogether(Animator… items)
创建AnimatiorSet.Builder对象,并设置所有动画同时执行
playTogether(Collection items)
同上,只不过是集合形式
setDuration(long duration)
设置此AnimatorSet的每个子动画的持续时长,优先级高于单独设置
AnimatiorSet.Builder
Java实现
public void animatorSet(View view) {
// 顺序执行
AnimatorSet set1 = new AnimatorSet();
ObjectAnimator translationAnimator1 = ObjectAnimator.ofFloat(iv, "translationX", -300);
ObjectAnimator translationAnimator2 = ObjectAnimator.ofFloat(iv, "translationY", 600);
ObjectAnimator translationAnimator3 = ObjectAnimator.ofFloat(iv, "translationX", 300);
ObjectAnimator translationAnimator4 = ObjectAnimator.ofFloat(iv, "translationY", 0);
ObjectAnimator translationAnimator5 = ObjectAnimator.ofFloat(iv, "translationX", 0);
set1.playSequentially(translationAnimator1, translationAnimator2, translationAnimator3, translationAnimator4, translationAnimator5);
// 设置动画时长,这里的时长是每个子动画的时长
set1.setDuration(1000);
// 同时执行
AnimatorSet set2 = new AnimatorSet();
ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(iv, "rotationY", 0, 360);
ObjectAnimator scaleAnimator1 = ObjectAnimator.ofFloat(iv, "scaleX", 0.2f, 1);
ObjectAnimator scaleAnimator2 = ObjectAnimator.ofFloat(iv, "scaleY", 0.2f, 1);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(iv, "alpha", 0.2f, 1);
set2.playTogether(rotationAnimator, scaleAnimator1, scaleAnimator2, alphaAnimator);
// 设置动画时长,这里的时长是每个子动画的时长
set2.setDuration(5000);
// 最终组合 , 这里设置时长是每个子动画的时长 , 因为想让set1 和 set2 总时长一样,所以分开设置了
AnimatorSet set = new AnimatorSet();
set.playTogether(set1,set2);
set.start();
}
xml实现
public void animatorSet(View view) {
// 加载动画
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.set);
// 作用对象
animator.setTarget(iv);
// 开始
animator.start();
}
ViewPropertyAnimator#setUpdateListener() 和 ValueAnimator#addUpdateListener()
参数都是ValueAnimator.AnimatorUpdateListener,上面也示例过了,只是其中参数都是ValueAnimator#getAnimatedValue()不同:
ViewPropertyAnimator#setUpdateListener返回的是当前时刻完成度
ValueAnimator#addUpdateListener返回的是当前时刻的具体属性值
ViewPropertyAnimator#setListener() 和 ValueAnimator#addListener()
参数都是Animator.AnimatorListener,如下:
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
// 动画开始时被调用
}
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束、取消时被调用
}
@Override
public void onAnimationCancel(Animator animation) {
// 动画取消时调用,先调用cancel,再调用end
}
@Override
public void onAnimationRepeat(Animator animation) {
// 当动画通过 setRepeatMode() / setRepeatCount() 或 repeat() 方法重复执行时,这个方法被调用
// ViewPropertyAnimator 不支持重复,所以这个方法对 ViewPropertyAnimator 无效
}
});
插值器,就是速度设置器
java | xml | 效果 |
---|---|---|
AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 默认,先加速再减速 |
AccelerateInterpolator | @android:anim/accelerate_interpolator | 持续加速 |
DecelerateInterpolator | @android:anim/decelerate_interpolator | 持续减速 |
AnticipateInterpolator | @android:anim/anticipate_interpolator | 蓄力效果,先回拉再加速动画 |
OvershootInterpolator | @android:anim/overshoot_interpolator | 回弹效果,超出终点后再回终点 |
AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 蓄力+回弹 |
BounceInterpolator | @android:anim/bounce_interpolator | 结束时有弹跳效果 |
CycleInterpolator | @android:anim/cycle_interpolator | 回弹效果,不同于上面的一点回弹,像荡秋千一样,回弹次数由构造中传入值决定 |
LinearInterpolator | @android:anim/linear_interpolator | 匀速 |
个人总结,水平有限,如果有错误,希望大家能给留言指正!如果对您有所帮助,可以帮忙点个赞!如果转载,希望可以标明文章出处!最后,非常感谢您的阅读!