1.属性动画
属性动画可以对任意对象的属性进行动画不仅仅是View
,动画默认时间间隔是300ms
,默认帧率是100ms/帧
。
作用:在一个时间间隔内完成对一个对象从属性值到另一个属性值的改变。
三个常用类:ValueAnimator,ObjectAnimator,AnimatorSet
属性动画
Java代码
private void initView() {
Button bt = (Button) findViewById(R.id.bt_object_animation_activity);
ImageView iv = (ImageView) findViewById(R.id.iv_object_animation_activity);
//改变背景属性
ValueAnimator colorAnim = ObjectAnimator.ofInt(iv, "backgroundColor", Color.parseColor("#FF4081"), Color.CYAN);
colorAnim.setRepeatCount(2);
colorAnim.setRepeatMode(ObjectAnimator.REVERSE);
colorAnim.setDuration(1000);
colorAnim.setEvaluator(new ArgbEvaluator());//估值器
//动画集合
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(iv, "rotationX", 0, 360),//绕x轴旋转360度
ObjectAnimator.ofFloat(iv, "rotation", 0, -90),//逆时针旋转90度
ObjectAnimator.ofFloat(iv, "translationX", 0, 90),//右移
ObjectAnimator.ofFloat(iv, "scaleY", 1, 0.5f),//y轴缩放到一半
ObjectAnimator.ofFloat(iv, "alpha", 1, 0.25f, 1)//透明度变换
);
//延迟一秒开始
set.setStartDelay(1000);
bt.setOnClickListener((v) -> {
//改变属性 位置 向下移动iv高的二分之一
ObjectAnimator.ofFloat(iv, "translationY", iv.getHeight() / 2).start();
//背景属性改变开始
colorAnim.start();
//集合动画
set.setDuration(3000).start();
});
}
轴点默认为View
的中心点。
常用的propertyName
:
-
rotationX
围绕x轴旋转 -
rotationY
围绕y轴旋转 -
rotation
围绕轴点旋转 -
translationX
在x轴方向上平移 -
translationY
在y轴方向上平移 -
scaleX
在x轴方向缩放 -
scaleY
在y轴方向缩放 -
alpha
透明度 -
width
宽度 -
height
高度
1.2 常用方法介绍
1.2.1 ObjectAnimator
类
ObjectAnimator.ofFloat(Object target, String propertyName, float... values)
Constructs and returns an ObjectAnimator that animates between float values.
返回一个根据方法内的具体的values
创建的ObjectAnimator
对象
-
Object target
,目标控件View
的对象 -
String propertyName
,属性的名字 -
float... values
,根据具体需求需要的属性值
ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)
Constructs and returns an ObjectAnimator that animates between the sets of values specified in PropertyValueHolder objects
返回一个由PropertyValueHolder
对象创建的ObjectAnimator
对象
public void byPropertyValuesHolder(ImageView iv) {
PropertyValuesHolder pvh_1 = PropertyValuesHolder.ofFloat("rotationX", 0, 360);
PropertyValuesHolder pvh_2 = PropertyValuesHolder.ofFloat("rotation", 0, -90);
PropertyValuesHolder pvh_3 = PropertyValuesHolder.ofFloat("alpha", 1, 0.25f, 1);
ObjectAnimator.ofPropertyValuesHolder(iv, pvh_1, pvh_2, pvh_3).setDuration(1000).start();
}
ofInt(Object target, String propertyName, int... values)
Constructs and returns an ObjectAnimator that animates between int values.
返回一个由int
值属性创建的ObjectAnimator
对象
-
setTarget(Object target)
设置动画目标View
1.2.2 ValueAnimator
类
-
ValueAnimator
的setEvaluator(new ArgbEvaluator())
设置估值器 -
addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
Adds a listener to the set of listeners that are sent update events through the life of an animation.
添加一个监听,可以用来在动画过程中改变属性
-
setRepeatCount(int num)
设置动画的循环次数,默认为0,-1为无限循环 -
setStartDelay(long startDelay)
设置动画开始的延迟时间
-
cancel()
取消一个正在进行的动画。取消前,动画进行到哪个状态,取消后,就保持在那个状态。 -
end()
结束动画。调用结束方法后,View
会跳转到结束状态。如果动画设置了循环次数setRepeatCount()
和重复模式setRepeatMode(ObjectAnimator.REVERSE)
,结束状态就要根据具体设置分析。
1.2.3 AnimatorSet
类
playTogether(Animator... items)
Sets up this AnimatorSet to play all of the supplied animations at the same time.
同时播放所有的Animator
动画对象。
playSequentially(Animator... items)
Sets up this AnimatorSet to play each of the supplied animations when the previous animation ends.
顺序播放Animator
动画对象
setInterpolator(TimeInterpolator interpolator)
Sets the TimeInterpolator for all current child animations of this AnimatorSet.
设置插值器
1.2.4 Animator
类
直接子类:AnimatorSet
和 ValueAnimator
间接子类:ObjectAnimator
和 TimeAnimator
Animator
类的方法子类都可以直接使用。
addListener(Animator.AnimatorListener listener)
Adds a listener to the set of listeners that are sent events through the life of an animation, such as start, repeat, and end.
为动画添加一个监听过程的接口。如果不想实现AnimatorListener
接口中的所有方法也可以继承AnimatorListenerAdapter
。
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
toast("动画开始");
}
@Override
public void onAnimationEnd(Animator animator) {
toast("动画结束");
}
@Override
public void onAnimationCancel(Animator animator) {
toast("动画取消");
}
@Override
public void onAnimationRepeat(Animator animator) {
toast("动画重建");
}
});
实现了接口中全部的方法。
继承AnimatorListenerAdapter
:
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
toast("动画结束");
}
});
根据实际需求,实现不同的方法。
addPauseListener(Animator.AnimatorPauseListener listener)
Adds a pause listener to this animator
为动画增加暂停监听
2.插值器和估值器
TimeInterpolator
,时间插值器。用来根据时间流逝的百分比来计算出当前属性的值变化的百分比。
常用插值器:
名称 | 作用 |
---|---|
LinearInterpolator |
线性插值器。匀速动画 |
AccelerateDecelerateInterpolator |
加速减速插值器 |
DecelerateInterpolator |
匀减速插值器。动作越来越慢 |
BounceInterpolator |
回弹插值器。到达平移后,回弹 |
CycleInterpolator |
循环插值器。在两点间往还运动 |
PathInterpolator |
路径插值器。根据单一方向定义的路径坐标运动 |
OvershootInterpolator |
超越插值器。超出后,再返回来 |
AnticipateInterpolator |
预期插值器。先反向运动再根据指定的方向运动 |
都是Interpolator
的子类
TypeEvaluator
,类型估值算法(估值器)。用来根据当前属性变化改变的百分比来计算改变后的属性值。
-
IntEvaluator
,针对整型属性 -
FloatEvaluator
,针对浮点型属性 -
ArgbEvaluator
,针对Color
属性
2.1简单Demo
模拟小球下落private void initView() {
Button bt = (Button) findViewById(R.id.bt_interpolator_activity);
ImageView iv = (ImageView) findViewById(R.id.iv_interpolator_activity);
LinearLayout root = (LinearLayout) findViewById(R.id.root_interpolator_activity);//根布局
AnimatorSet set = new AnimatorSet();
set.setInterpolator(new BounceInterpolator());
set.setDuration(3000);
//利用View的post方法拿到根布局的高度
root.post(() -> {
//计算下降高度
int height = root.getHeight() - iv.getHeight() - bt.getHeight();
//设置动画
set.play(ObjectAnimator.ofFloat(iv, "translationY", height));
});
bt.setOnClickListener(v ->set.start());
}
利用BounceInterpolator
可以很方便的做出模拟小球下落的动画。也可以根据需求进行自定义插值器。
2.2 对任意属性做动画
对Object
的属性abc
做动画,必须满足2个条件:
-
object
必须提供setAbc()
的方法。如果动画的时候没有传递初始值,还要提供getAbc()
方法。因为系统要去abc
的初始值。如果不满足,程序直接Crash
-
object
的setAbc
对属性abc
所做的改变必须能够通过某种方法反映出来,比如UI
改变之类的。这条不满足,动画无效但程序不会Crash
2.2.1 改变Button
的宽度
例如,想要利用属性对话来改变一个Button
的宽度。
private void initView() {
Button bt = (Button) findViewById(R.id.bt_button_activity);
bt.setOnClickListener((v) -> performAnimate(bt));
}
private void performAnimate(Button bt) {
ObjectAnimator.ofInt(bt, "width", 500).setDuration(1000).start();
}
实际测试,这段代码完全不起作用。
2.2.2不起作用的原因
Button
继承的TextView
/**
* Makes the TextView exactly this many pixels wide.
* You could do the same thing by specifying this number in the
* LayoutParams.
*
* @see #setMaxWidth(int)
* @see #setMinWidth(int)
* @see #getMinWidth()
* @see #getMaxWidth()
*
* @attr ref android.R.styleable#TextView_width
*/
@android.view.RemotableViewMethod
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
requestLayout();
invalidate();
}
/**
* Return the width of the your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
getWidth()
方法View
的方法,是可以获取Button
高度的。setWidth()
方法TextView
和子类的专属方法。是用来设置TextView
的最大宽度和最小宽度的,并不是用来设置TextView
的宽度的方法。TextView
的宽度对应于XML
的android:layout_width
,setWidth
方法对应的是android:width
。
也就是说:Button
的setWidth()
和getWidth()
对应的就不是一个属性。只满足的条件1,不满足条件2
2.2.3 解决办法
有三种解决办法:
- 如果有权限,给对象加上
get
和set
方法 - 用一个类包装原始对象,间接提供
get
和set
方法 - 采用
ValueAnimation
,监听动画过程,实现属性的改变
get
和set
方法往往拿不到权限。
利用包装类方法:
private void initView() {
Button bt = (Button) findViewById(R.id.bt_button_activity);
bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth()));
}
private void performAnimate(Button bt,int width) {
ButtonWrapper wrapper = new ButtonWrapper(bt);
wrapper.setWidth(width);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(1000).start();
}
private static class ButtonWrapper {
private View target;
public ButtonWrapper(View target) {
this.target = target;
}
public int getWidth() {
return target.getLayoutParams().width;
}
public void setWidth(int width) {
target.getLayoutParams().width = width;
target.requestLayout();
}
}
拿到Button
的宽度后,设置给ButtonWrapper
。这样动画开始后,Button
会从原始大小开始变化。
利用ValueAnimation
方法:
private void initView() {
Button bt = (Button) findViewById(R.id.bt_button_activity);
bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth(),500));
}
private void performAnimate(Button bt,int start, int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
//IntEvaluator对象,估值的时候使用
IntEvaluator intEvaluator = new IntEvaluator();
valueAnimator.addUpdateListener((animator -> {
//获取当前动画的进度值 , 整型, 1到100
int currentValue = (int) animator.getAnimatedValue();
//获取当前进度的占整个动画过程的比例,浮点型, 0到1
float fraction = animator.getAnimatedFraction();
//直接利用整型估值器,通过比例计算宽度,然后Button设置
bt.getLayoutParams().width = intEvaluator.evaluate(fraction,start,end);
bt.requestLayout();
}));
//开启动画
valueAnimator.setDuration(1000).start();
}
监控动画过程,利用IntEvaluator
估值器
3.最后
动画基础知识大概介绍完,自定义TypeEvaluator
和Interpolator
以及配合自定义View
更多高级的用法,以后再做补充。
接下来相当长的时间会用来学习自定义View
。想通过一系列博客来记录学习,一开始先学习一些View
的基础属性知识为学习自定义View
做准备,再学习具体的测量,绘制过程,View
的事件体系,工作原理。学习过程中间会加入继续对动画的深入学习,也可能会加入RxJava
的后续学习或者其他的框架的学习。