Android View 动画

View 动画框架

使用 View 动画框架可以在 View 上执行补间动画。
补间动画是指,只要指定动画的开始与结束的“关键帧”,而动画变化的“中间帧”由系统计算并补齐。
动画并没有改变 View 的实际位置,仅是改变了 View 的显示位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
FILE LOCATION:
res/anim/filename.xml
The filename will be used as the resource ID.
COMPILED RESOURCE DATATYPE:
Resource pointer to an Animation.
RESOURCE REFERENCE:
In Java: R.anim.filename
In XML: @[package:]anim/filename

文件位置:
res /anim/ filename.xml
文件名将用作资源ID。
编制资源数据类型:
指向动画的资源指针。
资源引用:
在Java: R.anim.filename
在XML: @包:anim/文件名

// XML使用
// res /anim/ anim_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/anim_file"
android:animationOrder="normal"
android:delay="0.5">

<!--android:delay="0.5" 子View动画显示的延迟时间比例-->
<!--比如动画执行时间是300ms,延迟比例是0.5,那么延迟时间就是150ms-->

<!--android:animationOrder="normal" 表示动画执行的顺序类型,共有三种-->
<!--normal表示子View按顺序显示,reverse表示子View按逆序显示,random表示子View随机先后显示。-->

</layoutAnimation>

// res /layout/ activity_main.xml
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutAnimation="@anim/anim_layout"
android:divider="#f1f1f1"
android:dividerHeight="1dp">

</ListView>

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?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"] >

<!--<set>标签表示补间动画的集合,对应于AnimationSet类。-->

<!--android:interpolator-->
<!--Interpolator resource. 设置动画集合所采用的插值器,默认值为@android:anim/accelerate_decelerate_interpolator-->

<!--android:shareInterpolator-->
<!--Boolean. 表示集合中的动画是否共享集合的插值器。当值为true且集合没有设置插值器,-->
<!--此时集合中的动画就会使用默认的插值器@android:anim/accelerate_decelerate_interpolator,-->
<!--但也可以为集合中的动画单独指定所需的插值器。-->

<--透明度动画-->
<alpha
android:fromAlpha="float" // 透明度初始值。其中,0.0是完全透明,1.0是完全不透明。
android:toAlpha="float" // 透明度结束值。其中,0.0是完全透明,1.0是完全不透明。
/>

<--缩放动画-->
<scale
android:fromXScale="float" // 水平方向缩放比例的初始值,其中1.0是没有任何变化。
android:toXScale="float" // 水平方向缩放比例的结束值,其中1.0是没有任何变化。
android:fromYScale="float" // 竖直方向缩放比例的初始值,其中1.0是没有任何变化。
android:toYScale="float" // 竖直方向缩放比例的结束值,其中1.0是没有任何变化。
android:pivotX="float" // 缩放中心点的x坐标
android:pivotY="float" // 缩放中心点的y坐标
/>

<--平移动画-->
<translate
android:fromXDelta="float or percentage"
// 移动起始点的x坐标,表现形式有三种;
// 1.相对于自己的左边界的距离,单位像素值。(例如 "5")
// 2.相对于自己的左边界的距离与自身宽度的百分比。(例如 "5%")
// 3.相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:toXDelta="float or percentage"
// 移动结束点的x坐标,表现形式同上;
android:fromYDelta="float or percentage"
// 移动起始点的y坐标,表现形式有三种;
// 1.相对于自己的上边界的距离,单位像素值。(例如 "5")
// 2.相对于自己的上边界的距离与自身高度的百分比。(例如 "5%")
// 3.相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p")
android:toYDelta="float or percentage"
// 移动结束点的y坐标,表现形式同上;
/>

<--旋转动画-->
<rotate
android:fromDegrees="float" // 旋转初始的角度
android:toDegrees="float" // 旋转结束的角度
android:pivotX="float or percentage"
// 旋转中心点x坐标,表示形式有三种:
// 1.相对于自己的左边界的距离,单位像素值。(例如 "5")
// 2.相对于自己的左边界的距离与自身宽度的百分比。(例如 "5%")
// 3.相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:pivotY="float or percentage"
// 旋转中心点y坐标,表示形式有三种:
// 1.相对于自己的上边界的距离,单位像素值。(例如 "5")
// 2.相对于自己的上边界的距离与自身宽度的百分比。(例如 "5%")
// 3.相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p")
/>

<set>
...
</set>

</set>

四种动画示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private Animation animation;

private TextView tvAlpha,tvScale,tvTranslate,tvRotate;
private ImageView iv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
// animation = AnimationUtils.loadAnimation(this,R.anim.anim_alpha);
// 因为有动画链的原因,假定有一个移动的动画紧跟一个淡出的动画,
// 如果不把移动的动画的setFillAfter置为true,那么移动动画结束后,View会回到原来的位置淡出,
// 就像重置了一样,动画执行完,回到初始样式。
// 如果setFillAfter置为true, 就会在移动动画结束的位置淡出
// 也就是动画执行完,会保持动画执行后的样式。
// animation.setFillAfter(true);
// iv.startAnimation(animation);

}

private void initView() {
iv=findViewById(R.id.iv_main_anim);
tvAlpha=findViewById(R.id.tv_main_alpha);
tvScale=findViewById(R.id.tv_main_scale);
tvTranslate=findViewById(R.id.tv_main_translate);
tvRotate=findViewById(R.id.tv_main_rotate);

tvAlpha.setOnClickListener(this);
tvScale.setOnClickListener(this);
tvTranslate.setOnClickListener(this);
tvRotate.setOnClickListener(this);
}

@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_main_alpha:
animation = AnimationUtils.loadAnimation(this,R.anim.anim_alpha);
iv.startAnimation(animation);
break;
case R.id.tv_main_scale:
animation = AnimationUtils.loadAnimation(this,R.anim.anim_scale);
iv.startAnimation(animation);
break;
case R.id.tv_main_translate:
animation = AnimationUtils.loadAnimation(this,R.anim.anim_translate);
iv.startAnimation(animation);
break;
case R.id.tv_main_rotate:
animation = AnimationUtils.loadAnimation(this,R.anim.anim_ratate);
iv.startAnimation(animation);
break;
default:
break;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 透明度动画
**/
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="2000" />
</set>

/**
* 缩放动画
**/
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="0.0"
android:toYScale="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:duration="2000"/>
</set>

/**
* 平移动画
**/
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="20"
android:fromYDelta="20"
android:toXDelta="100"
android:toYDelta="100"
android:duration="2000" />
</set>

/**
* 旋转动画
**/
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0"
android:toDegrees="-360"
android:pivotY="50%"
android:pivotX="50%"
android:duration="2000" />
</set>

特殊场景示例

LayoutAnimation

通过布局动画(LayoutAnimation)给 ViewGroup 的子 View 指定入场动画。

也是一个 View 动画,作用于 ViewGroup,这样它的子元素出场时都会具有这种动画效果。

使用步骤

  1. 先写一个 tween 动画 xml
  2. 在写一个专门的 layoutAnimation xml 文件
  3. 最后给根节点的 viewGroup 设置上这个定义的 layoutAnimation

除了在 XML 中指定 LayoutAnimation 外,还可通过 LayoutAnimationController 来实现。

ListView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* res/anim/zoom_in.xml
**/
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="@android:anim/decelerate_interpolator">

<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="500" />

<!--从右向左-->
<scale
android:fromXScale="0.1"
android:fromYScale="0.1"
android:toYScale="1.0"
android:toXScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500" />

<!--从下至上-->
<!--<translate-->
<!--android:duration="500"-->
<!--android:fromYDelta="100%p"-->
<!--android:toYDelta="0"-->
<!--/>-->

</set>

1.通过xml的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* activity_main.xml
**/
<!--xml中使用动画-->
<ListView
android:id="@+id/lv_main"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/anim_layout"
android:divider="#f1f1f1"
android:dividerHeight="1dp"
android:scrollbars="none">

</ListView>

/**
* res/anim/anim_layout.xml
**/
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/zoom_in"
android:animationOrder="random"
android:delay="0.5">

<!--android:delay="0.5" 子View动画显示的延迟时间比例-->
<!--比如动画执行时间是300ms,延迟比例是0.5,那么延迟时间就是150ms-->

<!--android:animationOrder="normal" 表示动画执行的顺序类型,共有三种-->
<!--normal表示子View按顺序显示,reverse表示子View按逆序显示,random表示子View随机先后显示。-->

</layoutAnimation>

2.通过动态代码的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private TextView tvListview;
private ListView lv;

private String datas[]={ "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" };
private ArrayAdapter<String> adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {

adapter=new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,datas);
lv.setAdapter(adapter);
LayoutAnimationController lac = new LayoutAnimationController(AnimationUtils.loadAnimation(this,R.anim.zoom_in));
lac.setDelay(0.5f);
lac.setOrder(LayoutAnimationController.ORDER_RANDOM);
lv.setLayoutAnimation(lac);
lv.setLayoutAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,"end",Toast.LENGTH_SHORT).show();
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

}

private void initView() {
tvListview=findViewById(R.id.tv_main_listview);
lv=findViewById(R.id.lv_main);

tvListview.setOnClickListener(this);
}

@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_main_listview:
lv.startLayoutAnimation();
break;
default:
break;
}
}
}

RecyclerView

对于给RecyclerView添加初始动画的简单方法有几种:

  • 实现一个自定义的ItemAnimator
  • 在Adapter的onBindViewHolder()方法中添加动画
  • 采用LayoutAnimation;使用方式与上述ListView的差不多,代码在demo中。

GridView

对于GridView,为了展示效果更好,可以使用GridLayoutAnimation来实现。
主要就是动画文件的不同,其它类似。代码在demo中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<gridLayoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/item_anim_gridview"
android:animationOrder="normal"
android:startOffset="700"
android:columnDelay="15%"
android:rowDelay="15%"
android:direction="top_to_bottom|left_to_right"
/>
<!--android:animation="@anim/item_animation_from_bottom”-->
<!--定义布局中的每个item所要应用的动画-->

<!--android:animationOrder="normal"-->
<!--可以选择三种类型:normal, reverse 以及 random。它控制内容动画的顺序。Normal:遵循direction和delay所定义的顺序;Reverse的顺序恰好跟Normal相反;Random为随机的顺序。-->

<!--android:columnDelay=”15%"-->
<!--应用到每列上的动画延迟,定义为item动画持续时间的百分比。-->

<!--android:rowDelay=”15%"-->
<!--应用到每行上的动画延迟,定义为item动画持续时间的百分比。-->

<!--android:direction=”top_to_bottom|left_to_right"-->
<!--定义动画执行的方向。这里动画将从左上角开始,移动到右下角。如果定义为top_to_bottom|right_to_left,那么将从右上角开始移动到左下角。-->

<!--对于一个GridLayoutAnimation来说,每个item最终的延迟是根据行和列的延迟以及方向计算出来的:-->
<!--itemAnimationDuration = 300ms-->
<!--rowDelay = 10% (30ms)-->
<!--columnDelay = 10% (30ms)-->

<!--把行和列的延迟设置为相同值可以让item动画的执行是对称的(沿对角线)。-->

为 Activity 自定义切换动画

1.通过调用 Activity 类的 overridePendingTransition(int enterAnim, int exitAnim) 方法可以实现自定义Activity 的切换动画,注意这个方法必须在 startActivity 和 finish 调用之后被调用,否者没有效果。

Fragment(API 11 引入) 可以通过 FragmentTransaction 中的 setCustomAnimations() 来添加切换动画(View 动画)。
注意,要使用 support-v4 兼容包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CustomSwitchingActivity extends AppCompatActivity{

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
overridePendingTransition(R.anim.custom_in,0);
setContentView(R.layout.activity_custom_switching);

}

public void back(View view){
finish();
overridePendingTransition(0, R.anim.custom_out);
}


@Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(0, R.anim.custom_out);
}

}

2.使用style的方式定义Activity的切换动画
貌似问题很多

自定义 View 动画

对于 View 动画,建议采用 XML 来定义动画,因为 XML 格式的动画可读性更好。

需要继承 Animation 这个抽象类,并重写 initialize() 和 applyTransformation()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Rotate3dAnimation rotate3dAnimation = new Rotate3dAnimation(0,360,100,100,100,true);
rotate3dAnimation.setDuration(2000);
// 重复次数
rotate3dAnimation.setRepeatCount(1);
// 动画结束,停留在原地
rotate3dAnimation.setFillAfter(true);
rotate3dAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.e(TAG,"onAnimationStart");
}

@Override
public void onAnimationEnd(Animation animation) {
Log.e(TAG,"onAnimationEnd");
}

@Override
public void onAnimationRepeat(Animation animation) {
Log.e(TAG,"onAnimationRepeat");
}
});
tv.startAnimation(rotate3dAnimation);

备注

欢迎关注微信公众号:非也缘也