提起动画,无论是哪种语言哪种系统框架,比如说android、iOS、H5、Flash等,动画在其之中都扮演着举足轻重的角色。Android系统中最常用的动画方式有三种:
- 补间动画(Tween Animation)
- 帧动画(Frame Animation)
- 属性动画(Property Animation)
本文就总结一下补间动画的相关玩法。
Android动画系列:
什么是补间动画
Creates an animation by performing a series of transformations on a single image with an Animation.
Tween Animation,通过 Animation 对象在图像上执行一系列的变换而形成的动画。举例来说,就是 Tween Animation 可以改变界面上显示控件的状态,如 Button 的显示、隐藏,ImageView 的尺寸缩放等等。
补间动画分类
补间动画包括五类动画,分别是:
- AlphaAnimation,主要用于控制 View 的可见性(显示|隐藏)。
- ScaleAnimation,主要用于缩放 View 大小。
- TranslateAnimation,主要用于移动 View 的位置。
- RotateAnimation,主要用于旋转 View。
- AnimationSet,某些场景仅靠上面单一类型的动画是无法实现的,需要多个类型的动画组合才能达到最终的效果,AnimationSet 的主要作用就是组合各类 Tween Animation。
补间动画的实现形式
补间动画的实现形式有两种:xml创建和code实现。其中xml创建的xml动画文件要放在res/anim/
目录下。
- res/anim/
目录下放的是视图动画的XML实现和布局动画(LayoutAnimation) - res/animations/
目录下存放的是属性动画的XML实现 - res/drawable/
目录下存放的是帧动画的XML实现
AlphaAnimation
通过XML创建
语法
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="integer"
android:fillAfter="true|false"
android:fillBefore="true|false"
android:fillEnabled="true|false"
android:interpolator="@[package:]anim/interpolator_resource"
android:repeatCount="infinite|integer"
android:repeatMode="reverse|restart"
android:fromAlpha="float"
android:toAlpha="float" />
示例
XML定义:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/integer_one_thousand_and_two_hundred"
android:fillAfter="true"
android:fromAlpha="@integer/integer_one"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toAlpha="@integer/integer_zero" />
代码调用:
Button mButton = (Button) findViewById(R.id.Button);
// 创建动画对象,并传入设置的动画效果xml文件
Animation alphaAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation_alpha);
// 播放动画
mButton.startAnimation(alphaAnimation);
其他动画XML定义形式在代码调用这部分也是类似。
通过代码实现
语法
AlphaAnimation alphaAnimation = new AlphaAnimation(float fromAlpha, float toAlpha);
alphaAnimation.setInterpolator(Interpolator i);
alphaAnimation.setDuration(long durationMillis);
AnimationTarget.startAnimation(alphaAnimation);
示例
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.1f);
alphaAnimation.setInterpolator(new AccelerateInterpolator());
alphaAnimation.setFillAfter(mIsSaveAnimationState);
alphaAnimation.setDuration(800);
mTarget.startAnimation(alphaAnimation);
ScaleAnimation
通过XML创建
语法
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="integer"
android:fillAfter="true|false"
android:fillBefore="true|false"
android:fillEnabled="true|false"
android:interpolator="@[package:]anim/interpolator_resource"
android:repeatCount="infinite|integer"
android:repeatMode="reverse|restart"
android:fromXScale="float"
android:fromYScale="float"
android:toXScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float"
/>
示例
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/integer_one_thousand_and_two_hundred"
android:fillAfter="true"
android:fromXScale="@fraction/percent_one_hundred"
android:fromYScale="@fraction/percent_one_hundred"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="@fraction/percent_fifty"
android:pivotY="@fraction/percent_fifty"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toXScale="@fraction/percent_two_hundred"
android:toYScale="@fraction/percent_two_hundred" />
通过代码实现
语法
ScaleAnimation scaleAnimation = new ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue);
scaleAnimation.setInterpolator(Interpolator i);
scaleAnimation.setDuration(long durationMillis);
AnimationTarget.startAnimation(scaleAnimation);
示例
ScaleAnimation scaleAnimation = new ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new AccelerateInterpolator());
scaleAnimation.setFillAfter(mIsSaveAnimationState);
scaleAnimation.setDuration(800);
mTarget.startAnimation(scaleAnimation);
TranslateAnimation
通过XML创建
语法
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="integer"
android:fillAfter="true|false"
android:fillBefore="true|false"
android:fillEnabled="true|false"
android:interpolator="@[package:]anim/interpolator_resource"
android:repeatCount="infinite|integer"
android:repeatMode="reverse|restart"
android:fromXDelta="float"
android:fromYDelta="float"
android:toXDelta="float"
android:toYDelta="float"
/>
示例
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1200"
android:fillAfter="true"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@anim/overshoot_interpolator"
android:toXDelta="0"
android:toYDelta="50%p" />
通过代码实现
语法
TranslateAnimation translateAnimation = new TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta);
translateAnimation.setInterpolator(Interpolator i);
translateAnimation.setDuration(long durationMillis);
AnimationTarget.startAnimation(translateAnimation);
示例
TranslateAnimation translateAnimation = new TranslateAnimation(0f, 200f, 0f, 200f);
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setFillAfter(mIsSaveAnimationState);
translateAnimation.setDuration(800);
mTarget.startAnimation(translateAnimation);
RotateAnimation
通过XML创建
语法
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="integer"
android:fillAfter="true|false"
android:fillBefore="true|false"
android:fillEnabled="true|false"
android:interpolator="@[package:]anim/interpolator_resource"
android:repeatCount="infinite|integer"
android:repeatMode="reverse|restart"
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float"
/>
示例
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1200"
android:fillAfter="true"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toDegrees="360" />
通过代码实现
语法
RotateAnimation rotateAnimation = new RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue);
rotateAnimation.setInterpolator(Interpolator i);
rotateAnimation.setDuration(long durationMillis);
AnimationTarget.startAnimation(rotateAnimation);
示例
RotateAnimation rotateAnimation = new RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue);
rotateAnimation.setInterpolator(Interpolator i);
rotateAnimation.setDuration(long durationMillis);
AnimationTarget.startAnimation(rotateAnimation);
AnimationSet
通过XML创建
语法
<?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>
示例
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/integer_three_thousand"
android:fillAfter="true"
android:shareInterpolator="true">
<translate
android:fromXDelta="@integer/integer_zero"
android:fromYDelta="@integer/integer_zero"
android:toXDelta="@integer/integer_zero"
android:toYDelta="@integer/integer_two_hundred" />
<alpha
android:fromAlpha="@integer/integer_one"
android:toAlpha="@fraction/scale_smaller" />
<rotate
android:fromDegrees="@integer/integer_zero"
android:pivotX="@fraction/percent_fifty"
android:pivotY="@fraction/percent_fifty"
android:toDegrees="@integer/integer_seven_hundred_and_five" />
</set>
通过代码实现
语法
AnimationSet animationSet = new AnimationSet(boolean shareInterpolator);
animationSet.addAnimation(Animation a)
...
AnimationTarget.startAnimation(animationSet);
示例
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.5f);
alphaAnimation.setInterpolator(new AccelerateInterpolator());
alphaAnimation.setFillAfter(mIsSaveAnimationState);
alphaAnimation.setDuration(800);
ScaleAnimation scaleAnimation = new ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new AccelerateInterpolator());
scaleAnimation.setFillAfter(mIsSaveAnimationState);
scaleAnimation.setDuration(800);
RotateAnimation rotateAnimation = new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setInterpolator(new AccelerateInterpolator());
rotateAnimation.setFillAfter(mIsSaveAnimationState);
rotateAnimation.setDuration(800);
AnimationSet animationSet = new AnimationSet(false);
animationSet.setFillAfter(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(rotateAnimation);
mTarget.startAnimation(animationSet);
动画监听
Animation类通过监听动画开始 / 结束 / 重复时刻可以进行一系列自定义操作,如跳转页面等等。通过在 Java 代码里setAnimationListener()方法设置:
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个方法,有些时候我们只需要实现其中一个,因此其余的很累赘,我们可以采用动画适配器AnimatorListenerAdapter来针对化的实现:
// 向addListener()方法中传入适配器对象AnimatorListenerAdapter()
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// 如想只想监听动画开始时刻,就只需要单独重写该方法就可以
}
});
插值器
细心的同学观察到以上动画属性中几乎都存在一个插值器属性。插值器是动画执行速率调节器,主要用来控制动画的变化率。
常用的插值器汇总
自定义插值器
自定义插值器同样可以有两种方式:XML和CODE。
通过XML自定义
通过 XML 自定义插值器的时候,限制性比较大,因为系统只提供了部分插值器的自定义,如 AccelerateInterpolator,有些插值器是不支持自定义的,如 AccelerateDecelerateInterpolator。
我们看看AccelerateInterpolator如何自定义,AccelerateInterpolator中可以自定义的属性只有:android:factor
,表示加速的比率(The acceleration rate),默认值为 1。
<!-- custom accelerateInterpolator -->
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="4.0" />
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1200"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="false"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/custom_accelerate_interpolator"
android:toXDelta="40%p"
android:toYDelta="0" />
可以通过 XML 自定义插值器,除了 AccelerateInterpolator,还有很多,以下是具体列表:
通过代码自定义
通过 CODE 自定义插值器就没有那么多限制, 也很简单,只要实现 Interpolator 接口,并实现其中的方法(getInterpolation)就好了。接下来,我们先看下 Google 官方是如何实现插值器的。
//AccelerateDecelerateInterpolator
package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*/
@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();
}
}
通过代码可知,AccelerateDecelerateInterpolator 是通过余弦函数实现的。在 AccelerateDecelerateInterpolator 中,加速的过程是函数曲线斜率逐渐增大的过程,减速的过程是函数曲线斜率逐渐减小的过程。
明白了AccelerateDecelerateInterpolator插值器原理之后,我们用正切函数实现一个 DecelerateAccelerateInterpolator。(快-慢-快)
1.Interpolator 接口中 getInterpolation 方法中 input 的取值范围为 [0,1],而蓝色框圈出的 X 的取值范围为 [-π/4,π/4],所以,需要将 [0,1] 转换为 [-π/4,π/4]:
π/2 * input - π/4
2.正切函数在 [-π/4,π/4] 取值范围内,相应的函数值的取值范围为[-1,1],而 getInterpolation 最终返回值的取值范围为 [0,1],所以,需要将 [-1,1] 转换为 [0,1]:
(tan(π/2 * input - π/4) + 1)/2
所以代码实现过程:
//自定义
public class DecelerateAccelerateInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) ((Math.tan(Math.PI/2 * input - Math.PI/4) + 1)/2);
}
}
//调用
TranslateAnimation translateAnimation = new TranslateAnimation(0f, 0f, 0f, 800f);
translateAnimation.setInterpolator(new DecelerateAccelerateInterpolator());
translateAnimation.setFillAfter(mIsSaveAnimationState);
translateAnimation.setDuration(1800);
mTarget.startAnimation(translateAnimation);
使用场景
补间动画常用于视图View的一些标准动画效果:平移、旋转、缩放,透明度等,除了常规的动画使用,补间动画还有一些特殊的应用场景,例如Activity和Fragment的切换动效以及视图组(ViewGroup)中子元素的出场效果。
Activity 的切换效果
页面进入动画:
Intent intent = new Intent (this,Acvtivity.class);
startActivity(intent);
// enter_anim(b页面的进场动画),exit_anim(a页面的消失动画)
// 特别注意:overridePendingTransition()必须要在startActivity(intent)后被调用才能生效
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);
页面退出动画:
@Override
public void finish(){
super.finish();
// 特别注意: overridePendingTransition()必须要在finish()后被调用才能生效
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);
}
系统自带的效果android.R.anim.xxx
:
// 淡入淡出的动画效果
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
// 从左向右滑动的效果
overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
Fragment 切换效果
系统自带的切换效果:
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
// 通过setTransition(int transit)进行设置
// transit参数说明
// 1. FragmentTransaction.TRANSIT_NONE:无动画
// 2. FragmentTransaction.TRANSIT_FRAGMENT_OPEN:标准的打开动画效果
// 3. FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:标准的关闭动画效果
// 标准动画设置好后,在Fragment添加和移除的时候都会有。
fragmentTransaction.setTransition(int transit);
自定义动画效果:
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
// 采用`FragmentTransavtion`的 `setCustomAnimations()`进行设置
fragmentTransaction.setCustomAnimations(R.anim.in_from_right,R.anim.out_to_left);
视图组(ViewGroup)中子元素的出场效果
有些时候我们想为ViewGroup中的子元素的出场统一动画规则,那么我们可以这样做:
1.定义子元素统一出场动画:
<?xml version="1.0" encoding="utf-8"?>
// 此处采用了组合动画
<set xmlns:android="http://schemas.android.com/apk/res/android" >
android:duration="3000"
<alpha
android:duration="1500"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
<translate
android:fromXDelta="500"
android:toXDelta="0"
/>
</set>
2.定义视图组(ViewGroup)动画管理规则:
<?xml version="1.0" encoding="utf-8"?>
// 采用LayoutAnimation标签
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
// 子元素开始动画的时间延迟
// 如子元素入场动画的时间总长设置为300ms
// 那么 delay = "0.5" 表示每个子元素都会延迟150ms才会播放动画效果
// 第一个子元素延迟150ms播放入场效果;第二个延迟300ms,以此类推
android:delay="0.5"
// 表示子元素动画的顺序
// 可设置属性为:
// 1. normal :顺序显示,即排在前面的子元素先播放入场动画
// 2. reverse:倒序显示,即排在后面的子元素先播放入场动画
// 3. random:随机播放入场动画
android:animationOrder="normal"
// 设置入场的具体动画效果
// 将步骤1的子元素出场动画设置到这里
android:animation="@anim/view_animation"
/>
3.为视图组(ViewGroup)指定andorid:layoutAnimation属性,这里有两种方式:xml设置和code设置。
方式1:在 XML 中指定:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical" >
<ListView
android:id="@+id/listView1"
android:layoutAnimation="@anim/anim_layout"
// 指定layoutAnimation属性用以指定子元素的入场动画
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
方式2:在Java代码中指定【这样就不用额外设置res/ anim /anim_layout.xml该xml文件了】:
ListView lv = (ListView) findViewById(R.id.listView1);
// 加载子元素的出场动画
Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_item);
// 设置LayoutAnimation的属性
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
// 为ListView设置LayoutAnimation的属性
lv.setLayoutAnimation(controller);
参考
- https://developer.android.google.cn/guide/topics/resources/animation-resource#Tween
- https://juejin.cn/post/6844903793713233927#heading-64
- https://blog.csdn.net/carson_ho/article/details/72827747