视图动画虽然能够实现控件的透明度变化、移动、拉伸、旋转,但是无法改变控件的内部属性。看过上篇文章的知道,本质上视图动画就是通过强制父容器重新绘制子View来实现,但是并没有改变子view的内部属性(如颜色,大小,宽高,位置)。例如将TextView从位置A移动到位置C,分别使用视图动画和属性动画。

val animator = ValueAnimator.ofInt(0,400).apply{
duration = 2000
}
animator.start()
在这里我们利用ValueAnimator.ofInt()创建了一个值从0 到 400 变化的动画,动画时长为 2s,从代码中,可以看到,valueAnimator 没有和任何控件相关联,只是进行了值的计算,
animator.addUpdateListener {
val curValue = it.animatedValue.toString()
Log.d("qin", curValue)
//根据valueAnimmator的值来变换textview的位置
tvText.layout(500,curValue.toIntOrNull()?:0,500 + tvText.width,(curValue.toIntOrNull()?:0).plus(tvText.height))
}
打印log如下
注意看打印出来的值不一定连续,有可能重复,有知道原因的好兄弟可以在评论区说一下
public static ValueAnimator ofInt(int... values)
接收可变长参数,接收的参数越多,变化越多越复杂
public static ValueAnimator ofFloat(Float... values)
接收可变长参数,接收的参数越多,变化越多越复杂
ValueAnimator setDuration(long duration) 设置动画持续时间
Object getAnimatedValue() 监听ValueAnimator动画,获取当前的值
start() 开始动画
setRepeatCount(int vlaue) 设置循环次数,值为INFININTE(无限循环动画时,在Activity结束时,需要手动调用cancel(),防止内存泄露
setRepeatMode(int value) 设置循环模式,值为ValueAnimator.REVERSE 或者ValueAnimator.RESTART
void cancel() 取消动画
removeUpdateListener(AnimatorUpdateListener listener)移除值变化监听器
removeAllUptateListeners() 移除所有值变化的监听器
removeListener(AnimatorListener listener)移除Animator中指定的监听器
removeAllListeners() 移除所有监听器
public void setStartDelay(long startDelay) 设置动画延迟多少秒开始
例子如下
val animator = ValueAnimator.ofFloat(0f,100f,30f,300f)
animator.duration = 3000
animator.addUpdateListener{
val curValueFloat = it.animatedValue.toString()
val curValue = curValueFloat.toIntOrNull()
tv.layout(curValue?:0,curValue?:0,(curValue?:0).plus(tv.width),(curValue?:0).plus(tv.heigth))
}
上面代码实现了将textview将从屏幕(0,0)移动到(100,100),再移动到(30,30),最后移动到(300,300)
ValueAnimator有两个不同的监听器,一个addUpdateListener监听值的变化过程,一个AnimatorListener监听动画的某个瞬间
public static interface AnimatorListener{
void onAnimatorStart(Animator animation); //动画开始
void onAnimatorEnd(Animator animation); //动画结束
void onAnimatorCancel(Animator animation); //动画取消
void onAnimatorRepeat(Animator animation); //动画重复
}
开发者可以通过监听ValueAnimator的四个状态start、end、cancel、repeat,来针对动画的不同状态做出特殊处理。
找到它的源码,找出计算方式
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolator {
//...省略其他代码
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
}

可以看出,这时一个余弦函数,使用了这个插值器,刚开始动画的变化速率较慢,然后慢慢加快,最后减慢,然后不断循环,斜率越大的地方,变化越快。
AnticipateInterpolator 是初始偏移插值器,表示动画刚开始时向前偏移一段距离,然后应用动画
跟踪源码
public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mTension;
public AnticipateInterpolator() {
mTension = 2.0f;
}
//省略其他代码
public float getInterpolation(float t) {
// a(t) = t * t * ((tension + 1) * t - tension)
return t * t * ((mTension + 1) * t - mTension);
}
使用画图工具得出下面图:
因为x 从0开始,刚开始时,向y轴的反方向移动,然后在继续向正方向移动,
弹跳插值器,模拟了控件自由落地后回弹的效果
@HasNativeInterpolator
public class BounceInterpolator extends BaseInterpolator implements NativeInterpolator {
public BounceInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public BounceInterpolator(Context context, AttributeSet attrs) {
}
private static float bounce(float t) {
return t * t * 8.0f;
}
public float getInterpolation(float t) {
// _b(t) = t * t * 8
// bs(t) = _b(t) for t < 0.3535
// bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
// bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
// bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
// b(t) = bs(t * 1.1226)
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
效果图如下
![图片描述]
下落时,首先超过了动画的极限1,然后进行第一次回弹,先上移动,速度由快到慢到快,进行第二次回弹,先快到慢,减速
结束偏移插值器,表示在动画结束时,沿着动画方向继续运动一段距离后结束动画,查看源码
public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mTension;
public OvershootInterpolator() {
mTension = 2.0f;
}
//..省略代码
mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
}
画出对应的数学图像
在动画快要结束的时候,随着时间的推移,动画进度会超过结束的位置,然后再逐渐返回。
AnticipateOvershootInterpolator 是AniticipateInterpolator 和OvershootInterpolator的结合体,即在动画开始时,先向前偏移一段,动画结束时向后偏移一段。跟踪代码
@HasNativeInterpolator
public class AnticipateOvershootInterpolator extends BaseInterpolator
implements NativeInterpolator {
private final float mTension;
public AnticipateOvershootInterpolator() {
mTension = 2.0f * 1.5f;
}
//省略其他代码
private static float a(float t, float s) {
return t * t * ((s + 1) * t - s);
}
private static float o(float t, float s) {
return t * t * ((s + 1) * t + s);
}
public float getInterpolation(float t) {
// a(t, s) = t * t * ((s + 1) * t - s)
// o(t, s) = t * t * ((s + 1) * t + s)
// f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
// f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}
}

这个是我们最常见的插值器,表示匀速
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
//...省略其他代码
public float getInterpolation(float input) {
return input;
}
}

加速插值器,表示动画开始速率改变的比较慢,然后开始加速
计算方式为:
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
//省略其他代码
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
}

减速插值器,在动画开始时一瞬间加速到最大值,然后逐渐变慢
public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
public DecelerateInterpolator() {
}
//省略其他代码
public float getInterpolation(float input) {
float result;
if (mFactor == 1.0f) {
result = (float)(1.0f - (1.0f - input) * (1.0f - input));
} else {
result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
}
return result;
}
## 1.6ValueObject进阶OfObject()
CycleInterpolator是循环插值器,表示动画循环播放特定的次数,速率沿着正弦曲线变化,
public class CycleInterpolator extends BaseInterpolator implements NativeInterpolator {
public float getInterpolation(float input) {
return (float)(Math.sin(2 * mCycles * Math.PI * input));
}
private float mCycles;
}
画出循环2次的函数图像
红色方框代表一次循环
上面我们通过ValueAnimator对动画的值进行变化,如果想要改变某个控件的属性,还要监听valueAnimator的动画过程,使用起来比较复杂。ObjectAnimator就是为了解决这个问题,相对于ValueAnimator,ObjectAnimator简化使用的API。ObjectAnimator是ValueAnimator的子类,所有ValueAnimator的方法都可以使用,同时对ofInt()、ofFloat()方法进行了重写,使用方法为:
val animator = ObjectAnimator(tv,"float",1f,0f,1.0f)
animator.duration = 1000
animator.start()
上面代码实现了TextView的透明度从1变到0再变到1,
从上面的代码中可以看到,构造ObjectAnimator的方法非常简单
public static ObjectAnimator ofFloat(object target,string propertyName,float... values)
ObjectAnimator通过构造方法ObjectAnimator()的第2个参数,propertyName,来调用相应的Set()方法,他们定义在View中
//设置透明度
public void setAlpha(float alpha)
//设置绕Z旋转
public void setRotation(float rotation)
//设置绕x轴旋转
public void setRotationX(float rotationX)
//设置绕y轴旋转
public void setRotationY(float rotationY)
//设置移动
public void setTranslateX(float translateX)
public void setTranslateY(float translateY)
//设置缩放
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)
首先我们要知道使用ObjectAnimator的前提是,view中有属性名字对应的set(),然后就是set()方法的命名方法是小驼峰,即set后对应的每个单词首字母大写,其余小写
AnimatorSet可以将ValueAnimator和ObjectAnimator组合到一起,实现组合动画,
函数说明如下
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)
playSequentially()的意思是先执行完动画一,再执行动画二,动画三
函数说明如下
public void playTogether(Animator... items)
public void playTogether(List<Animator> items)
AnimatorSet中没有设置循环次数的函数,是否无限循环主要看动画本身,和playTogether()、playSequntially()无关
虽然使用playTogether()和playSquentially()分别能实现动画依次播放和同时开始动画,但是不能非常自由第组合动画,比如我们有3个动画A、B、C,想先播放C,利用上面两个函数没办法实现。
val animatorSet = AnimatorSet()
val buidler = animator.play(tv1)
builder.with(tv1TranslateY)
AnimatorSet.play()这个方法生成Builder()的唯一方法,
//表示要播放某个动画
public Builder play(Animator anim)
//和前面动画一起执行
public Builder with(Animator anim)
//先执行此动画,在执行前面的动画
public Builder before(Animator anim)
//在前面的动画执行完之后,再执行此动画
public Builder after(Animator anim)
//延迟n 毫秒之后执行动画
public Builder after(long delay)
添加监听器,AnimatorSet和ValueAnimator都是同一个父类Animator,所以可以添加下面的监听器
public static interface AnimatorListener{
void onAnimatorStart(Animator animation); //动画开始
void onAnimatorEnd(Animator animation); //动画结束
void onAnimatorCancel(Animator animation); //动画取消
void onAnimatorRepeat(Animator animation); //动画重复
}
我们知道创建ValueAnimator实例方法是调用valueAnimator的ofInt(),ofFloat()或者ofObject()静态方法
ofInt()和ofFloat()的计算前面我们都学会了,那么ofObject()呢?初始对象和结束对象,如何过渡?或者说这玩意怎么用?
Android原生提供了以下几种Evaluator
public class IntEvaluator implements TypeEvaluator<Integer>{
public Integer evaluate(float frcation,Integer startValue,Integer endValue){
int startInt = startValue;
return (int)(start + fraction*(endValue-startInt);
}
}
返回值 = startValue + (endValue - startValue)* fraction
动画的值 = 初始值 + 完成度 * (结束值 - 初始值)
如果我们想告诉系统如何从初始对象过度到结束对象,那么我们就要自己来实现 TypeEvaluator 接口,即自定义 Evaluator
整个流程为:
插值器只能改变动画速度的快慢,而Evaluator可以通过接收插值器中的动画进度,增加一些自己对动画的处理,改变在addUpdateListener中的值,通过Evaluator 与ofObject的配合,使得valueAnimator更加强大,在Evaluator的处理中,可以给监听器返回一个自定义的对象。
ValueAnimator 和 ObjectAnimator 除了提供ofInt(),ofFloat(),ofObject()方法外,还提供了一个ofPropertyValuesHolder()
public static ValueAnimator ofPropertyValuesHolder(PropertyVlauesHolder... vlaues)
public static ObjbectAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)
ValueAnimator和ObjectAnimator的ofPropertyValuesHolder差不多,这里以ObjectAnimator为例子
PropertyValuesHolder类的含义就是,它其中保存了动画过程所需要操作的属性和对应的值,我们同构ofFloat(Object target,String propertyName,float... values)构建的动画,ofFloat()内部实现其实就是将传入的参数封装成PropertyValuesHolder实例来保存动画状态,后面的各种操作也是以PropertyValusHolder为主。下面是源码
public final class ObjectAnimator extends ValueAnimator {
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
}
点击setFloatValue()
//ObjectAnimator.java中
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
//ValueAnimator.java中
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
//封装成了PropertyValuesHolder实例
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
跟踪到这里我们验证了刚刚结论,动画的状态和参数被封装成了封装成了PropertyValuesHolder实例,并存在以属性名为键的HashMap中
public static PropertyValuesHolder ofFloat(String propertyName,float... values)
public static PropertyVlauesHolder ofInt(String propertyName,int... values)
public static PropertyValuesHolder ofObject(String propertyName,TypeEvaluator evaluator,object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName,keyframe.. values)
ObjectAnimator给我们提供了一个设置PropertyValuesHolder实例的入口
public static ObjecetAnimator ofPropertyValuesHolder(Object target ,PropertyValuesHolder... values)
示例如下:
前面提到,要想动画速率的变化,可以通过自定义插值器,也可以通过自定义Evaluator来实现,但是如果真的让我们从速率变化效果而自定义插值器或者Evaluator大部分要涉及数学知识,恐怕也不容易。
谷歌为根据电影的原理,为我们提供了KeyFrame这一套API,只需要知道动画某个进度点的关键帧,然后API会根据各个帧之间的先后顺序进行平滑过渡。
public static Keyframe ofFloat(float fraction,float value)
比如Keyframe.ofFloat(0,0)表示动画进度为0时,动画所在数值的位置为0,Keyframe.ofFloat(0.25f,-20f)表示动画进度为25时,动画所在的数值为-20,
val frame0 = Keyframe.ofFloat(0f,0)
val frame1 = Keyframe.ofFloat(0.1f,-20f)
val frame2 = keyframe.ofFloat(1f,0)
val frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2)
val animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder)
animator.duration = 1000
animator.start()
Keyframe也有一些常用的其他函数用来设置fraction、value、和Interpolator
//设置KeyFrame所对应的进度
public void setFraction(float fraction)
//设置KeyFrame所对应的值
public void setValue(Object value)
//设置插值器
//表示从上一帧到这一帧使用这个插值器
public void setInterpolator(TimeInterpolator interpolator)
实际中使用可以这样
val keyframe3 = Keyframe.ofFloat(1)
keyframe2.value = 0f
//对于keyframe而言,fraction和value这两个参数必须有的,所以不论使用哪种方法实例化Keyframe,都必须保证这两个值被初始化。
//没有使用插值器,系统默认调用线性插值器
PropertyValuesHolder除了拥有上面讲到的ofInt(),ofFloat(),ofObject(),ofKeyframe()还有其他函数
//设置动画的Evaluator
public void setEvaluator(TypeEvaluator evaluator)
//设置ofFloat()所对应的动画值列表
public void setFloatValues(float... values)
//用于设置ofInt()所对应的动画值列表
public void setIntValues(int... values)
//用于设置ofkeyframe()所对应的动画值列表
public void setKeyframes(Keyframe... values)
//用于设置ofObject()所对应的动画值列表
public void setObjectValues(Object... values)
//设置动画属性名
public void setPropertyName(String propertyName)
前面讲述的ViewAnimator、ObjectAnimator、AnimatorSet都只能针对一个控件做动画,如果我们想对ViewGroup内部控件做统一的入场动画、处场动画,如Recyclerview里面的item的添加、删除、数据更新时的动画,上面3个动都无法做到。
为了解决这个问题,android API 11 开始提供了一个简单的属性android:animateLayoutChanges = "true/false",所有的ViewGroup类控件都有这个属性,只要在xml添加了这个属性,就能实现在添加、删除viewGroup内部控件时的默认动画,但是这个动画不能自定义
上面我们体验了android给我们提供的viewGroup在增加、删除子控件时的默认动画,但是无法使用自定义动画。为了使用自定义动画,我们可以使用LayoutTransition
创建LayoutTransition示例
创建动画并进行设置
将LayoutTransition 设置到ViewGroup中
val transitioner = LayoutTransition()
val animOut = ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f)
transitioner.setAnimator(LayoutTransition.DISAPPEARING,animOut)
linearLayout.layoutTransition = transitioner
在第二步中,transitioner.setAnimator设置动画的函数声明如下
public void setAnimator(int transitionType,Animator animator)
其中transitionType表示应用当前动画的对象范围,取值如下:
LayoutTransition.CHANGE_APPEARING 和LayoutTransition.CHANGE_DISAPPEARING必须要使用PropertyValuesHolder所构建的动画才会有效果,也就是说使用ObjectAnimator构建的动画,在这里不会有效果。
//设置所有动画完成所需要的时间
public void setDuration(long duration)
//针对某个Type设置动画时长
public void setDuration(int transitionType,long duration)
//设置单个Type的插值器
public void setInterpolator(int transitonType,TimeInterpoloator interpolator)
//针对单个type设置动画延时
public void setStartDelay(int transitionType,long delay)
//针对单个Type设置每个子item动画的时间间隔
//参数一:transitionType用于指定Type类型的动画
//参数二:各个item间做动画的间隔
public void setStagger(int transitionType,long duration)
LayoutTransition 提供了一个监听函数
public void addTransitionListener(TransitionListener listener)
//其中TransitionListener
public interfaceTransitionListener{
public void startTransition(LayoutTransition transition,ViewGroup viewGroup,View view,int transitionType)
public void endTransition(LayoutTransition transition,ViewGroup viewGroup,View view,int transitionType)
}
在任何类型的LayoutTransition开始和结束时,都会调用TransitionListener的startTransition() 和 endTransition(),在transitionListener有4个参数
下一篇预告:动画实战实例























