属性动画
视图动画仅能对指定的控件做动画,而属性动画是通过改变空间的某一属性值来做动画的
补间动画虽然能够对控件做动画,但是并没有改变控件内部的属性值。
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(); textView.layout(curValue,curValue,curValue + textView.getWidth(), curValue + textView.getHeight()); } });
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
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)
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函数。
public void setAlpha(float alpha)
public void setRotation(float rotation) public void setRotationY(float rotationY) public void setRotationX(float rotationX)
public void setTranslationX(float translationX) public void setTranslationY(float translationY)
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();
|
函数声明:
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