本篇文章主要介绍以下几个知识点:
- View 动画
- View 动画的特殊使用场景
- 属性动画
- 使用动画的注意事项
Android 的动画可分三种:View 动画、帧动画、属性动画。
7.1 View 动画
View 动画的作用对象是 View,支持4种效果:平移动画、缩放动画、旋转动画、透明度动画。
帧动画也属于 View 动画,但其表现形式不同。
7.1.1 View 动画的种类
View 动画可通过 XML 来定义(可读性好),也可通过代码来动态创建,其4种动画效果如下:
要使用 View 动画,首先要创建动画的 XML 文件,其路径为:res/anim/filename.xml。其固定语法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- set标签 动画集合,对应 AnimationSet 类,可包含若干个动画
interpolator 动画集合采用的插值器,影响动画的速度(可不指定)
shareInterpolator 是否和集合共享同一个插值器
duration 持续时间
fillAfter 动画结束后 View 是否停留在结束位置 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true"
android:duration="32"
android:fillAfter="true">
<!-- alpha标签 透明度动画,对应 AlphaAnimation 类
fromAlpha 透明度的起始值
toAlpha 透明度的结束值 -->
<alpha
android:fromAlpha="0.1"
android:toAlpha="1"/>
<!-- scale标签 缩放动画,对应 ScaleAnimation 类
fromXScale 水平方向缩放的起始值
toXScale 水平方向缩放的结束值
fromYScale 竖直方向缩放的起始值
toYScale 竖直方向缩放的结束值
pivotX 缩放的轴点的 x 坐标
pivotY 缩放的轴点的 y 坐标 -->
<scale
android:fromXScale="0.5"
android:toXScale="1.2"
android:fromYScale="32"
android:toYScale="32"
android:pivotX="32"
android:pivotY="32"/>
<!-- translate标签 平移动画,对应 TranslateAnimation 类
fromXDelta x 的起始值
toXDelta x 的结束值
fromYDelta y 的起始值
toYDelta y 的结束值 -->
<translate
android:fromXDelta="0"
android:toXDelta="32"
android:fromYDelta="0"
android:toYDelta="32"/>
<!-- rotate标签 旋转动画,对应 RotateAnimation 类
fromDegrees 旋转开始的角度
toDegrees 旋转结束的角度
pivotX 旋转的轴点的 x 坐标
pivotY 旋转的轴点的 y 坐标 -->
<rotate
android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="32"
android:pivotY="32" />
<set>
<!-- ... -->
</set>
</set>
下面举个例子,创建动画xml文件如下:
<!-- res/anim/chapter_07_view_animation_test.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="normal"
android:fillAfter="true">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100"/>
<rotate
android:duration="400"
android:fromDegrees="0"
android:toDegrees="90" />
</set>
然后在代码中应用上面的动画如下:
ImageView mImage01 = (ImageView) findViewById(R.id.iv_view_anim_01);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.chapter_07_view_animation_test);
mImage01.startAnimation(animation);
运行效果:
除在 XML 中定义动画外,还可以通过代码来应用动画,如下:
// 将一张图片的透明度在1000ms内由0变1。
ImageView mImage02 = (ImageView) findViewById(R.id.iv_view_anim_02);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(1000);
mImage02.startAnimation(alphaAnimation);
运行效果:
另外,通过 Animation 的 setAnimationListener
可给 View 动画添加过程监听,接口如下:
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
7.1.2 自定义 View 动画
自定义动画需要继承 Animation 这个类,重写它的 initialize
(初始化工作)和 applyTransformation
(进行相应的矩阵变换)方法即可。(矩阵变换是数学上的概念)
这里提供一个来自 Android 的 ApiDemos 中的一个自定义 View 动画的例子:
/**
* Function:类3D效果:围绕y轴旋转并同时沿着z轴平移
* Author:Wonderful on 2017/8/21 11:19
*/
public class Rotate3dAnimation extends Animation{
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
public Rotate3dAnimation(float mFromDegrees, float mToDegrees, float mCenterX,
float mCenterY, float mDepthZ, boolean mReverse) {
this.mFromDegrees = mFromDegrees;
this.mToDegrees = mToDegrees;
this.mCenterX = mCenterX;
this.mCenterY = mCenterY;
this.mDepthZ = mDepthZ;
this.mReverse = mReverse;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse){
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
运行效果:
7.1.3 帧动画
帧动画是顺序播放一组预先定义好的图片,类似于电影播放,用类 AnimationDrawable 来使用帧动画。
首先通过 XML 定义一个 AnimationDrawable 如下:
<!-- res/drawable/chapter_07_frame_animation.xml -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/summer_01" android:duration="500" />
<item android:drawable="@drawable/summer_02" android:duration="500" />
<item android:drawable="@drawable/summer_03" android:duration="500" />
</animation-list>
然后在代码中应用上面的动画如下:
ImageView mImage04 = (ImageView) findViewById(R.id.iv_frame_anim);
mImage04.setBackgroundResource(R.drawable.chapter_07_frame_animation);
AnimationDrawable drawable = (AnimationDrawable) mImage04.getBackground();
drawable.start();
运行效果:
帧动画使用简单,但容易引起 OOM,应尽量少使用过多尺寸大的图片。
7.2 View 动画的特殊使用场景
View 动画还可以在一些特殊场景使用,如在 ViewGroup 中控制子元素的出场效果,在 Activity 中实现其切换效果。
7.2.1 LayoutAnimation
LayoutAnimation 也是一个 View 动画,作用于 ViewGroup,为 ViewGroup 指定一个动画,控制子元素的出场动画效果。使用步骤如下:
1. 定义 LayoutAnimation
<!-- res/anim/chapter_07_view_animation_layout.xml -->
<!-- delay 子元素开始动画的时间延迟
animationOrder 子元素的动画顺序
animation 为子元素指定具体的入场动画-->
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="normal"
android:animation="@anim/chapter_07_view_animation_layout_item"/>
2. 为子元素指定具体的入场动画
<!-- res/anim/anim/chapter_07_view_animation_layout_item.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
<translate
android:fromXDelta="500"
android:toXDelta="0"/>
</set>
3. 为 ViewGroup 指定 android:layoutAnimation
属性
<!-- 指定 layoutAnimation 属性,ListView 的 item 就具有出场动画了
这种方式适用于所有的 ViewGroup -->
<ListView
android:id="@+id/lv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/chapter_07_view_animation_layout"
android:background="#fff4f7f9"
android:cacheColorHint="#00000000"
android:divider="#dddbdb"
android:dividerHeight="1.0px"
android:listSelector="@android:color/transparent"/>
当然也可以通过 LayoutAnimationController
在代码中实现:
// 为 listView 指定入场动画
Animation animation = AnimationUtils.loadAnimation(this, R.anim.chapter_07_view_animation_layout_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
mListView.setLayoutAnimation(controller);
运行效果:
7.2.2 Activity 的切换效果
Activity 有默认的切换效果,也可用 overridePendingTransition(int enterAnim, int exitAnim)
自定义切换效果,这个方法必须在 startActivity(Intent)
或 finish()
后调用才能生效,其两参数含义如下:
enterAnim
Acitivity 被打开时所需的动画资源 id;exitAnim
Activity 被暂停时所需的动画资源 id。
下面举个例子,创建动画xml文件如下:
<!-- res/anim/enter_anim.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="100%"
android:toYDelta="0"
android:duration="1000" />
<!-- res/anim/exit_anim.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0"
android:toYDelta="100%"
android:duration="1000" />
在代码中使用如下:
// 启动 AnimViewActivity 时
IntentUtils.to(this, AnimViewActivity.class);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
@Override
public void finish() {
super.finish();
// 退出 AnimViewActivity 时
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}
运行效果:
Fragment 可通过 FragmentTransaction
中的 setCustomAnimations()
方法来添加切换动画。
7.3 属性动画
属性动画是 API 11 新加入的特性,对作用对象进行了扩展,可对任何对象做动画。在 API 11 之前的系统上使用属性动画,可采用开源动画库 nineoldandroids。
7.3.1 使用属性动画
下面先简单介绍几个例子看看如何使用属性动画。
(1)
// 改变一个对象的 translationY 属性,让其沿着 Y 轴向上平移一段距离
ObjectAnimator.ofFloat(ivObject01, "translationY", -ivObject01.getHeight()).start();
(2)
// 改变一个对象的背景色属性
ValueAnimator colorAnim = ObjectAnimator.ofInt(tvObject02, "backgroundColor",0xFFFF8080, 0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();
(3)
// 动画集合,5 秒内对 View 的旋转、平移、缩放、透明度改变
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(ivObject03, "rotationX", 0, 360),
ObjectAnimator.ofFloat(ivObject03, "rotationY", 0, 180),
ObjectAnimator.ofFloat(ivObject03, "rotation", 0, -90),
ObjectAnimator.ofFloat(ivObject03, "translationX", 0, 90),
ObjectAnimator.ofFloat(ivObject03, "translationY", 0, 90),
ObjectAnimator.ofFloat(ivObject03, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(ivObject03, "scaleY", 1, 0.5f),
ObjectAnimator.ofFloat(ivObject03, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();
运行效果:
属性动画也可以通过 XML 来定义(res/animator 目录下),其语法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- set标签 动画集合,对应 AnimationSet 类,可包含若干个动画
ordering 1. together 同时播放(默认) 2. sequentially 依次播放 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<!-- objectAnimator标签 对应 ObjectAnimator 类
propertyName 作用对象的属性的名称(若指定颜色则不需要指定属性valueType)
duration 动画的时长
valueFrom 属性的起始值
valueTo 属性的结束值
startOffset 动画的延迟时间
repeatCount 动画的重复次数(默认0,-1 表无限循环)
repeatMode 动画的重复模式
valueType propertyName 所指定的属性的类型
1. intType 整型 2.floatType 浮点型-->
<objectAnimator
android:propertyName="string"
android:duration="1"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="1"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<!-- animator 标签 对应 ValueAnimator 类
其属性含义和 objectAnimator 的一样 -->
<animator
android:duration="1"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="1"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<set>
<!-- ... -->
</set>
</set>
下面举个例子,定义属性动画如下:
<!-- res/animator/property_animator.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:propertyName="x"
android:duration="300"
android:valueTo="200"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="300"
android:valueTo="200"
android:valueType="intType"/>
</set>
然后在代码中应用上面的动画如下:
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.property_animator);
animatorSet.setTarget(ivObject04);
animatorSet.start();
在实际开发中建议采用代码来实现属性动画。
7.3.2 理解插值器和估值器
TimeInterpolator,时间插值器,其作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有:
LinearInterpolator
线性插值器:匀速动画AccelerateDecelerateInterpolator
加速减速插值器:动画两头慢中间快DecelerateInterpolator
减速插值器:动画越来越慢
TypeEvaluator,类型估值算法,即估值器,其作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有:
- IntEvaluator 针对整形属性
- FloatEvaluator 浮点型
- ArgbEvaluator Color
例子,一个匀速动画,采用线性插值器和整形估值算法,在 40ms内,View 的 x 属性实现从0到40的转变:
自定义插值器需实现 Interpolator
或 TimeInterpolator
,自定义估值算法需实现 TypeEvaluator
。
7.3.3 属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:
- AnimatorListener
public static interface AnimatorListener {
/**
* 动画开始
*/
void onAnimationStart(Animator animation);
/**
* 动画结束
*/
void onAnimationEnd(Animator animation);
/**
* 动画取消
*/
void onAnimationCancel(Animator animation);
/**
* 动画重复播放
*/
void onAnimationRepeat(Animator animation);
}
针对上述方法,系统还提供了类 AnimatorListenerAdapter
,用于有选择性的实现上面的4个方法。
- AnimatorUpdateListener
public static interface AnimatorUpdateListener {
/**
* 监听整个动画过程,每播放一帧,此方法调用一次
*/
void onAnimationUpdate(ValueAnimator animation);
}
7.3.4 对任意属性做动画
情景:实现让Button的宽度从当前宽度增加到500px的动画。用属性动画如下(View 动画不支持对宽度进行动画):
@Override
public void onClick(View v){
if(v == mButton){
ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
}
}
运行后发现没效果(下面解释)。
对 object 的属性 O 做动画,让动画生效需同时满足两个条件:
(1)object 必须提供 setO 方法,若没传递初始值,还需提供 getO 方法。(不满足,程序直接 Crash)
(2)object 的 setO 对属性 O 所做的改变必须能通过某种方法反映除了,如会改变 UI 之类的。(不满足,动画无效果但不会 Crash)
Button 继承 TextView 有 setWidth
方法,但 TextView 和 Button 的 setWidth
、getWidth
做的不是同一件事情,无法通过 setWidth
改变控件宽度。
因此上面情景中满足了条件1而未满足条件2,动画不生效。
针对上面问题,有如下3中解决方法:
1. 给你的对象加上 get
和 set
方法,如果你有权限的话。
2. 用一个类来包装原始对象,间接为其提供 get
和 set
方法。
具体代码如下:
@Override
public void onClick(View v){
if(v == mButton){
// 先将 Button 包装
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper , "width", 500).setDuration(5000).start();
}
}
/**
* 此类用于包装 View,间接为 View 提供 get 和 set 方法
*/
private static class ViewWrapper{
private View mTarget;
public ViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
// 修改 target 的宽度
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
运行效果:
3. 采用 ValueAnimator,监听动画过程,自己实现属性的改变。
ValueAnimator 本身不作用于任何对象,即直接使用它无动画效果。它可对一个值做动画,监听其动画过程,修改对象的属性值,从而达到对象的动画效果。具体代码如下:
@Override
public void onClick(View v){
if(v == mButton){
performAnimate(mButton, mButton.getWidth(), 500);
}
}
private void performAnimate(final View target, final int start, final int end){
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// 持有一个 IntEvaluator 对象,方便下面估值时使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得当前动画的进度值,整形,1-100之间
int currentValue = (int) animation.getAnimatedValue();
// 获得当前进度占整个动画过程的比例,浮点型,0-1之间
float fraction = animation.getAnimatedFraction();
// 直接调用整型估值器,通过比例计算出宽度,然后再设给 Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
运行效果和上面一样。
7.3.5 属性动画的工作原理
属性动画要求动画作用的对象提供该属性的 set
方法,属性动画根据传递该属性的初始值和最终值,以动画的效果多次去调用 set
方法。
每次传递给 set
方法的值都不一样,确切来时是随着时间的推移,所传递的值越来越接近最终值。
若动画时没传递初始值,则还要提供 get
方法,因为系统要去获取属性的初始值。
7.4 使用动画的注意事项
1. OOM 问题
在帧动画中使用较多的大图片时容易出现 OOM,尽量避免使用帧动画。
2. 内存泄漏
在 Activity 退出时及时停止属性动画中的无限循环动画。
3. 兼容性问题
动画在 3.0 以下系统有兼容性问题,要做好适配工作。
4. View 动画的问题
View 动画并不是真正改变 View 的状态,有时完成动画后无法隐藏 View,即 setVisibility(View.GONE)
无效,此时只要调用 view.clearAnimation()
清除即可。
5. 不要使用 px
执行动画过程中尽量使用 dp,避免在不同设备上出现不同的效果。
6. 动画元素的交互
将 view 移动后,在 3.0 以下系统上,不管是 View 动画还是属性动画,新位置均无法触发点击事件,但老位置仍然可以触发点击事件。
7. 硬件加速
使用动画过程中,建议开启硬件加速,提高流畅性。
本篇文章就介绍到这。