Android 属性动画

属性动画框架

属性动画可以使用 ViewPropertyAnimator、ObjectAnimator、ValueAnimator 这三种 Animator。
它们其实是一种递进的关系:从左到右依次变得更加难用,也更加灵活。但它们的性能是一样的,因为 ViewPropertyAnimator 和 ObjectAnimator 的内部实现其实都是 ValueAnimator,ObjectAnimator 更是本来就是 ValueAnimator 的子类,它们三个的性能并没有差别。
它们的差别只是使用的便捷性以及功能的灵活性。所以在实际使用时候的选择,只要遵循一个原则就行:尽量用简单的。能用 View.animate() 实现就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator。

属性动画要求动画作用的对象提供该属性的 set 方法,属性动画根据传递的该属性的初始值和最终值,以动画的效果多次去调用 set 方法。
每次传递给 set 方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。
如果动画的时候没有传递初始值,那么还要提供 get 方法,因为系统要去获取属性的初始值。

工作原理可以总结为三步:

  1. 创建动画时,如果没有设置属性的初始值,此时 Android 系统会通过该属性的 get 方法获取初始值。(ObjectAnimator)
  2. 动画播放过程中,属性动画框架会利用时间流逝的百分比获取属性值改变的百分比(即通过时间插值器),接着利用获取的属性值改变的百分比获取改变后的属性值(即通过类型估值器)。(ObjectAnimator,ValueAnimator)
  3. 通过该属性的 set 方法将改变后的属性值设置到对象中,并将这种改变用动画得形式表现出来。(ObjectAnimator)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FILE LOCATION:
res/animator/filename.xml
The filename will be used as the resource ID.
COMPILED RESOURCE DATATYPE:
Resource pointer to a ValueAnimator, ObjectAnimator, or AnimatorSet.
RESOURCE REFERENCE:
In Java: R.animator.filename
In XML: @[package:]animator/filename

文件位置:
res /animator/ filename.xml
文件名将用作资源ID。
编制资源数据类型:
指向ValueAnimator、ObjectAnimator或AnimatorSet的资源指针。
资源引用:
在Java: R.animator.filename
在XML: @包:animator/文件名

语法;

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
<set
android:ordering=["together" | "sequentially"]>

<!--<set>对应AnimatorSet(属性动画集合)类-->
<!--android:ordering=["动画集合中的子动画同时播放(默认值)"|"动画集合中的子动画按照书写顺序依次播放"]-->

<!--<objectAnimator>对应ObjectAnimator类-->
<!--<animator>标签对应ValueAnimator类-->

<objectAnimator
android:propertyName="string" // 属性动画作用的属性名称
android:duration="int" // 动画周期,默认值为300毫秒
android:valueFrom="float | int | color" // 属性的初始值
android:valueTo="float | int | color" // 属性的结束值
android:startOffset="int" // 调用start方法后延迟多少毫秒开始播放动画
android:repeatCount="int" // 动画的重复次数,-1为无限循环。默认为0,表示只播放一次。
android:repeatMode=["repeat" | "reverse"] // 动画的重复模式;repeat(连续重复),reverse(逆向重复)。
android:valueType=["intType" | "floatType"] // 表示android:propertyName所指定属性的类型,默认为floatType。不需要指定颜色指属性,
/>

<animator
android:duration="int" // 动画周期,默认值为300毫秒
android:valueFrom="float | int | color" // 属性的初始值
android:valueTo="float | int | color" // 属性的结束值
android:startOffset="int" // 调用start方法后延迟多少毫秒开始播放动画
android:repeatCount="int" // 动画的重复次数,-1为无限循环。默认为0,表示只播放一次。
android:repeatMode=["repeat" | "reverse"] // 动画的重复模式;repeat(连续重复),reverse(逆向重复)。
android:valueType=["intType" | "floatType"] // 表示android:propertyName所指定属性的类型,默认为floatType。不需要指定颜色指属性,
/>

<set>
...
</set>
</set>

View.animate() 示例

注:对应的 ViewPropertyAnimator 中的两个方法;

  • 第一个:例如 translationX(100) 表示用动画把 View 的 translationX 值渐变为 100。
    View会向右移动100像素。ViewPropertyAnimator不支持重复
  • 第二个:例如 translationXBy(100) 表示用动画把 View 的 translationX 值渐变地增加 100。
    View会向右移动100像素。如果再次执行动画,会从动画执行结束后的位置执行,再次向右移动100像素。
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
/**
* View.animate()
* ViewPropertyAnimator 不支持重复
*/
public void viewAnimate(View view){
ViewPropertyAnimator vpa = img.animate();
vpa.translationX(-300);
Path interpolatorPath = new Path();
// 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25%
interpolatorPath.lineTo(0.25f, 0.25f);
// 然后瞬间跳跃到 150% 的动画完成度
interpolatorPath.moveTo(0.25f, 1.5f);
// 再匀速倒车,返回到目标点
interpolatorPath.lineTo(1, 1);
vpa.setInterpolator(PathInterpolatorCompat.create(interpolatorPath));
vpa.setDuration(2000);
vpa.withStartAction(new Runnable() {
@Override
public void run() {
// 一次性的监听方法
Toast.makeText(CustomSwitchingActivity.this,"动画开始",Toast.LENGTH_SHORT).show();
}
});
vpa.withEndAction(new Runnable() {
@Override
public void run() {
Toast.makeText(CustomSwitchingActivity.this,"动画结束",Toast.LENGTH_SHORT).show();
// 一次性的监听方法
// 只在动画正常结束时才会被调用,而在动画被取消时不会被执行
}
});
}

ObjectAnimator 示例

为 ValueAnimator 的子类
因为属性的起始和结束值大多是在程序运行的时候动态获取的,所以推荐使用动态创建代码的方式。

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
public class CustomSwitchingActivity extends AppCompatActivity implements View.OnClickListener {

private ImageView iv;

/**
* 使用属性动画 ObjectAnimator
* 来实现补间动画的四种效果
*/
private TextView tvAlpha,tvScale,tvTranslate,tvRotate;

/**
* 自封装
* 提供get和set方法
*/
private TextView tvWrapper;

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

private void initData() {
iv.setImageResource(R.drawable.ic_launcher_background);
}

@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_custom_alpha:
ObjectAnimator ob = ObjectAnimator.ofFloat(iv,"alpha",1,0,1).setDuration(2000);
ob.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (Float) valueAnimator.getAnimatedValue();
Log.e("TAG", value + "");
if (value == 1){
Toast.makeText(CustomSwitchingActivity.this,"执行结束",Toast.LENGTH_SHORT).show();
}
}
});
// 延迟1000ms后执行,需要在start()前调用
ob.setStartDelay(1000);
ob.start();
break;
case R.id.tv_custom_scale:
final AnimatorSet as = new AnimatorSet();
iv.setPivotX(iv.getWidth()/2);
iv.setPivotY(iv.getHeight()/2);
as.playTogether(
ObjectAnimator.ofFloat(iv,"scaleX",1,0).setDuration(2000),
ObjectAnimator.ofFloat(iv,"scaleY",1,0).setDuration(2000)
);
//添加监听事件
as.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
// 动画开始的时候调用
}

@Override
public void onAnimationEnd(Animator animator) {
// 动画结束的时候调用
Toast.makeText(CustomSwitchingActivity.this,"执行结束,重置动画",Toast.LENGTH_SHORT).show();
// 重置动画
as.removeListener(this);
as.setDuration(0);
as.setInterpolator(new ReverseInterpolator());
as.start();
}

@Override
public void onAnimationCancel(Animator animator) {
// 动画被取消的时候调用

}

@Override
public void onAnimationRepeat(Animator animator) {
// 动画重复执行的时候调用

}
});
as.start();
break;
case R.id.tv_custom_translate:
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(iv,"translationX",20,100).setDuration(2000),
ObjectAnimator.ofFloat(iv,"translationY",20,100).setDuration(2000)
);
//另一种设置监听的方式,里面的监听方法可以选择性重写
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Toast.makeText(CustomSwitchingActivity.this,"执行结束",Toast.LENGTH_SHORT).show();
// 重置动画
animatorSet.removeListener(this);
animatorSet.setDuration(0);
animatorSet.setInterpolator(new ReverseInterpolator());
animatorSet.start();
}
});
animatorSet.start();
break;
case R.id.tv_custom_rotate:
iv.setPivotX(iv.getWidth()/2);
iv.setPivotY(iv.getHeight()/2);
ObjectAnimator.ofFloat(iv,"rotation",0,360).setDuration(2000).start();
break;
case R.id.tv_custom_wrapper:
// 无法使用属性动画或者属性动画不起作用的情况:
// 1.该字段没有set和get方法
// 2.该属性的set方法仅仅改变了对象的属性值,但是没有将这种改变用动画的形式表现出来
// 解决方法:
// 1.如果有权限的话,给这个字段添加get和set方法,比如在自定义View中。
// 2.使用一个包装类来封装该字段对应的类,间接为该字段提供get和set方法。
// 3.采用ValueAnimator,监听动画过程,自己实现属性的改变
// 关于ValueAnimator,其本身不作用于任何对象,也就是说直接使用它没有任何动画效果。
// 它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。

// 改变ImageView宽度,采用第二种方式。
ViewWrapper vw = new ViewWrapper(iv);
ObjectAnimator.ofInt(vw,"width",20).setDuration(2000).start();

// 改变ImageView宽度,采用第三种方式。
// performAnimate(iv, iv.getWidth(), 20);
break;
default:
break;
}
}

private void performAnimate(final View target, final int start, final int end) {
// 关于这个ValueAnimator,它会在2000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法,
// 在这个方法里,我们可以获取当前的值(1-100),根据当前值所占的比例(当前值/100),
// 我们可以计算出现在的宽度应该是多少,比如时间过了一半,当前值是50,比例为0.5,
// 假设起始宽度是100px,最终宽度是500px,那么增加的宽度也应该占总增加宽度的一半,
// 总增加宽度是500-100=400,所以这个时候应该增加宽度400*0.5=200,
// 那么当前的宽度应该为初始宽度+ 增加宽度(100+200=300)。
// 上述计算过程很简单,其实它就是整型估值器IntEvaluator的内部实现。
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

//持有一个IntEvaluator对象,方便下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();

@Override
public void onAnimationUpdate(ValueAnimator animator) {
//获得当前动画的进度值,整型,1-100之间
int currentValue = (Integer)animator.getAnimatedValue();

//计算当前进度占整个动画过程的比例,浮点型,0-1之间
float fraction = currentValue / 100f;

//这里刚哥偷懒了,不过有现成的干吗不用呢(有理)
//直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(2000).start();
}

private void initView() {
iv=findViewById(R.id.iv_custom_anim);
tvAlpha=findViewById(R.id.tv_custom_alpha);
tvScale=findViewById(R.id.tv_custom_scale);
tvTranslate=findViewById(R.id.tv_custom_translate);
tvRotate=findViewById(R.id.tv_custom_rotate);

tvWrapper=findViewById(R.id.tv_custom_wrapper);

tvAlpha.setOnClickListener(this);
tvScale.setOnClickListener(this);
tvTranslate.setOnClickListener(this);
tvRotate.setOnClickListener(this);
tvWrapper.setOnClickListener(this);
}
}
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
/**
* 无法使用属性动画或者属性动画不起作用的情况:
* 1.该字段没有set和get方法
* 2.该属性的set方法仅仅改变了对象的属性值,但是没有将这种改变用动画的形式表现出来
* 解决方法:
* 1.如果有权限的话,给这个字段添加get和set方法,比如在自定义View中。
* 2.使用一个包装类来封装该字段对应的类,间接为该字段提供get和set方法。
* 3.采用ValueAnimator,监听动画过程,自己实现属性的改变
* 关于ValueAnimator,其本身不作用于任何对象,也就是说直接使用它没有任何动画效果。
* 它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
*
* 这里为了改变ImageView的宽度,采用第二种方法。
* View包装类
* 提供了get和set方法
*/

public class ViewWrapper {

private View view;

public ViewWrapper(View view) {
this.view = view;
}

public int getHeight(){
return view.getLayoutParams().height;
}

public void setHeight(int height){
view.getLayoutParams().height = height;
view.requestLayout();
}
}

常用的场景

同一个动画中改变多个属性

区别于view.animate()的可以链式调用,对于 ObjectAnimator,可以使用 PropertyValuesHolder 来同时在一个动画中改变多个属性。

1
2
3
4
5
6
7
8
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 0,1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 0,1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0,1);
// PropertyValuesHolder 是一个属性值的批量存放地。
// 使用 ofPropertyValuesHolder() 统一放进 Animator。
ObjectAnimator animator3 = ObjectAnimator.ofPropertyValuesHolder(ivHolder, holder1, holder2, holder3);
animator3.setDuration(2000);
animator3.start();

AnimatorSet 多个动画配合执行

如果在同一个动画中,会共享动画的开始时间、结束时间、Interpolator 等一系列的设定,这样就不能有先后次序地执行动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   ObjectAnimator animator4 = ObjectAnimator.ofFloat(ivHolder, "translationX", 0, -200);
animator4.setDuration(1000);
ObjectAnimator animator5 = ObjectAnimator.ofFloat(ivHolder, "alpha", 0, 1);
animator5.setDuration(1000);
ObjectAnimator animator6 = ObjectAnimator.ofFloat(ivHolder, "translationX", -200, 200);
ObjectAnimator animator7 = ObjectAnimator.ofFloat(ivHolder, "rotation", 0, 1080);
animator7.setDuration(1000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator4).before(animator5); // 先执行 4 再执行 5
animatorSet.playTogether(animator5,animator6, animator7); // 5,6 和 7 同时开始
animatorSet.start();

// 两个动画依次执行
animatorSet.playSequentially(animator4, animator5);
// 两个动画同时执行
animatorSet.playTogether(animator1, animator2);
// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式来精确配置各个 Animator 之间的关系
// animatorSet.play(animator1).with(animator2);
// animatorSet.play(animator1).before(animator2);
// animatorSet.play(animator1).after(animator2);

PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

PropertyValuesHolder 通过设置 Keyframe(关键帧),把同一个动画属性拆分成多个阶段。
例如,你可以让一个进度增加到 100% 后再「反弹」回来。

1
2
3
4
5
6
7
8
9
10
11
12
// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);

ObjectAnimator animator8 = ObjectAnimator.ofPropertyValuesHolder(viewKey, holder);
animator8.setInterpolator(new FastOutSlowInInterpolator());
animator8.setDuration(2000);
animator8.start();

ValueAnimator 示例

其实 ValueAnimator 类就是一个数值生成器,它只有上述工作原理的第二步,只要给ValueAnimator提供一个初始值、结束值和周期时间,ValueAnimator就会按照属性动画框架工作原理的第2步中的步骤生成具有一定规则的数字。
ValueAnimator 并不常用,因为它的功能太基础了。实际上,ValueAnimator 就是一个不能指定目标对象版本的 ObjectAnimator。
ObjectAnimator 是自动调用目标对象的 setter 方法来更新目标属性的值,以及很多的时候还会以此来改变目标对象的 UI。
而 ValueAnimator 只是通过渐变的方式来改变一个独立的数据,这个数据不是属于某个对象的,至于在数据更新后要做什么事,全都由你来定。
功能最少、最不方便,但有时也是束缚最少、最灵活。
比如有的时候,你要给一个第三方控件做动画,你需要更新的那个属性没有 setter 方法,只能直接修改,这个时候就可以用 ValueAnimator,在它的 onUpdate() 里面更新这个属性的值,并且手动调用 invalidate()。

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public class CustomSwitchingActivity extends AppCompatActivity {

/**
* 属性动画 ValueAnimator
* 实现搜索框的显示和隐藏
*/
private EditText etSearch;
private Button btSearch;
private int etWidth;

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

private void initData() {
etSearch.post(new Runnable() {
@Override
public void run() {
etWidth=etSearch.getWidth();
}
});
}

private void initView() {
btSearch = findViewById(R.id.bt_custom_search);
etSearch = findViewById(R.id.et_custom_search);
}

/**
* 控制搜索栏的显示
*/
public void search(View view){
btSearch.setEnabled(false);
if (etSearch.getVisibility() == View.GONE){
// 显示搜索栏
animateShow(etSearch);
}else {
// 隐藏搜索栏
animateHide(etSearch);
}
}

/**
* 隐藏
*/
private void animateHide(final View view) {
ValueAnimator va = createDropAnimator(view,etWidth,0);
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.GONE);
btSearch.setEnabled(true);
}
});
va.setInterpolator(new DecelerateInterpolator());
va.setDuration(2000);
va.start();
}

/**
* 显示
*/
private void animateShow(final View view) {
view.setVisibility(View.VISIBLE);
ValueAnimator va = createDropAnimator(view,0,etWidth);
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
btSearch.setEnabled(true);
}
});
va.setInterpolator(new AccelerateInterpolator());
va.setDuration(2000);
va.start();
}

private ValueAnimator createDropAnimator(final View view, int start, int end){
ValueAnimator va = ValueAnimator.ofInt(start,end);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
params.width = (int) valueAnimator.getAnimatedValue();
view.setLayoutParams(params);
}
});
return va;
}

}

常用的场景

为 ViewGroup 的子 View 的显示和隐藏设置过渡动画

Android系统共提供了4种类型的过渡动画:
在layout文件中将ViewGroup的animateLayoutChanges属性打开就可以使用系统提供的默认过渡动画
自定义过渡动画示例:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class CustomSwitchingActivity extends AppCompatActivity {

/**
* ViewGroup添加子View的过渡动画
*/
private LinearLayout llImgs;

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

private void initData() {
// 为ViewGroup的子View添加过渡动画
imgsTransition();
}

private void initView() {
llImgs = findViewById(R.id.ll_imgs);
}

/**
* 添加图片
*/
public void imgAdd(View view){
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(0,0,10,0);
FrameLayout frameLayout = new FrameLayout(this);
frameLayout.setLayoutParams(layoutParams);

FrameLayout.LayoutParams imgParams = new FrameLayout.LayoutParams(200,200);
ImageView imageView = new ImageView(this);
imageView.setImageResource(R.drawable.ic_launcher_background);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(imgParams);

FrameLayout.LayoutParams tvParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
TextView tv = new TextView(this);
tvParams.gravity = Gravity.RIGHT|Gravity.BOTTOM;
tv.setText(String.valueOf(llImgs.getChildCount()+1));
tv.setLayoutParams(tvParams);

frameLayout.addView(imageView);
frameLayout.addView(tv);

llImgs.addView(frameLayout, llImgs.getChildCount());
}

/**
* 移除图片
*/
public void imgRemove(View view){
int count = llImgs.getChildCount();
if (count > 0) {
llImgs.removeViewAt(llImgs.getChildCount()-1);
}
}

private void imgsTransition() {
// APPEARING
// 当通过 设置子View的可见性为VISIBLE或者通过addView方法添加子View 来显示子View时,
// 子View就会执行该类型的动画。
// 该类型动画的周期为300毫秒,默认延迟为300毫秒。
//
// DISAPPEARING
// 当通过 设置子View的可见性为GONE或者通过removeView方法移除子View 来隐藏子View时,
// 子View就会执行该类型的动画。
// 该类型动画的周期为300毫秒,默认延迟为0毫秒。
//
// CHANGE_APPEARING
// 当显示子View时,所有的兄弟View就会立即依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒,然后才会执行显示子View的动画。
// 该类型动画的周期为300毫秒,默认延迟为0毫秒。
//
// CHANGE_DISAPPEARING
// 当隐藏子View的动画执行完毕后,所有的兄弟View就会依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒。
// 该类型动画的周期为300毫秒,默认延迟为300毫秒。
//
// 注意 上面描述的都是系统默认的行为,我们可以做适当的改变。
// 依次对CHANGE_APPEARING、APPEARING、DISAPPEARING 和 CHANGE_DISAPPEARING 类型的过渡动画进行设置
LayoutTransition transition = new LayoutTransition();

// 当多个子View要执行同一个类型的动画时,就可以通过该方法来设置子View之间执行动画的间隙,默认为0毫秒。
transition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
// 为指定类型的过渡动画设置执行动画的周期,默认为300毫秒。
transition.setDuration(LayoutTransition.CHANGE_APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));
// 为指定类型的过渡动画设置延迟执行的时间,默认与过渡动画的类型相关。
transition.setStartDelay(LayoutTransition.CHANGE_APPEARING, 0);

ObjectAnimator appearingAnimator = ObjectAnimator.ofPropertyValuesHolder(
(Object) null,
PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("alpha", 0, 1.0f));
// 为指定类型的过渡动画设置自定义的属性动画。
transition.setAnimator(LayoutTransition.APPEARING, appearingAnimator);
transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
transition.setStartDelay(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));

ObjectAnimator disappearingAnimator = ObjectAnimator
.ofPropertyValuesHolder(
(Object) null,
PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0));
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnimator);
transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setStartDelay(LayoutTransition.DISAPPEARING, 0);

transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));

// setLayoutTransition方法,为ViewGroup设置过渡动画。
llImgs.setLayoutTransition(transition);
}

}

属性动画的监听器

主要两个接口:

  • AnimatorUpdateListener:可监听动画的开始、结束、取消以及重复播放。、
  • AnimatorListener:会监听整个动画过程,每播放一帧,会被调用一次。

备注

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