Android 视图动画(View Animation) 使用详解
谨以文章记录学习历程,如有错误还请指明。
动画综述
Google大大对动画的总述如下:
Animations can add visual cues that notify users about what’s going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.
没错,放上原文我只是装个逼,~
简单来说,动画就两个作用:
- 添加可视提示,通知我们这个APP中正在发生的事情。比如用户界面发生变化时,有新的内容加载或某些操作变为可用。
- 提供高逼格的外观(装逼利器)
动画的分类如下:
视图动画(View Animation)
补间动画(Tween Animation)
概述
- 作用于视图对象
View
,如TextView
,不可作用于属性,如点击响应位置等 - 通过确定开始的视图样式 & 结束的视图样式、中间动画变化过程由系统补全来确定一个动画
- 动画可以在视图对象的内容上执行一系列简单的转换(位置、大小、旋转和透明度)。如果你有一个TextView对象,你可以移动,旋转,缩放文本。如果它有一个背景图像,背景图像将随着文本一起被转换。
- 由
XML
或JAVA
代码定义。建议使用XML
文件,因为它比更可读、可重用和可切换
分类
根据动画效果,补间动画分为以下4类:
* 透明度动画(alpha)
* 缩放动画(scale)
* 平移动画(Translate)
* 旋转动画(rotate)
不同动画与Java类、xml文件关键字对应关系如下:
名称 | Java子类 | xml关键字 | 说明 |
---|---|---|---|
透明度动画 | AlphaAnimation |
<alpha> 放置在res/anim/ 目录下 |
透明度渐变 |
旋转动画 | RotateAnimation |
<rotate> 放置在res/anim/ 目录下 |
视图旋转 |
缩放动画 | ScaleAnimation |
<scale> 放置在res/anim/ 目录下 |
放大/缩小 视图尺寸大小 |
平移动画 | TranslateAnimation |
<translate> 放置在res/anim/ 目录下 |
视图位置移动 |
复合动画 | AnimationSet |
<set> 放置在res/anim/ 目录下 |
一个持有其它动画元素alpha、scale、translate、rotate或者其它set元素的容器 |
详细说明
补间动画的继承关系:
由于Animation
是抽象基类,其提供了一些通用的动画属性方法,如下所示
xml属性 | Java方法 | 说明 |
---|---|---|
android:detachWallpaper | setDetachWallpaper(boolean) | 是否在壁纸上运行 |
android:duration | setDuration(long) | 动画的运行时间(以毫秒为单位);必须设置 |
android:fillAfter | setFillAfter(boolean) | 动画结束时是否保持动画最后的状态;默认为false,优先于fillBefore |
android:fillBefore | setFillBefore(boolean) | 动画结束时是否还原到开始动画前的状态;默认为true |
android:fillEnabled | setFillEnabled(boolean) | 是否应用fillBefore的值,对fillAfter无影响;默认为true |
android:interpolator | setInterpolator(Interpolator) | 设定插值器(指定的动画效果,譬如回弹等) |
android:repeatCount | setRepeatCount(int) | 重复次数 |
android:repeatMode | setRepeatMode(int) | 重复类型有两个值,reverse表示倒序回放,restart表示从头播放 |
android:startOffset | setStartOffset(long) | 调用start函数之后等待开始运行的时间,单位为毫秒 |
android:zAdjustment | setZAdjustment(int) | 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal |
透明度动画(Alpha)– 属性
xml属性 | Java方法 | 说明 |
---|---|---|
android:fromAlpha | AlphaAnimation(float fromAlpha, …) | 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明) |
android:toAlpha | AlphaAnimation(…, float toAlpha) | 动画结束的透明度,同上 |
旋转动画(Rotate)– 属性
xml属性 | Java方法 | 说明 |
---|---|---|
android:fromDegrees | RotateAnimation(float fromDegrees, …) | 旋转开始角度,正代表顺时针度数,负代表逆时针度数 |
android:toDegrees | RotateAnimation(…, float toDegrees, …) | 旋转结束角度,正代表顺时针度数,负代表逆时针度数 |
android:pivotX | RotateAnimation(…, float pivotX, …) | 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) |
android:pivotY | RotateAnimation(…, float pivotY) | 缩放起点Y坐标,同上规律 |
缩放动画(Scale)– 属性
xml属性 | Java方法 | 说明 |
---|---|---|
android:fromXScale | ScaleAnimation(float fromX, …) | 初始X轴缩放比例,1.0表示无变化 |
android:toXScale | ScaleAnimation(…, float toX, …) | 结束X轴缩放比例 |
android:fromYScale | ScaleAnimation(…, float fromY, …) | 初始Y轴缩放比例 |
android:toYScale | ScaleAnimation(…, float toY, …) | 结束Y轴缩放比例 |
android:pivotX | ScaleAnimation(…, float pivotX, …) | 缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) |
android:pivotY | ScaleAnimation(…, float pivotY) | 缩放起点Y轴坐标,同上规律 |
平移动画(Translate)– 属性
xml属性 | Java方法 | 说明 |
---|---|---|
android:fromXDelta | TranslateAnimation(float fromXDelta, …) | 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) |
android:fromYDelta | TranslateAnimation(…, float fromYDelta, …) | 起始点Y轴从标,同上规律 |
android:toXDelta | TranslateAnimation(…, float toXDelta, …) | 结束点X轴坐标,同上规律 |
android:toYDelta | TranslateAnimation(…, float toYDelta) | 结束点Y轴坐标,同上规律 |
具体使用
下面我们直接以复合动画为例,演示如何通过XML
以及JAVA
两种方式使用动画
XML方式
- 文件位置:
res/anim/filename.xml
- 资源引用
Java:R.anim.filename
XML:@[package:]anim/filename
- 语法:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true" | "false"] >
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float" />
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float" />
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />
<set>
...
</set>
</set>
- 创建动画
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation myAnimation= AnimationUtils.loadAnimation(this, R.anim.filename);
spaceshipImage.startAnimation(myAnimation);
以上就是一个标准的XML方式使用自定义的补间动画的模板
JAVA方式
示例如下:
ImageView imageView = findViewById(R.id.image_view);
// 创建 需要设置动画的 视图View
// 组合动画设置
AnimationSet setAnimation = new AnimationSet(true);
// 创建组合动画对象(设置为true)
// 设置组合动画的属性
setAnimation.setRepeatMode(Animation.RESTART);
...
// 逐个创建子动画,不作过多描述
// 子动画1:透明度动画
Animation alpha = new AlphaAnimation(1,0);
alpha.setDuration(3000);
...
// 子动画2:缩放动画
Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale1.setDuration(1000);
...
// 子动画3:平移动画
Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
TranslateAnimation.RELATIVE_TO_SELF,0
,TranslateAnimation.RELATIVE_TO_SELF,0);
translate.setDuration(10000);
...
// 子动画4:旋转动画
Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotate.setDuration(1000);
rotate.setRepeatMode(Animation.RESTART);
rotate.setRepeatCount(Animation.INFINITE);
...
// 将创建的子动画添加到组合动画里
setAnimation.addAnimation(alpha);
setAnimation.addAnimation(rotate);
setAnimation.addAnimation(translate);
setAnimation.addAnimation(scale1);
imageView.startAnimation(setAnimation);
// 播放动画
首选XML方式使用动画(不意味着XML方式全面优于JAVA方式)
- XML方式:可读性好
- JAVA方式:可动态创建动画效果
至于补间动画的使用,Animation
还有如下一些比较实用的方法介绍:
Animation类的方法 | 解释 |
---|---|
reset() | 重置Animation的初始化 |
cancel() | 取消Animation动画 |
start() | 开始Animation动画 |
hasStarted() | 判断当前Animation是否开始 |
hasEnded() | 判断当前Animation是否结束 |
既然补间动画只能给View
使用,那就来看看View
中和动画相关的几个常用方法吧,如下:
View类的常用动画操作方法 | 解释 |
---|---|
startAnimation(Animation animation) | 对当前View开始设置的Animation动画 |
clearAnimation() | 取消当View在执行的Animation动画 |
特别特别注意:补间动画执行之后并未改变View的真实布局属性值。切记这一点,譬如我们在Activity中有一个Button在屏幕上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button的地方却响应的是点击Button的事件。
动画监听
- 动画监听器接收来自动画的通知。通知表示与动画相关的事件,例如动画的结束或重复
- 使用方法:
Animation.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animation animation) {
//动画开始时执行
}
@Override
public void onAnimationRepeat(Animation animation) {
//动画重复时执行
}
@Override
public void onAnimationCancel()(Animation animation) {
//动画取消时执行
}
@Override
public void onAnimationEnd(Animation animation) {
//动画结束时执行
}
});
- 补间动画的监听只能通过此方式,且必须要复写全部4个方法。
- 属性动画时,可以采用
Animator.addListener(new AnimatorListenerAdapter(){
——动画适配器AnimatorListenerAdapter中已经实现好每个接口
//复写指定方法
})
插值器Interpolator
简介
插值器是在XML中定义的一个动画修改器,它影响动画的变化率。这允许现有动画附加加速、减速、重复、反弹等效果。
继承关系
系统为我们提供了上图中的各种插值器,其都是实现了Interpolator接口的实现类,具体说明如下:
java类 | XML 资源ID | 说明 |
---|---|---|
AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 先加速再减速 |
AccelerateInterpolator | @android:anim/accelerate_interpolator | 持续加速 |
AnticipateInterpolator | @android:anim/anticipate_interpolator | 先退后再加速前进 |
AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 先退后再加速前进,超出终点后再回终点 |
BounceInterpolator | @android:anim/bounce_interpolator | 结束时弹球效果 |
CycleInterpolator | @android:anim/cycle_interpolator | 周期运动 |
DecelerateInterpolator | @android:anim/decelerate_interpolator | 减速 |
LinearInterpolator | @android:anim/linear_interpolator | 匀速 |
OvershootInterpolator | @android:anim/overshoot_interpolator | 向前弹出一定值之后回到原来位置(快速完成动画,超出再回到结束样式) |
使用方法
- XML方式
<set android:interpolator="@android:anim/accelerate_interpolator">
...
</set>
- JAVA方式
Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(3000);
//创建插值器对象
Interpolator interpolator = new OvershootInterpolator();
//为动画添加插值器
alphaAnimation.setInterpolator(interpolator);
imageView.startAnimation(alphaAnimation);
自定义插值器
某些情景可能你会发现系统提供的插值器不能满足需求,此时我们需要自定义插值器。有两种实现方式:XML自定义实现 和 JAVA代码实现方式
XML实现
在
res/anim/
下创建filename.xml
-
修改插值器属性(如下不作任何修改,则与系统预设插值器功能相同)
<?xml version="1.0" encoding="utf-8"?> <InterpolatorName xmlns:android="http://schemas.android.com/apk/res/android" android:attribute_name="value" />
- 补间动画中引用该文件(资源引用为
@[package:]anim/filename
)
XML实现方式本质就是修改系统提供的插值器的某些属性,具体可修改属性如下:
插值器 | 可修改属性 | 属性说明 |
---|---|---|
无属性 | ||
android:factor | Float,加速速率(默认值为1) | |
android:tension | Float. 起始点后拉的张力数(默认值为2) | |
android:tension android:extraTension |
Float. 起始点后拉的张力数(默认值为2) Float. 拉力的倍数(默认值为1.5) |
|
无属性 | ||
android:cycles | Integer. 循环次数(默认为1) | |
android:factor | Float. 减速的速率(默认为1) | |
无属性 | ||
android:tension | Float. 超出终点后的张力(默认为2) |
JAVA方式
在此只讨论原理,不设计具体逻辑(奈何我是个数学渣,公式真的是一头雾水= =)
在前面继承关系的图中,我们可以看出所有插值器继承自BaseInterpolator
,其又实现了Interpolator
接口,而Interpolator
接口继承自TimeInterpolator
接口,TimeInterpolator
接口中的唯一抽象方法为getInterpolation(float input)
,这个方法是由系统调用的,其中的参数input代表动画的时间,在0和1之间,也就是开始和结束之间。
我们可以看一下系统提供的插值器,如AccelerateDecelerateInterpolator
,其源码极其极其简单,如下:
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
可以看到,核心方法就是前面提到的getInterpolation()
,其效果就是通过这一数学公式得来的,好久不看数学,已经快看不懂这个公式结果是什么了,汗颜= =
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
由此我们总结以下JAVA方式自定义插值器:
- 根据需求实现
TimeInterpolator
接口 -
实现抽象方法
getInterpolation()
,在该方法中处理逻辑 - 为动画设置该 自定义插值器
帧动画(Frame Animation)
概述
- 作用于视图对象View,如
TextView
,不可作用于属性,如点击响应位置等 - 将动画拆分为帧的形式,且每一帧都是一张图片,本质就是按序播放一组预先定义好的图片
- 两种实现方式:
XML
&JAVA
,依旧推荐XML方式(你问我为什么还是推荐XML
?连Google API中都只介绍XML
方式,还不能说明问题么)
继承关系
我们很清楚的看到,其本质是Drawable,,因此帧动画的XML定义文件放在res/drawable/
目录下
使用说明
XML方式
1.在res/drawable/
下创建filename.xml
文件
2.设置图片资源,示例如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot=["true" | "false"] >
<item
android:drawable="@[package:]drawable/drawable_resource_name"
android:duration="integer" />
//如下
<item android:drawable="@drawable/wheel0" android:duration="50" />
<item android:drawable="@drawable/wheel1" android:duration="50" />
<item android:drawable="@drawable/wheel2" android:duration="50" />
<item android:drawable="@drawable/wheel3" android:duration="50" />
<item android:drawable="@drawable/wheel4" android:duration="50" />
<item android:drawable="@drawable/wheel5" android:duration="50" />
</animation-list>
3.启动动画
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();
参数说明
<animation-list>
:必须是根节点,包含一个或者多个<item>
元素,包含属性如下:
-
android:oneshot
:true
代表只执行一次,false
循环执行。 -
<item>
类似一帧的动画资源。
<item>
:animation-list
的子项,包含属性如下:
-
android:drawable
一帧的Drawable
资源。 -
android:duration
一帧显示多长时间。
JAVA方式
<-- 直接从drawable文件夹获取动画资源(图片) -->
animationDrawable = new AnimationDrawable();
for (int i = 0; i <= 25; i++) {
int id = getResources().getIdentifier("a" + i, "drawable", getPackageName());
Drawable drawable = getResources().getDrawable(id);
animationDrawable.addFrame(drawable, 100);
}
<-- 开始动画 -->
btn_startFrame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animationDrawable.setOneShot(true);
iv.setImageDrawable(animationDrawable);
// 获取资源对象
animationDrawable.stop();
// 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
animationDrawable.start();
// 启动动画
}
});
<-- 停止动画 -->
btn_stopFrame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animationDrawable.setOneShot(true);
iv.setImageDrawable(animationDrawable);
animationDrawable.stop();
}
});
特别注意,
AnimationDrawable
的start()
方法不能在Activity
的onCreate()
方法中调运,因为AnimationDrawable
还未完全附着到window
上,所以最好的调运时机是onWindowFocusChanged()
方法中。
相关方法说明
XML属性和Java方法的对应关系如下:
XML属性 | Java方法 | 说明 |
---|---|---|
android:drawable | addFrame(Drawable frame, int duration)第一个参数 | 帧的Drawable的引用 |
android:duration | addFrame(Drawable frame, int duration)第二个参数 | 显示此帧的时间(以毫秒为单位) |
android:oneshot | setOneShot(boolean oneShot) | 如果true,动画将只运行一次,然后停止。 |
android:visible | setVisible(boolean, boolean). | 初始可见状态;默认值为false。 |
一些常用的方法如下:
方法 | 说明 |
---|---|
start() | 从第一帧开始动画 |
stop() | 停止动画,显示当前帧 |
getFrame() | 取得某帧的Drawable |
isRunning() | 动画是否正在运行 |
特殊说明
- 优点:使用简单、方便
- 缺点尽量避免使用尺寸较大的图片,否则会引起
OOM
总结
- 本文对Android中的视图动画作出了总结
- 关于另一种动画-属性动画,请移步属性动画(Property Animation) 使用详解
- 笔者水平有限,如有错漏,欢迎指正。
- 接下来我也会将所学的知识分享出来,有兴趣可以继续关注whd_Alive的Android开发笔记
- 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。
推荐阅读
-
Android中ViewFlipper的使用及设置动画效果实例详解
-
mysql视图之创建视图(CREATE VIEW)和使用限制实例详解
-
Android 动画 属性动画 视图动画 补间动画 帧动画 详解 使用
-
Android 自定义 View 中使用 Spannable的实例详解
-
Android开发触摸touch事件(补间动画和自定义view使用方法)
-
Android 属性动画和自定义View的使用
-
mysql视图之创建视图(CREATE VIEW)和使用限制实例详解
-
Animation动画详解(七)--ObjectAnimator基本使用_html/css_WEB-ITnose
-
Android Animation动画详解(二): 组合动画特效_html/css_WEB-ITnose
-
Android动画三部曲之一 View Animation & LayoutAnimation_html/css_WEB-ITnose