在《Android动画 – Property Animation(一)》中已经对属性动画进行了初步的认识和使用,这一篇是对属性动画的高级使用,其中包括了多属性动画和布局动画(LayoutTransition),不过属性动画中的插值器(Interpolator)和估值器(TypeEvaluator)这两个很重要的属性不会在本篇中详细讲述,会额外开一篇来讲这两个。
在通过上一篇Android动画-Property Animation(一)中了解到多个动画一起执行可以使用XML去定义或者AnimatorSet,在 Property Animation 中还提供了其他的方法。
PropertyValuesHolder的用法和AnimatorSet其实差不太多,它把动画分开来,然后塞给ObjectAnimator
public void start(View view) {
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0.3f, 1f);
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("scaleX", 0.2f, 1.4f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleY", 0.2f, 1.4f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alpha, pvhX, pvhY);
animator.setInterpolator(new BounceInterpolator());
animator.setDuration(2000);
animator.start();
}
和PropertyValuesHolder不同的是,ViewPropertyAnimator是不需要知道起始点,也不需要过程,它要的仅仅是结果,而且这个结果是一个 绝对结果,先看个效果:
在上图中,很明显我点击了多次 start 按钮,但是看到的效果只有第一次,那么来解释下什么是”绝对结果”
public void start(View view) {
ViewPropertyAnimator animate = imageView.animate();
animate.alpha(0.3f);
animate.rotationX(30);
animate.scaleX(1.4f);
animate.scaleY(1.4f);
animate.setInterpolator(new BounceInterpolator());
animate.setDuration(2000);
animate.start();
}
ViewPropertyAnimator是从控件本身获取到的,上图中我想要的结果是:
透明度:30%
向屏幕内侧与X轴平面成 30°
X轴、Y轴分别放大道 1.4倍
那么当第一次动画结束,已经达到我想要的效果了,那么之后的点击都不会再看到动画效果,这个就是所谓的 绝对结果 。
想看到 相对结果 ,可以让方法里面的变量随着点击次数变化,这里可以通过ViewPropertyAnimator的两个不同的API translationYBy(float) translationY(float),来看看相对结果,顺便认识下这两个API的不同。
translationY(float)
translationYBy(float)
上面的效果中,我都点了多次,只有translationYBy这个看到了多次效果
public void start(View view) {
ViewPropertyAnimator animate = imageView.animate();
animate.alpha(0.3f);
animate.rotationX(30);
// animate.translationY(250);
animate.translationYBy(250);
animate.scaleX(1.4f);
animate.scaleY(1.4f);
animate.setInterpolator(new BounceInterpolator());
animate.setDuration(2000);
animate.start();
}
那么很明显了,translationY 这个API,它是相对于控件初始原点(即控件左上角),位移到Y轴250处,这个坐标点是一个绝对的,因为控件初始原点是绝对的。而 translationYBy 不同,它是以控件当前位置的原点作比较,随意会一直位移下去。最后,ViewPropertyAnimator 这个动画是没有setRepeat的方法,它的用法决定了这一点。
keyFrame是一个 时间/值 对,通过它可以定义一个在特定时间的特定状态,即关键帧。这里的时间 key,是在 0-1之间取值,上面动画可以看到在不同的时间段上设置差异较大的值,动画快慢也不同了。
以第一个改变View 宽高的代码为例:
public void start(View view) {
Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("height", kf0, kf1, kf2, kf4, kf3);
PropertyValuesHolder pvhRotation2 = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhRotation, pvhRotation2);
rotationAnim.setDuration(3000);
rotationAnim.start();
}
代码的意思是:
动画初始:宽高都是400
动画1/4:宽高变成200
动画1/2:宽高变成400
动画3/4:宽高变成100
动画结束:宽高变成500
这一部分参考了 hongyang 大神的Android 属性动画(Property Animation) 完全解析 (下),这块儿的布局动画确实应用场景不是太多,在APP里面数据多,一般都会用 ListView GridView ViewPager 之类,所以这种动画的情景就不多见了。
APPEARING
当一个元素变为Visible时对其应用的动画
CHANGE_APPEARING
当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
DISAPPEARING
当一个元素变为InVisible时对其应用的动画
CHANGE_DISAPPEARING
当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
在这个动画里面,不管是删除子View,还是添加子View,不管是父布局、还是自布局都有动画,这是因为我把上述的四中属性全部添加进去了,不过子View的添加、删除动画,这里是我自定义的,直接看代码。
public class LayoutTransitionActivity extends AppCompatActivity {
private GridLayout gridLayout, gridLayout2;
private int gridlayout_count, gridlayout2_count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_transition);
gridLayout = (GridLayout) findViewById(R.id.gridLayout);
gridLayout2 = (GridLayout) findViewById(R.id.gridLayout2);
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.APPEARING,
getChildObjectAnimator(0f, 1f));
transition.setAnimator(LayoutTransition.DISAPPEARING,
getChildObjectAnimator(1f, 0f));
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
transition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING));
gridLayout.setLayoutTransition(transition);
gridLayout2.setLayoutTransition(transition);
}
public void add(View view) {
createButton();
}
public void add2(View view) {
createButton2();
}
public void createButton() {
final Button button = new Button(this);
button.setText((++gridlayout_count) + "");
gridLayout.addView(button, Math.min(1, gridLayout.getChildCount()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gridLayout.removeView(button);
}
});
}
public void createButton2() {
final Button button = new Button(this);
button.setText((++gridlayout2_count) + "");
gridLayout2.addView(button, Math.min(1, gridLayout2.getChildCount()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gridLayout2.removeView(button);
}
});
}
private ObjectAnimator getChildObjectAnimator(float start, float end) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "scaleX", start, end);
objectAnimator.setDuration(200);
return objectAnimator;
}
}
很明显的
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.APPEARING,
getChildObjectAnimator(0f, 1f));
transition.setAnimator(LayoutTransition.DISAPPEARING,
getChildObjectAnimator(1f, 0f));
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
transition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING));
gridLayout.setLayoutTransition(transition);
gridLayout2.setLayoutTransition(transition);
这一块代码是对布局动画的初始化,父布局的添加删除动画是系统默认的,子View的添加删除动画默认是淡入、淡出,这里我设置成了自己定义的缩放效果,最后再设置给两个gridLayout。
不单单是GridLayout,我还写了个简单的 LinearLayout,
其实都是一样的。
以上就是本章内容了,多属性动画还好,布局动画就有些局限性了,就如上面的LinearLayout展示的,如果有这样的UI,我们肯定会用ListView RecyclerView这些,不单单是因为写起来简单,还因为控件的复用性,LinearLayout就做不到。