属性动画

视图动画仅能对指定的控件做动画,而属性动画是通过改变空间的某一属性值来做动画的

补间动画虽然能够对控件做动画,但是并没有改变控件内部的属性值。

3.1简单实现

ValueAnimator valueAnimator = ValueAnimator.ofInt(0,400);
valueAnimator.setDuration(1000L);
//添加监听事件
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
int curValue = (int) animator.getAnimatedValue();
//通过更改控件left、top、right、bottom这四个点的坐标来更改坐标位置
//textView将从屏幕左上角点(0,0)运行到点(400,400)
textView.layout(curValue,curValue,curValue + textView.getWidth(), curValue + textView.getHeight());
}
});
//设置重复次数,ValueAnimator.INFINITE 表示无限循环
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
//设置重复模式 RESTART 和 REVERSE
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
//开始动画
valueAnimator.start();

3.1.1 ofInt和ofFloat

public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)

接受类型都是可变长参数,所以我们可以传入任何数量的值;传进去的值列表就表示动画时的变化范围,如ofInt(0,400,200)就表示从数字0变化到400再变化到数字200,我们传进去的数字越多,动画变化就越复杂。ofInt和ofFloat唯一的区别就是参数类型不一样。

3.1.2 常用函数

Object getAnimatedValue()
void cancel()
void pause() //暂停
void resume() //恢复

注意:重复次数为INFINITE(无限循环)的动画,当Activity结束的时候,必须调用cancel()函数取消动画,否则动画将无限循环,从而导致View无法释放,进一步导致整个Activity无法释放,引起内存泄漏

3.1.3 添加与移除监听器

1.添加监听器

在前面已经添加了一个监听器 addUpdateListener,以监听动画过程中值的实时变化,在ValueAnimator中共有三个监听器。

//监听器一:监听动画过程中值的变化
public void addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
//监听动画变化时的4个状态
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
//监听动画暂停/恢复 的状态
valueAnimator.addPauseListener(new Animator.AnimatorPauseListener() {
@Override
public void onAnimationPause(Animator animator) {
}
@Override
public void onAnimationResume(Animator animator) {
}
});

2.移除监听器

public void removePauseListener(Animator.AnimatorPauseListener listener) 
public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
public void removeAllListeners()

这部分比较简单,就不详细描述了。

3.2 示例:弹跳加载中效果

3.3 自定义插值器与Evaluator

3.3.2 示例:抛物动画

3.4 ObjectAnimator

3.4.1 简单示例

下例代码实现了将TextView透明度从0到1的变化过程。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"alpha",1.0f,0.0f,1.0f);
objectAnimator.setDuration(2000L);
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
  • 第一个参数用于指定这个动画要操作的是哪个控件
  • 第二个参数用于指定这个动画要操作这个控件的哪个属性。
  • 第三个参数时可变长按书,是指这个属性值如何变化。

我们来看看如何实现旋转效果.

ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(button,"rotation",0,270,0);

3.4.2 set函数

我们通过查看TextView的源代码可以发现,它和它的父类View都没有rotation这个属性,那么它是怎么改变这个值的呢?其实ObjectAnimator做动画,并不是根据控件XML的属性来改变的,而是通过指定属性对应的set函数来改变的。比如上面的rotation属性值,ObjectAnimator在动画时就会到指定控件中去找对应的setRotation()函数来改变控件中对应的值。在View中,有关动画共有下面几组set函数。

//1.透明度 alpha
public void setAlpha(float alpha)
//旋转度数: rotation、rotationX、rotationY
public void setRotation(float rotation) //围绕Z轴旋转
public void setRotationY(float rotationY) //围绕Y轴旋转
public void setRotationX(float rotationX) //围绕X轴旋转
//平移: translationX、translationY
public void setTranslationX(float translationX) // X轴平移
public void setTranslationY(float translationY) //Y轴平移
//缩放: scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)

3.4.3 自定义ObjectAnimator属性

同样实现小球抛物线效果

public static ObjectAnimator ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values) 

观察 ObjectAnimator的ofObject生成方法,需要实现自定义的TypeEvaluator,以及自定义propertyName属性

1.自定义ImageView

public class FallingBallImageView extends androidx.appcompat.widget.AppCompatImageView {
public FallingBallImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setFallingPos(Point point){
layout(point.x,point.y , getWidth() + point.x, + getHeight() + point.y);
}
}

这段代码中,只有一个set函数。注意点

  • 这个set函数对应的属性应该是fallingPos或者FallingPos
  • setFallingPos()函数中,参数类型时Point对象,所以我们在狗仔ObjectAnimator时,必须使用ofObject()函数。

2:布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.carl.demo.customobjectanimator.FallingBallImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/shape_circle"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button"
android:layout_marginStart="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />

</LinearLayout>

shape_circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF4949"/>
</shape>

3: 自定义TypeEvaluator

public class FallingBallEvaluator implements TypeEvaluator<Point> {
private final Point point = new Point();
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
point.x = (int) (startValue.x + fraction *(endValue.x - startValue.x));

if (fraction * 2 <= 1){
point.y = (int)(startValue.y + fraction * 2*(endValue.y - startValue.y));
}else{
point.y = endValue.y;
}
return point;
}
}

4.使用

ObjectAnimator objectAnimator = ObjectAnimator.ofObject(
ball,"fallingPos",
new FallingBallEvaluator(),
new Point(0,0),
new Point(500,500));
objectAnimator.setDuration(2000L);
objectAnimator.start();

整体过程时:当点击按钮时开始动画, ObjectAnimator.ofObject() 函数会根据 FallingBallEvaluator 实时得到当前的 Point 值;然后到 ball_imageView 控件中去找 setFallingPos(Point point) 函数,它的参数就是 FallingBallEvaluator 返回的 Point 对象 ;在找到 setFallingPos(Point point) 函数后,通过反射调用他。在 setFallingPos(Point point)函数中,我们会根据参数值实时改变圆形的位置。

3.5 组合动画

3.5.1 playSequentially()与playTogether()函数

  • playSequentially() 表示动画依次播放

函数声明:

public void playSequentially(Animator... items) 
public void playSequentially(List<Animator> items)
 ObjectAnimator colorAnimator = ObjectAnimator.ofInt(textView1,"BackgroundColor",
getColor(R.color.color1),getColor(R.color.color2),getColor(R.color.color3));
ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(textView1,"translationY",
0,400,0);
ObjectAnimator translateAnimator2 = ObjectAnimator.ofFloat(textView2,"translationY",0,400,0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000L);
animatorSet.playSequentially(colorAnimator,translateAnimator,translateAnimator2);
animatorSet.start();

布局

  • playTogether() 表示动画一起播放

函数声明:

public void playTogether(Animator... items)  
public void playTogether(Collection<Animator> items)

使用:

ObjectAnimator colorAnimator = ObjectAnimator.ofInt(textView1,"BackgroundColor",
getColor(R.color.color1),getColor(R.color.color2),getColor(R.color.color3));
ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(textView1,"translationY",
0,400,0);
translateAnimator.setStartDelay(2000L);

ObjectAnimator translateAnimator2 = ObjectAnimator.ofFloat(textView2,"translationY",0,400,0);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000L);
animatorSet.playTogether(colorAnimator,translateAnimator,translateAnimator2);
animatorSet.start();

上述代码中: 在textView1 和textView2 平移动画上加了一个延时,所以在textView1颜色变化结束后,textview1和textview2会一起移动

3.5.2 如何实现无限循环的组合动画

是否无限循环主要看动画本身,与playTogether()无关。

ObjectAnimator colorAnimator = ObjectAnimator.ofInt(textView1,"BackgroundColor",
getColor(R.color.color1),getColor(R.color.color2),getColor(R.color.color3));
colorAnimator.setRepeatCount(ValueAnimator.INFINITE);
ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(textView1,"translationY",0,400,0);
translateAnimator.setRepeatCount(ValueAnimator.INFINITE);
ObjectAnimator translateAnimator2 = ObjectAnimator.ofFloat(textView2,"translationY",0,400,0);
translateAnimator2.setRepeatCount(ValueAnimator.INFINITE);
...

3.5.3 AnimatorSet.Builder